Fixed length of some DB fields: path and name in table images, name in table flavors
[osm/openvim.git] / httpserver.py
index 2be5164..5d48beb 100644 (file)
@@ -2,7 +2,7 @@
 
 ##
 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
 
 ##
 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
-# This file is part of openmano
+# This file is part of openvim
 # All Rights Reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # All Rights Reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -26,15 +26,19 @@ This is the thread for the http server North API.
 Two thread will be launched, with normal and administrative permissions.
 '''
 
 Two thread will be launched, with normal and administrative permissions.
 '''
 
-__author__="Alfonso Tierno"
+__author__="Alfonso Tierno, Gerardo Garcia"
 __date__ ="$10-jul-2014 12:07:15$"
 
 import bottle
 __date__ ="$10-jul-2014 12:07:15$"
 
 import bottle
+import urlparse
 import yaml
 import json
 import threading
 import datetime
 import yaml
 import json
 import threading
 import datetime
-from utils import RADclass
+import hashlib
+import os
+import imp
+#import only if needed because not needed in test mode. To allow an easier installation   import RADclass
 from jsonschema import validate as js_v, exceptions as js_e
 import host_thread as ht
 from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \
 from jsonschema import validate as js_v, exceptions as js_e
 import host_thread as ht
 from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \
@@ -47,6 +51,8 @@ from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \
 global my
 global url_base
 global config_dic
 global my
 global url_base
 global config_dic
+global RADclass_module
+RADclass=None  #RADclass module is charged only if not in test mode
 
 url_base="/openvim"
 
 
 url_base="/openvim"
 
@@ -61,6 +67,17 @@ HTTP_Conflict =             409
 HTTP_Service_Unavailable =  503 
 HTTP_Internal_Server_Error= 500 
 
 HTTP_Service_Unavailable =  503 
 HTTP_Internal_Server_Error= 500 
 
+def md5(fname):
+    hash_md5 = hashlib.md5()
+    with open(fname, "rb") as f:
+        for chunk in iter(lambda: f.read(4096), b""):
+            hash_md5.update(chunk)
+    return hash_md5.hexdigest()
+
+def md5_string(fname):
+    hash_md5 = hashlib.md5()
+    hash_md5.update(fname)
+    return hash_md5.hexdigest()
 
 def check_extended(extended, allow_net_attach=False):
     '''Makes and extra checking of extended input that cannot be done using jsonschema
 
 def check_extended(extended, allow_net_attach=False):
     '''Makes and extra checking of extended input that cannot be done using jsonschema
@@ -443,6 +460,18 @@ def check_valid_uuid(uuid):
     except js_e.ValidationError:
         return False
 
     except js_e.ValidationError:
         return False
 
+
+def is_url(url):
+    '''
+    Check if string value is a well-wormed url
+    :param url: string url
+    :return: True if is a valid url, False if is not well-formed
+    '''
+
+    parsed_url = urlparse.urlparse(url)
+    return parsed_url
+
+
 @bottle.error(400)
 @bottle.error(401) 
 @bottle.error(404) 
 @bottle.error(400)
 @bottle.error(401) 
 @bottle.error(404) 
@@ -514,9 +543,14 @@ def http_post_hosts():
         ip_name=host['ip_name']
         user=host['user']
         password=host.get('password', None)
         ip_name=host['ip_name']
         user=host['user']
         password=host.get('password', None)
+        if not RADclass_module:
+            try:
+                RADclass_module = imp.find_module("RADclass")
+            except (IOError, ImportError) as e:
+                raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e))
 
         #fill rad info
 
         #fill rad info
-        rad = RADclass.RADclass()
+        rad = RADclass_module.RADclass()
         (return_status, code) = rad.obtain_RAD(user, password, ip_name)
         
         #return 
         (return_status, code) = rad.obtain_RAD(user, password, ip_name)
         
         #return 
@@ -578,7 +612,6 @@ def http_post_hosts():
                                 sriov['source_name'] = index
                                 index += 1
                             interfaces.append  ({'pci':str(port_k), 'Mbps': port_v['speed']/1000000, 'sriovs': new_sriovs, 'mac':port_v['mac'], 'source_name':port_v['source_name']})
                                 sriov['source_name'] = index
                                 index += 1
                             interfaces.append  ({'pci':str(port_k), 'Mbps': port_v['speed']/1000000, 'sriovs': new_sriovs, 'mac':port_v['mac'], 'source_name':port_v['source_name']})
-            #@TODO LA memoria devuelta por el RAD es incorrecta, almenos para IVY1, NFV100
             memory=node['memory']['node_size'] / (1024*1024*1024)
             #memory=get_next_2pow(node['memory']['hugepage_nr'])
             host['numas'].append( {'numa_socket': node['id'], 'hugepages': node['memory']['hugepage_nr'], 'memory':memory, 'interfaces': interfaces, 'cores': cores } )
             memory=node['memory']['node_size'] / (1024*1024*1024)
             #memory=get_next_2pow(node['memory']['hugepage_nr'])
             host['numas'].append( {'numa_socket': node['id'], 'hugepages': node['memory']['hugepage_nr'], 'memory':memory, 'interfaces': interfaces, 'cores': cores } )
@@ -1011,13 +1044,14 @@ def http_get_images(tenant_id):
         bottle.abort(result, content)
     #obtain data
     select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
         bottle.abort(result, content)
     #obtain data
     select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
-            ('id','name','description','path','public') )
+            ('id','name','checksum','description','path','public') )
     if tenant_id=='any':
         from_  ='images'
     if tenant_id=='any':
         from_  ='images'
+        where_or_ = None
     else:
     else:
-        from_  ='tenants_images inner join images on tenants_images.image_id=images.uuid'
-        where_['tenant_id'] = tenant_id
-    result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
+        from_  ='tenants_images right join images on tenants_images.image_id=images.uuid'
+        where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
+    result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
     if result < 0:
         print "http_get_images Error", content
         bottle.abort(-result, content)
     if result < 0:
         print "http_get_images Error", content
         bottle.abort(-result, content)
@@ -1036,14 +1070,15 @@ def http_get_image_id(tenant_id, image_id):
         bottle.abort(result, content)
     #obtain data
     select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
         bottle.abort(result, content)
     #obtain data
     select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
-            ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
+            ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
     if tenant_id=='any':
         from_  ='images'
     if tenant_id=='any':
         from_  ='images'
+        where_or_ = None
     else:
     else:
-        from_  ='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
-        where_['tenant_id'] = tenant_id
+        from_  ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
+        where_or_ = {'tenant_id': tenant_id, 'public': "yes"}
     where_['uuid'] = image_id
     where_['uuid'] = image_id
-    result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
+    result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
 
     if result < 0:
         print "http_get_images error %d %s" % (result, content)
 
     if result < 0:
         print "http_get_images error %d %s" % (result, content)
@@ -1077,6 +1112,33 @@ def http_post_images(tenant_id):
     metadata_dict = http_content['image'].pop('metadata', None)
     if metadata_dict is not None: 
         http_content['image']['metadata'] = json.dumps(metadata_dict)
     metadata_dict = http_content['image'].pop('metadata', None)
     if metadata_dict is not None: 
         http_content['image']['metadata'] = json.dumps(metadata_dict)
+    #calculate checksum
+    try:
+        image_file = http_content['image'].get('path',None)
+        parsed_url = urlparse.urlparse(image_file)
+        if parsed_url.scheme == "" and parsed_url.netloc == "":
+            # The path is a local file
+            if os.path.exists(image_file):
+                http_content['image']['checksum'] = md5(image_file)
+        else:
+            # The path is a URL. Code should be added to download the image and calculate the checksum
+            #http_content['image']['checksum'] = md5(downloaded_image)
+            pass
+        # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
+        host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
+        if host_test_mode:
+            if 'checksum' not in http_content['image']:
+                http_content['image']['checksum'] = md5_string(image_file)
+        else:
+            # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
+            # If it is a URL, no error is sent. Checksum will be an empty string
+            if parsed_url.scheme == "" and parsed_url.netloc == "" and 'checksum' not in http_content['image']:
+                content = "Image file not found"
+                print "http_post_images error: %d %s" % (HTTP_Bad_Request, content)
+                bottle.abort(HTTP_Bad_Request, content)
+    except Exception as e:
+        print "ERROR. Unexpected exception: %s" % (str(e))
+        bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
     #insert in data base
     result, content = my.db.new_image(http_content['image'], tenant_id)
     if result >= 0:
     #insert in data base
     result, content = my.db.new_image(http_content['image'], tenant_id)
     if result >= 0:
@@ -1181,10 +1243,11 @@ def http_put_image_id(tenant_id, image_id):
     where_={'uuid': image_id}
     if tenant_id=='any':
         from_  ='images'
     where_={'uuid': image_id}
     if tenant_id=='any':
         from_  ='images'
+        where_or_ = None
     else:
     else:
-        from_  ='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
-        where_['tenant_id'] = tenant_id
-    result, content = my.db.get_table(SELECT=('public',), FROM=from_, WHERE=where_)
+        from_  ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
+        where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
+    result, content = my.db.get_table(SELECT=('public',), DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND")
     if result==0:
         text_error="Image '%s' not found" % image_id
         if tenant_id!='any':
     if result==0:
         text_error="Image '%s' not found" % image_id
         if tenant_id!='any':
@@ -1312,8 +1375,9 @@ def http_post_server_id(tenant_id):
         return
     server['flavor']=content[0]
     #check image valid and take info
         return
     server['flavor']=content[0]
     #check image valid and take info
-    result, content = my.db.get_table(FROM='tenants_images as ti join images as i on ti.image_id=i.uuid',
-        SELECT=('path','metadata'), WHERE={'uuid':server['image_id'], 'tenant_id':tenant_id, "status":"ACTIVE"})
+    result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
+        SELECT=('path','metadata'), WHERE={'uuid':server['image_id'], "status":"ACTIVE"},
+        WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'}, WHERE_AND_OR="AND", DISTINCT=True)
     if result<=0:
         bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % server['image_id'])
         return
     if result<=0:
         bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % server['image_id'])
         return
@@ -1455,8 +1519,9 @@ def http_server_action(server_id, tenant_id, action):
                 else: #result==1
                     image_id = content[0]['image_id']    
                 
                 else: #result==1
                     image_id = content[0]['image_id']    
                 
-        result, content = my.db.get_table(FROM='tenants_images as ti join images as i on ti.image_id=i.uuid',
-            SELECT=('path','metadata'), WHERE={'uuid':image_id, 'tenant_id':tenant_id, "status":"ACTIVE"})
+        result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
+            SELECT=('path','metadata'), WHERE={'uuid':image_id, "status":"ACTIVE"},
+            WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'}, WHERE_AND_OR="AND", DISTINCT=True)
         if result<=0:
             bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
             return
         if result<=0:
             bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
             return