X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=osm_ro%2Fhttpserver.py;h=d77846c8d161573a959ef7c5175fd75cb00a875e;hb=868220c566cfd302a38f9a45a75f4dbd4ebbf395;hp=a0216e12f2f209853703f77e240bf54565b57291;hpb=2c290ca4088492a3c32bb6ab218d0004da68f6ea;p=osm%2FRO.git diff --git a/osm_ro/httpserver.py b/osm_ro/httpserver.py index a0216e12..d77846c8 100644 --- a/osm_ro/httpserver.py +++ b/osm_ro/httpserver.py @@ -42,7 +42,9 @@ from openmano_schemas import vnfd_schema_v01, vnfd_schema_v02, \ scenario_action_schema, instance_scenario_action_schema, instance_scenario_create_schema_v01, \ tenant_schema, tenant_edit_schema,\ datacenter_schema, datacenter_edit_schema, datacenter_action_schema, datacenter_associate_schema,\ - object_schema, netmap_new_schema, netmap_edit_schema + object_schema, netmap_new_schema, netmap_edit_schema, sdn_controller_schema, sdn_controller_edit_schema, \ + sdn_port_mapping_schema, sdn_external_port_schema + import nfvo import utils from db_base import db_base_Exception @@ -182,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 " @@ -210,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: @@ -227,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: @@ -303,6 +309,9 @@ def enable_cors(): '''Don't know yet if really needed. Keep it just in case''' bottle.response.headers['Access-Control-Allow-Origin'] = '*' +@bottle.route(url_base + '/version', method='GET') +def http_get_version(): + return nfvo.get_version() # # VNFs # @@ -506,7 +515,7 @@ def http_get_datacenter_id(tenant_id, datacenter_id): @bottle.route(url_base + '/datacenters', method='POST') def http_post_datacenters(): - '''insert a tenant into the catalogue. ''' + '''insert a datacenter into the catalogue. ''' #parse input data logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) http_content,_ = format_in( datacenter_schema ) @@ -544,6 +553,138 @@ def http_edit_datacenter_id(datacenter_id_name): logger.error("Unexpected exception: ", exc_info=True) bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) +@bottle.route(url_base + '//sdn_controllers', method='POST') +def http_post_sdn_controller(tenant_id): + '''insert a sdn controller into the catalogue. ''' + #parse input data + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content,_ = format_in( sdn_controller_schema ) + try: + logger.debug("tenant_id: "+tenant_id) + #logger.debug("content: {}".format(http_content['sdn_controller'])) + + data = nfvo.sdn_controller_create(mydb, tenant_id, http_content['sdn_controller']) + return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, data)}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_sdn_controller 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 + '//sdn_controllers/', method='PUT') +def http_put_sdn_controller_update(tenant_id, controller_id): + '''Update sdn controller''' + #parse input data + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content,_ = format_in( sdn_controller_edit_schema ) +# r = utils.remove_extra_items(http_content, datacenter_schema) +# if r: +# logger.debug("Remove received extra items %s", str(r)) + try: + #logger.debug("tenant_id: "+tenant_id) + logger.debug("content: {}".format(http_content['sdn_controller'])) + + data = nfvo.sdn_controller_update(mydb, tenant_id, controller_id, http_content['sdn_controller']) + return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, controller_id)}) + + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_sdn_controller 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 + '//sdn_controllers', method='GET') +def http_get_sdn_controller(tenant_id): + '''get sdn controllers list, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + + data = {'sdn_controllers': nfvo.sdn_controller_list(mydb, tenant_id)} + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_get_sdn_controller 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 + '//sdn_controllers/', method='GET') +def http_get_sdn_controller_id(tenant_id, controller_id): + '''get sdn controller details, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + data = nfvo.sdn_controller_list(mydb, tenant_id, controller_id) + return format_out({"sdn_controllers": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_get_sdn_controller_id 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 + '//sdn_controllers/', method='DELETE') +def http_delete_sdn_controller_id(tenant_id, controller_id): + '''delete sdn controller, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + data = nfvo.sdn_controller_delete(mydb, tenant_id, controller_id) + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_delete_sdn_controller_id 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 + '//datacenters//sdn_mapping', method='POST') +def http_post_datacenter_sdn_port_mapping(tenant_id, datacenter_id): + '''Set the sdn port mapping for a datacenter. ''' + #parse input data + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content, _ = format_in(sdn_port_mapping_schema) +# r = utils.remove_extra_items(http_content, datacenter_schema) +# if r: +# logger.debug("Remove received extra items %s", str(r)) + try: + data = nfvo.datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, http_content['sdn_port_mapping']) + return format_out({"sdn_port_mapping": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_datacenter_sdn_port_mapping 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 + '//datacenters//sdn_mapping', method='GET') +def http_get_datacenter_sdn_port_mapping(tenant_id, datacenter_id): + '''get datacenter sdn mapping details, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + + data = nfvo.datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id) + return format_out({"sdn_port_mapping": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_get_datacenter_sdn_port_mapping 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 + '//datacenters//sdn_mapping', method='DELETE') +def http_delete_datacenter_sdn_port_mapping(tenant_id, datacenter_id): + '''clean datacenter sdn mapping, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + data = nfvo.datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id) + return format_out({"result": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_delete_datacenter_sdn_port_mapping 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 + '//datacenters//networks', method='GET') #deprecated @bottle.route(url_base + '//datacenters//netmaps', method='GET') @@ -739,6 +880,30 @@ def http_associate_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 + '//datacenters/', method='PUT') +def http_associate_datacenters_edit(tenant_id, datacenter_id): + '''associate an existing datacenter to a this tenant. ''' + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + #parse input data + http_content,_ = format_in( datacenter_associate_schema ) + r = utils.remove_extra_items(http_content, datacenter_associate_schema) + if r: + logger.debug("Remove received extra items %s", str(r)) + try: + id_ = nfvo.edit_datacenter_to_tenant(mydb, tenant_id, datacenter_id, + http_content['datacenter'].get('vim_tenant'), + http_content['datacenter'].get('vim_tenant_name'), + http_content['datacenter'].get('vim_username'), + http_content['datacenter'].get('vim_password'), + http_content['datacenter'].get('config') + ) + return http_get_datacenter_id(tenant_id, id_) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_associate_datacenters_edit 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 + '//datacenters/', method='DELETE') def http_deassociate_datacenters(tenant_id, datacenter_id): @@ -754,6 +919,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') @@ -807,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 @@ -845,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) @@ -869,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, @@ -991,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): @@ -1043,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 @@ -1188,10 +1434,18 @@ def http_get_instance_id(tenant_id, instance_id): nfvo.refresh_instance(mydb, tenant_id, instance_dict) except (nfvo.NfvoException, db_base_Exception) as e: logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e)) - #obtain data with results upated + # obtain data with results upated instance = mydb.get_instance_scenario(instance_id, tenant_id) + # Workaround to SO, convert vnfs:vms:interfaces:ip_address from ";" separated list to report the first value + for vnf in instance.get("vnfs", ()): + for vm in vnf.get("vms", ()): + for iface in vm.get("interfaces", ()): + if iface.get("ip_address"): + index = iface["ip_address"].find(";") + if index >= 0: + iface["ip_address"] = iface["ip_address"][:index] convert_datetime2str(instance) - #print json.dumps(instance, indent=4) + # print json.dumps(instance, indent=4) return format_out(instance) except (nfvo.NfvoException, db_base_Exception) as e: logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e))) @@ -1224,7 +1478,12 @@ def http_delete_instance_id(tenant_id, instance_id): @bottle.route(url_base + '//instances//action', method='POST') def http_post_instance_scenario_action(tenant_id, instance_id): - '''take an action over a scenario instance''' + """ + take an action over a scenario instance + :param tenant_id: tenant where user belongs to + :param instance_id: instance indentity + :return: + """ logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) # parse input data http_content, _ = format_in(instance_scenario_action_schema) @@ -1251,6 +1510,30 @@ def http_post_instance_scenario_action(tenant_id, instance_id): bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) +@bottle.route(url_base + '//instances//action', method='GET') +@bottle.route(url_base + '//instances//action/', method='GET') +def http_get_instance_scenario_action(tenant_id, instance_id, action_id=None): + """ + List the actions done over an instance, or the action details + :param tenant_id: tenant where user belongs to. Can be "any" to ignore + :param instance_id: instance id, can be "any" to get actions of all instances + :return: + """ + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + try: + # check valid tenant_id + if tenant_id != "any": + nfvo.check_tenant(mydb, tenant_id) + data = nfvo.instance_action_get(mydb, tenant_id, instance_id, action_id) + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_get_instance_scenario_action 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.error(400) @bottle.error(401) @bottle.error(404)