Implemented functionality for SDN in RO regarding controllers and port mapping manage... 19/1319/4
authorPablo Montes Moreno <pablo.montesmoreno@telefonica.com>
Wed, 8 Mar 2017 10:28:15 +0000 (11:28 +0100)
committerPablo Montes Moreno <pablo.montesmoreno@telefonica.com>
Thu, 23 Mar 2017 11:51:42 +0000 (12:51 +0100)
Change-Id: Iff71058bdd51b61f7391a7144636a08c56249498
Signed-off-by: Pablo Montes Moreno <pablo.montesmoreno@telefonica.com>
14 files changed:
database_utils/migrate_mano_db.sh
httpserver.py
nfvo.py
nfvo_db.py
openmano
openmano_schemas.py
openmanoclient.py
openmanod.py
sdn/sdn_port_mapping.yaml [new file with mode: 0644]
test/RO_tests/sr_iov/scenario_p2p_sriov.yaml [new file with mode: 0644]
test/RO_tests/sr_iov/vnfd_1sriov.yaml [new file with mode: 0644]
vim_thread.py
vimconn.py
vimconn_openstack.py

index a8ec8f9..ba8a829 100755 (executable)
@@ -187,6 +187,7 @@ DATABASE_TARGET_VER_NUM=0
 [ $OPENMANO_VER_NUM -ge 5003 ] && DATABASE_TARGET_VER_NUM=17  #0.5.3 =>  17
 [ $OPENMANO_VER_NUM -ge 5004 ] && DATABASE_TARGET_VER_NUM=18  #0.5.4 =>  18
 [ $OPENMANO_VER_NUM -ge 5005 ] && DATABASE_TARGET_VER_NUM=19  #0.5.5 =>  19
+[ $OPENMANO_VER_NUM -ge 5009 ] && DATABASE_TARGET_VER_NUM=20  #0.5.9 =>  20
 #TODO ... put next versions here
 
 
@@ -730,6 +731,27 @@ function downgrade_from_19(){
     echo "DELETE FROM schema_version WHERE version_int='19';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
 }
 
+function upgrade_to_20(){
+    echo "    upgrade database from version 0.19 to version 0.20"
+    echo "      add column 'sdn_net_id' at table 'instance_nets' and columns 'sdn_port_id', 'compute_node', 'pci' and 'vlan' to table 'instance_interfaces'"
+    echo "ALTER TABLE instance_nets ADD sdn_net_id varchar(36) DEFAULT NULL NULL COMMENT 'Network id in ovim';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces ADD sdn_port_id varchar(36) DEFAULT NULL NULL COMMENT 'Port id in ovim';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces ADD compute_node varchar(100) DEFAULT NULL NULL COMMENT 'Compute node id used to specify the SDN port mapping';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces ADD pci varchar(12) DEFAULT NULL NULL COMMENT 'PCI of the physical port in the host';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces ADD vlan SMALLINT UNSIGNED DEFAULT NULL NULL COMMENT 'VLAN tag used by the port';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) VALUES (20, '0.20', '0.5.9', 'Added columns to store dataplane connectivity info', '2017-03-13');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_20(){
+    echo "    downgrade database from version 0.20 to version 0.19"
+    echo "      remove column 'sdn_net_id' at table 'instance_nets' and columns 'sdn_port_id', 'compute_node', 'pci' and 'vlan' to table 'instance_interfaces'"
+    echo "ALTER TABLE instance_nets DROP COLUMN sdn_net_id;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces DROP COLUMN vlan;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces DROP COLUMN pci;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces DROP COLUMN compute_node;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "ALTER TABLE instance_interfaces DROP COLUMN sdn_port_id;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "DELETE FROM schema_version WHERE version_int='20';" | $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
index a0216e1..f412aa1 100644 (file)
@@ -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
+
 import nfvo
 import utils
 from db_base import db_base_Exception
@@ -544,6 +546,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 + '/<tenant_id>/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 + '/<tenant_id>/sdn_controllers/<controller_id>', 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 + '/<tenant_id>/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 + '/<tenant_id>/sdn_controllers/<controller_id>', 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 + '/<tenant_id>/sdn_controllers/<controller_id>', 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 + '/<tenant_id>/datacenters/<datacenter_id>/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 + '/<tenant_id>/datacenters/<datacenter_id>/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 + '/<tenant_id>/datacenters/<datacenter_id>/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 + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET')  #deprecated
 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
@@ -739,6 +873,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 + '/<tenant_id>/datacenters/<datacenter_id>', 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 + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
 def http_deassociate_datacenters(tenant_id, datacenter_id):
diff --git a/nfvo.py b/nfvo.py
index 14ec54f..1a30150 100644 (file)
--- a/nfvo.py
+++ b/nfvo.py
@@ -39,15 +39,19 @@ import vimconn
 import logging
 import collections
 from db_base import db_base_Exception
+
 import nfvo_db
 from threading import Lock
 from time import time
+import openvim.ovim as Ovim
 
 global global_config
 global vimconn_imported
 global logger
 global default_volume_size
 default_volume_size = '5' #size in GB
+global ovim
+ovim = None
 
 
 vimconn_imported = {}   # dictionary with VIM type as key, loaded module as value
@@ -107,6 +111,31 @@ def start_service(mydb):
     global db, global_config
     db = nfvo_db.nfvo_db()
     db.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
+    global ovim
+
+    # Initialize openvim for SDN control
+    # TODO: Avoid static configuration by adding new parameters to openmanod.cfg
+    # TODO: review ovim.py to delete not needed configuration
+    ovim_configuration = {
+        'logger_name': 'openvim',
+        'network_vlan_range_start': 1000,
+        'network_vlan_range_end': 4096,
+        'log_level_db': 'DEBUG',
+        'db_name': 'mano_vim_db',
+        'db_host': 'localhost',
+        'db_user': 'vim',
+        'db_passwd': 'vimpw',
+        'database_version': '0.15',
+        'bridge_ifaces': {},
+        'mode': 'normal',
+        'of_controller_nets_with_same_vlan': True,
+        'network_type': 'bridge',
+        #TODO: log_level_of should not be needed. To be modified in ovim
+        'log_level_of': 'DEBUG'
+    }
+    ovim = Ovim.ovim(ovim_configuration)
+    ovim.start_service()
+
     from_= 'tenants_datacenters as td join datacenters as d on td.datacenter_id=d.uuid join datacenter_tenants as dt on td.datacenter_tenant_id=dt.uuid'
     select_ = ('type','d.config as config','d.uuid as datacenter_id', 'vim_url', 'vim_url_admin', 'd.name as datacenter_name',
                    'dt.uuid as datacenter_tenant_id','dt.vim_tenant_name as vim_tenant_name','dt.vim_tenant_id as vim_tenant_id',
@@ -148,14 +177,15 @@ def start_service(mydb):
                 raise NfvoException("Error at VIM  {}; {}: {}".format(vim["type"], type(e).__name__, str(e)), HTTP_Internal_Server_Error)
             thread_name = get_non_used_vim_name(vim['datacenter_name'], vim['vim_tenant_id'], vim['vim_tenant_name'], vim['vim_tenant_id'])
             new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, vim['datacenter_name'],
-                                               vim.get('datacenter_tenant_id'), db=db, db_lock=db_lock)
+                                               vim.get('datacenter_tenant_id'), db=db, db_lock=db_lock, ovim=ovim)
             new_thread.start()
             vim_threads["running"][thread_id] = new_thread
     except db_base_Exception as e:
         raise NfvoException(str(e) + " at nfvo.get_vim", e.http_code)
 
-
 def stop_service():
+    if ovim:
+        ovim.stop_service()
     for thread_id,thread in vim_threads["running"].items():
         thread.insert_task(new_task("exit", None, store=False))
         vim_threads["deleting"][thread_id] = thread
@@ -2394,7 +2424,7 @@ def delete_instance(mydb, tenant_id, instance_id):
                     else:  # ok
                         task = new_task("del-net", old_task["result"])
             else:
-                task = new_task("del-net", net['vim_net_id'], store=False)
+                task = new_task("del-net", (net['vim_net_id'], net['sdn_net_id']), store=False)
             if task:
                 myvim_thread.insert_task(task)
         except vimconn.vimconnNotFoundException as e:
@@ -2731,7 +2761,10 @@ def edit_datacenter(mydb, datacenter_id_name, datacenter_descriptor):
                     if new_config_dict[k]==None:
                         to_delete.append(k)
 
-                config_dict = yaml.load(datacenter["config"])
+                config_text = datacenter.get("config")
+                if not config_text:
+                    config_text = '{}'
+                config_dict = yaml.load(config_text)
                 config_dict.update(new_config_dict)
                 #delete null fields
                 for k in to_delete:
@@ -2811,12 +2844,48 @@ def associate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter, vim_tenant_id=
     # create thread
     datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, tenant_dict['uuid'], datacenter_id)  # reload data
     thread_name = get_non_used_vim_name(datacenter_name, datacenter_id, tenant_dict['name'], tenant_dict['uuid'])
-    new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, datacenter_name, db=db, db_lock=db_lock)
+    new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, datacenter_name, db=db, db_lock=db_lock, ovim=ovim)
     new_thread.start()
     thread_id = datacenter_id + "." + tenant_dict['uuid']
     vim_threads["running"][thread_id] = new_thread
     return datacenter_id
 
+def edit_datacenter_to_tenant(mydb, nfvo_tenant, datacenter_id, vim_tenant_id=None, vim_tenant_name=None, vim_username=None, vim_password=None, config=None):
+    #Obtain the data of this datacenter_tenant_id
+    vim_data = mydb.get_rows(
+        SELECT=("datacenter_tenants.vim_tenant_name", "datacenter_tenants.vim_tenant_id", "datacenter_tenants.user",
+                "datacenter_tenants.passwd", "datacenter_tenants.config"),
+        FROM="datacenter_tenants JOIN tenants_datacenters ON datacenter_tenants.uuid=tenants_datacenters.datacenter_tenant_id",
+        WHERE={"tenants_datacenters.nfvo_tenant_id": nfvo_tenant,
+               "tenants_datacenters.datacenter_id": datacenter_id})
+
+    logger.debug(str(vim_data))
+    if len(vim_data) < 1:
+        raise NfvoException("Datacenter {} is not attached for tenant {}".format(datacenter_id, nfvo_tenant), HTTP_Conflict)
+
+    v = vim_data[0]
+    if v['config']:
+        v['config'] = yaml.load(v['config'])
+
+    if vim_tenant_id:
+        v['vim_tenant_id'] = vim_tenant_id
+    if vim_tenant_name:
+        v['vim_tenant_name'] = vim_tenant_name
+    if vim_username:
+        v['user'] = vim_username
+    if vim_password:
+        v['passwd'] = vim_password
+    if config:
+        if not v['config']:
+            v['config'] = {}
+        v['config'].update(config)
+
+    logger.debug(str(v))
+    deassociate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter_id, vim_tenant_id=v['vim_tenant_id'])
+    associate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter_id, vim_tenant_id=v['vim_tenant_id'], vim_tenant_name=v['vim_tenant_name'],
+                                   vim_username=v['user'], vim_password=v['passwd'], config=v['config'])
+
+    return datacenter_id
 
 def deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter, vim_tenant_id=None):
     #get datacenter info
@@ -3054,3 +3123,122 @@ def vim_action_create(mydb, tenant_id, datacenter, item, descriptor):
         raise NfvoException("Not possible to create {} at VIM: {}".format(item, str(e)), e.http_code)
 
     return vim_action_get(mydb, tenant_id, datacenter, item, content)
+
+def sdn_controller_create(mydb, tenant_id, sdn_controller):
+    data = ovim.new_of_controller(sdn_controller)
+    logger.debug('New SDN controller created with uuid {}'.format(data))
+    return data
+
+def sdn_controller_update(mydb, tenant_id, controller_id, sdn_controller):
+    data = ovim.edit_of_controller(controller_id, sdn_controller)
+    msg = 'SDN controller {} updated'.format(data)
+    logger.debug(msg)
+    return msg
+
+def sdn_controller_list(mydb, tenant_id, controller_id=None):
+    if controller_id == None:
+        data = ovim.get_of_controllers()
+    else:
+        data = ovim.show_of_controller(controller_id)
+
+    msg = 'SDN controller list:\n {}'.format(data)
+    logger.debug(msg)
+    return data
+
+def sdn_controller_delete(mydb, tenant_id, controller_id):
+    select_ = ('uuid', 'config')
+    datacenters = mydb.get_rows(FROM='datacenters', SELECT=select_)
+    for datacenter in datacenters:
+        if datacenter['config']:
+            config = yaml.load(datacenter['config'])
+            if 'sdn-controller' in config and config['sdn-controller'] == controller_id:
+                raise NfvoException("SDN controller {} is in use by datacenter {}".format(controller_id, datacenter['uuid']), HTTP_Conflict)
+
+    data = ovim.delete_of_controller(controller_id)
+    msg = 'SDN controller {} deleted'.format(data)
+    logger.debug(msg)
+    return msg
+
+def datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, sdn_port_mapping):
+    controller = mydb.get_rows(FROM="datacenters", SELECT=("config",), WHERE={"uuid":datacenter_id})
+    if len(controller) < 1:
+        raise NfvoException("Datacenter {} not present in the database".format(datacenter_id), HTTP_Not_Found)
+
+    try:
+        sdn_controller_id = yaml.load(controller[0]["config"])["sdn-controller"]
+    except:
+        raise NfvoException("The datacenter {} has not an SDN controller associated".format(datacenter_id), HTTP_Bad_Request)
+
+    sdn_controller = ovim.show_of_controller(sdn_controller_id)
+    switch_dpid = sdn_controller["dpid"]
+
+    maps = list()
+    for compute_node in sdn_port_mapping:
+        #element = {"ofc_id": sdn_controller_id, "region": datacenter_id, "switch_dpid": switch_dpid}
+        element = dict()
+        element["compute_node"] = compute_node["compute_node"]
+        for port in compute_node["ports"]:
+            element["pci"] = port.get("pci")
+            element["switch_port"] = port.get("switch_port")
+            element["switch_mac"] = port.get("switch_mac")
+            if not element["pci"] or not (element["switch_port"] or element["switch_mac"]):
+                raise NfvoException ("The mapping must contain the 'pci' and at least one of the elements 'switch_port'"
+                                     " or 'switch_mac'", HTTP_Bad_Request)
+            maps.append(dict(element))
+
+    return ovim.set_of_port_mapping(maps, ofc_id=sdn_controller_id, switch_dpid=switch_dpid, region=datacenter_id)
+
+def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id):
+    maps = ovim.get_of_port_mappings(db_filter={"region": datacenter_id})
+
+    result = {
+        "sdn-controller": None,
+        "datacenter-id": datacenter_id,
+        "dpid": None,
+        "ports_mapping": list()
+    }
+
+    datacenter = mydb.get_table_by_uuid_name('datacenters', datacenter_id)
+    if datacenter['config']:
+        config = yaml.load(datacenter['config'])
+        if 'sdn-controller' in config:
+            controller_id = config['sdn-controller']
+            sdn_controller = sdn_controller_list(mydb, tenant_id, controller_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 len(maps) == 0:
+        return result
+
+    ports_correspondence_dict = dict()
+    for link in maps:
+        if result["sdn-controller"] != link["ofc_id"]:
+            raise NfvoException("The sdn-controller specified for different port mappings differ", HTTP_Internal_Server_Error)
+        if result["dpid"] != link["switch_dpid"]:
+            raise NfvoException("The dpid specified for different port mappings differ", HTTP_Internal_Server_Error)
+        element = dict()
+        element["pci"] = link["pci"]
+        if link["switch_port"]:
+            element["switch_port"] = link["switch_port"]
+        if link["switch_mac"]:
+            element["switch_mac"] = link["switch_mac"]
+
+        if not link["compute_node"] in ports_correspondence_dict:
+            content = dict()
+            content["compute_node"] = link["compute_node"]
+            content["ports"] = list()
+            ports_correspondence_dict[link["compute_node"]] = content
+
+        ports_correspondence_dict[link["compute_node"]]["ports"].append(element)
+
+    for key in sorted(ports_correspondence_dict):
+        result["ports_mapping"].append(ports_correspondence_dict[key])
+
+    return result
+
+def datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id):
+    return ovim.clear_of_port_mapping(db_filter={"region":datacenter_id})
\ No newline at end of file
index b5ec9a0..d8305a7 100644 (file)
@@ -912,7 +912,7 @@ class nfvo_db(db_base.db_base):
                     #from_text = "instance_nets join instance_scenarios on instance_nets.instance_scenario_id=instance_scenarios.uuid " + \
                     #            "join sce_nets on instance_scenarios.scenario_id=sce_nets.scenario_id"
                     #where_text = "instance_nets.instance_scenario_id='"+ instance_dict['uuid'] + "'"
-                    cmd = "SELECT uuid,vim_net_id,status,error_msg,vim_info,created, sce_net_id, net_id as vnf_net_id, datacenter_id, datacenter_tenant_id"\
+                    cmd = "SELECT uuid,vim_net_id,status,error_msg,vim_info,created, sce_net_id, net_id as vnf_net_id, datacenter_id, datacenter_tenant_id, sdn_net_id"\
                             " FROM instance_nets" \
                             " WHERE instance_scenario_id='{}' ORDER BY created_at".format(instance_dict['uuid'])
                     self.logger.debug(cmd)
index c34d831..0e4287e 100755 (executable)
--- a/openmano
+++ b/openmano
 '''
 openmano client used to interact with openmano-server (openmanod) 
 '''
-__author__="Alfonso Tierno, Gerardo Garcia"
+__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes"
 __date__ ="$09-oct-2014 09:09:48$"
-__version__="0.4.11-r517"
-version_date="Jan 2017"
+__version__="0.4.13-r519"
+version_date="Mar 2017"
 
 from argcomplete.completers import FilesCompleter
 import os
@@ -120,6 +120,7 @@ def _print_verbose(mano_response, verbose_level=0):
         return result
 
     if mano_response.status_code == 200:
+        uuid = None
         for content in content_list:
             if "uuid" in content:
                 uuid = content['uuid']
@@ -931,6 +932,38 @@ def datacenter_attach(args):
             print "Try to specify a different name with --vim-tenant-name"
     return result
 
+
+def datacenter_edit_vim_tenant(args):
+    tenant = _get_tenant()
+    datacenter = _get_datacenter(args.name)
+    headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+
+    if not (args.vim_tenant_id or args.vim_tenant_name or args.user or args.password or args.config):
+        raise OpenmanoCLIError("Error. At least one parameter must be updated.")
+
+    datacenter_dict = {}
+    if args.vim_tenant_id != None:
+        datacenter_dict['vim_tenant'] = args.vim_tenant_id
+    if args.vim_tenant_name != None:
+        datacenter_dict['vim_tenant_name'] = args.vim_tenant_name
+    if args.user != None:
+        datacenter_dict['vim_username'] = args.user
+    if args.password != None:
+        datacenter_dict['vim_password'] = args.password
+    if args.config != None:
+        datacenter_dict["config"] = _load_file_or_yaml(args.config)
+    payload_req = json.dumps({"datacenter": datacenter_dict})
+
+    # print payload_req
+
+    URLrequest = "http://%s:%s/openmano/%s/datacenters/%s" % (mano_host, mano_port, tenant, datacenter)
+    logger.debug("openmano request: %s", payload_req)
+    mano_response = requests.put(URLrequest, headers=headers_req, data=payload_req)
+    logger.debug("openmano response: %s", mano_response.text)
+    result = _print_verbose(mano_response, args.verbose)
+
+    return result
+
 def datacenter_detach(args):
     if args.all:
         tenant = "any"
@@ -960,7 +993,13 @@ def datacenter_create(args):
     if args.url!=None:
         datacenter_dict["vim_url_admin"] = args.url_admin 
     if args.config!=None:
-        datacenter_dict["config"] = _load_file_or_yaml(args.config) 
+        datacenter_dict["config"] = _load_file_or_yaml(args.config)
+    if args.sdn_controller!=None:
+        tenant = _get_tenant()
+        sdn_controller = _get_item_uuid("sdn_controllers", args.sdn_controller, tenant)
+        if not 'config' in datacenter_dict:
+            datacenter_dict['config'] = {}
+        datacenter_dict['config']['sdn-controller'] = sdn_controller
     payload_req = json.dumps( {"datacenter": datacenter_dict })
     
     #print payload_req
@@ -1007,6 +1046,181 @@ def datacenter_list(args):
         args.verbose += 1
     return _print_verbose(mano_response, args.verbose)
 
+def datacenter_sdn_port_mapping_set(args):
+    tenant = _get_tenant()
+    datacenter = _get_datacenter(args.name, tenant)
+    headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+
+    if not args.file:
+        raise OpenmanoCLIError(
+            "No yaml/json has been provided specifying the SDN port mapping")
+
+    port_mapping = yaml.load(datacenter_sdn_port_mapping_list(args))
+    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))
+            if not (len(r) > 0 and r[0].lower() == "y"):
+                return 0
+        args.force = True
+        print datacenter_sdn_port_mapping_clear(args)
+
+    sdn_port_mapping = _load_file_or_yaml(args.file)
+    payload_req = json.dumps({"sdn_port_mapping": sdn_port_mapping})
+
+    URLrequest = "http://%s:%s/openmano/%s/datacenters/%s/sdn_mapping" % (mano_host, mano_port, tenant, datacenter)
+    logger.debug("openmano request: %s", payload_req)
+    mano_response = requests.post(URLrequest, headers=headers_req, data=payload_req)
+    logger.debug("openmano response: %s", mano_response.text)
+
+    if mano_response.status_code == 200:
+        return yaml.safe_dump(mano_response.json())
+    else:
+        return mano_response.content
+
+def datacenter_sdn_port_mapping_list(args):
+    tenant = _get_tenant()
+    datacenter = _get_datacenter(args.name, tenant)
+
+    URLrequest = "http://%s:%s/openmano/%s/datacenters/%s/sdn_mapping" % (mano_host, mano_port, tenant, datacenter)
+    mano_response = requests.get(URLrequest)
+    logger.debug("openmano response: %s", mano_response.text)
+
+    if mano_response.status_code != 200:
+        return mano_response.content
+
+    return yaml.safe_dump(mano_response.json())
+
+def datacenter_sdn_port_mapping_clear(args):
+    tenant = _get_tenant()
+    datacenter = _get_datacenter(args.name, tenant)
+
+    if not args.force:
+        r = raw_input("Clean SDN port mapping for datacenter %s (y/N)? " %(datacenter))
+        if  not (len(r)>0  and r[0].lower()=="y"):
+            return 0
+
+    URLrequest = "http://%s:%s/openmano/%s/datacenters/%s/sdn_mapping" % (mano_host, mano_port, tenant, datacenter)
+    mano_response = requests.delete(URLrequest)
+    logger.debug("openmano response: %s", mano_response.text)
+
+    if mano_response.status_code != 200:
+        if "No port mapping for datacenter" in mano_response.content:
+            return "No port mapping for datacenter " + datacenter + " has been found"
+        return mano_response.content
+
+    return yaml.safe_dump(mano_response.json())
+
+def sdn_controller_create(args):
+    tenant = _get_tenant()
+    headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+
+    if not (args.ip and args.port and args.dpid and args.type):
+        raise OpenmanoCLIError("The following arguments are required: ip, port, dpid, type")
+
+    controller_dict = {}
+    controller_dict['name'] = args.name
+    controller_dict['ip'] = args.ip
+    controller_dict['port'] = int(args.port)
+    controller_dict['dpid'] = args.dpid
+    controller_dict['type'] = args.type
+    if args.description != None:
+        controller_dict['description'] = args.description
+    if args.user != None:
+        controller_dict['user'] = args.user
+    if args.password != None:
+        controller_dict['password'] = args.password
+
+    payload_req = json.dumps({"sdn_controller": controller_dict})
+
+    # print payload_req
+
+    URLrequest = "http://%s:%s/openmano/%s/sdn_controllers" % (mano_host, mano_port, tenant)
+    logger.debug("openmano request: %s", payload_req)
+    mano_response = requests.post(URLrequest, headers=headers_req, data=payload_req)
+    logger.debug("openmano response: %s", mano_response.text)
+    result = _print_verbose(mano_response, args.verbose)
+
+    return result
+
+def sdn_controller_edit(args):
+    tenant = _get_tenant()
+    controller_uuid = _get_item_uuid("sdn_controllers", args.name, tenant)
+    headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+
+    if not (args.new_name or args.ip or args.port or args.dpid or args.type):
+        raise OpenmanoCLIError("At least one parameter must be editd")
+
+    if not args.force:
+        r = raw_input("Update SDN controller %s (y/N)? " %(args.name))
+        if  not (len(r)>0  and r[0].lower()=="y"):
+            return 0
+
+    controller_dict = {}
+    if args.new_name != None:
+        controller_dict['name'] = args.new_name
+    if args.ip != None:
+        controller_dict['ip'] = args.ip
+    if args.port != None:
+        controller_dict['port'] = args.port
+    if args.dpid != None:
+        controller_dict['dpid'] = args.dpid
+    if args.type != None:
+        controller_dict['type'] = args.type
+    if args.description != None:
+        controller_dict['description'] = args.description
+    if args.user != None:
+        controller_dict['user'] = args.user
+    if args.password != None:
+        controller_dict['password'] = args.password
+
+    payload_req = json.dumps({"sdn_controller": controller_dict})
+
+    # print payload_req
+
+    URLrequest = "http://%s:%s/openmano/%s/sdn_controllers/%s" % (mano_host, mano_port, tenant, controller_uuid)
+    logger.debug("openmano request: %s", payload_req)
+    mano_response = requests.put(URLrequest, headers=headers_req, data=payload_req)
+    logger.debug("openmano response: %s", mano_response.text)
+    result = _print_verbose(mano_response, args.verbose)
+
+    return result
+
+def sdn_controller_list(args):
+    tenant = _get_tenant()
+    headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+
+    if args.name:
+        toshow = _get_item_uuid("sdn_controllers", args.name, tenant)
+        URLrequest = "http://%s:%s/openmano/%s/sdn_controllers/%s" %(mano_host, mano_port, tenant, toshow)
+    else:
+        URLrequest = "http://%s:%s/openmano/%s/sdn_controllers" %(mano_host, mano_port, tenant)
+    #print URLrequest
+    mano_response = requests.get(URLrequest)
+    logger.debug("openmano response: %s", mano_response.text )
+    if args.verbose==None:
+        args.verbose=0
+    if args.name!=None:
+        args.verbose += 1
+
+    result = json.dumps(mano_response.json(), indent=4)
+    return result
+
+def sdn_controller_delete(args):
+    tenant = _get_tenant()
+    controller_uuid = _get_item_uuid("sdn_controllers", args.name, tenant)
+
+    if not args.force:
+        r = raw_input("Delete SDN controller %s (y/N)? " % (args.name))
+        if not (len(r) > 0 and r[0].lower() == "y"):
+            return 0
+
+    URLrequest = "http://%s:%s/openmano/%s/sdn_controllers/%s" % (mano_host, mano_port, tenant, controller_uuid)
+    mano_response = requests.delete(URLrequest)
+    logger.debug("openmano response: %s", mano_response.text)
+    result = _print_verbose(mano_response, args.verbose)
+
+    return result
+
 def vim_action(args):
     #print "datacenter-net-action",args
     tenant = _get_tenant()
@@ -1045,11 +1259,11 @@ def vim_action(args):
             create_dict[args.item]['name'] = args.name
         #if args.description:
         #    create_dict[args.item]['description'] = args.description
-        if args.item=="vim-net":
+        if args.item=="network":
             if args.bind_net:
                 create_dict[args.item]['bind_net'] = args.bind_net
-            if args.bind_type:
-                create_dict[args.item]['bind_type'] = args.bind_type
+            if args.type:
+                create_dict[args.item]['type'] = args.type
             if args.shared:
                 create_dict[args.item]['shared'] = args.shared
         if "name" not in create_dict[args.item]:
@@ -1201,6 +1415,53 @@ def element_edit(args):
     return _print_verbose(mano_response, args.verbose)
 
 
+def datacenter_edit(args):
+    tenant = _get_tenant()
+    element = _get_item_uuid('datacenters', args.name, tenant)
+    headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+    URLrequest = "http://%s:%s/openmano/datacenters/%s" % (mano_host, mano_port, element)
+
+    has_arguments = False
+    if args.file != None:
+        has_arguments = True
+        payload = _load_file_or_yaml(args.file)
+    else:
+        payload = {}
+
+    if args.sdn_controller != None:
+        has_arguments = True
+        if not 'config' in payload:
+            payload['config'] = {}
+        if not 'sdn-controller' in payload['config']:
+            payload['config']['sdn-controller'] = {}
+        if args.sdn_controller == 'null':
+            payload['config']['sdn-controller'] = None
+        else:
+            payload['config']['sdn-controller'] = _get_item_uuid("sdn_controllers", args.sdn_controller, tenant)
+
+    if not has_arguments:
+        raise OpenmanoCLIError("At least one argument must be provided to modify the datacenter")
+
+    if 'datacenter' not in payload:
+        payload = {'datacenter': payload}
+    payload_req = json.dumps(payload)
+
+    # print payload_req
+    if not args.force or (args.name == None and args.filer == None):
+        r = raw_input(" Edit datacenter " + args.name + " (y/N)? ")
+        if len(r) > 0 and r[0].lower() == "y":
+            pass
+        else:
+            return 0
+    logger.debug("openmano request: %s", payload_req)
+    mano_response = requests.put(URLrequest, headers=headers_req, data=payload_req)
+    logger.debug("openmano response: %s", mano_response.text)
+    if args.verbose == None:
+        args.verbose = 0
+    if args.name != None:
+        args.verbose += 1
+    return _print_verbose(mano_response, args.verbose)
+
 global mano_host
 global mano_port
 global mano_tenant
@@ -1329,7 +1590,7 @@ if __name__=="__main__":
     tenant_list_parser.add_argument("name", nargs='?', help="name or uuid of the tenant")
     tenant_list_parser.set_defaults(func=tenant_list)
 
-    item_list=('tenant','datacenter') #put tenant before so that help appear in order
+    item_list=('tenant') #put tenant before so that help appear in order
     for item in item_list:
         element_edit_parser = subparsers.add_parser(item+'-edit', parents=[parent_parser], help="edits one "+item)
         element_edit_parser.add_argument("name", help="name or uuid of the "+item)
@@ -1344,6 +1605,7 @@ if __name__=="__main__":
     datacenter_create_parser.add_argument("--type", action="store", help="datacenter type: openstack or openvim (default)")
     datacenter_create_parser.add_argument("--config", action="store", help="aditional configuration in json/yaml format")
     datacenter_create_parser.add_argument("--description", action="store", help="description of the datacenter")
+    datacenter_create_parser.add_argument("--sdn-controller", action="store", help="Name or uuid of the SDN controller to be used", dest='sdn_controller')
     datacenter_create_parser.set_defaults(func=datacenter_create)
 
     datacenter_delete_parser = subparsers.add_parser('datacenter-delete', parents=[parent_parser], help="deletes a datacenter from the catalogue")
@@ -1351,6 +1613,14 @@ if __name__=="__main__":
     datacenter_delete_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking")
     datacenter_delete_parser.set_defaults(func=datacenter_delete)
 
+    datacenter_edit_parser = subparsers.add_parser('datacenter-edit', parents=[parent_parser], help="Edit datacenter")
+    datacenter_edit_parser.add_argument("name", help="name or uuid of the datacenter")
+    datacenter_edit_parser.add_argument("--file", help="json/yaml text or file with the changes").completer = FilesCompleter
+    datacenter_edit_parser.add_argument("--sdn-controller", action="store",
+                                          help="Name or uuid of the SDN controller to be used. Specify 'null' to clear entry", dest='sdn_controller')
+    datacenter_edit_parser.add_argument("-f", "--force", action="store_true", help="do not prompt for confirmation")
+    datacenter_edit_parser.set_defaults(func=datacenter_edit)
+
     datacenter_list_parser = subparsers.add_parser('datacenter-list', parents=[parent_parser], help="lists information about a datacenter")
     datacenter_list_parser.add_argument("name", nargs='?', help="name or uuid of the datacenter")
     datacenter_list_parser.add_argument("-a", "--all", action="store_true", help="shows all datacenters, not only datacenters attached to tenant")
@@ -1365,11 +1635,107 @@ if __name__=="__main__":
     datacenter_attach_parser.add_argument("--config", action="store", help="aditional configuration in json/yaml format")
     datacenter_attach_parser.set_defaults(func=datacenter_attach)
 
+    datacenter_edit_vim_tenant_parser = subparsers.add_parser('datacenter-edit-vim-tenant', parents=[parent_parser],
+                                                     help="Edit the association of a datacenter to the operating tenant")
+    datacenter_edit_vim_tenant_parser.add_argument("name", help="name or uuid of the datacenter")
+    datacenter_edit_vim_tenant_parser.add_argument('--vim-tenant-id', action='store',
+                                          help="specify a datacenter tenant to use. A new one is created by default")
+    datacenter_edit_vim_tenant_parser.add_argument('--vim-tenant-name', action='store', help="specify a datacenter tenant name.")
+    datacenter_edit_vim_tenant_parser.add_argument("--user", action="store", help="user credentials for the datacenter")
+    datacenter_edit_vim_tenant_parser.add_argument("--password", action="store", help="password credentials for the datacenter")
+    datacenter_edit_vim_tenant_parser.add_argument("--config", action="store",
+                                          help="aditional configuration in json/yaml format")
+    datacenter_edit_vim_tenant_parser.set_defaults(func=datacenter_edit_vim_tenant)
+
     datacenter_detach_parser = subparsers.add_parser('datacenter-detach', parents=[parent_parser], help="removes the association between a datacenter and the operating tenant")
     datacenter_detach_parser.add_argument("name", help="name or uuid of the datacenter")
     datacenter_detach_parser.add_argument("-a", "--all", action="store_true", help="removes all associations from this datacenter")
     datacenter_detach_parser.set_defaults(func=datacenter_detach)
 
+    #=======================datacenter_sdn_port_mapping_xxx section=======================
+    #datacenter_sdn_port_mapping_set
+    datacenter_sdn_port_mapping_set_parser = subparsers.add_parser('datacenter-sdn-port-mapping-set',
+                                                                   parents=[parent_parser],
+                                                                   help="Load a file with the mapping of physical ports "
+                                                                        "and the ports of the dataplaneswitch controlled "
+                                                                        "by a datacenter")
+    datacenter_sdn_port_mapping_set_parser.add_argument("name", action="store", help="specifies the datacenter")
+    datacenter_sdn_port_mapping_set_parser.add_argument("file",
+                                                        help="json/yaml text or file with the port mapping").completer = FilesCompleter
+    datacenter_sdn_port_mapping_set_parser.add_argument("-f", "--force", action="store_true",
+                                                          help="forces overwriting without asking")
+    datacenter_sdn_port_mapping_set_parser.set_defaults(func=datacenter_sdn_port_mapping_set)
+
+    #datacenter_sdn_port_mapping_list
+    datacenter_sdn_port_mapping_list_parser = subparsers.add_parser('datacenter-sdn-port-mapping-list',
+                                                                    parents=[parent_parser],
+                                                                    help="Show the SDN port mapping in a datacenter")
+    datacenter_sdn_port_mapping_list_parser.add_argument("name", action="store", help="specifies the datacenter")
+    datacenter_sdn_port_mapping_list_parser.set_defaults(func=datacenter_sdn_port_mapping_list)
+
+    # datacenter_sdn_port_mapping_clear
+    datacenter_sdn_port_mapping_clear_parser = subparsers.add_parser('datacenter-sdn-port-mapping-clear',
+                                                                    parents=[parent_parser],
+                                                                    help="Clean the the SDN port mapping in a datacenter")
+    datacenter_sdn_port_mapping_clear_parser.add_argument("name", action="store",
+                                                         help="specifies the datacenter")
+    datacenter_sdn_port_mapping_clear_parser.add_argument("-f", "--force", action="store_true",
+                                              help="forces clearing without asking")
+    datacenter_sdn_port_mapping_clear_parser.set_defaults(func=datacenter_sdn_port_mapping_clear)
+    # =======================
+
+    # =======================sdn_controller_xxx section=======================
+    # sdn_controller_create
+    sdn_controller_create_parser = subparsers.add_parser('sdn-controller-create', parents=[parent_parser],
+                                                        help="Creates an SDN controller entity within RO")
+    sdn_controller_create_parser.add_argument("name", help="name of the SDN controller")
+    sdn_controller_create_parser.add_argument("--description", action="store", help="description of the SDN controller")
+    sdn_controller_create_parser.add_argument("--ip", action="store", help="IP of the SDN controller")
+    sdn_controller_create_parser.add_argument("--port", action="store", help="Port of the SDN controller")
+    sdn_controller_create_parser.add_argument("--dpid", action="store",
+                                             help="DPID of the dataplane switch controlled by this SDN controller")
+    sdn_controller_create_parser.add_argument("--type", action="store",
+                                             help="Specify the SDN controller type. Valid types are 'opendaylight' and 'floodlight'")
+    sdn_controller_create_parser.add_argument("--user", action="store", help="user credentials for the SDN controller")
+    sdn_controller_create_parser.add_argument("--passwd", action="store", dest='password',
+                                             help="password credentials for the SDN controller")
+    sdn_controller_create_parser.set_defaults(func=sdn_controller_create)
+
+    # sdn_controller_edit
+    sdn_controller_edit_parser = subparsers.add_parser('sdn-controller-edit', parents=[parent_parser],
+                                                        help="Update one or more options of a SDN controller")
+    sdn_controller_edit_parser.add_argument("name", help="name or uuid of the SDN controller", )
+    sdn_controller_edit_parser.add_argument("--name", action="store", help="Update the name of the SDN controller",
+                                              dest='new_name')
+    sdn_controller_edit_parser.add_argument("--description", action="store", help="description of the SDN controller")
+    sdn_controller_edit_parser.add_argument("--ip", action="store", help="IP of the SDN controller")
+    sdn_controller_edit_parser.add_argument("--port", action="store", help="Port of the SDN controller")
+    sdn_controller_edit_parser.add_argument("--dpid", action="store",
+                                             help="DPID of the dataplane switch controlled by this SDN controller")
+    sdn_controller_edit_parser.add_argument("--type", action="store",
+                                             help="Specify the SDN controller type. Valid types are 'opendaylight' and 'floodlight'")
+    sdn_controller_edit_parser.add_argument("--user", action="store", help="user credentials for the SDN controller")
+    sdn_controller_edit_parser.add_argument("--password", action="store",
+                                             help="password credentials for the SDN controller", dest='password')
+    sdn_controller_edit_parser.add_argument("-f", "--force", action="store_true", help="do not prompt for confirmation")
+    #TODO: include option --file
+    sdn_controller_edit_parser.set_defaults(func=sdn_controller_edit)
+
+    #sdn_controller_list
+    sdn_controller_list_parser = subparsers.add_parser('sdn-controller-list',
+                                                                    parents=[parent_parser],
+                                                                    help="List the SDN controllers")
+    sdn_controller_list_parser.add_argument("name", nargs='?', help="name or uuid of the SDN controller")
+    sdn_controller_list_parser.set_defaults(func=sdn_controller_list)
+
+    # sdn_controller_delete
+    sdn_controller_delete_parser = subparsers.add_parser('sdn-controller-delete',
+                                                                    parents=[parent_parser],
+                                                                    help="Delete the the SDN controller")
+    sdn_controller_delete_parser.add_argument("name", help="name or uuid of the SDN controller")
+    sdn_controller_delete_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking")
+    sdn_controller_delete_parser.set_defaults(func=sdn_controller_delete)
+    # =======================
 
     action_dict={'net-update': 'retrieves external networks from datacenter',
                  'net-edit': 'edits an external network',
index 1ea64f6..5b1b942 100644 (file)
@@ -1095,3 +1095,75 @@ instance_scenario_action_schema = {
     #"maxProperties": 1,
     "additionalProperties": False
 }
+
+sdn_controller_properties={
+    "name": name_schema,
+    "dpid": {"type":"string", "pattern":"^[0-9a-fA-F][02468aceACE](:[0-9a-fA-F]{2}){7}$"},
+    "ip": ip_schema,
+    "port": port_schema,
+    "type": {"type": "string", "enum": ["opendaylight","floodlight","onos"]},
+    "version": {"type" : "string", "minLength":1, "maxLength":12},
+    "user": nameshort_schema,
+    "password": passwd_schema
+}
+sdn_controller_schema = {
+    "title":"sdn controller information schema",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "type":"object",
+    "properties":{
+        "sdn_controller":{
+            "type":"object",
+            "properties":sdn_controller_properties,
+            "required": ["name", "port", 'ip', 'dpid', 'type'],
+            "additionalProperties": False
+        }
+    },
+    "required": ["sdn_controller"],
+    "additionalProperties": False
+}
+
+sdn_controller_edit_schema = {
+    "title":"sdn controller update information schema",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "type":"object",
+    "properties":{
+        "sdn_controller":{
+            "type":"object",
+            "properties":sdn_controller_properties,
+            "additionalProperties": False
+        }
+    },
+    "required": ["sdn_controller"],
+    "additionalProperties": False
+}
+
+sdn_port_mapping_schema  = {
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title":"sdn port mapping information schema",
+    "type": "object",
+    "properties": {
+        "sdn_port_mapping": {
+            "type": "array",
+            "items": {
+                "type": "object",
+                "properties": {
+                    "compute_node": nameshort_schema,
+                    "ports": {
+                        "type": "array",
+                        "items": {
+                            "type": "object",
+                            "properties": {
+                                "pci": pci_schema,
+                                "switch_port": nameshort_schema,
+                                "switch_mac": mac_schema
+                            },
+                            "required": ["pci"]
+                        }
+                    }
+                },
+                "required": ["compute_node", "ports"]
+            }
+        }
+    },
+    "required": ["sdn_port_mapping"]
+}
\ No newline at end of file
index c11f747..19a430d 100644 (file)
 '''
 openmano python client used to interact with openmano-server  
 '''
-__author__="Alfonso Tierno"
+__author__="Alfonso Tierno, Pablo Montes"
 __date__ ="$09-Mar-2016 09:09:48$"
-__version__="0.0.1-r467"
-version_date="Mar 2016"
+__version__="0.0.2-r468"
+version_date="Feb 2017"
 
 import requests
 import json
index 03901c3..4ac475d 100755 (executable)
@@ -33,9 +33,9 @@ It loads the configuration file and launches the http_server thread that will li
 '''
 __author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes"
 __date__ ="$26-aug-2014 11:09:29$"
-__version__="0.5.8-r518"
-version_date="Jan 2017"
-database_version="0.19"      #expected database schema version
+__version__="0.5.9-r519"
+version_date="Mar 2017"
+database_version="0.20"      #expected database schema version
 
 import httpserver
 import time
diff --git a/sdn/sdn_port_mapping.yaml b/sdn/sdn_port_mapping.yaml
new file mode 100644 (file)
index 0000000..47da6e0
--- /dev/null
@@ -0,0 +1,44 @@
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# 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
+##
+---
+#The mapping is composed of a list of compute nodes. Each compute nodes has two elements:
+#"compute_node": name to identify the compute node within the datacenter
+#"ports": list of ports mapped to a switch for that compute node.
+#The information to identify the SDN controller and the dataplane switch is obtained from the datacenter information
+- compute_node:    "compute node 1"
+  ports:
+    #Each mapped port contains the following information:
+    #"pci": pci address of the port in the compute node. This is a mandatory parameter
+    #"switch_mac": MAC address of the corresponding port in the dataplane switch.
+    #"switch_port": Openflow name of the port in the dataplane switch.
+    #"switch_mac" or "switch_port" must be specified. Both of them could be specified
+    - pci:         "0000:81:00.0"
+      switch_port: "port-2/1"
+    - pci:         "0000:81:00.1"
+      switch_mac:  "52:54:00:94:21:22"
+- compute_node:    "compute node 2"
+  ports:
+    - pci:         "0000:81:00.0"
+      switch_port: "port-2/3"
+      switch_mac:  "52:54:00:94:22:21"
+    - pci:         "0000:81:00.1"
+      switch_port: "port-2/4"
+      switch_mac:  "52:54:00:94:22:22"
diff --git a/test/RO_tests/sr_iov/scenario_p2p_sriov.yaml b/test/RO_tests/sr_iov/scenario_p2p_sriov.yaml
new file mode 100644 (file)
index 0000000..57cb2c8
--- /dev/null
@@ -0,0 +1,41 @@
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# 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
+##
+---
+schema_version:  2
+scenario:
+  name:          p2p_sriov
+  description:   Network scenario consisting of two machines with a sr-iov interconnected between them
+  vnfs: 
+    sriov1:                   # vnf/net name in the scenario
+      vnf_name:  sriov        # VNF name as introduced in OPENMANO DB
+    sriov2:                   # vnf/net name in the scenario
+      vnf_name:  sriov        # VNF name as introduced in OPENMANO DB
+  networks: 
+    mgmt:                   # provide a name for this net or connection
+      external:  true
+      interfaces: 
+      - sriov1:  eth0       # Node and its interface
+      - sriov2:  eth0       # Node and its interface
+    dataplane:                   # provide a name for this net or connection
+      interfaces: 
+      - sriov1:  xe0       # Node and its interface
+      - sriov2:  xe0       # Node and its interface
+
diff --git a/test/RO_tests/sr_iov/vnfd_1sriov.yaml b/test/RO_tests/sr_iov/vnfd_1sriov.yaml
new file mode 100644 (file)
index 0000000..e424b02
--- /dev/null
@@ -0,0 +1,53 @@
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# 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
+##
+---
+vnf:
+    name:        sriov
+    description: Machine with EPA and a SR-IOV interface
+    external-connections:
+    -   name:              eth0
+        type:              bridge
+        VNFC:              sriov-VM
+        local_iface_name:  eth0
+        description:       management interface
+    -   name:              xe0
+        type:              data
+        VNFC:              sriov-VM
+        local_iface_name:  xe0
+        description:       Dataplane interface
+    VNFC:
+    -   name:        sriov-VM
+        description: Machine with EPA and a SR-IOV interface
+        image name:  centos
+        disk: 20
+        numas: 
+        -   threads: 1          # "cores", "paired-threads", "threads"
+            memory: 1                 # GBytes
+            interfaces:
+            -   name:      xe0
+                vpci:      "0000:00:11.0"
+                dedicated: "no"         # "yes"(passthrough), "no"(sriov with vlan tags), "yes:sriov"(sriovi, but exclusive and without vlan tag)
+                bandwidth: 1 Gbps
+
+        bridge-ifaces:
+        -   name:      eth0
+            vpci:      "0000:00:0a.0"
+
index 780d6e5..d89c4d1 100644 (file)
@@ -25,7 +25,7 @@
 This is thread that interact with the host and the libvirt to manage VM
 One thread will be launched per host 
 '''
-__author__ = "Alfonso Tierno"
+__author__ = "Alfonso Tierno, Pablo Montes"
 __date__ = "$10-feb-2017 12:07:15$"
 
 import threading
@@ -34,6 +34,7 @@ import Queue
 import logging
 import vimconn
 from db_base import db_base_Exception
+from openvim.ovim import ovimException
 
 
 # from logging import Logger
@@ -46,7 +47,7 @@ def is_task_id(id):
 
 class vim_thread(threading.Thread):
 
-    def __init__(self, vimconn, task_lock, name=None, datacenter_name=None, datacenter_tenant_id=None, db=None, db_lock=None):
+    def __init__(self, vimconn, task_lock, name=None, datacenter_name=None, datacenter_tenant_id=None, db=None, db_lock=None, ovim=None):
         """Init a thread.
         Arguments:
             'id' number of thead
@@ -64,6 +65,7 @@ class vim_thread(threading.Thread):
         self.vim = vimconn
         self.datacenter_name = datacenter_name
         self.datacenter_tenant_id = datacenter_tenant_id
+        self.ovim = ovim
         if not name:
             self.name = vimconn["id"] + "." + vimconn["config"]["datacenter_tenant_id"]
         else:
@@ -152,11 +154,30 @@ class vim_thread(threading.Thread):
             task_id = task["id"]
             params = task["params"]
             net_id = self.vim.new_network(*params)
-            try:
-                with self.db_lock:
-                    self.db.update_rows("instance_nets", UPDATE={"vim_net_id": net_id}, WHERE={"vim_net_id": task_id})
-            except db_base_Exception as e:
-                self.logger.error("Error updating database %s", str(e))
+
+            net_name = params[0]
+            net_type = params[1]
+
+            network = None
+            sdn_controller = self.vim.config.get('sdn-controller')
+            if sdn_controller and (net_type == "data" or net_type == "ptp"):
+                network = {"name": net_name, "type": net_type}
+
+                vim_net = self.vim.get_network(net_id)
+                if vim_net.get('network_type') != 'vlan':
+                    raise vimconn.vimconnException(net_name + "defined as type " + net_type + " but the created network in vim is " + vim_net['provider:network_type'])
+
+                network["vlan"] = vim_net.get('segmentation_id')
+
+            sdn_net_id = None
+            with self.db_lock:
+                if network:
+                    sdn_net_id = self.ovim.new_network(network)
+                self.db.update_rows("instance_nets", UPDATE={"vim_net_id": net_id, "sdn_net_id": sdn_net_id}, WHERE={"vim_net_id": task_id})
+
+            return True, net_id
+        except db_base_Exception as e:
+            self.logger.error("Error updating database %s", str(e))
             return True, net_id
         except vimconn.vimconnException as e:
             self.logger.error("Error creating NET, task=%s: %s", str(task_id), str(e))
@@ -168,6 +189,9 @@ class vim_thread(threading.Thread):
             except db_base_Exception as e:
                 self.logger.error("Error updating database %s", str(e))
             return False, str(e)
+        except ovimException as e:
+            self.logger.error("Error creating NET in ovim, task=%s: %s", str(task_id), str(e))
+            return False, str(e)
 
     def new_vm(self, task):
         try:
@@ -227,7 +251,8 @@ class vim_thread(threading.Thread):
             return False, str(e)
 
     def del_net(self, task):
-        net_id = task["params"]
+        net_id = task["params"][0]
+        sdn_net_id = task["params"][1]
         if is_task_id(net_id):
             try:
                 task_create = task["depends"][net_id]
@@ -240,8 +265,15 @@ class vim_thread(threading.Thread):
             except Exception as e:
                 return False, "Error trying to get task_id='{}':".format(net_id, str(e))
         try:
-            return True, self.vim.delete_network(net_id)
+            result = self.vim.delete_network(net_id)
+            if sdn_net_id:
+                with self.db_lock:
+                    self.ovim.delete_network(sdn_net_id)
+            return True, result
         except vimconn.vimconnException as e:
             return False, str(e)
+        except ovimException as e:
+            logging.error("Error deleting network from ovim. net_id: {}, sdn_net_id: {}".format(net_id, sdn_net_id))
+            return False, str(e)
 
 
index a9bd9be..ed259e4 100644 (file)
@@ -232,6 +232,8 @@ class vimconnector():
             'id': (mandatory) VIM network id
             'name': (mandatory) VIM network name
             'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER'
+            'network_type': (optional) can be 'vxlan', 'vlan' or 'flat'
+            'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id
             'error_msg': (optional) text that explains the ERROR status
             other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param
         List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity,
index b501d9d..2c238db 100644 (file)
@@ -389,6 +389,8 @@ class vimconnector(vimconn.vimconnector):
                 subnet = {"id": subnet_id, "fault": str(e)}
             subnets.append(subnet)
         net["subnets"] = subnets
+        net["network_type"] = net.get('provider:network_type')
+        net["segmentation_id"] = net.get('provider:segmentation_id')
         return net
 
     def delete_network(self, net_id):