From 6aa0b2b620956618cfdd318af5399239c874243d Mon Sep 17 00:00:00 2001 From: Pablo Montes Moreno Date: Tue, 23 May 2017 18:33:12 +0200 Subject: [PATCH] External port implementation for SDN assist Change-Id: I8dec60c615ffe473adc96c4cd557af9d8de78e04 Signed-off-by: Pablo Montes Moreno --- database_utils/migrate_mano_db.sh | 23 +++- openmano | 119 +++++++++++++++- openmanod | 6 +- osm_ro/httpserver.py | 29 +++- osm_ro/nfvo.py | 219 +++++++++++++++++++++++++++++- osm_ro/openmano_schemas.py | 14 +- osm_ro/vim_thread.py | 24 +++- 7 files changed, 418 insertions(+), 16 deletions(-) diff --git a/database_utils/migrate_mano_db.sh b/database_utils/migrate_mano_db.sh index 27e7a899..d57194e0 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=20 +LAST_DB_VERSION=21 # Detect paths MYSQL=$(which mysql) @@ -188,6 +188,7 @@ fi #[ $OPENMANO_VER_NUM -ge 5004 ] && DB_VERSION=18 #0.5.4 => 18 #[ $OPENMANO_VER_NUM -ge 5005 ] && DB_VERSION=19 #0.5.5 => 19 #[ $OPENMANO_VER_NUM -ge 5009 ] && DB_VERSION=20 #0.5.9 => 20 +#[ $OPENMANO_VER_NUM -ge 5015 ] && DB_VERSION=21 #0.5.15 => 21 #TODO ... put next versions here function upgrade_to_1(){ @@ -751,6 +752,26 @@ function downgrade_from_20(){ echo "DELETE FROM schema_version WHERE version_int='20';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 } +function upgrade_to_21(){ + # echo " upgrade database from version 0.20 to version 0.21" + echo " edit 'instance_nets' to allow instance_scenario_id=None" + echo "ALTER TABLE instance_nets MODIFY COLUMN instance_scenario_id varchar(36) NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo " enlarge column 'dns_address' at table 'ip_profiles'" + echo "ALTER TABLE ip_profiles MODIFY dns_address varchar(255) DEFAULT NULL NULL "\ + "comment 'dns ip list separated by semicolon';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) VALUES (21, '0.21', '0.5.15', 'Edit instance_nets to allow instance_scenario_id=None and enlarge column dns_address at table ip_profiles', '2017-06-02');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} +function downgrade_from_21(){ + # echo " downgrade database from version 0.21 to version 0.20" + echo " edit 'instance_nets' to disallow instance_scenario_id=None" + #Delete all lines with a instance_scenario_id=NULL in order to disable this option + echo "DELETE FROM instance_nets WHERE instance_scenario_id IS NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_nets MODIFY COLUMN instance_scenario_id varchar(36) NOT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo " shorten column 'dns_address' at table 'ip_profiles'" + echo "ALTER TABLE ip_profiles MODIFY dns_address varchar(64) DEFAULT NULL NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "DELETE FROM schema_version WHERE version_int='21';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} + function upgrade_to_X(){ echo " change 'datacenter_nets'" echo "ALTER TABLE datacenter_nets ADD COLUMN vim_tenant_id VARCHAR(36) NOT NULL AFTER datacenter_id, DROP INDEX name_datacenter_id, ADD UNIQUE INDEX name_datacenter_id (name, datacenter_id, vim_tenant_id);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 diff --git a/openmano b/openmano index 111df432..6c0d15b2 100755 --- a/openmano +++ b/openmano @@ -141,6 +141,9 @@ def _print_verbose(mano_response, verbose_level=0): if verbose_level >=1: if content.get('created_at'): myoutput += " " + content['created_at'].ljust(20) + if content.get('sdn_attached_ports'): + #myoutput += " " + str(content['sdn_attached_ports']).ljust(20) + myoutput += "\nsdn_attached_ports:\n" + yaml.safe_dump(content['sdn_attached_ports'], indent=4, default_flow_style=False) if verbose_level >=2: new_line='\n' if content.get('type'): @@ -1057,6 +1060,8 @@ def datacenter_sdn_port_mapping_set(args): "No yaml/json has been provided specifying the SDN port mapping") port_mapping = yaml.load(datacenter_sdn_port_mapping_list(args)) + if 'error' in port_mapping: + raise OpenmanoCLIError("openmano client error: {}".format(port_mapping['error']['description'])) if len(port_mapping["sdn_port_mapping"]["ports_mapping"]) > 0: if not args.force: r = raw_input("Datacenter %s already contains a port mapping. Overwrite? (y/N)? " % (datacenter)) @@ -1279,6 +1284,82 @@ def vim_action(args): args.verbose=0 return _print_verbose(mano_response, args.verbose) +def _get_items(item, item_name_id=None, datacenter=None, tenant=None): + URLrequest = "http://%s:%s/openmano" %(mano_host, mano_port) + if tenant: + URLrequest += "/" + tenant + if datacenter: + URLrequest += "/vim/" + datacenter + if item: + URLrequest += "/" + item +"s" + if item_name_id: + URLrequest += "/" + item_name_id + mano_response = requests.get(URLrequest) + logger.debug("openmano response: %s", mano_response.text ) + + return mano_response + +def vim_net_sdn_attach(args): + #Verify the network exists in the vim + tenant = _get_tenant() + datacenter = _get_datacenter(args.datacenter, tenant) + result = _get_items('network', item_name_id=args.vim_net, datacenter=datacenter, tenant=tenant) + content = yaml.load(result.content) + if 'networks' in content: + raise OpenmanoCLIError('More than one network in the vim named ' + args.vim_net + '. Use uuid instead') + if 'error' in content: + raise OpenmanoCLIError(yaml.safe_dump(content)) + network_uuid = content['network']['id'] + + #Make call to attach the dataplane port to the SND network associated to the vim network + headers_req = {'content-type': 'application/yaml'} + payload_req = {'port': args.port} + if args.vlan: + payload_req['vlan'] = int(args.vlan) + if args.mac: + payload_req['mac'] = args.mac + + URLrequest = "http://%s:%s/openmano/%s/vim/%s/network/%s/attach" % (mano_host, mano_port, tenant, datacenter, network_uuid) + logger.debug("openmano request: %s", payload_req) + mano_response = requests.post(URLrequest, headers=headers_req, data=json.dumps(payload_req)) + logger.debug("openmano response: %s", mano_response.text) + result = _print_verbose(mano_response, args.verbose) + + return + + +def vim_net_sdn_detach(args): + if not args.all and not args.id: + print "--all or --id must be used" + return 1 + + # Verify the network exists in the vim + tenant = _get_tenant() + datacenter = _get_datacenter(args.datacenter, tenant) + result = _get_items('network', item_name_id=args.vim_net, datacenter=datacenter, tenant=tenant) + content = yaml.load(result.content) + if 'networks' in content: + raise OpenmanoCLIError('More than one network in the vim named ' + args.vim_net + '. Use uuid instead') + if 'error' in content: + raise OpenmanoCLIError(yaml.safe_dump(content)) + network_uuid = content['network']['id'] + + if not args.force: + r = raw_input("Confirm action' (y/N)? ") + if len(r) == 0 or r[0].lower() != "y": + return 0 + + if args.id: + URLrequest = "http://%s:%s/openmano/%s/vim/%s/network/%s/detach/%s" % ( + mano_host, mano_port, tenant, datacenter, network_uuid, args.id) + else: + URLrequest = "http://%s:%s/openmano/%s/vim/%s/network/%s/detach" % ( + mano_host, mano_port, tenant, datacenter, network_uuid) + mano_response = requests.delete(URLrequest) + logger.debug("openmano response: %s", mano_response.text) + result = _print_verbose(mano_response, args.verbose) + + return def datacenter_net_action(args): if args.action == "net-update": @@ -1798,24 +1879,50 @@ if __name__=="__main__": if item=='netmap-import': datacenter_action_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation") datacenter_action_parser.set_defaults(func=datacenter_netmap_action, action=item) - + + # =======================vim_net_sdn_xxx section======================= + # vim_net_sdn_attach + vim_net_sdn_attach_parser = subparsers.add_parser('vim-net-sdn-attach', + parents=[parent_parser], + help="Specify the port to access to an external network using SDN") + vim_net_sdn_attach_parser.add_argument("vim_net", action="store", + help="Name/id of the network in the vim that will be used to connect to the external network") + vim_net_sdn_attach_parser.add_argument("port", action="store", help="Specifies the port in the dataplane switch to access to the external network") + vim_net_sdn_attach_parser.add_argument("--vlan", action="store", help="Specifies the vlan (if any) to use in the defined port") + vim_net_sdn_attach_parser.add_argument("--mac", action="store", help="Specifies the MAC (if known) of the physical device that will be reachable by this external port") + vim_net_sdn_attach_parser.add_argument("--datacenter", action="store", help="specifies the datacenter") + vim_net_sdn_attach_parser.set_defaults(func=vim_net_sdn_attach) + + # vim_net_sdn_detach + vim_net_sdn_detach_parser = subparsers.add_parser('vim-net-sdn-detach', + parents=[parent_parser], + help="Remove the port information to access to an external network using SDN") + + vim_net_sdn_detach_parser.add_argument("vim_net", action="store", help="Name/id of the vim network") + vim_net_sdn_detach_parser.add_argument("--id", action="store",help="Specify the uuid of the external ports from this network to be detached") + vim_net_sdn_detach_parser.add_argument("--all", action="store_true", help="Detach all external ports from this network") + vim_net_sdn_detach_parser.add_argument("-f", "--force", action="store_true", help="forces clearing without asking") + vim_net_sdn_detach_parser.add_argument("--datacenter", action="store", help="specifies the datacenter") + vim_net_sdn_detach_parser.set_defaults(func=vim_net_sdn_detach) + # ======================= + for item in ("network", "tenant", "image"): if item=="network": - commnad_name = 'vim-net' + command_name = 'vim-net' else: - commnad_name = 'vim-'+item - vim_item_list_parser = subparsers.add_parser(commnad_name + '-list', parents=[parent_parser], help="list the vim " + item + "s") + command_name = 'vim-'+item + vim_item_list_parser = subparsers.add_parser(command_name + '-list', parents=[parent_parser], help="list the vim " + item + "s") vim_item_list_parser.add_argument("name", nargs='?', help="name or uuid of the " + item + "s") vim_item_list_parser.add_argument("--datacenter", action="store", help="specifies the datacenter") vim_item_list_parser.set_defaults(func=vim_action, item=item, action="list") - vim_item_del_parser = subparsers.add_parser(commnad_name + '-delete', parents=[parent_parser], help="list the vim " + item + "s") + vim_item_del_parser = subparsers.add_parser(command_name + '-delete', parents=[parent_parser], help="list the vim " + item + "s") vim_item_del_parser.add_argument("name", help="name or uuid of the " + item + "s") vim_item_del_parser.add_argument("--datacenter", action="store", help="specifies the datacenter") vim_item_del_parser.set_defaults(func=vim_action, item=item, action="delete") if item == "network" or item == "tenant": - vim_item_create_parser = subparsers.add_parser(commnad_name + '-create', parents=[parent_parser], help="create a "+item+" at vim") + vim_item_create_parser = subparsers.add_parser(command_name + '-create', parents=[parent_parser], help="create a "+item+" at vim") vim_item_create_parser.add_argument("file", nargs='?', help="descriptor of the %s. Must be a file or yaml/json text" % item).completer = FilesCompleter vim_item_create_parser.add_argument("--name", action="store", help="name of the %s" % item ) vim_item_create_parser.add_argument("--datacenter", action="store", help="specifies the datacenter") diff --git a/openmanod b/openmanod index e8bd2da4..8f8c3f1b 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.14-r523" -version_date = "May 2017" -database_version = 20 #expected database schema version +__version__ = "0.5.15-r524" +version_date = "Jun 2017" +database_version = 21 #expected database schema version global global_config global logger diff --git a/osm_ro/httpserver.py b/osm_ro/httpserver.py index d6dc0c7a..94544f6b 100644 --- a/osm_ro/httpserver.py +++ b/osm_ro/httpserver.py @@ -43,7 +43,7 @@ from openmano_schemas import vnfd_schema_v01, vnfd_schema_v02, \ tenant_schema, tenant_edit_schema,\ datacenter_schema, datacenter_edit_schema, datacenter_action_schema, datacenter_associate_schema,\ object_schema, netmap_new_schema, netmap_edit_schema, sdn_controller_schema, sdn_controller_edit_schema, \ - sdn_port_mapping_schema + sdn_port_mapping_schema, sdn_external_port_schema import nfvo import utils @@ -915,6 +915,33 @@ def http_deassociate_datacenters(tenant_id, datacenter_id): logger.error("Unexpected exception: ", exc_info=True) bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) +@bottle.route(url_base + '//vim//network//attach', method='POST') +def http_post_vim_net_sdn_attach(tenant_id, datacenter_id, network_id): + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content, _ = format_in(sdn_external_port_schema) + try: + data = nfvo.vim_net_sdn_attach(mydb, tenant_id, datacenter_id, network_id, http_content) + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_vim_net_sdn_attach 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 + '//vim//network//detach', method='DELETE') +@bottle.route(url_base + '//vim//network//detach/', method='DELETE') +def http_delete_vim_net_sdn_detach(tenant_id, datacenter_id, network_id, port_id=None): + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + try: + data = nfvo.vim_net_sdn_detach(mydb, tenant_id, datacenter_id, network_id, port_id) + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_delete_vim_net_sdn_detach 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 + '//vim//', method='GET') @bottle.route(url_base + '//vim///', method='GET') diff --git a/osm_ro/nfvo.py b/osm_ro/nfvo.py index 3e59285b..93300472 100644 --- a/osm_ro/nfvo.py +++ b/osm_ro/nfvo.py @@ -44,6 +44,7 @@ import nfvo_db from threading import Lock from time import time from lib_osm_openvim import ovim as ovim_module +from lib_osm_openvim.ovim import ovimException global global_config global vimconn_imported @@ -3075,6 +3076,137 @@ def datacenter_new_netmap(mydb, tenant_id, datacenter, action_dict=None): net_list.append(net_nfvo) return net_list +def get_sdn_net_id(mydb, tenant_id, datacenter, network_id): + # obtain all network data + try: + if utils.check_valid_uuid(network_id): + filter_dict = {"id": network_id} + else: + filter_dict = {"name": 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) + + # ensure the network is defined + if len(network) == 0: + raise NfvoException("Network {} is not present in the system".format(network_id), + HTTP_Bad_Request) + + # ensure there is only one network with the provided name + if len(network) > 1: + raise NfvoException("Multiple networks present in vim identified by {}".format(network_id), HTTP_Bad_Request) + + # ensure it is a dataplane network + if network[0]['type'] != 'data': + return None + + # ensure we use the id + network_id = network[0]['id'] + + # search in dabase mano_db in table instance nets for the sdn_net_id that corresponds to the vim_net_id==network_id + # and with instance_scenario_id==NULL + #search_dict = {'vim_net_id': network_id, 'instance_scenario_id': None} + search_dict = {'vim_net_id': network_id} + + try: + #sdn_network_id = mydb.get_rows(SELECT=('sdn_net_id',), FROM='instance_nets', WHERE=search_dict)[0]['sdn_net_id'] + result = mydb.get_rows(SELECT=('sdn_net_id',), FROM='instance_nets', WHERE=search_dict) + except db_base_Exception as e: + raise NfvoException("db_base_Exception obtaining SDN network to associated to vim network {}".format( + network_id) + str(e), HTTP_Internal_Server_Error) + + sdn_net_counter = 0 + for net in result: + if net['sdn_net_id'] != None: + sdn_net_counter+=1 + sdn_net_id = net['sdn_net_id'] + + if sdn_net_counter == 0: + return None + elif sdn_net_counter == 1: + return sdn_net_id + else: + raise NfvoException("More than one SDN network is associated to vim network {}".format( + network_id), HTTP_Internal_Server_Error) + +def get_sdn_controller_id(mydb, datacenter): + # Obtain sdn controller id + config = mydb.get_rows(SELECT=('config',), FROM='datacenters', WHERE={'uuid': datacenter})[0].get('config', '{}') + if not config: + return None + + return yaml.load(config).get('sdn-controller') + +def vim_net_sdn_attach(mydb, tenant_id, datacenter, network_id, descriptor): + try: + sdn_network_id = get_sdn_net_id(mydb, tenant_id, datacenter, network_id) + if not sdn_network_id: + raise NfvoException("No SDN network is associated to vim-network {}".format(network_id), HTTP_Internal_Server_Error) + + #Obtain sdn controller id + controller_id = get_sdn_controller_id(mydb, datacenter) + if not controller_id: + raise NfvoException("No SDN controller is set for datacenter {}".format(datacenter), HTTP_Internal_Server_Error) + + #Obtain sdn controller info + sdn_controller = ovim.show_of_controller(controller_id) + + port_data = { + 'name': 'external_port', + 'net_id': sdn_network_id, + 'ofc_id': controller_id, + 'switch_dpid': sdn_controller['dpid'], + 'switch_port': descriptor['port'] + } + + if 'vlan' in descriptor: + port_data['vlan'] = descriptor['vlan'] + if 'mac' in descriptor: + port_data['mac'] = descriptor['mac'] + + result = ovim.new_port(port_data) + except ovimException as e: + raise NfvoException("ovimException attaching SDN network {} to vim network {}".format( + sdn_network_id, network_id) + str(e), HTTP_Internal_Server_Error) + except db_base_Exception as e: + raise NfvoException("db_base_Exception attaching SDN network to vim network {}".format( + network_id) + str(e), HTTP_Internal_Server_Error) + + return 'Port uuid: '+ result + +def vim_net_sdn_detach(mydb, tenant_id, datacenter, network_id, port_id=None): + if port_id: + filter = {'uuid': port_id} + else: + sdn_network_id = get_sdn_net_id(mydb, tenant_id, datacenter, network_id) + if not sdn_network_id: + raise NfvoException("No SDN network is associated to vim-network {}".format(network_id), + HTTP_Internal_Server_Error) + #in case no port_id is specified only ports marked as 'external_port' will be detached + filter = {'name': 'external_port', 'net_id': sdn_network_id} + + try: + port_list = ovim.get_ports(columns={'uuid'}, filter=filter) + except ovimException as e: + raise NfvoException("ovimException obtaining external ports for net {}. ".format(network_id) + str(e), + HTTP_Internal_Server_Error) + + if len(port_list) == 0: + raise NfvoException("No ports attached to the network {} were found with the requested criteria".format(network_id), + HTTP_Bad_Request) + + port_uuid_list = [] + for port in port_list: + try: + port_uuid_list.append(port['uuid']) + ovim.delete_port(port['uuid']) + except ovimException as e: + raise NfvoException("ovimException deleting port {} for net {}. ".format(port['uuid'], network_id) + str(e), HTTP_Internal_Server_Error) + + return 'Detached ports uuid: {}'.format(','.join(port_uuid_list)) def vim_action_get(mydb, tenant_id, datacenter, item, name): #get datacenter info @@ -3089,9 +3221,32 @@ def vim_action_get(mydb, tenant_id, datacenter, item, name): if item=="networks": #filter_dict['tenant_id'] = myvim['tenant_id'] content = myvim.get_network_list(filter_dict=filter_dict) + + if len(content) == 0: + raise NfvoException("Network {} is not present in the system. ".format(name), + HTTP_Bad_Request) + + #Update the networks with the attached ports + for net in content: + sdn_network_id = get_sdn_net_id(mydb, tenant_id, datacenter, net['id']) + if sdn_network_id != None: + try: + #port_list = ovim.get_ports(columns={'uuid', 'switch_port', 'vlan'}, filter={'name': 'external_port', 'net_id': sdn_network_id}) + port_list = ovim.get_ports(columns={'uuid', 'switch_port', 'vlan','name'}, filter={'net_id': sdn_network_id}) + except ovimException as e: + raise NfvoException("ovimException obtaining external ports for net {}. ".format(network_id) + str(e), HTTP_Internal_Server_Error) + #Remove field name and if port name is external_port save it as 'type' + for port in port_list: + if port['name'] == 'external_port': + port['type'] = "External" + del port['name'] + net['sdn_network_id'] = sdn_network_id + net['sdn_attached_ports'] = port_list + elif item=="tenants": content = myvim.get_tenant_list(filter_dict=filter_dict) elif item == "images": + content = myvim.get_image_list(filter_dict=filter_dict) else: raise NfvoException(item + "?", HTTP_Method_Not_Allowed) @@ -3128,6 +3283,36 @@ def vim_action_delete(mydb, tenant_id, datacenter, item, name): try: if item=="networks": + # If there is a SDN network associated to the vim-network, proceed to clear the relationship and delete it + sdn_network_id = get_sdn_net_id(mydb, tenant_id, datacenter, item_id) + if sdn_network_id != None: + #Delete any port attachment to this network + try: + port_list = ovim.get_ports(columns={'uuid'}, filter={'net_id': sdn_network_id}) + except ovimException as e: + raise NfvoException( + "ovimException obtaining external ports for net {}. ".format(network_id) + str(e), + HTTP_Internal_Server_Error) + + # By calling one by one all ports to be detached we ensure that not only the external_ports get detached + for port in port_list: + vim_net_sdn_detach(mydb, tenant_id, datacenter, item_id, port['uuid']) + + #Delete from 'instance_nets' the correspondence between the vim-net-id and the sdn-net-id + try: + mydb.delete_row(FROM='instance_nets', WHERE={'instance_scenario_id': None, 'sdn_net_id': sdn_network_id, 'vim_net_id': item_id}) + except db_base_Exception as e: + raise NfvoException("Error deleting correspondence for VIM/SDN dataplane networks{}: ".format(correspondence) + + str(e), HTTP_Internal_Server_Error) + + #Delete the SDN network + try: + ovim.delete_network(sdn_network_id) + except ovimException as e: + logger.error("ovimException deleting SDN network={} ".format(sdn_network_id) + str(e), exc_info=True) + raise NfvoException("ovimException deleting SDN network={} ".format(sdn_network_id) + str(e), + HTTP_Internal_Server_Error) + content = myvim.delete_network(item_id) elif item=="tenants": content = myvim.delete_tenant(item_id) @@ -3157,6 +3342,32 @@ def vim_action_create(mydb, tenant_id, datacenter, item, descriptor): net_ipprofile = net.pop("ip_profile", None) net_vlan = net.pop("vlan", None) content = myvim.new_network(net_name, net_type, net_ipprofile, shared=net_public, vlan=net_vlan) #, **net) + + #If the datacenter has a SDN controller defined and the network is of dataplane type, then create the sdn network + if get_sdn_controller_id(mydb, datacenter) != None and (net_type == 'data' or net_type == 'ptp'): + try: + sdn_network = {} + sdn_network['vlan'] = net_vlan + sdn_network['type'] = net_type + sdn_network['name'] = net_name + ovim_content = ovim.new_network(sdn_network) + except ovimException as e: + self.logger.error("ovimException creating SDN network={} ".format( + sdn_network) + str(e), exc_info=True) + raise NfvoException("ovimException creating SDN network={} ".format(sdn_network) + str(e), + HTTP_Internal_Server_Error) + + # Save entry in in dabase mano_db in table instance_nets to stablish a dictionary vim_net_id <->sdn_net_id + # use instance_scenario_id=None to distinguish from real instaces of nets + correspondence = {'instance_scenario_id': None, 'sdn_net_id': ovim_content, 'vim_net_id': content} + #obtain datacenter_tenant_id + correspondence['datacenter_tenant_id'] = mydb.get_rows(SELECT=('uuid',), FROM='datacenter_tenants', WHERE={'datacenter_id': datacenter})[0]['uuid'] + + try: + mydb.new_row('instance_nets', correspondence, add_uuid=True) + except db_base_Exception as e: + raise NfvoException("Error saving correspondence for VIM/SDN dataplane networks{}: ".format(correspondence) + + str(e), HTTP_Internal_Server_Error) elif item=="tenants": tenant = descriptor["tenant"] content = myvim.new_tenant(tenant["name"], tenant.get("description")) @@ -3250,9 +3461,11 @@ def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id): result["sdn-controller"] = controller_id result["dpid"] = sdn_controller["dpid"] - if result["sdn-controller"] == None or result["dpid"] == None: - raise NfvoException("Not all SDN controller information for datacenter {} could be found: {}".format(datacenter_id, result), - HTTP_Internal_Server_Error) + if result["sdn-controller"] == None: + raise NfvoException("SDN controller is not defined for datacenter {}".format(datacenter_id), HTTP_Bad_Request) + if result["dpid"] == None: + raise NfvoException("It was not possible to determine DPID for SDN controller {}".format(result["sdn-controller"]), + HTTP_Internal_Server_Error) if len(maps) == 0: return result diff --git a/osm_ro/openmano_schemas.py b/osm_ro/openmano_schemas.py index 1dfcffe4..765c05f0 100644 --- a/osm_ro/openmano_schemas.py +++ b/osm_ro/openmano_schemas.py @@ -1104,7 +1104,7 @@ instance_scenario_action_schema = { sdn_controller_properties={ "name": name_schema, - "dpid": {"type":"string", "pattern":"^[0-9a-fA-F][02468aceACE](:[0-9a-fA-F]{2}){7}$"}, + "dpid": {"type":"string", "pattern":"^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){7}$"}, "ip": ip_schema, "port": port_schema, "type": {"type": "string", "enum": ["opendaylight","floodlight","onos"]}, @@ -1172,4 +1172,16 @@ sdn_port_mapping_schema = { } }, "required": ["sdn_port_mapping"] +} + +sdn_external_port_schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title":"External port ingformation", + "type": "object", + "properties": { + "port": {"type" : "string", "minLength":1, "maxLength":60}, + "vlan": vlan_schema, + "mac": mac_schema + }, + "required": ["port"] } \ No newline at end of file diff --git a/osm_ro/vim_thread.py b/osm_ro/vim_thread.py index 373fe7bf..8a987ac2 100644 --- a/osm_ro/vim_thread.py +++ b/osm_ro/vim_thread.py @@ -161,7 +161,7 @@ class vim_thread(threading.Thread): FROM="instance_interfaces as ii left join instance_nets as ine on " "ii.instance_net_id=ine.uuid left join instance_vms as iv on " "ii.instance_vm_id=iv.uuid", - SELECT=("ii.uuid as iface_id", "ine.uuid as net_id", "iv.uuid as vm_id", "sdn_net_id"), + SELECT=("ii.uuid as iface_id", "ine.uuid as net_id", "iv.uuid as vm_id", "sdn_net_id", "vim_net_id"), WHERE=where_) if len(db_ifaces)>1: self.logger.critical("Refresing interfaces. " @@ -172,6 +172,14 @@ class vim_thread(threading.Thread): continue else: db_iface = db_ifaces[0] + #If there is no sdn_net_id, check if it is because an already created vim network is being used + #in that case, the sdn_net_id will be in that entry of the instance_nets table + if not db_iface.get("sdn_net_id"): + result = self.db.get_rows(SELECT=('sdn_net_id',), FROM='instance_nets', + WHERE={'vim_net_id': db_iface.get("vim_net_id"), 'instance_scenario_id': None, "datacenter_tenant_id": self.datacenter_tenant_id}) + if len(result) == 1: + db_iface["sdn_net_id"] = result[0]['sdn_net_id'] + if db_iface.get("sdn_net_id") and interface.get("compute_node") and interface.get("pci"): sdn_net_id = db_iface["sdn_net_id"] sdn_port_name = sdn_net_id + "." + db_iface["vm_id"] @@ -549,6 +557,20 @@ class vim_thread(threading.Thread): self._remove_refresh("get-net", net_id) result = self.vim.delete_network(net_id) if sdn_net_id: + #Delete any attached port to this sdn network + #At this point, there will be ports associated to this network in case it was manually done using 'openmano vim-net-sdn-attach' + try: + port_list = self.ovim.get_ports(columns={'uuid'}, filter={'name': 'external_port', 'net_id': sdn_net_id}) + except ovimException as e: + raise vimconn.vimconnException( + "ovimException obtaining external ports for net {}. ".format(sdn_net_id) + str(e)) + + for port in port_list: + try: + self.ovim.delete_port(port['uuid']) + except ovimException as e: + raise vimconn.vimconnException( + "ovimException deleting port {} for net {}. ".format(port['uuid'], sdn_net_id) + str(e)) with self.db_lock: self.ovim.delete_network(sdn_net_id) return True, result -- 2.17.1