feature8030 move WIM connector to plugins 66/8066/10 py3
authortierno <alfonso.tiernosepulveda@telefonica.com>
Mon, 21 Oct 2019 15:31:27 +0000 (15:31 +0000)
committertierno <alfonso.tiernosepulveda@telefonica.com>
Sat, 23 Nov 2019 15:08:17 +0000 (15:08 +0000)
Change-Id: I2e08ce7aa04f3e61adcf866925bf235b53e15baf
Signed-off-by: tierno <alfonso.tiernosepulveda@telefonica.com>
73 files changed:
Dockerfile
Dockerfile-local
RO-SDN-dynpac/Makefile [new file with mode: 0644]
RO-SDN-dynpac/osm_rosdn_dynpac/wimconn_dynpac.py [new file with mode: 0644]
RO-SDN-dynpac/requirements.txt [new file with mode: 0644]
RO-SDN-dynpac/setup.py [new file with mode: 0644]
RO-SDN-dynpac/stdeb.cfg [new file with mode: 0644]
RO-SDN-dynpac/tox.ini [new file with mode: 0644]
RO-SDN-onos_openflow/Makefile [new file with mode: 0644]
RO-SDN-onos_openflow/osm_rosdn_onosof/onos_of.py [new file with mode: 0644]
RO-SDN-onos_openflow/osm_rosdn_onosof/sdnconn_onosof.py [new file with mode: 0644]
RO-SDN-onos_openflow/requirements.txt [new file with mode: 0644]
RO-SDN-onos_openflow/setup.py [new file with mode: 0644]
RO-SDN-onos_openflow/stdeb.cfg [new file with mode: 0644]
RO-SDN-onos_openflow/tox.ini [new file with mode: 0644]
RO-SDN-tapi/Makefile [new file with mode: 0644]
RO-SDN-tapi/osm_rosdn_tapi/wimconn_ietfl2vpn.py [new file with mode: 0644]
RO-SDN-tapi/requirements.txt [new file with mode: 0644]
RO-SDN-tapi/setup.py [new file with mode: 0644]
RO-SDN-tapi/stdeb.cfg [new file with mode: 0644]
RO-SDN-tapi/tox.ini [new file with mode: 0644]
RO-client/README.rst
RO/README.rst
RO/osm_ro/__init__.py
RO/osm_ro/database_utils/install-db-server.sh
RO/osm_ro/database_utils/migrate_mano_db.sh
RO/osm_ro/database_utils/migrations/down/34_remove_wim_tables.sql
RO/osm_ro/database_utils/migrations/down/35_remove_sfc_ingress_and_egress.sql
RO/osm_ro/database_utils/migrations/up/34_add_wim_tables.sql
RO/osm_ro/database_utils/migrations/up/35_add_sfc_ingress_and_egress.sql
RO/osm_ro/http_tools/__init__.py
RO/osm_ro/http_tools/errors.py
RO/osm_ro/http_tools/request_processing.py
RO/osm_ro/http_tools/tests/__init__.py
RO/osm_ro/http_tools/tests/test_errors.py
RO/osm_ro/http_tools/tests/test_handler.py
RO/osm_ro/http_tools/tox.ini
RO/osm_ro/nfvo.py
RO/osm_ro/nfvo_db.py
RO/osm_ro/openmanod.py
RO/osm_ro/osm-ro.service
RO/osm_ro/scripts/RO-start.sh
RO/osm_ro/sdn.py [new file with mode: 0755]
RO/osm_ro/tests/__init__.py
RO/osm_ro/tests/test_db.py
RO/osm_ro/tests/test_utils.py
RO/osm_ro/vim_thread.py
RO/osm_ro/wim/__init__.py
RO/osm_ro/wim/engine.py
RO/osm_ro/wim/errors.py
RO/osm_ro/wim/failing_connector.py
RO/osm_ro/wim/openflow_conn.py [new file with mode: 0644]
RO/osm_ro/wim/persistence.py
RO/osm_ro/wim/schemas.py
RO/osm_ro/wim/sdnconn.py [new file with mode: 0644]
RO/osm_ro/wim/tests/__init__.py
RO/osm_ro/wim/tests/fixtures.py
RO/osm_ro/wim/tests/test_actions.py
RO/osm_ro/wim/tests/test_http_handler.py
RO/osm_ro/wim/tests/test_wim_thread.py
RO/osm_ro/wim/tox.ini
RO/osm_ro/wim/wan_link_actions.py
RO/osm_ro/wim/wim_thread.py
RO/osm_ro/wim/wimconn.py [deleted file]
RO/osm_ro/wim/wimconn_dynpac.py [deleted file]
RO/osm_ro/wim/wimconn_fake.py
RO/osm_ro/wim/wimconn_ietfl2vpn.py [deleted file]
RO/osm_ro/wim/wimconn_odl.py
RO/requirements.txt
RO/test/RO_tests/v3_2vdu_set_ip_mac/scenario_2vdu_set_ip_mac.yaml
RO/test/RO_tests/v3_2vdu_set_ip_mac/vnfd_2vdu_set_ip_mac.yaml
RO/test/RO_tests/v3_2vdu_set_ip_mac/vnfd_2vdu_set_ip_mac2.yaml
devops-stages/stage-build.sh

index c758db9..8eec007 100644 (file)
@@ -20,7 +20,7 @@
 FROM ubuntu:16.04
 RUN  apt-get update && \
   DEBIAN_FRONTEND=noninteractive apt-get --yes install git tox make python-all python3 python3-pip debhelper wget && \
 FROM ubuntu:16.04
 RUN  apt-get update && \
   DEBIAN_FRONTEND=noninteractive apt-get --yes install git tox make python-all python3 python3-pip debhelper wget && \
-  DEBIAN_FRONTEND=noninteractive apt-get --yes install python3-all libssl-dev && \
+  DEBIAN_FRONTEND=noninteractive apt-get --yes install python3-all libssl-dev flake8 && \
   DEBIAN_FRONTEND=noninteractive pip3 install -U setuptools setuptools-version-command stdeb
 
 # FROM ubuntu:16.04
   DEBIAN_FRONTEND=noninteractive pip3 install -U setuptools setuptools-version-command stdeb
 
 # FROM ubuntu:16.04
index 48447ac..8430fa9 100644 (file)
@@ -26,7 +26,6 @@ RUN apt-get update && apt-get install -y git python3 python3-pip \
 
 # This is not needed, because package dependency will install anyway.
 # But done here in order to harry up image generation using cache
 
 # This is not needed, because package dependency will install anyway.
 # But done here in order to harry up image generation using cache
-
 RUN DEBIAN_FRONTEND=noninteractive  apt-get -y install python3-neutronclient python3-openstackclient \
     python3-requests python3-netaddr python3-argcomplete
 
 RUN DEBIAN_FRONTEND=noninteractive  apt-get -y install python3-neutronclient python3-openstackclient \
     python3-requests python3-netaddr python3-argcomplete
 
@@ -54,6 +53,8 @@ RUN /root/RO/RO/osm_ro/scripts/install-osm-im.sh --develop && \
     python3 -m pip install -e /root/RO/RO-VIM-openvim && \
     python3 -m pip install -e /root/RO/RO-VIM-aws && \
     python3 -m pip install -e /root/RO/RO-VIM-fos && \
     python3 -m pip install -e /root/RO/RO-VIM-openvim && \
     python3 -m pip install -e /root/RO/RO-VIM-aws && \
     python3 -m pip install -e /root/RO/RO-VIM-fos && \
+    python3 -m pip install -e /root/RO/RO-SDN-dynpac && \
+    python3 -m pip install -e /root/RO/RO-SDN-tapi && \
     rm -rf /root/.cache && \
     apt-get clean && \
     rm -rf /var/lib/apt/lists/*
     rm -rf /root/.cache && \
     apt-get clean && \
     rm -rf /var/lib/apt/lists/*
diff --git a/RO-SDN-dynpac/Makefile b/RO-SDN-dynpac/Makefile
new file mode 100644 (file)
index 0000000..9fb4408
--- /dev/null
@@ -0,0 +1,24 @@
+##
+# 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.
+##
+
+all: clean package
+
+clean:
+       rm -rf dist deb_dist osm_rosdn_dynpac-*.tar.gz osm_rosdn_dynpac.egg-info .eggs
+
+package:
+       python3 setup.py --command-packages=stdeb.command sdist_dsc
+       cd deb_dist/osm-rosdn-dynpac*/ && dpkg-buildpackage -rfakeroot -uc -us
+
diff --git a/RO-SDN-dynpac/osm_rosdn_dynpac/wimconn_dynpac.py b/RO-SDN-dynpac/osm_rosdn_dynpac/wimconn_dynpac.py
new file mode 100644 (file)
index 0000000..b32856b
--- /dev/null
@@ -0,0 +1,233 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2018 David GarcĂ­a, University of the Basque Country
+# Copyright 2018 University of the Basque Country
+# This file is part of openmano
+# All Rights Reserved.
+# Contact information at http://i2t.ehu.eus
+#
+# # 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.
+
+import requests
+import json
+import logging
+from enum import Enum
+
+from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
+
+
+class SdnError(Enum):
+    UNREACHABLE = 'Unable to reach the WIM.',
+    SERVICE_TYPE_ERROR = 'Unexpected service_type. Only "L2" is accepted.',
+    CONNECTION_POINTS_SIZE = \
+        'Unexpected number of connection points: 2 expected.',
+    ENCAPSULATION_TYPE = \
+        'Unexpected service_endpoint_encapsulation_type. \
+         Only "dotq1" is accepted.',
+    BANDWIDTH = 'Unable to get the bandwidth.',
+    STATUS = 'Unable to get the status for the service.',
+    DELETE = 'Unable to delete service.',
+    CLEAR_ALL = 'Unable to clear all the services',
+    UNKNOWN_ACTION = 'Unknown action invoked.',
+    BACKUP = 'Unable to get the backup parameter.',
+    UNSUPPORTED_FEATURE = "Unsupported feature",
+    UNAUTHORIZED = "Failed while authenticating"
+
+
+class SdnAPIActions(Enum):
+    CHECK_CONNECTIVITY = "CHECK_CONNECTIVITY",
+    CREATE_SERVICE = "CREATE_SERVICE",
+    DELETE_SERVICE = "DELETE_SERVICE",
+    CLEAR_ALL = "CLEAR_ALL",
+    SERVICE_STATUS = "SERVICE_STATUS",
+
+
+class DynpacConnector(SdnConnectorBase):
+    __supported_service_types = ["ELINE (L2)", "ELINE"]
+    __supported_encapsulation_types = ["dot1q"]
+    __WIM_LOGGER = 'openmano.sdnconn.dynpac'
+    __ENCAPSULATION_TYPE_PARAM = "service_endpoint_encapsulation_type"
+    __ENCAPSULATION_INFO_PARAM = "service_endpoint_encapsulation_info"
+    __BACKUP_PARAM = "backup"
+    __BANDWIDTH_PARAM = "bandwidth"
+    __SERVICE_ENDPOINT_PARAM = "service_endpoint_id"
+    __WAN_SERVICE_ENDPOINT_PARAM = "service_endpoint_id"
+    __WAN_MAPPING_INFO_PARAM = "service_mapping_info"
+    __SW_ID_PARAM = "switch_dpid"
+    __SW_PORT_PARAM = "switch_port"
+    __VLAN_PARAM = "vlan"
+
+    # Public functions exposed to the Resource Orchestrator
+    def __init__(self, wim, wim_account, config=None, logger=None):
+        self.logger = logger or logging.getLogger(self.__WIM_LOGGER)
+        super().__init__(wim, wim_account, config, self.logger)
+        self.__wim = wim
+        self.__wim_account = wim_account
+        self.__config = config
+        self.__wim_url = self.__wim.get("wim_url")
+        self.__user = wim_account.get("user")
+        self.__passwd = wim_account.get("password")
+        self.logger.info("Initialized.")
+
+    def create_connectivity_service(self, service_type, connection_points, **kwargs):
+        self.__check_service(service_type, connection_points, kwargs)
+
+        body = self.__get_body(service_type, connection_points, kwargs)
+
+        headers = {'Content-type': 'application/x-www-form-urlencoded'}
+        endpoint = "{}/service/create".format(self.__wim_url)
+
+        try:
+            response = requests.post(endpoint, data=body, headers=headers)
+        except requests.exceptions.RequestException as e:
+            self.__exception(e.message, http_code=503)
+
+        if response.status_code != 200:
+            error = json.loads(response.content)
+            reason = "Reason: {}. ".format(error.get("code"))
+            description = "Description: {}.".format(error.get("description"))
+            exception = reason + description
+            self.__exception(exception, http_code=response.status_code)
+        uuid = response.content
+        self.logger.info("Service with uuid {} created.".format(uuid))
+        return (uuid, None)
+
+    def edit_connectivity_service(self, service_uuid,
+                                  conn_info, connection_points,
+                                  **kwargs):
+        self.__exception(SdnError.UNSUPPORTED_FEATURE, http_code=501)
+
+    def get_connectivity_service_status(self, service_uuid):
+        endpoint = "{}/service/status/{}".format(self.__wim_url, service_uuid)
+        try:
+            response = requests.get(endpoint)
+        except requests.exceptions.RequestException as e:
+            self.__exception(e.message, http_code=503)
+
+        if response.status_code != 200:
+            self.__exception(SdnError.STATUS, http_code=response.status_code)
+        self.logger.info("Status for service with uuid {}: {}"
+                         .format(service_uuid, response.content))
+        return response.content
+
+    def delete_connectivity_service(self, service_uuid, conn_info):
+        endpoint = "{}/service/delete/{}".format(self.__wim_url, service_uuid)
+        try:
+            response = requests.delete(endpoint)
+        except requests.exceptions.RequestException as e:
+            self.__exception(e.message, http_code=503)
+        if response.status_code != 200:
+            self.__exception(SdnError.DELETE, http_code=response.status_code)
+
+        self.logger.info("Service with uuid: {} deleted".format(service_uuid))
+
+    def clear_all_connectivity_services(self):
+        endpoint = "{}/service/clearAll".format(self.__wim_url)
+        try:
+            response = requests.delete(endpoint)
+            http_code = response.status_code
+        except requests.exceptions.RequestException as e:
+            self.__exception(e.message, http_code=503)
+        if http_code != 200:
+            self.__exception(SdnError.CLEAR_ALL, http_code=http_code)
+
+        self.logger.info("{} services deleted".format(response.content))
+        return "{} services deleted".format(response.content)
+
+    def check_connectivity(self):
+        endpoint = "{}/checkConnectivity".format(self.__wim_url)
+
+        try:
+            response = requests.get(endpoint)
+            http_code = response.status_code
+        except requests.exceptions.RequestException as e:
+            self.__exception(e.message, http_code=503)
+
+        if http_code != 200:
+            self.__exception(SdnError.UNREACHABLE, http_code=http_code)
+        self.logger.info("Connectivity checked")
+
+    def check_credentials(self):
+        endpoint = "{}/checkCredentials".format(self.__wim_url)
+        auth = (self.__user, self.__passwd)
+
+        try:
+            response = requests.get(endpoint, auth=auth)
+            http_code = response.status_code
+        except requests.exceptions.RequestException as e:
+            self.__exception(e.message, http_code=503)
+
+        if http_code != 200:
+            self.__exception(SdnError.UNAUTHORIZED, http_code=http_code)
+        self.logger.info("Credentials checked")
+
+    # Private functions
+    def __exception(self, x, **kwargs):
+        http_code = kwargs.get("http_code")
+        if hasattr(x, "value"):
+            error = x.value
+        else:
+            error = x
+        self.logger.error(error)
+        raise SdnConnectorError(error, http_code=http_code)
+
+    def __check_service(self, service_type, connection_points, kwargs):
+        if service_type not in self.__supported_service_types:
+            self.__exception(SdnError.SERVICE_TYPE_ERROR, http_code=400)
+
+        if len(connection_points) != 2:
+            self.__exception(SdnError.CONNECTION_POINTS_SIZE, http_code=400)
+
+        for connection_point in connection_points:
+            enc_type = connection_point.get(self.__ENCAPSULATION_TYPE_PARAM)
+            if enc_type not in self.__supported_encapsulation_types:
+                self.__exception(SdnError.ENCAPSULATION_TYPE, http_code=400)
+
+        # Commented out for as long as parameter isn't implemented
+        # bandwidth = kwargs.get(self.__BANDWIDTH_PARAM)
+        # if not isinstance(bandwidth, int):
+            # self.__exception(SdnError.BANDWIDTH, http_code=400)
+
+        # Commented out for as long as parameter isn't implemented
+        # backup = kwargs.get(self.__BACKUP_PARAM)
+        # if not isinstance(backup, bool):
+            # self.__exception(SdnError.BACKUP, http_code=400)
+
+    def __get_body(self, service_type, connection_points, kwargs):
+        port_mapping = self.__config.get("service_endpoint_mapping")
+        selected_ports = []
+        for connection_point in connection_points:
+            endpoint_id = connection_point.get(self.__SERVICE_ENDPOINT_PARAM)
+            port = filter(lambda x: x.get(self.__WAN_SERVICE_ENDPOINT_PARAM) == endpoint_id, port_mapping)[0]
+            port_info = port.get(self.__WAN_MAPPING_INFO_PARAM)
+            selected_ports.append(port_info)
+        if service_type == "ELINE (L2)" or service_type == "ELINE":
+            service_type = "L2"
+        body = {
+            "connection_points": [{
+                "wan_switch_dpid": selected_ports[0].get(self.__SW_ID_PARAM),
+                "wan_switch_port": selected_ports[0].get(self.__SW_PORT_PARAM),
+                "wan_vlan": connection_points[0].get(self.__ENCAPSULATION_INFO_PARAM).get(self.__VLAN_PARAM)
+            }, {
+                "wan_switch_dpid": selected_ports[1].get(self.__SW_ID_PARAM),
+                "wan_switch_port": selected_ports[1].get(self.__SW_PORT_PARAM),
+                "wan_vlan": connection_points[1].get(self.__ENCAPSULATION_INFO_PARAM).get(self.__VLAN_PARAM)
+            }],
+            "bandwidth": 100,  # Hardcoded for as long as parameter isn't implemented
+            "service_type": service_type,
+            "backup": False    # Hardcoded for as long as parameter isn't implemented
+        }
+        return "body={}".format(json.dumps(body))
diff --git a/RO-SDN-dynpac/requirements.txt b/RO-SDN-dynpac/requirements.txt
new file mode 100644 (file)
index 0000000..44c797f
--- /dev/null
@@ -0,0 +1,18 @@
+##
+# 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.
+##
+
+requests
+git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro
+
diff --git a/RO-SDN-dynpac/setup.py b/RO-SDN-dynpac/setup.py
new file mode 100644 (file)
index 0000000..46d25e1
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+##
+# 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.
+##
+
+from setuptools import setup
+
+_name = "osm_rosdn_dynpac"
+
+README = """
+===========
+osm-rosdn_dynpac
+===========
+
+osm-ro pluging for dynpac SDN
+"""
+
+setup(
+    name=_name,
+    description='OSM ro sdn plugin for dynpac',
+    long_description=README,
+    version_command=('git describe --match v* --tags --long --dirty', 'pep440-git-full'),
+    # version=VERSION,
+    # python_requires='>3.5.0',
+    author='ETSI OSM',
+    # TODO py3 author_email='',
+    maintainer='OSM_TECH@LIST.ETSI.ORG',  # TODO py3
+    # TODO py3 maintainer_email='',
+    url='https://osm.etsi.org/gitweb/?p=osm/RO.git;a=summary',
+    license='Apache 2.0',
+
+    packages=[_name],
+    include_package_data=True,
+    dependency_links=["git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro"],
+    install_requires=["requests", "osm-ro"],
+    setup_requires=['setuptools-version-command'],
+    entry_points={
+        'osm_rosdn.plugins': ['rosdn_dynpac = osm_rosdn_dynpac.wimconn_dynpac'],
+    },
+)
diff --git a/RO-SDN-dynpac/stdeb.cfg b/RO-SDN-dynpac/stdeb.cfg
new file mode 100644 (file)
index 0000000..0c718e4
--- /dev/null
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+[DEFAULT]
+X-Python3-Version : >= 3.5
+Depends3: python3-requests, python3-osm-ro
+
diff --git a/RO-SDN-dynpac/tox.ini b/RO-SDN-dynpac/tox.ini
new file mode 100644 (file)
index 0000000..a1e866a
--- /dev/null
@@ -0,0 +1,41 @@
+##
+# 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.
+##
+
+[tox]
+envlist = py3
+toxworkdir={homedir}/.tox
+
+[testenv]
+basepython = python3
+install_command = python3 -m pip install -r requirements.txt -U {opts} {packages}
+# deps = -r{toxinidir}/test-requirements.txt
+commands=python3 -m unittest discover -v
+
+[testenv:flake8]
+basepython = python3
+deps = flake8
+commands = flake8 osm_rosdn_dynpac --max-line-length 120 \
+    --exclude .svn,CVS,.gz,.git,__pycache__,.tox,local,temp --ignore W291,W293,E226,W504
+
+[testenv:unittest]
+basepython = python3
+commands = python3 -m unittest osm_rosdn_dynpac.tests
+
+[testenv:build]
+basepython = python3
+deps = stdeb
+       setuptools-version-command
+commands = python3 setup.py --command-packages=stdeb.command bdist_deb
+
diff --git a/RO-SDN-onos_openflow/Makefile b/RO-SDN-onos_openflow/Makefile
new file mode 100644 (file)
index 0000000..5e96ce0
--- /dev/null
@@ -0,0 +1,24 @@
+##
+# 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.
+##
+
+all: clean package
+
+clean:
+       rm -rf dist deb_dist osm_rosdn_onosof-*.tar.gz osm_rosdn_onosof.egg-info .eggs
+
+package:
+       python3 setup.py --command-packages=stdeb.command sdist_dsc
+       cd deb_dist/osm-rosdn-onosof*/ && dpkg-buildpackage -rfakeroot -uc -us
+
diff --git a/RO-SDN-onos_openflow/osm_rosdn_onosof/onos_of.py b/RO-SDN-onos_openflow/osm_rosdn_onosof/onos_of.py
new file mode 100644 (file)
index 0000000..060d1d3
--- /dev/null
@@ -0,0 +1,469 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2016, I2T Research Group (UPV/EHU)
+# 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: alaitz.mendiola@ehu.eus or alaitz.mendiola@gmail.com
+##
+
+'''
+ImplementS the pluging for the Open Network Operating System (ONOS) openflow
+controller. It creates the class OF_conn to create dataplane connections
+with static rules based on packet destination MAC address
+'''
+
+__author__="Alaitz Mendiola"
+__date__ ="$22-nov-2016$"
+
+
+import json
+import requests
+import base64
+import logging
+from osm_ro.wim.openflow_conn import OpenflowConn, OpenflowConnException, OpenflowConnConnectionException, \
+    OpenflowConnUnexpectedResponse, OpenflowConnAuthException, OpenflowConnNotFoundException, \
+    OpenflowConnConflictException, OpenflowConnNotSupportedException, OpenflowConnNotImplemented
+
+
+class OfConnOnos(OpenflowConn):
+    """
+    ONOS connector. No MAC learning is used
+    """
+    def __init__(self, params):
+        """ Constructor.
+            Params: dictionary with the following keys:
+                of_dpid:     DPID to use for this controller ?? Does a controller have a dpid?
+                url:         must be [http://HOST:PORT/
+                of_user:     user credentials, can be missing or None
+                of_password: password credentials
+                of_debug:    debug level for logging. Default to ERROR
+                other keys are ignored
+            Raise an exception if same parameter is missing or wrong
+        """
+
+        OpenflowConn.__init__(self, params)
+
+        # check params
+        url = params.get("of_url")
+        if not url:
+            raise ValueError("'url' must be provided")
+        if not url.startswith("http"):
+            url = "http://" + url
+        if not url.endswith("/"):
+            url = url + "/"
+        self.url = url + "onos/v1/"
+
+        #internal variables
+        self.name = "onosof"
+        self.headers = {'content-type':'application/json','accept':'application/json',}
+
+        self.auth="None"
+        self.pp2ofi={}  # From Physical Port to OpenFlow Index
+        self.ofi2pp={}  # From OpenFlow Index to Physical Port
+
+        self.dpid = str(params["of_dpid"])
+        self.id = 'of:'+str(self.dpid.replace(':', ''))
+
+        # TODO This may not be straightforward
+        if params.get("of_user"):
+            of_password=params.get("of_password", "")
+            self.auth = base64.b64encode(bytes(params["of_user"] + ":" + of_password, "utf-8"))
+            self.auth = self.auth.decode()
+            self.headers['authorization'] = 'Basic ' + self.auth
+
+        self.logger = logging.getLogger('vim.OF.onos')
+        self.logger.setLevel( getattr(logging, params.get("of_debug", "ERROR")) )
+        self.ip_address = None
+
+    def get_of_switches(self):
+        """
+        Obtain a a list of switches or DPID detected by this controller
+        :return: list where each element a tuple pair (DPID, IP address)
+                 Raise a openflowconnUnexpectedResponse expection in case of failure
+        """
+        try:
+            self.headers['content-type'] = 'text/plain'
+            of_response = requests.get(self.url + "devices", headers=self.headers)
+            error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+            if of_response.status_code != 200:
+                self.logger.warning("get_of_switches " + error_text)
+                raise OpenflowConnUnexpectedResponse(error_text)
+
+            self.logger.debug("get_of_switches " + error_text)
+            info = of_response.json()
+
+            if type(info) != dict:
+                self.logger.error("get_of_switches. Unexpected response, not a dict: %s", str(info))
+                raise OpenflowConnUnexpectedResponse("Unexpected response, not a dict. Wrong version?")
+
+            node_list = info.get('devices')
+
+            if type(node_list) is not list:
+                self.logger.error(
+                    "get_of_switches. Unexpected response, at 'devices', not found or not a list: %s",
+                    str(type(node_list)))
+                raise OpenflowConnUnexpectedResponse("Unexpected response, at 'devices', not found "
+                                                                   "or not a list. Wrong version?")
+
+            switch_list = []
+            for node in node_list:
+                node_id = node.get('id')
+                if node_id is None:
+                    self.logger.error("get_of_switches. Unexpected response at 'device':'id', not found: %s",
+                                      str(node))
+                    raise OpenflowConnUnexpectedResponse("Unexpected response at 'device':'id', "
+                                                                       "not found . Wrong version?")
+
+                node_ip_address = node.get('annotations').get('managementAddress')
+                if node_ip_address is None:
+                    self.logger.error(
+                        "get_of_switches. Unexpected response at 'device':'managementAddress', not found: %s",
+                        str(node))
+                    raise OpenflowConnUnexpectedResponse(
+                        "Unexpected response at 'device':'managementAddress', not found. Wrong version?")
+
+                node_id_hex = hex(int(node_id.split(':')[1])).split('x')[1].zfill(16)
+
+                switch_list.append(
+                    (':'.join(a + b for a, b in zip(node_id_hex[::2], node_id_hex[1::2])), node_ip_address))
+            return switch_list
+
+        except requests.exceptions.RequestException as e:
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("get_of_switches " + error_text)
+            raise OpenflowConnConnectionException(error_text)
+        except ValueError as e:
+            # ValueError in the case that JSON can not be decoded
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("get_of_switches " + error_text)
+            raise OpenflowConnUnexpectedResponse(error_text)
+
+    def obtain_port_correspondence(self):
+        """
+        Obtain the correspondence between physical and openflow port names
+        :return: dictionary with physical name as key, openflow name as value
+                 Raise a openflowconnUnexpectedResponse expection in case of failure
+        """
+        try:
+            self.headers['content-type'] = 'text/plain'
+            of_response = requests.get(self.url + "devices/" + self.id + "/ports", headers=self.headers)
+            error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+            if of_response.status_code != 200:
+                self.logger.warning("obtain_port_correspondence " + error_text)
+                raise OpenflowConnUnexpectedResponse(error_text)
+
+            self.logger.debug("obtain_port_correspondence " + error_text)
+            info = of_response.json()
+
+            node_connector_list = info.get('ports')
+            if type(node_connector_list) is not list:
+                self.logger.error(
+                    "obtain_port_correspondence. Unexpected response at 'ports', not found or not a list: %s",
+                    str(node_connector_list))
+                raise OpenflowConnUnexpectedResponse("Unexpected response at 'ports', not found  or not "
+                                                                   "a list. Wrong version?")
+
+            for node_connector in node_connector_list:
+                if node_connector['port'] != "local":
+                    self.pp2ofi[str(node_connector['annotations']['portName'])] = str(node_connector['port'])
+                    self.ofi2pp[str(node_connector['port'])] = str(node_connector['annotations']['portName'])
+
+            node_ip_address = info['annotations']['managementAddress']
+            if node_ip_address is None:
+                self.logger.error(
+                    "obtain_port_correspondence. Unexpected response at 'managementAddress', not found: %s",
+                    str(self.id))
+                raise OpenflowConnUnexpectedResponse("Unexpected response at 'managementAddress', "
+                                                                   "not found. Wrong version?")
+            self.ip_address = node_ip_address
+
+            # print self.name, ": obtain_port_correspondence ports:", self.pp2ofi
+            return self.pp2ofi
+        except requests.exceptions.RequestException as e:
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("obtain_port_correspondence " + error_text)
+            raise OpenflowConnConnectionException(error_text)
+        except ValueError as e:
+            # ValueError in the case that JSON can not be decoded
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("obtain_port_correspondence " + error_text)
+            raise OpenflowConnUnexpectedResponse(error_text)
+
+    def get_of_rules(self, translate_of_ports=True):
+        """
+        Obtain the rules inserted at openflow controller
+        :param translate_of_ports: if True it translates ports from openflow index to physical switch name
+        :return: list where each item is a  dictionary with the following content:
+                    priority: rule priority
+                    name:         rule name (present also as the master dict key)
+                    ingress_port: match input port of the rule
+                    dst_mac:      match destination mac address of the rule, can be missing or None if not apply
+                    vlan_id:      match vlan tag of the rule, can be missing or None if not apply
+                    actions:      list of actions, composed by a pair tuples:
+                        (vlan, None/int): for stripping/setting a vlan tag
+                        (out, port):      send to this port
+                    switch:       DPID, all
+                 Raise a openflowconnUnexpectedResponse expection in case of failure
+        """
+
+        try:
+
+            if len(self.ofi2pp) == 0:
+                self.obtain_port_correspondence()
+
+            # get rules
+            self.headers['content-type'] = 'text/plain'
+            of_response = requests.get(self.url + "flows/" + self.id, headers=self.headers)
+            error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+
+            # The configured page does not exist if there are no rules installed. In that case we return an empty dict
+            if of_response.status_code == 404:
+                return {}
+
+            elif of_response.status_code != 200:
+                self.logger.warning("get_of_rules " + error_text)
+                raise OpenflowConnUnexpectedResponse(error_text)
+            self.logger.debug("get_of_rules " + error_text)
+
+            info = of_response.json()
+
+            if type(info) != dict:
+                self.logger.error("get_of_rules. Unexpected response, not a dict: %s", str(info))
+                raise OpenflowConnUnexpectedResponse("Unexpected openflow response, not a dict. "
+                                                                   "Wrong version?")
+
+            flow_list = info.get('flows')
+
+            if flow_list is None:
+                return {}
+
+            if type(flow_list) is not list:
+                self.logger.error(
+                    "get_of_rules. Unexpected response at 'flows', not a list: %s",
+                    str(type(flow_list)))
+                raise OpenflowConnUnexpectedResponse("Unexpected response at 'flows', not a list. "
+                                                                   "Wrong version?")
+
+            rules = [] # Response list
+            for flow in flow_list:
+                if not ('id' in flow and 'selector' in flow and 'treatment' in flow and \
+                                    'instructions' in flow['treatment'] and 'criteria' in \
+                                    flow['selector']):
+                    raise OpenflowConnUnexpectedResponse("unexpected openflow response, one or more "
+                                                                       "elements are missing. Wrong version?")
+
+                rule = dict()
+                rule['switch'] = self.dpid
+                rule['priority'] = flow.get('priority')
+                rule['name'] = flow['id']
+
+                for criteria in flow['selector']['criteria']:
+                    if criteria['type'] == 'IN_PORT':
+                        in_port = str(criteria['port'])
+                        if in_port != "CONTROLLER":
+                            if not in_port in self.ofi2pp:
+                                raise OpenflowConnUnexpectedResponse("Error: Ingress port {} is not "
+                                                                                   "in switch port list".format(in_port))
+                            if translate_of_ports:
+                                in_port = self.ofi2pp[in_port]
+                        rule['ingress_port'] = in_port
+
+                    elif criteria['type'] == 'VLAN_VID':
+                        rule['vlan_id'] = criteria['vlanId']
+
+                    elif criteria['type'] == 'ETH_DST':
+                        rule['dst_mac'] = str(criteria['mac']).lower()
+
+                actions = []
+                for instruction in flow['treatment']['instructions']:
+                    if instruction['type'] == "OUTPUT":
+                        out_port = str(instruction['port'])
+                        if out_port != "CONTROLLER":
+                            if not out_port in self.ofi2pp:
+                                raise OpenflowConnUnexpectedResponse("Error: Output port {} is not in "
+                                                                                   "switch port list".format(out_port))
+
+                            if translate_of_ports:
+                                out_port = self.ofi2pp[out_port]
+
+                        actions.append( ('out', out_port) )
+
+                    if instruction['type'] == "L2MODIFICATION" and instruction['subtype'] == "VLAN_POP":
+                        actions.append( ('vlan', 'None') )
+                    if instruction['type'] == "L2MODIFICATION" and instruction['subtype'] == "VLAN_ID":
+                        actions.append( ('vlan', instruction['vlanId']) )
+
+                rule['actions'] = actions
+                rules.append(rule)
+            return rules
+
+        except requests.exceptions.RequestException as e:
+            # ValueError in the case that JSON can not be decoded
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("get_of_rules " + error_text)
+            raise OpenflowConnConnectionException(error_text)
+        except ValueError as e:
+            # ValueError in the case that JSON can not be decoded
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("get_of_rules " + error_text)
+            raise OpenflowConnUnexpectedResponse(error_text)
+
+    def del_flow(self, flow_name):
+        """
+        Delete an existing rule
+        :param flow_name:
+        :return: Raise a openflowconnUnexpectedResponse expection in case of failure
+        """
+
+        try:
+            self.headers['content-type'] = None
+            of_response = requests.delete(self.url + "flows/" + self.id + "/" + flow_name, headers=self.headers)
+            error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+
+            if of_response.status_code != 204:
+                self.logger.warning("del_flow " + error_text)
+                raise OpenflowConnUnexpectedResponse(error_text)
+
+            self.logger.debug("del_flow OK " + error_text)
+            return None
+
+        except requests.exceptions.RequestException as e:
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("del_flow " + error_text)
+            raise OpenflowConnConnectionException(error_text)
+
+    def new_flow(self, data):
+        """
+        Insert a new static rule
+        :param data: dictionary with the following content:
+                priority:     rule priority
+                name:         rule name
+                ingress_port: match input port of the rule
+                dst_mac:      match destination mac address of the rule, missing or None if not apply
+                vlan_id:      match vlan tag of the rule, missing or None if not apply
+                actions:      list of actions, composed by a pair tuples with these posibilities:
+                    ('vlan', None/int): for stripping/setting a vlan tag
+                    ('out', port):      send to this port
+        :return: Raise a openflowconnUnexpectedResponse expection in case of failure
+        """
+        try:
+
+            if len(self.pp2ofi) == 0:
+                self.obtain_port_correspondence()
+
+            # Build the dictionary with the flow rule information for ONOS
+            flow = dict()
+            #flow['id'] = data['name']
+            flow['tableId'] = 0
+            flow['priority'] = data.get('priority')
+            flow['timeout'] = 0
+            flow['isPermanent'] = "true"
+            flow['appId'] = 10 # FIXME We should create an appId for OSM
+            flow['selector'] = dict()
+            flow['selector']['criteria'] = list()
+
+            # Flow rule matching criteria
+            if not data['ingress_port'] in self.pp2ofi:
+                error_text = 'Error. Port ' + data['ingress_port'] + ' is not present in the switch'
+                self.logger.warning("new_flow " + error_text)
+                raise OpenflowConnUnexpectedResponse(error_text)
+
+            ingress_port_criteria = dict()
+            ingress_port_criteria['type'] = "IN_PORT"
+            ingress_port_criteria['port'] = self.pp2ofi[data['ingress_port']]
+            flow['selector']['criteria'].append(ingress_port_criteria)
+
+            if 'dst_mac' in data:
+                dst_mac_criteria = dict()
+                dst_mac_criteria["type"] = "ETH_DST"
+                dst_mac_criteria["mac"] = data['dst_mac']
+                flow['selector']['criteria'].append(dst_mac_criteria)
+
+            if data.get('vlan_id'):
+                vlan_criteria = dict()
+                vlan_criteria["type"] = "VLAN_VID"
+                vlan_criteria["vlanId"] = int(data['vlan_id'])
+                flow['selector']['criteria'].append(vlan_criteria)
+
+            # Flow rule treatment
+            flow['treatment'] = dict()
+            flow['treatment']['instructions'] = list()
+            flow['treatment']['deferred'] = list()
+
+            for action in data['actions']:
+                new_action = dict()
+                if  action[0] == "vlan":
+                    new_action['type'] = "L2MODIFICATION"
+                    if action[1] == None:
+                        new_action['subtype'] = "VLAN_POP"
+                    else:
+                        new_action['subtype'] = "VLAN_ID"
+                        new_action['vlanId'] = int(action[1])
+                elif action[0] == 'out':
+                    new_action['type'] = "OUTPUT"
+                    if not action[1] in self.pp2ofi:
+                        error_msj = 'Port '+ action[1] + ' is not present in the switch'
+                        raise OpenflowConnUnexpectedResponse(error_msj)
+                    new_action['port'] = self.pp2ofi[action[1]]
+                else:
+                    error_msj = "Unknown item '%s' in action list" % action[0]
+                    self.logger.error("new_flow " + error_msj)
+                    raise OpenflowConnUnexpectedResponse(error_msj)
+
+                flow['treatment']['instructions'].append(new_action)
+
+            self.headers['content-type'] = 'application/json'
+            path = self.url + "flows/" + self.id
+            of_response = requests.post(path, headers=self.headers, data=json.dumps(flow) )
+
+            error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+            if of_response.status_code != 201:
+                self.logger.warning("new_flow " + error_text)
+                raise OpenflowConnUnexpectedResponse(error_text)
+
+            flowId = of_response.headers['location'][path.__len__() + 1:]
+
+            data['name'] = flowId
+
+            self.logger.debug("new_flow OK " + error_text)
+            return None
+
+        except requests.exceptions.RequestException as e:
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("new_flow " + error_text)
+            raise OpenflowConnConnectionException(error_text)
+
+    def clear_all_flows(self):
+        """
+        Delete all existing rules
+        :return: Raise a openflowconnUnexpectedResponse expection in case of failure
+        """
+        try:
+            rules = self.get_of_rules(True)
+
+            for rule in rules:
+                self.del_flow(rule)
+
+            self.logger.debug("clear_all_flows OK ")
+            return None
+
+        except requests.exceptions.RequestException as e:
+            error_text = type(e).__name__ + ": " + str(e)
+            self.logger.error("clear_all_flows " + error_text)
+            raise OpenflowConnConnectionException(error_text)
diff --git a/RO-SDN-onos_openflow/osm_rosdn_onosof/sdnconn_onosof.py b/RO-SDN-onos_openflow/osm_rosdn_onosof/sdnconn_onosof.py
new file mode 100644 (file)
index 0000000..79c1441
--- /dev/null
@@ -0,0 +1,41 @@
+##
+# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
+# 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.
+#
+##
+"""The SdnConnectorOnosOf connector is responsible for creating services using pro active operflow rules.
+"""
+
+import logging
+from osm_ro.wim.openflow_conn import SdnConnectorOpenFlow
+from .onos_of import OfConnOnos
+
+
+class SdnConnectorOnosOf(SdnConnectorOpenFlow):
+
+    def __init__(self, wim, wim_account, config=None, logger=None):
+        """Creates a connectivity based on pro-active openflow rules
+        """
+        self.logger = logging.getLogger('openmano.sdnconn.onosof')
+        super().__init__(wim, wim_account, config, logger)
+        of_params = {
+            "of_url": wim["wim_url"],
+            "of_dpid": config.get("dpid"),
+            "of_user": wim_account["user"],
+            "of_password": wim_account["password"],
+        }
+        self.openflow_conn = OfConnOnos(of_params)
+        super().__init__(wim, wim_account, config, logger, self.openflow_conn)
diff --git a/RO-SDN-onos_openflow/requirements.txt b/RO-SDN-onos_openflow/requirements.txt
new file mode 100644 (file)
index 0000000..44c797f
--- /dev/null
@@ -0,0 +1,18 @@
+##
+# 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.
+##
+
+requests
+git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro
+
diff --git a/RO-SDN-onos_openflow/setup.py b/RO-SDN-onos_openflow/setup.py
new file mode 100644 (file)
index 0000000..380adc7
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+##
+# 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.
+##
+
+from setuptools import setup
+
+_name = "osm_rosdn_onosof"
+
+README = """
+===========
+osm-rosdn_onosof
+===========
+
+osm-ro pluging for onosof (ietfl2vpn) SDN
+"""
+
+setup(
+    name=_name,
+    description='OSM ro sdn plugin for onosof (ietfl2vpn)',
+    long_description=README,
+    version_command=('git describe --match v* --tags --long --dirty', 'pep440-git-full'),
+    # version=VERSION,
+    # python_requires='>3.5.0',
+    author='ETSI OSM',
+    author_email='alfonso.tiernosepulveda@telefonica.com',
+    maintainer='Alfonso Tierno',
+    maintainer_email='alfonso.tiernosepulveda@telefonica.com',
+    url='https://osm.etsi.org/gitweb/?p=osm/RO.git;a=summary',
+    license='Apache 2.0',
+
+    packages=[_name],
+    include_package_data=True,
+    dependency_links=["git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro"],
+    install_requires=["requests", "osm-ro"],
+    setup_requires=['setuptools-version-command'],
+    entry_points={
+        'osm_rosdn.plugins': ['rosdn_onosof = osm_rosdn_onosof.sdnconn_onosof:SdnConnectorOnosOf'],
+    },
+)
diff --git a/RO-SDN-onos_openflow/stdeb.cfg b/RO-SDN-onos_openflow/stdeb.cfg
new file mode 100644 (file)
index 0000000..0c718e4
--- /dev/null
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+[DEFAULT]
+X-Python3-Version : >= 3.5
+Depends3: python3-requests, python3-osm-ro
+
diff --git a/RO-SDN-onos_openflow/tox.ini b/RO-SDN-onos_openflow/tox.ini
new file mode 100644 (file)
index 0000000..00b4585
--- /dev/null
@@ -0,0 +1,41 @@
+##
+# 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.
+##
+
+[tox]
+envlist = py3
+toxworkdir={homedir}/.tox
+
+[testenv]
+basepython = python3
+install_command = python3 -m pip install -r requirements.txt -U {opts} {packages}
+# deps = -r{toxinidir}/test-requirements.txt
+commands=python3 -m unittest discover -v
+
+[testenv:flake8]
+basepython = python3
+deps = flake8
+commands = flake8 osm_rosdn_onosof --max-line-length 120 \
+    --exclude .svn,CVS,.gz,.git,__pycache__,.tox,local,temp --ignore W291,W293,E226,W504
+
+[testenv:unittest]
+basepython = python3
+commands = python3 -m unittest osm_rosdn_onosof.tests
+
+[testenv:build]
+basepython = python3
+deps = stdeb
+       setuptools-version-command
+commands = python3 setup.py --command-packages=stdeb.command bdist_deb
+
diff --git a/RO-SDN-tapi/Makefile b/RO-SDN-tapi/Makefile
new file mode 100644 (file)
index 0000000..2e05280
--- /dev/null
@@ -0,0 +1,24 @@
+##
+# 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.
+##
+
+all: clean package
+
+clean:
+       rm -rf dist deb_dist osm_rosdn_tapi-*.tar.gz osm_rosdn_tapi.egg-info .eggs
+
+package:
+       python3 setup.py --command-packages=stdeb.command sdist_dsc
+       cd deb_dist/osm-rosdn-tapi*/ && dpkg-buildpackage -rfakeroot -uc -us
+
diff --git a/RO-SDN-tapi/osm_rosdn_tapi/wimconn_ietfl2vpn.py b/RO-SDN-tapi/osm_rosdn_tapi/wimconn_ietfl2vpn.py
new file mode 100644 (file)
index 0000000..26680b5
--- /dev/null
@@ -0,0 +1,362 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 Telefonica
+# All Rights Reserved.
+#
+# Contributors: Oscar Gonzalez de Dios, Manuel Lopez Bravo, Guillermo Pajares Martin
+# 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.
+#
+# This work has been performed in the context of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 program.
+##
+"""The SDN/WIM connector is responsible for establishing wide area network
+connectivity.
+
+This SDN/WIM connector implements the standard IETF RFC 8466 "A YANG Data
+ Model for Layer 2 Virtual Private Network (L2VPN) Service Delivery"
+
+It receives the endpoints and the necessary details to request
+the Layer 2 service.
+"""
+import requests
+import uuid
+import logging
+from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
+"""CHeck layer where we move it"""
+
+
+class WimconnectorIETFL2VPN(SdnConnectorBase):
+
+    def __init__(self, wim, wim_account, config=None, logger=None):
+        """IETF L2VPM WIM connector
+
+        Arguments: (To be completed)
+            wim (dict): WIM record, as stored in the database
+            wim_account (dict): WIM account record, as stored in the database
+        """
+        self.logger = logging.getLogger('openmano.sdnconn.ietfl2vpn')
+        super().__init__(wim, wim_account, config, logger)
+        self.headers = {'Content-Type': 'application/json'}
+        self.mappings = {m['service_endpoint_id']: m
+                         for m in self.service_endpoint_mapping}
+        self.user = wim_account.get("user")
+        self.passwd = wim_account.get("passwordd")
+        if self.user and self.passwd is not None:
+            self.auth = (self.user, self.passwd)
+        else:
+            self.auth = None
+        self.logger.info("IETFL2VPN Connector Initialized.")
+
+    def check_credentials(self):
+        endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
+        try:
+            response = requests.get(endpoint, auth=self.auth)    
+            http_code = response.status_code
+        except requests.exceptions.RequestException as e:
+            raise SdnConnectorError(e.message, http_code=503)
+
+        if http_code != 200:
+            raise SdnConnectorError("Failed while authenticating", http_code=http_code)
+        self.logger.info("Credentials checked")
+
+    def get_connectivity_service_status(self, service_uuid, conn_info=None):
+        """Monitor the status of the connectivity service stablished
+
+        Arguments:
+            service_uuid: Connectivity service unique identifier
+
+        Returns:
+            Examples::
+                {'sdn_status': 'ACTIVE'}
+                {'sdn_status': 'INACTIVE'}
+                {'sdn_status': 'DOWN'}
+                {'sdn_status': 'ERROR'}
+        """
+        try:
+            self.logger.info("Sending get connectivity service stuatus")
+            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+                self.wim["wim_url"], service_uuid)
+            response = requests.get(servicepoint, auth=self.auth)
+            if response.status_code != requests.codes.ok:
+                raise SdnConnectorError("Unable to obtain connectivity servcice status", http_code=response.status_code)
+            service_status = {'sdn_status': 'ACTIVE'}
+            return service_status
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
+               
+    def search_mapp(self, connection_point):
+        id = connection_point['service_endpoint_id']
+        if id not in self.mappings:         
+            raise SdnConnectorError("Endpoint {} not located".format(str(id)))
+        else:
+            return self.mappings[id]
+
+    def create_connectivity_service(self, service_type, connection_points, **kwargs):
+        """Stablish WAN connectivity between the endpoints
+
+        Arguments:
+            service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
+                ``L3``.
+            connection_points (list): each point corresponds to
+                an entry point from the DC to the transport network. One
+                connection point serves to identify the specific access and
+                some other service parameters, such as encapsulation type.
+                Represented by a dict as follows::
+
+                    {
+                      "service_endpoint_id": ..., (str[uuid])
+                      "service_endpoint_encapsulation_type": ...,
+                           (enum: none, dot1q, ...)
+                      "service_endpoint_encapsulation_info": {
+                        ... (dict)
+                        "vlan": ..., (int, present if encapsulation is dot1q)
+                        "vni": ... (int, present if encapsulation is vxlan),
+                        "peers": [(ipv4_1), (ipv4_2)]
+                            (present if encapsulation is vxlan)
+                      }
+                    }
+
+              The service endpoint ID should be previously informed to the WIM
+              engine in the RO when the WIM port mapping is registered.
+
+        Keyword Arguments:
+            bandwidth (int): value in kilobytes
+            latency (int): value in milliseconds
+
+        Other QoS might be passed as keyword arguments.
+
+        Returns:
+            tuple: ``(service_id, conn_info)`` containing:
+               - *service_uuid* (str): UUID of the established connectivity
+                  service
+               - *conn_info* (dict or None): Information to be stored at the
+                 database (or ``None``). This information will be provided to
+                 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
+                 **MUST** be JSON/YAML-serializable (plain data structures).
+
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+        if service_type == "ELINE":
+            if len(connection_points) > 2:
+                raise SdnConnectorError('Connections between more than 2 endpoints are not supported')
+            if len(connection_points) < 2:
+                raise SdnConnectorError('Connections must be of at least 2 endpoints')
+            """ First step, create the vpn service """    
+            uuid_l2vpn = str(uuid.uuid4())
+            vpn_service = {}
+            vpn_service["vpn-id"] = uuid_l2vpn
+            vpn_service["vpn-scv-type"] = "vpws"
+            vpn_service["svc-topo"] = "any-to-any"
+            vpn_service["customer-name"] = "osm"
+            vpn_service_list = []
+            vpn_service_list.append(vpn_service)
+            vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
+            response_service_creation = None
+            conn_info = []
+            self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
+            try:
+                endpoint_service_creation = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+                    self.wim["wim_url"])
+                response_service_creation = requests.post(endpoint_service_creation, headers=self.headers,
+                                                          json=vpn_service_l, auth=self.auth)
+            except requests.exceptions.ConnectionError:
+                raise SdnConnectorError("Request to create service Timeout", http_code=408)
+            if response_service_creation.status_code == 409:
+                raise SdnConnectorError("Service already exists", http_code=response_service_creation.status_code)
+            elif response_service_creation.status_code != requests.codes.created:
+                raise SdnConnectorError("Request to create service not accepted",
+                                        http_code=response_service_creation.status_code)
+            """ Second step, create the connections and vpn attachments """   
+            for connection_point in connection_points:
+                connection_point_wan_info = self.search_mapp(connection_point)
+                site_network_access = {}
+                connection = {}
+                if connection_point["service_endpoint_encapsulation_type"] != "none":
+                    if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
+                        """ The connection is a VLAN """
+                        connection["encapsulation-type"] = "dot1q-vlan-tagged"
+                        tagged = {}
+                        tagged_interf = {}
+                        service_endpoint_encapsulation_info = connection_point["service_endpoint_encapsulation_info"]
+                        if service_endpoint_encapsulation_info["vlan"] is None:
+                            raise SdnConnectorError("VLAN must be provided")
+                        tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info["vlan"]
+                        tagged["dot1q-vlan-tagged"] = tagged_interf
+                        connection["tagged-interface"] = tagged
+                    else:
+                        raise NotImplementedError("Encapsulation type not implemented")
+                site_network_access["connection"] = connection
+                self.logger.info("Sending connection:{}".format(connection))
+                vpn_attach = {}
+                vpn_attach["vpn-id"] = uuid_l2vpn
+                vpn_attach["site-role"] = vpn_service["svc-topo"]+"-role"
+                site_network_access["vpn-attachment"] = vpn_attach
+                self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
+                uuid_sna = str(uuid.uuid4())
+                site_network_access["network-access-id"] = uuid_sna
+                site_network_access["bearer"] = connection_point_wan_info["service_mapping_info"]["bearer"]
+                site_network_accesses = {}
+                site_network_access_list = []
+                site_network_access_list.append(site_network_access)
+                site_network_accesses["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
+                conn_info_d = {}
+                conn_info_d["site"] = connection_point_wan_info["service_mapping_info"]["site-id"]
+                conn_info_d["site-network-access-id"] = site_network_access["network-access-id"]
+                conn_info_d["mapping"] = None
+                conn_info.append(conn_info_d)
+                try:
+                    endpoint_site_network_access_creation = \
+                        "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
+                            self.wim["wim_url"], connection_point_wan_info["service_mapping_info"]["site-id"])
+                    response_endpoint_site_network_access_creation = requests.post(
+                        endpoint_site_network_access_creation,
+                        headers=self.headers,
+                        json=site_network_accesses,
+                        auth=self.auth)
+                    
+                    if response_endpoint_site_network_access_creation.status_code == 409:
+                        self.delete_connectivity_service(vpn_service["vpn-id"])
+                        raise SdnConnectorError("Site_Network_Access with ID '{}' already exists".format(
+                            site_network_access["network-access-id"]),
+                            http_code=response_endpoint_site_network_access_creation.status_code)
+                    
+                    elif response_endpoint_site_network_access_creation.status_code == 400:
+                        self.delete_connectivity_service(vpn_service["vpn-id"])
+                        raise SdnConnectorError("Site {} does not exist".format(
+                            connection_point_wan_info["service_mapping_info"]["site-id"]),
+                            http_code=response_endpoint_site_network_access_creation.status_code)
+                    
+                    elif response_endpoint_site_network_access_creation.status_code != requests.codes.created and \
+                            response_endpoint_site_network_access_creation.status_code != requests.codes.no_content:
+                        self.delete_connectivity_service(vpn_service["vpn-id"])
+                        raise SdnConnectorError("Request no accepted",
+                                                http_code=response_endpoint_site_network_access_creation.status_code)
+                
+                except requests.exceptions.ConnectionError:
+                    self.delete_connectivity_service(vpn_service["vpn-id"])
+                    raise SdnConnectorError("Request Timeout", http_code=408)
+            return uuid_l2vpn, conn_info
+        
+        else:
+            raise NotImplementedError
+
+    def delete_connectivity_service(self, service_uuid, conn_info=None):
+        """Disconnect multi-site endpoints previously connected
+
+        This method should receive as the first argument the UUID generated by
+        the ``create_connectivity_service``
+        """
+        try:
+            self.logger.info("Sending delete")
+            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+                self.wim["wim_url"], service_uuid)
+            response = requests.delete(servicepoint, auth=self.auth)
+            if response.status_code != requests.codes.no_content:
+                raise SdnConnectorError("Error in the request", http_code=response.status_code)
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
+
+    def edit_connectivity_service(self, service_uuid, conn_info=None,
+                                  connection_points=None, **kwargs):
+        """Change an existing connectivity service, see
+        ``create_connectivity_service``"""
+
+        # sites = {"sites": {}}
+        # site_list = []
+        vpn_service = {}
+        vpn_service["svc-topo"] = "any-to-any"
+        counter = 0
+        for connection_point in connection_points:
+            site_network_access = {}
+            connection_point_wan_info = self.search_mapp(connection_point)
+            params_site = {}
+            params_site["site-id"] = connection_point_wan_info["service_mapping_info"]["site-id"]
+            params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
+            device_site = {}
+            device_site["device-id"] = connection_point_wan_info["device-id"]
+            params_site["devices"] = device_site
+            # network_access = {}
+            connection = {}
+            if connection_point["service_endpoint_encapsulation_type"] != "none":
+                if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
+                    """ The connection is a VLAN """
+                    connection["encapsulation-type"] = "dot1q-vlan-tagged"
+                    tagged = {}
+                    tagged_interf = {}
+                    service_endpoint_encapsulation_info = connection_point["service_endpoint_encapsulation_info"]
+                    if service_endpoint_encapsulation_info["vlan"] is None:
+                        raise SdnConnectorError("VLAN must be provided")
+                    tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info["vlan"]
+                    tagged["dot1q-vlan-tagged"] = tagged_interf
+                    connection["tagged-interface"] = tagged
+                else:
+                    raise NotImplementedError("Encapsulation type not implemented")
+            site_network_access["connection"] = connection
+            vpn_attach = {}
+            vpn_attach["vpn-id"] = service_uuid
+            vpn_attach["site-role"] = vpn_service["svc-topo"]+"-role"
+            site_network_access["vpn-attachment"] = vpn_attach
+            uuid_sna = conn_info[counter]["site-network-access-id"]
+            site_network_access["network-access-id"] = uuid_sna
+            site_network_access["bearer"] = connection_point_wan_info["service_mapping_info"]["bearer"]
+            site_network_accesses = {}
+            site_network_access_list = []
+            site_network_access_list.append(site_network_access)
+            site_network_accesses["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
+            try:
+                endpoint_site_network_access_edit = \
+                    "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
+                        self.wim["wim_url"], connection_point_wan_info["service_mapping_info"]["site-id"])
+                response_endpoint_site_network_access_creation = requests.put(endpoint_site_network_access_edit,
+                                                                              headers=self.headers,
+                                                                              json=site_network_accesses,
+                                                                              auth=self.auth)
+                if response_endpoint_site_network_access_creation.status_code == 400:
+                    raise SdnConnectorError("Service does not exist",
+                                            http_code=response_endpoint_site_network_access_creation.status_code)
+                elif response_endpoint_site_network_access_creation.status_code != 201 and \
+                        response_endpoint_site_network_access_creation.status_code != 204:
+                    raise SdnConnectorError("Request no accepted",
+                                            http_code=response_endpoint_site_network_access_creation.status_code)
+            except requests.exceptions.ConnectionError:
+                raise SdnConnectorError("Request Timeout", http_code=408)
+            counter += 1
+        return None
+
+    def clear_all_connectivity_services(self):
+        """Delete all WAN Links corresponding to a WIM"""
+        try:
+            self.logger.info("Sending clear all connectivity services")
+            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
+            response = requests.delete(servicepoint, auth=self.auth)
+            if response.status_code != requests.codes.no_content:
+                raise SdnConnectorError("Unable to clear all connectivity services", http_code=response.status_code)
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
+
+    def get_all_active_connectivity_services(self):
+        """Provide information about all active connections provisioned by a
+        WIM
+        """
+        try:
+            self.logger.info("Sending get all connectivity services")
+            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
+            response = requests.get(servicepoint, auth=self.auth)
+            if response.status_code != requests.codes.ok:
+                raise SdnConnectorError("Unable to get all connectivity services", http_code=response.status_code)
+            return response
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
diff --git a/RO-SDN-tapi/requirements.txt b/RO-SDN-tapi/requirements.txt
new file mode 100644 (file)
index 0000000..44c797f
--- /dev/null
@@ -0,0 +1,18 @@
+##
+# 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.
+##
+
+requests
+git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro
+
diff --git a/RO-SDN-tapi/setup.py b/RO-SDN-tapi/setup.py
new file mode 100644 (file)
index 0000000..931dd66
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+##
+# 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.
+##
+
+from setuptools import setup
+
+_name = "osm_rosdn_tapi"
+
+README = """
+===========
+osm-rosdn_tapi
+===========
+
+osm-ro pluging for tapi (ietfl2vpn) SDN
+"""
+
+setup(
+    name=_name,
+    description='OSM ro sdn plugin for tapi (ietfl2vpn)',
+    long_description=README,
+    version_command=('git describe --match v* --tags --long --dirty', 'pep440-git-full'),
+    # version=VERSION,
+    # python_requires='>3.5.0',
+    author='ETSI OSM',
+    # TODO py3 author_email='',
+    maintainer='OSM_TECH@LIST.ETSI.ORG',  # TODO py3
+    # TODO py3 maintainer_email='',
+    url='https://osm.etsi.org/gitweb/?p=osm/RO.git;a=summary',
+    license='Apache 2.0',
+
+    packages=[_name],
+    include_package_data=True,
+    dependency_links=["git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro"],
+    install_requires=["requests", "osm-ro"],
+    setup_requires=['setuptools-version-command'],
+    entry_points={
+        'osm_rosdn.plugins': ['rosdn_tapi = osm_rosdn_tapi.wimconn_ietfl2vpn:WimconnectorIETFL2VPN'],
+    },
+)
diff --git a/RO-SDN-tapi/stdeb.cfg b/RO-SDN-tapi/stdeb.cfg
new file mode 100644 (file)
index 0000000..0c718e4
--- /dev/null
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+[DEFAULT]
+X-Python3-Version : >= 3.5
+Depends3: python3-requests, python3-osm-ro
+
diff --git a/RO-SDN-tapi/tox.ini b/RO-SDN-tapi/tox.ini
new file mode 100644 (file)
index 0000000..7d643cd
--- /dev/null
@@ -0,0 +1,41 @@
+##
+# 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.
+##
+
+[tox]
+envlist = py3
+toxworkdir={homedir}/.tox
+
+[testenv]
+basepython = python3
+install_command = python3 -m pip install -r requirements.txt -U {opts} {packages}
+# deps = -r{toxinidir}/test-requirements.txt
+commands=python3 -m unittest discover -v
+
+[testenv:flake8]
+basepython = python3
+deps = flake8
+commands = flake8 osm_rosdn_tapi --max-line-length 120 \
+    --exclude .svn,CVS,.gz,.git,__pycache__,.tox,local,temp --ignore W291,W293,E226,W504
+
+[testenv:unittest]
+basepython = python3
+commands = python3 -m unittest osm_rosdn_tapi.tests
+
+[testenv:build]
+basepython = python3
+deps = stdeb
+       setuptools-version-command
+commands = python3 setup.py --command-packages=stdeb.command bdist_deb
+
index 0e9c887..9b60216 100644 (file)
@@ -1,6 +1,20 @@
-===========
+ Copyright 2018 Telefonica S.A.
+ 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.
+
+============
 osm-roclient
 osm-roclient
-===========
+============
 
 osm-roclient is a client for interact with osm-ro server
 
 
 osm-roclient is a client for interact with osm-ro server
 
index 3a2be88..44a4fc4 100644 (file)
@@ -1,3 +1,17 @@
+ Copyright 2018 Telefonica S.A.
+ 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.
+
 ===========
 osm-ro
 ===========
 ===========
 osm-ro
 ===========
index e69de29..7284a2b 100644 (file)
@@ -0,0 +1,13 @@
+##
+# 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.
+##
index 36b8003..8ef780c 100755 (executable)
@@ -1,5 +1,22 @@
 #!/usr/bin/env bash
 
 #!/usr/bin/env bash
 
+##
+# Copyright Telefonica Investigacion y Desarrollo, S.A.U.
+# 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.
+##
+
 DB_NAME='mano_db'
 DB_ADMIN_USER="root"
 DB_USER="mano"
 DB_NAME='mano_db'
 DB_ADMIN_USER="root"
 DB_USER="mano"
index 096a21a..5d03b96 100755 (executable)
@@ -36,7 +36,7 @@ QUIET_MODE=""
 BACKUP_DIR=""
 BACKUP_FILE=""
 #TODO update it with the last database version
 BACKUP_DIR=""
 BACKUP_FILE=""
 #TODO update it with the last database version
-LAST_DB_VERSION=39
+LAST_DB_VERSION=40
 
 # Detect paths
 MYSQL=$(which mysql)
 
 # Detect paths
 MYSQL=$(which mysql)
@@ -197,6 +197,7 @@ fi
 #[ $OPENMANO_VER_NUM -ge 6009 ] && DB_VERSION=37  #0.6.09 =>  37
 #[ $OPENMANO_VER_NUM -ge 6011 ] && DB_VERSION=38  #0.6.11 =>  38
 #[ $OPENMANO_VER_NUM -ge 6020 ] && DB_VERSION=39  #0.6.20 =>  39
 #[ $OPENMANO_VER_NUM -ge 6009 ] && DB_VERSION=37  #0.6.09 =>  37
 #[ $OPENMANO_VER_NUM -ge 6011 ] && DB_VERSION=38  #0.6.11 =>  38
 #[ $OPENMANO_VER_NUM -ge 6020 ] && DB_VERSION=39  #0.6.20 =>  39
+#[ $OPENMANO_VER_NUM -ge 6000004 ] && DB_VERSION=40  #6.0.4 =>  40
 #TODO ... put next versions here
 
 function upgrade_to_1(){
 #TODO ... put next versions here
 
 function upgrade_to_1(){
@@ -1461,6 +1462,56 @@ function downgrade_from_39(){
 
     sql "DELETE FROM schema_version WHERE version_int='39';"
 }
 
     sql "DELETE FROM schema_version WHERE version_int='39';"
 }
+function upgrade_to_40(){
+    echo "      Adding instance_wim_net_id, created_at, modified_at at 'instance_interfaces'"
+    sql "ALTER TABLE instance_interfaces ADD COLUMN instance_wim_net_id VARCHAR(36) NULL AFTER instance_net_id, "\
+        "ADD COLUMN model VARCHAR(12) NULL DEFAULT NULL AFTER type, "\"
+        "ADD COLUMN created_at DOUBLE NULL DEFAULT NULL AFTER vlan, " \
+        "ADD COLUMN modified_at DOUBLE NULL DEFAULT NULL AFTER created_at;"
+    echo "      Adding sdn to 'instance_wim_nets'"
+    sql "ALTER TABLE instance_wim_nets ADD COLUMN sdn ENUM('true','false') NOT NULL DEFAULT 'false' AFTER created;"
+    echo "      Change from created to sdn at 'wim_accounts'"
+    sql "ALTER TABLE wim_accounts CHANGE COLUMN created sdn ENUM('true','false') NOT NULL DEFAULT 'false' AFTER wim_id;"
+    echo "      Remove unique_datacenter_port_mapping at 'wim_port_mappings'"
+    sql "ALTER TABLE wim_port_mappings DROP INDEX unique_datacenter_port_mapping;"
+    echo "      change 'wim_port_mappings' pop_x to device_x, adding switch_dpid, switch_port"
+    sql "ALTER TABLE wim_port_mappings ALTER pop_switch_dpid DROP DEFAULT, ALTER pop_switch_port DROP DEFAULT;"
+    sql "ALTER TABLE wim_port_mappings CHANGE COLUMN pop_switch_dpid device_id VARCHAR(64) NULL AFTER datacenter_id," \
+        " CHANGE COLUMN pop_switch_port device_interface_id VARCHAR(64) NULL AFTER device_id, " \
+        " CHANGE COLUMN wan_service_endpoint_id service_endpoint_id VARCHAR(256) NOT NULL AFTER device_interface_id, " \
+        " CHANGE COLUMN wan_service_mapping_info service_mapping_info TEXT NULL AFTER service_endpoint_id, " \
+        " ADD COLUMN switch_dpid VARCHAR(64) NULL AFTER wan_service_endpoint_id," \
+        " ADD COLUMN switch_port VARCHAR(64) NULL AFTER switch_dpid;"
+    echo "      remove unique name to 'datacenters'"
+    sql "ALTER TABLE datacenters DROP INDEX name;"
+
+    sql "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) " \
+        "VALUES (40, '0.40', '6.0.4', 'Chagnes to SDN ', '2019-10-23');"
+}
+function downgrade_from_40(){
+    echo "      Removing instance_wim_net_id, created_at, modified_at from 'instance_interfaces'"
+    sql "ALTER TABLE instance_interfaces DROP COLUMN instance_wim_net_id, DROP COLUMN created_at, " \
+        "DROP COLUMN modified_at, DROP COLUMN model;"
+    echo "      Removing sdn from 'instance_wim_nets'"
+    sql "ALTER TABLE instance_wim_nets DROP COLUMN sdn;"
+    echo "      Change back from sdn to created at 'wim_accounts'"
+    sql "ALTER TABLE wim_accounts CHANGE COLUMN sdn created ENUM('true','false') NOT NULL DEFAULT 'false' AFTER wim_id;"
+    echo "      Restore back unique_datacenter_port_mapping at 'wim_port_mappings'"
+    echo "      change 'wim_port_mappings' device_x to pop_x, remove switch_dpid, switch_port"
+    sql "ALTER TABLE wim_port_mappings ALTER device_id DROP DEFAULT, ALTER device_interface_id DROP DEFAULT;"
+    sql "ALTER TABLE wim_port_mappings CHANGE COLUMN device_id pop_switch_dpid VARCHAR(64) NOT NULL AFTER " \
+        "datacenter_id,        CHANGE COLUMN device_interface_id pop_switch_port VARCHAR(64) NOT NULL AFTER pop_switch_dpid," \
+        " CHANGE COLUMN service_endpoint_id wan_service_endpoint_id VARCHAR(256) NOT NULL AFTER pop_switch_port, " \
+        " CHANGE COLUMN service_mapping_info wan_service_mapping_info TEXT NULL AFTER wan_service_endpoint_id, " \
+             " DROP COLUMN switch_dpid, DROP COLUMN switch_port;"
+    sql "ALTER TABLE wim_port_mappings ADD UNIQUE INDEX unique_datacenter_port_mapping(datacenter_id, pop_switch_dpid,
+         pop_switch_port);"
+    echo "      add unique name to 'datacenters'"
+    sql "ALTER TABLE datacenters ADD UNIQUE INDEX name (name);"
+    sql "DELETE FROM schema_version WHERE version_int='40';"
+}
+
+
 #TODO ... put functions here
 
 
 #TODO ... put functions here
 
 
index 4400e39..7ab4bf7 100644 (file)
@@ -1,3 +1,16 @@
+/**
+* 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.
+**/
 --
 -- Tear down database structure required for integrating OSM with
 -- Wide Are Network Infrastructure Managers
 --
 -- Tear down database structure required for integrating OSM with
 -- Wide Are Network Infrastructure Managers
index 01f38f4..668eeb3 100644 (file)
@@ -1,3 +1,16 @@
+/**
+* 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.
+**/
 --
 -- Removing ingress and egress ports for SFC purposes.
 -- Inserting only one port for ingress and egress.
 --
 -- Removing ingress and egress ports for SFC purposes.
 -- Inserting only one port for ingress and egress.
index 343f370..eb99b8b 100644 (file)
@@ -1,3 +1,16 @@
+/**
+* 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.
+**/
 --
 -- Setup database structure required for integrating OSM with
 -- Wide Are Network Infrastructure Managers
 --
 -- Setup database structure required for integrating OSM with
 -- Wide Are Network Infrastructure Managers
index b528c6d..fea4cef 100644 (file)
@@ -1,3 +1,16 @@
+/**
+* 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.
+**/
 --
 -- Adding different ingress and egress ports for SFC.
 --
 --
 -- Adding different ingress and egress ports for SFC.
 --
index e69de29..7284a2b 100644 (file)
@@ -0,0 +1,13 @@
+##
+# 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.
+##
index 552e85b..2a3f027 100644 (file)
@@ -1,4 +1,18 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+##
+# 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.
+##
+
 import logging
 from functools import wraps
 
 import logging
 from functools import wraps
 
index 13e19ed..7285142 100644 (file)
@@ -1,4 +1,17 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+##
+# 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.
+##
 
 #
 # Util functions previously in `httpserver`
 
 #
 # Util functions previously in `httpserver`
index e69de29..7284a2b 100644 (file)
@@ -0,0 +1,13 @@
+##
+# 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.
+##
index a968e76..e2b1d43 100644 (file)
@@ -1,4 +1,18 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+##
+# 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.
+##
+
 import unittest
 
 import bottle
 import unittest
 
 import bottle
index af32545..e015758 100644 (file)
@@ -1,4 +1,18 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+##
+# 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.
+##
+
 import unittest
 
 from mock import MagicMock, patch
 import unittest
 
 from mock import MagicMock, patch
index 43055c2..93e2f15 100644 (file)
@@ -1,3 +1,17 @@
+##
+# 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.
+##
+
 # This tox file allows the devs to run unit tests only for this subpackage.
 # In order to do so, cd into the directory and run `tox`
 
 # This tox file allows the devs to run unit tests only for this subpackage.
 # In order to do so, cd into the directory and run `tox`
 
index a28f57f..6a06a4c 100644 (file)
@@ -45,13 +45,11 @@ from osm_ro import nfvo_db
 from threading import Lock
 import time as t
 # TODO py3 BEGIN
 from threading import Lock
 import time as t
 # TODO py3 BEGIN
-# from lib_osm_openvim import ovim as ovim_module
+from osm_ro.sdn import Sdn, SdnException as ovimException
 # from lib_osm_openvim.ovim import ovimException
 # from lib_osm_openvim.ovim import ovimException
-from unittest.mock  import MagicMock
-ovim_module = MagicMock()
-class ovimException(Exception):
-   pass
-ovim_module.ovimException = ovimException
+# from unittest.mock  import MagicMock
+# class ovimException(Exception):
+#    pass
 # TODO py3 END
 
 from Crypto.PublicKey import RSA
 # TODO py3 END
 
 from Crypto.PublicKey import RSA
@@ -64,11 +62,12 @@ from pkg_resources import iter_entry_points
 
 
 # WIM
 
 
 # WIM
-import osm_ro.wim.wimconn as wimconn
-import osm_ro.wim.wim_thread as wim_thread
-from osm_ro.http_tools import errors as httperrors
-from osm_ro.wim.engine import WimEngine
-from osm_ro.wim.persistence import WimPersistence
+from .wim import sdnconn
+from .wim.wimconn_fake import FakeConnector
+from .wim.failing_connector import FailingConnector
+from .http_tools import errors as httperrors
+from .wim.engine import WimEngine
+from .wim.persistence import WimPersistence
 from copy import deepcopy
 from pprint import pformat
 #
 from copy import deepcopy
 from pprint import pformat
 #
@@ -77,7 +76,7 @@ global global_config
 # WIM
 global wim_engine
 wim_engine  = None
 # WIM
 global wim_engine
 wim_engine  = None
-global wimconn_imported
+global sdnconn_imported
 #
 global logger
 global default_volume_size
 #
 global logger
 global default_volume_size
@@ -90,7 +89,7 @@ plugins = {}   # dictionary with VIM type as key, loaded module as value
 vim_threads = {"running":{}, "deleting": {}, "names": []}      # threads running for attached-VIMs
 vim_persistent_info = {}
 # WIM
 vim_threads = {"running":{}, "deleting": {}, "names": []}      # threads running for attached-VIMs
 vim_persistent_info = {}
 # WIM
-wimconn_imported = {}   # dictionary with WIM type as key, loaded module as value
+sdnconn_imported = {}   # dictionary with WIM type as key, loaded module as value
 wim_threads = {"running":{}, "deleting": {}, "names": []}      # threads running for attached-WIMs
 wim_persistent_info = {}
 #
 wim_threads = {"running":{}, "deleting": {}, "names": []}      # threads running for attached-WIMs
 wim_persistent_info = {}
 #
@@ -105,13 +104,23 @@ db_lock = Lock()
 class NfvoException(httperrors.HttpMappedError):
     """Common Class for NFVO errors"""
 
 class NfvoException(httperrors.HttpMappedError):
     """Common Class for NFVO errors"""
 
-def _load_vim_plugin(name):
+def _load_plugin(name, type="vim"):
+    # type can be vim or sdn
     global plugins
     global plugins
-    for v in iter_entry_points('osm_rovim.plugins', name):
-        plugins[name] = v.load()
+    try:
+        for v in iter_entry_points('osm_ro{}.plugins'.format(type), name):
+            plugins[name] = v.load()
+    except Exception as e:
+        logger.critical("Cannot load osm_{}: {}".format(name, e))
+        if name:
+            plugins[name] = FailingConnector("Cannot load osm_{}: {}".format(name, e))
     if name and name not in plugins:
     if name and name not in plugins:
-        raise NfvoException("Unknown vim type '{}'. This plugin has not been registered".format(name),
-                            httperrors.Bad_Request)
+        error_text = "Cannot load a module for {t} type '{n}'. The plugin 'osm_{n}' has not been" \
+                     " registered".format(t=type, n=name)
+        logger.critical(error_text)
+        plugins[name] = FailingConnector(error_text)
+        # raise NfvoException("Cannot load a module for {t} type '{n}'. The plugin 'osm_{n}' has not been registered".
+        #                     format(t=type, n=name), httperrors.Bad_Request)
 
 def get_task_id():
     global last_task_id
 
 def get_task_id():
     global last_task_id
@@ -166,37 +175,21 @@ def get_non_used_wim_name(wim_name, wim_id, tenant_name, tenant_id):
 
 
 def start_service(mydb, persistence=None, wim=None):
 
 
 def start_service(mydb, persistence=None, wim=None):
-    global db, global_config, plugins
+    global db, global_config, plugins, ovim
     db = nfvo_db.nfvo_db(lock=db_lock)
     mydb.lock = db_lock
     db.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
     db = nfvo_db.nfvo_db(lock=db_lock)
     mydb.lock = db_lock
     db.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name'])
-    global ovim
 
     persistence = persistence or  WimPersistence(db)
 
 
     persistence = persistence or  WimPersistence(db)
 
-    # 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': 'openmano.ovim',
-        'network_vlan_range_start': 1000,
-        'network_vlan_range_end': 4096,
-        'db_name': global_config["db_ovim_name"],
-        'db_host': global_config["db_ovim_host"],
-        'db_user': global_config["db_ovim_user"],
-        'db_passwd': global_config["db_ovim_passwd"],
-        'bridge_ifaces': {},
-        'mode': 'normal',
-        'network_type': 'bridge',
-        #TODO: log_level_of should not be needed. To be modified in ovim
-        'log_level_of': 'DEBUG'
-    }
     try:
     try:
+        if "rosdn_fake" not in plugins:
+            plugins["rosdn_fake"] = FakeConnector
         # starts ovim library
         # starts ovim library
-        ovim = ovim_module.ovim(ovim_configuration)
+        ovim = Sdn(db, plugins)
 
         global wim_engine
 
         global wim_engine
-        wim_engine = wim or WimEngine(persistence)
+        wim_engine = wim or WimEngine(persistence, plugins)
         wim_engine.ovim = ovim
 
         ovim.start_service()
         wim_engine.ovim = ovim
 
         ovim.start_service()
@@ -221,7 +214,7 @@ def start_service(mydb, persistence=None, wim=None):
                 extra.update(yaml.load(vim["dt_config"], Loader=yaml.Loader))
             plugin_name = "rovim_" + vim["type"]
             if plugin_name not in plugins:
                 extra.update(yaml.load(vim["dt_config"], Loader=yaml.Loader))
             plugin_name = "rovim_" + vim["type"]
             if plugin_name not in plugins:
-                _load_vim_plugin(plugin_name)
+                _load_plugin(plugin_name, type="vim")
 
             thread_id = vim['datacenter_tenant_id']
             vim_persistent_info[thread_id] = {}
 
             thread_id = vim['datacenter_tenant_id']
             vim_persistent_info[thread_id] = {}
@@ -240,19 +233,33 @@ def start_service(mydb, persistence=None, wim=None):
                 logger.error("Cannot launch thread for VIM {} '{}': {}".format(vim['datacenter_name'],
                                                                                vim['datacenter_id'], e))
             except Exception as e:
                 logger.error("Cannot launch thread for VIM {} '{}': {}".format(vim['datacenter_name'],
                                                                                vim['datacenter_id'], e))
             except Exception as e:
-                raise NfvoException("Error at VIM  {}; {}: {}".format(vim["type"], type(e).__name__, e),
-                                    httperrors.Internal_Server_Error)
+                logger.critical("Cannot launch thread for VIM {} '{}': {}".format(vim['datacenter_name'],
+                                                                                  vim['datacenter_id'], e))
+                # raise NfvoException("Error at VIM  {}; {}: {}".format(vim["type"], type(e).__name__, e),
+                #                     httperrors.Internal_Server_Error)
             thread_name = get_non_used_vim_name(vim['datacenter_name'], vim['datacenter_id'], vim['vim_tenant_name'],
                                                 vim['vim_tenant_id'])
             thread_name = get_non_used_vim_name(vim['datacenter_name'], vim['datacenter_id'], vim['vim_tenant_name'],
                                                 vim['vim_tenant_id'])
-            new_thread = vim_thread(task_lock, plugins, thread_name, vim['datacenter_name'],
-                                    vim['datacenter_tenant_id'], db=db, db_lock=db_lock, ovim=ovim)
+            new_thread = vim_thread(task_lock, plugins, thread_name, None,
+                                    vim['datacenter_tenant_id'], db=db)
             new_thread.start()
             vim_threads["running"][thread_id] = new_thread
             new_thread.start()
             vim_threads["running"][thread_id] = new_thread
+        wims = mydb.get_rows(FROM="wim_accounts join wims on wim_accounts.wim_id=wims.uuid",
+                             WHERE={"sdn": "true"},
+                             SELECT=("wim_accounts.uuid as uuid", "type", "wim_accounts.name as name"))
+        for wim in wims:
+            plugin_name = "rosdn_" + wim["type"]
+            if plugin_name not in plugins:
+                _load_plugin(plugin_name, type="sdn")
 
 
+            thread_id = wim['uuid']
+            thread_name = get_non_used_vim_name(wim['name'], wim['uuid'], wim['uuid'], None)
+            new_thread = vim_thread(task_lock, plugins, thread_name, wim['uuid'], None, db=db)
+            new_thread.start()
+            vim_threads["running"][thread_id] = new_thread
         wim_engine.start_threads()
     except db_base_Exception as e:
         raise NfvoException(str(e) + " at nfvo.get_vim", e.http_code)
         wim_engine.start_threads()
     except db_base_Exception as e:
         raise NfvoException(str(e) + " at nfvo.get_vim", e.http_code)
-    except ovim_module.ovimException as e:
+    except ovimException as e:
         message = str(e)
         if message[:22] == "DATABASE wrong version":
             message = "DATABASE wrong version of lib_osm_openvim {msg} -d{dbname} -u{dbuser} -p{dbpass} {ver}' "\
         message = str(e)
         if message[:22] == "DATABASE wrong version":
             message = "DATABASE wrong version of lib_osm_openvim {msg} -d{dbname} -u{dbuser} -p{dbpass} {ver}' "\
@@ -397,7 +404,7 @@ def get_vim(mydb, nfvo_tenant=None, datacenter_id=None, datacenter_name=None, da
             plugin_name = "rovim_" + vim["type"]
             if plugin_name not in plugins:
                 try:
             plugin_name = "rovim_" + vim["type"]
             if plugin_name not in plugins:
                 try:
-                    _load_vim_plugin(plugin_name)
+                    _load_plugin(plugin_name, type="vim")
                 except NfvoException as e:
                     if ignore_errors:
                         logger.error("{}".format(e))
                 except NfvoException as e:
                     if ignore_errors:
                         logger.error("{}".format(e))
@@ -3030,6 +3037,14 @@ def update(d, u):
     return d
 
 
     return d
 
 
+def _get_wim(db, wim_account_id):
+    # get wim from wim_account
+    wim_accounts = db.get_rows(FROM='wim_accounts', WHERE={"uuid": wim_account_id})
+    if not wim_accounts:
+        raise NfvoException("Not found sdn id={}".format(wim_account_id), http_code=httperrors.Not_Found)
+    return wim_accounts[0]["wim_id"]
+
+
 def create_instance(mydb, tenant_id, instance_dict):
     # print "Checking that nfvo_tenant_id exists and getting the VIM URI and the VIM tenant_id"
     # logger.debug("Creating instance...")
 def create_instance(mydb, tenant_id, instance_dict):
     # print "Checking that nfvo_tenant_id exists and getting the VIM URI and the VIM tenant_id"
     # logger.debug("Creating instance...")
@@ -3094,6 +3109,7 @@ def create_instance(mydb, tenant_id, instance_dict):
     }
 
     # Auxiliary dictionaries from x to y
     }
 
     # Auxiliary dictionaries from x to y
+    sce_net2wim_instance = {}
     sce_net2instance = {}
     net2task_id = {'scenario': {}}
     # Mapping between local networks and WIMs
     sce_net2instance = {}
     net2task_id = {'scenario': {}}
     # Mapping between local networks and WIMs
@@ -3229,6 +3245,7 @@ def create_instance(mydb, tenant_id, instance_dict):
         # 1. Creating new nets (sce_nets) in the VIM"
         number_mgmt_networks = 0
         db_instance_nets = []
         # 1. Creating new nets (sce_nets) in the VIM"
         number_mgmt_networks = 0
         db_instance_nets = []
+        db_instance_wim_nets = []
         for sce_net in scenarioDict['nets']:
             sce_net_uuid = sce_net.get('uuid', sce_net["name"])
             # get involved datacenters where this network need to be created
         for sce_net in scenarioDict['nets']:
             sce_net_uuid = sce_net.get('uuid', sce_net["name"])
             # get involved datacenters where this network need to be created
@@ -3286,6 +3303,7 @@ def create_instance(mydb, tenant_id, instance_dict):
                     if site.get("datacenter") and site["datacenter"] not in involved_datacenters:
                         involved_datacenters.append(site["datacenter"])
             sce_net2instance[sce_net_uuid] = {}
                     if site.get("datacenter") and site["datacenter"] not in involved_datacenters:
                         involved_datacenters.append(site["datacenter"])
             sce_net2instance[sce_net_uuid] = {}
+            sce_net2wim_instance[sce_net_uuid] = {}
             net2task_id['scenario'][sce_net_uuid] = {}
 
             use_network = None
             net2task_id['scenario'][sce_net_uuid] = {}
 
             use_network = None
@@ -3393,6 +3411,41 @@ def create_instance(mydb, tenant_id, instance_dict):
                 sce_net2instance[sce_net_uuid][datacenter_id] = net_uuid
                 if not related_network:   # all db_instance_nets will have same related
                     related_network = use_network or net_uuid
                 sce_net2instance[sce_net_uuid][datacenter_id] = net_uuid
                 if not related_network:   # all db_instance_nets will have same related
                     related_network = use_network or net_uuid
+                sdn_net_id = None
+                sdn_controller = vim.config.get('sdn-controller')
+                sce_net2wim_instance[sce_net_uuid][datacenter_id] = None
+                if sdn_controller and net_type in ("data", "ptp"):
+                    wim_id = _get_wim(mydb, sdn_controller)
+                    sdn_net_id = str(uuid4())
+                    sce_net2wim_instance[sce_net_uuid][datacenter_id] = sdn_net_id
+                    task_extra["sdn_net_id"] = sdn_net_id
+                    db_instance_wim_nets.append({
+                        "uuid": sdn_net_id,
+                        "instance_scenario_id": instance_uuid,
+                        "sce_net_id": sce_net.get("uuid"),
+                        "wim_id": wim_id,
+                        "wim_account_id": sdn_controller,
+                        'status': 'BUILD',  # if create_network else "ACTIVE"
+                        "related": related_network,
+                        'multipoint': True if net_type=="data" else False,
+                        "created": create_network, # TODO py3
+                        "sdn": True,
+                    })
+                    task_wim_extra = {"params": [net_type, wim_account_name]}
+                    db_vim_action = {
+                        "instance_action_id": instance_action_id,
+                        "status": "SCHEDULED",
+                        "task_index": task_index,
+                        # "datacenter_vim_id": myvim_thread_id,
+                        "wim_account_id": sdn_controller,
+                        "action": task_action,
+                        "item": "instance_wim_nets",
+                        "item_id": sdn_net_id,
+                        "related": related_network,
+                        "extra": yaml.safe_dump(task_wim_extra, default_flow_style=True, width=256)
+                    }
+                    task_index += 1
+                    db_vim_actions.append(db_vim_action)
                 db_net = {
                     "uuid": net_uuid,
                     "osm_id": sce_net.get("osm_id") or sce_net["name"],
                 db_net = {
                     "uuid": net_uuid,
                     "osm_id": sce_net.get("osm_id") or sce_net["name"],
@@ -3404,7 +3457,8 @@ def create_instance(mydb, tenant_id, instance_dict):
                     "created": create_network,
                     'datacenter_id': datacenter_id,
                     'datacenter_tenant_id': myvim_thread_id,
                     "created": create_network,
                     'datacenter_id': datacenter_id,
                     'datacenter_tenant_id': myvim_thread_id,
-                    'status': 'BUILD' #  if create_network else "ACTIVE"
+                    'status': 'BUILD', #  if create_network else "ACTIVE"
+                    'sdn_net_id': sdn_net_id,
                 }
                 db_instance_nets.append(db_net)
                 db_vim_action = {
                 }
                 db_instance_nets.append(db_net)
                 db_vim_action = {
@@ -3451,6 +3505,7 @@ def create_instance(mydb, tenant_id, instance_dict):
             "task_index": task_index,
             "uuid_list": uuid_list,
             "db_instance_nets": db_instance_nets,
             "task_index": task_index,
             "uuid_list": uuid_list,
             "db_instance_nets": db_instance_nets,
+            "db_instance_wim_nets": db_instance_wim_nets,
             "db_vim_actions": db_vim_actions,
             "db_ip_profiles": db_ip_profiles,
             "db_instance_vnfs": db_instance_vnfs,
             "db_vim_actions": db_vim_actions,
             "db_ip_profiles": db_ip_profiles,
             "db_instance_vnfs": db_instance_vnfs,
@@ -3458,6 +3513,7 @@ def create_instance(mydb, tenant_id, instance_dict):
             "db_instance_interfaces": db_instance_interfaces,
             "net2task_id": net2task_id,
             "sce_net2instance": sce_net2instance,
             "db_instance_interfaces": db_instance_interfaces,
             "net2task_id": net2task_id,
             "sce_net2instance": sce_net2instance,
+            "sce_net2wim_instance": sce_net2wim_instance,
         }
         # sce_vnf_list = sorted(scenarioDict['vnfs'], key=lambda k: k['name'])
         for sce_vnf in scenarioDict.get('vnfs', ()):  # sce_vnf_list:
         }
         # sce_vnf_list = sorted(scenarioDict['vnfs'], key=lambda k: k['name'])
         for sce_vnf in scenarioDict.get('vnfs', ()):  # sce_vnf_list:
@@ -3655,7 +3711,7 @@ def create_instance(mydb, tenant_id, instance_dict):
             {"instance_sfs": db_instance_sfs},
             {"instance_classifications": db_instance_classifications},
             {"instance_sfps": db_instance_sfps},
             {"instance_sfs": db_instance_sfs},
             {"instance_classifications": db_instance_classifications},
             {"instance_sfps": db_instance_sfps},
-            {"instance_wim_nets": wan_links},
+            {"instance_wim_nets": db_instance_wim_nets + wan_links},
             {"vim_wim_actions": db_vim_actions + wim_actions}
         ]
 
             {"vim_wim_actions": db_vim_actions + wim_actions}
         ]
 
@@ -3670,13 +3726,13 @@ def create_instance(mydb, tenant_id, instance_dict):
         returned_instance = mydb.get_instance_scenario(instance_uuid)
         returned_instance["action_id"] = instance_action_id
         return returned_instance
         returned_instance = mydb.get_instance_scenario(instance_uuid)
         returned_instance["action_id"] = instance_action_id
         return returned_instance
-    except (NfvoException, vimconn.vimconnException, wimconn.WimConnectorError, db_base_Exception) as e:
+    except (NfvoException, vimconn.vimconnException, sdnconn.SdnConnectorError, db_base_Exception) as e:
         message = rollback(mydb, myvims, rollbackList)
         if isinstance(e, db_base_Exception):
             error_text = "database Exception"
         elif isinstance(e, vimconn.vimconnException):
             error_text = "VIM Exception"
         message = rollback(mydb, myvims, rollbackList)
         if isinstance(e, db_base_Exception):
             error_text = "database Exception"
         elif isinstance(e, vimconn.vimconnException):
             error_text = "VIM Exception"
-        elif isinstance(e, wimconn.WimConnectorError):
+        elif isinstance(e, sdnconn.SdnConnectorError):
             error_text = "WIM Exception"
         else:
             error_text = "Exception"
             error_text = "WIM Exception"
         else:
             error_text = "Exception"
@@ -3699,6 +3755,7 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
     task_index = params_out["task_index"]
     uuid_list = params_out["uuid_list"]
     db_instance_nets = params_out["db_instance_nets"]
     task_index = params_out["task_index"]
     uuid_list = params_out["uuid_list"]
     db_instance_nets = params_out["db_instance_nets"]
+    db_instance_wim_nets = params_out["db_instance_wim_nets"]
     db_vim_actions = params_out["db_vim_actions"]
     db_ip_profiles = params_out["db_ip_profiles"]
     db_instance_vnfs = params_out["db_instance_vnfs"]
     db_vim_actions = params_out["db_vim_actions"]
     db_ip_profiles = params_out["db_ip_profiles"]
     db_instance_vnfs = params_out["db_instance_vnfs"]
@@ -3706,15 +3763,18 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
     db_instance_interfaces = params_out["db_instance_interfaces"]
     net2task_id = params_out["net2task_id"]
     sce_net2instance = params_out["sce_net2instance"]
     db_instance_interfaces = params_out["db_instance_interfaces"]
     net2task_id = params_out["net2task_id"]
     sce_net2instance = params_out["sce_net2instance"]
+    sce_net2wim_instance = params_out["sce_net2wim_instance"]
 
     vnf_net2instance = {}
 
     # 2. Creating new nets (vnf internal nets) in the VIM"
     # For each vnf net, we create it and we add it to instanceNetlist.
     if sce_vnf.get("datacenter"):
 
     vnf_net2instance = {}
 
     # 2. Creating new nets (vnf internal nets) in the VIM"
     # For each vnf net, we create it and we add it to instanceNetlist.
     if sce_vnf.get("datacenter"):
+        vim = myvims[sce_vnf["datacenter"]]
         datacenter_id = sce_vnf["datacenter"]
         myvim_thread_id = myvim_threads_id[sce_vnf["datacenter"]]
     else:
         datacenter_id = sce_vnf["datacenter"]
         myvim_thread_id = myvim_threads_id[sce_vnf["datacenter"]]
     else:
+        vim = myvims[default_datacenter_id]
         datacenter_id = default_datacenter_id
         myvim_thread_id = myvim_threads_id[default_datacenter_id]
     for net in sce_vnf['nets']:
         datacenter_id = default_datacenter_id
         myvim_thread_id = myvim_threads_id[default_datacenter_id]
     for net in sce_vnf['nets']:
@@ -3731,12 +3791,29 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
             vnf_net2instance[sce_vnf['uuid']] = {}
         if sce_vnf['uuid'] not in net2task_id:
             net2task_id[sce_vnf['uuid']] = {}
             vnf_net2instance[sce_vnf['uuid']] = {}
         if sce_vnf['uuid'] not in net2task_id:
             net2task_id[sce_vnf['uuid']] = {}
-        net2task_id[sce_vnf['uuid']][net['uuid']] = task_index
 
         # fill database content
         net_uuid = str(uuid4())
         uuid_list.append(net_uuid)
         vnf_net2instance[sce_vnf['uuid']][net['uuid']] = net_uuid
 
         # fill database content
         net_uuid = str(uuid4())
         uuid_list.append(net_uuid)
         vnf_net2instance[sce_vnf['uuid']][net['uuid']] = net_uuid
+
+        sdn_controller = vim.config.get('sdn-controller')
+        sdn_net_id = None
+        if sdn_controller and net_type in ("data", "ptp"):
+            wim_id = _get_wim(mydb, sdn_controller)
+            sdn_net_id = str(uuid4())
+            db_instance_wim_nets.append({
+                "uuid": sdn_net_id,
+                "instance_scenario_id": instance_uuid,
+                "wim_id": wim_id,
+                "wim_account_id": sdn_controller,
+                'status': 'BUILD',  # if create_network else "ACTIVE"
+                "related": net_uuid,
+                'multipoint': True if net_type == "data" else False,
+                "created": True,  # TODO py3
+                "sdn": True,
+            })
+
         db_net = {
             "uuid": net_uuid,
             "related": net_uuid,
         db_net = {
             "uuid": net_uuid,
             "related": net_uuid,
@@ -3747,6 +3824,7 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
             "created": True,
             'datacenter_id': datacenter_id,
             'datacenter_tenant_id': myvim_thread_id,
             "created": True,
             'datacenter_id': datacenter_id,
             'datacenter_tenant_id': myvim_thread_id,
+            'sdn_net_id': sdn_net_id,
         }
         db_instance_nets.append(db_net)
 
         }
         db_instance_nets.append(db_net)
 
@@ -3761,7 +3839,25 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
         else:
             task_action = "CREATE"
             task_extra = {"params": (net_name, net_type, net.get('ip_profile', None))}
         else:
             task_action = "CREATE"
             task_extra = {"params": (net_name, net_type, net.get('ip_profile', None))}
+        if sdn_net_id:
+            task_extra["sdn_net_id"] = sdn_net_id
 
 
+        if sdn_net_id:
+            task_wim_extra = {"params": [net_type, None]}
+            db_vim_action = {
+                "instance_action_id": instance_action_id,
+                "status": "SCHEDULED",
+                "task_index": task_index,
+                # "datacenter_vim_id": myvim_thread_id,
+                "wim_account_id": sdn_controller,
+                "action": task_action,
+                "item": "instance_wim_nets",
+                "item_id": sdn_net_id,
+                "related": net_uuid,
+                "extra": yaml.safe_dump(task_wim_extra, default_flow_style=True, width=256)
+            }
+            task_index += 1
+            db_vim_actions.append(db_vim_action)
         db_vim_action = {
             "instance_action_id": instance_action_id,
             "task_index": task_index,
         db_vim_action = {
             "instance_action_id": instance_action_id,
             "task_index": task_index,
@@ -3773,6 +3869,7 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
             "related": net_uuid,
             "extra": yaml.safe_dump(task_extra, default_flow_style=True, width=256)
         }
             "related": net_uuid,
             "extra": yaml.safe_dump(task_extra, default_flow_style=True, width=256)
         }
+        net2task_id[sce_vnf['uuid']][net['uuid']] = task_index
         task_index += 1
         db_vim_actions.append(db_vim_action)
 
         task_index += 1
         db_vim_actions.append(db_vim_action)
 
@@ -3946,6 +4043,7 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
                         netDict['net_id'] = "TASK-{}".format(
                             net2task_id['scenario'][vnf_iface['sce_net_id']][datacenter_id])
                         instance_net_id = sce_net2instance[vnf_iface['sce_net_id']][datacenter_id]
                         netDict['net_id'] = "TASK-{}".format(
                             net2task_id['scenario'][vnf_iface['sce_net_id']][datacenter_id])
                         instance_net_id = sce_net2instance[vnf_iface['sce_net_id']][datacenter_id]
+                        instance_wim_net_id = sce_net2wim_instance[vnf_iface['sce_net_id']][datacenter_id]
                         task_depends_on.append(net2task_id['scenario'][vnf_iface['sce_net_id']][datacenter_id])
                         break
             else:
                         task_depends_on.append(net2task_id['scenario'][vnf_iface['sce_net_id']][datacenter_id])
                         break
             else:
@@ -3960,9 +4058,11 @@ def instantiate_vnf(mydb, sce_vnf, params, params_out, rollbackList):
                 # "uuid"
                 # 'instance_vm_id': instance_vm_uuid,
                 "instance_net_id": instance_net_id,
                 # "uuid"
                 # 'instance_vm_id': instance_vm_uuid,
                 "instance_net_id": instance_net_id,
+                "instance_wim_net_id": instance_wim_net_id,
                 'interface_id': iface['uuid'],
                 # 'vim_interface_id': ,
                 'type': 'external' if iface['external_name'] is not None else 'internal',
                 'interface_id': iface['uuid'],
                 # 'vim_interface_id': ,
                 'type': 'external' if iface['external_name'] is not None else 'internal',
+                'model': iface['model'],
                 'ip_address': iface.get('ip_address'),
                 'mac_address': iface.get('mac'),
                 'floating_ip': int(iface.get('floating-ip', False)),
                 'ip_address': iface.get('ip_address'),
                 'mac_address': iface.get('mac'),
                 'floating_ip': int(iface.get('floating-ip', False)),
@@ -4349,6 +4449,23 @@ def delete_instance(mydb, tenant_id, instance_id):
         }
         task_index += 1
         db_vim_actions.append(db_vim_action)
         }
         task_index += 1
         db_vim_actions.append(db_vim_action)
+    for sdn_net in instanceDict['sdn_nets']:
+        if not sdn_net["sdn"]:
+            continue
+        extra = {}
+        db_vim_action = {
+            "instance_action_id": instance_action_id,
+            "task_index": task_index,
+            "wim_account_id": sdn_net["wim_account_id"],
+            "action": "DELETE",
+            "status": "SCHEDULED",
+            "item": "instance_wim_nets",
+            "item_id": sdn_net["uuid"],
+            "related": sdn_net["related"],
+            "extra": yaml.safe_dump(extra, default_flow_style=True, width=256)
+        }
+        task_index += 1
+        db_vim_actions.append(db_vim_action)
 
     db_instance_action["number_tasks"] = task_index
 
 
     db_instance_action["number_tasks"] = task_index
 
@@ -4383,18 +4500,19 @@ def get_instance_id(mydb, tenant_id, instance_id):
     #obtain data
 
     instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
     #obtain data
 
     instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
-    for net in instance_dict["nets"]:
-        if net.get("sdn_net_id"):
-            net_sdn = ovim.show_network(net["sdn_net_id"])
-            net["sdn_info"] = {
-                "admin_state_up": net_sdn.get("admin_state_up"),
-                "flows": net_sdn.get("flows"),
-                "last_error": net_sdn.get("last_error"),
-                "ports": net_sdn.get("ports"),
-                "type": net_sdn.get("type"),
-                "status": net_sdn.get("status"),
-                "vlan": net_sdn.get("vlan"),
-            }
+    # TODO py3
+    # for net in instance_dict["nets"]:
+    #     if net.get("sdn_net_id"):
+    #         net_sdn = ovim.show_network(net["sdn_net_id"])
+    #         net["sdn_info"] = {
+    #             "admin_state_up": net_sdn.get("admin_state_up"),
+    #             "flows": net_sdn.get("flows"),
+    #             "last_error": net_sdn.get("last_error"),
+    #             "ports": net_sdn.get("ports"),
+    #             "type": net_sdn.get("type"),
+    #             "status": net_sdn.get("status"),
+    #             "vlan": net_sdn.get("vlan"),
+    #         }
     return instance_dict
 
 @deprecated("Instance is automatically refreshed by vim_threads")
     return instance_dict
 
 @deprecated("Instance is automatically refreshed by vim_threads")
@@ -4943,7 +5061,7 @@ def new_datacenter(mydb, datacenter_descriptor):
     # load plugin
     plugin_name = "rovim_" + datacenter_type
     if plugin_name not in plugins:
     # load plugin
     plugin_name = "rovim_" + datacenter_type
     if plugin_name not in plugins:
-        _load_vim_plugin(plugin_name)
+        _load_plugin(plugin_name, type="vim")
 
     datacenter_id = mydb.new_row("datacenters", datacenter_descriptor, add_uuid=True, confidential_data=True)
     if sdn_port_mapping:
 
     datacenter_id = mydb.new_row("datacenters", datacenter_descriptor, add_uuid=True, confidential_data=True)
     if sdn_port_mapping:
@@ -5095,8 +5213,7 @@ def create_vim_account(mydb, nfvo_tenant, datacenter_id, name=None, vim_id=None,
 
         # create thread
         thread_name = get_non_used_vim_name(datacenter_name, datacenter_id, tenant_dict['name'], tenant_dict['uuid'])
 
         # create thread
         thread_name = get_non_used_vim_name(datacenter_name, datacenter_id, tenant_dict['name'], tenant_dict['uuid'])
-        new_thread = vim_thread(task_lock, plugins, thread_name, datacenter_name, datacenter_tenant_id,
-                                db=db, db_lock=db_lock, ovim=ovim)
+        new_thread = vim_thread(task_lock, plugins, thread_name, None, datacenter_tenant_id, db=db)
         new_thread.start()
         thread_id = datacenter_tenants_dict["uuid"]
         vim_threads["running"][thread_id] = new_thread
         new_thread.start()
         thread_id = datacenter_tenants_dict["uuid"]
         vim_threads["running"][thread_id] = new_thread
@@ -5609,13 +5726,20 @@ def vim_action_create(mydb, tenant_id, datacenter, item, descriptor):
     return vim_action_get(mydb, tenant_id, datacenter, item, content)
 
 def sdn_controller_create(mydb, tenant_id, sdn_controller):
     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
+    wim_id = ovim.new_of_controller(sdn_controller)
+
+    thread_name = get_non_used_vim_name(sdn_controller['name'], wim_id, wim_id, None)
+    new_thread = vim_thread(task_lock, plugins, thread_name, wim_id, None, db=db)
+    new_thread.start()
+    thread_id = wim_id
+    vim_threads["running"][thread_id] = new_thread
+    logger.debug('New SDN controller created with uuid {}'.format(wim_id))
+    return wim_id
 
 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)
 
 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)
+    vim_threads["running"][controller_id].insert_task("reload")
     logger.debug(msg)
     return msg
 
     logger.debug(msg)
     return msg
 
@@ -5661,20 +5785,23 @@ def datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, sdn_port_map
         #element = {"ofc_id": sdn_controller_id, "region": datacenter_id, "switch_dpid": switch_dpid}
         element = dict()
         element["compute_node"] = compute_node["compute_node"]
         #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"]:
-            pci = port.get("pci")
-            element["switch_port"] = port.get("switch_port")
-            element["switch_mac"] = port.get("switch_mac")
-            if not element["switch_port"] and not element["switch_mac"]:
-                raise NfvoException ("The mapping must contain 'switch_port' or 'switch_mac'", httperrors.Bad_Request)
-            for pci_expanded in utils.expand_brackets(pci):
-                element["pci"] = pci_expanded
-                maps.append(dict(element))
-
-    return ovim.set_of_port_mapping(maps, ofc_id=sdn_controller_id, switch_dpid=switch_dpid, region=datacenter_id)
+        if compute_node["ports"]:
+            for port in compute_node["ports"]:
+                pci = port.get("pci")
+                element["switch_port"] = port.get("switch_port")
+                element["switch_mac"] = port.get("switch_mac")
+                if not element["switch_port"] and not element["switch_mac"]:
+                    raise NfvoException ("The mapping must contain 'switch_port' or 'switch_mac'", httperrors.Bad_Request)
+                for pci_expanded in utils.expand_brackets(pci):
+                    element["pci"] = pci_expanded
+                    maps.append(dict(element))
+
+    out = ovim.set_of_port_mapping(maps, sdn_id=sdn_controller_id, switch_dpid=switch_dpid, vim_id=datacenter_id)
+    vim_threads["running"][sdn_controller_id].insert_task("reload")
+    return out
 
 def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id):
 
 def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id):
-    maps = ovim.get_of_port_mappings(db_filter={"region": datacenter_id})
+    maps = ovim.get_of_port_mappings(db_filter={"datacenter_id": datacenter_id})
 
     result = {
         "sdn-controller": None,
 
     result = {
         "sdn-controller": None,
@@ -5703,24 +5830,25 @@ def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id):
 
     ports_correspondence_dict = dict()
     for link in maps:
 
     ports_correspondence_dict = dict()
     for link in maps:
-        if result["sdn-controller"] != link["ofc_id"]:
+        if result["sdn-controller"] != link["wim_id"]:
             raise NfvoException("The sdn-controller specified for different port mappings differ", httperrors.Internal_Server_Error)
         if result["dpid"] != link["switch_dpid"]:
             raise NfvoException("The dpid specified for different port mappings differ", httperrors.Internal_Server_Error)
             raise NfvoException("The sdn-controller specified for different port mappings differ", httperrors.Internal_Server_Error)
         if result["dpid"] != link["switch_dpid"]:
             raise NfvoException("The dpid specified for different port mappings differ", httperrors.Internal_Server_Error)
+        link_config = link["service_mapping_info"]
         element = dict()
         element = dict()
-        element["pci"] = link["pci"]
+        element["pci"] = link.get("device_interface_id")
         if link["switch_port"]:
             element["switch_port"] = link["switch_port"]
         if link["switch_port"]:
             element["switch_port"] = link["switch_port"]
-        if link["switch_mac"]:
-            element["switch_mac"] = link["switch_mac"]
+        if link_config["switch_mac"]:
+            element["switch_mac"] = link_config.get("switch_mac")
 
 
-        if not link["compute_node"] in ports_correspondence_dict:
+        if not link.get("interface_id") in ports_correspondence_dict:
             content = dict()
             content = dict()
-            content["compute_node"] = link["compute_node"]
+            content["compute_node"] = link.get("interface_id")
             content["ports"] = list()
             content["ports"] = list()
-            ports_correspondence_dict[link["compute_node"]] = content
+            ports_correspondence_dict[link.get("interface_id")] = content
 
 
-        ports_correspondence_dict[link["compute_node"]]["ports"].append(element)
+        ports_correspondence_dict[link["interface_id"]]["ports"].append(element)
 
     for key in sorted(ports_correspondence_dict):
         result["ports_mapping"].append(ports_correspondence_dict[key])
 
     for key in sorted(ports_correspondence_dict):
         result["ports_mapping"].append(ports_correspondence_dict[key])
@@ -5728,7 +5856,7 @@ def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id):
     return result
 
 def datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id):
     return result
 
 def datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id):
-    return ovim.clear_of_port_mapping(db_filter={"region":datacenter_id})
+    return ovim.clear_of_port_mapping(db_filter={"datacenter_id":datacenter_id})
 
 def create_RO_keypair(tenant_id):
     """
 
 def create_RO_keypair(tenant_id):
     """
index afd9d15..df4f161 100644 (file)
@@ -48,7 +48,7 @@ tables_with_createdat_field=["datacenters","instance_nets","instance_scenarios",
                            "instance_actions", "sce_vnffgs", "sce_rsps", "sce_rsp_hops",
                            "sce_classifiers", "sce_classifier_matches", "instance_sfis", "instance_sfs",
                            "instance_classifications", "instance_sfps", "wims", "wim_accounts", "wim_nfvo_tenants",
                            "instance_actions", "sce_vnffgs", "sce_rsps", "sce_rsp_hops",
                            "sce_classifiers", "sce_classifier_matches", "instance_sfis", "instance_sfs",
                            "instance_classifications", "instance_sfps", "wims", "wim_accounts", "wim_nfvo_tenants",
-                           "wim_port_mappings", "vim_wim_actions",
+                           "wim_port_mappings", "vim_wim_actions", "instance_interfaces",
                            "instance_wim_nets"]
 
 
                            "instance_wim_nets"]
 
 
@@ -976,7 +976,7 @@ class nfvo_db(db_base.db_base):
                 cmd = "SELECT vim_interface_id, instance_net_id, internal_name,external_name, mac_address,"\
                         " ii.ip_address as ip_address, vim_info, i.type as type, sdn_port_id, i.uuid"\
                         " FROM instance_interfaces as ii join interfaces as i on ii.interface_id=i.uuid"\
                 cmd = "SELECT vim_interface_id, instance_net_id, internal_name,external_name, mac_address,"\
                         " ii.ip_address as ip_address, vim_info, i.type as type, sdn_port_id, i.uuid"\
                         " FROM instance_interfaces as ii join interfaces as i on ii.interface_id=i.uuid"\
-                        " WHERE instance_vm_id='{}' ORDER BY created_at".format(vm['uuid'])
+                        " WHERE instance_vm_id='{}' ORDER BY i.created_at".format(vm['uuid'])
                 self.logger.debug(cmd)
                 self.cur.execute(cmd )
                 vm['interfaces'] = self.cur.fetchall()
                 self.logger.debug(cmd)
                 self.cur.execute(cmd )
                 vm['interfaces'] = self.cur.fetchall()
@@ -1010,6 +1010,13 @@ class nfvo_db(db_base.db_base):
         self.cur.execute(cmd)
         instance_dict['nets'] = self.cur.fetchall()
 
         self.cur.execute(cmd)
         instance_dict['nets'] = self.cur.fetchall()
 
+        # instance sdn_nets:
+        cmd = "SELECT * FROM instance_wim_nets WHERE instance_scenario_id='{}' ORDER BY created_at;".format(
+            instance_dict['uuid'])
+        self.logger.debug(cmd)
+        self.cur.execute(cmd)
+        instance_dict['sdn_nets'] = self.cur.fetchall()
+
         #instance_sfps
         cmd = "SELECT uuid,vim_sfp_id,sce_rsp_id,datacenter_id,"\
                 "datacenter_tenant_id,status,error_msg,vim_info, related"\
         #instance_sfps
         cmd = "SELECT uuid,vim_sfp_id,sce_rsp_id,datacenter_id,"\
                 "datacenter_tenant_id,status,error_msg,vim_info, related"\
index cdf451a..637b1da 100755 (executable)
@@ -53,9 +53,9 @@ import osm_ro
 
 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
 __date__ = "$26-aug-2014 11:09:29$"
 
 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
 __date__ = "$26-aug-2014 11:09:29$"
-__version__ = "6.0.3.post5"
+__version__ = "6.0.4.post6"
 version_date = "Oct 2019"
 version_date = "Oct 2019"
-database_version = 39      # expected database schema version
+database_version = 40      # expected database schema version
 
 global global_config
 global logger
 
 global global_config
 global logger
@@ -327,7 +327,7 @@ if __name__ == "__main__":
 
         # WIM module
         wim_persistence = WimPersistence(mydb)
 
         # WIM module
         wim_persistence = WimPersistence(mydb)
-        wim_engine = WimEngine(wim_persistence)
+        wim_engine = WimEngine(wim_persistence, nfvo.plugins)
         # ---
         nfvo.start_service(mydb, wim_persistence, wim_engine)
 
         # ---
         nfvo.start_service(mydb, wim_persistence, wim_engine)
 
index 2246885..4f4e4ca 100644 (file)
@@ -1,3 +1,18 @@
+##
+# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
+# 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.
+##
+
 [Unit]
 Description=openmano server (OSM RO)
 After=mysql.service
 [Unit]
 Description=openmano server (OSM RO)
 After=mysql.service
index 1a8750d..94183e9 100755 (executable)
@@ -1,4 +1,18 @@
 #!/bin/bash
 #!/bin/bash
+##
+# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
+# 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.
+##
 
 # This script is intended for launching RO from a docker container.
 # It waits for mysql server ready, normally running on a separate container, ...
 
 # This script is intended for launching RO from a docker container.
 # It waits for mysql server ready, normally running on a separate container, ...
diff --git a/RO/osm_ro/sdn.py b/RO/osm_ro/sdn.py
new file mode 100755 (executable)
index 0000000..91cc9b2
--- /dev/null
@@ -0,0 +1,329 @@
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
+# 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.
+##
+
+"""
+This is the thread for the http server North API. 
+Two thread will be launched, with normal and administrative permissions.
+"""
+import yaml
+from uuid import uuid4
+from http import HTTPStatus
+
+__author__ = "Alfonso Tierno"
+__date__ = "2019-10-22"
+__version__ = "0.1"
+version_date = "Oct 2019"
+
+
+class SdnException(Exception):
+    def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value):
+        self.http_code = http_code
+        Exception.__init__(self, message)
+
+
+class Sdn():
+    running_info = {}  # TODO OVIM move the info of running threads from config_dic to this static variable
+    of_module = {}
+
+    def __init__(self, db, plugins):
+        self.db = db
+        self.plugins = plugins
+
+    def start_service(self):
+        pass  # TODO py3 needed to load wims and plugins
+
+    def stop_service(self):
+        pass  # nothing needed
+
+    def show_network(self, uuid):
+        pass
+
+    def delete_network(self, uuid):
+        pass
+
+    def new_network(self, network):
+        pass
+
+    def get_openflow_rules(self, network_id=None):
+        """
+        Get openflow id from DB
+        :param network_id: Network id, if none all networks will be retrieved
+        :return: Return a list with Openflow rules per net
+        """
+        # ignore input data
+        if not network_id:
+
+            where_ = {}
+        else:
+            where_ = {"net_id": network_id}
+        result, content = self.db.get_table(
+            SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
+            WHERE=where_, FROM='of_flows')
+
+        if result < 0:
+            raise SdnException(str(content), -result)
+        return content
+
+    def edit_openflow_rules(self, network_id=None):
+
+        """
+        To make actions over the net. The action is to reinstall the openflow rules
+        network_id can be 'all'
+        :param network_id: Network id, if none all networks will be retrieved
+        :return : Number of nets updated
+        """
+
+        # ignore input data
+        if not network_id:
+            where_ = {}
+        else:
+            where_ = {"uuid": network_id}
+        result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
+
+        if result < 0:
+            raise SdnException(str(content), -result)
+
+        for net in content:
+            if net["type"] != "ptp" and net["type"] != "data":
+                result -= 1
+                continue
+
+            try:
+                self.net_update_ofc_thread(net['uuid'])
+            except SdnException as e:
+                raise SdnException("Error updating network'{}' {}".format(net['uuid'], e),
+                                   HTTPStatus.INTERNAL_SERVER_ERROR.value)
+            except Exception as e:
+                raise SdnException("Error updating network '{}' {}".format(net['uuid'], e),
+                                   HTTPStatus.INTERNAL_SERVER_ERROR.value)
+
+        return result
+
+    def delete_openflow_rules(self, ofc_id=None):
+        """
+        To make actions over the net. The action is to delete ALL openflow rules
+        :return: return operation result
+        """
+
+        if not ofc_id:
+            if 'Default' in self.config['ofcs_thread']:
+                r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
+            else:
+                raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
+
+        elif ofc_id in self.config['ofcs_thread']:
+            r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
+
+            # ignore input data
+            if r < 0:
+                raise SdnException(str(c), -r)
+        else:
+            raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
+                               HTTPStatus.NOT_FOUND.value)
+        return r
+
+    def get_openflow_ports(self, ofc_id=None):
+        """
+        Obtain switch ports names of openflow controller
+        :return: Return flow ports in DB
+        """
+        if not ofc_id:
+            if 'Default' in self.config['ofcs_thread']:
+                conn = self.config['ofcs_thread']['Default'].OF_connector
+            else:
+                raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
+
+        elif ofc_id in self.config['ofcs_thread']:
+            conn = self.config['ofcs_thread'][ofc_id].OF_connector
+        else:
+            raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
+                               HTTPStatus.NOT_FOUND.value)
+        return conn.pp2ofi
+
+    def new_of_controller(self, ofc_data):
+        """
+        Create a new openflow controller into DB
+        :param ofc_data: Dict openflow controller data
+        :return: openflow controller dpid
+        """
+        db_wim = {
+            "uuid": str(uuid4()),
+            "name": ofc_data["name"],
+            "description": "",
+            "type": ofc_data["type"],
+            "wim_url": "{}:{}".format(ofc_data["ip"], ofc_data["port"]),
+        }
+        db_wim_account = {
+            "uuid": str(uuid4()),
+            "name": ofc_data["name"],
+            "wim_id": db_wim["uuid"],
+            "sdn": "true",
+            "user": ofc_data.get("user"),
+            "password": ofc_data.get("password"),
+            "config": yaml.safe_dump({"dpid": ofc_data["dpid"], "version": ofc_data.get("version")},
+                                     default_flow_style=True, width=256)
+        }
+        db_tables = [
+            {"wims": db_wim},
+            {"wim_accounts": db_wim_account},
+        ]
+        uuid_list = [db_wim["uuid"], db_wim_account["uuid"]]
+        self.db.new_rows(db_tables, uuid_list)
+        return db_wim_account["uuid"]
+
+    def edit_of_controller(self, of_id, ofc_data):
+        """
+        Edit an openflow controller entry from DB
+        :return:
+        """
+        if not ofc_data:
+            raise SdnException("No data received during uptade OF contorller",
+                               http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value)
+
+        old_of_controller = self.show_of_controller(of_id)
+
+        if old_of_controller:
+            result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
+            if result >= 0:
+                return ofc_data
+            else:
+                raise SdnException("Error uptating OF contorller with uuid {}".format(of_id),
+                                   http_code=-result)
+        else:
+            raise SdnException("Error uptating OF contorller with uuid {}".format(of_id),
+                               http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value)
+
+    def delete_of_controller(self, of_id):
+        """
+        Delete an openflow controller from DB.
+        :param of_id: openflow controller dpid
+        :return:
+        """
+        wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
+        if not wim_accounts:
+            raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
+                               http_code=HTTPStatus.NOT_FOUND.value)
+        elif len(wim_accounts) > 1:
+            raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
+                               http_code=HTTPStatus.CONFLICT.value)
+        self.db.delete_row(FROM='wim_accounts', WHERE={"uuid": of_id})
+        self.db.delete_row(FROM='wims', WHERE={"uuid": wim_accounts[0]["wim_id"]})
+        return of_id
+
+    def _format_of_controller(self, wim_account, wim=None):
+        of_data = {x: wim_account[x] for x in ("uuid", "name", "user")}
+        if isinstance(wim_account["config"], str):
+            config = yaml.load(wim_account["config"], Loader=yaml.Loader)
+        of_data["dpid"] = config.get("dpid")
+        of_data["version"] = config.get("version")
+        if wim:
+            ip, port = wim["wim_url"].split(":")
+            of_data["ip"] = ip
+            of_data["port"] = port
+            of_data["type"] = wim["type"]
+        return of_data
+
+    def show_of_controller(self, of_id):
+        """
+        Show an openflow controller by dpid from DB.
+        :param db_filter: List with where query parameters
+        :return:
+        """
+        wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
+        if not wim_accounts:
+            raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
+                               http_code=HTTPStatus.NOT_FOUND.value)
+        elif len(wim_accounts) > 1:
+            raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
+                               http_code=HTTPStatus.CONFLICT.value)
+        wims = self.db.get_rows(FROM='wims', WHERE={"uuid": wim_accounts[0]["wim_id"]})
+        return self._format_of_controller(wim_accounts[0], wims[0])
+
+    def get_of_controllers(self, filter=None):
+        """
+        Show an openflow controllers from DB.
+        :return:
+        """
+        filter = filter or {}
+        filter["sdn"] = "true"
+        wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE=filter)
+        return [self._format_of_controller(w) for w in wim_accounts]
+
+    def set_of_port_mapping(self, maps, sdn_id, switch_dpid, vim_id):
+        """
+        Create new port mapping entry
+        :param of_maps: List with port mapping information
+        # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
+                "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
+        :param sdn_id: ofc id
+        :param switch_dpid: switch  dpid
+        :param vim_id: datacenter
+        :return:
+        """
+        # get wim from wim_account
+        wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": sdn_id})
+        if not wim_accounts:
+            raise SdnException("Not found sdn id={}".format(sdn_id), http_code=HTTPStatus.NOT_FOUND.value)
+        wim_id = wim_accounts[0]["wim_id"]
+        db_wim_port_mappings = []
+        for map in maps:
+            new_map = {
+                'wim_id': wim_id,
+                'switch_dpid': switch_dpid,
+                "switch_port": map.get("switch_port"),
+                'datacenter_id': vim_id,
+                "device_id": map.get("compute_node"),
+                "service_endpoint_id": switch_dpid + "-" + str(uuid4())
+            }
+            if map.get("pci"):
+                new_map["device_interface_id"] = map["pci"].lower()
+            config = {}
+            if map.get("switch_mac"):
+                config["switch_mac"] = map["switch_mac"]
+            if config:
+                new_map["service_mapping_info"] = yaml.safe_dump(config, default_flow_style=True, width=256)
+            db_wim_port_mappings.append(new_map)
+
+        db_tables = [
+            {"wim_port_mappings": db_wim_port_mappings},
+        ]
+        self.db.new_rows(db_tables, [])
+        return db_wim_port_mappings
+
+    def clear_of_port_mapping(self, db_filter=None):
+        """
+        Clear port mapping filtering using db_filter dict
+        :param db_filter: Parameter to filter during remove process
+        :return:
+        """
+        return self.db.delete_row(FROM='wim_port_mappings', WHERE=db_filter)
+
+    def get_of_port_mappings(self, db_filter=None):
+        """
+        Retrive port mapping from DB
+        :param db_filter:
+        :return:
+        """
+        maps = self.db.get_rows(WHERE=db_filter, FROM='wim_port_mappings')
+        for map in maps:
+            if map.get("service_mapping_info"):
+                map["service_mapping_info"] = yaml.load(map["service_mapping_info"], Loader=yaml.Loader)
+            else:
+                map["service_mapping_info"] = {}
+        return maps
index e69de29..7284a2b 100644 (file)
@@ -0,0 +1,13 @@
+##
+# 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.
+##
index 5e90bd9..e381116 100644 (file)
@@ -1,4 +1,18 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+##
+# 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.
+##
+
 # pylint: disable=E1101
 import unittest
 
 # pylint: disable=E1101
 import unittest
 
index 9fd71cf..c62c954 100644 (file)
@@ -1,4 +1,18 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+##
+# 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.
+##
+
 # pylint: disable=E1101
 
 import unittest
 # pylint: disable=E1101
 
 import unittest
index 1e1e6d2..728b659 100644 (file)
@@ -81,12 +81,10 @@ import time
 import queue
 import logging
 from osm_ro import vimconn
 import queue
 import logging
 from osm_ro import vimconn
+from osm_ro.wim.sdnconn import SdnConnectorError
 import yaml
 from osm_ro.db_base import db_base_Exception
 import yaml
 from osm_ro.db_base import db_base_Exception
-# TODO py3 BEGIN
-class ovimException(Exception):
-    pass
-# TODO py3 END
+from http import HTTPStatus
 from copy import deepcopy
 
 __author__ = "Alfonso Tierno, Pablo Montes"
 from copy import deepcopy
 
 __author__ = "Alfonso Tierno, Pablo Montes"
@@ -111,8 +109,7 @@ class vim_thread(threading.Thread):
     REFRESH_ERROR = 600
     REFRESH_DELETE = 3600 * 10
 
     REFRESH_ERROR = 600
     REFRESH_DELETE = 3600 * 10
 
-    def __init__(self, task_lock, plugins, name=None, datacenter_name=None, datacenter_tenant_id=None,
-                 db=None, db_lock=None, ovim=None):
+    def __init__(self, task_lock, plugins, name=None, wim_account_id=None, datacenter_tenant_id=None, db=None):
         """Init a thread.
         Arguments:
             'id' number of thead
         """Init a thread.
         Arguments:
             'id' number of thead
@@ -122,60 +119,114 @@ class vim_thread(threading.Thread):
         """
         threading.Thread.__init__(self)
         self.plugins = plugins
         """
         threading.Thread.__init__(self)
         self.plugins = plugins
+        self.plugin_name = "unknown"
         self.vim = None
         self.vim = None
+        self.sdnconnector = None
+        self.sdnconn_config = None
         self.error_status = None
         self.error_status = None
-        self.datacenter_name = datacenter_name
+        self.wim_account_id = wim_account_id
         self.datacenter_tenant_id = datacenter_tenant_id
         self.datacenter_tenant_id = datacenter_tenant_id
-        self.ovim = ovim
+        self.port_mapping = None
+        if self.wim_account_id:
+            self.target_k = "wim_account_id"
+            self.target_v = self.wim_account_id
+        else:
+            self.target_k = "datacenter_vim_id"
+            self.target_v = self.datacenter_tenant_id
         if not name:
         if not name:
-            self.name = vimconn["id"] + "." + vimconn["config"]["datacenter_tenant_id"]
+            self.name = wim_account_id or str(datacenter_tenant_id)
         else:
             self.name = name
         self.vim_persistent_info = {}
         self.my_id = self.name[:64]
 
         else:
             self.name = name
         self.vim_persistent_info = {}
         self.my_id = self.name[:64]
 
-        self.logger = logging.getLogger('openmano.vim.' + self.name)
+        self.logger = logging.getLogger('openmano.{}.{}'.format("vim" if self.datacenter_tenant_id else "sdn",
+                                                                self.name))
         self.db = db
         self.db = db
-        self.db_lock = db_lock
 
         self.task_lock = task_lock
         self.task_queue = queue.Queue(2000)
 
 
         self.task_lock = task_lock
         self.task_queue = queue.Queue(2000)
 
-    def get_vimconnector(self):
-        try:
-            from_ = "datacenter_tenants as dt join datacenters as d on dt.datacenter_id=d.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',
-                       'user', 'passwd', 'dt.config as dt_config')
-            where_ = {"dt.uuid": self.datacenter_tenant_id}
-            vims = self.db.get_rows(FROM=from_, SELECT=select_, WHERE=where_)
-            vim = vims[0]
-            vim_config = {}
-            if vim["config"]:
-                vim_config.update(yaml.load(vim["config"], Loader=yaml.Loader))
-            if vim["dt_config"]:
-                vim_config.update(yaml.load(vim["dt_config"], Loader=yaml.Loader))
-            vim_config['datacenter_tenant_id'] = vim.get('datacenter_tenant_id')
-            vim_config['datacenter_id'] = vim.get('datacenter_id')
-
-            # get port_mapping
-            with self.db_lock:
-                vim_config["wim_external_ports"] = self.ovim.get_of_port_mappings(
-                    db_filter={"region": vim_config['datacenter_id'], "pci": None})
-
-            self.vim = self.plugins["rovim_" + vim["type"]].vimconnector(
-                uuid=vim['datacenter_id'], name=vim['datacenter_name'],
-                tenant_id=vim['vim_tenant_id'], tenant_name=vim['vim_tenant_name'],
-                url=vim['vim_url'], url_admin=vim['vim_url_admin'],
-                user=vim['user'], passwd=vim['passwd'],
-                config=vim_config, persistent_info=self.vim_persistent_info
-            )
-            self.error_status = None
-        except Exception as e:
-            self.logger.error("Cannot load vimconnector for vim_account {}: {}".format(self.datacenter_tenant_id, e))
-            self.vim = None
-            self.error_status = "Error loading vimconnector: {}".format(e)
+    def _proccess_sdn_exception(self, exc):
+        if isinstance(exc, SdnConnectorError):
+            raise
+        else:
+            self.logger.error("plugin={} throws a non SdnConnectorError exception {}".format(self.plugin_name, exc),
+                              exc_info=True)
+            raise SdnConnectorError(str(exc), http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value) from exc
+
+    def _proccess_vim_exception(self, exc):
+        if isinstance(exc, vimconn.vimconnException):
+            raise
+        else:
+            self.logger.error("plugin={} throws a non vimconnException exception {}".format(self.plugin_name, exc),
+                              exc_info=True)
+            raise vimconn.vimconnException(str(exc), http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value) from exc
+
+    def get_vim_sdn_connector(self):
+        if self.datacenter_tenant_id:
+            try:
+                from_ = "datacenter_tenants as dt join datacenters as d on dt.datacenter_id=d.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',
+                           'user', 'passwd', 'dt.config as dt_config')
+                where_ = {"dt.uuid": self.datacenter_tenant_id}
+                vims = self.db.get_rows(FROM=from_, SELECT=select_, WHERE=where_)
+                vim = vims[0]
+                vim_config = {}
+                if vim["config"]:
+                    vim_config.update(yaml.load(vim["config"], Loader=yaml.Loader))
+                if vim["dt_config"]:
+                    vim_config.update(yaml.load(vim["dt_config"], Loader=yaml.Loader))
+                vim_config['datacenter_tenant_id'] = vim.get('datacenter_tenant_id')
+                vim_config['datacenter_id'] = vim.get('datacenter_id')
+
+                # get port_mapping
+                # vim_port_mappings = self.ovim.get_of_port_mappings(
+                #     db_filter={"datacenter_id": vim_config['datacenter_id']})
+                # vim_config["wim_external_ports"] = [x for x in vim_port_mappings
+                #                                     if x["service_mapping_info"].get("wim")]
+                self.plugin_name = "rovim_" + vim["type"]
+                self.vim = self.plugins[self.plugin_name].vimconnector(
+                    uuid=vim['datacenter_id'], name=vim['datacenter_name'],
+                    tenant_id=vim['vim_tenant_id'], tenant_name=vim['vim_tenant_name'],
+                    url=vim['vim_url'], url_admin=vim['vim_url_admin'],
+                    user=vim['user'], passwd=vim['passwd'],
+                    config=vim_config, persistent_info=self.vim_persistent_info
+                )
+                self.error_status = None
+                self.logger.info("Vim Connector loaded for vim_account={}, plugin={}".format(
+                    self.datacenter_tenant_id, self.plugin_name))
+            except Exception as e:
+                self.logger.error("Cannot load vimconnector for vim_account={} plugin={}: {}".format(
+                    self.datacenter_tenant_id, self.plugin_name, e))
+                self.vim = None
+                self.error_status = "Error loading vimconnector: {}".format(e)
+        else:
+            try:
+                wim_account = self.db.get_rows(FROM="wim_accounts", WHERE={"uuid": self.wim_account_id})[0]
+                wim = self.db.get_rows(FROM="wims", WHERE={"uuid": wim_account["wim_id"]})[0]
+                if wim["config"]:
+                    self.sdnconn_config = yaml.load(wim["config"], Loader=yaml.Loader)
+                else:
+                    self.sdnconn_config = {}
+                if wim_account["config"]:
+                    self.sdnconn_config.update(yaml.load(wim_account["config"], Loader=yaml.Loader))
+                self.port_mappings = self.db.get_rows(FROM="wim_port_mappings", WHERE={"wim_id": wim_account["wim_id"]})
+                if self.port_mappings:
+                    self.sdnconn_config["service_endpoint_mapping"] = self.port_mappings
+                self.plugin_name = "rosdn_" + wim["type"]
+                self.sdnconnector = self.plugins[self.plugin_name](
+                    wim, wim_account, config=self.sdnconn_config)
+                self.error_status = None
+                self.logger.info("Sdn Connector loaded for wim_account={}, plugin={}".format(
+                    self.wim_account_id, self.plugin_name))
+            except Exception as e:
+                self.logger.error("Cannot load sdn connector for wim_account={}, plugin={}: {}".format(
+                    self.wim_account_id, self.plugin_name, e))
+                self.sdnconnector = None
+                self.error_status = "Error loading sdn connector: {}".format(e)
 
     def _get_db_task(self):
         """
 
     def _get_db_task(self):
         """
@@ -189,7 +240,7 @@ class vim_thread(threading.Thread):
             while True:
                 # get 20 (database_limit) entries each time
                 vim_actions = self.db.get_rows(FROM="vim_wim_actions",
             while True:
                 # get 20 (database_limit) entries each time
                 vim_actions = self.db.get_rows(FROM="vim_wim_actions",
-                                               WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                                               WHERE={self.target_k: self.target_v,
                                                       "status": ['SCHEDULED', 'BUILD', 'DONE'],
                                                       "worker": [None, self.my_id], "modified_at<=": now
                                                       },
                                                       "status": ['SCHEDULED', 'BUILD', 'DONE'],
                                                       "worker": [None, self.my_id], "modified_at<=": now
                                                       },
@@ -206,7 +257,7 @@ class vim_thread(threading.Thread):
                     task_related = task["related"]
                     # lock ...
                     self.db.update_rows("vim_wim_actions", UPDATE={"worker": self.my_id}, modified_time=0,
                     task_related = task["related"]
                     # lock ...
                     self.db.update_rows("vim_wim_actions", UPDATE={"worker": self.my_id}, modified_time=0,
-                                        WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                                        WHERE={self.target_k: self.target_v,
                                                "status": ['SCHEDULED', 'BUILD', 'DONE', 'FAILED'],
                                                "worker": [None, self.my_id],
                                                "related": task_related,
                                                "status": ['SCHEDULED', 'BUILD', 'DONE', 'FAILED'],
                                                "worker": [None, self.my_id],
                                                "related": task_related,
@@ -214,7 +265,7 @@ class vim_thread(threading.Thread):
                                                })
                     # ... and read all related and check if locked
                     related_tasks = self.db.get_rows(FROM="vim_wim_actions",
                                                })
                     # ... and read all related and check if locked
                     related_tasks = self.db.get_rows(FROM="vim_wim_actions",
-                                                     WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                                                     WHERE={self.target_k: self.target_v,
                                                             "status": ['SCHEDULED', 'BUILD', 'DONE', 'FAILED'],
                                                             "related": task_related,
                                                             "item": task["item"],
                                                             "status": ['SCHEDULED', 'BUILD', 'DONE', 'FAILED'],
                                                             "related": task_related,
                                                             "item": task["item"],
@@ -235,7 +286,7 @@ class vim_thread(threading.Thread):
                     if some_tasks_not_locked:
                         if some_tasks_locked:  # unlock
                             self.db.update_rows("vim_wim_actions", UPDATE={"worker": None}, modified_time=0,
                     if some_tasks_not_locked:
                         if some_tasks_locked:  # unlock
                             self.db.update_rows("vim_wim_actions", UPDATE={"worker": None}, modified_time=0,
-                                                WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                                                WHERE={self.target_k: self.target_v,
                                                        "worker": self.my_id,
                                                        "related": task_related,
                                                        "item": task["item"],
                                                        "worker": self.my_id,
                                                        "related": task_related,
                                                        "item": task["item"],
@@ -285,7 +336,7 @@ class vim_thread(threading.Thread):
         try:
             # get all related tasks
             related_tasks = self.db.get_rows(FROM="vim_wim_actions",
         try:
             # get all related tasks
             related_tasks = self.db.get_rows(FROM="vim_wim_actions",
-                                             WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                                             WHERE={self.target_k: self.target_v,
                                                     "status": ['SCHEDULED', 'BUILD', 'DONE', 'FAILED'],
                                                     "action": ["FIND", "CREATE"],
                                                     "related": task["related"],
                                                     "status": ['SCHEDULED', 'BUILD', 'DONE', 'FAILED'],
                                                     "action": ["FIND", "CREATE"],
                                                     "related": task["related"],
@@ -308,7 +359,7 @@ class vim_thread(threading.Thread):
 
             # mark task_create as FINISHED
             self.db.update_rows("vim_wim_actions", UPDATE={"status": "FINISHED"},
 
             # mark task_create as FINISHED
             self.db.update_rows("vim_wim_actions", UPDATE={"status": "FINISHED"},
-                                WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                                WHERE={self.target_k: self.target_v,
                                        "instance_action_id": task_create["instance_action_id"],
                                        "task_index": task_create["task_index"]
                                        })
                                        "instance_action_id": task_create["instance_action_id"],
                                        "task_index": task_create["task_index"]
                                        })
@@ -324,7 +375,7 @@ class vim_thread(threading.Thread):
                                     UPDATE={"extra": yaml.safe_dump(extra_new_created, default_flow_style=True,
                                                                     width=256),
                                             "vim_id": task_create.get("vim_id")},
                                     UPDATE={"extra": yaml.safe_dump(extra_new_created, default_flow_style=True,
                                                                     width=256),
                                             "vim_id": task_create.get("vim_id")},
-                                    WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                                    WHERE={self.target_k: self.target_v,
                                            "instance_action_id": dependency_task["instance_action_id"],
                                            "task_index": dependency_task["task_index"]
                                            })
                                            "instance_action_id": dependency_task["instance_action_id"],
                                            "task_index": dependency_task["task_index"]
                                            })
@@ -366,40 +417,38 @@ class vim_thread(threading.Thread):
             task_vim_interface = task_interface.get("vim_info")
             if task_vim_interface != interface:
                 # delete old port
             task_vim_interface = task_interface.get("vim_info")
             if task_vim_interface != interface:
                 # delete old port
-                if task_interface.get("sdn_port_id"):
-                    try:
-                        with self.db_lock:
-                            self.ovim.delete_port(task_interface["sdn_port_id"], idempotent=True)
-                            task_interface["sdn_port_id"] = None
-                    except ovimException as e:
-                        error_text = "ovimException deleting external_port={}: {}".format(
-                            task_interface["sdn_port_id"], e)
-                        self.logger.error("task={} get-VM: {}".format(task_id, error_text), exc_info=True)
-                        task_warning_msg += error_text
-                        # TODO Set error_msg at instance_nets instead of instance VMs
+                # if task_interface.get("sdn_port_id"):
+                #     try:
+                #         self.ovim.delete_port(task_interface["sdn_port_id"], idempotent=True)
+                #         task_interface["sdn_port_id"] = None
+                #     except ovimException as e:
+                #         error_text = "ovimException deleting external_port={}: {}".format(
+                #             task_interface["sdn_port_id"], e)
+                #         self.logger.error("task={} get-VM: {}".format(task_id, error_text), exc_info=True)
+                #         task_warning_msg += error_text
+                #         # TODO Set error_msg at instance_nets instead of instance VMs
 
                 # Create SDN port
 
                 # Create SDN port
-                sdn_net_id = task_interface.get("sdn_net_id")
-                if sdn_net_id and interface.get("compute_node") and interface.get("pci"):
-                    sdn_port_name = sdn_net_id + "." + task["vim_id"]
-                    sdn_port_name = sdn_port_name[:63]
-                    try:
-                        with self.db_lock:
-                            sdn_port_id = self.ovim.new_external_port(
-                                {"compute_node": interface["compute_node"],
-                                    "pci": interface["pci"],
-                                    "vlan": interface.get("vlan"),
-                                    "net_id": sdn_net_id,
-                                    "region": self.vim["config"]["datacenter_id"],
-                                    "name": sdn_port_name,
-                                    "mac": interface.get("mac_address")})
-                            task_interface["sdn_port_id"] = sdn_port_id
-                    except (ovimException, Exception) as e:
-                        error_text = "ovimException creating new_external_port compute_node={} pci={} vlan={} {}".\
-                            format(interface["compute_node"], interface["pci"], interface.get("vlan"), e)
-                        self.logger.error("task={} get-VM: {}".format(task_id, error_text), exc_info=True)
-                        task_warning_msg += error_text
-                        # TODO Set error_msg at instance_nets instead of instance VMs
+                # sdn_net_id = task_interface.get("sdn_net_id")
+                # if sdn_net_id and interface.get("compute_node") and interface.get("pci"):
+                #     sdn_port_name = sdn_net_id + "." + task["vim_id"]
+                #     sdn_port_name = sdn_port_name[:63]
+                #     try:
+                #         sdn_port_id = self.ovim.new_external_port(
+                #             {"compute_node": interface["compute_node"],
+                #                 "pci": interface["pci"],
+                #                 "vlan": interface.get("vlan"),
+                #                 "net_id": sdn_net_id,
+                #                 "region": self.vim["config"]["datacenter_id"],
+                #                 "name": sdn_port_name,
+                #                 "mac": interface.get("mac_address")})
+                #         task_interface["sdn_port_id"] = sdn_port_id
+                #     except (ovimException, Exception) as e:
+                #         error_text = "ovimException creating new_external_port compute_node={} pci={} vlan={} {}".\
+                #             format(interface["compute_node"], interface["pci"], interface.get("vlan"), e)
+                #         self.logger.error("task={} get-VM: {}".format(task_id, error_text), exc_info=True)
+                #         task_warning_msg += error_text
+                #         # TODO Set error_msg at instance_nets instead of instance VMs
 
                 self.db.update_rows('instance_interfaces',
                                     UPDATE={"mac_address": interface.get("mac_address"),
 
                 self.db.update_rows('instance_interfaces',
                                     UPDATE={"mac_address": interface.get("mac_address"),
@@ -412,6 +461,8 @@ class vim_thread(threading.Thread):
                                             "vlan": interface.get("vlan")},
                                     WHERE={'uuid': task_interface["iface_id"]})
                 task_interface["vim_info"] = interface
                                             "vlan": interface.get("vlan")},
                                     WHERE={'uuid': task_interface["iface_id"]})
                 task_interface["vim_info"] = interface
+                # if sdn_net_id and interface.get("compute_node") and interface.get("pci"):
+                # # TODO Send message to task SDN to update
 
         # check and update task and instance_vms database
         vim_info_error_msg = None
 
         # check and update task and instance_vms database
         vim_info_error_msg = None
@@ -455,30 +506,29 @@ class vim_thread(threading.Thread):
         task_vim_info = task["extra"].get("vim_info")
         task_vim_status = task["extra"].get("vim_status")
         task_error_msg = task.get("error_msg")
         task_vim_info = task["extra"].get("vim_info")
         task_vim_status = task["extra"].get("vim_status")
         task_error_msg = task.get("error_msg")
-        task_sdn_net_id = task["extra"].get("sdn_net_id")
+        task_sdn_net_id = task["extra"].get("sdn_net_id")
 
         vim_info_status = vim_info["status"]
         vim_info_error_msg = vim_info.get("error_msg")
         # get ovim status
 
         vim_info_status = vim_info["status"]
         vim_info_error_msg = vim_info.get("error_msg")
         # get ovim status
-        if task_sdn_net_id:
-            try:
-                with self.db_lock:
-                    sdn_net = self.ovim.show_network(task_sdn_net_id)
-            except (ovimException, Exception) as e:
-                text_error = "ovimException getting network snd_net_id={}: {}".format(task_sdn_net_id, e)
-                self.logger.error("task={} get-net: {}".format(task_id, text_error), exc_info=True)
-                sdn_net = {"status": "ERROR", "last_error": text_error}
-            if sdn_net["status"] == "ERROR":
-                if not vim_info_error_msg:
-                    vim_info_error_msg = str(sdn_net.get("last_error"))
-                else:
-                    vim_info_error_msg = "VIM_ERROR: {} && SDN_ERROR: {}".format(
-                        self._format_vim_error_msg(vim_info_error_msg, 1024 // 2 - 14),
-                        self._format_vim_error_msg(sdn_net["last_error"], 1024 // 2 - 14))
-                vim_info_status = "ERROR"
-            elif sdn_net["status"] == "BUILD":
-                if vim_info_status == "ACTIVE":
-                    vim_info_status = "BUILD"
+        # if task_sdn_net_id:
+        #     try:
+        #         sdn_net = self.ovim.show_network(task_sdn_net_id)
+        #     except (ovimException, Exception) as e:
+        #         text_error = "ovimException getting network snd_net_id={}: {}".format(task_sdn_net_id, e)
+        #         self.logger.error("task={} get-net: {}".format(task_id, text_error), exc_info=True)
+        #         sdn_net = {"status": "ERROR", "last_error": text_error}
+        #     if sdn_net["status"] == "ERROR":
+        #         if not vim_info_error_msg:
+        #             vim_info_error_msg = str(sdn_net.get("last_error"))
+        #         else:
+        #             vim_info_error_msg = "VIM_ERROR: {} && SDN_ERROR: {}".format(
+        #                 self._format_vim_error_msg(vim_info_error_msg, 1024 // 2 - 14),
+        #                 self._format_vim_error_msg(sdn_net["last_error"], 1024 // 2 - 14))
+        #         vim_info_status = "ERROR"
+        #     elif sdn_net["status"] == "BUILD":
+        #         if vim_info_status == "ACTIVE":
+        #             vim_info_status = "BUILD"
 
         # update database
         if vim_info_error_msg:
 
         # update database
         if vim_info_error_msg:
@@ -530,7 +580,7 @@ class vim_thread(threading.Thread):
                     # Move this task to the time dependency is going to be modified plus 10 seconds.
                     self.db.update_rows("vim_wim_actions", modified_time=dependency_modified_at + 10,
                                         UPDATE={"worker": None},
                     # Move this task to the time dependency is going to be modified plus 10 seconds.
                     self.db.update_rows("vim_wim_actions", modified_time=dependency_modified_at + 10,
                                         UPDATE={"worker": None},
-                                        WHERE={"datacenter_vim_id": self.datacenter_tenant_id, "worker": self.my_id,
+                                        WHERE={self.target_k: self.target_v, "worker": self.my_id,
                                                "related": task["related"],
                                                })
                     # task["extra"]["tries"] = task["extra"].get("tries", 0) + 1
                                                "related": task["related"],
                                                })
                     # task["extra"]["tries"] = task["extra"].get("tries", 0) + 1
@@ -553,7 +603,7 @@ class vim_thread(threading.Thread):
             if task["status"] == "SUPERSEDED":
                 # not needed to do anything but update database with the new status
                 database_update = None
             if task["status"] == "SUPERSEDED":
                 # not needed to do anything but update database with the new status
                 database_update = None
-            elif not self.vim:
+            elif not self.vim and not self.sdnconnector:
                 task["status"] = "FAILED"
                 task["error_msg"] = self.error_status
                 database_update = {"status": "VIM_ERROR", "error_msg": task["error_msg"]}
                 task["status"] = "FAILED"
                 task["error_msg"] = self.error_status
                 database_update = {"status": "VIM_ERROR", "error_msg": task["error_msg"]}
@@ -595,6 +645,19 @@ class vim_thread(threading.Thread):
                     database_update = self.get_net(task)
                 else:
                     raise vimconn.vimconnException(self.name + "unknown task action {}".format(task["action"]))
                     database_update = self.get_net(task)
                 else:
                     raise vimconn.vimconnException(self.name + "unknown task action {}".format(task["action"]))
+            elif task["item"] == 'instance_wim_nets':
+                if task["status"] in ('BUILD', 'DONE') and task["action"] in ("FIND", "CREATE"):
+                    database_update = self.new_or_update_sdn_net(task)
+                    create_or_find = True
+                elif task["action"] == "CREATE":
+                    create_or_find = True
+                    database_update = self.new_or_update_sdn_net(task)
+                elif task["action"] == "DELETE":
+                    self.del_sdn_net(task)
+                elif task["action"] == "FIND":
+                    database_update = self.get_sdn_net(task)
+                else:
+                    raise vimconn.vimconnException(self.name + "unknown task action {}".format(task["action"]))
             elif task["item"] == 'instance_sfis':
                 if task["action"] == "CREATE":
                     create_or_find = True
             elif task["item"] == 'instance_sfis':
                 if task["action"] == "CREATE":
                     create_or_find = True
@@ -666,7 +729,7 @@ class vim_thread(threading.Thread):
                             "error_msg": task["error_msg"],
                             },
 
                             "error_msg": task["error_msg"],
                             },
 
-                    WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                    WHERE={self.target_k: self.target_v,
                            "worker": self.my_id,
                            "action": ["FIND", "CREATE"],
                            "related": task["related"],
                            "worker": self.my_id,
                            "action": ["FIND", "CREATE"],
                            "related": task["related"],
@@ -683,7 +746,7 @@ class vim_thread(threading.Thread):
             self.db.update_rows(
                 table="vim_wim_actions", modified_time=0,
                 UPDATE={"worker": None},
             self.db.update_rows(
                 table="vim_wim_actions", modified_time=0,
                 UPDATE={"worker": None},
-                WHERE={"datacenter_vim_id": self.datacenter_tenant_id,
+                WHERE={self.target_k: self.target_v,
                        "worker": self.my_id,
                        "related": task["related"],
                        })
                        "worker": self.my_id,
                        "related": task["related"],
                        })
@@ -723,7 +786,7 @@ class vim_thread(threading.Thread):
     def run(self):
         self.logger.debug("Starting")
         while True:
     def run(self):
         self.logger.debug("Starting")
         while True:
-            self.get_vimconnector()
+            self.get_vim_sdn_connector()
             self.logger.debug("Vimconnector loaded")
             reload_thread = False
 
             self.logger.debug("Vimconnector loaded")
             reload_thread = False
 
@@ -848,19 +911,18 @@ class vim_thread(threading.Thread):
             return instance_element_update
 
     def del_vm(self, task):
             return instance_element_update
 
     def del_vm(self, task):
-        task_id = task["instance_action_id"] + "." + str(task["task_index"])
+        task_id = task["instance_action_id"] + "." + str(task["task_index"])
         vm_vim_id = task["vim_id"]
         vm_vim_id = task["vim_id"]
-        interfaces = task["extra"].get("interfaces", ())
+        interfaces = task["extra"].get("interfaces", ())
         try:
         try:
-            for iface in interfaces.values():
-                if iface.get("sdn_port_id"):
-                    try:
-                        with self.db_lock:
-                            self.ovim.delete_port(iface["sdn_port_id"], idempotent=True)
-                    except ovimException as e:
-                        self.logger.error("task={} del-VM: ovimException when deleting external_port={}: {} ".format(
-                            task_id, iface["sdn_port_id"], e), exc_info=True)
-                        # TODO Set error_msg at instance_nets
+            # for iface in interfaces.values():
+            #     if iface.get("sdn_port_id"):
+            #         try:
+            #             self.ovim.delete_port(iface["sdn_port_id"], idempotent=True)
+            #         except ovimException as e:
+            #             self.logger.error("task={} del-VM: ovimException when deleting external_port={}: {} ".format(
+            #                 task_id, iface["sdn_port_id"], e), exc_info=True)
+            #             # TODO Set error_msg at instance_nets
 
             self.vim.delete_vminstance(vm_vim_id, task["extra"].get("created_items"))
             task["status"] = "FINISHED"  # with FINISHED instead of DONE it will not be refreshing
 
             self.vim.delete_vminstance(vm_vim_id, task["extra"].get("created_items"))
             task["status"] = "FINISHED"  # with FINISHED instead of DONE it will not be refreshing
@@ -929,7 +991,6 @@ class vim_thread(threading.Thread):
 
     def new_net(self, task):
         vim_net_id = None
 
     def new_net(self, task):
         vim_net_id = None
-        sdn_net_id = None
         task_id = task["instance_action_id"] + "." + str(task["task_index"])
         action_text = ""
         try:
         task_id = task["instance_action_id"] + "." + str(task["task_index"])
         action_text = ""
         try:
@@ -947,89 +1008,70 @@ class vim_thread(threading.Thread):
             action_text = "creating VIM"
             vim_net_id, created_items = self.vim.new_network(*params[0:3])
 
             action_text = "creating VIM"
             vim_net_id, created_items = self.vim.new_network(*params[0:3])
 
-            net_name = params[0]
-            net_type = params[1]
-            wim_account_name = None
-            if len(params) >= 4:
-                wim_account_name = params[3]
-
-            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, "region": self.vim["config"]["datacenter_id"]}
-
-                vim_net = self.vim.get_network(vim_net_id)
-                if vim_net.get('encapsulation') != 'vlan':
-                    raise vimconn.vimconnException(
-                        "net '{}' defined as type '{}' has not vlan encapsulation '{}'".format(
-                            net_name, net_type, vim_net['encapsulation']))
-                network["vlan"] = vim_net.get('segmentation_id')
-                action_text = "creating SDN"
-                with self.db_lock:
-                    sdn_net_id = self.ovim.new_network(network)
-
-                if wim_account_name and self.vim.config["wim_external_ports"]:
-                    # add external port to connect WIM. Try with compute node __WIM:wim_name and __WIM
-                    action_text = "attaching external port to ovim network"
-                    sdn_port_name = "external_port"
-                    sdn_port_data = {
-                        "compute_node": "__WIM:" + wim_account_name[0:58],
-                        "pci": None,
-                        "vlan": network["vlan"],
-                        "net_id": sdn_net_id,
-                        "region": self.vim["config"]["datacenter_id"],
-                        "name": sdn_port_name,
-                    }
-                    try:
-                        with self.db_lock:
-                            sdn_external_port_id = self.ovim.new_external_port(sdn_port_data)
-                    except ovimException:
-                        sdn_port_data["compute_node"] = "__WIM"
-                        with self.db_lock:
-                            sdn_external_port_id = self.ovim.new_external_port(sdn_port_data)
-                    self.logger.debug("Added sdn_external_port {} to sdn_network {}".format(sdn_external_port_id,
-                                                                                            sdn_net_id))
+            # net_name = params[0]
+            # net_type = params[1]
+            # wim_account_name = None
+            # if len(params) >= 4:
+            #     wim_account_name = params[3]
+
+            # TODO fix at nfvo adding external port
+            # if wim_account_name and self.vim.config["wim_external_ports"]:
+            #     # add external port to connect WIM. Try with compute node __WIM:wim_name and __WIM
+            #     action_text = "attaching external port to ovim network"
+            #     sdn_port_name = "external_port"
+            #     sdn_port_data = {
+            #         "compute_node": "__WIM:" + wim_account_name[0:58],
+            #         "pci": None,
+            #         "vlan": network["vlan"],
+            #         "net_id": sdn_net_id,
+            #         "region": self.vim["config"]["datacenter_id"],
+            #         "name": sdn_port_name,
+            #     }
+            #     try:
+            #         sdn_external_port_id = self.ovim.new_external_port(sdn_port_data)
+            #     except ovimException:
+            #         sdn_port_data["compute_node"] = "__WIM"
+            #         sdn_external_port_id = self.ovim.new_external_port(sdn_port_data)
+            #     self.logger.debug("Added sdn_external_port {} to sdn_network {}".format(sdn_external_port_id,
+            #                                                                             sdn_net_id))
             task["status"] = "DONE"
             task["extra"]["vim_info"] = {}
             task["status"] = "DONE"
             task["extra"]["vim_info"] = {}
-            task["extra"]["sdn_net_id"] = sdn_net_id
+            task["extra"]["sdn_net_id"] = sdn_net_id
             task["extra"]["vim_status"] = "BUILD"
             task["extra"]["created"] = True
             task["extra"]["created_items"] = created_items
             task["error_msg"] = None
             task["vim_id"] = vim_net_id
             task["extra"]["vim_status"] = "BUILD"
             task["extra"]["created"] = True
             task["extra"]["created_items"] = created_items
             task["error_msg"] = None
             task["vim_id"] = vim_net_id
-            instance_element_update = {"vim_net_id": vim_net_id, "sdn_net_id": sdn_net_id, "status": "BUILD",
+            instance_element_update = {"vim_net_id": vim_net_id, "status": "BUILD",
                                        "created": True, "error_msg": None}
             return instance_element_update
                                        "created": True, "error_msg": None}
             return instance_element_update
-        except (vimconn.vimconnException, ovimException) as e:
+        except vimconn.vimconnException as e:
             self.logger.error("task={} new-net: Error {}: {}".format(task_id, action_text, e))
             task["status"] = "FAILED"
             task["vim_id"] = vim_net_id
             task["error_msg"] = self._format_vim_error_msg(str(e))
             self.logger.error("task={} new-net: Error {}: {}".format(task_id, action_text, e))
             task["status"] = "FAILED"
             task["vim_id"] = vim_net_id
             task["error_msg"] = self._format_vim_error_msg(str(e))
-            task["extra"]["sdn_net_id"] = sdn_net_id
-            instance_element_update = {"vim_net_id": vim_net_id, "sdn_net_id": sdn_net_id, "status": "VIM_ERROR",
+            task["extra"]["sdn_net_id"] = sdn_net_id
+            instance_element_update = {"vim_net_id": vim_net_id, "status": "VIM_ERROR",
                                        "error_msg": task["error_msg"]}
             return instance_element_update
 
     def del_net(self, task):
         net_vim_id = task["vim_id"]
                                        "error_msg": task["error_msg"]}
             return instance_element_update
 
     def del_net(self, task):
         net_vim_id = task["vim_id"]
-        sdn_net_id = task["extra"].get("sdn_net_id")
+        sdn_net_id = task["extra"].get("sdn_net_id")
         try:
             if net_vim_id:
                 self.vim.delete_network(net_vim_id, task["extra"].get("created_items"))
         try:
             if net_vim_id:
                 self.vim.delete_network(net_vim_id, task["extra"].get("created_items"))
-            if sdn_net_id:
-                # Delete any attached port to this sdn network. There can be ports associated to this network in case
-                # it was manually done using 'openmano vim-net-sdn-attach'
-                with self.db_lock:
-                    port_list = self.ovim.get_ports(columns={'uuid'},
-                                                    filter={'name': 'external_port', 'net_id': sdn_net_id})
-                    for port in port_list:
-                        self.ovim.delete_port(port['uuid'], idempotent=True)
-                    self.ovim.delete_network(sdn_net_id, idempotent=True)
+            # if sdn_net_id:
+            #     # Delete any attached port to this sdn network. There can be ports associated to this network in case
+            #     # it was manually done using 'openmano vim-net-sdn-attach'
+            #     port_list = self.ovim.get_ports(columns={'uuid'},
+            #                                     filter={'name': 'external_port', 'net_id': sdn_net_id})
+            #     for port in port_list:
+            #         self.ovim.delete_port(port['uuid'], idempotent=True)
+            #     self.ovim.delete_network(sdn_net_id, idempotent=True)
             task["status"] = "FINISHED"  # with FINISHED instead of DONE it will not be refreshing
             task["error_msg"] = None
             return None
             task["status"] = "FINISHED"  # with FINISHED instead of DONE it will not be refreshing
             task["error_msg"] = None
             return None
-        except ovimException as e:
-            task["error_msg"] = self._format_vim_error_msg("ovimException obtaining and deleting external "
-                                                           "ports for net {}: {}".format(sdn_net_id, str(e)))
         except vimconn.vimconnException as e:
             task["error_msg"] = self._format_vim_error_msg(str(e))
             if isinstance(e, vimconn.vimconnNotFoundException):
         except vimconn.vimconnException as e:
             task["error_msg"] = self._format_vim_error_msg(str(e))
             if isinstance(e, vimconn.vimconnNotFoundException):
@@ -1039,6 +1081,160 @@ class vim_thread(threading.Thread):
         task["status"] = "FAILED"
         return None
 
         task["status"] = "FAILED"
         return None
 
+    def new_or_update_sdn_net(self, task):
+        wimconn_net_id = task["vim_id"]
+        created_items = task["extra"].get("created_items")
+        connected_ports = task["extra"].get("connected_ports", [])
+        new_connected_ports = []
+        last_update = task["extra"].get("last_update", 0)
+        sdn_status = "BUILD"
+        sdn_info = None
+
+        task_id = task["instance_action_id"] + "." + str(task["task_index"])
+        error_list = []
+        try:
+            # FIND
+            if task["extra"].get("find"):
+                wimconn_id = task["extra"]["find"][0]
+                try:
+                    instance_element_update = self.sdnconnector.get_connectivity_service_status(wimconn_id)
+                    wimconn_net_id = wimconn_id
+                    instance_element_update = {"wim_internal_id": wimconn_net_id, "created": False, "status": "BUILD",
+                                               "error_msg": None, }
+                    return instance_element_update
+                except Exception as e:
+                    if isinstance(e, SdnConnectorError) and e.http_error == HTTPStatus.NOT_FOUND.value:
+                        pass
+                    else: 
+                        self._proccess_sdn_exception(e)
+
+            params = task["params"]
+            # CREATE
+            # look for ports
+            sdn_ports = []
+            pending_ports = 0
+
+            ports = self.db.get_rows(FROM='instance_interfaces', WHERE={'instance_wim_net_id': task["item_id"]})
+            sdn_need_update = False
+            for port in ports:
+                # TODO. Do not connect if already done
+                if port.get("compute_node") and port.get("pci"):
+                    for map in self.port_mappings:
+                        if map.get("device_id") == port["compute_node"] and \
+                                map.get("device_interface_id") == port["pci"]:
+                            break
+                    else:
+                        if self.sdnconn_config.get("mapping_not_needed"):
+                            map = {
+                                "service_endpoint_id": "{}:{}".format(port["compute_node"], port["pci"]),
+                                "service_endpoint_encapsulation_info": {
+                                    "vlan": port["vlan"],
+                                    "mac": port["mac_address"],
+                                    "device_id": port["compute_node"],
+                                    "device_interface_id": port["pci"]
+                                }
+                            }
+                        else:
+                            map = None
+                            error_list.append("Port mapping not found for compute_node={} pci={}".format(
+                                port["compute_node"], port["pci"]))
+
+                    if map:
+                        if port["uuid"] not in connected_ports or port["modified_at"] > last_update:
+                            sdn_need_update = True
+                        new_connected_ports.append(port["uuid"])
+                        sdn_ports.append({
+                            "service_endpoint_id": map["service_endpoint_id"],
+                            "service_endpoint_encapsulation_type": "dot1q" if port["model"] == "SR-IOV" else None,
+                            "service_endpoint_encapsulation_info": {
+                                "vlan": port["vlan"],
+                                "mac": port["mac_address"],
+                                "device_id": map.get("device_id"),
+                                "device_interface_id": map.get("device_interface_id"),
+                                "switch_dpid": map.get("switch_dpid"),
+                                "switch_port": map.get("switch_port"),
+                                "service_mapping_info": map.get("service_mapping_info"),
+                            }
+                        })
+
+                else:
+                    pending_ports += 1
+            if pending_ports:
+                error_list.append("Waiting for getting interfaces location from VIM. Obtained '{}' of {}"
+                                  .format(len(ports)-pending_ports, len(ports)))
+            # if there are more ports to connect or they have been modified, call create/update
+            if sdn_need_update and len(sdn_ports) >= 2:
+                if not wimconn_net_id:
+                    if params[0] == "data":
+                        net_type = "ELAN"
+                    elif params[0] == "ptp":
+                        net_type = "ELINE"
+                    else:
+                        net_type = "L3"
+
+                    wimconn_net_id, created_items = self.sdnconnector.create_connectivity_service(net_type, sdn_ports)
+                else:
+                    created_items = self.sdnconnector.edit_connectivity_service(wimconn_net_id, conn_info=created_items,
+                                                                                connection_points=sdn_ports)
+                last_update = time.time()
+                connected_ports = new_connected_ports
+            elif wimconn_net_id:
+                try: 
+                    wim_status_dict = self.sdnconnector.get_connectivity_service_status(wimconn_net_id,
+                                                                                        conn_info=created_items)
+                    sdn_status = wim_status_dict["sdn_status"]
+                    if wim_status_dict.get("error_msg"):
+                        error_list.append(wim_status_dict.get("error_msg"))
+                    if wim_status_dict.get("sdn_info"):
+                        sdn_info = str(wim_status_dict.get("sdn_info"))
+                except Exception as e:
+                    self._proccess_sdn_exception(e)
+
+            task["status"] = "DONE"
+            task["extra"]["vim_info"] = {}
+            # task["extra"]["sdn_net_id"] = sdn_net_id
+            task["extra"]["vim_status"] = "BUILD"
+            task["extra"]["created"] = True
+            task["extra"]["created_items"] = created_items
+            task["extra"]["connected_ports"] = connected_ports
+            task["extra"]["last_update"] = last_update
+            task["error_msg"] = self._format_vim_error_msg(" ; ".join(error_list))
+            task["vim_id"] = wimconn_net_id
+            instance_element_update = {"wim_internal_id": wimconn_net_id, "status": sdn_status,
+                                       "created": True, "error_msg": task["error_msg"] or None}
+        except (vimconn.vimconnException, SdnConnectorError) as e:
+            self.logger.error("task={} new-sdn-net: Error: {}".format(task_id, e))
+            task["status"] = "FAILED"
+            task["vim_id"] = wimconn_net_id
+            task["error_msg"] = self._format_vim_error_msg(str(e))
+            # task["extra"]["sdn_net_id"] = sdn_net_id
+            instance_element_update = {"wim_internal_id": wimconn_net_id, "status": "WIM_ERROR",
+                                       "error_msg": task["error_msg"]}
+        if sdn_info:
+            instance_element_update["wim_info"] = sdn_info
+        return instance_element_update
+
+    def del_sdn_net(self, task):
+        wimconn_net_id = task["vim_id"]
+        try:
+            try:
+                if wimconn_net_id:
+                    self.sdnconnector.delete_connectivity_service(wimconn_net_id, task["extra"].get("created_items"))
+                task["status"] = "FINISHED"  # with FINISHED instead of DONE it will not be refreshing
+                task["error_msg"] = None
+                return None
+            except Exception as e:
+                self._proccess_sdn_exception(e)
+        except SdnConnectorError as e:
+            task["error_msg"] = self._format_vim_error_msg(str(e))
+            if e.http_code == HTTPStatus.NOT_FOUND.value:
+                # If not found mark as Done and fill error_msg
+                task["status"] = "FINISHED"  # with FINISHED instead of DONE it will not be refreshing
+                task["error_msg"] = None
+                return None
+        task["status"] = "FAILED"
+        return None
+
     # Service Function Instances
     def new_sfi(self, task):
         vim_sfi_id = None
     # Service Function Instances
     def new_sfi(self, task):
         vim_sfi_id = None
index e69de29..7284a2b 100644 (file)
@@ -0,0 +1,13 @@
+##
+# 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.
+##
index cf5b85a..6a232a4 100644 (file)
@@ -48,7 +48,7 @@ import logging
 from contextlib import contextmanager
 from itertools import groupby
 from operator import itemgetter
 from contextlib import contextmanager
 from itertools import groupby
 from operator import itemgetter
-from sys import exc_info
+from sys import exc_info
 from uuid import uuid4
 
 from ..utils import remove_none_items
 from uuid import uuid4
 
 from ..utils import remove_none_items
@@ -57,22 +57,33 @@ from .errors import (
     DbBaseException,
     NoWimConnectedToDatacenters,
     UnexpectedDatabaseError,
     DbBaseException,
     NoWimConnectedToDatacenters,
     UnexpectedDatabaseError,
-    WimAccountNotActive
+    WimAccountNotActive,
+    UndefinedWimConnector
 )
 from .wim_thread import WimThread
 )
 from .wim_thread import WimThread
+# from ..http_tools.errors import Bad_Request
+from pkg_resources import iter_entry_points
 
 
 class WimEngine(object):
     """Logic supporting the establishment of WAN links when NS spans across
     different datacenters.
     """
 
 
 class WimEngine(object):
     """Logic supporting the establishment of WAN links when NS spans across
     different datacenters.
     """
-    def __init__(self, persistence, logger=None, ovim=None):
+    def __init__(self, persistence, plugins, logger=None, ovim=None):
         self.persist = persistence
         self.persist = persistence
+        self.plugins = plugins if plugins is not None else {}
         self.logger = logger or logging.getLogger('openmano.wim.engine')
         self.threads = {}
         self.connectors = {}
         self.ovim = ovim
 
         self.logger = logger or logging.getLogger('openmano.wim.engine')
         self.threads = {}
         self.connectors = {}
         self.ovim = ovim
 
+    def _load_plugin(self, name, type="sdn"):
+        # type can be vim or sdn
+        for v in iter_entry_points('osm_ro{}.plugins'.format(type), name):
+            self.plugins[name] = v.load()
+        if name and name not in self.plugins:
+            raise UndefinedWimConnector(type, name)
+
     def create_wim(self, properties):
         """Create a new wim record according to the properties
 
     def create_wim(self, properties):
         """Create a new wim record according to the properties
 
@@ -88,6 +99,10 @@ class WimEngine(object):
         """
         port_mapping = ((properties.get('config', {}) or {})
                         .pop('wim_port_mapping', {}))
         """
         port_mapping = ((properties.get('config', {}) or {})
                         .pop('wim_port_mapping', {}))
+        plugin_name = "rosdn_" + properties["type"]
+        if plugin_name not in self.plugins:
+            self._load_plugin(plugin_name, type="sdn")
+
         uuid = self.persist.create_wim(properties)
 
         if port_mapping:
         uuid = self.persist.create_wim(properties)
 
         if port_mapping:
@@ -415,7 +430,7 @@ class WimEngine(object):
         past"""
         if instance_scenario_id:
             wan_links = self.persist.get_wan_links(
         past"""
         if instance_scenario_id:
             wan_links = self.persist.get_wan_links(
-                instance_scenario_id=instance_scenario_id)
+                instance_scenario_id=instance_scenario_id, sdn='false')
         return [self.delete_action(l) for l in wan_links]
 
     def incorporate_actions(self, wim_actions, instance_action):
         return [self.delete_action(l) for l in wan_links]
 
     def incorporate_actions(self, wim_actions, instance_action):
@@ -466,7 +481,7 @@ class WimEngine(object):
         """
         thread = None
         try:
         """
         thread = None
         try:
-            thread = WimThread(self.persist, wim_account, ovim=self.ovim)
+            thread = WimThread(self.persist, self.plugins, wim_account, ovim=self.ovim)
             self.threads[wim_account['uuid']] = thread
             thread.start()
         except:  # noqa
             self.threads[wim_account['uuid']] = thread
             thread.start()
         except:  # noqa
@@ -478,8 +493,16 @@ class WimEngine(object):
     def start_threads(self):
         """Start the threads responsible for processing WIM Actions"""
         accounts = self.persist.get_wim_accounts(error_if_none=False)
     def start_threads(self):
         """Start the threads responsible for processing WIM Actions"""
         accounts = self.persist.get_wim_accounts(error_if_none=False)
-        self.threads = remove_none_items(
-            {a['uuid']: self._spawn_thread(a) for a in accounts})
+        thread_dict = {}
+        for account in accounts:
+            try:
+                plugin_name = "rosdn_" + account["wim"]["type"]
+                if plugin_name not in self.plugins:
+                    self._load_plugin(plugin_name, type="sdn")
+                thread_dict[account["uuid"]] = self._spawn_thread(account)
+            except UndefinedWimConnector as e:
+                self.logger.error(e)
+        self.threads = remove_none_items(thread_dict)
 
     def stop_threads(self):
         """Stop the threads responsible for processing WIM Actions"""
 
     def stop_threads(self):
         """Stop the threads responsible for processing WIM Actions"""
index e8d4b63..8fd0a88 100644 (file)
@@ -103,12 +103,10 @@ class UndefinedAction(HttpMappedError):
 class UndefinedWimConnector(DbBaseException):
     """The connector class for the specified wim type is not implemented"""
 
 class UndefinedWimConnector(DbBaseException):
     """The connector class for the specified wim type is not implemented"""
 
-    def __init__(self, wim_type, module_name, location_reference):
-        super(UndefinedWimConnector, self).__init__(
-            ('{}: `{}`. Could not find module `{}` '
-             '(check if it is necessary to install a plugin)'
-             .format(self.__class__.__doc__, wim_type, module_name)),
-            http_code=Bad_Request)
+    def __init__(self, wim_type, module_name):
+        super(UndefinedWimConnector, self).__init__("Cannot load a module for {t} type '{n}'. The plugin 'osm_{n}' has"
+                                                    " not been registered".format(t=wim_type, n=module_name),
+                                                    http_code=Bad_Request)
 
 
 class WimAccountOverwrite(DbBaseException):
 
 
 class WimAccountOverwrite(DbBaseException):
index b66551c..6bbab35 100644 (file)
@@ -37,7 +37,7 @@ we need a replacement for it, that will throw an error every time we try to
 execute any action
 """
 import json
 execute any action
 """
 import json
-from .wimconn import WimConnectorError
+from .sdnconn import SdnConnectorError
 
 
 class FailingConnector(object):
 
 
 class FailingConnector(object):
@@ -51,32 +51,38 @@ class FailingConnector(object):
     def __init__(self, error_msg):
         self.error_msg = error_msg
 
     def __init__(self, error_msg):
         self.error_msg = error_msg
 
+    def __call__(self, wim, wim_account, config=None, logger=None):
+        return self
+
+    def vimconnector(self, *args, **kwargs):
+        raise Exception(self.error_msg)
+
     def check_credentials(self):
     def check_credentials(self):
-        raise WimConnectorError('Impossible to use WIM:\n' + self.error_msg)
+        raise SdnConnectorError('Impossible to use WIM:\n' + self.error_msg)
 
     def get_connectivity_service_status(self, service_uuid, _conn_info=None):
 
     def get_connectivity_service_status(self, service_uuid, _conn_info=None):
-        raise WimConnectorError('Impossible to retrieve status for {}\n\n{}'
+        raise SdnConnectorError('Impossible to retrieve status for {}\n\n{}'
                                 .format(service_uuid, self.error_msg))
 
     def create_connectivity_service(self, service_uuid, *args, **kwargs):
                                 .format(service_uuid, self.error_msg))
 
     def create_connectivity_service(self, service_uuid, *args, **kwargs):
-        raise WimConnectorError('Impossible to connect {}.\n{}\n{}\n{}'
+        raise SdnConnectorError('Impossible to connect {}.\n{}\n{}\n{}'
                                 .format(service_uuid, self.error_msg,
                                         json.dumps(args, indent=4),
                                         json.dumps(kwargs, indent=4)))
 
     def delete_connectivity_service(self, service_uuid, _conn_info=None):
                                 .format(service_uuid, self.error_msg,
                                         json.dumps(args, indent=4),
                                         json.dumps(kwargs, indent=4)))
 
     def delete_connectivity_service(self, service_uuid, _conn_info=None):
-        raise WimConnectorError('Impossible to disconnect {}\n\n{}'
+        raise SdnConnectorError('Impossible to disconnect {}\n\n{}'
                                 .format(service_uuid, self.error_msg))
 
     def edit_connectivity_service(self, service_uuid, *args, **kwargs):
                                 .format(service_uuid, self.error_msg))
 
     def edit_connectivity_service(self, service_uuid, *args, **kwargs):
-        raise WimConnectorError('Impossible to change connection {}.\n{}\n'
+        raise SdnConnectorError('Impossible to change connection {}.\n{}\n'
                                 '{}\n{}'
                                 .format(service_uuid, self.error_msg,
                                         json.dumps(args, indent=4),
                                         json.dumps(kwargs, indent=4)))
 
     def clear_all_connectivity_services(self):
                                 '{}\n{}'
                                 .format(service_uuid, self.error_msg,
                                         json.dumps(args, indent=4),
                                         json.dumps(kwargs, indent=4)))
 
     def clear_all_connectivity_services(self):
-        raise WimConnectorError('Impossible to use WIM:\n' + self.error_msg)
+        raise SdnConnectorError('Impossible to use WIM:\n' + self.error_msg)
 
     def get_all_active_connectivity_services(self):
 
     def get_all_active_connectivity_services(self):
-        raise WimConnectorError('Impossible to use WIM:\n' + self.error_msg)
+        raise SdnConnectorError('Impossible to use WIM:\n' + self.error_msg)
diff --git a/RO/osm_ro/wim/openflow_conn.py b/RO/osm_ro/wim/openflow_conn.py
new file mode 100644 (file)
index 0000000..7d029f7
--- /dev/null
@@ -0,0 +1,464 @@
+##
+# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
+# 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.
+#
+##
+import logging
+from http import HTTPStatus
+from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
+from uuid import uuid4
+
+"""
+Implement an Abstract class 'OpenflowConn' and an engine 'SdnConnectorOpenFlow' used for base class for SDN plugings
+that implements a pro-active opeflow rules.
+"""
+
+__author__ = "Alfonso Tierno"
+__date__ = "2019-11-11"
+
+
+class OpenflowConnException(Exception):
+    """Common and base class Exception for all vimconnector exceptions"""
+    def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value):
+        Exception.__init__(self, message)
+        self.http_code = http_code
+
+
+class OpenflowConnConnectionException(OpenflowConnException):
+    """Connectivity error with the VIM"""
+    def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
+        OpenflowConnException.__init__(self, message, http_code)
+
+
+class OpenflowConnUnexpectedResponse(OpenflowConnException):
+    """Get an wrong response from VIM"""
+    def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value):
+        OpenflowConnException.__init__(self, message, http_code)
+
+
+class OpenflowConnAuthException(OpenflowConnException):
+    """Invalid credentials or authorization to perform this action over the VIM"""
+    def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED.value):
+        OpenflowConnException.__init__(self, message, http_code)
+
+
+class OpenflowConnNotFoundException(OpenflowConnException):
+    """The item is not found at VIM"""
+    def __init__(self, message, http_code=HTTPStatus.NOT_FOUND.value):
+        OpenflowConnException.__init__(self, message, http_code)
+
+
+class OpenflowConnConflictException(OpenflowConnException):
+    """There is a conflict, e.g. more item found than one"""
+    def __init__(self, message, http_code=HTTPStatus.CONFLICT.value):
+        OpenflowConnException.__init__(self, message, http_code)
+
+
+class OpenflowConnNotSupportedException(OpenflowConnException):
+    """The request is not supported by connector"""
+    def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
+        OpenflowConnException.__init__(self, message, http_code)
+
+
+class OpenflowConnNotImplemented(OpenflowConnException):
+    """The method is not implemented by the connected"""
+    def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED.value):
+        OpenflowConnException.__init__(self, message, http_code)
+
+
+class OpenflowConn:
+    """
+    Openflow controller connector abstract implementeation.
+    """
+    def __init__(self, params):
+        self.name = "openflow_conector"
+        self.pp2ofi = {}  # From Physical Port to OpenFlow Index
+        self.ofi2pp = {}  # From OpenFlow Index to Physical Port
+        self.logger = logging.getLogger('openflow_conn')
+        self.logger.setLevel(getattr(logging, params.get("of_debug", "ERROR")))
+
+    def get_of_switches(self):
+        """"
+        Obtain a a list of switches or DPID detected by this controller
+        :return: list length, and a list where each element a tuple pair (DPID, IP address), text_error: if fails
+        """
+        raise OpenflowConnNotImplemented("Should have implemented this")
+
+    def obtain_port_correspondence(self):
+        """
+        Obtain the correspondence between physical and openflow port names
+        :return: dictionary: with physical name as key, openflow name as value, error_text: if fails
+        """
+        raise OpenflowConnNotImplemented("Should have implemented this")
+
+    def get_of_rules(self, translate_of_ports=True):
+        """
+        Obtain the rules inserted at openflow controller
+        :param translate_of_ports: if True it translates ports from openflow index to physical switch name
+        :return: list where each item is a  dictionary with the following content:
+                    priority: rule priority
+                    priority: rule priority
+                    name:         rule name (present also as the master dict key)
+                    ingress_port: match input port of the rule
+                    dst_mac:      match destination mac address of the rule, can be missing or None if not apply
+                    vlan_id:      match vlan tag of the rule, can be missing or None if not apply
+                    actions:      list of actions, composed by a pair tuples:
+                        (vlan, None/int): for stripping/setting a vlan tag
+                        (out, port):      send to this port
+                    switch:       DPID, all
+                 text_error if fails
+        """
+        raise OpenflowConnNotImplemented("Should have implemented this")
+
+    def del_flow(self, flow_name):
+        """
+        Delete all existing rules
+        :param flow_name: flow_name, this is the rule name
+        :return: None if ok, text_error if fails
+        """
+        raise OpenflowConnNotImplemented("Should have implemented this")
+
+    def new_flow(self, data):
+        """
+        Insert a new static rule
+        :param data: dictionary with the following content:
+                priority:     rule priority
+                name:         rule name
+                ingress_port: match input port of the rule
+                dst_mac:      match destination mac address of the rule, missing or None if not apply
+                vlan_id:      match vlan tag of the rule, missing or None if not apply
+                actions:      list of actions, composed by a pair tuples with these posibilities:
+                    ('vlan', None/int): for stripping/setting a vlan tag
+                    ('out', port):      send to this port
+        :return: None if ok, text_error if fails
+        """
+        raise OpenflowConnNotImplemented("Should have implemented this")
+
+    def clear_all_flows(self):
+        """"
+        Delete all existing rules
+        :return: None if ok, text_error if fails
+        """
+        raise OpenflowConnNotImplemented("Should have implemented this")
+
+
+class SdnConnectorOpenFlow(SdnConnectorBase):
+    """
+    This class is the base engine of SDN plugins base on openflow rules
+    """
+    flow_fields = ('priority', 'vlan', 'ingress_port', 'actions', 'dst_mac', 'src_mac', 'net_id')
+
+    def __init__(self, wim, wim_account, config=None, logger=None, of_connector=None):
+        self.logger = logger or logging.getLogger('openmano.sdnconn.openflow')
+        self.of_connector = of_connector
+        self.of_controller_nets_with_same_vlan = config.get("of_controller_nets_with_same_vlan", False)
+
+    def check_credentials(self):
+        try:
+            self.openflow_conn.obtain_port_correspondence()
+        except OpenflowConnException as e:
+            raise SdnConnectorError(e, http_code=e.http_code)
+
+    def get_connectivity_service_status(self, service_uuid, conn_info=None):
+        conn_info = conn_info or {}
+        return {
+            "sdn_status": conn_info.get("status", "ERROR"),
+            "error_msg": conn_info.get("error_msg", "Variable conn_info not provided"),
+        }
+        # TODO check rules connectirng to of_connector
+
+    def create_connectivity_service(self, service_type, connection_points, **kwargs):
+        net_id = str(uuid4())
+        ports = []
+        for cp in connection_points:
+            port = {
+                "uuid": cp["service_endpoint_id"],
+                "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
+                "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
+                "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get("switch_port"),
+            }
+            ports.append(port)
+        try:
+            created_items = self._set_openflow_rules(service_type, net_id, ports, created_items=None)
+            return net_id, created_items
+        except (SdnConnectorError, OpenflowConnException) as e:
+            raise SdnConnectorError(e, http_code=e.http_code)
+
+    def delete_connectivity_service(self, service_uuid, conn_info=None):
+        try:
+            service_type = "ELAN"
+            ports = []
+            self._set_openflow_rules(service_type, service_uuid, ports, created_items=conn_info)
+            return None
+        except (SdnConnectorError, OpenflowConnException) as e:
+            raise SdnConnectorError(e, http_code=e.http_code)
+
+    def edit_connectivity_service(self, service_uuid, conn_info=None, connection_points=None, **kwargs):
+        ports = []
+        for cp in connection_points:
+            port = {
+                "uuid": cp["service_endpoint_id"],
+                "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
+                "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
+                "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get("switch_port"),
+            }
+            ports.append(port)
+        service_type = "ELAN"  # TODO. Store at conn_info for later use
+        try:
+            created_items = self._set_openflow_rules(service_type, service_uuid, ports, created_items=conn_info)
+            return created_items
+        except (SdnConnectorError, OpenflowConnException) as e:
+            raise SdnConnectorError(e, http_code=e.http_code)
+
+    def clear_all_connectivity_services(self):
+        """Delete all WAN Links corresponding to a WIM"""
+        pass
+
+    def get_all_active_connectivity_services(self):
+        """Provide information about all active connections provisioned by a
+        WIM
+        """
+        pass
+
+    def _set_openflow_rules(self, net_type, net_id, ports, created_items=None):
+        ifaces_nb = len(ports)
+        if not created_items:
+            created_items = {"status": None, "error_msg": None, "installed_rules_ids": []}
+        rules_to_delete = created_items.get("installed_rules_ids") or []
+        new_installed_rules_ids = []
+        error_list = []
+
+        try:
+            step = "Checking ports and network type compatibility"
+            if ifaces_nb < 2:
+                pass
+            elif net_type == 'ELINE':
+                if ifaces_nb > 2:
+                    raise SdnConnectorError("'ELINE' type network cannot connect {} interfaces, only 2".format(
+                        ifaces_nb))
+            elif net_type == 'ELAN':
+                if ifaces_nb > 2 and self.of_controller_nets_with_same_vlan:
+                    # check all ports are VLAN (tagged) or none
+                    vlan_tags = []
+                    for port in ports:
+                        if port["vlan"] not in vlan_tags:
+                            vlan_tags.append(port["vlan"])
+                    if len(vlan_tags) > 1:
+                        raise SdnConnectorError("This pluging cannot connect ports with diferent VLAN tags when flag "
+                                                "'of_controller_nets_with_same_vlan' is active")
+            else:
+                raise SdnConnectorError('Only ELINE or ELAN network types are supported for openflow')
+
+            # Get the existing flows at openflow controller
+            step = "Getting installed openflow rules"
+            existing_flows = self.of_connector.get_of_rules()
+            existing_flows_ids = [flow["name"] for flow in existing_flows]
+
+            # calculate new flows to be inserted
+            step = "Compute needed openflow rules"
+            new_flows = self._compute_net_flows(net_id, ports)
+
+            name_index = 0
+            for flow in new_flows:
+                # 1 check if an equal flow is already present
+                index = self._check_flow_already_present(flow, existing_flows)
+                if index >= 0:
+                    flow_id = existing_flows[index]["name"]
+                    self.logger.debug("Skipping already present flow %s", str(flow))
+                else:
+                    # 2 look for a non used name
+                    flow_name = flow["net_id"] + "." + str(name_index)
+                    while flow_name in existing_flows_ids:
+                        name_index += 1
+                        flow_name = flow["net_id"] + "." + str(name_index)
+                    flow['name'] = flow_name
+                    # 3 insert at openflow
+                    try:
+                        self.of_connector.new_flow(flow)
+                        flow_id = flow["name"]
+                        existing_flows_ids.append(flow_id)
+                    except OpenflowConnException as e:
+                        flow_id = None
+                        error_list.append("Cannot create rule for ingress_port={}, dst_mac={}: {}"
+                                          .format(flow["ingress_port"], flow["dst_mac"], e))
+
+                # 4 insert at database
+                if flow_id:
+                    new_installed_rules_ids.append(flow_id)
+                    if flow_id in rules_to_delete:
+                        rules_to_delete.remove(flow_id)
+
+            # delete not needed old flows from openflow
+            for flow_id in rules_to_delete:
+                # Delete flow
+                try:
+                    self.of_connector.del_flow(flow_id)
+                except OpenflowConnNotFoundException:
+                    pass
+                except OpenflowConnException as e:
+                    error_text = "Cannot remove rule '{}': {}".format(flow['name'], e)
+                    error_list.append(error_text)
+                    self.logger.error(error_text)
+            created_items["installed_rules_ids"] = new_installed_rules_ids
+            if error_list:
+                created_items["error_msg"] = ";".join(error_list)[:1000]
+                created_items["error_msg"] = "ERROR"
+            else:
+                created_items["error_msg"] = None
+                created_items["status"] = "ACTIVE"
+            return created_items
+        except (SdnConnectorError, OpenflowConnException) as e:
+            raise SdnConnectorError("Error while {}: {}".format(step, e)) from e
+        except Exception as e:
+            self.logger.critical(error_text, exc_info=True)
+            raise SdnConnectorError("Error while {}: {}".format(step, e))
+
+    def _compute_net_flows(self, net_id, ports):
+        new_flows = []
+        new_broadcast_flows = {}
+        nb_ports = len(ports)
+
+        # Check switch_port information is right
+        for port in ports:
+            nb_ports += 1
+            if str(port['switch_port']) not in self.of_connector.pp2ofi:
+                raise SdnConnectorError("switch port name '{}' is not valid for the openflow controller".
+                                        format(port['switch_port']))
+        priority = 1000  # 1100
+
+        for src_port in ports:
+            # if src_port.get("groups")
+            vlan_in = src_port['vlan']
+
+            # BROADCAST:
+            broadcast_key = src_port['uuid'] + "." + str(vlan_in)
+            if broadcast_key in new_broadcast_flows:
+                flow_broadcast = new_broadcast_flows[broadcast_key]
+            else:
+                flow_broadcast = {'priority': priority,
+                                  'net_id': net_id,
+                                  'dst_mac': 'ff:ff:ff:ff:ff:ff',
+                                  "ingress_port": str(src_port['switch_port']),
+                                  'vlan_id': vlan_in,
+                                  'actions': []
+                                  }
+                new_broadcast_flows[broadcast_key] = flow_broadcast
+                if vlan_in is not None:
+                    flow_broadcast['vlan_id'] = str(vlan_in)
+
+            for dst_port in ports:
+                vlan_out = dst_port['vlan']
+                if src_port['switch_port'] == dst_port['switch_port'] and vlan_in == vlan_out:
+                    continue
+                flow = {
+                    "priority": priority,
+                    'net_id': net_id,
+                    "ingress_port": str(src_port['switch_port']),
+                    'vlan_id': vlan_in,
+                    'actions': []
+                }
+                # allow that one port have no mac
+                if dst_port['mac'] is None or nb_ports == 2:  # point to point or nets with 2 elements
+                    flow['priority'] = priority - 5  # less priority
+                else:
+                    flow['dst_mac'] = str(dst_port['mac'])
+
+                if vlan_out is None:
+                    if vlan_in:
+                        flow['actions'].append(('vlan', None))
+                else:
+                    flow['actions'].append(('vlan', vlan_out))
+                flow['actions'].append(('out', str(dst_port['switch_port'])))
+
+                if self._check_flow_already_present(flow, new_flows) >= 0:
+                    self.logger.debug("Skipping repeated flow '%s'", str(flow))
+                    continue
+
+                new_flows.append(flow)
+
+                # BROADCAST:
+                if nb_ports <= 2:  # point to multipoint or nets with more than 2 elements
+                    continue
+                out = (vlan_out, str(dst_port['switch_port']))
+                if out not in flow_broadcast['actions']:
+                    flow_broadcast['actions'].append(out)
+
+        # BROADCAST
+        for flow_broadcast in new_broadcast_flows.values():
+            if len(flow_broadcast['actions']) == 0:
+                continue  # nothing to do, skip
+            flow_broadcast['actions'].sort()
+            if 'vlan_id' in flow_broadcast:
+                previous_vlan = 0  # indicates that a packet contains a vlan, and the vlan
+            else:
+                previous_vlan = None
+            final_actions = []
+            action_number = 0
+            for action in flow_broadcast['actions']:
+                if action[0] != previous_vlan:
+                    final_actions.append(('vlan', action[0]))
+                    previous_vlan = action[0]
+                    if self.of_controller_nets_with_same_vlan and action_number:
+                        raise SdnConnectorError("Cannot interconnect different vlan tags in a network when flag "
+                                                "'of_controller_nets_with_same_vlan' is True.")
+                    action_number += 1
+                final_actions.append(('out', action[1]))
+            flow_broadcast['actions'] = final_actions
+
+            if self._check_flow_already_present(flow_broadcast, new_flows) >= 0:
+                self.logger.debug("Skipping repeated flow '%s'", str(flow_broadcast))
+                continue
+
+            new_flows.append(flow_broadcast)
+
+        # UNIFY openflow rules with the same input port and vlan and the same output actions
+        # These flows differ at the dst_mac; and they are unified by not filtering by dst_mac
+        # this can happen if there is only two ports. It is converted to a point to point connection
+        flow_dict = {}  # use as key vlan_id+ingress_port and as value the list of flows matching these values
+        for flow in new_flows:
+            key = str(flow.get("vlan_id")) + ":" + flow["ingress_port"]
+            if key in flow_dict:
+                flow_dict[key].append(flow)
+            else:
+                flow_dict[key] = [flow]
+        new_flows2 = []
+        for flow_list in flow_dict.values():
+            convert2ptp = False
+            if len(flow_list) >= 2:
+                convert2ptp = True
+                for f in flow_list:
+                    if f['actions'] != flow_list[0]['actions']:
+                        convert2ptp = False
+                        break
+            if convert2ptp:  # add only one unified rule without dst_mac
+                self.logger.debug("Convert flow rules to NON mac dst_address " + str(flow_list))
+                flow_list[0].pop('dst_mac')
+                flow_list[0]["priority"] -= 5
+                new_flows2.append(flow_list[0])
+            else:  # add all the rules
+                new_flows2 += flow_list
+        return new_flows2
+
+    def _check_flow_already_present(self, new_flow, flow_list):
+        '''check if the same flow is already present in the flow list
+        The flow is repeated if all the fields, apart from name, are equal
+        Return the index of matching flow, -1 if not match'''
+        for index, flow in enumerate(flow_list):
+            for f in self.flow_fields:
+                if flow.get(f) != new_flow.get(f):
+                    break
+            else:
+                return index
+        return -1
index f0f1ac3..32a46b3 100644 (file)
@@ -44,7 +44,7 @@ from contextlib import contextmanager
 from hashlib import sha1
 from itertools import groupby
 from operator import itemgetter
 from hashlib import sha1
 from itertools import groupby
 from operator import itemgetter
-from sys import exc_info
+from sys import exc_info
 # from time import time
 from uuid import uuid1 as generate_uuid
 
 # from time import time
 from uuid import uuid1 as generate_uuid
 
@@ -362,6 +362,7 @@ class WimPersistence(object):
     def get_wim_accounts(self, **kwargs):
         """Retrieve all the accounts from the database"""
         kwargs.setdefault('postprocess', _postprocess_wim_account)
     def get_wim_accounts(self, **kwargs):
         """Retrieve all the accounts from the database"""
         kwargs.setdefault('postprocess', _postprocess_wim_account)
+        kwargs.setdefault('WHERE', {"sdn": "false"})
         return self.query(FROM=_WIM_ACCOUNT_JOIN, **kwargs)
 
     def get_wim_account(self, uuid_or_name, **kwargs):
         return self.query(FROM=_WIM_ACCOUNT_JOIN, **kwargs)
 
     def get_wim_account(self, uuid_or_name, **kwargs):
@@ -523,7 +524,7 @@ class WimPersistence(object):
             self.logger.exception(old_exception)
             ex = InvalidParameters(
                 "The mapping must contain the "
             self.logger.exception(old_exception)
             ex = InvalidParameters(
                 "The mapping must contain the "
-                "'pop_switch_dpid', 'pop_switch_port',  and "
+                "'device_id', 'device_interface_id',  and "
                 "wan_service_mapping_info: "
                 "('wan_switch_dpid' and 'wan_switch_port') or "
                 "'wan_service_endpoint_id}'")
                 "wan_service_mapping_info: "
                 "('wan_switch_dpid' and 'wan_switch_port') or "
                 "'wan_service_endpoint_id}'")
@@ -727,7 +728,7 @@ class WimPersistence(object):
         kwargs.setdefault('error_if_none', False)
 
         criteria_fields = ('uuid', 'instance_scenario_id', 'sce_net_id',
         kwargs.setdefault('error_if_none', False)
 
         criteria_fields = ('uuid', 'instance_scenario_id', 'sce_net_id',
-                           'wim_id', 'wim_account_id')
+                           'wim_id', 'wim_account_id', 'sdn')
         criteria = remove_none_items(filter_dict_keys(kwargs, criteria_fields))
         kwargs = filter_out_dict_keys(kwargs, criteria_fields)
 
         criteria = remove_none_items(filter_dict_keys(kwargs, criteria_fields))
         kwargs = filter_out_dict_keys(kwargs, criteria_fields)
 
index 101bcb1..8f9653b 100644 (file)
@@ -69,36 +69,21 @@ wim_port_mapping_desc = {
                 "items": {
                     "type": "object",
                     "properties": {
                 "items": {
                     "type": "object",
                     "properties": {
-                        "pop_switch_dpid": dpid_type,
-                        "pop_switch_port": port_type,
-                        "wan_service_endpoint_id": name_schema,
-                        "wan_service_mapping_info": {
+                        "device_id": nameshort_schema,
+                        "device_interface_id": nameshort_schema,
+                        "service_endpoint_id": name_schema,
+                        "switch_dpid": dpid_type,
+                        "switch_port": port_type,
+                        "service_mapping_info": {
                             "type": "object",
                             "properties": {
                                 "mapping_type": name_schema,
                             "type": "object",
                             "properties": {
                                 "mapping_type": name_schema,
-                                "wan_switch_dpid": dpid_type,
-                                "wan_switch_port": port_type
                             },
                             "additionalProperties": True,
                             "required": ["mapping_type"]
                         }
                     },
                             },
                             "additionalProperties": True,
                             "required": ["mapping_type"]
                         }
                     },
-                    "anyOf": [
-                        {
-                            "required": [
-                                "pop_switch_dpid",
-                                "pop_switch_port",
-                                "wan_service_endpoint_id"
-                            ]
-                        },
-                        {
-                            "required": [
-                                "pop_switch_dpid",
-                                "pop_switch_port",
-                                "wan_service_mapping_info"
-                            ]
-                        }
-                    ]
+                    "required": ["service_endpoint_id"]
                 }
             }
         },
                 }
             }
         },
@@ -111,7 +96,7 @@ wim_schema_properties = {
     "description": description_schema,
     "type": {
         "type": "string",
     "description": description_schema,
     "type": {
         "type": "string",
-        "enum": ["tapi", "onos", "odl", "dynpac", "fake"]
+        "enum": ["tapi", "onos", "odl", "dynpac", "fake"]
     },
     "wim_url": description_schema,
     "config": {
     },
     "wim_url": description_schema,
     "config": {
diff --git a/RO/osm_ro/wim/sdnconn.py b/RO/osm_ro/wim/sdnconn.py
new file mode 100644 (file)
index 0000000..46649ce
--- /dev/null
@@ -0,0 +1,238 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 University of Bristol - High Performance Networks Research
+# Group
+# All Rights Reserved.
+#
+# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
+# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
+#
+# 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: <highperformance-networks@bristol.ac.uk>
+#
+# Neither the name of the University of Bristol nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# This work has been performed in the context of DCMS UK 5G Testbeds
+# & Trials Programme and in the framework of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 and 5G-PPP programmes.
+##
+"""The SDN connector is responsible for establishing both wide area network connectivity (WIM)
+and intranet SDN connectivity.
+
+It receives information from ports to be connected .
+"""
+import logging
+
+from ..http_tools.errors import HttpMappedError
+
+
+class SdnConnectorError(HttpMappedError):
+    """Base Exception for all connector related errors
+        provide the parameter 'http_code' (int) with the error code:
+            Bad_Request = 400
+            Unauthorized = 401  (e.g. credentials are not valid)
+            Not_Found = 404    (e.g. try to edit or delete a non existing connectivity service)
+            Forbidden = 403
+            Method_Not_Allowed = 405
+            Not_Acceptable = 406
+            Request_Timeout = 408  (e.g timeout reaching server, or cannot reach the server)
+            Conflict = 409
+            Service_Unavailable = 503
+            Internal_Server_Error = 500
+    """
+
+
+class SdnConnectorBase(object):
+    """Abstract base class for all the SDN connectors
+
+    Arguments:
+        wim (dict): WIM record, as stored in the database
+        wim_account (dict): WIM account record, as stored in the database
+        config
+    The arguments of the constructor are converted to object attributes.
+    An extra property, ``service_endpoint_mapping`` is created from ``config``.
+    """
+    def __init__(self, wim, wim_account, config=None, logger=None):
+        """
+
+        :param wim: (dict). Contains among others 'wim_url'
+        :param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
+            'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
+        :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
+            'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
+            'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
+                KEY                    meaning for WIM                 meaning for SDN assist
+                --------                --------                    --------
+                device_id                      pop_switch_dpid                 compute_id
+                device_interface_id            pop_switch_port                 compute_pci_address
+                service_endpoint_id        wan_service_endpoint_id     SDN_service_endpoint_id
+                service_mapping_info   wan_service_mapping_info    SDN_service_mapping_info
+                    contains extra information if needed. Text in Yaml format
+                switch_dpid                    wan_switch_dpid                 SDN_switch_dpid
+                switch_port                    wan_switch_port                 SDN_switch_port
+                datacenter_id           vim_account                 vim_account
+                id: (internal, do not use)
+                wim_id: (internal, do not use)
+        :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
+        """
+        self.logger = logger or logging.getLogger('openmano.sdn.sdnconn')
+
+        self.wim = wim
+        self.wim_account = wim_account
+        self.config = config or {}
+        self.service_endpoint_mapping = (
+            self.config.get('service_endpoint_mapping', []))
+
+    def check_credentials(self):
+        """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
+            user (wim_account.user), and password (wim_account.password)
+
+        Raises:
+            SdnConnectorError: Issues regarding authorization, access to
+                external URLs, etc are detected.
+        """
+        raise NotImplementedError
+
+    def get_connectivity_service_status(self, service_uuid, conn_info=None):
+        """Monitor the status of the connectivity service established
+
+        Arguments:
+            service_uuid (str): UUID of the connectivity service
+            conn_info (dict or None): Information returned by the connector
+                during the service creation/edition and subsequently stored in
+                the database.
+
+        Returns:
+            dict: JSON/YAML-serializable dict that contains a mandatory key
+                ``sdn_status`` associated with one of the following values::
+
+                    {'sdn_status': 'ACTIVE'}
+                        # The service is up and running.
+
+                    {'sdn_status': 'INACTIVE'}
+                        # The service was created, but the connector
+                        # cannot determine yet if connectivity exists
+                        # (ideally, the caller needs to wait and check again).
+
+                    {'sdn_status': 'DOWN'}
+                        # Connection was previously established,
+                        # but an error/failure was detected.
+
+                    {'sdn_status': 'ERROR'}
+                        # An error occurred when trying to create the service/
+                        # establish the connectivity.
+
+                    {'sdn_status': 'BUILD'}
+                        # Still trying to create the service, the caller
+                        # needs to wait and check again.
+
+                Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
+                keys can be used to provide additional status explanation or
+                new information available for the connectivity service.
+        """
+        raise NotImplementedError
+
+    def create_connectivity_service(self, service_type, connection_points, **kwargs):
+        """
+        Stablish SDN/WAN connectivity between the endpoints
+        :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
+        :param connection_points:  (list): each point corresponds to
+            an entry point to be connected. For WIM: from the DC to the transport network.
+            For SDN: Compute/PCI to the transport network. One
+            connection point serves to identify the specific access and
+            some other service parameters, such as encapsulation type.
+            Each item of the list is a dict with:
+                "service_endpoint_id": (str)(uuid)  Same meaning that for 'service_endpoint_mapping' (see __init__)
+                    In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
+                    it will contain the string "device_id:device_interface_id"
+                "service_endpoint_encapsulation_type": None, "dot1q", ...
+                "service_endpoint_encapsulation_info": (dict) with:
+                    "vlan": ..., (int, present if encapsulation is dot1q)
+                    "vni": ... (int, present if encapsulation is vxlan),
+                    "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
+                    "mac": ...
+                    "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
+                    "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
+                    "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
+                    "swith_port": ... present if mapping has been found for this device_id,device_interface_id
+                    "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
+        :param kwargs: For future versions:
+            bandwidth (int): value in kilobytes
+            latency (int): value in milliseconds
+            Other QoS might be passed as keyword arguments.
+        :return: tuple: ``(service_id, conn_info)`` containing:
+            - *service_uuid* (str): UUID of the established connectivity service
+            - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
+                This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
+                **MUST** be JSON/YAML-serializable (plain data structures).
+        :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
+            Provide the parameter http_code
+        """
+        raise NotImplementedError
+
+    def delete_connectivity_service(self, service_uuid, conn_info=None):
+        """
+        Disconnect multi-site endpoints previously connected
+
+        :param service_uuid: The one returned by create_connectivity_service
+        :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
+            if they do not return None
+        :return: None
+        :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
+        """
+        raise NotImplementedError
+
+    def edit_connectivity_service(self, service_uuid, conn_info=None, connection_points=None, **kwargs):
+        """ Change an existing connectivity service.
+
+        This method's arguments and return value follow the same convention as
+        :meth:`~.create_connectivity_service`.
+
+        :param service_uuid: UUID of the connectivity service.
+        :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
+            or edit_connectivity_service
+        :param connection_points: (list): If provided, the old list of connection points will be replaced.
+        :param kwargs: Same meaning that create_connectivity_service
+        :return: dict or None: Information to be updated and stored at the database.
+                When ``None`` is returned, no information should be changed.
+                When an empty dict is returned, the database record will be deleted.
+                **MUST** be JSON/YAML-serializable (plain data structures).
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+
+    def clear_all_connectivity_services(self):
+        """Delete all WAN Links in a WIM.
+
+        This method is intended for debugging only, and should delete all the
+        connections controlled by the WIM/SDN, not only the  connections that
+        a specific RO is aware of.
+
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+        raise NotImplementedError
+
+    def get_all_active_connectivity_services(self):
+        """Provide information about all active connections provisioned by a
+        WIM.
+
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+        raise NotImplementedError
index e69de29..7284a2b 100644 (file)
@@ -0,0 +1,13 @@
+##
+# 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.
+##
index 8984020..b37cc13 100644 (file)
@@ -99,7 +99,7 @@ def datacenter(identifier, external_ports_config=False):
                 'encapsulation_type': 'vlan'},
                 'vim_external_port':
                 dict(zip(('switch', 'port'),
                 'encapsulation_type': 'vlan'},
                 'vim_external_port':
                 dict(zip(('switch', 'port'),
-                          _datacenter_to_switch_port(identifier)))}
+                         _datacenter_to_switch_port(identifier)))}
         ]})
 
     return {'uuid': uuid('dc%d' % identifier),
         ]})
 
     return {'uuid': uuid('dc%d' % identifier),
@@ -144,13 +144,13 @@ def wim_port_mapping(wim, datacenter,
 
     return {'wim_id': uuid('wim%d' % wim),
             'datacenter_id': uuid('dc%d' % datacenter),
 
     return {'wim_id': uuid('wim%d' % wim),
             'datacenter_id': uuid('dc%d' % datacenter),
-            'pop_switch_dpid': pop_dpid,
-            'pop_switch_port': (str(pop_port) if pop_port else
-                                str(int(datacenter) + int(wim) + 1)),
+            'device_id': pop_dpid,
+            'device_interface_id': (str(pop_port) if pop_port else
+                                    str(int(datacenter) + int(wim) + 1)),
             # ^  Datacenter router have one port managed by each WIM
             # ^  Datacenter router have one port managed by each WIM
-            'wan_service_endpoint_id': id_,
+            'service_endpoint_id': id_,
             # ^  WIM managed router have one port connected to each DC
             # ^  WIM managed router have one port connected to each DC
-            'wan_service_mapping_info': json.dumps(mapping_info)}
+            'service_mapping_info': json.dumps(mapping_info)}
 
 
 def processed_port_mapping(wim, datacenter,
 
 
 def processed_port_mapping(wim, datacenter,
@@ -164,11 +164,11 @@ def processed_port_mapping(wim, datacenter,
         'wim_id': uuid('wim%d' % wim),
         'datacenter_id': uuid('dc%d' % datacenter),
         'pop_wan_mappings': [
         'wim_id': uuid('wim%d' % wim),
         'datacenter_id': uuid('dc%d' % datacenter),
         'pop_wan_mappings': [
-            {'pop_switch_dpid': pop_dpid,
-             'pop_switch_port': wim + 1 + i,
-             'wan_service_endpoint_id':
+            {'device_id': pop_dpid,
+             'device_interface_id': wim + 1 + i,
+             'service_endpoint_id':
                  sha1('dpid-port|%s|%d' % (wan_dpid, datacenter + 1 + i)),
                  sha1('dpid-port|%s|%d' % (wan_dpid, datacenter + 1 + i)),
-             'wan_service_mapping_info': {
+             'service_mapping_info': {
                  'mapping_type': 'dpid-port',
                  'wan_switch_dpid': wan_dpid,
                  'wan_switch_port': datacenter + 1 + i}}
                  'mapping_type': 'dpid-port',
                  'wan_switch_dpid': wan_dpid,
                  'wan_switch_port': datacenter + 1 + i}}
index 3756869..ba66fbc 100644 (file)
@@ -33,7 +33,7 @@
 ##
 # pylint: disable=E1101
 
 ##
 # pylint: disable=E1101
 
-#from __future__ import unicode_literals, print_function
+# from __future__ import unicode_literals, print_function
 
 import json
 import unittest
 
 import json
 import unittest
@@ -49,7 +49,7 @@ from ...tests.db_helpers import (
 )
 from ..persistence import WimPersistence, preprocess_record
 from ..wan_link_actions import WanLinkCreate, WanLinkDelete
 )
 from ..persistence import WimPersistence, preprocess_record
 from ..wan_link_actions import WanLinkCreate, WanLinkDelete
-from ..wimconn import WimConnectorError
+from ..sdnconn import SdnConnectorError
 
 
 class TestActionsWithDb(TestCaseWithDatabasePerTest):
 
 
 class TestActionsWithDb(TestCaseWithDatabasePerTest):
@@ -192,7 +192,7 @@ class TestCreate(TestActionsWithDb):
 
         # If the connector raises an error
         with patch.object(self.connector, 'create_connectivity_service',
 
         # If the connector raises an error
         with patch.object(self.connector, 'create_connectivity_service',
-                          MagicMock(side_effect=WimConnectorError('foobar'))):
+                          MagicMock(side_effect=SdnConnectorError('foobar'))):
             # When we try to process a CREATE action that refers to the same
             # instance_scenario_id and sce_net_id
             action.process(self.connector, self.persist, self.ovim)
             # When we try to process a CREATE action that refers to the same
             # instance_scenario_id and sce_net_id
             action.process(self.connector, self.persist, self.ovim)
@@ -219,8 +219,8 @@ class TestCreate(TestActionsWithDb):
         port_mappings = next(r['wim_port_mappings']
                              for r in db_state if 'wim_port_mappings' in r)
         for mapping in port_mappings:
         port_mappings = next(r['wim_port_mappings']
                              for r in db_state if 'wim_port_mappings' in r)
         for mapping in port_mappings:
-            mapping['pop_switch_dpid'] = switch
-            mapping['pop_switch_port'] = port
+            mapping['device_id'] = switch
+            mapping['device_interface_id'] = port
 
         instance_action = eg.instance_action(action_id='ACTION-000')
         instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
 
         instance_action = eg.instance_action(action_id='ACTION-000')
         instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
@@ -294,7 +294,7 @@ class TestCreate(TestActionsWithDb):
 
         connector_patch = patch.object(
             self.connector, 'create_connectivity_service',
 
         connector_patch = patch.object(
             self.connector, 'create_connectivity_service',
-            MagicMock(side_effect=WimConnectorError('foobar')))
+            MagicMock(side_effect=SdnConnectorError('foobar')))
 
         # If the connector throws an error
         with connector_patch, ovim_patch:
 
         # If the connector throws an error
         with connector_patch, ovim_patch:
index e42e53c..ab3e2d0 100644 (file)
@@ -147,9 +147,9 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
                     config={'wim_port_mapping': [{
                         'datacenter_name': 'dc0',
                         'pop_wan_mappings': [{
                     config={'wim_port_mapping': [{
                         'datacenter_name': 'dc0',
                         'pop_wan_mappings': [{
-                            'pop_switch_dpid': '00:AA:11:BB:22:CC:33:DD',
-                            'pop_switch_port': 1,
-                            'wan_service_mapping_info': {
+                            'device_id': '00:AA:11:BB:22:CC:33:DD',
+                            'device_interface_id': 1,
+                            'service_mapping_info': {
                                 'mapping_type': 'dpid-port',
                                 'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:0A',
                                 'wan_switch_port': 1
                                 'mapping_type': 'dpid-port',
                                 'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:0A',
                                 'wan_switch_port': 1
@@ -170,7 +170,7 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
         mappings = response.json['wim']['config']['wim_port_mapping']
         self.assertEqual(len(mappings), 1)
         self.assertEqual(
         mappings = response.json['wim']['config']['wim_port_mapping']
         self.assertEqual(len(mappings), 1)
         self.assertEqual(
-            mappings[0]['pop_wan_mappings'][0]['pop_switch_dpid'],
+            mappings[0]['pop_wan_mappings'][0]['device_id'],
             '00:AA:11:BB:22:CC:33:DD')
 
     def test_delete_wim(self):
             '00:AA:11:BB:22:CC:33:DD')
 
     def test_delete_wim(self):
@@ -224,9 +224,9 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
                     config={'wim_port_mapping': [{
                         'datacenter_name': 'dc0',
                         'pop_wan_mappings': [{
                     config={'wim_port_mapping': [{
                         'datacenter_name': 'dc0',
                         'pop_wan_mappings': [{
-                            'pop_switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:01',
-                            'pop_switch_port': 1,
-                            'wan_service_mapping_info': {
+                            'device_id': 'AA:AA:AA:AA:AA:AA:AA:01',
+                            'device_interface_id': 1,
+                            'service_mapping_info': {
                                 'mapping_type': 'dpid-port',
                                 'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:01',
                                 'wan_switch_port': 1
                                 'mapping_type': 'dpid-port',
                                 'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:01',
                                 'wan_switch_port': 1
@@ -523,9 +523,9 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
             {'wim_port_mapping': [{
                 'datacenter_name': 'dc888',
                 'pop_wan_mappings': [
             {'wim_port_mapping': [{
                 'datacenter_name': 'dc888',
                 'pop_wan_mappings': [
-                    {'pop_switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
-                     'pop_switch_port': 1,
-                     'wan_service_mapping_info': {
+                    {'device_id': 'AA:AA:AA:AA:AA:AA:AA:AA',
+                     'device_interface_id': 1,
+                     'service_mapping_info': {
                          'mapping_type': 'dpid-port',
                          'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:BB',
                          'wan_switch_port': 1
                          'mapping_type': 'dpid-port',
                          'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:BB',
                          'wan_switch_port': 1
index b8c8231..7ad66c2 100644 (file)
@@ -67,7 +67,7 @@ class TestWimThreadWithDb(TestCaseWithDatabasePerTest):
         wim = eg.wim(0)
         account = eg.wim_account(0, 0)
         account['wim'] = wim
         wim = eg.wim(0)
         account = eg.wim_account(0, 0)
         account['wim'] = wim
-        self.thread = WimThread(self.persist, account)
+        self.thread = WimThread(self.persist, {}, account)
         self.thread.connector = MagicMock()
 
     def assertTasksEqual(self, left, right):
         self.thread.connector = MagicMock()
 
     def assertTasksEqual(self, left, right):
@@ -282,7 +282,7 @@ class TestWimThread(unittest.TestCase):
         account = eg.wim_account(0, 0)
         account['wim'] = wim
         self.persist = MagicMock()
         account = eg.wim_account(0, 0)
         account['wim'] = wim
         self.persist = MagicMock()
-        self.thread = WimThread(self.persist, account)
+        self.thread = WimThread(self.persist, {}, account)
         self.thread.connector = MagicMock()
 
         super(TestWimThread, self).setUp()
         self.thread.connector = MagicMock()
 
         super(TestWimThread, self).setUp()
index 29f1a8f..a426d4d 100644 (file)
@@ -1,3 +1,17 @@
+##
+# 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.
+##
+
 # This tox file allows the devs to run unit tests only for this subpackage.
 # In order to do so, cd into the directory and run `tox`
 
 # This tox file allows the devs to run unit tests only for this subpackage.
 # In order to do so, cd into the directory and run `tox`
 
index 0d878b2..458f3d5 100644 (file)
@@ -34,7 +34,7 @@
 # pylint: disable=E1101,E0203,W0201
 import json
 from pprint import pformat
 # pylint: disable=E1101,E0203,W0201
 import json
 from pprint import pformat
-from sys import exc_info
+from sys import exc_info
 from time import time
 
 from ..utils import filter_dict_keys as filter_keys
 from time import time
 
 from ..utils import filter_dict_keys as filter_keys
@@ -45,7 +45,7 @@ from .errors import (
     NoRecordFound,
     NoExternalPortFound
 )
     NoRecordFound,
     NoExternalPortFound
 )
-from .wimconn import WimConnectorError
+from .sdnconn import SdnConnectorError
 
 INSTANCE_NET_STATUS_ERROR = ('DOWN', 'ERROR', 'VIM_ERROR',
                              'DELETED', 'SCHEDULED_DELETION')
 
 INSTANCE_NET_STATUS_ERROR = ('DOWN', 'ERROR', 'VIM_ERROR',
                              'DELETED', 'SCHEDULED_DELETION')
@@ -64,26 +64,26 @@ class RefreshMixin(object):
                 Infrastructure Manager system
             persistence: abstraction layer for the database
         """
                 Infrastructure Manager system
             persistence: abstraction layer for the database
         """
-        fields = ('wim_status', 'wim_info', 'error_msg')
+        fields = ('sdn_status', 'sdn_info', 'error_msg')
         result = dict.fromkeys(fields)
 
         try:
             result.update(
                 connector
                 .get_connectivity_service_status(self.wim_internal_id))
         result = dict.fromkeys(fields)
 
         try:
             result.update(
                 connector
                 .get_connectivity_service_status(self.wim_internal_id))
-        except WimConnectorError as ex:
+        except SdnConnectorError as ex:
             self.logger.exception(ex)
             self.logger.exception(ex)
-            result.update(wim_status='WIM_ERROR', error_msg=truncate(ex))
+            result.update(sdn_status='WIM_ERROR', error_msg=truncate(ex))
 
         result = filter_keys(result, fields)
 
         action_changes = remove_none_items({
             'extra': merge_dicts(self.extra, result),
 
         result = filter_keys(result, fields)
 
         action_changes = remove_none_items({
             'extra': merge_dicts(self.extra, result),
-            'status': 'BUILD' if result['wim_status'] == 'BUILD' else None,
+            'status': 'BUILD' if result['sdn_status'] == 'BUILD' else None,
             'error_msg': result['error_msg'],
             'modified_at': time()})
             'error_msg': result['error_msg'],
             'modified_at': time()})
-        link_changes = merge_dicts(result, status=result.pop('wim_status'))
-        # ^  Rename field: wim_status => status
+        link_changes = merge_dicts(result, status=result.pop('sdn_status'))
+        # ^  Rename field: sdn_status => status
 
         persistence.update_wan_link(self.item_id,
                                     remove_none_items(link_changes))
 
         persistence.update_wan_link(self.item_id,
                                     remove_none_items(link_changes))
@@ -159,10 +159,10 @@ class WanLinkCreate(RefreshMixin, CreateAction):
         Returns:
             dict: Record representing the wan_port_mapping associated to the
                   given instance_net. The expected fields are:
         Returns:
             dict: Record representing the wan_port_mapping associated to the
                   given instance_net. The expected fields are:
-                  **wim_id**, **datacenter_id**, **pop_switch_dpid** (the local
-                  network is expected to be connected at this switch),
-                  **pop_switch_port**, **wan_service_endpoint_id**,
-                  **wan_service_mapping_info**.
+                  **wim_id**, **datacenter_id**, **device_id** (the local
+                  network is expected to be connected at this switch dpid),
+                  **device_interface_id**, **service_endpoint_id**,
+                  **service_mapping_info**.
         """
         # First, we need to find a route from the datacenter to the outside
         # world. For that, we can use the rules given in the datacenter
         """
         # First, we need to find a route from the datacenter to the outside
         # world. For that, we can use the rules given in the datacenter
@@ -185,8 +185,8 @@ class WanLinkCreate(RefreshMixin, CreateAction):
 
             criteria = {
                 'wim_id': wim_account['wim_id'],
 
             criteria = {
                 'wim_id': wim_account['wim_id'],
-                'pop_switch_dpid': external_port[0],
-                'pop_switch_port': external_port[1],
+                'device_id': external_port[0],
+                'device_interface_id': external_port[1],
                 'datacenter_id': datacenter_id}
 
             wan_port_mapping = persistence.query_one(
                 'datacenter_id': datacenter_id}
 
             wan_port_mapping = persistence.query_one(
@@ -200,11 +200,11 @@ class WanLinkCreate(RefreshMixin, CreateAction):
 
         # It is important to return encapsulation information if present
         mapping = merge_dicts(
 
         # It is important to return encapsulation information if present
         mapping = merge_dicts(
-            wan_port_mapping.get('wan_service_mapping_info'),
+            wan_port_mapping.get('service_mapping_info'),
             filter_keys(vim_info, ('encapsulation_type', 'encapsulation_id'))
         )
 
             filter_keys(vim_info, ('encapsulation_type', 'encapsulation_id'))
         )
 
-        return merge_dicts(wan_port_mapping, wan_service_mapping_info=mapping)
+        return merge_dicts(wan_port_mapping, service_mapping_info=mapping)
 
     def _get_port_sdn(self, ovim, instance_net):
         criteria = {'net_id': instance_net['sdn_net_id']}
 
     def _get_port_sdn(self, ovim, instance_net):
         criteria = {'net_id': instance_net['sdn_net_id']}
@@ -310,9 +310,9 @@ class WanLinkCreate(RefreshMixin, CreateAction):
 
     @staticmethod
     def _derive_connection_point(wan_info):
 
     @staticmethod
     def _derive_connection_point(wan_info):
-        point = {'service_endpoint_id': wan_info['wan_service_endpoint_id']}
+        point = {'service_endpoint_id': wan_info['service_endpoint_id']}
         # TODO: Cover other scenarios, e.g. VXLAN.
         # TODO: Cover other scenarios, e.g. VXLAN.
-        details = wan_info.get('wan_service_mapping_info', {})
+        details = wan_info.get('service_mapping_info', {})
         if details.get('encapsulation_type') == 'vlan':
             point['service_endpoint_encapsulation_type'] = 'dot1q'
             point['service_endpoint_encapsulation_info'] = {
         if details.get('encapsulation_type') == 'vlan':
             point['service_endpoint_encapsulation_type'] = 'dot1q'
             point['service_endpoint_encapsulation_info'] = {
@@ -335,7 +335,7 @@ class WanLinkCreate(RefreshMixin, CreateAction):
         """Store plugin/connector specific information in the database"""
         persistence.update_wan_link(self.item_id, {
             'wim_internal_id': service_uuid,
         """Store plugin/connector specific information in the database"""
         persistence.update_wan_link(self.item_id, {
             'wim_internal_id': service_uuid,
-            'wim_info': {'conn_info': conn_info},
+            'sdn_info': {'conn_info': conn_info},
             'status': 'BUILD'})
 
     def execute(self, connector, persistence, ovim, instance_nets):
             'status': 'BUILD'})
 
     def execute(self, connector, persistence, ovim, instance_nets):
@@ -353,7 +353,7 @@ class WanLinkCreate(RefreshMixin, CreateAction):
                 connection_points
                 # TODO: other properties, e.g. bandwidth
             )
                 connection_points
                 # TODO: other properties, e.g. bandwidth
             )
-        except (WimConnectorError, InconsistentState,
+        except (SdnConnectorError, InconsistentState,
                 NoExternalPortFound) as ex:
             self.logger.exception(ex)
             return self.fail(
                 NoExternalPortFound) as ex:
             self.logger.exception(ex)
             return self.fail(
@@ -412,12 +412,12 @@ class WanLinkDelete(DeleteAction):
 
         try:
             id = self.wim_internal_id
 
         try:
             id = self.wim_internal_id
-            conn_info = safe_get(wan_link, 'wim_info.conn_info')
+            conn_info = safe_get(wan_link, 'sdn_info.conn_info')
             self.logger.debug('Connection Service %s (wan_link: %s):\n%s\n',
                               id, wan_link['uuid'],
                               json.dumps(conn_info, indent=4))
             result = connector.delete_connectivity_service(id, conn_info)
             self.logger.debug('Connection Service %s (wan_link: %s):\n%s\n',
                               id, wan_link['uuid'],
                               json.dumps(conn_info, indent=4))
             result = connector.delete_connectivity_service(id, conn_info)
-        except (WimConnectorError, InconsistentState) as ex:
+        except (SdnConnectorError, InconsistentState) as ex:
             self.logger.exception(ex)
             return self.fail(
                 persistence,
             self.logger.exception(ex)
             return self.fail(
                 persistence,
index 13502b9..3466193 100644 (file)
@@ -45,7 +45,7 @@ from contextlib import contextmanager
 from functools import partial
 from itertools import islice, chain, takewhile
 from operator import itemgetter, attrgetter
 from functools import partial
 from itertools import islice, chain, takewhile
 from operator import itemgetter, attrgetter
-from sys import exc_info
+from sys import exc_info
 from time import time, sleep
 
 import queue
 from time import time, sleep
 
 import queue
@@ -60,10 +60,8 @@ from .errors import (
     UndefinedAction,
 )
 from .failing_connector import FailingConnector
     UndefinedAction,
 )
 from .failing_connector import FailingConnector
-from .wimconn import WimConnectorError
-from .wimconn_dynpac import DynpacConnector
+from .sdnconn import SdnConnectorError
 from .wimconn_fake import FakeConnector
 from .wimconn_fake import FakeConnector
-from .wimconn_ietfl2vpn import WimconnectorIETFL2VPN
 
 ACTIONS = {
     'instance_wim_nets': wan_link_actions.ACTIONS
 
 ACTIONS = {
     'instance_wim_nets': wan_link_actions.ACTIONS
@@ -71,10 +69,8 @@ ACTIONS = {
 
 CONNECTORS = {
     # "odl": wimconn_odl.OdlConnector,
 
 CONNECTORS = {
     # "odl": wimconn_odl.OdlConnector,
-    "dynpac": DynpacConnector,
     "fake": FakeConnector,
     "fake": FakeConnector,
-    "tapi": WimconnectorIETFL2VPN,
-    # Add extra connectors here
+    # Add extra connectors here not managed via plugins
 }
 
 
 }
 
 
@@ -101,17 +97,21 @@ class WimThread(threading.Thread):
     MAX_RECOVERY_TIME = 180
     WAITING_TIME = 1      # Wait 1s for taks to arrive, when there are none
 
     MAX_RECOVERY_TIME = 180
     WAITING_TIME = 1      # Wait 1s for taks to arrive, when there are none
 
-    def __init__(self, persistence, wim_account, logger=None, ovim=None):
+    def __init__(self, persistence, plugins, wim_account, logger=None, ovim=None):
         """Init a thread.
 
         Arguments:
             persistence: Database abstraction layer
         """Init a thread.
 
         Arguments:
             persistence: Database abstraction layer
+            plugins: dictionary with the vim/sdn plugins
             wim_account: Record containing wim_account, tenant and wim
                 information.
         """
         name = '{}.{}.{}'.format(wim_account['wim']['name'],
                                  wim_account['name'], wim_account['uuid'])
         super(WimThread, self).__init__(name=name)
             wim_account: Record containing wim_account, tenant and wim
                 information.
         """
         name = '{}.{}.{}'.format(wim_account['wim']['name'],
                                  wim_account['name'], wim_account['uuid'])
         super(WimThread, self).__init__(name=name)
+        self.plugins = plugins
+        if "rosdn_fake" not in self.plugins:
+            self.plugins["rosdn_fake"] = FakeConnector
 
         self.name = name
         self.connector = None
 
         self.name = name
         self.connector = None
@@ -160,9 +160,11 @@ class WimThread(threading.Thread):
             mapping = self.persist.query('wim_port_mappings',
                                          WHERE={'wim_id': wim['uuid']},
                                          error_if_none=False)
             mapping = self.persist.query('wim_port_mappings',
                                          WHERE={'wim_id': wim['uuid']},
                                          error_if_none=False)
-            return CONNECTORS[wim['type']](wim, account, {
-                'service_endpoint_mapping': mapping or []
-            })
+            if wim["type"] in CONNECTORS:
+                return CONNECTORS[wim['type']](wim, account, {'service_endpoint_mapping': mapping or []})
+            else:    # load a plugin
+                return self.plugins["rosdn_" + wim["type"]](
+                    wim, account, {'service_endpoint_mapping': mapping or []})
         except DbBaseException as ex:
             error_msg = ('Error when retrieving WIM account ({})\n'
                          .format(account_id)) + str(ex)
         except DbBaseException as ex:
             error_msg = ('Error when retrieving WIM account ({})\n'
                          .format(account_id)) + str(ex)
@@ -170,8 +172,8 @@ class WimThread(threading.Thread):
         except KeyError as ex:
             error_msg = ('Unable to find the WIM connector for WIM ({})\n'
                          .format(wim['type'])) + str(ex)
         except KeyError as ex:
             error_msg = ('Unable to find the WIM connector for WIM ({})\n'
                          .format(wim['type'])) + str(ex)
-            self.logger.error(error_msg, exc_info=True)
-        except (WimConnectorError, Exception) as ex:
+            self.logger.error(error_msg)
+        except (SdnConnectorError, Exception) as ex:
             # TODO: Remove the Exception class here when the connector class is
             # ready
             error_msg = ('Error when loading WIM connector for WIM ({})\n'
             # TODO: Remove the Exception class here when the connector class is
             # ready
             error_msg = ('Error when loading WIM connector for WIM ({})\n'
diff --git a/RO/osm_ro/wim/wimconn.py b/RO/osm_ro/wim/wimconn.py
deleted file mode 100644 (file)
index 92b6db0..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-# -*- coding: utf-8 -*-
-##
-# Copyright 2018 University of Bristol - High Performance Networks Research
-# Group
-# All Rights Reserved.
-#
-# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
-# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
-#
-# 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: <highperformance-networks@bristol.ac.uk>
-#
-# Neither the name of the University of Bristol nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# This work has been performed in the context of DCMS UK 5G Testbeds
-# & Trials Programme and in the framework of the Metro-Haul project -
-# funded by the European Commission under Grant number 761727 through the
-# Horizon 2020 and 5G-PPP programmes.
-##
-"""The WIM connector is responsible for establishing wide area network
-connectivity.
-
-It receives information from the WimThread/WAN Actions about the endpoints of
-a link that spans across multiple datacenters and stablish a path between them.
-"""
-import logging
-
-from ..http_tools.errors import HttpMappedError
-
-
-class WimConnectorError(HttpMappedError):
-    """Base Exception for all connector related errors"""
-
-
-class WimConnector(object):
-    """Abstract base class for all the WIM connectors
-
-    Arguments:
-        wim (dict): WIM record, as stored in the database
-        wim_account (dict): WIM account record, as stored in the database
-        config (dict): optional persistent information related to an specific
-            connector.  Inside this dict, a special key,
-            ``service_endpoint_mapping`` provides the internal endpoint
-            mapping.
-        logger (logging.Logger): optional logger object. If none is passed
-            ``openmano.wim.wimconn`` is used.
-
-    The arguments of the constructor are converted to object attributes.
-    An extra property, ``service_endpoint_mapping`` is created from ``config``.
-    """
-    def __init__(self, wim, wim_account, config=None, logger=None):
-        self.logger = logger or logging.getLogger('openmano.wim.wimconn')
-
-        self.wim = wim
-        self.wim_account = wim_account
-        self.config = config or {}
-        self.service_endpoint_mapping = (
-            config.get('service_endpoint_mapping', []))
-
-    def check_credentials(self):
-        """Check if the connector itself can access the WIM.
-
-        Raises:
-            WimConnectorError: Issues regarding authorization, access to
-                external URLs, etc are detected.
-        """
-        raise NotImplementedError
-
-    def get_connectivity_service_status(self, service_uuid, conn_info=None):
-        """Monitor the status of the connectivity service established
-
-        Arguments:
-            service_uuid (str): UUID of the connectivity service
-            conn_info (dict or None): Information returned by the connector
-                during the service creation/edition and subsequently stored in
-                the database.
-
-        Returns:
-            dict: JSON/YAML-serializable dict that contains a mandatory key
-                ``wim_status`` associated with one of the following values::
-
-                    {'wim_status': 'ACTIVE'}
-                        # The service is up and running.
-
-                    {'wim_status': 'INACTIVE'}
-                        # The service was created, but the connector
-                        # cannot determine yet if connectivity exists
-                        # (ideally, the caller needs to wait and check again).
-
-                    {'wim_status': 'DOWN'}
-                        # Connection was previously established,
-                        # but an error/failure was detected.
-
-                    {'wim_status': 'ERROR'}
-                        # An error occurred when trying to create the service/
-                        # establish the connectivity.
-
-                    {'wim_status': 'BUILD'}
-                        # Still trying to create the service, the caller
-                        # needs to wait and check again.
-
-                Additionally ``error_msg``(**str**) and ``wim_info``(**dict**)
-                keys can be used to provide additional status explanation or
-                new information available for the connectivity service.
-        """
-        raise NotImplementedError
-
-    def create_connectivity_service(self, service_type, connection_points,
-                                    **kwargs):
-        """Stablish WAN connectivity between the endpoints
-
-        Arguments:
-            service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
-                ``L3``.
-            connection_points (list): each point corresponds to
-                an entry point from the DC to the transport network. One
-                connection point serves to identify the specific access and
-                some other service parameters, such as encapsulation type.
-                Represented by a dict as follows::
-
-                    {
-                      "service_endpoint_id": ..., (str[uuid])
-                      "service_endpoint_encapsulation_type": ...,
-                           (enum: none, dot1q, ...)
-                      "service_endpoint_encapsulation_info": {
-                        ... (dict)
-                        "vlan": ..., (int, present if encapsulation is dot1q)
-                        "vni": ... (int, present if encapsulation is vxlan),
-                        "peers": [(ipv4_1), (ipv4_2)]
-                            (present if encapsulation is vxlan)
-                      }
-                    }
-
-              The service endpoint ID should be previously informed to the WIM
-              engine in the RO when the WIM port mapping is registered.
-
-        Keyword Arguments:
-            bandwidth (int): value in kilobytes
-            latency (int): value in milliseconds
-
-        Other QoS might be passed as keyword arguments.
-
-        Returns:
-            tuple: ``(service_id, conn_info)`` containing:
-               - *service_uuid* (str): UUID of the established connectivity
-                  service
-               - *conn_info* (dict or None): Information to be stored at the
-                 database (or ``None``). This information will be provided to
-                 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
-                 **MUST** be JSON/YAML-serializable (plain data structures).
-
-        Raises:
-            WimConnectorException: In case of error.
-        """
-        raise NotImplementedError
-
-    def delete_connectivity_service(self, service_uuid, conn_info=None):
-        """Disconnect multi-site endpoints previously connected
-
-        This method should receive as arguments both the UUID and the
-        connection info dict (respectively), as returned by
-        :meth:`~.create_connectivity_service` and
-        :meth:`~.edit_connectivity_service`.
-
-        Arguments:
-            service_uuid (str): UUID of the connectivity service
-            conn_info (dict or None): Information returned by the connector
-                during the service creation and subsequently stored in the
-                database.
-
-        Raises:
-            WimConnectorException: In case of error.
-        """
-        raise NotImplementedError
-
-    def edit_connectivity_service(self, service_uuid, conn_info=None,
-                                  connection_points=None, **kwargs):
-        """Change an existing connectivity service.
-
-        This method's arguments and return value follow the same convention as
-        :meth:`~.create_connectivity_service`.
-
-        Arguments:
-            service_uuid (str): UUID of the connectivity service.
-            conn_info (dict or None): Information previously stored in the
-                database.
-            connection_points (list): If provided, the old list of connection
-                points will be replaced.
-
-        Returns:
-            dict or None: Information to be updated and stored at the
-                database.
-                When ``None`` is returned, no information should be changed.
-                When an empty dict is returned, the database record will be
-                deleted.
-                **MUST** be JSON/YAML-serializable (plain data structures).
-
-        Raises:
-            WimConnectorException: In case of error.
-        """
-        raise NotImplementedError
-
-    def clear_all_connectivity_services(self):
-        """Delete all WAN Links in a WIM.
-
-        This method is intended for debugging only, and should delete all the
-        connections controlled by the WIM, not only the WIM connections that
-        a specific RO is aware of.
-
-        Raises:
-            WimConnectorException: In case of error.
-        """
-        raise NotImplementedError
-
-    def get_all_active_connectivity_services(self):
-        """Provide information about all active connections provisioned by a
-        WIM.
-
-        Raises:
-            WimConnectorException: In case of error.
-        """
-        raise NotImplementedError
diff --git a/RO/osm_ro/wim/wimconn_dynpac.py b/RO/osm_ro/wim/wimconn_dynpac.py
deleted file mode 100644 (file)
index 661f6f6..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-##
-# Copyright 2018 David GarcĂ­a, University of the Basque Country
-# Copyright 2018 University of the Basque Country
-# This file is part of openmano
-# All Rights Reserved.
-# Contact information at http://i2t.ehu.eus
-#
-# # 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.
-
-import requests
-import json
-import logging
-from enum import Enum
-
-from .wimconn import WimConnector, WimConnectorError
-
-
-class WimError(Enum):
-    UNREACHABLE = 'Unable to reach the WIM.',
-    SERVICE_TYPE_ERROR = 'Unexpected service_type. Only "L2" is accepted.',
-    CONNECTION_POINTS_SIZE = \
-        'Unexpected number of connection points: 2 expected.',
-    ENCAPSULATION_TYPE = \
-        'Unexpected service_endpoint_encapsulation_type. \
-         Only "dotq1" is accepted.',
-    BANDWIDTH = 'Unable to get the bandwidth.',
-    STATUS = 'Unable to get the status for the service.',
-    DELETE = 'Unable to delete service.',
-    CLEAR_ALL = 'Unable to clear all the services',
-    UNKNOWN_ACTION = 'Unknown action invoked.',
-    BACKUP = 'Unable to get the backup parameter.',
-    UNSUPPORTED_FEATURE = "Unsupported feature",
-    UNAUTHORIZED = "Failed while authenticating"
-
-
-class WimAPIActions(Enum):
-    CHECK_CONNECTIVITY = "CHECK_CONNECTIVITY",
-    CREATE_SERVICE = "CREATE_SERVICE",
-    DELETE_SERVICE = "DELETE_SERVICE",
-    CLEAR_ALL = "CLEAR_ALL",
-    SERVICE_STATUS = "SERVICE_STATUS",
-
-
-class DynpacConnector(WimConnector):
-    __supported_service_types = ["ELINE (L2)", "ELINE"]
-    __supported_encapsulation_types = ["dot1q"]
-    __WIM_LOGGER = 'openmano.wimconn.dynpac'
-    __ENCAPSULATION_TYPE_PARAM = "service_endpoint_encapsulation_type"
-    __ENCAPSULATION_INFO_PARAM = "service_endpoint_encapsulation_info"
-    __BACKUP_PARAM = "backup"
-    __BANDWIDTH_PARAM = "bandwidth"
-    __SERVICE_ENDPOINT_PARAM = "service_endpoint_id"
-    __WAN_SERVICE_ENDPOINT_PARAM = "wan_service_endpoint_id"
-    __WAN_MAPPING_INFO_PARAM = "wan_service_mapping_info"
-    __SW_ID_PARAM = "wan_switch_dpid"
-    __SW_PORT_PARAM = "wan_switch_port"
-    __VLAN_PARAM = "vlan"
-
-    # Public functions exposed to the Resource Orchestrator
-    def __init__(self, wim, wim_account, config):
-        self.logger = logging.getLogger(self.__WIM_LOGGER)
-        self.__wim = wim
-        self.__wim_account = wim_account
-        self.__config = config
-        self.__wim_url = self.__wim.get("wim_url")
-        self.__user = wim_account.get("user")
-        self.__passwd = wim_account.get("passwd")
-        self.logger.info("Initialized.")
-
-    def create_connectivity_service(self,
-                                    service_type,
-                                    connection_points,
-                                    **kwargs):
-        self.__check_service(service_type, connection_points, kwargs)
-
-        body = self.__get_body(service_type, connection_points, kwargs)
-
-        headers = {'Content-type': 'application/x-www-form-urlencoded'}
-        endpoint = "{}/service/create".format(self.__wim_url)
-
-        try:
-            response = requests.post(endpoint, data=body, headers=headers)
-        except requests.exceptions.RequestException as e:
-            self.__exception(e.message, http_code=503)
-
-        if response.status_code != 200:
-            error = json.loads(response.content)
-            reason = "Reason: {}. ".format(error.get("code"))
-            description = "Description: {}.".format(error.get("description"))
-            exception = reason + description
-            self.__exception(exception, http_code=response.status_code)
-        uuid = response.content
-        self.logger.info("Service with uuid {} created.".format(uuid))
-        return (uuid, None)
-
-    def edit_connectivity_service(self, service_uuid,
-                                  conn_info, connection_points,
-                                  **kwargs):
-        self.__exception(WimError.UNSUPPORTED_FEATURE, http_code=501)
-
-    def get_connectivity_service_status(self, service_uuid):
-        endpoint = "{}/service/status/{}".format(self.__wim_url, service_uuid)
-        try:
-            response = requests.get(endpoint)
-        except requests.exceptions.RequestException as e:
-            self.__exception(e.message, http_code=503)
-
-        if response.status_code != 200:
-            self.__exception(WimError.STATUS, http_code=response.status_code)
-        self.logger.info("Status for service with uuid {}: {}"
-                         .format(service_uuid, response.content))
-        return response.content
-
-    def delete_connectivity_service(self, service_uuid, conn_info):
-        endpoint = "{}/service/delete/{}".format(self.__wim_url, service_uuid)
-        try:
-            response = requests.delete(endpoint)
-        except requests.exceptions.RequestException as e:
-            self.__exception(e.message, http_code=503)
-        if response.status_code != 200:
-            self.__exception(WimError.DELETE, http_code=response.status_code)
-
-        self.logger.info("Service with uuid: {} deleted".format(service_uuid))
-
-    def clear_all_connectivity_services(self):
-        endpoint = "{}/service/clearAll".format(self.__wim_url)
-        try:
-            response = requests.delete(endpoint)
-            http_code = response.status_code
-        except requests.exceptions.RequestException as e:
-            self.__exception(e.message, http_code=503)
-        if http_code != 200:
-            self.__exception(WimError.CLEAR_ALL, http_code=http_code)
-
-        self.logger.info("{} services deleted".format(response.content))
-        return "{} services deleted".format(response.content)
-
-    def check_connectivity(self):
-        endpoint = "{}/checkConnectivity".format(self.__wim_url)
-
-        try:
-            response = requests.get(endpoint)
-            http_code = response.status_code
-        except requests.exceptions.RequestException as e:
-            self.__exception(e.message, http_code=503)
-
-        if http_code != 200:
-            self.__exception(WimError.UNREACHABLE, http_code=http_code)
-        self.logger.info("Connectivity checked")
-
-    def check_credentials(self):
-        endpoint = "{}/checkCredentials".format(self.__wim_url)
-        auth = (self.__user, self.__passwd)
-
-        try:
-            response = requests.get(endpoint, auth=auth)
-            http_code = response.status_code
-        except requests.exceptions.RequestException as e:
-            self.__exception(e.message, http_code=503)
-
-        if http_code != 200:
-            self.__exception(WimError.UNAUTHORIZED, http_code=http_code)
-        self.logger.info("Credentials checked")
-
-    # Private functions
-    def __exception(self, x, **kwargs):
-        http_code = kwargs.get("http_code")
-        if hasattr(x, "value"):
-            error = x.value
-        else:
-            error = x
-        self.logger.error(error)
-        raise WimConnectorError(error, http_code=http_code)
-
-    def __check_service(self, service_type, connection_points, kwargs):
-        if service_type not in self.__supported_service_types:
-            self.__exception(WimError.SERVICE_TYPE_ERROR, http_code=400)
-
-        if len(connection_points) != 2:
-            self.__exception(WimError.CONNECTION_POINTS_SIZE, http_code=400)
-
-        for connection_point in connection_points:
-            enc_type = connection_point.get(self.__ENCAPSULATION_TYPE_PARAM)
-            if enc_type not in self.__supported_encapsulation_types:
-                self.__exception(WimError.ENCAPSULATION_TYPE, http_code=400)
-
-        # Commented out for as long as parameter isn't implemented
-        # bandwidth = kwargs.get(self.__BANDWIDTH_PARAM)
-        # if not isinstance(bandwidth, int):
-            # self.__exception(WimError.BANDWIDTH, http_code=400)
-
-        # Commented out for as long as parameter isn't implemented
-        # backup = kwargs.get(self.__BACKUP_PARAM)
-        # if not isinstance(backup, bool):
-            # self.__exception(WimError.BACKUP, http_code=400)
-
-    def __get_body(self, service_type, connection_points, kwargs):
-        port_mapping = self.__config.get("service_endpoint_mapping")
-        selected_ports = []
-        for connection_point in connection_points:
-            endpoint_id = connection_point.get(self.__SERVICE_ENDPOINT_PARAM)
-            port = filter(lambda x: x.get(self.__WAN_SERVICE_ENDPOINT_PARAM) == endpoint_id, port_mapping)[0]
-            port_info = port.get(self.__WAN_MAPPING_INFO_PARAM)
-            selected_ports.append(port_info)
-        if service_type == "ELINE (L2)" or service_type == "ELINE":
-            service_type = "L2"
-        body = {
-            "connection_points": [{
-                "wan_switch_dpid": selected_ports[0].get(self.__SW_ID_PARAM),
-                "wan_switch_port": selected_ports[0].get(self.__SW_PORT_PARAM),
-                "wan_vlan": connection_points[0].get(self.__ENCAPSULATION_INFO_PARAM).get(self.__VLAN_PARAM)
-            }, {
-                "wan_switch_dpid": selected_ports[1].get(self.__SW_ID_PARAM),
-                "wan_switch_port": selected_ports[1].get(self.__SW_PORT_PARAM),
-                "wan_vlan": connection_points[1].get(self.__ENCAPSULATION_INFO_PARAM).get(self.__VLAN_PARAM)
-            }],
-            "bandwidth": 100,  # Hardcoded for as long as parameter isn't implemented
-            "service_type": service_type,
-            "backup": False    # Hardcoded for as long as parameter isn't implemented
-        }
-        return "body={}".format(json.dumps(body))
index 36929f4..168996d 100644 (file)
@@ -22,12 +22,12 @@ This WIM does nothing and allows using it for testing and when no WIM is needed
 
 import logging
 from uuid import uuid4
 
 import logging
 from uuid import uuid4
-from .wimconn import WimConnector
-
+from .sdnconn import SdnConnectorBase, SdnConnectorError
+from http import HTTPStatus
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
 
-class FakeConnector(WimConnector):
+class FakeConnector(SdnConnectorBase):
     """Abstract base class for all the WIM connectors
 
     Arguments:
     """Abstract base class for all the WIM connectors
 
     Arguments:
@@ -44,8 +44,8 @@ class FakeConnector(WimConnector):
     An extra property, ``service_endpoint_mapping`` is created from ``config``.
     """
     def __init__(self, wim, wim_account, config=None, logger=None):
     An extra property, ``service_endpoint_mapping`` is created from ``config``.
     """
     def __init__(self, wim, wim_account, config=None, logger=None):
-        self.logger = logging.getLogger('openmano.wimconn.fake')
-        super(FakeConnector, self).__init__(wim, wim_account, config, logger)
+        self.logger = logger or logging.getLogger('openmano.sdnconn.fake')
+        super(FakeConnector, self).__init__(wim, wim_account, config, self.logger)
         self.logger.debug("__init: wim='{}' wim_account='{}'".format(wim, wim_account))
         self.connections = {}
         self.counter = 0
         self.logger.debug("__init: wim='{}' wim_account='{}'".format(wim, wim_account))
         self.connections = {}
         self.counter = 0
@@ -54,7 +54,7 @@ class FakeConnector(WimConnector):
         """Check if the connector itself can access the WIM.
 
         Raises:
         """Check if the connector itself can access the WIM.
 
         Raises:
-            WimConnectorError: Issues regarding authorization, access to
+            SdnConnectorError: Issues regarding authorization, access to
                 external URLs, etc are detected.
         """
         self.logger.debug("check_credentials")
                 external URLs, etc are detected.
         """
         self.logger.debug("check_credentials")
@@ -71,15 +71,15 @@ class FakeConnector(WimConnector):
 
         Returns:
             dict: JSON/YAML-serializable dict that contains a mandatory key
 
         Returns:
             dict: JSON/YAML-serializable dict that contains a mandatory key
-                ``wim_status`` associated with one of the following values::
+                ``sdn_status`` associated with one of the following values::
 
 
-                Additionally ``error_msg``(**str**) and ``wim_info``(**dict**)
+                Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
                 keys can be used to provide additional status explanation or
                 new information available for the connectivity service.
         """
         self.logger.debug("get_connectivity_service_status: service_uuid='{}' conn_info='{}'".format(service_uuid,
                                                                                                      conn_info))
                 keys can be used to provide additional status explanation or
                 new information available for the connectivity service.
         """
         self.logger.debug("get_connectivity_service_status: service_uuid='{}' conn_info='{}'".format(service_uuid,
                                                                                                      conn_info))
-        return {'wim_status': 'ACTIVE', 'wim_info': self.connectivity.get(service_uuid)}
+        return {'sdn_status': 'ACTIVE', 'sdn_info': self.connectivity.get(service_uuid)}
 
     def create_connectivity_service(self, service_type, connection_points,
                                     **kwargs):
 
     def create_connectivity_service(self, service_type, connection_points,
                                     **kwargs):
@@ -90,9 +90,9 @@ class FakeConnector(WimConnector):
         self.logger.debug("create_connectivity_service: service_type='{}' connection_points='{}', kwargs='{}'".
                           format(service_type, connection_points, kwargs))
         _id = str(uuid4())
         self.logger.debug("create_connectivity_service: service_type='{}' connection_points='{}', kwargs='{}'".
                           format(service_type, connection_points, kwargs))
         _id = str(uuid4())
-        self.connectivity[_id] = {"nb": self.counter}
+        self.connections[_id] = connection_points.copy()
         self.counter += 1
         self.counter += 1
-        return _id, self.connectivity[_id]
+        return _id, None
 
     def delete_connectivity_service(self, service_uuid, conn_info=None):
         """Disconnect multi-site endpoints previously connected
 
     def delete_connectivity_service(self, service_uuid, conn_info=None):
         """Disconnect multi-site endpoints previously connected
@@ -100,7 +100,10 @@ class FakeConnector(WimConnector):
         """
         self.logger.debug("delete_connectivity_service: service_uuid='{}' conn_info='{}'".format(service_uuid,
                                                                                                  conn_info))
         """
         self.logger.debug("delete_connectivity_service: service_uuid='{}' conn_info='{}'".format(service_uuid,
                                                                                                  conn_info))
-        self.connectivity.pop(service_uuid, None)
+        if service_uuid not in self.connections:
+            raise SdnConnectorError("connectivity {} not found".format(service_uuid),
+                                    http_code=HTTPStatus.NOT_FOUND.value)
+        self.connections.pop(service_uuid, None)
         return None
 
     def edit_connectivity_service(self, service_uuid, conn_info=None,
         return None
 
     def edit_connectivity_service(self, service_uuid, conn_info=None,
@@ -112,6 +115,10 @@ class FakeConnector(WimConnector):
         """
         self.logger.debug("edit_connectivity_service: service_uuid='{}' conn_info='{}', connection_points='{}'"
                           "kwargs='{}'".format(service_uuid, conn_info, connection_points, kwargs))
         """
         self.logger.debug("edit_connectivity_service: service_uuid='{}' conn_info='{}', connection_points='{}'"
                           "kwargs='{}'".format(service_uuid, conn_info, connection_points, kwargs))
+        if service_uuid not in self.connections:
+            raise SdnConnectorError("connectivity {} not found".format(service_uuid),
+                                    http_code=HTTPStatus.NOT_FOUND.value)
+        self.connections[service_uuid] = connection_points.copy()
         return None
 
     def clear_all_connectivity_services(self):
         return None
 
     def clear_all_connectivity_services(self):
@@ -123,7 +130,7 @@ class FakeConnector(WimConnector):
 
         """
         self.logger.debug("clear_all_connectivity_services")
 
         """
         self.logger.debug("clear_all_connectivity_services")
-        self.connectivity.clear()
+        self.connections.clear()
         return None
 
     def get_all_active_connectivity_services(self):
         return None
 
     def get_all_active_connectivity_services(self):
@@ -131,7 +138,7 @@ class FakeConnector(WimConnector):
         WIM.
 
         Raises:
         WIM.
 
         Raises:
-            WimConnectorException: In case of error.
+            SdnConnectorException: In case of error.
         """
         self.logger.debug("get_all_active_connectivity_services")
         """
         self.logger.debug("get_all_active_connectivity_services")
-        return self.connectivity
+        return self.connections
diff --git a/RO/osm_ro/wim/wimconn_ietfl2vpn.py b/RO/osm_ro/wim/wimconn_ietfl2vpn.py
deleted file mode 100644 (file)
index dc7cc97..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-# -*- coding: utf-8 -*-
-##
-# Copyright 2018 Telefonica
-# All Rights Reserved.
-#
-# Contributors: Oscar Gonzalez de Dios, Manuel Lopez Bravo, Guillermo Pajares Martin
-# 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.
-#
-# This work has been performed in the context of the Metro-Haul project -
-# funded by the European Commission under Grant number 761727 through the
-# Horizon 2020 program.
-##
-"""The WIM connector is responsible for establishing wide area network
-connectivity.
-
-This WIM connector implements the standard IETF RFC 8466 "A YANG Data
- Model for Layer 2 Virtual Private Network (L2VPN) Service Delivery"
-
-It receives the endpoints and the necessary details to request
-the Layer 2 service.
-"""
-import requests
-import uuid
-import logging
-from .wimconn import WimConnector, WimConnectorError
-"""CHeck layer where we move it"""
-
-
-class WimconnectorIETFL2VPN(WimConnector):
-
-    def __init__(self, wim, wim_account, config=None, logger=None):
-        """IETF L2VPM WIM connector
-
-        Arguments: (To be completed)
-            wim (dict): WIM record, as stored in the database
-            wim_account (dict): WIM account record, as stored in the database
-        """
-        self.logger = logging.getLogger('openmano.wimconn.ietfl2vpn')
-        super(WimconnectorIETFL2VPN, self).__init__(wim, wim_account, config, logger)
-        self.headers = {'Content-Type': 'application/json'}
-        self.mappings = {m['wan_service_endpoint_id']: m
-                         for m in self.service_endpoint_mapping}
-        self.user = wim_account.get("user")
-        self.passwd = wim_account.get("passwd")
-        if self.user and self.passwd is not None:
-            self.auth = (self.user, self.passwd)
-        else:
-            self.auth = None
-        self.logger.info("IETFL2VPN Connector Initialized.")
-
-    def check_credentials(self):
-        endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
-        try:
-            response = requests.get(endpoint, auth=self.auth)    
-            http_code = response.status_code
-        except requests.exceptions.RequestException as e:
-            raise WimConnectorError(e.message, http_code=503)
-
-        if http_code != 200:
-            raise WimConnectorError("Failed while authenticating", http_code=http_code)
-        self.logger.info("Credentials checked")
-
-    def get_connectivity_service_status(self, service_uuid, conn_info=None):
-        """Monitor the status of the connectivity service stablished
-
-        Arguments:
-            service_uuid: Connectivity service unique identifier
-
-        Returns:
-            Examples::
-                {'wim_status': 'ACTIVE'}
-                {'wim_status': 'INACTIVE'}
-                {'wim_status': 'DOWN'}
-                {'wim_status': 'ERROR'}
-        """
-        try:
-            self.logger.info("Sending get connectivity service stuatus")
-            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
-                self.wim["wim_url"], service_uuid)
-            response = requests.get(servicepoint, auth=self.auth)
-            if response.status_code != requests.codes.ok:
-                raise WimConnectorError("Unable to obtain connectivity servcice status", http_code=response.status_code)
-            service_status = {'wim_status': 'ACTIVE'}
-            return service_status
-        except requests.exceptions.ConnectionError:
-            raise WimConnectorError("Request Timeout", http_code=408)
-               
-    def search_mapp(self, connection_point):
-        id = connection_point['service_endpoint_id']
-        if id not in self.mappings:         
-            raise WimConnectorError("Endpoint {} not located".format(str(id)))
-        else:
-            return self.mappings[id]
-
-    def create_connectivity_service(self, service_type, connection_points, **kwargs):
-        """Stablish WAN connectivity between the endpoints
-
-        Arguments:
-            service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
-                ``L3``.
-            connection_points (list): each point corresponds to
-                an entry point from the DC to the transport network. One
-                connection point serves to identify the specific access and
-                some other service parameters, such as encapsulation type.
-                Represented by a dict as follows::
-
-                    {
-                      "service_endpoint_id": ..., (str[uuid])
-                      "service_endpoint_encapsulation_type": ...,
-                           (enum: none, dot1q, ...)
-                      "service_endpoint_encapsulation_info": {
-                        ... (dict)
-                        "vlan": ..., (int, present if encapsulation is dot1q)
-                        "vni": ... (int, present if encapsulation is vxlan),
-                        "peers": [(ipv4_1), (ipv4_2)]
-                            (present if encapsulation is vxlan)
-                      }
-                    }
-
-              The service endpoint ID should be previously informed to the WIM
-              engine in the RO when the WIM port mapping is registered.
-
-        Keyword Arguments:
-            bandwidth (int): value in kilobytes
-            latency (int): value in milliseconds
-
-        Other QoS might be passed as keyword arguments.
-
-        Returns:
-            tuple: ``(service_id, conn_info)`` containing:
-               - *service_uuid* (str): UUID of the established connectivity
-                  service
-               - *conn_info* (dict or None): Information to be stored at the
-                 database (or ``None``). This information will be provided to
-                 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
-                 **MUST** be JSON/YAML-serializable (plain data structures).
-
-        Raises:
-            WimConnectorException: In case of error.
-        """
-        if service_type == "ELINE":
-            if len(connection_points) > 2:
-                raise WimConnectorError('Connections between more than 2 endpoints are not supported')
-            if len(connection_points) < 2:
-                raise WimConnectorError('Connections must be of at least 2 endpoints')
-            """ First step, create the vpn service """    
-            uuid_l2vpn = str(uuid.uuid4())
-            vpn_service = {}
-            vpn_service["vpn-id"] = uuid_l2vpn
-            vpn_service["vpn-scv-type"] = "vpws"
-            vpn_service["svc-topo"] = "any-to-any"
-            vpn_service["customer-name"] = "osm"
-            vpn_service_list = []
-            vpn_service_list.append(vpn_service)
-            vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
-            response_service_creation = None
-            conn_info = []
-            self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
-            try:
-                endpoint_service_creation = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
-                    self.wim["wim_url"])
-                response_service_creation = requests.post(endpoint_service_creation, headers=self.headers,
-                                                          json=vpn_service_l, auth=self.auth)
-            except requests.exceptions.ConnectionError:
-                raise WimConnectorError("Request to create service Timeout", http_code=408)
-            if response_service_creation.status_code == 409:
-                raise WimConnectorError("Service already exists", http_code=response_service_creation.status_code)
-            elif response_service_creation.status_code != requests.codes.created:
-                raise WimConnectorError("Request to create service not accepted",
-                                        http_code=response_service_creation.status_code)
-            """ Second step, create the connections and vpn attachments """   
-            for connection_point in connection_points:
-                connection_point_wan_info = self.search_mapp(connection_point)
-                site_network_access = {}
-                connection = {}
-                if connection_point["service_endpoint_encapsulation_type"] != "none":
-                    if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
-                        """ The connection is a VLAN """
-                        connection["encapsulation-type"] = "dot1q-vlan-tagged"
-                        tagged = {}
-                        tagged_interf = {}
-                        service_endpoint_encapsulation_info = connection_point["service_endpoint_encapsulation_info"]
-                        if service_endpoint_encapsulation_info["vlan"] is None:
-                            raise WimConnectorError("VLAN must be provided")
-                        tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info["vlan"]
-                        tagged["dot1q-vlan-tagged"] = tagged_interf
-                        connection["tagged-interface"] = tagged
-                    else:
-                        raise NotImplementedError("Encapsulation type not implemented")
-                site_network_access["connection"] = connection
-                self.logger.info("Sending connection:{}".format(connection))
-                vpn_attach = {}
-                vpn_attach["vpn-id"] = uuid_l2vpn
-                vpn_attach["site-role"] = vpn_service["svc-topo"]+"-role"
-                site_network_access["vpn-attachment"] = vpn_attach
-                self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
-                uuid_sna = str(uuid.uuid4())
-                site_network_access["network-access-id"] = uuid_sna
-                site_network_access["bearer"] = connection_point_wan_info["wan_service_mapping_info"]["bearer"]
-                site_network_accesses = {}
-                site_network_access_list = []
-                site_network_access_list.append(site_network_access)
-                site_network_accesses["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
-                conn_info_d = {}
-                conn_info_d["site"] = connection_point_wan_info["wan_service_mapping_info"]["site-id"]
-                conn_info_d["site-network-access-id"] = site_network_access["network-access-id"]
-                conn_info_d["mapping"] = None
-                conn_info.append(conn_info_d)
-                try:
-                    endpoint_site_network_access_creation = \
-                        "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
-                            self.wim["wim_url"], connection_point_wan_info["wan_service_mapping_info"]["site-id"])
-                    response_endpoint_site_network_access_creation = requests.post(
-                        endpoint_site_network_access_creation,
-                        headers=self.headers,
-                        json=site_network_accesses,
-                        auth=self.auth)
-                    
-                    if response_endpoint_site_network_access_creation.status_code == 409:
-                        self.delete_connectivity_service(vpn_service["vpn-id"])
-                        raise WimConnectorError("Site_Network_Access with ID '{}' already exists".format(
-                            site_network_access["network-access-id"]),
-                            http_code=response_endpoint_site_network_access_creation.status_code)
-                    
-                    elif response_endpoint_site_network_access_creation.status_code == 400:
-                        self.delete_connectivity_service(vpn_service["vpn-id"])
-                        raise WimConnectorError("Site {} does not exist".format(
-                            connection_point_wan_info["wan_service_mapping_info"]["site-id"]),
-                            http_code=response_endpoint_site_network_access_creation.status_code)
-                    
-                    elif response_endpoint_site_network_access_creation.status_code != requests.codes.created and \
-                            response_endpoint_site_network_access_creation.status_code != requests.codes.no_content:
-                        self.delete_connectivity_service(vpn_service["vpn-id"])
-                        raise WimConnectorError("Request no accepted",
-                                                http_code=response_endpoint_site_network_access_creation.status_code)
-                
-                except requests.exceptions.ConnectionError:
-                    self.delete_connectivity_service(vpn_service["vpn-id"])
-                    raise WimConnectorError("Request Timeout", http_code=408)
-            return uuid_l2vpn, conn_info
-        
-        else:
-            raise NotImplementedError
-
-    def delete_connectivity_service(self, service_uuid, conn_info=None):
-        """Disconnect multi-site endpoints previously connected
-
-        This method should receive as the first argument the UUID generated by
-        the ``create_connectivity_service``
-        """
-        try:
-            self.logger.info("Sending delete")
-            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
-                self.wim["wim_url"], service_uuid)
-            response = requests.delete(servicepoint, auth=self.auth)
-            if response.status_code != requests.codes.no_content:
-                raise WimConnectorError("Error in the request", http_code=response.status_code)
-        except requests.exceptions.ConnectionError:
-            raise WimConnectorError("Request Timeout", http_code=408)
-
-    def edit_connectivity_service(self, service_uuid, conn_info=None,
-                                  connection_points=None, **kwargs):
-        """Change an existing connectivity service, see
-        ``create_connectivity_service``"""
-
-        # sites = {"sites": {}}
-        # site_list = []
-        vpn_service = {}
-        vpn_service["svc-topo"] = "any-to-any"
-        counter = 0
-        for connection_point in connection_points:
-            site_network_access = {}
-            connection_point_wan_info = self.search_mapp(connection_point)
-            params_site = {}
-            params_site["site-id"] = connection_point_wan_info["wan_service_mapping_info"]["site-id"]
-            params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
-            device_site = {}
-            device_site["device-id"] = connection_point_wan_info["device-id"]
-            params_site["devices"] = device_site
-            # network_access = {}
-            connection = {}
-            if connection_point["service_endpoint_encapsulation_type"] != "none":
-                if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
-                    """ The connection is a VLAN """
-                    connection["encapsulation-type"] = "dot1q-vlan-tagged"
-                    tagged = {}
-                    tagged_interf = {}
-                    service_endpoint_encapsulation_info = connection_point["service_endpoint_encapsulation_info"]
-                    if service_endpoint_encapsulation_info["vlan"] is None:
-                        raise WimConnectorError("VLAN must be provided")
-                    tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info["vlan"]
-                    tagged["dot1q-vlan-tagged"] = tagged_interf
-                    connection["tagged-interface"] = tagged
-                else:
-                    raise NotImplementedError("Encapsulation type not implemented")
-            site_network_access["connection"] = connection
-            vpn_attach = {}
-            vpn_attach["vpn-id"] = service_uuid
-            vpn_attach["site-role"] = vpn_service["svc-topo"]+"-role"
-            site_network_access["vpn-attachment"] = vpn_attach
-            uuid_sna = conn_info[counter]["site-network-access-id"]
-            site_network_access["network-access-id"] = uuid_sna
-            site_network_access["bearer"] = connection_point_wan_info["wan_service_mapping_info"]["bearer"]
-            site_network_accesses = {}
-            site_network_access_list = []
-            site_network_access_list.append(site_network_access)
-            site_network_accesses["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
-            try:
-                endpoint_site_network_access_edit = \
-                    "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
-                        self.wim["wim_url"], connection_point_wan_info["wan_service_mapping_info"]["site-id"])
-                response_endpoint_site_network_access_creation = requests.put(endpoint_site_network_access_edit,
-                                                                              headers=self.headers,
-                                                                              json=site_network_accesses,
-                                                                              auth=self.auth)
-                if response_endpoint_site_network_access_creation.status_code == 400:
-                    raise WimConnectorError("Service does not exist",
-                                            http_code=response_endpoint_site_network_access_creation.status_code)
-                elif response_endpoint_site_network_access_creation.status_code != 201 and \
-                        response_endpoint_site_network_access_creation.status_code != 204:
-                    raise WimConnectorError("Request no accepted",
-                                            http_code=response_endpoint_site_network_access_creation.status_code)
-            except requests.exceptions.ConnectionError:
-                raise WimConnectorError("Request Timeout", http_code=408)
-            counter += 1
-        return None
-
-    def clear_all_connectivity_services(self):
-        """Delete all WAN Links corresponding to a WIM"""
-        try:
-            self.logger.info("Sending clear all connectivity services")
-            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
-            response = requests.delete(servicepoint, auth=self.auth)
-            if response.status_code != requests.codes.no_content:
-                raise WimConnectorError("Unable to clear all connectivity services", http_code=response.status_code)
-        except requests.exceptions.ConnectionError:
-            raise WimConnectorError("Request Timeout", http_code=408)
-
-    def get_all_active_connectivity_services(self):
-        """Provide information about all active connections provisioned by a
-        WIM
-        """
-        try:
-            self.logger.info("Sending get all connectivity services")
-            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
-            response = requests.get(servicepoint, auth=self.auth)
-            if response.status_code != requests.codes.ok:
-                raise WimConnectorError("Unable to get all connectivity services", http_code=response.status_code)
-            return response
-        except requests.exceptions.ConnectionError:
-            raise WimConnectorError("Request Timeout", http_code=408)
index 2371046..e4af3c6 100644 (file)
 # funded by the European Commission under Grant number 761727 through the
 # Horizon 2020 and 5G-PPP programmes.
 ##
 # funded by the European Commission under Grant number 761727 through the
 # Horizon 2020 and 5G-PPP programmes.
 ##
-from .wimconn import WimConnector
+from .sdnconn import SdnConnectorBase
 
 
 # TODO: Basically create this file
 
 
 
 # TODO: Basically create this file
 
-class OdlConnector(WimConnector):
+class OdlConnector(SdnConnectorBase):
     def get_connectivity_service_status(self, link_uuid):
         raise NotImplementedError
 
     def get_connectivity_service_status(self, link_uuid):
         raise NotImplementedError
 
index 731b505..973b82d 100644 (file)
@@ -1,3 +1,17 @@
+##
+# 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.
+##
+
 PyYAML
 bottle
 MySQL-python
 PyYAML
 bottle
 MySQL-python
index fb76079..1b998b6 100644 (file)
@@ -1,3 +1,16 @@
+##
+# 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.
+##
 nsd:nsd-catalog:
     nsd:
     -   id:          test_2vdu_nsd
 nsd:nsd-catalog:
     nsd:
     -   id:          test_2vdu_nsd
index e790a9c..c95162e 100644 (file)
@@ -1,3 +1,16 @@
+##
+# 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.
+##
 vnfd-catalog:
     vnfd:
     -   connection-point:
 vnfd-catalog:
     vnfd:
     -   connection-point:
index 6c4b6cf..ad574a1 100644 (file)
@@ -1,3 +1,16 @@
+##
+# 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.
+##
 vnfd-catalog:
     vnfd:
     -   connection-point:
 vnfd-catalog:
     vnfd:
     -   connection-point:
index b1640eb..b48eaa3 100755 (executable)
@@ -51,3 +51,16 @@ cp RO-VIM-azure/deb_dist/python3-osm-rovim-azure_*.deb deb_dist/
 # VIM Opennebula plugin
 make -C RO-VIM-opennebula clean package
 cp RO-VIM-opennebula/deb_dist/python3-osm-rovim-opennebula_*.deb deb_dist/
 # VIM Opennebula plugin
 make -C RO-VIM-opennebula clean package
 cp RO-VIM-opennebula/deb_dist/python3-osm-rovim-opennebula_*.deb deb_dist/
+
+# SDN Dynpack plugin
+make -C RO-SDN-dynpac clean package
+cp RO-SDN-dynpac/deb_dist/python3-osm-rosdn-dynpac_*.deb deb_dist/
+
+# SDN Tapi plugin
+make -C RO-SDN-tapi clean package
+cp RO-SDN-tapi/deb_dist/python3-osm-rosdn-tapi_*.deb deb_dist/
+
+# SDN Onos openflow
+make -C RO-SDN-onos_openflow clean package
+cp RO-SDN-onos_openflow/deb_dist/python3-osm-rosdn-onosof_*.deb deb_dist/
+