From f1ba57e5806479a5a38d5c4f48da8799e78e400a Mon Sep 17 00:00:00 2001 From: tierno Date: Thu, 7 Sep 2017 12:23:19 +0200 Subject: [PATCH] new API v3 that consumes OSM IM models Change-Id: If73f79575903a9192f3cb9b043b41a894f5c053c Signed-off-by: tierno --- database_utils/migrate_mano_db.sh | 36 +- openmano | 239 +++++---- openmanod | 4 +- osm_ro/httpserver.py | 104 +++- osm_ro/nfvo.py | 601 +++++++++++++++++++++-- osm_ro/nfvo_db.py | 3 +- scenarios/examples/v3_3vdu_2vnf_nsd.yaml | 59 +++ test/basictest.sh | 14 +- vnfs/examples/v3_3vdu_vnfd.yaml | 47 ++ 9 files changed, 961 insertions(+), 146 deletions(-) create mode 100644 scenarios/examples/v3_3vdu_2vnf_nsd.yaml create mode 100644 vnfs/examples/v3_3vdu_vnfd.yaml diff --git a/database_utils/migrate_mano_db.sh b/database_utils/migrate_mano_db.sh index 1e3e5b6c..557f5978 100755 --- a/database_utils/migrate_mano_db.sh +++ b/database_utils/migrate_mano_db.sh @@ -33,7 +33,7 @@ DBPORT="3306" DBNAME="mano_db" QUIET_MODE="" #TODO update it with the last database version -LAST_DB_VERSION=24 +LAST_DB_VERSION=25 # Detect paths MYSQL=$(which mysql) @@ -192,6 +192,7 @@ fi #[ $OPENMANO_VER_NUM -ge 5016 ] && DB_VERSION=22 #0.5.16 => 22 #[ $OPENMANO_VER_NUM -ge 5020 ] && DB_VERSION=23 #0.5.20 => 23 #[ $OPENMANO_VER_NUM -ge 5021 ] && DB_VERSION=24 #0.5.21 => 24 +#[ $OPENMANO_VER_NUM -ge 5022 ] && DB_VERSION=25 #0.5.22 => 25 #TODO ... put next versions here function upgrade_to_1(){ @@ -815,6 +816,39 @@ function downgrade_from_24(){ sql "ALTER TABLE vms DROP COLUMN count;" sql "DELETE FROM schema_version WHERE version_int='24';" } +function upgrade_to_25(){ + # echo " upgrade database from version 0.24 to version 0.25" + echo " Add 'osm_id','short_name','vendor' to tables 'vnfs', 'scenarios'" + for table in vnfs scenarios; do + sql "ALTER TABLE $table ADD COLUMN osm_id VARCHAR(255) NULL AFTER uuid, "\ + "ADD UNIQUE INDEX osm_id_tenant_id (osm_id, tenant_id), "\ + "ADD COLUMN short_name VARCHAR(255) NULL AFTER name, "\ + "ADD COLUMN vendor VARCHAR(255) NULL AFTER description;" + done + sql "ALTER TABLE vnfs ADD COLUMN mgmt_access VARCHAR(2000) NULL AFTER vendor;" + sql "ALTER TABLE vms ADD COLUMN osm_id VARCHAR(255) NULL AFTER uuid;" + sql "ALTER TABLE sce_vnfs ADD COLUMN member_vnf_index SMALLINT(6) NULL DEFAULT NULL AFTER uuid;" + echo " Add 'security_group' to table 'ip_profiles'" + sql "ALTER TABLE ip_profiles ADD COLUMN security_group VARCHAR(255) NULL DEFAULT NULL AFTER dhcp_count;" + + sql "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) "\ + "VALUES (25, '0.25', '0.5.22', 'Added osm_id to vnfs,scenarios', '2017-09-01');" +} +function downgrade_from_25(){ + # echo " downgrade database from version 0.25 to version 0.24" + echo " Remove 'osm_id','short_name','vendor' from tables 'vnfs', 'scenarios'" + for table in vnfs scenarios; do + sql "ALTER TABLE $table DROP INDEX osm_id_tenant_id, DROP COLUMN osm_id, "\ + "DROP COLUMN short_name, DROP COLUMN vendor;" + done + sql "ALTER TABLE vnfs DROP COLUMN mgmt_access;" + sql "ALTER TABLE vms DROP COLUMN osm_id;" + sql "ALTER TABLE sce_vnfs DROP COLUMN member_vnf_index;" + echo " Remove 'security_group' from table 'ip_profiles'" + sql "ALTER TABLE ip_profiles DROP COLUMN security_group;" + + sql "DELETE FROM schema_version WHERE version_int='25';" +} function upgrade_to_X(){ echo " change 'datacenter_nets'" diff --git a/openmano b/openmano index ec588a98..26ef9b93 100755 --- a/openmano +++ b/openmano @@ -28,8 +28,8 @@ openmano client used to interact with openmano-server (openmanod) """ __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes" __date__ = "$09-oct-2014 09:09:48$" -__version__ = "0.4.15-r525" -version_date = "Jul 2017" +__version__ = "0.4.20-r530" +version_date = "Sep 2017" from argcomplete.completers import FilesCompleter import os @@ -226,6 +226,9 @@ def _get_item_uuid(item, item_name_id, tenant=None): if i["name"] == item_name_id: uuid = i["uuid"] found += 1 + if item_name_id.startswith("osm_id=") and i.get("osm_id") == item_name_id[7:]: + uuid = i["uuid"] + found += 1 if found == 0: raise OpenmanoCLIError("No %s found with name/uuid '%s'" %(item[:-1], item_name_id)) elif found > 1: @@ -259,45 +262,90 @@ def vnf_create(args): headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} tenant = _get_tenant() myvnf = _load_file_or_yaml(args.file) + api_version = "" + if "vnfd:vnfd-catalog" in myvnf or "vnfd-catalog" in myvnf: + api_version = "/v3" + token = "vnfd" + vnfd_catalog = myvnf.get("vnfd:vnfd-catalog") + if not vnfd_catalog: + vnfd_catalog = myvnf.get("vnfd-catalog") + vnfds = vnfd_catalog.get("vnfd:vnfd") + if not vnfds: + vnfds = vnfd_catalog.get("vnfd") + vnfd = vnfds[0] + vdu_list = vnfd["vdu"] + + else: # old API + api_version = "" + token = "vnfs" + vnfd = myvnf['vnf'] + vdu_list = vnfd["VNFC"] if args.name or args.description or args.image_path or args.image_name or args.image_checksum: - #print args.name + # TODO, change this for API v3 + # print args.name try: if args.name: - myvnf['vnf']['name'] = args.name + vnfd['name'] = args.name if args.description: - myvnf['vnf']['description'] = args.description + vnfd['description'] = args.description if args.image_path: - index=0 + index = 0 for image_path_ in args.image_path.split(","): - #print "image-path", image_path_ - myvnf['vnf']['VNFC'][index]['VNFC image']=image_path_ - if "image name" in myvnf['vnf']['VNFC'][index]: - del myvnf['vnf']['VNFC'][index]["image name"] - if "image checksum" in myvnf['vnf']['VNFC'][index]: - del myvnf['vnf']['VNFC'][index]["image checksum"] - index=index+1 - if args.image_name: #image name precedes if both are supplied - index=0 + # print "image-path", image_path_ + if api_version == "/v3": + if vdu_list[index].get("image"): + vdu_list[index]['image'] = image_path_ + if "image-checksum" in vdu_list[index]: + del vdu_list[index]["image-checksum"] + else: # image name in volumes + vdu_list[index]["volumes"][0]["image"] = image_path_ + if "image-checksum" in vdu_list[index]["volumes"][0]: + del vdu_list[index]["volumes"][0]["image-checksum"] + else: + vdu_list[index]['VNFC image'] = image_path_ + if "image name" in vdu_list[index]: + del vdu_list[index]["image name"] + if "image checksum" in vdu_list[index]: + del vdu_list[index]["image checksum"] + index += 1 + if args.image_name: # image name precedes if both are supplied + index = 0 for image_name_ in args.image_name.split(","): - myvnf['vnf']['VNFC'][index]['image name']=image_name_ - if "VNFC image" in myvnf['vnf']['VNFC'][index]: - del myvnf['vnf']['VNFC'][index]["VNFC image"] - index=index+1 + if api_version == "/v3": + if vdu_list[index].get("image"): + vdu_list[index]['image'] = image_name_ + if "image-checksum" in vdu_list[index]: + del vdu_list[index]["image-checksum"] + else: # image name in volumes + vdu_list[index]["volumes"][0]["image"] = image_name_ + if "image-checksum" in vdu_list[index]["volumes"][0]: + del vdu_list[index]["volumes"][0]["image-checksum"] + else: + vdu_list[index]['image name'] = image_name_ + if "VNFC image" in vdu_list[index]: + del vdu_list[index]["VNFC image"] + index += 1 if args.image_checksum: - index=0 + index = 0 for image_checksum_ in args.image_checksum.split(","): - myvnf['vnf']['VNFC'][index]['image checksum']=image_checksum_ - index=index+1 + if api_version == "/v3": + if vdu_list[index].get("image"): + vdu_list[index]['image-checksum'] = image_checksum_ + else: # image name in volumes + vdu_list[index]["volumes"][0]["image-checksum"] = image_checksum_ + else: + vdu_list[index]['image checksum'] = image_checksum_ + index += 1 except (KeyError, TypeError), e: - if str(e)=='vnf': error_pos= "missing field 'vnf'" - elif str(e)=='name': error_pos= "missing field 'vnf':'name'" - elif str(e)=='description': error_pos= "missing field 'vnf':'description'" - elif str(e)=='VNFC': error_pos= "missing field 'vnf':'VNFC'" - elif str(e)==str(index): error_pos= "field 'vnf':'VNFC' must be an array" - elif str(e)=='VNFC image': error_pos= "missing field 'vnf':'VNFC'['VNFC image']" - elif str(e)=='image name': error_pos= "missing field 'vnf':'VNFC'['image name']" - elif str(e)=='image checksum': error_pos= "missing field 'vnf':'VNFC'['image checksum']" + if str(e) == 'vnf': error_pos= "missing field 'vnf'" + elif str(e) == 'name': error_pos= "missing field 'vnf':'name'" + elif str(e) == 'description': error_pos= "missing field 'vnf':'description'" + elif str(e) == 'VNFC': error_pos= "missing field 'vnf':'VNFC'" + elif str(e) == str(index): error_pos= "field 'vnf':'VNFC' must be an array" + elif str(e) == 'VNFC image': error_pos= "missing field 'vnf':'VNFC'['VNFC image']" + elif str(e) == 'image name': error_pos= "missing field 'vnf':'VNFC'['image name']" + elif str(e) == 'image checksum': error_pos= "missing field 'vnf':'VNFC'['image checksum']" else: error_pos="wrong format" print "Wrong VNF descriptor: " + error_pos return -1 @@ -305,7 +353,7 @@ def vnf_create(args): #print payload_req - URLrequest = "http://%s:%s/openmano/%s/vnfs" %(mano_host, mano_port, tenant) + URLrequest = "http://{}:{}/openmano{}/{}/{token}".format(mano_host, mano_port, api_version, tenant, token=token) logger.debug("openmano request: %s", payload_req) mano_response = requests.post(URLrequest, headers = headers_req, data=payload_req) logger.debug("openmano response: %s", mano_response.text ) @@ -326,7 +374,7 @@ def vnf_list(args): mano_response = requests.get(URLrequest) logger.debug("openmano response: %s", mano_response.text ) content = mano_response.json() - #print json.dumps(content, indent=4) + # print json.dumps(content, indent=4) if args.verbose==None: args.verbose=0 result = 0 if mano_response.status_code==200 else mano_response.status_code @@ -337,36 +385,40 @@ def vnf_list(args): return result if len(content['vnfs']) == 0: print "No VNFs were found." - return 404 #HTTP_Not_Found + return 404 # HTTP_Not_Found for vnf in content['vnfs']: - myoutput = "%s %s" %(vnf['uuid'].ljust(38),vnf['name'].ljust(20)) - if args.verbose >=1: - myoutput = "%s %s" %(myoutput, vnf['created_at'].ljust(20)) - print myoutput - if args.verbose >=2: - print " Description: %s" %vnf['description'] - print " VNF descriptor file: %s" %vnf['path'] + myoutput = "{:38} {:20}".format(vnf['uuid'], vnf['name']) + if vnf.get('osm_id') or args.verbose >= 1: + myoutput += " osm_id={:20}".format(vnf.get('osm_id')) + if args.verbose >= 1: + myoutput += " {}".format(vnf['created_at']) + print (myoutput) + if args.verbose >= 2: + print (" Description: {}".format(vnf['description'])) + # print (" VNF descriptor file: {}".format(vnf['path'])) else: if args.verbose: print yaml.safe_dump(content, indent=4, default_flow_style=False) return result vnf = content['vnf'] - print "%s %s %s" %(vnf['uuid'].ljust(38),vnf['name'].ljust(20), vnf['created_at'].ljust(20)) - print " Description: %s" %vnf['description'] - #print " VNF descriptor file: %s" %vnf['path'] - print " VMs:" + print ("{:38} {:20} osm_id={:20} {:20}".format(vnf['uuid'], vnf['name'], vnf.get('osm_id'), + vnf['created_at'])) + print (" Description: {}".format(vnf['description'])) + # print " VNF descriptor file: %s" %vnf['path'] + print (" VMs:") for vm in vnf['VNFC']: - #print " %s %s %s" %(vm['name'].ljust(20), vm['uuid'].ljust(38), vm['description'].ljust(30)) - print " %s %s" %(vm['name'].ljust(20), vm['description']) - if len(vnf['nets'])>0: - print " Internal nets:" + print (" {:20} osm_id={:20} {}".format(vm['name'], vm.get('osm_id'), vm['description'])) + if len(vnf['nets']) > 0: + print (" Internal nets:") for net in vnf['nets']: - print " %s %s" %(net['name'].ljust(20), net['description']) - if len(vnf['external-connections'])>0: - print " External interfaces:" + print (" {:20} {}".format(net['name'], net['description'])) + if len(vnf['external-connections']) > 0: + print (" External interfaces:") for interface in vnf['external-connections']: - print " %s %s %s %s" %(interface['external_name'].ljust(20), interface['vm_name'].ljust(20), interface['internal_name'].ljust(20), \ - interface.get('vpci',"").ljust(14)) + print (" {:20} {:20} {:20} {:14}".format( + interface['external_name'], interface['vm_name'], + interface['internal_name'], + interface.get('vpci') if interface.get('vpci') else "")) else: print content['error']['description'] if args.verbose: @@ -397,20 +449,38 @@ def vnf_delete(args): return result def scenario_create(args): - #print "scenario-create",args + # print "scenario-create",args tenant = _get_tenant() headers_req = {'content-type': 'application/yaml'} myscenario = _load_file_or_yaml(args.file) - + if "nsd:nsd-catalog" in myscenario or "nsd-catalog" in myscenario: + api_version = "/v3" + token = "nsd" + nsd_catalog = myscenario.get("nsd:nsd-catalog") + if not nsd_catalog: + nsd_catalog = myscenario.get("nsd-catalog") + nsds = nsd_catalog.get("nsd:nsd") + if not nsds: + nsds = nsd_catalog.get("nsd") + nsd = nsds[0] + else: # API=1: - myoutput = "%s %s" %(myoutput, scenario['created_at'].ljust(20)) - print myoutput + myoutput = "{:38} {:20}".format(scenario['uuid'], scenario['name']) + if scenario.get('osm_id') or args.verbose >= 1: + myoutput += " osm_id={:20}".format(scenario.get('osm_id')) + if args.verbose >= 1: + myoutput += " {}".format(scenario['created_at']) + print (myoutput) if args.verbose >=2: - print " Description: %s" %scenario['description'] + print (" Description: {}".format(scenario['description'])) else: if args.verbose: print yaml.safe_dump(content, indent=4, default_flow_style=False) return result scenario = content['scenario'] - myoutput = "%s %s %s" %(scenario['uuid'].ljust(38),scenario['name'].ljust(20), scenario['created_at'].ljust(20)) - print myoutput - print " Description: %s" %scenario['description'] - print " VNFs:" + print ("{:38} {:20} osm_id={:20} {:20}".format(scenario['uuid'], scenario['name'], scenario.get('osm_id'), + scenario['created_at'])) + print (" Description: {}".format(scenario['description'])) + print (" VNFs:") for vnf in scenario['vnfs']: - print " %s %s %s" %(vnf['name'].ljust(20), vnf['vnf_id'].ljust(38), vnf['description']) - if len(scenario['nets'])>0: - print " Internal nets:" + print (" {:38} {:20} vnf_index={} {}".format(vnf['vnf_id'], vnf['name'], vnf.get("member_vnf_index"), + vnf['description'])) + if len(scenario['nets']) > 0: + print (" nets:") for net in scenario['nets']: - if net['description'] is None: #if description does not exist, description is "-". Valid for external and internal nets. - net['description'] = '-' - if not net['external']: - print " %s %s %s" %(net['name'].ljust(20), net['uuid'].ljust(38), net['description'].ljust(30)) - print " External nets:" - for net in scenario['nets']: - if net['external']: - print " %s %s %s vim-id:%s" %(net['name'].ljust(20), net['uuid'].ljust(38), net['description'].ljust(30), net['vim_id']) + description = net['description'] + if not description: # if description does not exist, description is "-". Valid for external and internal nets. + description = '-' + vim_id = "" + if net.get('vim_id'): + vim_id = " vim_id=" + net["vim_id"] + external = "" + if net["external"]: + external = " external" + print (" {:20} {:38} {:30}{}{}".format(net['name'], net['uuid'], description, vim_id, external)) else: - print content['error']['description'] + print (content['error']['description']) if args.verbose: print yaml.safe_dump(content, indent=4, default_flow_style=False) return result diff --git a/openmanod b/openmanod index 94924bd7..21180751 100755 --- a/openmanod +++ b/openmanod @@ -48,9 +48,9 @@ import osm_ro __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes" __date__ = "$26-aug-2014 11:09:29$" -__version__ = "0.5.21-r531" +__version__ = "0.5.22-r532" version_date = "Aug 2017" -database_version = 24 # expected database schema version +database_version = 25 # expected database schema version global global_config global logger diff --git a/osm_ro/httpserver.py b/osm_ro/httpserver.py index f9cbb9b4..66b744a1 100644 --- a/osm_ro/httpserver.py +++ b/osm_ro/httpserver.py @@ -184,16 +184,17 @@ def format_out(data): return json.dumps(data, indent=4) + "\n" def format_in(default_schema, version_fields=None, version_dict_schema=None): - ''' Parse the content of HTTP request against a json_schema - Parameters - default_schema: The schema to be parsed by default if no version field is found in the client data - version_fields: If provided it contains a tuple or list with the fields to iterate across the client data to obtain the version - version_dict_schema: It contains a dictionary with the version as key, and json schema to apply as value - It can contain a None as key, and this is apply if the client data version does not match any key - Return: - user_data, used_schema: if the data is successfully decoded and matches the schema - launch a bottle abort if fails - ''' + """ + Parse the content of HTTP request against a json_schema + :param default_schema: The schema to be parsed by default if no version field is found in the client data. In None + no validation is done + :param version_fields: If provided it contains a tuple or list with the fields to iterate across the client data to + obtain the version + :param version_dict_schema: It contains a dictionary with the version as key, and json schema to apply as value. + It can contain a None as key, and this is apply if the client data version does not match any key + :return: user_data, used_schema: if the data is successfully decoded and matches the schema. + Launch a bottle abort if fails + """ #print "HEADERS :" + str(bottle.request.headers.items()) try: error_text = "Invalid header format " @@ -212,13 +213,16 @@ def format_in(default_schema, version_fields=None, version_dict_schema=None): logger.warning('Content-Type ' + str(format_type) + ' not supported.') bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.') return - #if client_data == None: + # if client_data == None: # bottle.abort(HTTP_Bad_Request, "Content error, empty") # return - logger.debug('IN: %s', yaml.safe_dump(client_data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) ) - #look for the client provider version + logger.debug('IN: %s', yaml.safe_dump(client_data, explicit_start=True, indent=4, default_flow_style=False, + tags=False, encoding='utf-8', allow_unicode=True) ) + # look for the client provider version error_text = "Invalid content " + if not default_schema and not version_fields: + return client_data, None client_version = None used_schema = None if version_fields != None: @@ -229,9 +233,9 @@ def format_in(default_schema, version_fields=None, version_dict_schema=None): else: client_version=None break - if client_version==None: - used_schema=default_schema - elif version_dict_schema!=None: + if client_version == None: + used_schema = default_schema + elif version_dict_schema != None: if client_version in version_dict_schema: used_schema = version_dict_schema[client_version] elif None in version_dict_schema: @@ -995,7 +999,7 @@ def http_get_vnfs(tenant_id): #check valid tenant_id nfvo.check_tenant(mydb, tenant_id) select_,where_,limit_ = filter_query_string(bottle.request.query, None, - ('uuid','name','description','public', "tenant_id", "created_at") ) + ('uuid', 'name', 'osm_id', 'description', 'public', "tenant_id", "created_at") ) where_or = {} if tenant_id != "any": where_or["tenant_id"] = tenant_id @@ -1033,9 +1037,12 @@ def http_get_vnf_id(tenant_id,vnf_id): @bottle.route(url_base + '//vnfs', method='POST') def http_post_vnfs(tenant_id): - '''insert a vnf into the catalogue. Creates the flavor and images in the VIM, and creates the VNF and its internal structure in the OPENMANO DB''' - #print "Parsing the YAML file of the VNF" - #parse input data + """ Insert a vnf into the catalogue. Creates the flavor and images, and fill the tables at database + :param tenant_id: tenant that this vnf belongs to + :return: + """ + # print "Parsing the YAML file of the VNF" + # parse input data logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02}) r = utils.remove_extra_items(http_content, used_schema) @@ -1057,9 +1064,34 @@ def http_post_vnfs(tenant_id): logger.error("Unexpected exception: ", exc_info=True) bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) - + +@bottle.route(url_base + '/v3//vnfd', method='POST') +def http_post_vnfs_v3(tenant_id): + """ + Insert one or several VNFs in the catalog, following OSM IM + :param tenant_id: tenant owner of the VNF + :return: The detailed list of inserted VNFs, following the old format + """ + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content, _ = format_in(None) + try: + vnfd_uuid_list = nfvo.new_vnfd_v3(mydb, tenant_id, http_content) + vnfd_list = [] + for vnfd_uuid in vnfd_uuid_list: + vnf = nfvo.get_vnf_id(mydb, tenant_id, vnfd_uuid) + utils.convert_str2boolean(vnf, ('public',)) + convert_datetime2str(vnf) + vnfd_list.append(vnf["vnf"]) + return format_out({"vnfd": vnfd_list}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + @bottle.route(url_base + '//vnfs/', method='DELETE') -def http_delete_vnf_id(tenant_id,vnf_id): +def http_delete_vnf_id(tenant_id, vnf_id): '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name''' logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) #check valid tenant_id and deletes the vnf, including images, @@ -1179,6 +1211,31 @@ def http_post_scenarios(tenant_id): logger.error("Unexpected exception: ", exc_info=True) bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) +@bottle.route(url_base + '/v3//nsd', method='POST') +def http_post_nsds_v3(tenant_id): + """ + Insert one or several NSDs in the catalog, following OSM IM + :param tenant_id: tenant owner of the NSD + :return: The detailed list of inserted NSDs, following the old format + """ + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content, _ = format_in(None) + try: + nsd_uuid_list = nfvo.new_nsd_v3(mydb, tenant_id, http_content) + nsd_list = [] + for nsd_uuid in nsd_uuid_list: + scenario = mydb.get_scenario(nsd_uuid, tenant_id) + convert_datetime2str(scenario) + nsd_list.append(scenario) + data = {'nsd': nsd_list} + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_nsds_v3 error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + @bottle.route(url_base + '//scenarios//action', method='POST') def http_post_scenario_action(tenant_id, scenario_id): @@ -1231,7 +1288,8 @@ def http_get_scenarios(tenant_id): if tenant_id != "any": nfvo.check_tenant(mydb, tenant_id) #obtain data - s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public')) + s,w,l=filter_query_string(bottle.request.query, None, + ('uuid', 'name', 'osm_id', 'description', 'tenant_id', 'created_at', 'public')) where_or={} if tenant_id != "any": where_or["tenant_id"] = tenant_id diff --git a/osm_ro/nfvo.py b/osm_ro/nfvo.py index 8b6a2e13..af84204f 100644 --- a/osm_ro/nfvo.py +++ b/osm_ro/nfvo.py @@ -47,6 +47,12 @@ from time import time from lib_osm_openvim import ovim as ovim_module from lib_osm_openvim.ovim import ovimException +import osm_im.vnfd as vnfd_catalog +import osm_im.nsd as nsd_catalog + +from pyangbind.lib.serialise import pybindJSONDecoder +from itertools import chain + global global_config global vimconn_imported global logger @@ -631,11 +637,11 @@ def create_or_use_flavor(mydb, vims, flavor_dict, rollback_list, only_create_at_ # continue #elif res_vim==0: - #Create the flavor in VIM - #Translate images at devices from MANO id to VIM id + # Create the flavor in VIM + # Translate images at devices from MANO id to VIM id disk_list = [] if 'extended' in flavor_dict and flavor_dict['extended']!=None and "devices" in flavor_dict['extended']: - #make a copy of original devices + # make a copy of original devices devices_original=[] for device in flavor_dict["extended"].get("devices",[]): @@ -646,7 +652,9 @@ def create_or_use_flavor(mydb, vims, flavor_dict, rollback_list, only_create_at_ del device['image'] if 'image metadata' in device: del device['image metadata'] - dev_nb=0 + if 'image checksum' in device: + del device['image checksum'] + dev_nb = 0 for index in range(0,len(devices_original)) : device=devices_original[index] if "image" not in device and "image name" not in device: @@ -658,7 +666,7 @@ def create_or_use_flavor(mydb, vims, flavor_dict, rollback_list, only_create_at_ image_dict['universal_name']=device.get('image name') image_dict['description']=flavor_dict['name']+str(dev_nb)+"-img" image_dict['location']=device.get('image') - #image_dict['new_location']=device.get('image location') + # image_dict['new_location']=device.get('image location') image_dict['checksum']=device.get('image checksum') image_metadata_dict = device.get('image metadata', None) image_metadata_str = None @@ -720,6 +728,365 @@ def create_or_use_flavor(mydb, vims, flavor_dict, rollback_list, only_create_at_ return flavor_vim_id if only_create_at_vim else flavor_mano_id +def get_str(obj, field, length): + """ + Obtain the str value, + :param obj: + :param length: + :return: + """ + value = obj.get(field) + if value is not None: + value = str(value)[:length] + return value + +def _lookfor_or_create_image(db_image, mydb, descriptor): + """ + fill image content at db_image dictionary. Check if the image with this image and checksum exist + :param db_image: dictionary to insert data + :param mydb: database connector + :param descriptor: yang descriptor + :return: uuid if the image exist at DB, or None if a new image must be created with the data filled at db_image + """ + + db_image["name"] = get_str(descriptor, "image", 255) + db_image["checksum"] = get_str(descriptor, "image-checksum", 32) + if not db_image["checksum"]: # Ensure that if empty string, None is stored + db_image["checksum"] = None + if db_image["name"].startswith("/"): + db_image["location"] = db_image["name"] + existing_images = mydb.get_rows(FROM="images", WHERE={'location': db_image["location"]}) + else: + db_image["universal_name"] = db_image["name"] + existing_images = mydb.get_rows(FROM="images", WHERE={'universal_name': db_image['universal_name'], + 'checksum': db_image['checksum']}) + if existing_images: + return existing_images[0]["uuid"] + else: + image_uuid = str(uuid4()) + db_image["uuid"] = image_uuid + return None + +def new_vnfd_v3(mydb, tenant_id, vnf_descriptor): + """ + Parses an OSM IM vnfd_catalog and insert at DB + :param mydb: + :param tenant_id: + :param vnf_descriptor: + :return: The list of cretated vnf ids + """ + try: + myvnfd = vnfd_catalog.vnfd() + pybindJSONDecoder.load_ietf_json(vnf_descriptor, None, None, obj=myvnfd) + db_vnfs = [] + db_nets = [] + db_vms = [] + db_vms_index = 0 + db_interfaces = [] + db_images = [] + db_flavors = [] + uuid_list = [] + vnfd_uuid_list = [] + for rift_vnfd in myvnfd.vnfd_catalog.vnfd.itervalues(): + vnfd = rift_vnfd.get() + + # table vnf + vnf_uuid = str(uuid4()) + uuid_list.append(vnf_uuid) + vnfd_uuid_list.append(vnf_uuid) + db_vnf = { + "uuid": vnf_uuid, + "osm_id": get_str(vnfd, "id", 255), + "name": get_str(vnfd, "name", 255), + "description": get_str(vnfd, "description", 255), + "tenant_id": tenant_id, + "vendor": get_str(vnfd, "vendor", 255), + "short_name": get_str(vnfd, "short-name", 255), + "descriptor": str(vnf_descriptor)[:60000] + } + + # table nets (internal-vld) + net_id2uuid = {} # for mapping interface with network + for vld in vnfd.get("internal-vld").itervalues(): + net_uuid = str(uuid4()) + uuid_list.append(net_uuid) + db_net = { + "name": get_str(vld, "name", 255), + "vnf_id": vnf_uuid, + "uuid": net_uuid, + "description": get_str(vld, "description", 255), + "type": "bridge", # TODO adjust depending on connection point type + } + net_id2uuid[vld.get("id")] = net_uuid + db_nets.append(db_net) + + # table vms (vdus) + vdu_id2uuid = {} + vdu_id2db_table_index = {} + for vdu in vnfd.get("vdu").itervalues(): + vm_uuid = str(uuid4()) + uuid_list.append(vm_uuid) + db_vm = { + "uuid": vm_uuid, + "osm_id": get_str(vdu, "id", 255), + "name": get_str(vdu, "name", 255), + "description": get_str(vdu, "description", 255), + "vnf_id": vnf_uuid, + } + vdu_id2uuid[db_vm["osm_id"]] = vm_uuid + vdu_id2db_table_index[db_vm["osm_id"]] = db_vms_index + if vdu.get("count"): + db_vm["count"] = int(vdu["count"]) + + # table image + image_present = False + if vdu.get("image"): + image_present = True + db_image = {} + image_uuid = _lookfor_or_create_image(db_image, mydb, vdu) + if not image_uuid: + image_uuid = db_image["uuid"] + db_images.append(db_image) + db_vm["image_id"] = image_uuid + + # volumes + devices = [] + if vdu.get("volumes"): + for volume_key in sorted(vdu["volumes"]): + volume = vdu["volumes"][volume_key] + if not image_present: + # Convert the first volume to vnfc.image + image_present = True + db_image = {} + image_uuid = _lookfor_or_create_image(db_image, mydb, volume) + if not image_uuid: + image_uuid = db_image["uuid"] + db_images.append(db_image) + db_vm["image_id"] = image_uuid + else: + # Add Openmano devices + device = {} + device["type"] = str(volume.get("device-type")) + if volume.get("size"): + device["size"] = int(volume["size"]) + if volume.get("image"): + device["image name"] = str(volume["image"]) + if volume.get("image-checksum"): + device["image checksum"] = str(volume["image-checksum"]) + devices.append(device) + + # table flavors + db_flavor = { + "name": get_str(vdu, "name", 250) + "-flv", + "vcpus": int(vdu["vm-flavor"].get("vcpu-count", 1)), + "ram": int(vdu["vm-flavor"].get("memory-mb", 1)), + "disk": int(vdu["vm-flavor"].get("storage-gb", 1)), + } + # EPA TODO revise + extended = {} + numa = {} + if devices: + extended["devices"] = devices + if vdu.get("guest-epa"): # TODO or dedicated_int: + epa_vcpu_set = False + if vdu["guest-epa"].get("numa-node-policy"): # TODO or dedicated_int: + numa_node_policy = vdu["guest-epa"].get("numa-node-policy") + if numa_node_policy.get("node"): + numa_node = numa_node_policy.node[0] + if numa_node.get("num-cores"): + numa["cores"] = numa_node["num-cores"] + epa_vcpu_set = True + if numa_node.get("paired-threads"): + if numa_node["paired-threads"].get("num-paired-threads"): + numa["paired-threads"] = numa_node["paired-threads"]["num-paired-threads"] + epa_vcpu_set = True + if len(numa_node["paired-threads"].get("paired-thread-ids")) > 0: + numa["paired-threads-id"] = [] + for pair in numa_node["paired-threads"]["paired-thread-ids"].itervalues: + numa["paired-threads-id"].append( + (str(pair["thread-a"]), str(pair["thread-b"])) + ) + if numa_node.get("num-threads"): + numa["threads"] = numa_node["num-threads"] + epa_vcpu_set = True + if numa_node.get("memory-mb"): + numa["memory"] = max(int(numa_node["memory-mb"] / 1024), 1) + if vdu["guest-epa"].get("mempage-size"): + if vdu["guest-epa"]["mempage-size"] != "SMALL": + numa["memory"] = max(int(db_flavor["ram"] / 1024), 1) + if vdu["guest-epa"].get("cpu-pinning-policy") and not epa_vcpu_set: + if vdu["guest-epa"]["cpu-pinning-policy"] == "DEDICATED": + if vdu["guest-epa"].get("cpu-thread-pinning-policy") and \ + vdu["guest-epa"]["cpu-thread-pinning-policy"] != "PREFER": + numa["cores"] = max(db_flavor["vcpus"], 1) + else: + numa["threads"] = max(db_flavor["vcpus"], 1) + if numa: + extended["numas"] = [numa] + if extended: + extended_text = yaml.safe_dump(extended, default_flow_style=True, width=256) + db_flavor["extended"] = extended_text + # look if flavor exist + + temp_flavor_dict = {'disk': db_flavor.get('disk', 1), + 'ram': db_flavor.get('ram'), + 'vcpus': db_flavor.get('vcpus'), + 'extended': db_flavor.get('extended') + } + existing_flavors = mydb.get_rows(FROM="flavors", WHERE=temp_flavor_dict) + if existing_flavors: + flavor_uuid = existing_flavors[0]["uuid"] + else: + flavor_uuid = str(uuid4()) + uuid_list.append(flavor_uuid) + db_flavor["uuid"] = flavor_uuid + db_flavors.append(db_flavor) + db_vm["flavor_id"] = flavor_uuid + + # cloud-init + boot_data = {} + if vdu.get("cloud-init"): + boot_data["user-data"] = vdu["cloud-init"] + elif vdu.get("cloud-init-file"): + # TODO Where this file content is present??? + # boot_data["user-data"] = rift_vnfd.files[vdu["cloud-init-file"]] + boot_data["user-data"] = str(vdu["cloud-init-file"]) + + if vdu.get("supplemental-boot-data"): + if vdu["supplemental-boot-data"].get('boot-data-drive'): + boot_data['boot-data-drive'] = True + if vdu["supplemental-boot-data"].get('config-file'): + om_cfgfile_list = list() + for custom_config_file in vdu["supplemental-boot-data"]['config-file'].itervalues(): + # TODO Where this file content is present??? + cfg_source = str(custom_config_file["source"]) + om_cfgfile_list.append({"dest": custom_config_file["dest"], + "content": cfg_source}) + boot_data['config-files'] = om_cfgfile_list + if boot_data: + db_vm["boot_data"] = boot_data + + db_vms.append(db_vm) + db_vms_index += 1 + + # table interfaces (internal/external interfaces) + cp_name2iface_uuid = {} + cp_name2vm_uuid = {} + for iface in chain(vdu.get("internal-interface").itervalues(), vdu.get("external-interface").itervalues()): + iface_uuid = str(uuid4()) + uuid_list.append(iface_uuid) + db_interface = { + "uuid": iface_uuid, + "internal_name": get_str(iface, "name", 255), + "vm_id": vm_uuid, + } + if iface.get("virtual-interface").get("vpci"): + db_interface["vpci"] = get_str(iface.get("virtual-interface"), "vpci", 12) + + if iface.get("virtual-interface").get("bandwidth"): + bps = int(iface.get("virtual-interface").get("bandwidth")) + db_interface["bw"] = bps/1000 + + if iface.get("virtual-interface").get("type") == "OM-MGMT": + db_interface["type"] = "mgmt" + elif iface.get("virtual-interface").get("type") in ("VIRTIO", "E1000"): + db_interface["type"] = "bridge" + db_interface["model"] = get_str(iface.get("virtual-interface"), "type", 12) + elif iface.get("virtual-interface").get("type") in ("SR-IOV", "PCI-PASSTHROUGH"): + db_interface["type"] = "data" + db_interface["model"] = get_str(iface.get("virtual-interface"), "type", 12) + else: + raise ValueError("Interface type {} not supported".format(iface.get("virtual-interface").get("type"))) + + if iface.get("vnfd-connection-point-ref"): + try: + cp = vnfd.get("connection-point")[iface.get("vnfd-connection-point-ref")] + db_interface["external_name"] = get_str(cp, "name", 255) + cp_name2iface_uuid[db_interface["external_name"]] = iface_uuid + cp_name2vm_uuid[db_interface["external_name"]] = vm_uuid + # TODO add port-security-enable + # if cp.get("port-security-enabled") == False: + # elif cp.get("port-security-enabled") == True: + except KeyError: + raise KeyError( + "Error wrong reference at vnfd['{vnf}'] vdu['{vdu}']:internal-interface['{iface}']:" + "vnfd-connection-point-ref '{cp}' is not present at connection-point".format( + vnf=vnfd["id"], vdu=vdu["id"], iface=iface["name"], + cp=iface.get("vnfd-connection-point-ref")) + ) + elif iface.get("vdu-internal-connection-point-ref"): + try: + for vld in vnfd.get("internal-vld").itervalues(): + for cp in vld.get("internal-connection-point").itervalues(): + if cp.get("id-ref") == iface.get("vdu-internal-connection-point-ref"): + db_interface["net_id"] = net_id2uuid[vld.get("id")] + break + except KeyError: + raise KeyError( + "Error at vnfd['{vnf}'] vdu['{vdu}']:internal-interface['{iface}']:" + "vdu-internal-connection-point-ref '{cp}' is not referenced by any internal-vld".format( + vnf=vnfd["id"], vdu=vdu["id"], iface=iface["name"], + cp=iface.get("vdu-internal-connection-point-ref")) + ) + + db_interfaces.append(db_interface) + + # VNF affinity and antiaffinity + for pg in vnfd.get("placement-groups").itervalues(): + pg_name = get_str(pg, "name", 255) + for vdu in pg.get("member-vdus").itervalues(): + vdu_id = get_str(vdu, "member-vdu-ref", 255) + if vdu_id not in vdu_id2db_table_index: + raise KeyError( + "Error at 'vnfd'['{vnf}']:'placement-groups'['{pg}']:'member-vdus':'{vdu}' references a non existing vdu".format( + vnf=vnfd["id"], pg=pg_name, vdu=vdu_id)) + db_vms[vdu_id2db_table_index[vdu_id]]["availability_zone"] = pg_name + # TODO consider the case of isolation and not colocation + # if pg.get("strategy") == "ISOLATION": + + # VNF mgmt configuration + mgmt_access = {} + if vnfd["mgmt-interface"].get("vdu-id"): + if vnfd["mgmt-interface"]["vdu-id"] not in vdu_id2uuid: + raise KeyError( + "Error at vnfd['{vnf}']:'mgmt-interface':'vdu-id':{vdu} reference a non existing vdu".format( + vnf=vnfd["id"], vdu=vnfd["mgmt-interface"]["vdu-id"])) + mgmt_access["vm_id"] = vdu_id2uuid[vnfd["mgmt-interface"]["vdu-id"]] + if vnfd["mgmt-interface"].get("ip-address"): + mgmt_access["ip-address"] = str(vnfd["mgmt-interface"].get("ip-address")) + if vnfd["mgmt-interface"].get("cp"): + if vnfd["mgmt-interface"]["cp"] not in cp_name2iface_uuid: + raise KeyError( + "Error at vnfd['{vnf}']:'mgmt-interface':'cp':{cp} reference a non existing connection-point". + format(vnf=vnfd["id"], cp=vnfd["mgmt-interface"]["cp"])) + mgmt_access["vm_id"] = cp_name2vm_uuid[vnfd["mgmt-interface"]["cp"]] + mgmt_access["interface_id"] = cp_name2iface_uuid[vnfd["mgmt-interface"]["cp"]] + default_user = get_str(vnfd.get("vnf-configuration").get("config-access", {}).get("ssh-access", {}), + "default-user", 64) + if default_user: + mgmt_access["default_user"] = default_user + if mgmt_access: + db_vnf["mgmt_access"] = yaml.safe_dump(mgmt_access, default_flow_style=True, width=256) + + db_vnfs.append(db_vnf) + db_tables=[ + {"vnfs": db_vnfs}, + {"nets": db_nets}, + {"images": db_images}, + {"flavors": db_flavors}, + {"vms": db_vms}, + {"interfaces": db_interfaces}, + ] + + logger.debug("create_vnf Deployment done vnfDict: %s", + yaml.safe_dump(db_tables, indent=4, default_flow_style=False) ) + mydb.new_rows(db_tables, uuid_list) + return vnfd_uuid_list + except Exception as e: + logger.error("Exception {}".format(e)) + raise # NfvoException("Exception {}".format(e), HTTP_Bad_Request) + + def new_vnf(mydb, tenant_id, vnf_descriptor): global global_config @@ -1002,14 +1369,15 @@ def get_vnf_id(mydb, tenant_id, vnf_id): where_or["public"] = True vnf = mydb.get_table_by_uuid_name('vnfs', vnf_id, "VNF", WHERE_OR=where_or, WHERE_AND_OR="AND") - vnf_id=vnf["uuid"] - filter_keys = ('uuid','name','description','public', "tenant_id", "created_at") + vnf_id = vnf["uuid"] + filter_keys = ('uuid', 'name', 'description', 'public', "tenant_id", "osm_id", "created_at") filtered_content = dict( (k,v) for k,v in vnf.iteritems() if k in filter_keys ) #change_keys_http2db(filtered_content, http2db_vnf, reverse=True) data={'vnf' : filtered_content} #GET VM content = mydb.get_rows(FROM='vnfs join vms on vnfs.uuid=vms.vnf_id', - SELECT=('vms.uuid as uuid','vms.name as name', 'vms.description as description', 'boot_data'), + SELECT=('vms.uuid as uuid', 'vms.osm_id as osm_id', 'vms.name as name', 'vms.description as description', + 'boot_data'), WHERE={'vnfs.uuid': vnf_id} ) if len(content)==0: raise NfvoException("vnf '{}' not found".format(vnf_id), HTTP_Not_Found) @@ -1608,6 +1976,170 @@ def new_scenario_v02(mydb, tenant_id, scenario_dict, version): return scenario_id +def new_nsd_v3(mydb, tenant_id, nsd_descriptor): + """ + Parses an OSM IM nsd_catalog and insert at DB + :param mydb: + :param tenant_id: + :param nsd_descriptor: + :return: The list of cretated NSD ids + """ + try: + mynsd = nsd_catalog.nsd() + pybindJSONDecoder.load_ietf_json(nsd_descriptor, None, None, obj=mynsd) + db_scenarios = [] + db_sce_nets = [] + db_sce_vnfs = [] + db_sce_interfaces = [] + db_ip_profiles = [] + db_ip_profiles_index = 0 + uuid_list = [] + nsd_uuid_list = [] + for rift_nsd in mynsd.nsd_catalog.nsd.itervalues(): + nsd = rift_nsd.get() + + # table sceanrios + scenario_uuid = str(uuid4()) + uuid_list.append(scenario_uuid) + nsd_uuid_list.append(scenario_uuid) + db_scenario = { + "uuid": scenario_uuid, + "osm_id": get_str(nsd, "id", 255), + "name": get_str(nsd, "name", 255), + "description": get_str(nsd, "description", 255), + "tenant_id": tenant_id, + "vendor": get_str(nsd, "vendor", 255), + "short_name": get_str(nsd, "short-name", 255), + "descriptor": str(nsd_descriptor)[:60000], + } + db_scenarios.append(db_scenario) + + # table sce_vnfs (constituent-vnfd) + vnf_index2scevnf_uuid = {} + vnf_index2vnf_uuid = {} + for vnf in nsd.get("constituent-vnfd").itervalues(): + existing_vnf = mydb.get_rows(FROM="vnfs", WHERE={'osm_id': str(vnf["vnfd-id-ref"])[:255], + 'tenant_id': tenant_id}) + if not existing_vnf: + raise KeyError("Error at 'nsd[{}]':'constituent-vnfd':'vnfd-id-ref':'{}' references a " + "non existing VNFD in the catalog".format(str(nsd["id"]), + str(vnf["vnfd-id-ref"])[:255])) + sce_vnf_uuid = str(uuid4()) + uuid_list.append(sce_vnf_uuid) + db_sce_vnf = { + "uuid": sce_vnf_uuid, + "scenario_id": scenario_uuid, + "name": existing_vnf[0]["name"][:200] + "." + get_str(vnf, "member-vnf-index", 5), + "vnf_id": existing_vnf[0]["uuid"], + "member_vnf_index": int(vnf["member-vnf-index"]), + # TODO 'start-by-default': True + } + vnf_index2scevnf_uuid[int(vnf['member-vnf-index'])] = sce_vnf_uuid + vnf_index2vnf_uuid[int(vnf['member-vnf-index'])] = existing_vnf[0]["uuid"] + db_sce_vnfs.append(db_sce_vnf) + + # table ip_profiles (ip-profiles) + ip_profile_name2db_table_index = {} + for ip_profile in nsd.get("ip-profiles").itervalues(): + db_ip_profile = { + "ip_version": str(ip_profile["ip-profile-params"].get("ip-version", "ipv4")), + "subnet_address": str(ip_profile["ip-profile-params"].get("subnet-address")), + "gateway_address": str(ip_profile["ip-profile-params"].get("gateway-address")), + "dhcp_enabled": str(ip_profile["ip-profile-params"]["dhcp-params"].get("enabled", True)), + "dhcp_start_address": str(ip_profile["ip-profile-params"]["dhcp-params"].get("start-address")), + "dhcp_count": str(ip_profile["ip-profile-params"]["dhcp-params"].get("count")), + } + dns_list = [] + for dns in ip_profile["ip-profile-params"]["dns-server"].itervalues(): + dns_list.append(str(dns.get("address"))) + db_ip_profile["dns_address"] = ";".join(dns_list) + if ip_profile["ip-profile-params"].get('security-group'): + db_ip_profile["security_group"] = ip_profile["ip-profile-params"]['security-group'] + ip_profile_name2db_table_index[str(ip_profile["name"])] = db_ip_profiles_index + db_ip_profiles_index += 1 + db_ip_profiles.append(db_ip_profile) + + # table sce_nets (internal-vld) + for vld in nsd.get("vld").itervalues(): + sce_net_uuid = str(uuid4()) + uuid_list.append(sce_net_uuid) + db_sce_net = { + "uuid": sce_net_uuid, + "name": get_str(vld, "name", 255), + "scenario_id": scenario_uuid, + # "type": #TODO + "multipoint": not vld.get("type") == "ELINE", + # "external": #TODO + "description": get_str(vld, "description", 255), + } + # guess type of network + if vld.get("mgmt-network"): + db_sce_net["type"] = "bridge" + db_sce_net["external"] = True + elif vld.get("provider-network").get("overlay-type") == "VLAN": + db_sce_net["type"] = "data" + else: + db_sce_net["type"] = "bridge" + db_sce_nets.append(db_sce_net) + + # ip-profile, link db_ip_profile with db_sce_net + if vld.get("ip-profile-ref"): + ip_profile_name = vld.get("ip-profile-ref") + if ip_profile_name not in ip_profile_name2db_table_index: + raise KeyError("Error at 'nsd[{}]':'vld[{}]':'ip-profile-ref':'{}' references a non existing " + "'ip_profiles'".format( + str(nsd["id"]), str(vld["id"]), str(vld["ip-profile-ref"]))) + db_ip_profiles[ip_profile_name2db_table_index[ip_profile_name]]["sce_net_id"] = sce_net_uuid + + # table sce_interfaces (vld:vnfd-connection-point-ref) + for iface in vld.get("vnfd-connection-point-ref").itervalues(): + vnf_index = int(iface['member-vnf-index-ref']) + # check correct parameters + if vnf_index not in vnf_index2vnf_uuid: + raise KeyError("Error at 'nsd[{}]':'vld[{}]':'vnfd-connection-point-ref':'member-vnf-index-ref'" + ":'{}' references a non existing index at 'nsd':'constituent-vnfd'".format( + str(nsd["id"]), str(vld["id"]), str(iface["member-vnf-index-ref"]))) + + existing_ifaces = mydb.get_rows(SELECT=('i.uuid as uuid',), + FROM="interfaces as i join vms on i.vm_id=vms.uuid", + WHERE={'vnf_id': vnf_index2vnf_uuid[vnf_index], + 'external_name': get_str(iface, "vnfd-connection-point-ref", + 255)}) + if not existing_ifaces: + raise KeyError("Error at 'nsd[{}]':'vld[{}]':'vnfd-connection-point-ref':'vnfd-connection-point" + "-ref':'{}' references a non existing interface at VNFD '{}'".format( + str(nsd["id"]), str(vld["id"]), str(iface["vnfd-connection-point-ref"]), + str(iface.get("vnfd-id-ref"))[:255])) + + interface_uuid = existing_ifaces[0]["uuid"] + sce_interface_uuid = str(uuid4()) + uuid_list.append(sce_net_uuid) + db_sce_interface = { + "uuid": sce_interface_uuid, + "sce_vnf_id": vnf_index2scevnf_uuid[vnf_index], + "sce_net_id": sce_net_uuid, + "interface_id": interface_uuid, + # "ip_address": #TODO + } + db_sce_interfaces.append(db_sce_interface) + + db_tables = [ + {"scenarios": db_scenarios}, + {"sce_nets": db_sce_nets}, + {"ip_profiles": db_ip_profiles}, + {"sce_vnfs": db_sce_vnfs}, + {"sce_interfaces": db_sce_interfaces}, + ] + + logger.debug("create_vnf Deployment done vnfDict: %s", + yaml.safe_dump(db_tables, indent=4, default_flow_style=False) ) + mydb.new_rows(db_tables, uuid_list) + return nsd_uuid_list + except Exception as e: + logger.error("Exception {}".format(e)) + raise # NfvoException("Exception {}".format(e), HTTP_Bad_Request) + + def edit_scenario(mydb, tenant_id, scenario_id, data): data["uuid"] = scenario_id data["tenant_id"] = tenant_id @@ -2141,27 +2673,32 @@ def create_instance(mydb, tenant_id, instance_dict): myvim_thread_id = myvim_threads_id[default_datacenter_id] net_type = sce_net['type'] lookfor_filter = {'admin_state_up': True, 'status': 'ACTIVE'} #'shared': True - if sce_net["external"]: - if not net_name: + + if not net_name: + if sce_net["external"]: net_name = sce_net["name"] - if "netmap-use" in site or "netmap-create" in site: - create_network = False - lookfor_network = False - if "netmap-use" in site: - lookfor_network = True - if utils.check_valid_uuid(site["netmap-use"]): - filter_text = "scenario id '%s'" % site["netmap-use"] - lookfor_filter["id"] = site["netmap-use"] - else: - filter_text = "scenario name '%s'" % site["netmap-use"] - lookfor_filter["name"] = site["netmap-use"] - if "netmap-create" in site: - create_network = True - net_vim_name = net_name - if site["netmap-create"]: - net_vim_name = site["netmap-create"] - - elif sce_net['vim_id'] != None: + else: + net_name = "{}.{}".format(instance_name, sce_net["name"]) + net_name = net_name[:255] # limit length + + if "netmap-use" in site or "netmap-create" in site: + create_network = False + lookfor_network = False + if "netmap-use" in site: + lookfor_network = True + if utils.check_valid_uuid(site["netmap-use"]): + filter_text = "scenario id '%s'" % site["netmap-use"] + lookfor_filter["id"] = site["netmap-use"] + else: + filter_text = "scenario name '%s'" % site["netmap-use"] + lookfor_filter["name"] = site["netmap-use"] + if "netmap-create" in site: + create_network = True + net_vim_name = net_name + if site["netmap-create"]: + net_vim_name = site["netmap-create"] + elif sce_net["external"]: + if sce_net['vim_id'] != None: #there is a netmap at datacenter_nets database #TODO REVISE!!!! create_network = False lookfor_network = True @@ -2176,13 +2713,12 @@ def create_instance(mydb, tenant_id, instance_dict): net_vim_name = sce_net["name"] filter_text = "scenario name '%s'" % sce_net["name"] else: - if not net_name: - net_name = "%s.%s" %(instance_name, sce_net["name"]) - net_name = net_name[:255] #limit length net_vim_name = net_name create_network = True lookfor_network = False + + if lookfor_network: vim_nets = vim.get_network_list(filter_dict=lookfor_filter) if len(vim_nets) > 1: @@ -3312,8 +3848,7 @@ def get_sdn_net_id(mydb, tenant_id, datacenter, network_id): datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, tenant_id, datacenter) network = myvim.get_network_list(filter_dict=filter_dict) except vimconn.vimconnException as e: - print "vim_action Not possible to get_%s_list from VIM: %s " % (item, str(e)) - raise NfvoException("Not possible to get_{}_list from VIM: {}".format(item, str(e)), e.http_code) + raise NfvoException("Not possible to get_sdn_net_id from VIM: {}".format(str(e)), e.http_code) # ensure the network is defined if len(network) == 0: diff --git a/osm_ro/nfvo_db.py b/osm_ro/nfvo_db.py index 071d03ab..9d1a83cc 100644 --- a/osm_ro/nfvo_db.py +++ b/osm_ro/nfvo_db.py @@ -580,7 +580,8 @@ class nfvo_db(db_base.db_base): scenario_dict["cloud-config"] = yaml.load(scenario_dict["cloud_config"]) del scenario_dict["cloud_config"] #sce_vnfs - cmd = "SELECT uuid,name,vnf_id,description FROM sce_vnfs WHERE scenario_id='{}' ORDER BY created_at".format(scenario_dict['uuid']) + cmd = "SELECT uuid,name,member_vnf_index,vnf_id,description FROM sce_vnfs WHERE scenario_id='{}' "\ + "ORDER BY created_at".format(scenario_dict['uuid']) self.logger.debug(cmd) self.cur.execute(cmd) scenario_dict['vnfs'] = self.cur.fetchall() diff --git a/scenarios/examples/v3_3vdu_2vnf_nsd.yaml b/scenarios/examples/v3_3vdu_2vnf_nsd.yaml new file mode 100644 index 00000000..ffe8219e --- /dev/null +++ b/scenarios/examples/v3_3vdu_2vnf_nsd.yaml @@ -0,0 +1,59 @@ +nsd:nsd-catalog: + nsd: + - id: 3vdu_2vnf_nsd + name: 3vdu_2vnf_ns-name + short-name: 3vdu_2vnf-sname + description: 2 vnfs, eatch one with 3 cirros vdu + vendor: OSM + version: '1.0' + + # Place the logo as png in icons directory and provide the name here + logo: osm_2x.png + + # Specify the VNFDs that are part of this NSD + constituent-vnfd: + # The member-vnf-index needs to be unique, starting from 1 + # vnfd-id-ref is the id of the VNFD + # Multiple constituent VNFDs can be specified + - member-vnf-index: 1 + vnfd-id-ref: 3vdu_vnfd + - member-vnf-index: 2 + vnfd-id-ref: 3vdu_vnfd + + ip-profiles: + - description: Inter VNF Link + ip-profile-params: + gateway-address: 31.31.31.210 + ip-version: ipv4 + subnet-address: 31.31.31.0/24 + dns-server: + - address: 8.8.8.8 + - address: 8.8.8.9 + dhcp-params: + count: 200 + start-address: 31.31.31.2 + name: ipprofileA + + + vld: + # Networks for the VNFs + - id: vld1 + name: vld1-name + short-name: vld1-sname + type: ELAN + # vim-network-name: + # provider-network: + # overlay-type: VLAN + # segmentation_id: + ip-profile-ref: ipprofileA + vnfd-connection-point-ref: + # Specify the constituent VNFs + # member-vnf-index-ref - entry from constituent vnf + # vnfd-id-ref - VNFD id + # vnfd-connection-point-ref - connection point name in the VNFD + - member-vnf-index-ref: 1 + vnfd-id-ref: 3vdu_vnfd + vnfd-connection-point-ref: eth0 + - member-vnf-index-ref: 2 + vnfd-id-ref: 3vdu_vnfd + vnfd-connection-point-ref: eth0 diff --git a/test/basictest.sh b/test/basictest.sh index 3af91a71..e2169d89 100755 --- a/test/basictest.sh +++ b/test/basictest.sh @@ -187,12 +187,14 @@ then $openmano instance-scenario-delete -f complex3-instance || echo "fail" $openmano instance-scenario-delete -f complex4-instance || echo "fail" $openmano instance-scenario-delete -f complex5-instance || echo "fail" + $openmano instance-scenario-delete -f 3vdu_2vnf_nsd-instance || echo "fail" $openmano scenario-delete -f simple || echo "fail" $openmano scenario-delete -f complex || echo "fail" $openmano scenario-delete -f complex2 || echo "fail" $openmano scenario-delete -f complex3 || echo "fail" $openmano scenario-delete -f complex4 || echo "fail" $openmano scenario-delete -f complex5 || echo "fail" + $openmano scenario-delete -f osm_id=3vdu_2vnf_nsd || echo "fail" $openmano vnf-delete -f linux || echo "fail" $openmano vnf-delete -f linux_2VMs_v02 || echo "fail" $openmano vnf-delete -f dataplaneVNF_2VMs || echo "fail" @@ -201,6 +203,7 @@ then $openmano vnf-delete -f dataplaneVNF2 || echo "fail" $openmano vnf-delete -f dataplaneVNF3 || echo "fail" $openmano vnf-delete -f dataplaneVNF4 || echo "fail" + $openmano vnf-delete -f osm_id=3vdu_vnfd || echo "fail" elif [[ $action == "delete-all" ]] then @@ -245,10 +248,13 @@ then result=`$openmano datacenter-netmap-import -f` [[ $? != 0 ]] && echo "FAIL" && echo " $result" && $_exit 1 echo OK + result=`$openmano datacenter-netmap-create --name=default --vim-name=mgmt` + [[ $? != 0 ]] && echo "FAIL" && echo " $result" && $_exit 1 + echo OK elif [[ $action == "create" ]] then - for VNF in linux dataplaneVNF1 dataplaneVNF2 dataplaneVNF_2VMs dataplaneVNF_2VMs_v02 dataplaneVNF3 linux_2VMs_v02 dataplaneVNF4 + for VNF in linux dataplaneVNF1 dataplaneVNF2 dataplaneVNF_2VMs dataplaneVNF_2VMs_v02 dataplaneVNF3 linux_2VMs_v02 dataplaneVNF4 v3_3vdu_vnfd do printf "%-50s" "Creating VNF '${VNF}': " result=`$openmano vnf-create $DIRmano/vnfs/examples/${VNF}.yaml` @@ -257,7 +263,7 @@ then ! is_valid_uuid $vnf && echo FAIL && echo " $result" && $_exit 1 echo $vnf done - for NS in simple complex complex2 complex3 complex4 complex5 + for NS in simple complex complex2 complex3 complex4 complex5 v3_3vdu_2vnf_nsd do printf "%-50s" "Creating scenario '${NS}':" result=`$openmano scenario-create $DIRmano/scenarios/examples/${NS}.yaml` @@ -266,10 +272,10 @@ then echo $scenario done - for IS in simple complex complex2 complex3 complex5 + for IS in simple complex complex2 complex3 complex5 osm_id=3vdu_2vnf_nsd do printf "%-50s" "Creating instance-scenario '${IS}':" - result=`$openmano instance-scenario-create --scenario ${IS} --name ${IS}-instance` + result=`$openmano instance-scenario-create --scenario ${IS} --name ${IS#osm_id=}-instance` instance=`echo $result |gawk '{print $1}'` ! is_valid_uuid $instance && echo FAIL && echo " $result" && $_exit 1 echo $instance diff --git a/vnfs/examples/v3_3vdu_vnfd.yaml b/vnfs/examples/v3_3vdu_vnfd.yaml new file mode 100644 index 00000000..184bdcef --- /dev/null +++ b/vnfs/examples/v3_3vdu_vnfd.yaml @@ -0,0 +1,47 @@ +vnfd:vnfd-catalog: + vnfd: + - id: 3vdu_vnfd + name: 3vdu_vnfd-name + short-name: 3vdu-sname + description: Simple VNF example with a cirros and 3 vdu count + vendor: OSM + version: '1.0' + + # Place the logo as png in icons directory and provide the name here + logo: cirros-64.png + + # Management interface + mgmt-interface: + vdu-id: 3vduVM + + # Atleast one VDU need to be specified + vdu: + - id: 3vduVM + name: 3vduVM-name + description: 3vduVM-description + count: 3 + + # Flavour of the VM to be instantiated for the VDU + # flavor below can fit into m1.micro + vm-flavor: + vcpu-count: 1 + memory-mb: 256 + storage-gb: 2 + + # Image/checksum or image including the full path + image: 'cirros034' + #checksum: + + external-interface: + # Specify the external interfaces + # There can be multiple interfaces defined + - name: eth0 + virtual-interface: + type: OM-MGMT + bandwidth: '0' + vnfd-connection-point-ref: eth0 + + connection-point: + - name: eth0 + type: VPORT + -- 2.25.1