Feature 1413 resiliency to single component failure
[osm/RO.git] / osm_ro / httpserver.py
index d6dc0c7..d77846c 100644 (file)
@@ -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
@@ -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:
@@ -915,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 + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/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 + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/detach', method='DELETE')
+@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/detach/<port_id>', 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 + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
@@ -968,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
@@ -1006,9 +1037,12 @@ def http_get_vnf_id(tenant_id,vnf_id):
 
 @bottle.route(url_base + '/<tenant_id>/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)
@@ -1030,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/<tenant_id>/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 + '/<tenant_id>/vnfs/<vnf_id>', 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, 
@@ -1152,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/<tenant_id>/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 + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
 def http_post_scenario_action(tenant_id, scenario_id):
@@ -1204,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
@@ -1349,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)))
@@ -1385,7 +1478,12 @@ def http_delete_instance_id(tenant_id, instance_id):
 
 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/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)
@@ -1412,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 + '/<tenant_id>/instances/<instance_id>/action', method='GET')
+@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action/<action_id>', 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)