b209a5795dec9b4755f90314cbd52c5f71944e22
[osm/openvim.git] / test / test_openvim.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4
5 ##
6 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
7 # This file is part of openvim
8 # All Rights Reserved.
9 #
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
13 #
14 # http://www.apache.org/licenses/LICENSE-2.0
15 #
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
20 # under the License.
21 #
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact with: nfvlabs@tid.es
24 ##
25
26 '''
27 This is a client tester for openvim.
28 It is almost DEPRECATED by the openvim client
29
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
35 '''
36
37 __author__="Alfonso Tierno"
38 __date__ ="$5-oct-2014 11:09:29$"
39
40 import requests
41 import json
42 import yaml
43 import sys
44 import getopt
45 from jsonschema import validate as js_v, exceptions as js_e
46
47 version="0.0.2"
48 global global_config
49
50
51 def get_elements(url):
52 headers_req = {'content-type': 'application/json'}
53 try:
54 vim_response = requests.get(url, headers = headers_req)
55 #print vim_response
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()
61 return 1, content
62 #print http_content
63 else:
64 text = " Error. VIM response '%s': not possible to GET %s" % (vim_response.status_code, url)
65 text += "\n " + vim_response.text
66 #print text
67 return -vim_response.status_code,text
68 except requests.exceptions.RequestException, e:
69 return -1, " Exception "+ str(e.message)
70
71 def delete_elements(url):
72 headers_req = {'content-type': 'application/json'}
73
74 try:
75 vim_response = requests.delete(url, headers = headers_req)
76 #print vim_response
77 #print vim_response.status_code
78 if vim_response.status_code == 200:
79 pass
80 #print vim_response.json()
81 #print json.dumps(vim_response.json(), indent=4)
82 else:
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
86 #print text
87 return -vim_response.status_code,text
88 except requests.exceptions.RequestException, e:
89 return -1, " Exception "+ str(e.message)
90 return 1, None
91
92
93 def new_elements(url, payload):
94 headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
95 #print str(payload)
96 try:
97 vim_response = requests.post(url, data=json.dumps(payload), headers=headers_req)
98 #print vim_response
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
104 else:
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
108 #print text
109 return -vim_response.status_code,text
110 except requests.exceptions.RequestException, e:
111 return -1, " Exception "+ str(e.message)
112
113
114 def get_details(url, what, c):
115 item_list = []
116 return_dict = {what+'s': []}
117
118 item = c.get(what,None)
119 if item is None: item = c.get(what+'s',None)
120 if item is 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:
125 item_list = item
126 else:
127 item_list.append(item)
128 if len(item_list)==0:
129 print what, "not found"
130 return 1
131 for item in item_list:
132 uuid = item.get('id',None)
133 if uuid is None: uuid = item.get('uuid',None)
134 if uuid is 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)
140 if r<0:
141 # print "fail"
142 print " get", what, uuid, "fail", c
143 return -1, c
144 #else:
145 # print 'ok'
146 return_dict[what+'s'].append(c[what])
147 return 1, return_dict
148
149
150 def action_details(url, what, c, force, payload):
151 item_list = []
152 return_dict = {what+'s': []}
153 headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
154 fail=0
155 ok=0
156
157 #Allows for payload both keypairs inside a 'server','port' ... or directly. In later case, put keypairs inside what
158
159 item = c.get(what,None)
160 if item is None: item = c.get(what+'s',None)
161 if item is 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:
166 item_list = item
167 else:
168 item_list.append(item)
169 if len(item_list)==0:
170 print what, "not found"
171 return 1
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)
176 if uuid is None:
177 error_text= " Internal error, not found 'id/uuid' in item"
178 print 'get_details()', error_text, item
179 return -1, error_text
180 if not force:
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, " >>>>>>>> ",
184 else:
185 continue
186
187 #print str(payload)
188 try:
189 vim_response = requests.post(url + "/" + uuid + "/action", data=json.dumps(payload), headers=headers_req)
190 if vim_response.status_code == 200:
191 print 'ok'
192 ok += 1
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
196 else:
197 fail += 1
198 print "fail"
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
202 #print 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
212
213
214
215 def edit_details(url, what, c, force, payload):
216 item_list = []
217 return_dict = {what+'s': []}
218 headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
219 fail=0
220 ok=0
221
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}
225
226 item = c.get(what,None)
227 if item is None: item = c.get(what+'s',None)
228 if item is 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:
233 item_list = item
234 else:
235 item_list.append(item)
236 if len(item_list)==0:
237 print what, "not found"
238 return 1
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)
243 if uuid is None:
244 error_text= " Internal error, not found 'id/uuid' in item"
245 print 'get_details()', error_text, item
246 return -1, error_text
247 if not force:
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, " >>>>>>>> ",
251 else:
252 continue
253
254 #print str(payload)
255 try:
256 vim_response = requests.put(url + "/" + uuid, data=json.dumps(payload), headers=headers_req)
257 if vim_response.status_code == 200:
258 print 'ok'
259 ok += 1
260 return_dict[what+'s'].append( vim_response.json()[what] )
261 else:
262 fail += 1
263 print "fail"
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
267 #print 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
277
278 def get_del_recursive(url, what, url_suffix, force=False, recursive=False):
279 #print
280 #print " get", what, a, " >>>>>>>> ",
281 r,c = get_elements(url + what + 's' + url_suffix)
282 if r<0:
283 print c, "when getting", what, url_suffix
284 return -1
285 # print "ok"
286
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, )
294
295 if len(list_todelete)==0:
296 print what, url_suffix, "not found"
297 return 1
298 for c in list_todelete:
299 uuid=c.get('id', None)
300 if uuid is None:
301 uuid=c.get('uuid', None)
302 if uuid is None:
303 print "Id not found"
304 continue
305 name = c.get("name","")
306 if recursive:
307 if what=='tenant' :
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)
314 pass
315 elif what=='image' :
316 get_del_recursive(url, 'server', "?imageRef="+uuid, force, recursive)
317 elif what=='hosts' :
318 get_del_recursive(url, 'server', "?hostId="+uuid, force, recursive)
319
320 if not force:
321 r = raw_input("Delete " + what + " " + uuid + " " + name + " (y/N)? ")
322 if len(r)>0 and r[0].lower()=="y":
323 pass
324 else:
325 continue
326 r,c = delete_elements(url + what + "s/" + uuid)
327 if r<0:
328 #print "Error deleting", vimURI, -r
329 print c
330 else:
331 print what, uuid, name, "deleted"
332 return 1
333
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}$"}
336 try:
337 js_v(uuid, id_schema)
338 return True
339 except js_e.ValidationError:
340 return False
341
342 def change_string(text, var_list):
343 end=0
344 type_=None
345 while True:
346 ini = text.find("${", end)
347 if ini<0: return text
348 end = text.find("}", ini)
349 if end<0: return text
350 end+=1
351
352 var = text[ini:end]
353 if ' ' in var:
354 kk=var.split(" ")
355 var=kk[0]+"}"
356 type_=kk[-1][:-1]
357 var = var_list.get(var, None)
358 if var==None: return text
359
360 text = text[:ini] + var + text[end:]
361 if type_ != None:
362 if 'null' in type_ and text=="null":
363 return None
364 if 'int' in type_ : #and text.isnumeric():
365 return int(text)
366 return text
367
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,
371 Attributes:
372 'data': dictionary, or list. None or empty is consideted valid
373 'var_list': dictionary (name:change) pairs
374 Return:
375 None, data is modified
376 '''
377
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)
390
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:
395 return 0, data
396
397 var_list={}
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
403 if "${" in var[r:]:
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]
409 else: default=None
410 v = raw_input(var[r:] + "? ")
411 if v=="":
412 if default != None:
413 v = default
414 else:
415 v = raw_input(" empty string? try again: ")
416 var_list[ var[:r] ] = str(v)
417
418 del data["${}"]
419 chage_var_recursively(data, var_list)
420 return 0, data
421
422 def parse_yaml_json(text):
423 try:
424 data = yaml.load(text)
425 return 0, data
426 except yaml.YAMLError, exc:
427 error_pos = ""
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
432
433 def load_file(file_, parse=False):
434 try:
435 f = open(file_, 'r')
436 read_data = f.read()
437 f.close()
438 if not parse:
439 return 0, read_data
440 except IOError, e:
441 return -1, " Error opening file '" + file_ + "': " + e.args[1]
442
443 try:
444 data = yaml.load(read_data)
445 return change_var(data)
446 except yaml.YAMLError, exc:
447 error_pos = ""
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
452
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}
455
456 r, config = load_file(configuration_file, parse=True)
457 if r < 0:
458 return False, config
459
460 #Check default values tokens
461 for k,v in default_tokens.items():
462 if k not in config: config[k]=v
463
464 return (True, config)
465
466 items_list = ('server','host','tenant','image','flavor','network','port')
467 action_list = ('list','get','new','del','edit','action')
468
469
470 def usage(complete=False):
471 global items_list
472 global action_list
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'"
478 if not complete:
479 print " Type -h or --help for a complete list of options"
480 return
481 print " 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; ..."
490 print " Examples:"
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"
498 return
499
500
501 if __name__=="__main__":
502 global vimURI
503 global vimURI_admin
504
505 global what
506 global query_string
507 #init variables
508 action="list"
509 what=None
510 url=None
511 query_string = ""
512 force = False
513 recursive = False
514 tenant = None
515 additional = []
516 #look for parent dir
517 config_file = '../openvimd.cfg'
518 pos = sys.argv[0].rfind("/")
519 if pos<0:
520 base_dir="./"
521 else:
522 base_dir = sys.argv[0] [:pos+1]
523 if pos>=0:
524 config_file = base_dir + config_file
525
526 #get params
527 try:
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"
532 usage()
533 sys.exit(-2)
534
535 for o, a in opts:
536 if o in ("-v", "--version"):
537 print "test_openvim version", version, "Oct 2014"
538 print "(c) Copyright Telefonica"
539 sys.exit(0)
540 elif o in ("-h", "--help"):
541 usage(True)
542 sys.exit(0)
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
549 else:
550 assert False, "Unhandled option"
551
552 for a in args:
553 if len(a) == 0:
554 print " Warning!!! Found an empty parameter?"
555 elif a[0]=="-":
556 print " Error!!! Put options parameter at the beginning"
557 sys.exit(-2)
558 elif what is not None:
559 additional.append(a)
560 elif a in items_list:
561 what=a
562 elif a[:-1] in items_list and a[-1]=='s':
563 what=a[:-1]
564 elif a in action_list:
565 action=a
566 else:
567 print " Missing <item>", ",".join(items_list)
568 sys.exit(-2)
569 if what is None:
570 usage()
571 sys.exit(-1)
572 #Load configuration file
573 r, config_dic = load_configuration(config_file)
574 #print config_dic
575 if not r:
576 print config_dic
577 config_dic={}
578 #exit(-1)
579
580 #override parameters obtained by command line
581 try:
582 if url is not None:
583 vimURI = vimURI_admin = url
584 else:
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/"
588 except: #key error
589 print " Error: can not get URL; neither option --u,-url, nor reading configuration file"
590 exit(-1)
591 if tenant is None:
592 tenant = config_dic.get('tenant_id', None)
593
594 #check enough parameters
595 URI=vimURI
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"
599 exit(-1)
600 else:
601 URI=vimURI_admin
602 if URI[-1] != "/": URI+="/"
603 if what in ('server','image','flavor'):
604 if tenant is None:
605 print " Error: Can not get tenant; neither option -t,--tenant, nor reading configuration file"
606 exit(-1)
607 URI += tenant + "/"
608
609
610 exit_code=0
611 try:
612 #load file for new/edit
613 payload_list=[]
614 if action=='new' or action=='edit' or action=='action':
615 if len(additional)==0:
616 if action=='new' :
617 additional.append(base_dir+what+"s/new_"+what+".yaml")
618 #print " New what? Missing additional parameters to complete action"
619 else:
620 print " What must be edited? Missing additional parameters to complete action"
621 exit(-1)
622 if action=='edit'or action=='action':
623 #obtain only last element
624 additional_temp = additional[:-1]
625 additional = additional[-1:]
626
627 for a in additional:
628 r,payload = load_file(a, parse=True)
629 if r<0:
630 if r==-1 and "{" in a or ":" in a:
631 #try to parse directly
632 r,payload = parse_yaml_json(a)
633 if r<0:
634 print payload
635 exit (-1)
636 else:
637 print payload
638 exit (-1)
639 payload_list.append(payload)
640 if action=='edit'or action=='action':
641 additional = additional_temp
642
643
644 #perform actions NEW
645 if action=='new':
646 for payload in payload_list:
647 print "\n new", what, a, " >>>>>>>> ",
648 r,c = new_elements(URI+what+'s', payload)
649 if r>0:
650 print "ok"
651 else:
652 print "fail"
653 exit_code = -1
654 print c
655 #try to decode
656 exit(exit_code)
657
658 #perform actions GET LIST EDIT DEL
659 if len(additional)==0:
660 additional=[""]
661 for a in additional:
662 filter_qs = query_string
663 if a != "" :
664 if check_valid_uuid(a):
665 if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(a)
666 else: filter_qs += "?" + "id=" + str(a)
667 else:
668 if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(a)
669 else: filter_qs += "?" + "name=" + str(a)
670
671 if action=='list' or action=='get' or action=='edit'or action=='action':
672 url = URI + what+'s'
673 print url + filter_qs
674 #print " get", what, a, " >>>>>>>> ",
675 r,c = get_elements(url + filter_qs)
676 if r<0:
677 #print "fail"
678 exit_code = -1
679 print c
680 else:
681 #print "ok"
682 if action=='list':
683 print json.dumps(c, indent=4)
684 continue
685
686 if action=='get':
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])
692 if r1<0:
693 exit_code = -1
694 else:
695 if r>0: print "ok"
696 else: print "ok with some fails"
697 print json.dumps(c1, indent=4)
698
699 elif action=='del':
700 r = get_del_recursive(URI, what, filter_qs, force, recursive)
701 if r<0:
702 exit_code = -1
703 exit(exit_code)
704
705 except KeyboardInterrupt:
706 print " Canceled"
707
708