From: tierno Date: Thu, 9 Feb 2017 11:01:55 +0000 (+0100) Subject: Class OVIM to perform the logic done by http receptor X-Git-Tag: v2.0.0~57 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F81%2F1081%2F1;p=osm%2Fopenvim.git Class OVIM to perform the logic done by http receptor Change-Id: I7b496fee22888f73d2350be42d08bc6633895e2a Signed-off-by: tierno --- diff --git a/httpserver.py b/httpserver.py index 1237fb4..777bd22 100644 --- a/httpserver.py +++ b/httpserver.py @@ -48,6 +48,8 @@ from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \ image_new_schema, image_update_schema, \ server_new_schema, server_action_schema, network_new_schema, network_update_schema, \ port_new_schema, port_update_schema +import ovim +import logging global my global url_base @@ -186,7 +188,7 @@ def delete_nulls(var): class httpserver(threading.Thread): - def __init__(self, db_conn, name="http", host='localhost', port=8080, admin=False, config_=None): + def __init__(self, ovim, name="http", host='localhost', port=8080, admin=False, config_=None): ''' Creates a new thread to attend the http connections Attributes: @@ -208,7 +210,8 @@ class httpserver(threading.Thread): threading.Thread.__init__(self) self.host = host self.port = port - self.db = db_conn + self.db = ovim.db #TODO OVIM remove + self.ovim = ovim self.admin = admin if name in config_dic: print "httpserver Warning!!! Onether thread with the same name", name @@ -223,6 +226,7 @@ class httpserver(threading.Thread): #Ensure that when the main program exits the thread will also exit self.daemon = True self.setDaemon(True) + self.logger = logging.getLogger("openvim.http") def run(self): bottle.run(host=self.host, port=self.port, debug=True) #quiet=True @@ -337,40 +341,41 @@ def filter_query_string(qs, http2db, allowed): limit: limit dictated by user with the query string 'limit'. 100 by default abort if not permitted, using bottel.abort ''' - where={} - limit=100 - select=[] + where = {} + limit = 100 + select = [] if type(qs) is not bottle.FormsDict: print '!!!!!!!!!!!!!!invalid query string not a dictionary' - #bottle.abort(HTTP_Internal_Server_Error, "call programmer") + # bottle.abort(HTTP_Internal_Server_Error, "call programmer") else: for k in qs: - if k=='field': + if k == 'field': select += qs.getall(k) for v in select: if v not in allowed: - bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'") - elif k=='limit': + bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field=" + v + "'") + elif k == 'limit': try: - limit=int(qs[k]) + limit = int(qs[k]) except: - bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'") + bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit=" + qs[k] + "'") else: if k not in allowed: - bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'") - if qs[k]!="null": where[k]=qs[k] - else: where[k]=None - if len(select)==0: select += allowed - #change from http api to database naming - for i in range(0,len(select)): - k=select[i] - if k in http2db: + bottle.abort(HTTP_Bad_Request, "Invalid query string at '" + k + "=" + qs[k] + "'") + if qs[k] != "null": + where[k] = qs[k] + else: + where[k] = None + if len(select) == 0: select += allowed + # change from http api to database naming + for i in range(0, len(select)): + k = select[i] + if k in http2db: select[i] = http2db[k] change_keys_http2db(where, http2db) - #print "filter_query_string", select,where,limit - - return select,where,limit + # print "filter_query_string", select,where,limit + return select, where, limit def convert_bandwidth(data, reverse=False): '''Check the field bandwidth recursively and when found, it removes units and convert to number @@ -406,7 +411,7 @@ def convert_bandwidth(data, reverse=False): if type(k) is dict or type(k) is tuple or type(k) is list: convert_bandwidth(k, reverse) -def convert_boolean(data, items): +def convert_boolean(data, items): #TODO OVIM delete '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean It assumes that bandwidth is well formed Attributes: @@ -2305,37 +2310,37 @@ def http_get_ports(): select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_port, ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id', 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') ) - #result, content = my.db.get_ports(where_) - result, content = my.db.get_table(SELECT=select_, WHERE=where_, FROM='ports',LIMIT=limit_) - if result < 0: - print "http_get_ports Error", result, content - bottle.abort(-result, content) - return - else: - convert_boolean(content, ('admin_state_up',) ) - delete_nulls(content) - change_keys_http2db(content, http2db_port, reverse=True) - data={'ports' : content} + try: + ports = my.ovim.get_ports(columns=select_, filter=where_, limit=limit_) + delete_nulls(ports) + change_keys_http2db(ports, http2db_port, reverse=True) + data={'ports' : ports} return format_out(data) + except ovim.ovimException as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(e.http_code, str(e)) + except Exception as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(HTTP_Bad_Request, str(e)) @bottle.route(url_base + '/ports/', method='GET') def http_get_port_id(port_id): my = config_dic['http_threads'][ threading.current_thread().name ] - #obtain data - result, content = my.db.get_table(WHERE={'uuid': port_id}, FROM='ports') - if result < 0: - print "http_get_ports error", result, content - bottle.abort(-result, content) - elif result==0: - print "http_get_ports port '%s' not found" % str(port_id) - bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id) - else: - convert_boolean(content, ('admin_state_up',) ) - delete_nulls(content) - change_keys_http2db(content, http2db_port, reverse=True) - data={'port' : content[0]} + try: + ports = my.ovim.get_ports(filter={"uuid": port_id}) + if not ports: + bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id) + return + delete_nulls(ports) + change_keys_http2db(ports, http2db_port, reverse=True) + data = {'port': ports[0]} return format_out(data) - + except ovim.ovimException as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(e.http_code, str(e)) + except Exception as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(HTTP_Bad_Request, str(e)) @bottle.route(url_base + '/ports', method='POST') def http_post_ports(): @@ -2349,115 +2354,54 @@ def http_post_ports(): if r is not None: print "http_post_ports: Warning: remove extra items ", r change_keys_http2db(http_content['port'], http2db_port) port=http_content['port'] - - port['type'] = 'external' - if 'net_id' in port and port['net_id'] == None: - del port['net_id'] - - if 'net_id' in port: - #check that new net has the correct type - result, new_net = my.db.check_target_net(port['net_id'], None, 'external' ) - if result < 0: - bottle.abort(HTTP_Bad_Request, new_net) + try: + port_id = my.ovim.new_port(port) + ports = my.ovim.get_ports(filter={"uuid": port_id}) + if not ports: + bottle.abort(HTTP_Internal_Server_Error, "port '{}' inserted but not found at database".format(port_id)) return - #insert in data base - result, uuid = my.db.new_row('ports', port, True, True) - if result > 0: - if 'net_id' in port: - r,c = config_dic['of_thread'].insert_task("update-net", port['net_id']) - if r < 0: - print "http_post_ports error while launching openflow rules" - bottle.abort(HTTP_Internal_Server_Error, c) - return http_get_port_id(uuid) - else: - bottle.abort(-result, uuid) - return - + delete_nulls(ports) + change_keys_http2db(ports, http2db_port, reverse=True) + data = {'port': ports[0]} + return format_out(data) + except ovim.ovimException as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(e.http_code, str(e)) + except Exception as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(HTTP_Bad_Request, str(e)) + @bottle.route(url_base + '/ports/', method='PUT') def http_put_port_id(port_id): '''update a port_id into the database.''' - my = config_dic['http_threads'][ threading.current_thread().name ] #parse input data http_content = format_in( port_update_schema ) change_keys_http2db(http_content['port'], http2db_port) port_dict=http_content['port'] - #Look for the previous port data - where_ = {'uuid': port_id} - result, content = my.db.get_table(FROM="ports",WHERE=where_) - if result < 0: - print "http_put_port_id error", result, content - bottle.abort(-result, content) - return - elif result==0: - print "http_put_port_id port '%s' not found" % port_id - bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id) - return - print port_dict - for k in ('vlan','switch_port','mac_address', 'tenant_id'): + for k in ('vlan', 'switch_port', 'mac_address', 'tenant_id'): if k in port_dict and not my.admin: bottle.abort(HTTP_Unauthorized, "Needed admin privileges for changing " + k) return - - port=content[0] - #change_keys_http2db(port, http2db_port, reverse=True) - nets = [] - host_id = None - result=1 - if 'net_id' in port_dict: - #change of net. - old_net = port.get('net_id', None) - new_net = port_dict['net_id'] - if old_net != new_net: - - if new_net is not None: nets.append(new_net) #put first the new net, so that new openflow rules are created before removing the old ones - if old_net is not None: nets.append(old_net) - if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs': - bottle.abort(HTTP_Forbidden, "bridge interfaces cannot be attached to a different net") - return - elif port['type'] == 'external': - if not my.admin: - bottle.abort(HTTP_Unauthorized, "Needed admin privileges") - return - else: - if new_net != None: - #check that new net has the correct type - result, new_net_dict = my.db.check_target_net(new_net, None, port['type'] ) - - #change VLAN for SR-IOV ports - if result>=0 and port["type"]=="instance:data" and port["model"]=="VF": #TODO consider also VFnotShared - if new_net == None: - port_dict["vlan"] = None - else: - port_dict["vlan"] = new_net_dict["vlan"] - #get host where this VM is allocated - result, content = my.db.get_table(FROM="instances",WHERE={"uuid":port["instance_id"]}) - if result<0: - print "http_put_port_id database error", content - elif result>0: - host_id = content[0]["host_id"] - - #insert in data base - if result >= 0: - result, content = my.db.update_rows('ports', port_dict, WHERE={'uuid': port_id}, log=False ) - - #Insert task to complete actions - if result > 0: - for net_id in nets: - r,v = config_dic['of_thread'].insert_task("update-net", net_id) - if r<0: print "Error ********* http_put_port_id update_of_flows: ", v - #TODO Do something if fails - if host_id != None: - config_dic['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net) - - if result >= 0: - return http_get_port_id(port_id) - else: - bottle.abort(HTTP_Bad_Request, content) - return + try: + port_id = my.ovim.edit_port(port_id, port_dict, my.admin) + ports = my.ovim.get_ports(filter={"uuid": port_id}) + if not ports: + bottle.abort(HTTP_Internal_Server_Error, "port '{}' edited but not found at database".format(port_id)) + return + delete_nulls(ports) + change_keys_http2db(ports, http2db_port, reverse=True) + data = {'port': ports[0]} + return format_out(data) + except ovim.ovimException as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(e.http_code, str(e)) + except Exception as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(HTTP_Bad_Request, str(e)) + - @bottle.route(url_base + '/ports/', method='DELETE') def http_delete_port_id(port_id): '''delete a port_id from the database.''' @@ -2465,30 +2409,15 @@ def http_delete_port_id(port_id): if not my.admin: bottle.abort(HTTP_Unauthorized, "Needed admin privileges") return + try: + result = my.ovim.delete_port(port_id) + data = {'result': result} + return format_out(data) + except ovim.ovimException as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(e.http_code, str(e)) + except Exception as e: + my.logger.error(str(e), exc_info=True) + bottle.abort(HTTP_Bad_Request, str(e)) - #Look for the previous port data - where_ = {'uuid': port_id, "type": "external"} - result, ports = my.db.get_table(WHERE=where_, FROM='ports',LIMIT=100) - - if result<=0: - print "http_delete_port_id port '%s' not found" % port_id - bottle.abort(HTTP_Not_Found, 'port %s not found or device_owner is not external' % port_id) - return - #delete from the data base - result, content = my.db.delete_row('ports', port_id ) - if result == 0: - bottle.abort(HTTP_Not_Found, content) - elif result >0: - network = ports[0].get('net_id', None) - if network is not None: - #change of net. - r,c = config_dic['of_thread'].insert_task("update-net", network) - if r<0: print "!!!!!! http_delete_port_id update_of_flows error", r, c - data={'result' : content} - return format_out(data) - else: - print "http_delete_port_id error",result, content - bottle.abort(-result, content) - return - diff --git a/openvimd.py b/openvimd.py index 7c260b9..be5d46e 100755 --- a/openvimd.py +++ b/openvimd.py @@ -30,7 +30,7 @@ and host controllers __author__="Alfonso Tierno" __date__ ="$10-jul-2014 12:07:15$" -__version__="0.5.2-r519" +__version__="0.5.3-r520" version_date="Jan 2017" database_version="0.10" #expected database schema version @@ -39,19 +39,14 @@ import auxiliary_functions as af import sys import getopt import time -import vim_db import yaml import os from jsonschema import validate as js_v, exceptions as js_e -import host_thread as ht -import dhcp_thread as dt -import openflow_thread as oft -import threading from vim_schema import config_schema import logging import logging.handlers as log_handlers -import imp import socket +import ovim global config_dic global logger @@ -113,13 +108,6 @@ def load_configuration(configuration_file): return (False, "Error loading configuration file '"+configuration_file+"': "+str(e)) return (True, config) -def create_database_connection(config_dic): - db = vim_db.vim_db( (config_dic["network_vlan_range_start"],config_dic["network_vlan_range_end"]), config_dic['log_level_db'] ); - if db.connect(config_dic['db_host'], config_dic['db_user'], config_dic['db_passwd'], config_dic['db_name']) == -1: - logger.error("Cannot connect to database %s at %s@%s", config_dic['db_name'], config_dic['db_user'], config_dic['db_host']) - exit(-1) - return db - def usage(): print "Usage: ", sys.argv[0], "[options]" print " -v|--version: prints current version" @@ -144,7 +132,7 @@ if __name__=="__main__": log_format_simple = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s" log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S') logging.basicConfig(format=log_format_simple, level= logging.DEBUG) - logger = logging.getLogger('openmano') + logger = logging.getLogger('openvim') logger.setLevel(logging.DEBUG) try: opts, args = getopt.getopt(sys.argv[1:], "hvc:p:P:", ["config=", "help", "version", "port=", "adminport=", "log-file=", "dbname="]) @@ -182,6 +170,7 @@ if __name__=="__main__": assert False, "Unhandled option" + engine = None try: #Load configuration file r, config_dic = load_configuration(config_file) @@ -229,136 +218,20 @@ if __name__=="__main__": print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' config_dic['version'] = __version__ - #Connect to database - db_http = create_database_connection(config_dic) - r = db_http.get_db_version() - if r[0]<0: - logger.error("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '%s' with './database_utils/migrate_vim_db.sh'", database_version) - exit(-1) - elif r[1]!=database_version: - logger.error("DATABASE wrong version '%s'. Try to upgrade/downgrade to version '%s' with './database_utils/migrate_vim_db.sh'", r[1], database_version) - exit(-1) - db_of = create_database_connection(config_dic) - db_lock= threading.Lock() - config_dic['db'] = db_of - config_dic['db_lock'] = db_lock - - #precreate interfaces; [bridge:, VLAN used at Host, uuid of network camping in this bridge, speed in Gbit/s - config_dic['dhcp_nets']=[] - config_dic['bridge_nets']=[] - for bridge,vlan_speed in config_dic["bridge_ifaces"].items(): - #skip 'development_bridge' - if config_dic['mode'] == 'development' and config_dic['development_bridge'] == bridge: - continue - config_dic['bridge_nets'].append( [bridge, vlan_speed[0], vlan_speed[1], None] ) - del config_dic["bridge_ifaces"] - - #check if this bridge is already used (present at database) for a network) - used_bridge_nets=[] - for brnet in config_dic['bridge_nets']: - r,nets = db_of.get_table(SELECT=('uuid',), FROM='nets',WHERE={'provider': "bridge:"+brnet[0]}) - if r>0: - brnet[3] = nets[0]['uuid'] - used_bridge_nets.append(brnet[0]) - if config_dic.get("dhcp_server"): - if brnet[0] in config_dic["dhcp_server"]["bridge_ifaces"]: - config_dic['dhcp_nets'].append(nets[0]['uuid']) - if len(used_bridge_nets) > 0 : - logger.info("found used bridge nets: " + ",".join(used_bridge_nets)) - #get nets used by dhcp - if config_dic.get("dhcp_server"): - for net in config_dic["dhcp_server"].get("nets", () ): - r,nets = db_of.get_table(SELECT=('uuid',), FROM='nets',WHERE={'name': net}) - if r>0: - config_dic['dhcp_nets'].append(nets[0]['uuid']) - - # get host list from data base before starting threads - r,hosts = db_of.get_table(SELECT=('name','ip_name','user','uuid'), FROM='hosts', WHERE={'status':'ok'}) - if r<0: - logger.error("Cannot get hosts from database %s", hosts) - exit(-1) - # create connector to the openflow controller - of_test_mode = False if config_dic['mode']=='normal' or config_dic['mode']=="OF only" else True - - if of_test_mode: - OF_conn = oft.of_test_connector({"of_debug": config_dic['log_level_of']} ) - else: - #load other parameters starting by of_ from config dict in a temporal dict - temp_dict={ "of_ip": config_dic['of_controller_ip'], - "of_port": config_dic['of_controller_port'], - "of_dpid": config_dic['of_controller_dpid'], - "of_debug": config_dic['log_level_of'] - } - for k,v in config_dic.iteritems(): - if type(k) is str and k[0:3]=="of_" and k[0:13] != "of_controller": - temp_dict[k]=v - if config_dic['of_controller']=='opendaylight': - module = "ODL" - elif "of_controller_module" in config_dic: - module = config_dic["of_controller_module"] - else: - module = config_dic['of_controller'] - module_info=None - try: - module_info = imp.find_module(module) - - OF_conn = imp.load_module("OF_conn", *module_info) - try: - OF_conn = OF_conn.OF_conn(temp_dict) - except Exception as e: - logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e)) - if module_info and module_info[0]: - file.close(module_info[0]) - exit(-1) - except (IOError, ImportError) as e: - if module_info and module_info[0]: - file.close(module_info[0]) - logger.error("Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' field of configuration file.", module, type(e).__name__, str(e)) - exit(-1) - + config_dic["database_version"] = database_version + config_dic["logger_name"] = "openvim" - #create openflow thread - thread = oft.openflow_thread(OF_conn, of_test=of_test_mode, db=db_of, db_lock=db_lock, - pmp_with_same_vlan=config_dic['of_controller_nets_with_same_vlan'], - debug=config_dic['log_level_of']) - r,c = thread.OF_connector.obtain_port_correspondence() - if r<0: - logger.error("Cannot get openflow information %s", c) - exit() - thread.start() - config_dic['of_thread'] = thread + engine = ovim.ovim(config_dic) + engine.start_service() - #create dhcp_server thread - host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False - dhcp_params = config_dic.get("dhcp_server") - if dhcp_params: - thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=config_dic["dhcp_nets"], db=db_of, db_lock=db_lock, debug=config_dic['log_level_of']) - thread.start() - config_dic['dhcp_thread'] = thread - - - #Create one thread for each host - host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False - host_develop_mode = True if config_dic['mode']=='development' else False - host_develop_bridge_iface = config_dic.get('development_bridge', None) - config_dic['host_threads'] = {} - for host in hosts: - host['image_path'] = '/opt/VNF/images/openvim' - thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=db_of, db_lock=db_lock, - test=host_test_mode, image_path=config_dic['image_path'], version=config_dic['version'], - host_id=host['uuid'], develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface ) - thread.start() - config_dic['host_threads'][ host['uuid'] ] = thread - - #Create thread to listen to web requests - http_thread = httpserver.httpserver(db_http, 'http', config_dic['http_host'], config_dic['http_port'], False, config_dic) + http_thread = httpserver.httpserver(engine, 'http', config_dic['http_host'], config_dic['http_port'], False, config_dic) http_thread.start() - if 'http_admin_port' in config_dic: - db_http = create_database_connection(config_dic) - http_thread_admin = httpserver.httpserver(db_http, 'http-admin', config_dic['http_host'], config_dic['http_admin_port'], True) + if 'http_admin_port' in config_dic: + engine2 = ovim.ovim(config_dic) + http_thread_admin = httpserver.httpserver(engine2, 'http-admin', config_dic['http_host'], config_dic['http_admin_port'], True) http_thread_admin.start() else: http_thread_admin = None @@ -389,21 +262,14 @@ if __name__=="__main__": except LoadConfigurationException as e: logger.critical(str(e)) exit(-1) + except ovim.ovimException as e: + logger.critical(str(e)) + exit(-1) logger.info('Exiting openvimd') - threads = config_dic.get('host_threads', {}) - if 'of_thread' in config_dic: - threads['of'] = (config_dic['of_thread']) - if 'dhcp_thread' in config_dic: - threads['dhcp'] = (config_dic['dhcp_thread']) - - for thread in threads.values(): - thread.insert_task("exit") - for thread in threads.values(): - thread.join() - #http_thread.join() - #if http_thread_admin is not None: - #http_thread_admin.join() + if engine: + engine.stop_service() + logger.debug( "bye!") exit() diff --git a/ovim.py b/ovim.py new file mode 100644 index 0000000..e5cd40d --- /dev/null +++ b/ovim.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- + +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openvim +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +''' +This is the thread for the http server North API. +Two thread will be launched, with normal and administrative permissions. +''' + +__author__ = "Alfonso Tierno, Leonardo Mirabal" +__date__ = "$06-Feb-2017 12:07:15$" + +import threading +import vim_db +import logging +import threading +import imp +import host_thread as ht +import dhcp_thread as dt +import openflow_thread as oft + +HTTP_Bad_Request = 400 +HTTP_Unauthorized = 401 +HTTP_Not_Found = 404 +HTTP_Forbidden = 403 +HTTP_Method_Not_Allowed = 405 +HTTP_Not_Acceptable = 406 +HTTP_Request_Timeout = 408 +HTTP_Conflict = 409 +HTTP_Service_Unavailable = 503 +HTTP_Internal_Server_Error= 500 + + +def convert_boolean(data, items): + '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean + It assumes that bandwidth is well formed + Attributes: + 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid + 'items': tuple of keys to convert + Return: + None + ''' + if type(data) is dict: + for k in data.keys(): + if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list: + convert_boolean(data[k], items) + if k in items: + if type(data[k]) is str: + if data[k] == "false": + data[k] = False + elif data[k] == "true": + data[k] = True + if type(data) is tuple or type(data) is list: + for k in data: + if type(k) is dict or type(k) is tuple or type(k) is list: + convert_boolean(k, items) + + + +class ovimException(Exception): + def __init__(self, message, http_code=HTTP_Bad_Request): + self.http_code = http_code + Exception.__init__(self, message) + + +class ovim(): + running_info = {} #TODO OVIM move the info of running threads from config_dic to this static variable + def __init__(self, configuration): + self.config = configuration + self.logger = logging.getLogger(configuration["logger_name"]) + self.db = None + self.db = self._create_database_connection() + + def _create_database_connection(self): + db = vim_db.vim_db((self.config["network_vlan_range_start"], self.config["network_vlan_range_end"]), + self.config['log_level_db']); + if db.connect(self.config['db_host'], self.config['db_user'], self.config['db_passwd'], + self.config['db_name']) == -1: + # self.logger.error("Cannot connect to database %s at %s@%s", self.config['db_name'], self.config['db_user'], + # self.config['db_host']) + raise ovimException("Cannot connect to database {} at {}@{}".format(self.config['db_name'], + self.config['db_user'], + self.config['db_host']) ) + return db + + + def start_service(self): + #if self.running_info: + # return #TODO service can be checked and rebuild broken threads + r = self.db.get_db_version() + if r[0]<0: + raise ovimException("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '{}' with "\ + "'./database_utils/migrate_vim_db.sh'".format(self.config["database_version"]) ) + elif r[1]!=self.config["database_version"]: + raise ovimException("DATABASE wrong version '{}'. Try to upgrade/downgrade to version '{}' with "\ + "'./database_utils/migrate_vim_db.sh'".format(r[1], self.config["database_version"]) ) + + # create database connection for openflow threads + db_of = self._create_database_connection() + self.config["db"] = db_of + db_lock = threading.Lock() + self.config["db_lock"] = db_lock + + # precreate interfaces; [bridge:, VLAN used at Host, uuid of network camping in this bridge, speed in Gbit/s + self.config['dhcp_nets'] = [] + self.config['bridge_nets'] = [] + for bridge, vlan_speed in self.config["bridge_ifaces"].items(): + # skip 'development_bridge' + if self.config['mode'] == 'development' and self.config['development_bridge'] == bridge: + continue + self.config['bridge_nets'].append([bridge, vlan_speed[0], vlan_speed[1], None]) + + # check if this bridge is already used (present at database) for a network) + used_bridge_nets = [] + for brnet in self.config['bridge_nets']: + r, nets = db_of.get_table(SELECT=('uuid',), FROM='nets', WHERE={'provider': "bridge:" + brnet[0]}) + if r > 0: + brnet[3] = nets[0]['uuid'] + used_bridge_nets.append(brnet[0]) + if self.config.get("dhcp_server"): + if brnet[0] in self.config["dhcp_server"]["bridge_ifaces"]: + self.config['dhcp_nets'].append(nets[0]['uuid']) + if len(used_bridge_nets) > 0: + self.logger.info("found used bridge nets: " + ",".join(used_bridge_nets)) + # get nets used by dhcp + if self.config.get("dhcp_server"): + for net in self.config["dhcp_server"].get("nets", ()): + r, nets = db_of.get_table(SELECT=('uuid',), FROM='nets', WHERE={'name': net}) + if r > 0: + self.config['dhcp_nets'].append(nets[0]['uuid']) + + # get host list from data base before starting threads + r, hosts = db_of.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'}) + if r < 0: + raise ovimException("Cannot get hosts from database {}".format(hosts)) + # create connector to the openflow controller + of_test_mode = False if self.config['mode'] == 'normal' or self.config['mode'] == "OF only" else True + + if of_test_mode: + OF_conn = oft.of_test_connector({"of_debug": self.config['log_level_of']}) + else: + # load other parameters starting by of_ from config dict in a temporal dict + temp_dict = {"of_ip": self.config['of_controller_ip'], + "of_port": self.config['of_controller_port'], + "of_dpid": self.config['of_controller_dpid'], + "of_debug": self.config['log_level_of'] + } + for k, v in self.config.iteritems(): + if type(k) is str and k[0:3] == "of_" and k[0:13] != "of_controller": + temp_dict[k] = v + if self.config['of_controller'] == 'opendaylight': + module = "ODL" + elif "of_controller_module" in self.config: + module = self.config["of_controller_module"] + else: + module = self.config['of_controller'] + module_info = None + try: + module_info = imp.find_module(module) + + OF_conn = imp.load_module("OF_conn", *module_info) + try: + OF_conn = OF_conn.OF_conn(temp_dict) + except Exception as e: + self.logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e)) + if module_info and module_info[0]: + file.close(module_info[0]) + exit(-1) + except (IOError, ImportError) as e: + if module_info and module_info[0]: + file.close(module_info[0]) + self.logger.error( + "Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' field of configuration file.", + module, type(e).__name__, str(e)) + raise ovimException("Cannot open openflow controller module '{}'; {}: {}; revise 'of_controller' field of configuration file.".fromat( + module, type(e).__name__, str(e))) + + + # create openflow thread + thread = oft.openflow_thread(OF_conn, of_test=of_test_mode, db=db_of, db_lock=db_lock, + pmp_with_same_vlan=self.config['of_controller_nets_with_same_vlan'], + debug=self.config['log_level_of']) + r, c = thread.OF_connector.obtain_port_correspondence() + if r < 0: + raise ovimException("Cannot get openflow information %s", c) + thread.start() + self.config['of_thread'] = thread + + # create dhcp_server thread + host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False + dhcp_params = self.config.get("dhcp_server") + if dhcp_params: + thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=self.config["dhcp_nets"], + db=db_of, db_lock=db_lock, debug=self.config['log_level_of']) + thread.start() + self.config['dhcp_thread'] = thread + + # Create one thread for each host + host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False + host_develop_mode = True if self.config['mode'] == 'development' else False + host_develop_bridge_iface = self.config.get('development_bridge', None) + self.config['host_threads'] = {} + for host in hosts: + host['image_path'] = '/opt/VNF/images/openvim' + thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=db_of, db_lock=db_lock, + test=host_test_mode, image_path=self.config['image_path'], version=self.config['version'], + host_id=host['uuid'], develop_mode=host_develop_mode, + develop_bridge_iface=host_develop_bridge_iface) + thread.start() + self.config['host_threads'][host['uuid']] = thread + + def stop_service(self): + threads = self.config.get('host_threads', {}) + if 'of_thread' in self.config: + threads['of'] = (self.config['of_thread']) + if 'dhcp_thread' in self.config: + threads['dhcp'] = (self.config['dhcp_thread']) + + for thread in threads.values(): + thread.insert_task("exit") + for thread in threads.values(): + thread.join() + # http_thread.join() + # if http_thread_admin is not None: + # http_thread_admin.join() + + + def get_ports(self, columns=None, filter={}, limit=None): + # result, content = my.db.get_ports(where_) + result, content = self.db.get_table(SELECT=columns, WHERE=filter, FROM='ports', LIMIT=limit) + if result < 0: + self.logger.error("http_get_ports Error %d %s", result, content) + raise ovimException(str(content), -result) + else: + convert_boolean(content, ('admin_state_up',)) + return content + + + def new_port(self, port_data): + port_data['type'] = 'external' + if port_data.get('net_id'): + # check that new net has the correct type + result, new_net = self.db.check_target_net(port_data['net_id'], None, 'external') + if result < 0: + raise ovimException(str(new_net), -result) + # insert in data base + result, uuid = self.db.new_row('ports', port_data, True, True) + if result > 0: + if 'net_id' in port_data: + r, c = self.config['of_thread'].insert_task("update-net", port_data['net_id']) + if r < 0: + self.logger.error("Cannot insert a task for updating network '$s' %s", port_data['net_id'], c) + #TODO put network in error status + return uuid + else: + raise ovimException(str(uuid), -result) + + def delete_port(self, port_id): + # Look for the previous port data + result, ports = self.db.get_table(WHERE={'uuid': port_id, "type": "external"}, FROM='ports') + if result < 0: + raise ovimException("Cannot get port info from database: {}".format(ports), http_code=-result) + # delete from the data base + result, content = self.db.delete_row('ports', port_id) + if result == 0: + raise ovimException("External port '{}' not found".format(port_id), http_code=HTTP_Not_Found) + elif result < 0: + raise ovimException("Cannot delete port from database: {}".format(content), http_code=-result) + # update network + network = ports[0].get('net_id', None) + if network: + # change of net. + r, c = self.config['of_thread'].insert_task("update-net", network) + if r < 0: + self.logger.error("Cannot insert a task for updating network '$s' %s", network, c) + return content + + + def edit_port(self, port_id, port_data, admin=True): + # Look for the previous port data + result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id}) + if result < 0: + raise ovimException("Cannot get port info from database: {}".format(content), http_code=-result) + elif result == 0: + raise ovimException("Port '{}' not found".format(port_id), http_code=HTTP_Not_Found) + port = content[0] + nets = [] + host_id = None + result = 1 + if 'net_id' in port_data: + # change of net. + old_net = port.get('net_id', None) + new_net = port_data['net_id'] + if old_net != new_net: + + if new_net: + nets.append(new_net) # put first the new net, so that new openflow rules are created before removing the old ones + if old_net: + nets.append(old_net) + if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs': + raise ovimException("bridge interfaces cannot be attached to a different net", http_code=HTTP_Forbidden) + elif port['type'] == 'external' and not admin: + raise ovimException("Needed admin privileges",http_code=HTTP_Unauthorized) + if new_net: + # check that new net has the correct type + result, new_net_dict = self.db.check_target_net(new_net, None, port['type']) + if result < 0: + raise ovimException("Error {}".format(new_net_dict), http_code=HTTP_Conflict) + # change VLAN for SR-IOV ports + if result >= 0 and port["type"] == "instance:data" and port["model"] == "VF": # TODO consider also VFnotShared + if new_net: + port_data["vlan"] = None + else: + port_data["vlan"] = new_net_dict["vlan"] + # get host where this VM is allocated + result, content = self.db.get_table(FROM="instances", WHERE={"uuid": port["instance_id"]}) + if result > 0: + host_id = content[0]["host_id"] + + # insert in data base + if result >= 0: + result, content = self.db.update_rows('ports', port_data, WHERE={'uuid': port_id}, log=False) + + # Insert task to complete actions + if result > 0: + for net_id in nets: + r, v = self.config['of_thread'].insert_task("update-net", net_id) + if r < 0: + self.logger.error("Error updating network '{}' {}".format(r,v)) + # TODO Do something if fails + if host_id: + r, v = self.config['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net) + if r < 0: + self.logger.error("Error updating network '{}' {}".format(r,v)) + # TODO Do something if fails + if result >= 0: + return port_id + else: + raise ovimException("Error {}".format(content), http_code=-result) diff --git a/vim_db.py b/vim_db.py index a024427..d6ac871 100644 --- a/vim_db.py +++ b/vim_db.py @@ -226,7 +226,7 @@ class vim_db(): select_ = "SELECT " if sql_dict.get("DISTINCT"): select_ += "DISTINCT " - select_ += ("*" if 'SELECT' not in sql_dict else ",".join(map(str,sql_dict['SELECT'])) ) + select_ += ("*" if not sql_dict.get('SELECT') else ",".join(map(str,sql_dict['SELECT'])) ) #print 'select_', select_ from_ = "FROM " + str(sql_dict['FROM']) #print 'from_', from_ @@ -259,7 +259,7 @@ class vim_db(): else: where_ = "" #print 'where_', where_ - limit_ = "LIMIT " + str(sql_dict['LIMIT']) if 'LIMIT' in sql_dict else "" + limit_ = "LIMIT " + str(sql_dict['LIMIT']) if sql_dict.get("LIMIT") else "" #print 'limit_', limit_ cmd = " ".join( (select_, from_, where_, limit_) ) for retry_ in range(0,2):