2 # -*- coding: utf-8 -*-
6 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
7 # This file is part of openvim
10 # Licensed under the Apache License, Version 2.0 (the "License"); you may
11 # not use this file except in compliance with the License. You may obtain
12 # a copy of the License at
14 # http://www.apache.org/licenses/LICENSE-2.0
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19 # License for the specific language governing permissions and limitations
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact with: nfvlabs@tid.es
27 This is a client tester for openvim.
28 It is almost DEPRECATED by the openvim client
30 The reason for keeping is because it is used for some scripts
31 and it contain the -r option (delete recursive)
32 that it is very useful for deleting content of database.
33 Another difference from openvim is that it is more verbose
34 and so more suitable for the developers
37 __author__
="Alfonso Tierno"
38 __date__
="$5-oct-2014 11:09:29$"
45 from jsonschema
import validate
as js_v
, exceptions
as js_e
51 def get_elements(url
):
52 headers_req
= {'content-type': 'application/json'}
54 vim_response
= requests
.get(url
, headers
= headers_req
)
56 #print vim_response.status_code
57 if vim_response
.status_code
== 200:
58 #print vim_response.json()
59 #print json.dumps(vim_response.json(), indent=4)
60 content
= vim_response
.json()
64 text
= " Error. VIM response '%s': not possible to GET %s" % (vim_response
.status_code
, url
)
65 text
+= "\n " + vim_response
.text
67 return -vim_response
.status_code
,text
68 except requests
.exceptions
.RequestException
, e
:
69 return -1, " Exception "+ str(e
.message
)
71 def delete_elements(url
):
72 headers_req
= {'content-type': 'application/json'}
75 vim_response
= requests
.delete(url
, headers
= headers_req
)
77 #print vim_response.status_code
78 if vim_response
.status_code
== 200:
80 #print vim_response.json()
81 #print json.dumps(vim_response.json(), indent=4)
83 #print vim_response.text
84 text
= " Error. VIM response '%s': not possible to DELETE %s" % (vim_response
.status_code
, url
)
85 text
+= "\n " + vim_response
.text
87 return -vim_response
.status_code
,text
88 except requests
.exceptions
.RequestException
, e
:
89 return -1, " Exception "+ str(e
.message
)
93 def new_elements(url
, payload
):
94 headers_req
= {'Accept': 'application/json', 'content-type': 'application/json'}
97 vim_response
= requests
.post(url
, data
=json
.dumps(payload
), headers
=headers_req
)
99 #print vim_response.status_code
100 if vim_response
.status_code
== 200:
101 #print vim_response.json()
102 #print json.dumps(vim_response.json(), indent=4)
103 return 1, vim_response
.text
105 #print vim_response.text
106 text
= "Error. VIM response '%s': not possible to ADD %s" % (vim_response
.status_code
, url
)
107 text
+= "\n" + vim_response
.text
109 return -vim_response
.status_code
,text
110 except requests
.exceptions
.RequestException
, e
:
111 return -1, " Exception "+ str(e
.message
)
114 def get_details(url
, what
, c
):
116 return_dict
= {what
+'s': []}
118 item
= c
.get(what
,None)
119 if item
is None: item
= c
.get(what
+'s',None)
121 error_text
= " Internal error, not found '" + what
+"[s]' in content"
122 print 'get_details()', error_text
, c
123 return -1, error_text
124 if type(item
) is list:
127 item_list
.append(item
)
128 if len(item_list
)==0:
129 print what
, "not found"
131 for item
in item_list
:
132 uuid
= item
.get('id',None)
133 if uuid
is None: uuid
= item
.get('uuid',None)
135 error_text
= " Internal error, not found 'id/uuid' in item"
136 print 'get_details()', error_text
, item
137 return -1, error_text
138 #print " get", what, uuid, " >>>>>>>> ",
139 r
,c
= get_elements(url
+ "/" + uuid
)
142 print " get", what
, uuid
, "fail", c
146 return_dict
[what
+'s'].append(c
[what
])
147 return 1, return_dict
150 def action_details(url
, what
, c
, force
, payload
):
152 return_dict
= {what
+'s': []}
153 headers_req
= {'Accept': 'application/json', 'content-type': 'application/json'}
157 #Allows for payload both keypairs inside a 'server','port' ... or directly. In later case, put keypairs inside what
159 item
= c
.get(what
,None)
160 if item
is None: item
= c
.get(what
+'s',None)
162 error_text
= " Internal error, not found '" + what
+"[s]' in content"
163 print 'get_details()', error_text
, c
164 return -1, error_text
165 if type(item
) is list:
168 item_list
.append(item
)
169 if len(item_list
)==0:
170 print what
, "not found"
172 for item
in item_list
:
173 name
= item
.get('name',None)
174 uuid
= item
.get('id',None)
175 if uuid
is None: uuid
= item
.get('uuid',None)
177 error_text
= " Internal error, not found 'id/uuid' in item"
178 print 'get_details()', error_text
, item
179 return -1, error_text
181 r
= raw_input("Action on " + what
+ " " + uuid
+ " " + name
+ " (y/N)? ")
182 if len(r
)>0 and r
[0].lower()=="y":
183 print " put", what
, uuid
, " >>>>>>>> ",
189 vim_response
= requests
.post(url
+ "/" + uuid
+ "/action", data
=json
.dumps(payload
), headers
=headers_req
)
190 if vim_response
.status_code
== 200:
193 return_dict
[what
+'s'].append(vim_response
.json())
194 return_dict
[what
+'s'][-1]['uuid'] = uuid
195 return_dict
[what
+'s'][-1]['name'] = name
199 #print vim_response.text
200 #text = "Error. VIM response '%s': not possible to PUT %s" % (vim_response.status_code, url)
201 #text += "\n" + vim_response.text
203 error_dict
= vim_response
.json()
204 error_dict
['error']['uuid']=uuid
205 error_dict
['error']['name']=name
206 return_dict
[what
+'s'].append(error_dict
)
207 except requests
.exceptions
.RequestException
, e
:
208 return -1, " Exception "+ str(e
.message
)
209 if ok
>0 and fail
>0: return 0, return_dict
210 elif fail
==0 : return 1, return_dict
211 else: return -1, return_dict
215 def edit_details(url
, what
, c
, force
, payload
):
217 return_dict
= {what
+'s': []}
218 headers_req
= {'Accept': 'application/json', 'content-type': 'application/json'}
222 #Allows for payload both keypairs inside a 'server','port' ... or directly. In later case, put keypairs inside what
223 if what
not in payload
:
224 payload
= {what
:payload
}
226 item
= c
.get(what
,None)
227 if item
is None: item
= c
.get(what
+'s',None)
229 error_text
= " Internal error, not found '" + what
+"[s]' in content"
230 print 'get_details()', error_text
, c
231 return -1, error_text
232 if type(item
) is list:
235 item_list
.append(item
)
236 if len(item_list
)==0:
237 print what
, "not found"
239 for item
in item_list
:
240 name
= item
.get('name',None)
241 uuid
= item
.get('id',None)
242 if uuid
is None: uuid
= item
.get('uuid',None)
244 error_text
= " Internal error, not found 'id/uuid' in item"
245 print 'get_details()', error_text
, item
246 return -1, error_text
248 r
= raw_input("Edit " + what
+ " " + uuid
+ " " + name
+ " (y/N)? ")
249 if len(r
)>0 and r
[0].lower()=="y":
250 print " put", what
, uuid
, " >>>>>>>> ",
256 vim_response
= requests
.put(url
+ "/" + uuid
, data
=json
.dumps(payload
), headers
=headers_req
)
257 if vim_response
.status_code
== 200:
260 return_dict
[what
+'s'].append( vim_response
.json()[what
] )
264 #print vim_response.text
265 #text = "Error. VIM response '%s': not possible to PUT %s" % (vim_response.status_code, url)
266 #text += "\n" + vim_response.text
268 error_dict
= vim_response
.json()
269 error_dict
['error']['uuid']=uuid
270 error_dict
['error']['name']=name
271 return_dict
[what
+'s'].append(error_dict
)
272 except requests
.exceptions
.RequestException
, e
:
273 return -1, " Exception "+ str(e
.message
)
274 if ok
>0 and fail
>0: return 0, return_dict
275 elif fail
==0 : return 1, return_dict
276 else: return -1, return_dict
278 def get_del_recursive(url
, what
, url_suffix
, force
=False, recursive
=False):
280 #print " get", what, a, " >>>>>>>> ",
281 r
,c
= get_elements(url
+ what
+ 's' + url_suffix
)
283 print c
, "when getting", what
, url_suffix
287 list_todelete
= c
.get(what
, None)
288 if list_todelete
is None: list_todelete
= c
.get(what
+'s', None)
289 if list_todelete
is None:
290 print " Internal error, not found '" + what
+"[s]' in", c
291 return -3, " Internal error, not found a valid dictionary"
292 if type(list_todelete
) == dict:
293 list_todelete
= (list_todelete
, )
295 if len(list_todelete
)==0:
296 print what
, url_suffix
, "not found"
298 for c
in list_todelete
:
299 uuid
=c
.get('id', None)
301 uuid
=c
.get('uuid', None)
305 name
= c
.get("name","")
308 get_del_recursive(url
+ uuid
+ "/", 'server', "", force
, recursive
)
309 get_del_recursive(url
+ uuid
+ "/", 'flavor', "", force
, recursive
)
310 get_del_recursive(url
+ uuid
+ "/", 'image', "", force
, recursive
)
311 get_del_recursive(url
, 'network', "?tenant_id="+uuid
, force
, recursive
)
312 elif what
=='flavors' :
313 #get_del_recursive(url, 'servers', "?flavorRef="+uuid, force, recursive)
316 get_del_recursive(url
, 'server', "?imageRef="+uuid
, force
, recursive
)
318 get_del_recursive(url
, 'server', "?hostId="+uuid
, force
, recursive
)
321 r
= raw_input("Delete " + what
+ " " + uuid
+ " " + name
+ " (y/N)? ")
322 if len(r
)>0 and r
[0].lower()=="y":
326 r
,c
= delete_elements(url
+ what
+ "s/" + uuid
)
328 #print "Error deleting", vimURI, -r
331 print what
, uuid
, name
, "deleted"
334 def check_valid_uuid(uuid
):
335 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
337 js_v(uuid
, id_schema
)
339 except js_e
.ValidationError
:
342 def change_string(text
, var_list
):
346 ini
= text
.find("${", end
)
347 if ini
<0: return text
348 end
= text
.find("}", ini
)
349 if end
<0: return text
357 var
= var_list
.get(var
, None)
358 if var
==None: return text
360 text
= text
[:ini
] + var
+ text
[end
:]
362 if 'null' in type_
and text
=="null":
364 if 'int' in type_
: #and text.isnumeric():
368 def chage_var_recursively(data
, var_list
):
369 '''Check recursively the conent of data, and look for "*${*}*" variables and changes
370 It assumes that this variables are not in the key of dictionary,
372 'data': dictionary, or list. None or empty is consideted valid
373 'var_list': dictionary (name:change) pairs
375 None, data is modified
378 if type(data
) is dict:
379 for k
in data
.keys():
380 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
381 chage_var_recursively(data
[k
], var_list
)
382 elif type(data
[k
]) is str:
383 data
[k
] = change_string(data
[k
], var_list
)
384 if type(data
) is list:
385 for k
in range(0,len(data
)):
386 if type(data
[k
]) is dict or type(data
[k
]) is list:
387 chage_var_recursively(data
[k
], var_list
)
388 elif type(data
[k
]) is str:
389 data
[k
] = change_string(data
[k
], var_list
)
391 def change_var(data
):
392 if type(data
) is not dict:
393 return -1, "Format error, not a object (dictionary)"
394 if "${}" not in data
:
398 for var
in data
["${}"]:
399 r
= var
.find("}",) + 1
400 if r
<=2 or var
[:2] != '${':
401 return -1, "Format error at '${}':" + var
402 #change variables inside description text
404 var
= var
[:r
] + change_string(var
[r
:], var_list
)
405 d_start
= var
.rfind("(",) + 1
406 d_end
= var
.rfind(")",)
407 if d_start
>0 and d_end
>=d_start
:
408 default
= var
[d_start
:d_end
]
410 v
= raw_input(var
[r
:] + "? ")
415 v
= raw_input(" empty string? try again: ")
416 var_list
[ var
[:r
] ] = str(v
)
419 chage_var_recursively(data
, var_list
)
422 def parse_yaml_json(text
):
424 data
= yaml
.load(text
)
426 except yaml
.YAMLError
, exc
:
428 if hasattr(exc
, 'problem_mark'):
429 mark
= exc
.problem_mark
430 error_pos
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
431 return -1, " Error yaml/json format error at " + error_pos
433 def load_file(file_
, parse
=False):
441 return -1, " Error opening file '" + file_
+ "': " + e
.args
[1]
444 data
= yaml
.load(read_data
)
445 return change_var(data
)
446 except yaml
.YAMLError
, exc
:
448 if hasattr(exc
, 'problem_mark'):
449 mark
= exc
.problem_mark
450 error_pos
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
451 return -2, " Error yaml/json format error at '"+ file_
+"'"+error_pos
453 def load_configuration(configuration_file
):
454 default_tokens
={'http_port':8080, 'http_host':'localhost', 'test_mode':False, 'of_controller_nets_with_same_vlan':True}
456 r
, config
= load_file(configuration_file
, parse
=True)
460 #Check default values tokens
461 for k
,v
in default_tokens
.items():
462 if k
not in config
: config
[k
]=v
464 return (True, config
)
466 items_list
= ('server','host','tenant','image','flavor','network','port')
467 action_list
= ('list','get','new','del','edit','action')
470 def usage(complete
=False):
473 print "Usage: ", sys
.argv
[0], "[options]", " [" + ",".join(action_list
) +"] ", "<item> [<other>] "
474 print " Perform an test action over openvim"
475 print " "+",".join(action_list
)+": List (by default), GET detais, Creates, Deletes, Edit"
476 print " <item>: can be one of " + ",".join(items_list
)
477 print " <other>: list of uuid|name for 'get|del'; list of json/yaml files for 'new' or 'edit'"
479 print " Type -h or --help for a complete list of options"
482 print " -v|--version: prints current version"
483 print " -c|--config [configuration_file]: loads the configuration file (default: openvimd.cfg)"
484 print " -h|--help: shows this help"
485 print " -u|--url [URL]: url to use instead of the one loaded from configuration file"
486 print " -t|--tenant [tenant uuid]: tenant to be used for some comands. IF mising it will use the default obtained in configuration file"
487 print " -F|--filter [A=B[&C=D...]: URL query string used for 'get' or 'del' commands"
488 print " -f|--force : Do not ask for confirmation when deleting. Also remove dependent objects."
489 print " -r|--recursive : Delete also dependency elements, (from tenants: images, flavors,server; from hosts: instances; ..."
491 print " ",sys
.argv
[0]," tenant #list tenants "
492 print " ",sys
.argv
[0]," -F'device_owner=external' get port #get details of all external ports"
493 print " ",sys
.argv
[0]," del server ses pan #delete server names 'ses' and 'pan'. Do not ask for confirmation"
494 print " ",sys
.argv
[0]," -r -f del host #delete all host and all the dependencies "
495 print " ",sys
.argv
[0]," new host ./Host/nfv100.json #add a host which information is in this file"
496 print " ",sys
.argv
[0]," edit network f348faf8-59ef-11e4-b4c7-52540030594e '{\"network\":{\"admin_state_up\":false}}'"
497 print " #change the admin status of this network"
501 if __name__
=="__main__":
517 config_file
= '../openvimd.cfg'
518 pos
= sys
.argv
[0].rfind("/")
522 base_dir
= sys
.argv
[0] [:pos
+1]
524 config_file
= base_dir
+ config_file
528 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hvrfc:u:t:F:",
529 ["config", "help", "version", "force", "filter","tenant","url","recursive"])
530 except getopt
.GetoptError
, err
:
531 print " Error:", err
# will print something like "option -a not recognized"
536 if o
in ("-v", "--version"):
537 print "test_openvim version", version
, "Oct 2014"
538 print "(c) Copyright Telefonica"
540 elif o
in ("-h", "--help"):
543 elif o
in ("-c", "--config"): config_file
= a
544 elif o
in ("-f", "--force"): force
= True
545 elif o
in ("-r", "--recursive"): recursive
= True
546 elif o
in ("-F", "--filter"): query_string
= "?"+a
547 elif o
in ("-u", "--url"): url
= a
548 elif o
in ("-t", "--tenant"): tenant
= a
550 assert False, "Unhandled option"
554 print " Warning!!! Found an empty parameter?"
556 print " Error!!! Put options parameter at the beginning"
558 elif what
is not None:
560 elif a
in items_list
:
562 elif a
[:-1] in items_list
and a
[-1]=='s':
564 elif a
in action_list
:
567 print " Missing <item>", ",".join(items_list
)
572 #Load configuration file
573 r
, config_dic
= load_configuration(config_file
)
580 #override parameters obtained by command line
583 vimURI
= vimURI_admin
= url
585 vimURI
= "http://" + config_dic
['http_host'] +":"+ str(config_dic
['http_port']) + "/openvim/"
586 if 'http_admin_port' in config_dic
:
587 vimURI_admin
= "http://" + config_dic
['http_host'] +":"+ str(config_dic
['http_admin_port']) + "/openvim/"
589 print " Error: can not get URL; neither option --u,-url, nor reading configuration file"
592 tenant
= config_dic
.get('tenant_id', None)
594 #check enough parameters
596 if (what
in ('host','port') and action
in ('del','new')) or (what
=='host' and action
=='edit' ):
597 if vimURI_admin
is None:
598 print " Error: Can not get admin URL; neither option -t,--tenant, nor reading configuration file"
602 if URI
[-1] != "/": URI
+="/"
603 if what
in ('server','image','flavor'):
605 print " Error: Can not get tenant; neither option -t,--tenant, nor reading configuration file"
612 #load file for new/edit
614 if action
=='new' or action
=='edit' or action
=='action':
615 if len(additional
)==0:
617 additional
.append(base_dir
+what
+"s/new_"+what
+".yaml")
618 #print " New what? Missing additional parameters to complete action"
620 print " What must be edited? Missing additional parameters to complete action"
622 if action
=='edit'or action
=='action':
623 #obtain only last element
624 additional_temp
= additional
[:-1]
625 additional
= additional
[-1:]
628 r
,payload
= load_file(a
, parse
=True)
630 if r
==-1 and "{" in a
or ":" in a
:
631 #try to parse directly
632 r
,payload
= parse_yaml_json(a
)
639 payload_list
.append(payload
)
640 if action
=='edit'or action
=='action':
641 additional
= additional_temp
646 for payload
in payload_list
:
647 print "\n new", what
, a
, " >>>>>>>> ",
648 r
,c
= new_elements(URI
+what
+'s', payload
)
658 #perform actions GET LIST EDIT DEL
659 if len(additional
)==0:
662 filter_qs
= query_string
664 if check_valid_uuid(a
):
665 if len(filter_qs
) > 0: filter_qs
+= "&" + "id=" + str(a
)
666 else: filter_qs
+= "?" + "id=" + str(a
)
668 if len(filter_qs
) > 0: filter_qs
+= "&" + "name=" + str(a
)
669 else: filter_qs
+= "?" + "name=" + str(a
)
671 if action
=='list' or action
=='get' or action
=='edit'or action
=='action':
673 print url
+ filter_qs
674 #print " get", what, a, " >>>>>>>> ",
675 r
,c
= get_elements(url
+ filter_qs
)
683 print json
.dumps(c
, indent
=4)
687 r1
,c1
= get_details(url
, what
, c
)
688 elif action
=='action':
689 r1
,c1
= action_details(url
, what
, c
, force
, payload_list
[0])
690 else: # action=='edit':
691 r1
,c1
= edit_details(url
, what
, c
, force
, payload_list
[0])
696 else: print "ok with some fails"
697 print json
.dumps(c1
, indent
=4)
700 r
= get_del_recursive(URI
, what
, filter_qs
, force
, recursive
)
705 except KeyboardInterrupt: