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 && \
-  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
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
-
 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-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/*
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 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
 ===========
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
 
+##
+# 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"
index 096a21a..5d03b96 100755 (executable)
@@ -36,7 +36,7 @@ QUIET_MODE=""
 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)
@@ -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 6000004 ] && DB_VERSION=40  #6.0.4 =>  40
 #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';"
 }
+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
 
 
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
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.
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
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.
 --
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 -*-
+##
+# 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
 
index 13e19ed..7285142 100644 (file)
@@ -1,4 +1,17 @@
 # -*- 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`
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 -*-
+##
+# 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
index af32545..e015758 100644 (file)
@@ -1,4 +1,18 @@
 # -*- 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
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`
 
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 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 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
@@ -64,11 +62,12 @@ from pkg_resources import iter_entry_points
 
 
 # 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
 #
@@ -77,7 +76,7 @@ global global_config
 # WIM
 global wim_engine
 wim_engine  = None
-global wimconn_imported
+global sdnconn_imported
 #
 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
-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 = {}
 #
@@ -105,13 +104,23 @@ db_lock = Lock()
 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
-    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:
-        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
@@ -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):
-    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'])
-    global ovim
 
     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:
+        if "rosdn_fake" not in plugins:
+            plugins["rosdn_fake"] = FakeConnector
         # starts ovim library
-        ovim = ovim_module.ovim(ovim_configuration)
+        ovim = Sdn(db, plugins)
 
         global wim_engine
-        wim_engine = wim or WimEngine(persistence)
+        wim_engine = wim or WimEngine(persistence, plugins)
         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:
-                _load_vim_plugin(plugin_name)
+                _load_plugin(plugin_name, type="vim")
 
             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:
-                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'])
-            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
+        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)
-    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}' "\
@@ -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:
-                    _load_vim_plugin(plugin_name)
+                    _load_plugin(plugin_name, type="vim")
                 except NfvoException as e:
                     if ignore_errors:
                         logger.error("{}".format(e))
@@ -3030,6 +3037,14 @@ def update(d, u):
     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...")
@@ -3094,6 +3109,7 @@ def create_instance(mydb, tenant_id, instance_dict):
     }
 
     # Auxiliary dictionaries from x to y
+    sce_net2wim_instance = {}
     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 = []
+        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
@@ -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] = {}
+            sce_net2wim_instance[sce_net_uuid] = {}
             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
+                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"],
@@ -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,
-                    '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 = {
@@ -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,
+            "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,
@@ -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,
+            "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:
@@ -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_wim_nets": wan_links},
+            {"instance_wim_nets": db_instance_wim_nets + wan_links},
             {"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
-    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"
-        elif isinstance(e, wimconn.WimConnectorError):
+        elif isinstance(e, sdnconn.SdnConnectorError):
             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"]
+    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"]
@@ -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"]
+    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"):
+        vim = myvims[sce_vnf["datacenter"]]
         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']:
@@ -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']] = {}
-        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
+
+        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,
@@ -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,
+            'sdn_net_id': sdn_net_id,
         }
         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))}
+        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,
@@ -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)
         }
+        net2task_id[sce_vnf['uuid']][net['uuid']] = task_index
         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]
+                        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:
@@ -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,
+                "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',
+                'model': iface['model'],
                 '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)
+    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
 
@@ -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)
-    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")
@@ -4943,7 +5061,7 @@ def new_datacenter(mydb, datacenter_descriptor):
     # 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:
@@ -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'])
-        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
@@ -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):
-    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)
+    vim_threads["running"][controller_id].insert_task("reload")
     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"]
-        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):
-    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,
@@ -5703,24 +5830,25 @@ def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id):
 
     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)
+        link_config = link["service_mapping_info"]
         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_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["compute_node"] = link["compute_node"]
+            content["compute_node"] = link.get("interface_id")
             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])
@@ -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 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):
     """
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",
-                           "wim_port_mappings", "vim_wim_actions",
+                           "wim_port_mappings", "vim_wim_actions", "instance_interfaces",
                            "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"\
-                        " 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()
@@ -1010,6 +1010,13 @@ class nfvo_db(db_base.db_base):
         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"\
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$"
-__version__ = "6.0.3.post5"
+__version__ = "6.0.4.post6"
 version_date = "Oct 2019"
-database_version = 39      # expected database schema version
+database_version = 40      # expected database schema version
 
 global global_config
 global logger
@@ -327,7 +327,7 @@ if __name__ == "__main__":
 
         # 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)
 
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
index 1a8750d..94183e9 100755 (executable)
@@ -1,4 +1,18 @@
 #!/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, ...
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 -*-
+##
+# 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
 
index 9fd71cf..c62c954 100644 (file)
@@ -1,4 +1,18 @@
 # -*- 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
index 1e1e6d2..728b659 100644 (file)
@@ -81,12 +81,10 @@ import time
 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
-# TODO py3 BEGIN
-class ovimException(Exception):
-    pass
-# TODO py3 END
+from http import HTTPStatus
 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
 
-    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
@@ -122,60 +119,114 @@ class vim_thread(threading.Thread):
         """
         threading.Thread.__init__(self)
         self.plugins = plugins
+        self.plugin_name = "unknown"
         self.vim = None
+        self.sdnconnector = None
+        self.sdnconn_config = 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.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:
-            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]
 
-        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_lock = db_lock
 
         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):
         """
@@ -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",
-                                               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
                                                       },
@@ -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,
-                                        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,
@@ -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",
-                                                     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"],
@@ -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,
-                                                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"],
@@ -285,7 +336,7 @@ class vim_thread(threading.Thread):
         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"],
@@ -308,7 +359,7 @@ class vim_thread(threading.Thread):
 
             # 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"]
                                        })
@@ -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")},
-                                    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"]
                                            })
@@ -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
-                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
-                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"),
@@ -412,6 +461,8 @@ class vim_thread(threading.Thread):
                                             "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
@@ -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_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
-        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:
@@ -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},
-                                        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
@@ -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
-            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"]}
@@ -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"]))
+            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
@@ -666,7 +729,7 @@ class vim_thread(threading.Thread):
                             "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"],
@@ -683,7 +746,7 @@ class vim_thread(threading.Thread):
             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"],
                        })
@@ -723,7 +786,7 @@ class vim_thread(threading.Thread):
     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
 
@@ -848,19 +911,18 @@ class vim_thread(threading.Thread):
             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"]
-        interfaces = task["extra"].get("interfaces", ())
+        interfaces = task["extra"].get("interfaces", ())
         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
@@ -929,7 +991,6 @@ class vim_thread(threading.Thread):
 
     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:
@@ -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])
 
-            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["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
-            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
-        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))
-            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"]
-        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"))
-            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
-        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):
@@ -1039,6 +1081,160 @@ class vim_thread(threading.Thread):
         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
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 sys import exc_info
+from sys import exc_info
 from uuid import uuid4
 
 from ..utils import remove_none_items
@@ -57,22 +57,33 @@ from .errors import (
     DbBaseException,
     NoWimConnectedToDatacenters,
     UnexpectedDatabaseError,
-    WimAccountNotActive
+    WimAccountNotActive,
+    UndefinedWimConnector
 )
 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.
     """
-    def __init__(self, persistence, logger=None, ovim=None):
+    def __init__(self, persistence, plugins, logger=None, ovim=None):
         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
 
+    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
 
@@ -88,6 +99,10 @@ class WimEngine(object):
         """
         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:
@@ -415,7 +430,7 @@ class WimEngine(object):
         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):
@@ -466,7 +481,7 @@ class WimEngine(object):
         """
         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
@@ -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)
-        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"""
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"""
 
-    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):
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
-from .wimconn import WimConnectorError
+from .sdnconn import SdnConnectorError
 
 
 class FailingConnector(object):
@@ -51,32 +51,38 @@ class FailingConnector(object):
     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):
-        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):
-        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):
-        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):
-        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):
-        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):
-        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):
-        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 sys import exc_info
+from sys import exc_info
 # 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)
+        kwargs.setdefault('WHERE', {"sdn": "false"})
         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 "
-                "'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}'")
@@ -727,7 +728,7 @@ class WimPersistence(object):
         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)
 
index 101bcb1..8f9653b 100644 (file)
@@ -69,36 +69,21 @@ wim_port_mapping_desc = {
                 "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,
-                                "wan_switch_dpid": dpid_type,
-                                "wan_switch_port": port_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",
-        "enum": ["tapi", "onos", "odl", "dynpac", "fake"]
+        "enum": ["tapi", "onos", "odl", "dynpac", "fake"]
     },
     "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'),
-                          _datacenter_to_switch_port(identifier)))}
+                         _datacenter_to_switch_port(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),
-            '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
-            'wan_service_endpoint_id': id_,
+            'service_endpoint_id': id_,
             # ^  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,
@@ -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': [
-            {'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)),
-             'wan_service_mapping_info': {
+             'service_mapping_info': {
                  '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
 
-#from __future__ import unicode_literals, print_function
+# from __future__ import unicode_literals, print_function
 
 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 ..wimconn import WimConnectorError
+from ..sdnconn import SdnConnectorError
 
 
 class TestActionsWithDb(TestCaseWithDatabasePerTest):
@@ -192,7 +192,7 @@ class TestCreate(TestActionsWithDb):
 
         # 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)
@@ -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:
-            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,
@@ -294,7 +294,7 @@ class TestCreate(TestActionsWithDb):
 
         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:
index e42e53c..ab3e2d0 100644 (file)
@@ -147,9 +147,9 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
                     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
@@ -170,7 +170,7 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
         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):
@@ -224,9 +224,9 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
                     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
@@ -523,9 +523,9 @@ class TestHttpHandler(TestCaseWithDatabasePerTest):
             {'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
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
-        self.thread = WimThread(self.persist, account)
+        self.thread = WimThread(self.persist, {}, account)
         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()
-        self.thread = WimThread(self.persist, account)
+        self.thread = WimThread(self.persist, {}, account)
         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`
 
index 0d878b2..458f3d5 100644 (file)
@@ -34,7 +34,7 @@
 # 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
@@ -45,7 +45,7 @@ from .errors import (
     NoRecordFound,
     NoExternalPortFound
 )
-from .wimconn import WimConnectorError
+from .sdnconn import SdnConnectorError
 
 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
         """
-        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))
-        except WimConnectorError as ex:
+        except SdnConnectorError as 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),
-            '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()})
-        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))
@@ -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:
-                  **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
@@ -185,8 +185,8 @@ class WanLinkCreate(RefreshMixin, CreateAction):
 
             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(
@@ -200,11 +200,11 @@ class WanLinkCreate(RefreshMixin, CreateAction):
 
         # 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'))
         )
 
-        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']}
@@ -310,9 +310,9 @@ class WanLinkCreate(RefreshMixin, CreateAction):
 
     @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.
-        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'] = {
@@ -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,
-            'wim_info': {'conn_info': conn_info},
+            'sdn_info': {'conn_info': conn_info},
             '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
             )
-        except (WimConnectorError, InconsistentState,
+        except (SdnConnectorError, InconsistentState,
                 NoExternalPortFound) as ex:
             self.logger.exception(ex)
             return self.fail(
@@ -412,12 +412,12 @@ class WanLinkDelete(DeleteAction):
 
         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)
-        except (WimConnectorError, InconsistentState) as ex:
+        except (SdnConnectorError, InconsistentState) as ex:
             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 sys import exc_info
+from sys import exc_info
 from time import time, sleep
 
 import queue
@@ -60,10 +60,8 @@ from .errors import (
     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_ietfl2vpn import WimconnectorIETFL2VPN
 
 ACTIONS = {
     'instance_wim_nets': wan_link_actions.ACTIONS
@@ -71,10 +69,8 @@ ACTIONS = {
 
 CONNECTORS = {
     # "odl": wimconn_odl.OdlConnector,
-    "dynpac": DynpacConnector,
     "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
 
-    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
+            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)
+        self.plugins = plugins
+        if "rosdn_fake" not in self.plugins:
+            self.plugins["rosdn_fake"] = FakeConnector
 
         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)
-            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)
@@ -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)
-            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'
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
-from .wimconn import WimConnector
-
+from .sdnconn import SdnConnectorBase, SdnConnectorError
+from http import HTTPStatus
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
 
-class FakeConnector(WimConnector):
+class FakeConnector(SdnConnectorBase):
     """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):
-        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
@@ -54,7 +54,7 @@ class FakeConnector(WimConnector):
         """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")
@@ -71,15 +71,15 @@ class FakeConnector(WimConnector):
 
         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))
-        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):
@@ -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.connectivity[_id] = {"nb": self.counter}
+        self.connections[_id] = connection_points.copy()
         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
@@ -100,7 +100,10 @@ class FakeConnector(WimConnector):
         """
         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,
@@ -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))
+        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):
@@ -123,7 +130,7 @@ class FakeConnector(WimConnector):
 
         """
         self.logger.debug("clear_all_connectivity_services")
-        self.connectivity.clear()
+        self.connections.clear()
         return None
 
     def get_all_active_connectivity_services(self):
@@ -131,7 +138,7 @@ class FakeConnector(WimConnector):
         WIM.
 
         Raises:
-            WimConnectorException: In case of error.
+            SdnConnectorException: In case of error.
         """
         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_networ