| #!/usr/bin/env python |
| # -*- coding: utf-8 -*- |
| |
| |
| ## |
| # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. |
| # This file is part of openmano |
| # All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. You may obtain |
| # a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations |
| # under the License. |
| # |
| # For those usages not covered by the Apache License, Version 2.0 please |
| # contact with: nfvlabs@tid.es |
| ## |
| |
| ''' |
| This is a client tester for openvim. |
| It is almost DEPRECATED by the openvim client |
| |
| The reason for keeping is because it is used for some scripts |
| and it contain the -r option (delete recursive) |
| that it is very useful for deleting content of database. |
| Another difference from openvim is that it is more verbose |
| and so more suitable for the developers |
| ''' |
| |
| __author__="Alfonso Tierno" |
| __date__ ="$5-oct-2014 11:09:29$" |
| |
| import requests |
| import json |
| import yaml |
| import sys |
| import getopt |
| from jsonschema import validate as js_v, exceptions as js_e |
| |
| version="0.0.2" |
| global global_config |
| |
| |
| def get_elements(url): |
| headers_req = {'content-type': 'application/json'} |
| try: |
| vim_response = requests.get(url, headers = headers_req) |
| #print vim_response |
| #print vim_response.status_code |
| if vim_response.status_code == 200: |
| #print vim_response.json() |
| #print json.dumps(vim_response.json(), indent=4) |
| content = vim_response.json() |
| return 1, content |
| #print http_content |
| else: |
| text = " Error. VIM response '%s': not possible to GET %s" % (vim_response.status_code, url) |
| text += "\n " + vim_response.text |
| #print text |
| return -vim_response.status_code,text |
| except requests.exceptions.RequestException, e: |
| return -1, " Exception "+ str(e.message) |
| |
| def delete_elements(url): |
| headers_req = {'content-type': 'application/json'} |
| |
| try: |
| vim_response = requests.delete(url, headers = headers_req) |
| #print vim_response |
| #print vim_response.status_code |
| if vim_response.status_code == 200: |
| pass |
| #print vim_response.json() |
| #print json.dumps(vim_response.json(), indent=4) |
| else: |
| #print vim_response.text |
| text = " Error. VIM response '%s': not possible to DELETE %s" % (vim_response.status_code, url) |
| text += "\n " + vim_response.text |
| #print text |
| return -vim_response.status_code,text |
| except requests.exceptions.RequestException, e: |
| return -1, " Exception "+ str(e.message) |
| return 1, None |
| |
| |
| def new_elements(url, payload): |
| headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} |
| #print str(payload) |
| try: |
| vim_response = requests.post(url, data=json.dumps(payload), headers=headers_req) |
| #print vim_response |
| #print vim_response.status_code |
| if vim_response.status_code == 200: |
| #print vim_response.json() |
| #print json.dumps(vim_response.json(), indent=4) |
| return 1, vim_response.text |
| else: |
| #print vim_response.text |
| text = "Error. VIM response '%s': not possible to ADD %s" % (vim_response.status_code, url) |
| text += "\n" + vim_response.text |
| #print text |
| return -vim_response.status_code,text |
| except requests.exceptions.RequestException, e: |
| return -1, " Exception "+ str(e.message) |
| |
| |
| def get_details(url, what, c): |
| item_list = [] |
| return_dict = {what+'s': []} |
| |
| item = c.get(what,None) |
| if item is None: item = c.get(what+'s',None) |
| if item is None: |
| error_text= " Internal error, not found '" + what +"[s]' in content" |
| print 'get_details()', error_text, c |
| return -1, error_text |
| if type(item) is list: |
| item_list = item |
| else: |
| item_list.append(item) |
| if len(item_list)==0: |
| print what, "not found" |
| return 1 |
| for item in item_list: |
| uuid = item.get('id',None) |
| if uuid is None: uuid = item.get('uuid',None) |
| if uuid is None: |
| error_text= " Internal error, not found 'id/uuid' in item" |
| print 'get_details()', error_text, item |
| return -1, error_text |
| #print " get", what, uuid, " >>>>>>>> ", |
| r,c = get_elements(url + "/" + uuid) |
| if r<0: |
| # print "fail" |
| print " get", what, uuid, "fail", c |
| return -1, c |
| #else: |
| # print 'ok' |
| return_dict[what+'s'].append(c[what]) |
| return 1, return_dict |
| |
| |
| def action_details(url, what, c, force, payload): |
| item_list = [] |
| return_dict = {what+'s': []} |
| headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} |
| fail=0 |
| ok=0 |
| |
| #Allows for payload both keypairs inside a 'server','port' ... or directly. In later case, put keypairs inside what |
| |
| item = c.get(what,None) |
| if item is None: item = c.get(what+'s',None) |
| if item is None: |
| error_text= " Internal error, not found '" + what +"[s]' in content" |
| print 'get_details()', error_text, c |
| return -1, error_text |
| if type(item) is list: |
| item_list = item |
| else: |
| item_list.append(item) |
| if len(item_list)==0: |
| print what, "not found" |
| return 1 |
| for item in item_list: |
| name = item.get('name',None) |
| uuid = item.get('id',None) |
| if uuid is None: uuid = item.get('uuid',None) |
| if uuid is None: |
| error_text= " Internal error, not found 'id/uuid' in item" |
| print 'get_details()', error_text, item |
| return -1, error_text |
| if not force: |
| r = raw_input("Action on " + what + " " + uuid + " " + name + " (y/N)? ") |
| if len(r)>0 and r[0].lower()=="y": |
| print " put", what, uuid, " >>>>>>>> ", |
| else: |
| continue |
| |
| #print str(payload) |
| try: |
| vim_response = requests.post(url + "/" + uuid + "/action", data=json.dumps(payload), headers=headers_req) |
| if vim_response.status_code == 200: |
| print 'ok' |
| ok += 1 |
| return_dict[what+'s'].append(vim_response.json()) |
| return_dict[what+'s'][-1]['uuid'] = uuid |
| return_dict[what+'s'][-1]['name'] = name |
| else: |
| fail += 1 |
| print "fail" |
| #print vim_response.text |
| #text = "Error. VIM response '%s': not possible to PUT %s" % (vim_response.status_code, url) |
| #text += "\n" + vim_response.text |
| #print text |
| error_dict = vim_response.json() |
| error_dict['error']['uuid']=uuid |
| error_dict['error']['name']=name |
| return_dict[what+'s'].append(error_dict) |
| except requests.exceptions.RequestException, e: |
| return -1, " Exception "+ str(e.message) |
| if ok>0 and fail>0: return 0, return_dict |
| elif fail==0 : return 1, return_dict |
| else: return -1, return_dict |
| |
| |
| |
| def edit_details(url, what, c, force, payload): |
| item_list = [] |
| return_dict = {what+'s': []} |
| headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} |
| fail=0 |
| ok=0 |
| |
| #Allows for payload both keypairs inside a 'server','port' ... or directly. In later case, put keypairs inside what |
| if what not in payload: |
| payload = {what:payload} |
| |
| item = c.get(what,None) |
| if item is None: item = c.get(what+'s',None) |
| if item is None: |
| error_text= " Internal error, not found '" + what +"[s]' in content" |
| print 'get_details()', error_text, c |
| return -1, error_text |
| if type(item) is list: |
| item_list = item |
| else: |
| item_list.append(item) |
| if len(item_list)==0: |
| print what, "not found" |
| return 1 |
| for item in item_list: |
| name = item.get('name',None) |
| uuid = item.get('id',None) |
| if uuid is None: uuid = item.get('uuid',None) |
| if uuid is None: |
| error_text= " Internal error, not found 'id/uuid' in item" |
| print 'get_details()', error_text, item |
| return -1, error_text |
| if not force: |
| r = raw_input("Edit " + what + " " + uuid + " " + name + " (y/N)? ") |
| if len(r)>0 and r[0].lower()=="y": |
| print " put", what, uuid, " >>>>>>>> ", |
| else: |
| continue |
| |
| #print str(payload) |
| try: |
| vim_response = requests.put(url + "/" + uuid, data=json.dumps(payload), headers=headers_req) |
| if vim_response.status_code == 200: |
| print 'ok' |
| ok += 1 |
| return_dict[what+'s'].append( vim_response.json()[what] ) |
| else: |
| fail += 1 |
| print "fail" |
| #print vim_response.text |
| #text = "Error. VIM response '%s': not possible to PUT %s" % (vim_response.status_code, url) |
| #text += "\n" + vim_response.text |
| #print text |
| error_dict = vim_response.json() |
| error_dict['error']['uuid']=uuid |
| error_dict['error']['name']=name |
| return_dict[what+'s'].append(error_dict) |
| except requests.exceptions.RequestException, e: |
| return -1, " Exception "+ str(e.message) |
| if ok>0 and fail>0: return 0, return_dict |
| elif fail==0 : return 1, return_dict |
| else: return -1, return_dict |
| |
| def get_del_recursive(url, what, url_suffix, force=False, recursive=False): |
| #print |
| #print " get", what, a, " >>>>>>>> ", |
| r,c = get_elements(url + what + 's' + url_suffix) |
| if r<0: |
| print c, "when getting", what, url_suffix |
| return -1 |
| # print "ok" |
| |
| list_todelete = c.get(what, None) |
| if list_todelete is None: list_todelete = c.get(what+'s', None) |
| if list_todelete is None: |
| print " Internal error, not found '" + what +"[s]' in", c |
| return -3, " Internal error, not found a valid dictionary" |
| if type(list_todelete) == dict: |
| list_todelete = (list_todelete, ) |
| |
| if len(list_todelete)==0: |
| print what, url_suffix, "not found" |
| return 1 |
| for c in list_todelete: |
| uuid=c.get('id', None) |
| if uuid is None: |
| uuid=c.get('uuid', None) |
| if uuid is None: |
| print "Id not found" |
| continue |
| name = c.get("name","") |
| if recursive: |
| if what=='tenant' : |
| get_del_recursive(url + uuid + "/", 'server', "", force, recursive) |
| get_del_recursive(url + uuid + "/", 'flavor', "", force, recursive) |
| get_del_recursive(url + uuid + "/", 'image', "", force, recursive) |
| get_del_recursive(url, 'network', "?tenant_id="+uuid, force, recursive) |
| elif what=='flavors' : |
| #get_del_recursive(url, 'servers', "?flavorRef="+uuid, force, recursive) |
| pass |
| elif what=='image' : |
| get_del_recursive(url, 'server', "?imageRef="+uuid, force, recursive) |
| elif what=='hosts' : |
| get_del_recursive(url, 'server', "?hostId="+uuid, force, recursive) |
| |
| if not force: |
| r = raw_input("Delete " + what + " " + uuid + " " + name + " (y/N)? ") |
| if len(r)>0 and r[0].lower()=="y": |
| pass |
| else: |
| continue |
| r,c = delete_elements(url + what + "s/" + uuid) |
| if r<0: |
| #print "Error deleting", vimURI, -r |
| print c |
| else: |
| print what, uuid, name, "deleted" |
| return 1 |
| |
| def check_valid_uuid(uuid): |
| id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"} |
| try: |
| js_v(uuid, id_schema) |
| return True |
| except js_e.ValidationError: |
| return False |
| |
| def change_string(text, var_list): |
| end=0 |
| type_=None |
| while True: |
| ini = text.find("${", end) |
| if ini<0: return text |
| end = text.find("}", ini) |
| if end<0: return text |
| end+=1 |
| |
| var = text[ini:end] |
| if ' ' in var: |
| kk=var.split(" ") |
| var=kk[0]+"}" |
| type_=kk[-1][:-1] |
| var = var_list.get(var, None) |
| if var==None: return text |
| |
| text = text[:ini] + var + text[end:] |
| if type_ != None: |
| if 'null' in type_ and text=="null": |
| return None |
| if 'int' in type_ : #and text.isnumeric(): |
| return int(text) |
| return text |
| |
| def chage_var_recursively(data, var_list): |
| '''Check recursively the conent of data, and look for "*${*}*" variables and changes |
| It assumes that this variables are not in the key of dictionary, |
| Attributes: |
| 'data': dictionary, or list. None or empty is consideted valid |
| 'var_list': dictionary (name:change) pairs |
| Return: |
| None, data is modified |
| ''' |
| |
| if type(data) is dict: |
| for k in data.keys(): |
| if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list: |
| chage_var_recursively(data[k], var_list) |
| elif type(data[k]) is str: |
| data[k] = change_string(data[k], var_list) |
| if type(data) is list: |
| for k in range(0,len(data)): |
| if type(data[k]) is dict or type(data[k]) is list: |
| chage_var_recursively(data[k], var_list) |
| elif type(data[k]) is str: |
| data[k] = change_string(data[k], var_list) |
| |
| def change_var(data): |
| if type(data) is not dict: |
| return -1, "Format error, not a object (dictionary)" |
| if "${}" not in data: |
| return 0, data |
| |
| var_list={} |
| for var in data["${}"]: |
| r = var.find("}",) + 1 |
| if r<=2 or var[:2] != '${': |
| return -1, "Format error at '${}':" + var |
| #change variables inside description text |
| if "${" in var[r:]: |
| var = var[:r] + change_string(var[r:], var_list) |
| d_start = var.rfind("(",) + 1 |
| d_end = var.rfind(")",) |
| if d_start>0 and d_end>=d_start: |
| default = var[d_start:d_end] |
| else: default=None |
| v = raw_input(var[r:] + "? ") |
| if v=="": |
| if default != None: |
| v = default |
| else: |
| v = raw_input(" empty string? try again: ") |
| var_list[ var[:r] ] = str(v) |
| |
| del data["${}"] |
| chage_var_recursively(data, var_list) |
| return 0, data |
| |
| def parse_yaml_json(text): |
| try: |
| data = yaml.load(text) |
| return 0, data |
| except yaml.YAMLError, exc: |
| error_pos = "" |
| if hasattr(exc, 'problem_mark'): |
| mark = exc.problem_mark |
| error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1) |
| return -1, " Error yaml/json format error at " + error_pos |
| |
| def load_file(file_, parse=False): |
| try: |
| f = open(file_, 'r') |
| read_data = f.read() |
| f.close() |
| if not parse: |
| return 0, read_data |
| except IOError, e: |
| return -1, " Error opening file '" + file_ + "': " + e.args[1] |
| |
| try: |
| data = yaml.load(read_data) |
| return change_var(data) |
| except yaml.YAMLError, exc: |
| error_pos = "" |
| if hasattr(exc, 'problem_mark'): |
| mark = exc.problem_mark |
| error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1) |
| return -2, " Error yaml/json format error at '"+ file_ +"'"+error_pos |
| |
| def load_configuration(configuration_file): |
| default_tokens ={'http_port':8080, 'http_host':'localhost', 'test_mode':False, 'of_controller_nets_with_same_vlan':True} |
| |
| r, config = load_file(configuration_file, parse=True) |
| if r < 0: |
| return False, config |
| |
| #Check default values tokens |
| for k,v in default_tokens.items(): |
| if k not in config: config[k]=v |
| |
| return (True, config) |
| |
| items_list = ('server','host','tenant','image','flavor','network','port') |
| action_list = ('list','get','new','del','edit','action') |
| |
| |
| def usage(complete=False): |
| global items_list |
| global action_list |
| print "Usage: ", sys.argv[0], "[options]", " [" + ",".join(action_list) +"] ", "<item> [<other>] " |
| print " Perform an test action over openvim" |
| print " "+",".join(action_list)+": List (by default), GET detais, Creates, Deletes, Edit" |
| print " <item>: can be one of " + ",".join(items_list) |
| print " <other>: list of uuid|name for 'get|del'; list of json/yaml files for 'new' or 'edit'" |
| if not complete: |
| print " Type -h or --help for a complete list of options" |
| return |
| print " Options:" |
| print " -v|--version: prints current version" |
| print " -c|--config [configuration_file]: loads the configuration file (default: openvimd.cfg)" |
| print " -h|--help: shows this help" |
| print " -u|--url [URL]: url to use instead of the one loaded from configuration file" |
| print " -t|--tenant [tenant uuid]: tenant to be used for some comands. IF mising it will use the default obtained in configuration file" |
| print " -F|--filter [A=B[&C=D...]: URL query string used for 'get' or 'del' commands" |
| print " -f|--force : Do not ask for confirmation when deleting. Also remove dependent objects." |
| print " -r|--recursive : Delete also dependency elements, (from tenants: images, flavors,server; from hosts: instances; ..." |
| print " Examples:" |
| print " ",sys.argv[0]," tenant #list tenants " |
| print " ",sys.argv[0]," -F'device_owner=external' get port #get details of all external ports" |
| print " ",sys.argv[0]," del server ses pan #delete server names 'ses' and 'pan'. Do not ask for confirmation" |
| print " ",sys.argv[0]," -r -f del host #delete all host and all the dependencies " |
| print " ",sys.argv[0]," new host ./Host/nfv100.json #add a host which information is in this file" |
| print " ",sys.argv[0]," edit network f348faf8-59ef-11e4-b4c7-52540030594e '{\"network\":{\"admin_state_up\":false}}'" |
| print " #change the admin status of this network" |
| return |
| |
| |
| if __name__=="__main__": |
| global vimURI |
| global vimURI_admin |
| |
| global what |
| global query_string |
| #init variables |
| action="list" |
| what=None |
| url=None |
| query_string = "" |
| force = False |
| recursive = False |
| tenant = None |
| additional = [] |
| #look for parent dir |
| config_file = '../openvimd.cfg' |
| pos = sys.argv[0].rfind("/") |
| if pos<0: |
| base_dir="./" |
| else: |
| base_dir = sys.argv[0] [:pos+1] |
| if pos>=0: |
| config_file = base_dir + config_file |
| |
| #get params |
| try: |
| opts, args = getopt.getopt(sys.argv[1:], "hvrfc:u:t:F:", |
| ["config", "help", "version", "force", "filter","tenant","url","recursive"]) |
| except getopt.GetoptError, err: |
| print " Error:", err # will print something like "option -a not recognized" |
| usage() |
| sys.exit(-2) |
| |
| for o, a in opts: |
| if o in ("-v", "--version"): |
| print "test_openvim version", version, "Oct 2014" |
| print "(c) Copyright Telefonica" |
| sys.exit(0) |
| elif o in ("-h", "--help"): |
| usage(True) |
| sys.exit(0) |
| elif o in ("-c", "--config"): config_file = a |
| elif o in ("-f", "--force"): force = True |
| elif o in ("-r", "--recursive"): recursive = True |
| elif o in ("-F", "--filter"): query_string = "?"+a |
| elif o in ("-u", "--url"): url = a |
| elif o in ("-t", "--tenant"): tenant = a |
| else: |
| assert False, "Unhandled option" |
| |
| for a in args: |
| if len(a) == 0: |
| print " Warning!!! Found an empty parameter?" |
| elif a[0]=="-": |
| print " Error!!! Put options parameter at the beginning" |
| sys.exit(-2) |
| elif what is not None: |
| additional.append(a) |
| elif a in items_list: |
| what=a |
| elif a[:-1] in items_list and a[-1]=='s': |
| what=a[:-1] |
| elif a in action_list: |
| action=a |
| else: |
| print " Missing <item>", ",".join(items_list) |
| sys.exit(-2) |
| if what is None: |
| usage() |
| sys.exit(-1) |
| #Load configuration file |
| r, config_dic = load_configuration(config_file) |
| #print config_dic |
| if not r: |
| print config_dic |
| config_dic={} |
| #exit(-1) |
| |
| #override parameters obtained by command line |
| try: |
| if url is not None: |
| vimURI = vimURI_admin = url |
| else: |
| vimURI = "http://" + config_dic['http_host'] +":"+ str(config_dic['http_port']) + "/openvim/" |
| if 'http_admin_port' in config_dic: |
| vimURI_admin = "http://" + config_dic['http_host'] +":"+ str(config_dic['http_admin_port']) + "/openvim/" |
| except: #key error |
| print " Error: can not get URL; neither option --u,-url, nor reading configuration file" |
| exit(-1) |
| if tenant is None: |
| tenant = config_dic.get('tenant_id', None) |
| |
| #check enough parameters |
| URI=vimURI |
| if (what in ('host','port') and action in ('del','new')) or (what=='host' and action=='edit' ): |
| if vimURI_admin is None: |
| print " Error: Can not get admin URL; neither option -t,--tenant, nor reading configuration file" |
| exit(-1) |
| else: |
| URI=vimURI_admin |
| if URI[-1] != "/": URI+="/" |
| if what in ('server','image','flavor'): |
| if tenant is None: |
| print " Error: Can not get tenant; neither option -t,--tenant, nor reading configuration file" |
| exit(-1) |
| URI += tenant + "/" |
| |
| |
| exit_code=0 |
| try: |
| #load file for new/edit |
| payload_list=[] |
| if action=='new' or action=='edit' or action=='action': |
| if len(additional)==0: |
| if action=='new' : |
| additional.append(base_dir+what+"s/new_"+what+".yaml") |
| #print " New what? Missing additional parameters to complete action" |
| else: |
| print " What must be edited? Missing additional parameters to complete action" |
| exit(-1) |
| if action=='edit'or action=='action': |
| #obtain only last element |
| additional_temp = additional[:-1] |
| additional = additional[-1:] |
| |
| for a in additional: |
| r,payload = load_file(a, parse=True) |
| if r<0: |
| if r==-1 and "{" in a or ":" in a: |
| #try to parse directly |
| r,payload = parse_yaml_json(a) |
| if r<0: |
| print payload |
| exit (-1) |
| else: |
| print payload |
| exit (-1) |
| payload_list.append(payload) |
| if action=='edit'or action=='action': |
| additional = additional_temp |
| |
| |
| #perform actions NEW |
| if action=='new': |
| for payload in payload_list: |
| print "\n new", what, a, " >>>>>>>> ", |
| r,c = new_elements(URI+what+'s', payload) |
| if r>0: |
| print "ok" |
| else: |
| print "fail" |
| exit_code = -1 |
| print c |
| #try to decode |
| exit(exit_code) |
| |
| #perform actions GET LIST EDIT DEL |
| if len(additional)==0: |
| additional=[""] |
| for a in additional: |
| filter_qs = query_string |
| if a != "" : |
| if check_valid_uuid(a): |
| if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(a) |
| else: filter_qs += "?" + "id=" + str(a) |
| else: |
| if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(a) |
| else: filter_qs += "?" + "name=" + str(a) |
| |
| if action=='list' or action=='get' or action=='edit'or action=='action': |
| url = URI + what+'s' |
| print url + filter_qs |
| #print " get", what, a, " >>>>>>>> ", |
| r,c = get_elements(url + filter_qs) |
| if r<0: |
| #print "fail" |
| exit_code = -1 |
| print c |
| else: |
| #print "ok" |
| if action=='list': |
| print json.dumps(c, indent=4) |
| continue |
| |
| if action=='get': |
| r1,c1 = get_details(url, what, c) |
| elif action=='action': |
| r1,c1 = action_details(url, what, c, force, payload_list[0]) |
| else: # action=='edit': |
| r1,c1 = edit_details(url, what, c, force, payload_list[0]) |
| if r1<0: |
| exit_code = -1 |
| else: |
| if r>0: print "ok" |
| else: print "ok with some fails" |
| print json.dumps(c1, indent=4) |
| |
| elif action=='del': |
| r = get_del_recursive(URI, what, filter_qs, force, recursive) |
| if r<0: |
| exit_code = -1 |
| exit(exit_code) |
| |
| except KeyboardInterrupt: |
| print " Canceled" |
| |
| |