change SDN types from tapi to ietfl2vpn, from arista to arista_cloudvision 32/8432/8
authortierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 20 Dec 2019 12:16:46 +0000 (12:16 +0000)
committertierno <alfonso.tiernosepulveda@telefonica.com>
Wed, 17 Jun 2020 09:17:09 +0000 (09:17 +0000)
Change-Id: I01d61cf64521707c92f58c668f0372b5daa010c5
Signed-off-by: tierno <alfonso.tiernosepulveda@telefonica.com>
36 files changed:
Dockerfile-local
RO-SDN-arista/Makefile [deleted file]
RO-SDN-arista/debian/python3-osm-rosdn-arista.postinst [deleted file]
RO-SDN-arista/osm_rosdn_arista/aristaConfigLet.py [deleted file]
RO-SDN-arista/osm_rosdn_arista/aristaTask.py [deleted file]
RO-SDN-arista/osm_rosdn_arista/wimconn_arista.py [deleted file]
RO-SDN-arista/requirements.txt [deleted file]
RO-SDN-arista/setup.py [deleted file]
RO-SDN-arista/stdeb.cfg [deleted file]
RO-SDN-arista/tox.ini [deleted file]
RO-SDN-arista_cloudvision/Makefile [new file with mode: 0644]
RO-SDN-arista_cloudvision/debian/python3-osm-rosdn-arista-cloudvision.postinst [new file with mode: 0755]
RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/aristaConfigLet.py [new file with mode: 0644]
RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/aristaTask.py [new file with mode: 0644]
RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/wimconn_arista.py [new file with mode: 0644]
RO-SDN-arista_cloudvision/requirements.txt [new file with mode: 0644]
RO-SDN-arista_cloudvision/setup.py [new file with mode: 0644]
RO-SDN-arista_cloudvision/stdeb.cfg [new file with mode: 0644]
RO-SDN-arista_cloudvision/tox.ini [new file with mode: 0644]
RO-SDN-ietfl2vpn/Makefile [new file with mode: 0644]
RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn/wimconn_ietfl2vpn.py [new file with mode: 0644]
RO-SDN-ietfl2vpn/requirements.txt [new file with mode: 0644]
RO-SDN-ietfl2vpn/setup.py [new file with mode: 0644]
RO-SDN-ietfl2vpn/stdeb.cfg [new file with mode: 0644]
RO-SDN-ietfl2vpn/tox.ini [new file with mode: 0644]
RO-SDN-tapi/Makefile [deleted file]
RO-SDN-tapi/osm_rosdn_tapi/wimconn_ietfl2vpn.py [deleted file]
RO-SDN-tapi/requirements.txt [deleted file]
RO-SDN-tapi/setup.py [deleted file]
RO-SDN-tapi/stdeb.cfg [deleted file]
RO-SDN-tapi/tox.ini [deleted file]
RO-client/osm_roclient/roclient.py
RO/osm_ro/openmanoclient.py
RO/osm_ro/wim/schemas.py
RO/osm_ro/wim/tests/fixtures.py
devops-stages/stage-build.sh

index 1f9a117..d8c8432 100644 (file)
@@ -57,12 +57,12 @@ RUN /root/RO/RO/osm_ro/scripts/install-osm-im.sh --develop && \
     python3 -m pip install -e /root/RO/RO-VIM-azure && \
     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 && \
+    python3 -m pip install -e /root/RO/RO-SDN-ietfl2vpn && \
     python3 -m pip install -e /root/RO/RO-SDN-onos_vpls && \
     python3 -m pip install -e /root/RO/RO-SDN-onos_openflow && \
     python3 -m pip install -e /root/RO/RO-SDN-odl_openflow && \
     python3 -m pip install -e /root/RO/RO-SDN-floodlight_openflow && \
-    python3 -m pip install -e /root/RO/RO-SDN-arista && \
+    python3 -m pip install -e /root/RO/RO-SDN-arista_cloudvision && \
     python3 -m pip install -e /root/RO/RO-SDN-juniper_contrail && \
     rm -rf /root/.cache && \
     apt-get clean && \
diff --git a/RO-SDN-arista/Makefile b/RO-SDN-arista/Makefile
deleted file mode 100644 (file)
index 1606bb3..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-##
-# 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_arista-*.tar.gz osm_rosdn_arista.egg-info .eggs
-
-package:
-       python3 setup.py --command-packages=stdeb.command sdist_dsc
-       cp debian/python3-osm-rosdn-arista.postinst deb_dist/osm-rosdn-arista*/debian/
-       cd deb_dist/osm-rosdn-arista*/ && dpkg-buildpackage -rfakeroot -uc -us
-
diff --git a/RO-SDN-arista/debian/python3-osm-rosdn-arista.postinst b/RO-SDN-arista/debian/python3-osm-rosdn-arista.postinst
deleted file mode 100755 (executable)
index d87fa26..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-##
-# 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: OSM_TECH@list.etsi.org
-##
-
-echo "POST INSTALL OSM-ROSDN-ARISTA"
-
-#Pip packages required for openstack connector
-python3 -m pip install cvprac
diff --git a/RO-SDN-arista/osm_rosdn_arista/aristaConfigLet.py b/RO-SDN-arista/osm_rosdn_arista/aristaConfigLet.py
deleted file mode 100644 (file)
index 8e34091..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-# -*- coding: utf-8 -*-\r
-##\r
-# Copyright 2019 Atos - CoE Telco NFV Team\r
-# All Rights Reserved.\r
-#\r
-# Contributors: Oscar Luis Peral, Atos\r
-#\r
-# Licensed under the Apache License, Version 2.0 (the "License"); you may\r
-# not use this file except in compliance with the License. You may obtain\r
-# a copy of the License at\r
-#\r
-#         http://www.apache.org/licenses/LICENSE-2.0\r
-#\r
-# Unless required by applicable law or agreed to in writing, software\r
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
-# License for the specific language governing permissions and limitations\r
-# under the License.\r
-#\r
-# For those usages not covered by the Apache License, Version 2.0 please\r
-# contact with: <oscarluis.peral@atos.net>\r
-#\r
-# Neither the name of Atos nor the names of its\r
-# contributors may be used to endorse or promote products derived from\r
-# this software without specific prior written permission.\r
-#\r
-# This work has been performed in the context of Arista Telefonica OSM PoC.\r
-##\r
-\r
-\r
-class AristaSDNConfigLet:\r
-    _VLAN = "VLAN"\r
-    _VXLAN = "VXLAN"\r
-    _VLAN_MLAG = "VLAN-MLAG"\r
-    _VXLAN_MLAG = "VXLAN-MLAG"\r
-    topology = _VXLAN_MLAG\r
-\r
-    def __init__(self, topology=_VXLAN_MLAG):\r
-        self.topology = topology\r
-\r
-    _basic_int ="""\r
-interface {interface}\r
-   !! service: {uuid}\r
-   switchport\r
-   switchport mode {type}\r
-   switchport {switchport_def}\r
-!\r
-"""\r
-    _int_SRIOV = "trunk group {service}{vlan_id}"\r
-    _int_PASSTROUGH = "access vlan {vlan_id}"\r
-\r
-    def _get_interface(self, uuid, interface, vlan_id, s_type, index, i_type):\r
-        if i_type == "trunk":\r
-            switchport_def = self._int_SRIOV.format(service=s_type, vlan_id=vlan_id)\r
-        else:\r
-            switchport_def = self._int_PASSTROUGH.format(vlan_id=vlan_id)\r
-        return self._basic_int.format(uuid=uuid,\r
-                                      interface=interface,\r
-                                      type=i_type,\r
-                                      switchport_def=switchport_def)\r
-\r
-    def getElan_sriov(self, uuid, interface, vlan_id, index):\r
-        return self._get_interface(uuid, interface, vlan_id, "ELAN", index, "trunk")\r
-\r
-    def getEline_sriov(self, uuid, interface, vlan_id, index):\r
-        return self._get_interface(uuid, interface, vlan_id, "ELINE", index, "trunk")\r
-\r
-    def getElan_passthrough(self, uuid, interface, vlan_id, index):\r
-        return self._get_interface(uuid, interface, vlan_id, "ELAN", index, "dot1q-tunnel")\r
-\r
-    def getEline_passthrough(self, uuid, interface, vlan_id, index):\r
-        return self._get_interface(uuid, interface, vlan_id, "ELINE", index, "dot1q-tunnel")\r
-\r
-    _basic_vlan ="""\r
-vlan {vlan}\r
-   !! service: {service} {vlan} {uuid}\r
-   name {service}{vlan}\r
-   trunk group {service}{vlan}\r
-"""\r
-    _basic_mlag ="""   trunk group MLAGPEER\r
-"""\r
-    _basic_vxlan ="""interface VXLAN1\r
-   VXLAN vlan {vlan} vni {vni}\r
-"""\r
-    _basic_end ="!"\r
-\r
-    _configLet_VLAN = _basic_vlan + _basic_end\r
-    _configLet_VXLAN = _basic_vlan + _basic_vxlan + _basic_end\r
-    _configLet_VLAN_MLAG = _basic_vlan + _basic_mlag + _basic_end\r
-    _configLet_VXLAN_MLAG = _basic_vlan + _basic_mlag + _basic_vxlan + _basic_end\r
-\r
-    def _get_vlan(self, uuid, vlan_id, vni_id, s_type):\r
-        if self.topology == self._VLAN:\r
-            return self._configLet_VLAN.format(service=s_type, vlan=vlan_id, uuid=uuid)\r
-        if self.topology == self._VLAN_MLAG:\r
-            return self._configLet_VLAN_MLAG.format(service=s_type, vlan=vlan_id, uuid=uuid)\r
-        if self.topology == self._VXLAN:\r
-            return self._configLet_VXLAN.format(service=s_type, vlan=vlan_id, uuid=uuid, vni=vni_id)\r
-        if self.topology == self._VXLAN_MLAG:\r
-            return self._configLet_VXLAN_MLAG.format(service=s_type, vlan=vlan_id, uuid=uuid, vni=vni_id)\r
-\r
-    def getElan_vlan(self, uuid, vlan_id, vni_id):\r
-        return self._get_vlan(uuid, vlan_id, vni_id, "ELAN")\r
-\r
-    def getEline_vlan(self, uuid, vlan_id, vni_id):\r
-        return self._get_vlan(uuid, vlan_id, vni_id, "ELINE")\r
-\r
-    _configLet_BGP = """\r
-router bgp {bgp}\r
-    vlan {vlan}\r
-    !! service: {uuid}\r
-        rd {loopback}:{vni}\r
-        route-target both {vni}:{vni}\r
-        redistribute learned\r
-!\r
-"""\r
-\r
-    def _get_bgp(self, uuid, vlan_id, vni_id, loopback0, bgp, s_type):\r
-        if self.topology == self._VXLAN or self.topology == self._VXLAN_MLAG:\r
-            return self._configLet_BGP.format(uuid=uuid,\r
-                                              bgp=bgp,\r
-                                              vlan=vlan_id,\r
-                                              loopback=loopback0,\r
-                                              vni=vni_id)\r
-\r
-\r
-    def getElan_bgp(self, uuid, vlan_id, vni_id, loopback0, bgp):\r
-        return self._get_bgp(uuid, vlan_id, vni_id, loopback0, bgp, "ELAN")\r
-\r
-    def getEline_bgp(self, uuid, vlan_id, vni_id, loopback0, bgp):\r
-        return self._get_bgp(uuid, vlan_id, vni_id, loopback0, bgp, "ELINE")\r
diff --git a/RO-SDN-arista/osm_rosdn_arista/aristaTask.py b/RO-SDN-arista/osm_rosdn_arista/aristaTask.py
deleted file mode 100644 (file)
index a338afd..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-\r
-##\r
-# Copyright 2019 Atos - CoE Telco NFV Team\r
-# All Rights Reserved.\r
-#\r
-# Contributors: Oscar Luis Peral, Atos\r
-#\r
-# Licensed under the Apache License, Version 2.0 (the "License"); you may\r
-# not use this file except in compliance with the License. You may obtain\r
-# a copy of the License at\r
-#\r
-#         http://www.apache.org/licenses/LICENSE-2.0\r
-#\r
-# Unless required by applicable law or agreed to in writing, software\r
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
-# License for the specific language governing permissions and limitations\r
-# under the License.\r
-#\r
-# For those usages not covered by the Apache License, Version 2.0 please\r
-# contact with: <oscarluis.peral@atos.net>\r
-#\r
-# Neither the name of Atos nor the names of its\r
-# contributors may be used to endorse or promote products derived from\r
-# this software without specific prior written permission.\r
-#\r
-# This work has been performed in the context of Arista Telefonica OSM PoC.\r
-##\r
-import time\r
-\r
-\r
-class AristaCVPTask:\r
-    def __init__(self, cvpClientApi):\r
-        self.cvpClientApi = cvpClientApi\r
-\r
-    def __get_id(self, task):\r
-        return task.get("workOrderId")\r
-\r
-    def __get_state(self, task):\r
-        return task.get("workOrderUserDefinedStatus")\r
-\r
-    def __execute_task(self, task_id):\r
-        return self.cvpClientApi.execute_task(task_id)\r
-\r
-    def __cancel_task(self, task_id):\r
-        return self.cvpClientApi.cancel_task(task_id)\r
-\r
-    def __apply_state(self, task, state):\r
-        t_id = self.__get_id(task)\r
-        self.cvpClientApi.add_note_to_task(t_id, "Executed by OSM")\r
-        if state == "executed":\r
-            return self.__execute_task(t_id)\r
-        elif state == "cancelled":\r
-            return self.__cancel_task(t_id)\r
-\r
-    def __actionable(self, state):\r
-        return state in ["Pending"]\r
-\r
-    def __terminal(self, state):\r
-        return state in ["Completed", "Cancelled"]\r
-\r
-    def __state_is_different(self, task, target):\r
-        return self.__get_state(task) != target\r
-\r
-    def update_all_tasks(self, data):\r
-        new_data = dict()\r
-        for task_id in data.keys():\r
-            res = self.cvpClientApi.get_task_by_id(task_id)\r
-            new_data[task_id] = res\r
-        return new_data\r
-\r
-    def get_pending_tasks(self):\r
-        return self.cvpClientApi.get_tasks_by_status('Pending')\r
-\r
-    def get_pending_tasks_old(self):\r
-        taskList = []\r
-        tasksField = {'workOrderId': 'workOrderId',\r
-                      'workOrderState': 'workOrderState',\r
-                      'currentTaskName': 'currentTaskName',\r
-                      'description': 'description',\r
-                      'workOrderUserDefinedStatus':\r
-                      'workOrderUserDefinedStatus',\r
-                      'note': 'note',\r
-                      'taskStatus': 'taskStatus',\r
-                      'workOrderDetails': 'workOrderDetails'}\r
-        tasks = self.cvpClientApi.get_tasks_by_status('Pending')\r
-        # Reduce task data to required fields\r
-        for task in tasks:\r
-            taskFacts = {}\r
-            for field in task.keys():\r
-                if field in tasksField:\r
-                    taskFacts[tasksField[field]] = task[field]\r
-            taskList.append(taskFacts)\r
-        return taskList\r
-\r
-    def task_action(self, tasks, wait, state):\r
-        changed = False\r
-        data = dict()\r
-        warnings = list()\r
-\r
-        at = [t for t in tasks if self.__actionable(self.__get_state(t))]\r
-        actionable_tasks = at\r
-\r
-        if len(actionable_tasks) == 0:\r
-            warnings.append("No actionable tasks found on CVP")\r
-            return changed, data, warnings\r
-\r
-        for task in actionable_tasks:\r
-            if self.__state_is_different(task, state):\r
-                self.__apply_state(task, state)\r
-                changed = True\r
-                data[self.__get_id(task)] = task\r
-\r
-        if wait == 0:\r
-            return changed, data, warnings\r
-\r
-        start = time.time()\r
-        now = time.time()\r
-        while (now - start) < wait:\r
-            data = self.update_all_tasks(data)\r
-            if all([self.__terminal(self.__get_state(t)) for t in data.values()]):\r
-                break\r
-            time.sleep(1)\r
-            now = time.time()\r
-\r
-        if wait:\r
-            for i, task in data.items():\r
-                if not self.__terminal(self.__get_state(task)):\r
-                    warnings.append("Task {} has not completed in {} seconds".\r
-                                    format(i, wait))\r
-\r
-        return changed, data, warnings\r
diff --git a/RO-SDN-arista/osm_rosdn_arista/wimconn_arista.py b/RO-SDN-arista/osm_rosdn_arista/wimconn_arista.py
deleted file mode 100644 (file)
index 571add6..0000000
+++ /dev/null
@@ -1,1621 +0,0 @@
-# -*- coding: utf-8 -*-
-##
-# Copyright 2019 Atos - CoE Telco NFV Team
-# All Rights Reserved.
-#
-# Contributors: Oscar Luis Peral, Atos
-#
-# 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: <oscarluis.peral@atos.net>
-#
-# Neither the name of Atos 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 Arista Telefonica OSM PoC.
-##
-from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
-import re
-import socket
-# Required by compare function
-import difflib
-# Library that uses Levenshtein Distance to calculate the differences
-# between strings.
-# from fuzzywuzzy import fuzz
-
-import logging
-import uuid
-from enum import Enum
-from requests import RequestException
-
-from cvprac.cvp_client import CvpClient
-from cvprac.cvp_api import CvpApi
-from cvprac.cvp_client_errors import CvpLoginError,  CvpSessionLogOutError, CvpApiError
-from cvprac import __version__ as cvprac_version
-
-from osm_rosdn_arista.aristaConfigLet import AristaSDNConfigLet
-from osm_rosdn_arista.aristaTask import AristaCVPTask
-
-
-class SdnError(Enum):
-    UNREACHABLE = 'Unable to reach the WIM.',
-    VLAN_INCONSISTENT = \
-        'VLAN value inconsistent between the connection points',
-    VLAN_NOT_PROVIDED = 'VLAN value not provided',
-    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",
-    INTERNAL_ERROR = "Internal error"
-
-
-class AristaSdnConnector(SdnConnectorBase):
-    """Arista class for 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``.
-
-    The access to Arista CloudVision is made through the API defined in
-        https://github.com/aristanetworks/cvprac
-    The a connectivity service consist in creating a VLAN and associate the interfaces
-    of the connection points MAC addresses to this VLAN in all the switches of the topology,
-    the BDP is also configured for this VLAN.
-
-    The Arista Cloud Vision API workflow is the following
-    -- The switch configuration is defined as a set of switch configuration commands,
-       what is called 'ConfigLet'
-    -- The ConfigLet is associated to the device (leaf switch)
-    -- Automatically a task is associated to this activity for change control, the task
-       in this stage is in 'Pending' state
-    -- The task will be executed so that the configuration is applied to the switch.
-    -- The service information is saved in the response of the creation call
-    -- All created services identification is stored in a generic ConfigLet 'OSM_metadata'
-       to keep track of the managed resources by OSM in the Arista deployment.
-    """
-    __supported_service_types = ["ELINE (L2)", "ELINE", "ELAN"]
-    __service_types_ELAN = "ELAN"
-    __service_types_ELINE = "ELINE"
-    __ELINE_num_connection_points = 2
-    __supported_service_types = ["ELINE", "ELAN"]
-    __supported_encapsulation_types = ["dot1q"]
-    __WIM_LOGGER = 'openmano.sdnconn.arista'
-    __SERVICE_ENDPOINT_MAPPING = 'service_endpoint_mapping'
-    __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"
-    __MAC_PARAM = "mac"
-    __WAN_SERVICE_ENDPOINT_PARAM = "service_endpoint_id"
-    __WAN_MAPPING_INFO_PARAM = "service_mapping_info"
-    __DEVICE_ID_PARAM = "device_id"
-    __DEVICE_INTERFACE_ID_PARAM = "device_interface_id"
-    __SW_ID_PARAM = "switch_dpid"
-    __SW_PORT_PARAM = "switch_port"
-    __VLAN_PARAM = "vlan"
-    __VNI_PARAM = "vni"
-    __SEPARATOR = '_'
-    __MANAGED_BY_OSM = '## Managed by OSM '
-    __OSM_PREFIX = "osm_"
-    __OSM_METADATA = "OSM_metadata"
-    __METADATA_PREFIX = '!## Service'
-    __EXC_TASK_EXEC_WAIT = 10
-    __ROLLB_TASK_EXEC_WAIT = 10
-    __API_REQUEST_TOUT = 60
-    __SWITCH_TAG_NAME = 'topology_type'
-    __SWITCH_TAG_VALUE = 'leaf'
-    __LOOPBACK_INTF = "Loopback0"
-    _VLAN = "VLAN"
-    _VXLAN = "VXLAN"
-    _VLAN_MLAG = "VLAN-MLAG"
-    _VXLAN_MLAG = "VXLAN-MLAG"
-
-
-    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.__regex = re.compile(
-            r'^(?:http|ftp)s?://'  # http:// or https://
-            r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
-            r'localhost|'  # localhost...
-            r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
-            r'(?::\d+)?', re.IGNORECASE)  # optional port
-        self.raiseException = True
-        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
-        if self.is_valid_destination(self.__wim.get("wim_url")):
-            self.__wim_url = self.__wim.get("wim_url")
-        else:
-            raise SdnConnectorError(message='Invalid wim_url value',
-                                    http_code=500)
-        self.__user = wim_account.get("user")
-        self.__passwd = wim_account.get("password")
-        self.client = None
-        self.cvp_inventory = None
-        self.cvp_tags = None
-        self.logger.debug("Arista SDN plugin {}, cvprac version {}, user:{} and config:{}".
-                          format(wim, cvprac_version, self.__user,
-                                 self.delete_keys_from_dict(config, ('passwd',))))
-        self.allDeviceFacts = []
-        self.taskC = None
-        try:
-            self.__load_topology()
-            self.__load_switches()
-        except SdnConnectorError as sc:
-            raise sc
-        except Exception as e:
-            raise SdnConnectorError(message="Unable to load switches from CVP",
-                                    http_code=500) from e
-        self.logger.debug("Using topology {} in Arista Leaf switches: {}".format(
-                self.topology,
-                self.delete_keys_from_dict(self.switches, ('passwd',))))
-        self.clC = AristaSDNConfigLet(self.topology)
-
-    def __load_topology(self):
-        self.topology = self._VXLAN_MLAG
-        if self.__config and self.__config.get('topology'):
-            topology = self.__config.get('topology')
-            if topology == "VLAN":
-                self.topology = self._VLAN
-            elif topology == "VXLAN":
-                self.topology = self._VXLAN
-            elif topology == "VLAN-MLAG":
-                self.topology = self._VLAN_MLAG
-            elif topology == "VXLAN-MLAG":
-                self.topology = self._VXLAN_MLAG
-
-    def __load_switches(self):
-        """ Retrieves the switches to configure in the following order
-        1.  from incoming configuration:
-        1.1 using port mapping
-              using user and password from WIM
-              retrieving Lo0 and AS from switch
-        1.2 from 'switches' parameter,
-              if any parameter is not present
-                Lo0 and AS - it will be requested to the switch
-        2.  Looking in the CloudVision inventory if not in configuration parameters
-        2.1 using the switches with the topology_type tag set to 'leaf'
-
-        All the search methods will be used
-        """
-        self.switches = {}
-        if self.__config and self.__config.get(self.__SERVICE_ENDPOINT_MAPPING):
-            for port in self.__config.get(self.__SERVICE_ENDPOINT_MAPPING):
-                switch_dpid = port.get(self.__SW_ID_PARAM)
-                if switch_dpid and switch_dpid not in self.switches:
-                    self.switches[switch_dpid] = {'passwd': self.__passwd,
-                                                  'ip': None,
-                                                  'usr': self.__user,
-                                                  'lo0': None,
-                                                  'AS': None,
-                                                  'serialNumber': None,
-                                                  'mlagPeerDevice': None}
-
-        if self.__config and self.__config.get('switches'):
-            # Not directly from json, complete one by one
-            config_switches = self.__config.get('switches')
-            for cs, cs_content in config_switches.items():
-                if cs not in self.switches:
-                    self.switches[cs] = {'passwd': self.__passwd,
-                                         'ip': None,
-                                         'usr': self.__user,
-                                         'lo0': None,
-                                         'AS': None,
-                                         'serialNumber': None,
-                                         'mlagPeerDevice': None}
-                if cs_content:
-                    self.switches[cs].update(cs_content)
-
-        # Load the rest of the data
-        if self.client == None:
-            self.client = self.__connect()
-        self.__load_inventory()
-        if not self.switches:
-            self.__get_tags(self.__SWITCH_TAG_NAME, self.__SWITCH_TAG_VALUE)
-            for device in self.allDeviceFacts:
-                # get the switches whose topology_tag is 'leaf'
-                if device['serialNumber'] in self.cvp_tags:
-                    if not self.switches.get(device['hostname']):
-                        switch_data = {'passwd': self.__passwd,
-                                       'ip': device['ipAddress'],
-                                       'usr': self.__user,
-                                       'lo0': None,
-                                       'AS': None,
-                                       'serialNumber': None,
-                                       'mlagPeerDevice': None}
-                        self.switches[device['hostname']] = switch_data
-        if len(self.switches) == 0:
-            self.logger.error("Unable to load Leaf switches from CVP")
-            return
-
-        # self.switches are switch objects, one for each switch in self.switches,
-        # used to make eAPI calls by using switch.py module
-        for s in self.switches:
-            for device in self.allDeviceFacts:
-                if device['hostname'] == s:
-                    if not self.switches[s].get('ip'):
-                        self.switches[s]['ip'] = device['ipAddress']
-                    self.switches[s]['serialNumber'] = device['serialNumber']
-                    break
-
-            # Each switch has a different loopback address,
-            # so it's a different configLet
-            if not self.switches[s].get('lo0'):
-                inf = self.__get_interface_ip(self.switches[s]['serialNumber'], self.__LOOPBACK_INTF)
-                self.switches[s]["lo0"] = inf.split('/')[0]
-            if not self.switches[s].get('AS'):
-                self.switches[s]["AS"] = self.__get_device_ASN(self.switches[s]['serialNumber'])
-        if self.topology in (self._VXLAN_MLAG, self._VLAN_MLAG):
-            for s in self.switches:
-                if not self.switches[s].get('mlagPeerDevice'):
-                    self.switches[s]['mlagPeerDevice'] = self.__get_peer_MLAG(self.switches[s]['serialNumber'])
-
-    def __check_service(self, service_type, connection_points,
-                        check_vlan=True, check_num_cp=True, kwargs=None):
-        """ Reviews the connection points elements looking for semantic errors in the incoming data
-        """
-        if service_type not in self.__supported_service_types:
-            raise Exception("The service '{}' is not supported. Only '{}' are accepted".format(
-                            service_type,
-                            self.__supported_service_types))
-
-        if check_num_cp:
-            if len(connection_points) < 2:
-                raise Exception(SdnError.CONNECTION_POINTS_SIZE)
-            if (len(connection_points) != self.__ELINE_num_connection_points and
-               service_type == self.__service_types_ELINE):
-                raise Exception(SdnError.CONNECTION_POINTS_SIZE)
-
-        if check_vlan:
-            vlan_id = ''
-            for cp in connection_points:
-                enc_type = cp.get(self.__ENCAPSULATION_TYPE_PARAM)
-                if (enc_type and
-                        enc_type not in self.__supported_encapsulation_types):
-                    raise Exception(SdnError.ENCAPSULATION_TYPE)
-                encap_info = cp.get(self.__ENCAPSULATION_INFO_PARAM)
-                cp_vlan_id = str(encap_info.get(self.__VLAN_PARAM))
-                if cp_vlan_id:
-                    if not vlan_id:
-                        vlan_id = cp_vlan_id
-                    elif vlan_id != cp_vlan_id:
-                        raise Exception(SdnError.VLAN_INCONSISTENT)
-            if not vlan_id:
-                raise Exception(SdnError.VLAN_NOT_PROVIDED)
-            if vlan_id in self.__get_srvVLANs():
-                raise Exception('VLAN {} already assigned to a connectivity service'.format(vlan_id))
-
-        # 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 check_credentials(self):
-        """Retrieves the CloudVision version information, as the easiest way
-        for testing the access to CloudVision API
-        """
-        try:
-            if self.client == None:
-                self.client = self.__connect()
-            result = self.client.api.get_cvp_info()
-            self.logger.debug(result)
-        except CvpLoginError as e:
-            self.logger.info(str(e))
-            self.client = None
-            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
-                                    http_code=401) from e
-        except Exception as ex:
-            self.client = None
-            self.logger.error(str(ex))
-            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
-                                    http_code=500) from ex
-
-    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.
-        """
-        try:
-            self.logger.debug("invoked get_connectivity_service_status '{}'".format(service_uuid))
-            if not service_uuid:
-                raise SdnConnectorError(message='No connection service UUID',
-                                        http_code=500)
-
-            self.__get_Connection()
-            if conn_info == None:
-                raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid),
-                                        http_code=500)
-
-            if 'configLetPerSwitch' in conn_info.keys():
-                c_info = conn_info
-            else:
-                c_info = None
-            cls_perSw = self.__get_serviceData(service_uuid,
-                                               conn_info['service_type'],
-                                               conn_info['vlan_id'],
-                                               c_info)
-
-            t_isCancelled = False
-            t_isFailed = False
-            t_isPending = False
-            failed_switches = []
-            for s in self.switches:
-                if len(cls_perSw[s]) > 0:
-                    for cl in cls_perSw[s]:
-                        # Fix 1030 SDN-ARISTA Key error note when deploy a NS
-                        # Added protection to check that 'note' exists and additionally
-                        # verify that it is managed by OSM
-                        if (not cls_perSw[s][0]['config'] or
-                                not cl.get('note') or
-                                self.__MANAGED_BY_OSM not in cl['note']):
-                            continue
-                        note = cl['note']
-                        t_id = note.split(self.__SEPARATOR)[1]
-                        result = self.client.api.get_task_by_id(t_id)
-                        if result['workOrderUserDefinedStatus'] == 'Completed':
-                            continue
-                        elif result['workOrderUserDefinedStatus'] == 'Cancelled':
-                            t_isCancelled = True
-                        elif result['workOrderUserDefinedStatus'] == 'Failed':
-                            t_isFailed = True
-                        else:
-                            t_isPending = True
-                        failed_switches.append(s)
-            if t_isCancelled:
-                error_msg = 'Some works were cancelled in switches: {}'.format(str(failed_switches))
-                sdn_status = 'DOWN'
-            elif t_isFailed:
-                error_msg = 'Some works failed in switches: {}'.format(str(failed_switches))
-                sdn_status = 'ERROR'
-            elif t_isPending:
-                error_msg = 'Some works are still under execution in switches: {}'.format(str(failed_switches))
-                sdn_status = 'BUILD'
-            else:
-                error_msg = ''
-                sdn_status = 'ACTIVE'
-            sdn_info = ''
-            return {'sdn_status': sdn_status,
-                    'error_msg': error_msg,
-                    'sdn_info': sdn_info}
-        except CvpLoginError as e:
-            self.logger.info(str(e))
-            self.client = None
-            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
-                                    http_code=401) from e
-        except Exception as ex:
-            self.client = None
-            self.logger.error(str(ex), exc_info=True)
-            raise SdnConnectorError(message=str(ex),
-                                    http_code=500) from ex
-
-    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
-                    "switch_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: SdnConnectorError: In case of error. Nothing should be
-                                    created in this case.
-            Provide the parameter http_code
-        """
-        try:
-            self.logger.debug("invoked create_connectivity_service '{}' ports: {}".
-                              format(service_type, connection_points))
-            self.__get_Connection()
-            self.__check_service(service_type,
-                                 connection_points,
-                                 check_vlan=True,
-                                 kwargs=kwargs)
-            service_uuid = str(uuid.uuid4())
-
-            self.logger.info("Service with uuid {} created.".
-                             format(service_uuid))
-            s_uid, s_connInf = self.__processConnection(
-                                        service_uuid,
-                                        service_type,
-                                        connection_points,
-                                        kwargs)
-            try:
-                self.__addMetadata(s_uid, service_type, s_connInf['vlan_id'])
-            except Exception as e:
-                pass
-
-            return (s_uid, s_connInf)
-        except CvpLoginError as e:
-            self.logger.info(str(e))
-            self.client = None
-            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
-                                    http_code=401) from e
-        except SdnConnectorError as sde:
-            raise sde
-        except ValueError as err:
-            self.client = None
-            self.logger.error(str(err), exc_info=True)
-            raise SdnConnectorError(message=str(err),
-                                    http_code=500) from err
-        except Exception as ex:
-            self.client = None
-            self.logger.error(str(ex), exc_info=True)
-            if self.raiseException:
-                raise ex
-            raise SdnConnectorError(message=str(ex),
-                                    http_code=500) from ex
-
-    def __processConnection(self,
-                            service_uuid,
-                            service_type,
-                            connection_points,
-                            kwargs):
-        """
-        Invoked from creation and edit methods
-
-        Process the connection points array,
-            creating a set of configuration per switch where it has to be applied
-            for creating the configuration, the switches have to be queried for obtaining:
-                - the loopback address
-                - the BGP ASN (autonomous system number)
-                - the interface name of the MAC address to add in the connectivity service
-        Once the new configuration is ready, the __updateConnection method is invoked for appling the changes
-        """
-        try:
-            cls_perSw = {}
-            cls_cp = {}
-            cl_bgp = {}
-            for s in self.switches:
-                cls_perSw[s] = []
-                cls_cp[s] = []
-            vlan_processed = False
-            vlan_id = ''
-            i = 0
-            processed_connection_points = []
-            for cp in connection_points:
-                i += 1
-                encap_info = cp.get(self.__ENCAPSULATION_INFO_PARAM)
-                if not vlan_processed:
-                    vlan_id = str(encap_info.get(self.__VLAN_PARAM))
-                    if not vlan_id:
-                        continue
-                    vni_id = encap_info.get(self.__VNI_PARAM)
-                    if not vni_id:
-                        vni_id = str(10000 + int(vlan_id))
-
-                    if service_type == self.__service_types_ELAN:
-                        cl_vlan = self.clC.getElan_vlan(service_uuid,
-                                                        vlan_id,
-                                                        vni_id)
-                    else:
-                        cl_vlan = self.clC.getEline_vlan(service_uuid,
-                                                         vlan_id,
-                                                         vni_id)
-                    vlan_processed = True
-
-                encap_type = cp.get(self.__ENCAPSULATION_TYPE_PARAM)
-                switch_id = encap_info.get(self.__SW_ID_PARAM)
-                interface = encap_info.get(self.__SW_PORT_PARAM)
-                switches = [{'name': switch_id, 'interface': interface}]
-
-                # remove those connections that are equal. This happens when several sriovs are located in the same
-                # compute node interface, that is, in the same switch and interface
-                switches = [x for x in switches if x not in processed_connection_points]
-                if not switches:
-                    continue
-                processed_connection_points += switches
-                for switch in switches:
-                    if not interface:
-                        raise SdnConnectorError(message="Connection point switch port empty for switch_dpid {}".format(switch_id),
-                                                http_code=406)
-                    # it should be only one switch where the mac is attached
-                    if encap_type == 'dot1q':
-                        # SRIOV configLet for Leaf switch mac's attached to
-                        if service_type == self.__service_types_ELAN:
-                            cl_encap = self.clC.getElan_sriov(service_uuid, interface, vlan_id, i)
-                        else:
-                            cl_encap = self.clC.getEline_sriov(service_uuid, interface, vlan_id, i)
-                    elif not encap_type:
-                        # PT configLet for Leaf switch attached to the mac
-                        if service_type == self.__service_types_ELAN:
-                            cl_encap = self.clC.getElan_passthrough(service_uuid,
-                                                                    interface,
-                                                                    vlan_id, i)
-                        else:
-                            cl_encap = self.clC.getEline_passthrough(service_uuid,
-                                                                     interface,
-                                                                     vlan_id, i)
-                    if cls_cp.get(switch['name']):
-                        cls_cp[switch['name']] = str(cls_cp[switch['name']]) + cl_encap
-                    else:
-                        cls_cp[switch['name']] = cl_encap
-
-            # at least 1 connection point has to be received
-            if not vlan_processed:
-                raise SdnConnectorError(message=SdnError.UNSUPPORTED_FEATURE,
-                                        http_code=406)
-
-            for s in self.switches:
-                # for cl in cp_configLets:
-                cl_name = (self.__OSM_PREFIX +
-                           s +
-                           self.__SEPARATOR + service_type + str(vlan_id) +
-                           self.__SEPARATOR + service_uuid)
-                cl_config = ''
-                # Apply BGP configuration only for VXLAN topologies
-                if self.topology in (self._VXLAN_MLAG, self._VXLAN):
-                    if service_type == self.__service_types_ELAN:
-                        cl_bgp[s] = self.clC.getElan_bgp(service_uuid,
-                                                         vlan_id,
-                                                         vni_id,
-                                                         self.switches[s]['lo0'],
-                                                         self.switches[s]['AS'])
-                    else:
-                        cl_bgp[s] = self.clC.getEline_bgp(service_uuid,
-                                                          vlan_id,
-                                                          vni_id,
-                                                          self.switches[s]['lo0'],
-                                                          self.switches[s]['AS'])
-                else:
-                    cl_bgp[s] = ''
-
-                if not cls_cp.get(s):
-                    # Apply VLAN configuration to peer MLAG switch,
-                    # only necessary when there are no connection points in the switch
-                    if self.topology in (self._VXLAN_MLAG, self._VLAN_MLAG):
-                        for p in self.switches:
-                            if self.switches[p]['mlagPeerDevice'] == s:
-                                if cls_cp.get(p):
-                                    cl_config = str(cl_vlan)
-                else:
-                    cl_config = str(cl_vlan) + str(cl_bgp[s]) + str(cls_cp[s])
-
-                cls_perSw[s] = [{'name': cl_name, 'config': cl_config}]
-
-            allLeafConfigured, allLeafModified = self.__updateConnection(cls_perSw)
-
-            conn_info = {
-                "uuid": service_uuid,
-                "status": "BUILD",
-                "service_type": service_type,
-                "vlan_id": vlan_id,
-                "connection_points": connection_points,
-                "configLetPerSwitch": cls_perSw,
-                'allLeafConfigured': allLeafConfigured,
-                'allLeafModified': allLeafModified}
-
-            return service_uuid, conn_info
-        except Exception as ex:
-            self.logger.debug("Exception processing connection {}: {}".
-                              format(service_uuid, str(ex)))
-            raise ex
-
-    def __updateConnection(self, cls_perSw):
-        """ Invoked in the creation and modification
-
-        checks if the new connection points config is:
-            - already in the Cloud Vision, the configLet is modified, and applied to the switch,
-                executing the corresponding task
-            - if it has to be removed:
-                then configuration has to be removed from the switch executing the corresponding task,
-                before trying to remove the configuration
-            - created, the configuration set is created, associated to the switch, and the associated
-                task to the configLet modification executed
-        In case of any error, rollback is executed, removing the created elements, and restoring to the
-        previous state.
-        """
-        try:
-            allLeafConfigured = {}
-            allLeafModified = {}
-
-            for s in self.switches:
-                allLeafConfigured[s] = False
-                allLeafModified[s] = False
-            cl_toDelete = []
-            for s in self.switches:
-                toDelete_in_cvp = False
-                if not (cls_perSw.get(s) and cls_perSw[s][0].get('config')):
-                    # when there is no configuration, means that there is no interface
-                    # in the switch to be connected, so the configLet has to be removed from CloudVision
-                    # after removing the ConfigLet fron the switch if it was already there
-
-                    # get config let name and key
-                    cl = cls_perSw[s]
-                    try:
-                        cvp_cl = self.client.api.get_configlet_by_name(cl[0]['name'])
-                        # remove configLet
-                        cl_toDelete.append(cvp_cl)
-                        cl[0] = cvp_cl
-                        toDelete_in_cvp = True
-                    except CvpApiError as error:
-                        if "Entity does not exist" in error.msg:
-                            continue
-                        else:
-                            raise error
-                    # remove configLet from device
-                else:
-                    res = self.__configlet_modify(cls_perSw[s])
-                    allLeafConfigured[s] = res[0]
-                    if not allLeafConfigured[s]:
-                        continue
-                    cl = cls_perSw[s]
-                res = self.__device_modify(
-                                           device_to_update=s,
-                                           new_configlets=cl,
-                                           delete=toDelete_in_cvp)
-                if "errorMessage" in str(res):
-                    raise Exception(str(res))
-                self.logger.info("Device {} modify result {}".format(s, res))
-                for t_id in res[1]['tasks']:
-                    if not toDelete_in_cvp:
-                        note_msg = "{}{}{}{}##".format(self.__MANAGED_BY_OSM,
-                                                       self.__SEPARATOR,
-                                                       t_id,
-                                                       self.__SEPARATOR)
-                        self.client.api.add_note_to_configlet(
-                                cls_perSw[s][0]['key'],
-                                note_msg)
-                        cls_perSw[s][0]['note'] = note_msg
-                    tasks = { t_id : {'workOrderId': t_id} }
-                    self.__exec_task(tasks, self.__EXC_TASK_EXEC_WAIT)
-                # with just one configLet assigned to a device,
-                # delete all if there are errors in next loops
-                if not toDelete_in_cvp:
-                    allLeafModified[s] = True
-            if len(cl_toDelete) > 0:
-                self.__configlet_modify(cl_toDelete, delete=True)
-
-            return allLeafConfigured, allLeafModified
-        except Exception as ex:
-            try:
-                self.__rollbackConnection(cls_perSw,
-                                          allLeafConfigured,
-                                          allLeafModified)
-            except Exception as e:
-                self.logger.error("Exception rolling back in updating  connection: {}".
-                                 format(e), exc_info=True)
-            raise ex
-
-    def __rollbackConnection(self,
-                             cls_perSw,
-                             allLeafConfigured,
-                             allLeafModified):
-        """ Removes the given configLet from the devices and then remove the configLets
-        """
-        for s in self.switches:
-            if allLeafModified[s]:
-                try:
-                    res = self.__device_modify(
-                        device_to_update=s,
-                        new_configlets=cls_perSw[s],
-                        delete=True)
-                    if "errorMessage" in str(res):
-                        raise Exception(str(res))
-                    tasks = dict()
-                    for t_id in res[1]['tasks']:
-                        tasks[t_id] = {'workOrderId': t_id}
-                    self.__exec_task(tasks)
-                    self.logger.info("Device {} modify result {}".format(s, res))
-                except Exception as e:
-                    self.logger.error('Error removing configlets from device {}: {}'.format(s, e))
-                    pass
-        for s in self.switches:
-            if allLeafConfigured[s]:
-                self.__configlet_modify(cls_perSw[s], delete=True)
-
-    def __exec_task(self, tasks, tout=10):
-        if self.taskC == None:
-            self.__connect()
-        data = self.taskC.update_all_tasks(tasks).values()
-        self.taskC.task_action(data, tout, 'executed')
-
-    def __device_modify(self, device_to_update, new_configlets, delete):
-        """ Updates the devices (switches) adding or removing the configLet,
-        the tasks Id's associated to the change are returned
-        """
-        self.logger.info('Enter in __device_modify delete: {}'.format(
-                            delete))
-        updated = []
-        changed = False
-        # Task Ids that have been identified during device actions
-        newTasks = []
-
-        if (len(new_configlets) == 0 or
-                device_to_update == None or
-                len(device_to_update) == 0):
-            data = {'updated': updated, 'tasks': newTasks}
-            return [changed, data]
-
-        self.__load_inventory()
-
-        allDeviceFacts = self.allDeviceFacts
-        # Work through Devices list adding device specific information
-        device = None
-        for try_device in allDeviceFacts:
-            # Add Device Specific Configlets
-            # self.logger.debug(device)
-            if try_device['hostname'] not in device_to_update:
-                continue
-            dev_cvp_configlets = self.client.api.get_configlets_by_device_id(
-                                    try_device['systemMacAddress'])
-            # self.logger.debug(dev_cvp_configlets)
-            try_device['deviceSpecificConfiglets'] = []
-            for cvp_configlet in dev_cvp_configlets:
-                if int(cvp_configlet['containerCount']) == 0:
-                    try_device['deviceSpecificConfiglets'].append(
-                                {'name': cvp_configlet['name'],
-                                 'key': cvp_configlet['key']})
-            # self.logger.debug(device)
-            device = try_device
-            break
-
-        # Check assigned configlets
-        device_update = False
-        add_configlets = []
-        remove_configlets = []
-        update_devices = []
-
-        if delete:
-            for cvp_configlet in device['deviceSpecificConfiglets']:
-                for cl in new_configlets:
-                    if cvp_configlet['name'] == cl['name']:
-                        remove_configlets.append(cvp_configlet)
-                        device_update = True
-        else:
-            for configlet in new_configlets:
-                if configlet not in device['deviceSpecificConfiglets']:
-                    add_configlets.append(configlet)
-                    device_update = True
-        if device_update:
-            update_devices.append({'hostname': device['hostname'],
-                                   'configlets': [add_configlets,
-                                                  remove_configlets],
-                                   'device': device})
-        self.logger.info("Device to modify: {}".format(update_devices))
-
-        up_device = update_devices[0]
-        cl_toAdd = up_device['configlets'][0]
-        cl_toDel = up_device['configlets'][1]
-        # Update Configlets
-        try:
-            if delete and len(cl_toDel) > 0:
-                r = self.client.api.remove_configlets_from_device(
-                                                    'OSM',
-                                                    up_device['device'],
-                                                    cl_toDel,
-                                                    create_task=True)
-                dev_action = r
-                self.logger.debug("remove_configlets_from_device {} {}".format(dev_action, cl_toDel))
-            elif len(cl_toAdd) > 0:
-                r = self.client.api.apply_configlets_to_device(
-                                                    'OSM',
-                                                    up_device['device'],
-                                                    cl_toAdd,
-                                                    create_task=True)
-                dev_action = r
-                self.logger.debug("apply_configlets_to_device {} {}".format(dev_action, cl_toAdd))
-
-        except Exception as error:
-            errorMessage = str(error)
-            msg = "errorMessage: Device {} Configlets couldnot be updated: {}".format(
-                  up_device['hostname'], errorMessage)
-            raise SdnConnectorError(msg) from error
-        else:
-            if "errorMessage" in str(dev_action):
-                m = "Device {} Configlets update fail: {}".format(
-                            up_device['name'], dev_action['errorMessage'])
-                raise SdnConnectorError(m)
-            else:
-                changed = True
-                if 'taskIds' in str(dev_action):
-                    # Fix 1030 SDN-ARISTA Key error note when deploy a NS
-                    if not dev_action['data']['taskIds']:
-                        raise SdnConnectorError("No taskIds found: Device {} Configlets couldnot be updated".format(
-                                        up_device['hostname']))
-                    for taskId in dev_action['data']['taskIds']:
-                        updated.append({up_device['hostname']:
-                            "Configlets-{}".format(
-                                taskId)})
-                        newTasks.append(taskId)
-                else:
-                    updated.append({up_device['hostname']:
-                                   "Configlets-No_Specific_Tasks"})
-        data = {'updated': updated, 'tasks': newTasks}
-        return [changed, data]
-
-    def __configlet_modify(self, configletsToApply, delete=False):
-        ''' adds/update or delete the provided configLets
-        :param configletsToApply: list of configLets to apply
-        :param delete: flag to indicate if the configLets have to be deleted
-                        from Cloud Vision Portal
-        :return: data: dict of module actions and taskIDs
-        '''
-        self.logger.info('Enter in __configlet_modify delete:{}'.format(
-                            delete))
-
-        # Compare configlets against cvp_facts-configlets
-        changed = False
-        checked = []
-        deleted = []
-        updated = []
-        new = []
-
-        for cl in configletsToApply:
-            found_in_cvp = False
-            to_delete = False
-            to_update = False
-            to_create = False
-            to_check = False
-            try:
-                cvp_cl = self.client.api.get_configlet_by_name(cl['name'])
-                cl['key'] = cvp_cl['key']
-                cl['note'] = cvp_cl['note']
-                found_in_cvp = True
-            except CvpApiError as error:
-                if "Entity does not exist" in error.msg:
-                    pass
-                else:
-                    raise error
-
-            if delete:
-                if found_in_cvp:
-                    to_delete = True
-                    configlet = {'name': cvp_cl['name'],
-                                 'data': cvp_cl}
-            else:
-                if found_in_cvp:
-                    cl_compare = self.__compare(cl['config'],
-                                                cvp_cl['config'])
-                    # compare function returns a floating point number
-                    if cl_compare[0] != 100.0:
-                        to_update = True
-                        configlet = {'name': cl['name'],
-                                     'data': cvp_cl,
-                                     'config': cl['config']}
-                    else:
-                        to_check = True
-                        configlet = {'name': cl['name'],
-                                     'key': cvp_cl['key'],
-                                     'data': cvp_cl,
-                                     'config': cl['config']}
-                else:
-                    to_create = True
-                    configlet = {'name': cl['name'],
-                                 'config': cl['config']}
-            try:
-                if to_delete:
-                    operation = 'delete'
-                    resp = self.client.api.delete_configlet(
-                                    configlet['data']['name'],
-                                    configlet['data']['key'])
-                elif to_update:
-                    operation = 'update'
-                    resp = self.client.api.update_configlet(
-                                    configlet['config'],
-                                    configlet['data']['key'],
-                                    configlet['data']['name'],
-                                    wait_task_ids=True)
-                elif to_create:
-                    operation = 'create'
-                    resp = self.client.api.add_configlet(
-                                    configlet['name'],
-                                    configlet['config'])
-                else:
-                    operation = 'checked'
-                    resp = 'checked'
-            except Exception as error:
-                errorMessage = str(error).split(':')[-1]
-                message = "Configlet {} cannot be {}: {}".format(
-                            cl['name'], operation, errorMessage)
-                if to_delete:
-                    deleted.append({configlet['name']: message})
-                elif to_update:
-                    updated.append({configlet['name']: message})
-                elif to_create:
-                    new.append({configlet['name']: message})
-                elif to_check:
-                    checked.append({configlet['name']: message})
-
-            else:
-                if "error" in str(resp).lower():
-                    message = "Configlet {} cannot be deleted: {}".format(
-                            cl['name'], resp['errorMessage'])
-                    if to_delete:
-                        deleted.append({configlet['name']: message})
-                    elif to_update:
-                        updated.append({configlet['name']: message})
-                    elif to_create:
-                        new.append({configlet['name']: message})
-                    elif to_check:
-                        checked.append({configlet['name']: message})
-                else:
-                    if to_delete:
-                        changed = True
-                        deleted.append({configlet['name']: "success"})
-                    elif to_update:
-                        changed = True
-                        updated.append({configlet['name']: "success"})
-                    elif to_create:
-                        changed = True
-                        cl['key'] = resp  # This key is used in API call deviceApplyConfigLet FGA
-                        new.append({configlet['name']: "success"})
-                    elif to_check:
-                        changed = False
-                        checked.append({configlet['name']: "success"})
-
-        data = {'new': new, 'updated': updated, 'deleted': deleted, 'checked': checked}
-        return [changed, data]
-
-    def __get_configletsDevices(self, configlets):
-        for s in self.switches:
-            configlet = configlets[s]
-            # Add applied Devices
-            if len(configlet) > 0:
-                configlet['devices'] = []
-                applied_devices = self.client.api.get_applied_devices(
-                                configlet['name'])
-                for device in applied_devices['data']:
-                    configlet['devices'].append(device['hostName'])
-
-    def __get_serviceData(self, service_uuid, service_type, vlan_id, conn_info=None):
-        cls_perSw = {}
-        for s in self.switches:
-            cls_perSw[s] = []
-        if not conn_info:
-            srv_cls = self.__get_serviceConfigLets(service_uuid,
-                                                   service_type,
-                                                   vlan_id)
-            self.__get_configletsDevices(srv_cls)
-            for s in self.switches:
-                cl = srv_cls[s]
-                if len(cl) > 0:
-                    for dev in cl['devices']:
-                        cls_perSw[dev].append(cl)
-        else:
-            cls_perSw = conn_info['configLetPerSwitch']
-        return cls_perSw
-
-    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
-        """
-        try:
-            self.logger.debug('invoked delete_connectivity_service {}'.
-                              format(service_uuid))
-            if not service_uuid:
-                raise SdnConnectorError(message='No connection service UUID',
-                                        http_code=500)
-
-            self.__get_Connection()
-            if conn_info == None:
-                raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid),
-                                        http_code=500)
-            c_info = None
-            cls_perSw = self.__get_serviceData(service_uuid,
-                                               conn_info['service_type'],
-                                               conn_info['vlan_id'],
-                                               c_info)
-            allLeafConfigured = {}
-            allLeafModified = {}
-            for s in self.switches:
-                allLeafConfigured[s] = True
-                allLeafModified[s] = True
-            found_in_cvp = False
-            for s in self.switches:
-                if cls_perSw[s]:
-                    found_in_cvp = True
-            if found_in_cvp:
-                self.__rollbackConnection(cls_perSw,
-                                          allLeafConfigured,
-                                          allLeafModified)
-            else:
-                # if the service is not defined in Cloud Vision, return a 404 - NotFound error
-                raise SdnConnectorError(message='Service {} was not found in Arista Cloud Vision {}'.
-                                        format(service_uuid, self.__wim_url),
-                                        http_code=404)
-            self.__removeMetadata(service_uuid)
-        except CvpLoginError as e:
-            self.logger.info(str(e))
-            self.client = None
-            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
-                                    http_code=401) from e
-        except SdnConnectorError as sde:
-            raise sde
-        except Exception as ex:
-            self.client = None
-            self.logger.error(ex)
-            if self.raiseException:
-                raise ex
-            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
-                                    http_code=500) from ex
-
-    def __addMetadata(self, service_uuid, service_type, vlan_id):
-        """ Adds the connectivity service from 'OSM_metadata' configLet
-        """
-        found_in_cvp = False
-        try:
-            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
-            found_in_cvp = True
-        except CvpApiError as error:
-            if "Entity does not exist" in error.msg:
-                pass
-            else:
-                raise error
-        try:
-            new_serv = '{} {} {} {}\n'.format(self.__METADATA_PREFIX, service_type, vlan_id, service_uuid)
-
-            if found_in_cvp:
-                cl_config = cvp_cl['config'] + new_serv
-            else:
-                cl_config = new_serv
-            cl_meta = [{'name': self.__OSM_METADATA, 'config': cl_config}]
-            self.__configlet_modify(cl_meta)
-        except Exception as e:
-            self.logger.error('Error in setting metadata in CloudVision from OSM for service {}: {}'.
-                              format(service_uuid, str(e)))
-            pass
-
-    def __removeMetadata(self, service_uuid):
-        """ Removes the connectivity service from 'OSM_metadata' configLet
-        """
-        found_in_cvp = False
-        try:
-            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
-            found_in_cvp = True
-        except CvpApiError as error:
-            if "Entity does not exist" in error.msg:
-                pass
-            else:
-                raise error
-        try:
-            if found_in_cvp:
-                if service_uuid in cvp_cl['config']:
-                    cl_config = ''
-                    for line in cvp_cl['config'].split('\n'):
-                        if service_uuid in line:
-                            continue
-                        else:
-                            cl_config = cl_config + line
-                    cl_meta = [{'name': self.__OSM_METADATA, 'config': cl_config}]
-                    self.__configlet_modify(cl_meta)
-        except Exception as e:
-            self.logger.error('Error in removing metadata in CloudVision from OSM for service {}: {}'.
-                              format(service_uuid, str(e)))
-            pass
-
-    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:
-            SdnConnectorError: In case of error.
-        """
-        try:
-            self.logger.debug('invoked edit_connectivity_service for service {}. ports: {}'.format(service_uuid,
-                                                                                                   connection_points))
-
-            if not service_uuid:
-                raise SdnConnectorError(message='Unable to perform operation, missing or empty uuid',
-                                        http_code=500)
-            if not conn_info:
-                raise SdnConnectorError(message='Unable to perform operation, missing or empty connection information',
-                                        http_code=500)
-
-            if connection_points == None:
-                return None
-
-            self.__get_Connection()
-
-            cls_currentPerSw = conn_info['configLetPerSwitch']
-            service_type = conn_info['service_type']
-
-            self.__check_service(service_type,
-                                 connection_points,
-                                 check_vlan=False,
-                                 check_num_cp=False,
-                                 kwargs=kwargs)
-
-            s_uid, s_connInf = self.__processConnection(
-                                                        service_uuid,
-                                                        service_type,
-                                                        connection_points,
-                                                        kwargs)
-            self.logger.info("Service with uuid {} configuration updated".
-                             format(s_uid))
-            return s_connInf
-        except CvpLoginError as e:
-            self.logger.info(str(e))
-            self.client = None
-            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
-                                    http_code=401) from e
-        except SdnConnectorError as sde:
-            raise sde
-        except Exception as ex:
-            try:
-                # Add previous
-                # TODO check if there are pending task, and cancel them before restoring
-                self.__updateConnection(cls_currentPerSw)
-            except Exception as e:
-                self.logger.error("Unable to restore configuration in service {} after an error in the configuration updated: {}".
-                                  format(service_uuid, str(e)))
-            if self.raiseException:
-                raise ex
-            raise SdnConnectorError(message=str(ex),
-                                    http_code=500) from ex
-
-    def clear_all_connectivity_services(self):
-        """ Removes all connectivity services from Arista CloudVision with two steps:
-        - retrives all the services from Arista CloudVision
-        - removes each service
-        """
-        try:
-            self.logger.debug('invoked AristaImpl ' +
-                              'clear_all_connectivity_services')
-            self.__get_Connection()
-            s_list = self.__get_srvUUIDs()
-            for serv in s_list:
-                conn_info = {}
-                conn_info['service_type'] = serv['type']
-                conn_info['vlan_id'] = serv['vlan']
-
-                self.delete_connectivity_service(serv['uuid'], conn_info)
-        except CvpLoginError as e:
-            self.logger.info(str(e))
-            self.client = None
-            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
-                                    http_code=401) from e
-        except SdnConnectorError as sde:
-            raise sde
-        except Exception as ex:
-            self.client = None
-            self.logger.error(ex)
-            if self.raiseException:
-                raise ex
-            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
-                                    http_code=500) from ex
-
-    def get_all_active_connectivity_services(self):
-        """ Return the uuid of all the active connectivity services with two steps:
-        - retrives all the services from Arista CloudVision
-        - retrives the status of each server
-        """
-        try:
-            self.logger.debug('invoked AristaImpl {}'.format(
-                              'get_all_active_connectivity_services'))
-            self.__get_Connection()
-            s_list = self.__get_srvUUIDs()
-            result = []
-            for serv in s_list:
-                conn_info = {}
-                conn_info['service_type'] = serv['type']
-                conn_info['vlan_id'] = serv['vlan']
-
-                status = self.get_connectivity_service_status(serv['uuid'], conn_info)
-                if status['sdn_status'] == 'ACTIVE':
-                    result.append(serv['uuid'])
-            return result
-        except CvpLoginError as e:
-            self.logger.info(str(e))
-            self.client = None
-            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
-                                    http_code=401) from e
-        except SdnConnectorError as sde:
-            raise sde
-        except Exception as ex:
-            self.client = None
-            self.logger.error(ex)
-            if self.raiseException:
-                raise ex
-            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
-                                    http_code=500) from ex
-
-    def __get_serviceConfigLets(self, service_uuid, service_type, vlan_id):
-        """ Return the configLet's associated with a connectivity service,
-        There should be one, as maximum, per device (switch) for a given
-        connectivity service
-        """
-        srv_cls = {}
-        for s in self.switches:
-            srv_cls[s] = []
-            found_in_cvp = False
-            name = (self.__OSM_PREFIX +
-                    s +
-                    self.__SEPARATOR + service_type + str(vlan_id) +
-                    self.__SEPARATOR + service_uuid)
-            try:
-                cvp_cl = self.client.api.get_configlet_by_name(name)
-                found_in_cvp = True
-            except CvpApiError as error:
-                if "Entity does not exist" in error.msg:
-                    pass
-                else:
-                    raise error
-            if found_in_cvp:
-                srv_cls[s] = cvp_cl
-        return srv_cls
-
-    def __get_srvVLANs(self):
-        """ Returns a list with all the VLAN id's used in the connectivity services managed
-        in tha Arista CloudVision by checking the 'OSM_metadata' configLet where this
-        information is stored
-        """
-        found_in_cvp = False
-        try:
-            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
-            found_in_cvp = True
-        except CvpApiError as error:
-            if "Entity does not exist" in error.msg:
-                pass
-            else:
-                raise error
-        s_vlan_list = []
-        if found_in_cvp:
-            lines = cvp_cl['config'].split('\n')
-            for line in lines:
-                if self.__METADATA_PREFIX in line:
-                    s_vlan = line.split(' ')[3]
-                else:
-                    continue
-                if (s_vlan is not None and
-                        len(s_vlan) > 0 and
-                        s_vlan not in s_vlan_list):
-                    s_vlan_list.append(s_vlan)
-
-        return s_vlan_list
-
-    def __get_srvUUIDs(self):
-        """ Retrieves all the connectivity services, managed in tha Arista CloudVision
-        by checking the 'OSM_metadata' configLet where this information is stored
-        """
-        found_in_cvp = False
-        try:
-            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
-            found_in_cvp = True
-        except CvpApiError as error:
-            if "Entity does not exist" in error.msg:
-                pass
-            else:
-                raise error
-        serv_list = []
-        if found_in_cvp:
-            lines = cvp_cl['config'].split('\n')
-            for line in lines:
-                if self.__METADATA_PREFIX in line:
-                    line = line.split(' ')
-                    serv = {'uuid': line[4], 'type': line[2], 'vlan': line[3]}
-                else:
-                    continue
-                if (serv is not None and
-                        len(serv) > 0 and
-                        serv not in serv_list):
-                    serv_list.append(serv)
-
-        return serv_list
-
-    def __get_Connection(self):
-        """ Open a connection with Arista CloudVision,
-            invoking the version retrival as test
-        """
-        try:
-            if self.client == None:
-                self.client = self.__connect()
-            self.client.api.get_cvp_info()
-        except (CvpSessionLogOutError, RequestException) as e:
-            self.logger.debug("Connection error '{}'. Reconnecting".format(e))
-            self.client = self.__connect()
-            self.client.api.get_cvp_info()
-
-    def __connect(self):
-        ''' Connects to CVP device using user provided credentials from initialization.
-        :return: CvpClient object with connection instantiated.
-        '''
-        client = CvpClient()
-        protocol, _, rest_url = self.__wim_url.rpartition("://")
-        host, _, port = rest_url.partition(":")
-        if port and port.endswith("/"):
-            port = int(port[:-1])
-        elif port:
-            port = int(port)
-        else:
-            port = 443
-
-        client.connect([host],
-                       self.__user,
-                       self.__passwd,
-                       protocol=protocol or "https",
-                       port=port,
-                       connect_timeout=2)
-        client.api = CvpApi(client, request_timeout=self.__API_REQUEST_TOUT)
-        self.taskC = AristaCVPTask(client.api)
-        return client
-
-    def __compare(self, fromText, toText, lines=10):
-        """ Compare text string in 'fromText' with 'toText' and produce
-        diffRatio - a score as a float in the range [0, 1] 2.0*M / T
-          T is the total number of elements in both sequences,
-          M is the number of matches.
-          Score - 1.0 if the sequences are identical, and
-                  0.0 if they have nothing in common.
-        unified diff list
-          Code Meaning
-          '- ' line unique to sequence 1
-          '+ ' line unique to sequence 2
-          '  ' line common to both sequences
-          '? ' line not present in either input sequence
-        """
-        fromlines = fromText.splitlines(1)
-        tolines = toText.splitlines(1)
-        diff = list(difflib.unified_diff(fromlines, tolines, n=lines))
-        textComp = difflib.SequenceMatcher(None, fromText, toText)
-        diffRatio = round(textComp.quick_ratio()*100, 2)
-        return [diffRatio, diff]
-
-    def __load_inventory(self):
-        """ Get Inventory Data for All Devices (aka switches) from the Arista CloudVision
-        """
-        if not self.cvp_inventory:
-            self.cvp_inventory = self.client.api.get_inventory()
-        self.allDeviceFacts = []
-        for device in self.cvp_inventory:
-            self.allDeviceFacts.append(device)
-
-    def __get_tags(self, name, value):
-        if not self.cvp_tags:
-            self.cvp_tags = []
-            url = '/api/v1/rest/analytics/tags/labels/devices/{}/value/{}/elements'.format(name, value)
-            self.logger.debug('get_tags: URL {}'.format(url))
-            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
-            for dev in data['notifications']:
-                for elem in dev['updates']:
-                    self.cvp_tags.append(elem)
-        self.logger.debug('Available devices with tag_name {} - value {}: {} '.format(name, value, self.cvp_tags))
-
-    def __get_interface_ip(self, device_id, interface):
-        url = '/api/v1/rest/{}/Sysdb/ip/config/ipIntfConfig/{}/'.format(device_id, interface)
-        self.logger.debug('get_interface_ip: URL {}'.format(url))
-        try:
-            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
-            return data['notifications'][0]['updates']['addrWithMask']['value'].split('/')[0]
-        except Exception:
-            raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data))
-
-    def __get_device_ASN(self, device_id):
-        url = '/api/v1/rest/{}/Sysdb/routing/bgp/config/'.format(device_id)
-        self.logger.debug('get_device_ASN: URL {}'.format(url))
-        try:
-            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
-            return data['notifications'][0]['updates']['asNumber']['value']['value']['int']
-        except Exception:
-            raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data))
-
-    def __get_peer_MLAG(self, device_id):
-        peer = None
-        url = '/api/v1/rest/{}/Sysdb/mlag/status/'.format(device_id)
-        self.logger.debug('get_MLAG_status: URL {}'.format(url))
-        try:
-            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
-            if data['notifications']:
-                found = False
-                for notification in data['notifications']:
-                    for update in notification['updates']:
-                        if update == 'systemId':
-                            mlagSystemId = notification['updates'][update]['value']
-                            found = True
-                            break
-                    if found:
-                        break
-                # search the MLAG System Id
-                if found:
-                    for s in self.switches:
-                        if self.switches[s]['serialNumber'] == device_id:
-                            continue
-                        url = '/api/v1/rest/{}/Sysdb/mlag/status/'.format(self.switches[s]['serialNumber'])
-                        self.logger.debug('Searching for MLAG system id {} in switch {}'.format(mlagSystemId, s))
-                        data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
-                        found = False
-                        for notification in data['notifications']:
-                            for update in notification['updates']:
-                                if update == 'systemId':
-                                    if mlagSystemId == notification['updates'][update]['value']:
-                                        peer = s
-                                        found = True
-                                        break
-                            if found:
-                                break
-                        if found:
-                            break
-            if peer == None:
-                self.logger.error('No Peer device found for device {} with MLAG address {}'.format(device_id, mlagSystemId))
-            else:
-                self.logger.debug('Peer MLAG for device {} - value {}'.format(device_id, peer))
-            return peer
-        except Exception:
-            raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data))
-
-    def is_valid_destination(self, url):
-        """ Check that the provided WIM URL is correct
-        """
-        if re.match(self.__regex, url):
-            return True
-        elif self.is_valid_ipv4_address(url):
-            return True
-        else:
-            return self.is_valid_ipv6_address(url)
-
-    def is_valid_ipv4_address(self, address):
-        """ Checks that the given IP is IPv4 valid
-        """
-        try:
-            socket.inet_pton(socket.AF_INET, address)
-        except AttributeError:  # no inet_pton here, sorry
-            try:
-                socket.inet_aton(address)
-            except socket.error:
-                return False
-            return address.count('.') == 3
-        except socket.error:  # not a valid address
-            return False
-        return True
-
-    def is_valid_ipv6_address(self, address):
-        """ Checks that the given IP is IPv6 valid
-        """
-        try:
-            socket.inet_pton(socket.AF_INET6, address)
-        except socket.error:  # not a valid address
-            return False
-        return True
-
-    def delete_keys_from_dict(self, dict_del, lst_keys):
-        if dict_del == None:
-            return dict_del
-        dict_copy = {k: v for k, v in dict_del.items() if k not in lst_keys}
-        for k, v in dict_copy.items():
-            if isinstance(v, dict):
-                dict_copy[k] = self.delete_keys_from_dict(v, lst_keys)
-        return dict_copy
diff --git a/RO-SDN-arista/requirements.txt b/RO-SDN-arista/requirements.txt
deleted file mode 100644 (file)
index cd1edfe..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-##
-# 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
-uuid
-cvprac
-git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro
-
diff --git a/RO-SDN-arista/setup.py b/RO-SDN-arista/setup.py
deleted file mode 100644 (file)
index d974aad..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/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_arista"
-
-README = """
-===========
-osm-rosdn_arista
-===========
-
-osm-ro pluging for arista SDN
-"""
-
-setup(
-    name=_name,
-    description='OSM ro sdn plugin for arista',
-    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='oscarluis.peral@atos.net',  # 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,
-    install_requires=["requests",
-                      "uuid",
-                      "cvprac",
-                      "osm-ro @ git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro&subdirectory=RO"],
-    setup_requires=['setuptools-version-command'],
-    entry_points={
-        'osm_rosdn.plugins': ['rosdn_arista = osm_rosdn_arista.wimconn_arista:AristaSdnConnector']
-    },
-)
diff --git a/RO-SDN-arista/stdeb.cfg b/RO-SDN-arista/stdeb.cfg
deleted file mode 100644 (file)
index 0c718e4..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# 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-arista/tox.ini b/RO-SDN-arista/tox.ini
deleted file mode 100644 (file)
index d737d6e..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-##
-# 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_arista --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_arista.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-arista_cloudvision/Makefile b/RO-SDN-arista_cloudvision/Makefile
new file mode 100644 (file)
index 0000000..4890e8e
--- /dev/null
@@ -0,0 +1,25 @@
+##
+# 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_arista_cloudvision-*.tar.gz osm_rosdn_arista_cloudvision.egg-info .eggs
+
+package:
+       python3 setup.py --command-packages=stdeb.command sdist_dsc
+       cp debian/python3-osm-rosdn-arista-cloudvision.postinst deb_dist/osm-rosdn-arista-cloudvision*/debian/
+       cd deb_dist/osm-rosdn-arista-cloudvision*/ && dpkg-buildpackage -rfakeroot -uc -us
+
diff --git a/RO-SDN-arista_cloudvision/debian/python3-osm-rosdn-arista-cloudvision.postinst b/RO-SDN-arista_cloudvision/debian/python3-osm-rosdn-arista-cloudvision.postinst
new file mode 100755 (executable)
index 0000000..d87fa26
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+##
+# 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: OSM_TECH@list.etsi.org
+##
+
+echo "POST INSTALL OSM-ROSDN-ARISTA"
+
+#Pip packages required for openstack connector
+python3 -m pip install cvprac
diff --git a/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/aristaConfigLet.py b/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/aristaConfigLet.py
new file mode 100644 (file)
index 0000000..8e34091
--- /dev/null
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-\r
+##\r
+# Copyright 2019 Atos - CoE Telco NFV Team\r
+# All Rights Reserved.\r
+#\r
+# Contributors: Oscar Luis Peral, Atos\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License"); you may\r
+# not use this file except in compliance with the License. You may obtain\r
+# a copy of the License at\r
+#\r
+#         http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+# License for the specific language governing permissions and limitations\r
+# under the License.\r
+#\r
+# For those usages not covered by the Apache License, Version 2.0 please\r
+# contact with: <oscarluis.peral@atos.net>\r
+#\r
+# Neither the name of Atos nor the names of its\r
+# contributors may be used to endorse or promote products derived from\r
+# this software without specific prior written permission.\r
+#\r
+# This work has been performed in the context of Arista Telefonica OSM PoC.\r
+##\r
+\r
+\r
+class AristaSDNConfigLet:\r
+    _VLAN = "VLAN"\r
+    _VXLAN = "VXLAN"\r
+    _VLAN_MLAG = "VLAN-MLAG"\r
+    _VXLAN_MLAG = "VXLAN-MLAG"\r
+    topology = _VXLAN_MLAG\r
+\r
+    def __init__(self, topology=_VXLAN_MLAG):\r
+        self.topology = topology\r
+\r
+    _basic_int ="""\r
+interface {interface}\r
+   !! service: {uuid}\r
+   switchport\r
+   switchport mode {type}\r
+   switchport {switchport_def}\r
+!\r
+"""\r
+    _int_SRIOV = "trunk group {service}{vlan_id}"\r
+    _int_PASSTROUGH = "access vlan {vlan_id}"\r
+\r
+    def _get_interface(self, uuid, interface, vlan_id, s_type, index, i_type):\r
+        if i_type == "trunk":\r
+            switchport_def = self._int_SRIOV.format(service=s_type, vlan_id=vlan_id)\r
+        else:\r
+            switchport_def = self._int_PASSTROUGH.format(vlan_id=vlan_id)\r
+        return self._basic_int.format(uuid=uuid,\r
+                                      interface=interface,\r
+                                      type=i_type,\r
+                                      switchport_def=switchport_def)\r
+\r
+    def getElan_sriov(self, uuid, interface, vlan_id, index):\r
+        return self._get_interface(uuid, interface, vlan_id, "ELAN", index, "trunk")\r
+\r
+    def getEline_sriov(self, uuid, interface, vlan_id, index):\r
+        return self._get_interface(uuid, interface, vlan_id, "ELINE", index, "trunk")\r
+\r
+    def getElan_passthrough(self, uuid, interface, vlan_id, index):\r
+        return self._get_interface(uuid, interface, vlan_id, "ELAN", index, "dot1q-tunnel")\r
+\r
+    def getEline_passthrough(self, uuid, interface, vlan_id, index):\r
+        return self._get_interface(uuid, interface, vlan_id, "ELINE", index, "dot1q-tunnel")\r
+\r
+    _basic_vlan ="""\r
+vlan {vlan}\r
+   !! service: {service} {vlan} {uuid}\r
+   name {service}{vlan}\r
+   trunk group {service}{vlan}\r
+"""\r
+    _basic_mlag ="""   trunk group MLAGPEER\r
+"""\r
+    _basic_vxlan ="""interface VXLAN1\r
+   VXLAN vlan {vlan} vni {vni}\r
+"""\r
+    _basic_end ="!"\r
+\r
+    _configLet_VLAN = _basic_vlan + _basic_end\r
+    _configLet_VXLAN = _basic_vlan + _basic_vxlan + _basic_end\r
+    _configLet_VLAN_MLAG = _basic_vlan + _basic_mlag + _basic_end\r
+    _configLet_VXLAN_MLAG = _basic_vlan + _basic_mlag + _basic_vxlan + _basic_end\r
+\r
+    def _get_vlan(self, uuid, vlan_id, vni_id, s_type):\r
+        if self.topology == self._VLAN:\r
+            return self._configLet_VLAN.format(service=s_type, vlan=vlan_id, uuid=uuid)\r
+        if self.topology == self._VLAN_MLAG:\r
+            return self._configLet_VLAN_MLAG.format(service=s_type, vlan=vlan_id, uuid=uuid)\r
+        if self.topology == self._VXLAN:\r
+            return self._configLet_VXLAN.format(service=s_type, vlan=vlan_id, uuid=uuid, vni=vni_id)\r
+        if self.topology == self._VXLAN_MLAG:\r
+            return self._configLet_VXLAN_MLAG.format(service=s_type, vlan=vlan_id, uuid=uuid, vni=vni_id)\r
+\r
+    def getElan_vlan(self, uuid, vlan_id, vni_id):\r
+        return self._get_vlan(uuid, vlan_id, vni_id, "ELAN")\r
+\r
+    def getEline_vlan(self, uuid, vlan_id, vni_id):\r
+        return self._get_vlan(uuid, vlan_id, vni_id, "ELINE")\r
+\r
+    _configLet_BGP = """\r
+router bgp {bgp}\r
+    vlan {vlan}\r
+    !! service: {uuid}\r
+        rd {loopback}:{vni}\r
+        route-target both {vni}:{vni}\r
+        redistribute learned\r
+!\r
+"""\r
+\r
+    def _get_bgp(self, uuid, vlan_id, vni_id, loopback0, bgp, s_type):\r
+        if self.topology == self._VXLAN or self.topology == self._VXLAN_MLAG:\r
+            return self._configLet_BGP.format(uuid=uuid,\r
+                                              bgp=bgp,\r
+                                              vlan=vlan_id,\r
+                                              loopback=loopback0,\r
+                                              vni=vni_id)\r
+\r
+\r
+    def getElan_bgp(self, uuid, vlan_id, vni_id, loopback0, bgp):\r
+        return self._get_bgp(uuid, vlan_id, vni_id, loopback0, bgp, "ELAN")\r
+\r
+    def getEline_bgp(self, uuid, vlan_id, vni_id, loopback0, bgp):\r
+        return self._get_bgp(uuid, vlan_id, vni_id, loopback0, bgp, "ELINE")\r
diff --git a/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/aristaTask.py b/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/aristaTask.py
new file mode 100644 (file)
index 0000000..a338afd
--- /dev/null
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-\r
+##\r
+# Copyright 2019 Atos - CoE Telco NFV Team\r
+# All Rights Reserved.\r
+#\r
+# Contributors: Oscar Luis Peral, Atos\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License"); you may\r
+# not use this file except in compliance with the License. You may obtain\r
+# a copy of the License at\r
+#\r
+#         http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+# License for the specific language governing permissions and limitations\r
+# under the License.\r
+#\r
+# For those usages not covered by the Apache License, Version 2.0 please\r
+# contact with: <oscarluis.peral@atos.net>\r
+#\r
+# Neither the name of Atos nor the names of its\r
+# contributors may be used to endorse or promote products derived from\r
+# this software without specific prior written permission.\r
+#\r
+# This work has been performed in the context of Arista Telefonica OSM PoC.\r
+##\r
+import time\r
+\r
+\r
+class AristaCVPTask:\r
+    def __init__(self, cvpClientApi):\r
+        self.cvpClientApi = cvpClientApi\r
+\r
+    def __get_id(self, task):\r
+        return task.get("workOrderId")\r
+\r
+    def __get_state(self, task):\r
+        return task.get("workOrderUserDefinedStatus")\r
+\r
+    def __execute_task(self, task_id):\r
+        return self.cvpClientApi.execute_task(task_id)\r
+\r
+    def __cancel_task(self, task_id):\r
+        return self.cvpClientApi.cancel_task(task_id)\r
+\r
+    def __apply_state(self, task, state):\r
+        t_id = self.__get_id(task)\r
+        self.cvpClientApi.add_note_to_task(t_id, "Executed by OSM")\r
+        if state == "executed":\r
+            return self.__execute_task(t_id)\r
+        elif state == "cancelled":\r
+            return self.__cancel_task(t_id)\r
+\r
+    def __actionable(self, state):\r
+        return state in ["Pending"]\r
+\r
+    def __terminal(self, state):\r
+        return state in ["Completed", "Cancelled"]\r
+\r
+    def __state_is_different(self, task, target):\r
+        return self.__get_state(task) != target\r
+\r
+    def update_all_tasks(self, data):\r
+        new_data = dict()\r
+        for task_id in data.keys():\r
+            res = self.cvpClientApi.get_task_by_id(task_id)\r
+            new_data[task_id] = res\r
+        return new_data\r
+\r
+    def get_pending_tasks(self):\r
+        return self.cvpClientApi.get_tasks_by_status('Pending')\r
+\r
+    def get_pending_tasks_old(self):\r
+        taskList = []\r
+        tasksField = {'workOrderId': 'workOrderId',\r
+                      'workOrderState': 'workOrderState',\r
+                      'currentTaskName': 'currentTaskName',\r
+                      'description': 'description',\r
+                      'workOrderUserDefinedStatus':\r
+                      'workOrderUserDefinedStatus',\r
+                      'note': 'note',\r
+                      'taskStatus': 'taskStatus',\r
+                      'workOrderDetails': 'workOrderDetails'}\r
+        tasks = self.cvpClientApi.get_tasks_by_status('Pending')\r
+        # Reduce task data to required fields\r
+        for task in tasks:\r
+            taskFacts = {}\r
+            for field in task.keys():\r
+                if field in tasksField:\r
+                    taskFacts[tasksField[field]] = task[field]\r
+            taskList.append(taskFacts)\r
+        return taskList\r
+\r
+    def task_action(self, tasks, wait, state):\r
+        changed = False\r
+        data = dict()\r
+        warnings = list()\r
+\r
+        at = [t for t in tasks if self.__actionable(self.__get_state(t))]\r
+        actionable_tasks = at\r
+\r
+        if len(actionable_tasks) == 0:\r
+            warnings.append("No actionable tasks found on CVP")\r
+            return changed, data, warnings\r
+\r
+        for task in actionable_tasks:\r
+            if self.__state_is_different(task, state):\r
+                self.__apply_state(task, state)\r
+                changed = True\r
+                data[self.__get_id(task)] = task\r
+\r
+        if wait == 0:\r
+            return changed, data, warnings\r
+\r
+        start = time.time()\r
+        now = time.time()\r
+        while (now - start) < wait:\r
+            data = self.update_all_tasks(data)\r
+            if all([self.__terminal(self.__get_state(t)) for t in data.values()]):\r
+                break\r
+            time.sleep(1)\r
+            now = time.time()\r
+\r
+        if wait:\r
+            for i, task in data.items():\r
+                if not self.__terminal(self.__get_state(task)):\r
+                    warnings.append("Task {} has not completed in {} seconds".\r
+                                    format(i, wait))\r
+\r
+        return changed, data, warnings\r
diff --git a/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/wimconn_arista.py b/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/wimconn_arista.py
new file mode 100644 (file)
index 0000000..37f4c58
--- /dev/null
@@ -0,0 +1,1621 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2019 Atos - CoE Telco NFV Team
+# All Rights Reserved.
+#
+# Contributors: Oscar Luis Peral, Atos
+#
+# 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: <oscarluis.peral@atos.net>
+#
+# Neither the name of Atos 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 Arista Telefonica OSM PoC.
+##
+from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
+import re
+import socket
+# Required by compare function
+import difflib
+# Library that uses Levenshtein Distance to calculate the differences
+# between strings.
+# from fuzzywuzzy import fuzz
+
+import logging
+import uuid
+from enum import Enum
+from requests import RequestException
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_api import CvpApi
+from cvprac.cvp_client_errors import CvpLoginError,  CvpSessionLogOutError, CvpApiError
+from cvprac import __version__ as cvprac_version
+
+from osm_rosdn_arista_cloudvision.aristaConfigLet import AristaSDNConfigLet
+from osm_rosdn_arista_cloudvision.aristaTask import AristaCVPTask
+
+
+class SdnError(Enum):
+    UNREACHABLE = 'Unable to reach the WIM.',
+    VLAN_INCONSISTENT = \
+        'VLAN value inconsistent between the connection points',
+    VLAN_NOT_PROVIDED = 'VLAN value not provided',
+    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",
+    INTERNAL_ERROR = "Internal error"
+
+
+class AristaSdnConnector(SdnConnectorBase):
+    """Arista class for 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``.
+
+    The access to Arista CloudVision is made through the API defined in
+        https://github.com/aristanetworks/cvprac
+    The a connectivity service consist in creating a VLAN and associate the interfaces
+    of the connection points MAC addresses to this VLAN in all the switches of the topology,
+    the BDP is also configured for this VLAN.
+
+    The Arista Cloud Vision API workflow is the following
+    -- The switch configuration is defined as a set of switch configuration commands,
+       what is called 'ConfigLet'
+    -- The ConfigLet is associated to the device (leaf switch)
+    -- Automatically a task is associated to this activity for change control, the task
+       in this stage is in 'Pending' state
+    -- The task will be executed so that the configuration is applied to the switch.
+    -- The service information is saved in the response of the creation call
+    -- All created services identification is stored in a generic ConfigLet 'OSM_metadata'
+       to keep track of the managed resources by OSM in the Arista deployment.
+    """
+    __supported_service_types = ["ELINE (L2)", "ELINE", "ELAN"]
+    __service_types_ELAN = "ELAN"
+    __service_types_ELINE = "ELINE"
+    __ELINE_num_connection_points = 2
+    __supported_service_types = ["ELINE", "ELAN"]
+    __supported_encapsulation_types = ["dot1q"]
+    __WIM_LOGGER = 'openmano.sdnconn.arista'
+    __SERVICE_ENDPOINT_MAPPING = 'service_endpoint_mapping'
+    __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"
+    __MAC_PARAM = "mac"
+    __WAN_SERVICE_ENDPOINT_PARAM = "service_endpoint_id"
+    __WAN_MAPPING_INFO_PARAM = "service_mapping_info"
+    __DEVICE_ID_PARAM = "device_id"
+    __DEVICE_INTERFACE_ID_PARAM = "device_interface_id"
+    __SW_ID_PARAM = "switch_dpid"
+    __SW_PORT_PARAM = "switch_port"
+    __VLAN_PARAM = "vlan"
+    __VNI_PARAM = "vni"
+    __SEPARATOR = '_'
+    __MANAGED_BY_OSM = '## Managed by OSM '
+    __OSM_PREFIX = "osm_"
+    __OSM_METADATA = "OSM_metadata"
+    __METADATA_PREFIX = '!## Service'
+    __EXC_TASK_EXEC_WAIT = 10
+    __ROLLB_TASK_EXEC_WAIT = 10
+    __API_REQUEST_TOUT = 60
+    __SWITCH_TAG_NAME = 'topology_type'
+    __SWITCH_TAG_VALUE = 'leaf'
+    __LOOPBACK_INTF = "Loopback0"
+    _VLAN = "VLAN"
+    _VXLAN = "VXLAN"
+    _VLAN_MLAG = "VLAN-MLAG"
+    _VXLAN_MLAG = "VXLAN-MLAG"
+
+
+    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.__regex = re.compile(
+            r'^(?:http|ftp)s?://'  # http:// or https://
+            r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
+            r'localhost|'  # localhost...
+            r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
+            r'(?::\d+)?', re.IGNORECASE)  # optional port
+        self.raiseException = True
+        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
+        if self.is_valid_destination(self.__wim.get("wim_url")):
+            self.__wim_url = self.__wim.get("wim_url")
+        else:
+            raise SdnConnectorError(message='Invalid wim_url value',
+                                    http_code=500)
+        self.__user = wim_account.get("user")
+        self.__passwd = wim_account.get("password")
+        self.client = None
+        self.cvp_inventory = None
+        self.cvp_tags = None
+        self.logger.debug("Arista SDN plugin {}, cvprac version {}, user:{} and config:{}".
+                          format(wim, cvprac_version, self.__user,
+                                 self.delete_keys_from_dict(config, ('passwd',))))
+        self.allDeviceFacts = []
+        self.taskC = None
+        try:
+            self.__load_topology()
+            self.__load_switches()
+        except SdnConnectorError as sc:
+            raise sc
+        except Exception as e:
+            raise SdnConnectorError(message="Unable to load switches from CVP",
+                                    http_code=500) from e
+        self.logger.debug("Using topology {} in Arista Leaf switches: {}".format(
+                self.topology,
+                self.delete_keys_from_dict(self.switches, ('passwd',))))
+        self.clC = AristaSDNConfigLet(self.topology)
+
+    def __load_topology(self):
+        self.topology = self._VXLAN_MLAG
+        if self.__config and self.__config.get('topology'):
+            topology = self.__config.get('topology')
+            if topology == "VLAN":
+                self.topology = self._VLAN
+            elif topology == "VXLAN":
+                self.topology = self._VXLAN
+            elif topology == "VLAN-MLAG":
+                self.topology = self._VLAN_MLAG
+            elif topology == "VXLAN-MLAG":
+                self.topology = self._VXLAN_MLAG
+
+    def __load_switches(self):
+        """ Retrieves the switches to configure in the following order
+        1.  from incoming configuration:
+        1.1 using port mapping
+              using user and password from WIM
+              retrieving Lo0 and AS from switch
+        1.2 from 'switches' parameter,
+              if any parameter is not present
+                Lo0 and AS - it will be requested to the switch
+        2.  Looking in the CloudVision inventory if not in configuration parameters
+        2.1 using the switches with the topology_type tag set to 'leaf'
+
+        All the search methods will be used
+        """
+        self.switches = {}
+        if self.__config and self.__config.get(self.__SERVICE_ENDPOINT_MAPPING):
+            for port in self.__config.get(self.__SERVICE_ENDPOINT_MAPPING):
+                switch_dpid = port.get(self.__SW_ID_PARAM)
+                if switch_dpid and switch_dpid not in self.switches:
+                    self.switches[switch_dpid] = {'passwd': self.__passwd,
+                                                  'ip': None,
+                                                  'usr': self.__user,
+                                                  'lo0': None,
+                                                  'AS': None,
+                                                  'serialNumber': None,
+                                                  'mlagPeerDevice': None}
+
+        if self.__config and self.__config.get('switches'):
+            # Not directly from json, complete one by one
+            config_switches = self.__config.get('switches')
+            for cs, cs_content in config_switches.items():
+                if cs not in self.switches:
+                    self.switches[cs] = {'passwd': self.__passwd,
+                                         'ip': None,
+                                         'usr': self.__user,
+                                         'lo0': None,
+                                         'AS': None,
+                                         'serialNumber': None,
+                                         'mlagPeerDevice': None}
+                if cs_content:
+                    self.switches[cs].update(cs_content)
+
+        # Load the rest of the data
+        if self.client == None:
+            self.client = self.__connect()
+        self.__load_inventory()
+        if not self.switches:
+            self.__get_tags(self.__SWITCH_TAG_NAME, self.__SWITCH_TAG_VALUE)
+            for device in self.allDeviceFacts:
+                # get the switches whose topology_tag is 'leaf'
+                if device['serialNumber'] in self.cvp_tags:
+                    if not self.switches.get(device['hostname']):
+                        switch_data = {'passwd': self.__passwd,
+                                       'ip': device['ipAddress'],
+                                       'usr': self.__user,
+                                       'lo0': None,
+                                       'AS': None,
+                                       'serialNumber': None,
+                                       'mlagPeerDevice': None}
+                        self.switches[device['hostname']] = switch_data
+        if len(self.switches) == 0:
+            self.logger.error("Unable to load Leaf switches from CVP")
+            return
+
+        # self.switches are switch objects, one for each switch in self.switches,
+        # used to make eAPI calls by using switch.py module
+        for s in self.switches:
+            for device in self.allDeviceFacts:
+                if device['hostname'] == s:
+                    if not self.switches[s].get('ip'):
+                        self.switches[s]['ip'] = device['ipAddress']
+                    self.switches[s]['serialNumber'] = device['serialNumber']
+                    break
+
+            # Each switch has a different loopback address,
+            # so it's a different configLet
+            if not self.switches[s].get('lo0'):
+                inf = self.__get_interface_ip(self.switches[s]['serialNumber'], self.__LOOPBACK_INTF)
+                self.switches[s]["lo0"] = inf.split('/')[0]
+            if not self.switches[s].get('AS'):
+                self.switches[s]["AS"] = self.__get_device_ASN(self.switches[s]['serialNumber'])
+        if self.topology in (self._VXLAN_MLAG, self._VLAN_MLAG):
+            for s in self.switches:
+                if not self.switches[s].get('mlagPeerDevice'):
+                    self.switches[s]['mlagPeerDevice'] = self.__get_peer_MLAG(self.switches[s]['serialNumber'])
+
+    def __check_service(self, service_type, connection_points,
+                        check_vlan=True, check_num_cp=True, kwargs=None):
+        """ Reviews the connection points elements looking for semantic errors in the incoming data
+        """
+        if service_type not in self.__supported_service_types:
+            raise Exception("The service '{}' is not supported. Only '{}' are accepted".format(
+                            service_type,
+                            self.__supported_service_types))
+
+        if check_num_cp:
+            if len(connection_points) < 2:
+                raise Exception(SdnError.CONNECTION_POINTS_SIZE)
+            if (len(connection_points) != self.__ELINE_num_connection_points and
+               service_type == self.__service_types_ELINE):
+                raise Exception(SdnError.CONNECTION_POINTS_SIZE)
+
+        if check_vlan:
+            vlan_id = ''
+            for cp in connection_points:
+                enc_type = cp.get(self.__ENCAPSULATION_TYPE_PARAM)
+                if (enc_type and
+                        enc_type not in self.__supported_encapsulation_types):
+                    raise Exception(SdnError.ENCAPSULATION_TYPE)
+                encap_info = cp.get(self.__ENCAPSULATION_INFO_PARAM)
+                cp_vlan_id = str(encap_info.get(self.__VLAN_PARAM))
+                if cp_vlan_id:
+                    if not vlan_id:
+                        vlan_id = cp_vlan_id
+                    elif vlan_id != cp_vlan_id:
+                        raise Exception(SdnError.VLAN_INCONSISTENT)
+            if not vlan_id:
+                raise Exception(SdnError.VLAN_NOT_PROVIDED)
+            if vlan_id in self.__get_srvVLANs():
+                raise Exception('VLAN {} already assigned to a connectivity service'.format(vlan_id))
+
+        # 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 check_credentials(self):
+        """Retrieves the CloudVision version information, as the easiest way
+        for testing the access to CloudVision API
+        """
+        try:
+            if self.client == None:
+                self.client = self.__connect()
+            result = self.client.api.get_cvp_info()
+            self.logger.debug(result)
+        except CvpLoginError as e:
+            self.logger.info(str(e))
+            self.client = None
+            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
+                                    http_code=401) from e
+        except Exception as ex:
+            self.client = None
+            self.logger.error(str(ex))
+            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
+                                    http_code=500) from ex
+
+    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.
+        """
+        try:
+            self.logger.debug("invoked get_connectivity_service_status '{}'".format(service_uuid))
+            if not service_uuid:
+                raise SdnConnectorError(message='No connection service UUID',
+                                        http_code=500)
+
+            self.__get_Connection()
+            if conn_info == None:
+                raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid),
+                                        http_code=500)
+
+            if 'configLetPerSwitch' in conn_info.keys():
+                c_info = conn_info
+            else:
+                c_info = None
+            cls_perSw = self.__get_serviceData(service_uuid,
+                                               conn_info['service_type'],
+                                               conn_info['vlan_id'],
+                                               c_info)
+
+            t_isCancelled = False
+            t_isFailed = False
+            t_isPending = False
+            failed_switches = []
+            for s in self.switches:
+                if len(cls_perSw[s]) > 0:
+                    for cl in cls_perSw[s]:
+                        # Fix 1030 SDN-ARISTA Key error note when deploy a NS
+                        # Added protection to check that 'note' exists and additionally
+                        # verify that it is managed by OSM
+                        if (not cls_perSw[s][0]['config'] or
+                                not cl.get('note') or
+                                self.__MANAGED_BY_OSM not in cl['note']):
+                            continue
+                        note = cl['note']
+                        t_id = note.split(self.__SEPARATOR)[1]
+                        result = self.client.api.get_task_by_id(t_id)
+                        if result['workOrderUserDefinedStatus'] == 'Completed':
+                            continue
+                        elif result['workOrderUserDefinedStatus'] == 'Cancelled':
+                            t_isCancelled = True
+                        elif result['workOrderUserDefinedStatus'] == 'Failed':
+                            t_isFailed = True
+                        else:
+                            t_isPending = True
+                        failed_switches.append(s)
+            if t_isCancelled:
+                error_msg = 'Some works were cancelled in switches: {}'.format(str(failed_switches))
+                sdn_status = 'DOWN'
+            elif t_isFailed:
+                error_msg = 'Some works failed in switches: {}'.format(str(failed_switches))
+                sdn_status = 'ERROR'
+            elif t_isPending:
+                error_msg = 'Some works are still under execution in switches: {}'.format(str(failed_switches))
+                sdn_status = 'BUILD'
+            else:
+                error_msg = ''
+                sdn_status = 'ACTIVE'
+            sdn_info = ''
+            return {'sdn_status': sdn_status,
+                    'error_msg': error_msg,
+                    'sdn_info': sdn_info}
+        except CvpLoginError as e:
+            self.logger.info(str(e))
+            self.client = None
+            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
+                                    http_code=401) from e
+        except Exception as ex:
+            self.client = None
+            self.logger.error(str(ex), exc_info=True)
+            raise SdnConnectorError(message=str(ex),
+                                    http_code=500) from ex
+
+    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
+                    "switch_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: SdnConnectorError: In case of error. Nothing should be
+                                    created in this case.
+            Provide the parameter http_code
+        """
+        try:
+            self.logger.debug("invoked create_connectivity_service '{}' ports: {}".
+                              format(service_type, connection_points))
+            self.__get_Connection()
+            self.__check_service(service_type,
+                                 connection_points,
+                                 check_vlan=True,
+                                 kwargs=kwargs)
+            service_uuid = str(uuid.uuid4())
+
+            self.logger.info("Service with uuid {} created.".
+                             format(service_uuid))
+            s_uid, s_connInf = self.__processConnection(
+                                        service_uuid,
+                                        service_type,
+                                        connection_points,
+                                        kwargs)
+            try:
+                self.__addMetadata(s_uid, service_type, s_connInf['vlan_id'])
+            except Exception as e:
+                pass
+
+            return (s_uid, s_connInf)
+        except CvpLoginError as e:
+            self.logger.info(str(e))
+            self.client = None
+            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
+                                    http_code=401) from e
+        except SdnConnectorError as sde:
+            raise sde
+        except ValueError as err:
+            self.client = None
+            self.logger.error(str(err), exc_info=True)
+            raise SdnConnectorError(message=str(err),
+                                    http_code=500) from err
+        except Exception as ex:
+            self.client = None
+            self.logger.error(str(ex), exc_info=True)
+            if self.raiseException:
+                raise ex
+            raise SdnConnectorError(message=str(ex),
+                                    http_code=500) from ex
+
+    def __processConnection(self,
+                            service_uuid,
+                            service_type,
+                            connection_points,
+                            kwargs):
+        """
+        Invoked from creation and edit methods
+
+        Process the connection points array,
+            creating a set of configuration per switch where it has to be applied
+            for creating the configuration, the switches have to be queried for obtaining:
+                - the loopback address
+                - the BGP ASN (autonomous system number)
+                - the interface name of the MAC address to add in the connectivity service
+        Once the new configuration is ready, the __updateConnection method is invoked for appling the changes
+        """
+        try:
+            cls_perSw = {}
+            cls_cp = {}
+            cl_bgp = {}
+            for s in self.switches:
+                cls_perSw[s] = []
+                cls_cp[s] = []
+            vlan_processed = False
+            vlan_id = ''
+            i = 0
+            processed_connection_points = []
+            for cp in connection_points:
+                i += 1
+                encap_info = cp.get(self.__ENCAPSULATION_INFO_PARAM)
+                if not vlan_processed:
+                    vlan_id = str(encap_info.get(self.__VLAN_PARAM))
+                    if not vlan_id:
+                        continue
+                    vni_id = encap_info.get(self.__VNI_PARAM)
+                    if not vni_id:
+                        vni_id = str(10000 + int(vlan_id))
+
+                    if service_type == self.__service_types_ELAN:
+                        cl_vlan = self.clC.getElan_vlan(service_uuid,
+                                                        vlan_id,
+                                                        vni_id)
+                    else:
+                        cl_vlan = self.clC.getEline_vlan(service_uuid,
+                                                         vlan_id,
+                                                         vni_id)
+                    vlan_processed = True
+
+                encap_type = cp.get(self.__ENCAPSULATION_TYPE_PARAM)
+                switch_id = encap_info.get(self.__SW_ID_PARAM)
+                interface = encap_info.get(self.__SW_PORT_PARAM)
+                switches = [{'name': switch_id, 'interface': interface}]
+
+                # remove those connections that are equal. This happens when several sriovs are located in the same
+                # compute node interface, that is, in the same switch and interface
+                switches = [x for x in switches if x not in processed_connection_points]
+                if not switches:
+                    continue
+                processed_connection_points += switches
+                for switch in switches:
+                    if not interface:
+                        raise SdnConnectorError(message="Connection point switch port empty for switch_dpid {}".format(switch_id),
+                                                http_code=406)
+                    # it should be only one switch where the mac is attached
+                    if encap_type == 'dot1q':
+                        # SRIOV configLet for Leaf switch mac's attached to
+                        if service_type == self.__service_types_ELAN:
+                            cl_encap = self.clC.getElan_sriov(service_uuid, interface, vlan_id, i)
+                        else:
+                            cl_encap = self.clC.getEline_sriov(service_uuid, interface, vlan_id, i)
+                    elif not encap_type:
+                        # PT configLet for Leaf switch attached to the mac
+                        if service_type == self.__service_types_ELAN:
+                            cl_encap = self.clC.getElan_passthrough(service_uuid,
+                                                                    interface,
+                                                                    vlan_id, i)
+                        else:
+                            cl_encap = self.clC.getEline_passthrough(service_uuid,
+                                                                     interface,
+                                                                     vlan_id, i)
+                    if cls_cp.get(switch['name']):
+                        cls_cp[switch['name']] = str(cls_cp[switch['name']]) + cl_encap
+                    else:
+                        cls_cp[switch['name']] = cl_encap
+
+            # at least 1 connection point has to be received
+            if not vlan_processed:
+                raise SdnConnectorError(message=SdnError.UNSUPPORTED_FEATURE,
+                                        http_code=406)
+
+            for s in self.switches:
+                # for cl in cp_configLets:
+                cl_name = (self.__OSM_PREFIX +
+                           s +
+                           self.__SEPARATOR + service_type + str(vlan_id) +
+                           self.__SEPARATOR + service_uuid)
+                cl_config = ''
+                # Apply BGP configuration only for VXLAN topologies
+                if self.topology in (self._VXLAN_MLAG, self._VXLAN):
+                    if service_type == self.__service_types_ELAN:
+                        cl_bgp[s] = self.clC.getElan_bgp(service_uuid,
+                                                         vlan_id,
+                                                         vni_id,
+                                                         self.switches[s]['lo0'],
+                                                         self.switches[s]['AS'])
+                    else:
+                        cl_bgp[s] = self.clC.getEline_bgp(service_uuid,
+                                                          vlan_id,
+                                                          vni_id,
+                                                          self.switches[s]['lo0'],
+                                                          self.switches[s]['AS'])
+                else:
+                    cl_bgp[s] = ''
+
+                if not cls_cp.get(s):
+                    # Apply VLAN configuration to peer MLAG switch,
+                    # only necessary when there are no connection points in the switch
+                    if self.topology in (self._VXLAN_MLAG, self._VLAN_MLAG):
+                        for p in self.switches:
+                            if self.switches[p]['mlagPeerDevice'] == s:
+                                if cls_cp.get(p):
+                                    cl_config = str(cl_vlan)
+                else:
+                    cl_config = str(cl_vlan) + str(cl_bgp[s]) + str(cls_cp[s])
+
+                cls_perSw[s] = [{'name': cl_name, 'config': cl_config}]
+
+            allLeafConfigured, allLeafModified = self.__updateConnection(cls_perSw)
+
+            conn_info = {
+                "uuid": service_uuid,
+                "status": "BUILD",
+                "service_type": service_type,
+                "vlan_id": vlan_id,
+                "connection_points": connection_points,
+                "configLetPerSwitch": cls_perSw,
+                'allLeafConfigured': allLeafConfigured,
+                'allLeafModified': allLeafModified}
+
+            return service_uuid, conn_info
+        except Exception as ex:
+            self.logger.debug("Exception processing connection {}: {}".
+                              format(service_uuid, str(ex)))
+            raise ex
+
+    def __updateConnection(self, cls_perSw):
+        """ Invoked in the creation and modification
+
+        checks if the new connection points config is:
+            - already in the Cloud Vision, the configLet is modified, and applied to the switch,
+                executing the corresponding task
+            - if it has to be removed:
+                then configuration has to be removed from the switch executing the corresponding task,
+                before trying to remove the configuration
+            - created, the configuration set is created, associated to the switch, and the associated
+                task to the configLet modification executed
+        In case of any error, rollback is executed, removing the created elements, and restoring to the
+        previous state.
+        """
+        try:
+            allLeafConfigured = {}
+            allLeafModified = {}
+
+            for s in self.switches:
+                allLeafConfigured[s] = False
+                allLeafModified[s] = False
+            cl_toDelete = []
+            for s in self.switches:
+                toDelete_in_cvp = False
+                if not (cls_perSw.get(s) and cls_perSw[s][0].get('config')):
+                    # when there is no configuration, means that there is no interface
+                    # in the switch to be connected, so the configLet has to be removed from CloudVision
+                    # after removing the ConfigLet fron the switch if it was already there
+
+                    # get config let name and key
+                    cl = cls_perSw[s]
+                    try:
+                        cvp_cl = self.client.api.get_configlet_by_name(cl[0]['name'])
+                        # remove configLet
+                        cl_toDelete.append(cvp_cl)
+                        cl[0] = cvp_cl
+                        toDelete_in_cvp = True
+                    except CvpApiError as error:
+                        if "Entity does not exist" in error.msg:
+                            continue
+                        else:
+                            raise error
+                    # remove configLet from device
+                else:
+                    res = self.__configlet_modify(cls_perSw[s])
+                    allLeafConfigured[s] = res[0]
+                    if not allLeafConfigured[s]:
+                        continue
+                    cl = cls_perSw[s]
+                res = self.__device_modify(
+                                           device_to_update=s,
+                                           new_configlets=cl,
+                                           delete=toDelete_in_cvp)
+                if "errorMessage" in str(res):
+                    raise Exception(str(res))
+                self.logger.info("Device {} modify result {}".format(s, res))
+                for t_id in res[1]['tasks']:
+                    if not toDelete_in_cvp:
+                        note_msg = "{}{}{}{}##".format(self.__MANAGED_BY_OSM,
+                                                       self.__SEPARATOR,
+                                                       t_id,
+                                                       self.__SEPARATOR)
+                        self.client.api.add_note_to_configlet(
+                                cls_perSw[s][0]['key'],
+                                note_msg)
+                        cls_perSw[s][0]['note'] = note_msg
+                    tasks = { t_id : {'workOrderId': t_id} }
+                    self.__exec_task(tasks, self.__EXC_TASK_EXEC_WAIT)
+                # with just one configLet assigned to a device,
+                # delete all if there are errors in next loops
+                if not toDelete_in_cvp:
+                    allLeafModified[s] = True
+            if len(cl_toDelete) > 0:
+                self.__configlet_modify(cl_toDelete, delete=True)
+
+            return allLeafConfigured, allLeafModified
+        except Exception as ex:
+            try:
+                self.__rollbackConnection(cls_perSw,
+                                          allLeafConfigured,
+                                          allLeafModified)
+            except Exception as e:
+                self.logger.error("Exception rolling back in updating  connection: {}".
+                                 format(e), exc_info=True)
+            raise ex
+
+    def __rollbackConnection(self,
+                             cls_perSw,
+                             allLeafConfigured,
+                             allLeafModified):
+        """ Removes the given configLet from the devices and then remove the configLets
+        """
+        for s in self.switches:
+            if allLeafModified[s]:
+                try:
+                    res = self.__device_modify(
+                        device_to_update=s,
+                        new_configlets=cls_perSw[s],
+                        delete=True)
+                    if "errorMessage" in str(res):
+                        raise Exception(str(res))
+                    tasks = dict()
+                    for t_id in res[1]['tasks']:
+                        tasks[t_id] = {'workOrderId': t_id}
+                    self.__exec_task(tasks)
+                    self.logger.info("Device {} modify result {}".format(s, res))
+                except Exception as e:
+                    self.logger.error('Error removing configlets from device {}: {}'.format(s, e))
+                    pass
+        for s in self.switches:
+            if allLeafConfigured[s]:
+                self.__configlet_modify(cls_perSw[s], delete=True)
+
+    def __exec_task(self, tasks, tout=10):
+        if self.taskC == None:
+            self.__connect()
+        data = self.taskC.update_all_tasks(tasks).values()
+        self.taskC.task_action(data, tout, 'executed')
+
+    def __device_modify(self, device_to_update, new_configlets, delete):
+        """ Updates the devices (switches) adding or removing the configLet,
+        the tasks Id's associated to the change are returned
+        """
+        self.logger.info('Enter in __device_modify delete: {}'.format(
+                            delete))
+        updated = []
+        changed = False
+        # Task Ids that have been identified during device actions
+        newTasks = []
+
+        if (len(new_configlets) == 0 or
+                device_to_update == None or
+                len(device_to_update) == 0):
+            data = {'updated': updated, 'tasks': newTasks}
+            return [changed, data]
+
+        self.__load_inventory()
+
+        allDeviceFacts = self.allDeviceFacts
+        # Work through Devices list adding device specific information
+        device = None
+        for try_device in allDeviceFacts:
+            # Add Device Specific Configlets
+            # self.logger.debug(device)
+            if try_device['hostname'] not in device_to_update:
+                continue
+            dev_cvp_configlets = self.client.api.get_configlets_by_device_id(
+                                    try_device['systemMacAddress'])
+            # self.logger.debug(dev_cvp_configlets)
+            try_device['deviceSpecificConfiglets'] = []
+            for cvp_configlet in dev_cvp_configlets:
+                if int(cvp_configlet['containerCount']) == 0:
+                    try_device['deviceSpecificConfiglets'].append(
+                                {'name': cvp_configlet['name'],
+                                 'key': cvp_configlet['key']})
+            # self.logger.debug(device)
+            device = try_device
+            break
+
+        # Check assigned configlets
+        device_update = False
+        add_configlets = []
+        remove_configlets = []
+        update_devices = []
+
+        if delete:
+            for cvp_configlet in device['deviceSpecificConfiglets']:
+                for cl in new_configlets:
+                    if cvp_configlet['name'] == cl['name']:
+                        remove_configlets.append(cvp_configlet)
+                        device_update = True
+        else:
+            for configlet in new_configlets:
+                if configlet not in device['deviceSpecificConfiglets']:
+                    add_configlets.append(configlet)
+                    device_update = True
+        if device_update:
+            update_devices.append({'hostname': device['hostname'],
+                                   'configlets': [add_configlets,
+                                                  remove_configlets],
+                                   'device': device})
+        self.logger.info("Device to modify: {}".format(update_devices))
+
+        up_device = update_devices[0]
+        cl_toAdd = up_device['configlets'][0]
+        cl_toDel = up_device['configlets'][1]
+        # Update Configlets
+        try:
+            if delete and len(cl_toDel) > 0:
+                r = self.client.api.remove_configlets_from_device(
+                                                    'OSM',
+                                                    up_device['device'],
+                                                    cl_toDel,
+                                                    create_task=True)
+                dev_action = r
+                self.logger.debug("remove_configlets_from_device {} {}".format(dev_action, cl_toDel))
+            elif len(cl_toAdd) > 0:
+                r = self.client.api.apply_configlets_to_device(
+                                                    'OSM',
+                                                    up_device['device'],
+                                                    cl_toAdd,
+                                                    create_task=True)
+                dev_action = r
+                self.logger.debug("apply_configlets_to_device {} {}".format(dev_action, cl_toAdd))
+
+        except Exception as error:
+            errorMessage = str(error)
+            msg = "errorMessage: Device {} Configlets couldnot be updated: {}".format(
+                  up_device['hostname'], errorMessage)
+            raise SdnConnectorError(msg) from error
+        else:
+            if "errorMessage" in str(dev_action):
+                m = "Device {} Configlets update fail: {}".format(
+                            up_device['name'], dev_action['errorMessage'])
+                raise SdnConnectorError(m)
+            else:
+                changed = True
+                if 'taskIds' in str(dev_action):
+                    # Fix 1030 SDN-ARISTA Key error note when deploy a NS
+                    if not dev_action['data']['taskIds']:
+                        raise SdnConnectorError("No taskIds found: Device {} Configlets couldnot be updated".format(
+                                        up_device['hostname']))
+                    for taskId in dev_action['data']['taskIds']:
+                        updated.append({up_device['hostname']:
+                            "Configlets-{}".format(
+                                taskId)})
+                        newTasks.append(taskId)
+                else:
+                    updated.append({up_device['hostname']:
+                                   "Configlets-No_Specific_Tasks"})
+        data = {'updated': updated, 'tasks': newTasks}
+        return [changed, data]
+
+    def __configlet_modify(self, configletsToApply, delete=False):
+        ''' adds/update or delete the provided configLets
+        :param configletsToApply: list of configLets to apply
+        :param delete: flag to indicate if the configLets have to be deleted
+                        from Cloud Vision Portal
+        :return: data: dict of module actions and taskIDs
+        '''
+        self.logger.info('Enter in __configlet_modify delete:{}'.format(
+                            delete))
+
+        # Compare configlets against cvp_facts-configlets
+        changed = False
+        checked = []
+        deleted = []
+        updated = []
+        new = []
+
+        for cl in configletsToApply:
+            found_in_cvp = False
+            to_delete = False
+            to_update = False
+            to_create = False
+            to_check = False
+            try:
+                cvp_cl = self.client.api.get_configlet_by_name(cl['name'])
+                cl['key'] = cvp_cl['key']
+                cl['note'] = cvp_cl['note']
+                found_in_cvp = True
+            except CvpApiError as error:
+                if "Entity does not exist" in error.msg:
+                    pass
+                else:
+                    raise error
+
+            if delete:
+                if found_in_cvp:
+                    to_delete = True
+                    configlet = {'name': cvp_cl['name'],
+                                 'data': cvp_cl}
+            else:
+                if found_in_cvp:
+                    cl_compare = self.__compare(cl['config'],
+                                                cvp_cl['config'])
+                    # compare function returns a floating point number
+                    if cl_compare[0] != 100.0:
+                        to_update = True
+                        configlet = {'name': cl['name'],
+                                     'data': cvp_cl,
+                                     'config': cl['config']}
+                    else:
+                        to_check = True
+                        configlet = {'name': cl['name'],
+                                     'key': cvp_cl['key'],
+                                     'data': cvp_cl,
+                                     'config': cl['config']}
+                else:
+                    to_create = True
+                    configlet = {'name': cl['name'],
+                                 'config': cl['config']}
+            try:
+                if to_delete:
+                    operation = 'delete'
+                    resp = self.client.api.delete_configlet(
+                                    configlet['data']['name'],
+                                    configlet['data']['key'])
+                elif to_update:
+                    operation = 'update'
+                    resp = self.client.api.update_configlet(
+                                    configlet['config'],
+                                    configlet['data']['key'],
+                                    configlet['data']['name'],
+                                    wait_task_ids=True)
+                elif to_create:
+                    operation = 'create'
+                    resp = self.client.api.add_configlet(
+                                    configlet['name'],
+                                    configlet['config'])
+                else:
+                    operation = 'checked'
+                    resp = 'checked'
+            except Exception as error:
+                errorMessage = str(error).split(':')[-1]
+                message = "Configlet {} cannot be {}: {}".format(
+                            cl['name'], operation, errorMessage)
+                if to_delete:
+                    deleted.append({configlet['name']: message})
+                elif to_update:
+                    updated.append({configlet['name']: message})
+                elif to_create:
+                    new.append({configlet['name']: message})
+                elif to_check:
+                    checked.append({configlet['name']: message})
+
+            else:
+                if "error" in str(resp).lower():
+                    message = "Configlet {} cannot be deleted: {}".format(
+                            cl['name'], resp['errorMessage'])
+                    if to_delete:
+                        deleted.append({configlet['name']: message})
+                    elif to_update:
+                        updated.append({configlet['name']: message})
+                    elif to_create:
+                        new.append({configlet['name']: message})
+                    elif to_check:
+                        checked.append({configlet['name']: message})
+                else:
+                    if to_delete:
+                        changed = True
+                        deleted.append({configlet['name']: "success"})
+                    elif to_update:
+                        changed = True
+                        updated.append({configlet['name']: "success"})
+                    elif to_create:
+                        changed = True
+                        cl['key'] = resp  # This key is used in API call deviceApplyConfigLet FGA
+                        new.append({configlet['name']: "success"})
+                    elif to_check:
+                        changed = False
+                        checked.append({configlet['name']: "success"})
+
+        data = {'new': new, 'updated': updated, 'deleted': deleted, 'checked': checked}
+        return [changed, data]
+
+    def __get_configletsDevices(self, configlets):
+        for s in self.switches:
+            configlet = configlets[s]
+            # Add applied Devices
+            if len(configlet) > 0:
+                configlet['devices'] = []
+                applied_devices = self.client.api.get_applied_devices(
+                                configlet['name'])
+                for device in applied_devices['data']:
+                    configlet['devices'].append(device['hostName'])
+
+    def __get_serviceData(self, service_uuid, service_type, vlan_id, conn_info=None):
+        cls_perSw = {}
+        for s in self.switches:
+            cls_perSw[s] = []
+        if not conn_info:
+            srv_cls = self.__get_serviceConfigLets(service_uuid,
+                                                   service_type,
+                                                   vlan_id)
+            self.__get_configletsDevices(srv_cls)
+            for s in self.switches:
+                cl = srv_cls[s]
+                if len(cl) > 0:
+                    for dev in cl['devices']:
+                        cls_perSw[dev].append(cl)
+        else:
+            cls_perSw = conn_info['configLetPerSwitch']
+        return cls_perSw
+
+    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
+        """
+        try:
+            self.logger.debug('invoked delete_connectivity_service {}'.
+                              format(service_uuid))
+            if not service_uuid:
+                raise SdnConnectorError(message='No connection service UUID',
+                                        http_code=500)
+
+            self.__get_Connection()
+            if conn_info == None:
+                raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid),
+                                        http_code=500)
+            c_info = None
+            cls_perSw = self.__get_serviceData(service_uuid,
+                                               conn_info['service_type'],
+                                               conn_info['vlan_id'],
+                                               c_info)
+            allLeafConfigured = {}
+            allLeafModified = {}
+            for s in self.switches:
+                allLeafConfigured[s] = True
+                allLeafModified[s] = True
+            found_in_cvp = False
+            for s in self.switches:
+                if cls_perSw[s]:
+                    found_in_cvp = True
+            if found_in_cvp:
+                self.__rollbackConnection(cls_perSw,
+                                          allLeafConfigured,
+                                          allLeafModified)
+            else:
+                # if the service is not defined in Cloud Vision, return a 404 - NotFound error
+                raise SdnConnectorError(message='Service {} was not found in Arista Cloud Vision {}'.
+                                        format(service_uuid, self.__wim_url),
+                                        http_code=404)
+            self.__removeMetadata(service_uuid)
+        except CvpLoginError as e:
+            self.logger.info(str(e))
+            self.client = None
+            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
+                                    http_code=401) from e
+        except SdnConnectorError as sde:
+            raise sde
+        except Exception as ex:
+            self.client = None
+            self.logger.error(ex)
+            if self.raiseException:
+                raise ex
+            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
+                                    http_code=500) from ex
+
+    def __addMetadata(self, service_uuid, service_type, vlan_id):
+        """ Adds the connectivity service from 'OSM_metadata' configLet
+        """
+        found_in_cvp = False
+        try:
+            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
+            found_in_cvp = True
+        except CvpApiError as error:
+            if "Entity does not exist" in error.msg:
+                pass
+            else:
+                raise error
+        try:
+            new_serv = '{} {} {} {}\n'.format(self.__METADATA_PREFIX, service_type, vlan_id, service_uuid)
+
+            if found_in_cvp:
+                cl_config = cvp_cl['config'] + new_serv
+            else:
+                cl_config = new_serv
+            cl_meta = [{'name': self.__OSM_METADATA, 'config': cl_config}]
+            self.__configlet_modify(cl_meta)
+        except Exception as e:
+            self.logger.error('Error in setting metadata in CloudVision from OSM for service {}: {}'.
+                              format(service_uuid, str(e)))
+            pass
+
+    def __removeMetadata(self, service_uuid):
+        """ Removes the connectivity service from 'OSM_metadata' configLet
+        """
+        found_in_cvp = False
+        try:
+            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
+            found_in_cvp = True
+        except CvpApiError as error:
+            if "Entity does not exist" in error.msg:
+                pass
+            else:
+                raise error
+        try:
+            if found_in_cvp:
+                if service_uuid in cvp_cl['config']:
+                    cl_config = ''
+                    for line in cvp_cl['config'].split('\n'):
+                        if service_uuid in line:
+                            continue
+                        else:
+                            cl_config = cl_config + line
+                    cl_meta = [{'name': self.__OSM_METADATA, 'config': cl_config}]
+                    self.__configlet_modify(cl_meta)
+        except Exception as e:
+            self.logger.error('Error in removing metadata in CloudVision from OSM for service {}: {}'.
+                              format(service_uuid, str(e)))
+            pass
+
+    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:
+            SdnConnectorError: In case of error.
+        """
+        try:
+            self.logger.debug('invoked edit_connectivity_service for service {}. ports: {}'.format(service_uuid,
+                                                                                                   connection_points))
+
+            if not service_uuid:
+                raise SdnConnectorError(message='Unable to perform operation, missing or empty uuid',
+                                        http_code=500)
+            if not conn_info:
+                raise SdnConnectorError(message='Unable to perform operation, missing or empty connection information',
+                                        http_code=500)
+
+            if connection_points == None:
+                return None
+
+            self.__get_Connection()
+
+            cls_currentPerSw = conn_info['configLetPerSwitch']
+            service_type = conn_info['service_type']
+
+            self.__check_service(service_type,
+                                 connection_points,
+                                 check_vlan=False,
+                                 check_num_cp=False,
+                                 kwargs=kwargs)
+
+            s_uid, s_connInf = self.__processConnection(
+                                                        service_uuid,
+                                                        service_type,
+                                                        connection_points,
+                                                        kwargs)
+            self.logger.info("Service with uuid {} configuration updated".
+                             format(s_uid))
+            return s_connInf
+        except CvpLoginError as e:
+            self.logger.info(str(e))
+            self.client = None
+            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
+                                    http_code=401) from e
+        except SdnConnectorError as sde:
+            raise sde
+        except Exception as ex:
+            try:
+                # Add previous
+                # TODO check if there are pending task, and cancel them before restoring
+                self.__updateConnection(cls_currentPerSw)
+            except Exception as e:
+                self.logger.error("Unable to restore configuration in service {} after an error in the configuration updated: {}".
+                                  format(service_uuid, str(e)))
+            if self.raiseException:
+                raise ex
+            raise SdnConnectorError(message=str(ex),
+                                    http_code=500) from ex
+
+    def clear_all_connectivity_services(self):
+        """ Removes all connectivity services from Arista CloudVision with two steps:
+        - retrives all the services from Arista CloudVision
+        - removes each service
+        """
+        try:
+            self.logger.debug('invoked AristaImpl ' +
+                              'clear_all_connectivity_services')
+            self.__get_Connection()
+            s_list = self.__get_srvUUIDs()
+            for serv in s_list:
+                conn_info = {}
+                conn_info['service_type'] = serv['type']
+                conn_info['vlan_id'] = serv['vlan']
+
+                self.delete_connectivity_service(serv['uuid'], conn_info)
+        except CvpLoginError as e:
+            self.logger.info(str(e))
+            self.client = None
+            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
+                                    http_code=401) from e
+        except SdnConnectorError as sde:
+            raise sde
+        except Exception as ex:
+            self.client = None
+            self.logger.error(ex)
+            if self.raiseException:
+                raise ex
+            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
+                                    http_code=500) from ex
+
+    def get_all_active_connectivity_services(self):
+        """ Return the uuid of all the active connectivity services with two steps:
+        - retrives all the services from Arista CloudVision
+        - retrives the status of each server
+        """
+        try:
+            self.logger.debug('invoked AristaImpl {}'.format(
+                              'get_all_active_connectivity_services'))
+            self.__get_Connection()
+            s_list = self.__get_srvUUIDs()
+            result = []
+            for serv in s_list:
+                conn_info = {}
+                conn_info['service_type'] = serv['type']
+                conn_info['vlan_id'] = serv['vlan']
+
+                status = self.get_connectivity_service_status(serv['uuid'], conn_info)
+                if status['sdn_status'] == 'ACTIVE':
+                    result.append(serv['uuid'])
+            return result
+        except CvpLoginError as e:
+            self.logger.info(str(e))
+            self.client = None
+            raise SdnConnectorError(message=SdnError.UNAUTHORIZED,
+                                    http_code=401) from e
+        except SdnConnectorError as sde:
+            raise sde
+        except Exception as ex:
+            self.client = None
+            self.logger.error(ex)
+            if self.raiseException:
+                raise ex
+            raise SdnConnectorError(message=SdnError.INTERNAL_ERROR,
+                                    http_code=500) from ex
+
+    def __get_serviceConfigLets(self, service_uuid, service_type, vlan_id):
+        """ Return the configLet's associated with a connectivity service,
+        There should be one, as maximum, per device (switch) for a given
+        connectivity service
+        """
+        srv_cls = {}
+        for s in self.switches:
+            srv_cls[s] = []
+            found_in_cvp = False
+            name = (self.__OSM_PREFIX +
+                    s +
+                    self.__SEPARATOR + service_type + str(vlan_id) +
+                    self.__SEPARATOR + service_uuid)
+            try:
+                cvp_cl = self.client.api.get_configlet_by_name(name)
+                found_in_cvp = True
+            except CvpApiError as error:
+                if "Entity does not exist" in error.msg:
+                    pass
+                else:
+                    raise error
+            if found_in_cvp:
+                srv_cls[s] = cvp_cl
+        return srv_cls
+
+    def __get_srvVLANs(self):
+        """ Returns a list with all the VLAN id's used in the connectivity services managed
+        in tha Arista CloudVision by checking the 'OSM_metadata' configLet where this
+        information is stored
+        """
+        found_in_cvp = False
+        try:
+            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
+            found_in_cvp = True
+        except CvpApiError as error:
+            if "Entity does not exist" in error.msg:
+                pass
+            else:
+                raise error
+        s_vlan_list = []
+        if found_in_cvp:
+            lines = cvp_cl['config'].split('\n')
+            for line in lines:
+                if self.__METADATA_PREFIX in line:
+                    s_vlan = line.split(' ')[3]
+                else:
+                    continue
+                if (s_vlan is not None and
+                        len(s_vlan) > 0 and
+                        s_vlan not in s_vlan_list):
+                    s_vlan_list.append(s_vlan)
+
+        return s_vlan_list
+
+    def __get_srvUUIDs(self):
+        """ Retrieves all the connectivity services, managed in tha Arista CloudVision
+        by checking the 'OSM_metadata' configLet where this information is stored
+        """
+        found_in_cvp = False
+        try:
+            cvp_cl = self.client.api.get_configlet_by_name(self.__OSM_METADATA)
+            found_in_cvp = True
+        except CvpApiError as error:
+            if "Entity does not exist" in error.msg:
+                pass
+            else:
+                raise error
+        serv_list = []
+        if found_in_cvp:
+            lines = cvp_cl['config'].split('\n')
+            for line in lines:
+                if self.__METADATA_PREFIX in line:
+                    line = line.split(' ')
+                    serv = {'uuid': line[4], 'type': line[2], 'vlan': line[3]}
+                else:
+                    continue
+                if (serv is not None and
+                        len(serv) > 0 and
+                        serv not in serv_list):
+                    serv_list.append(serv)
+
+        return serv_list
+
+    def __get_Connection(self):
+        """ Open a connection with Arista CloudVision,
+            invoking the version retrival as test
+        """
+        try:
+            if self.client == None:
+                self.client = self.__connect()
+            self.client.api.get_cvp_info()
+        except (CvpSessionLogOutError, RequestException) as e:
+            self.logger.debug("Connection error '{}'. Reconnecting".format(e))
+            self.client = self.__connect()
+            self.client.api.get_cvp_info()
+
+    def __connect(self):
+        ''' Connects to CVP device using user provided credentials from initialization.
+        :return: CvpClient object with connection instantiated.
+        '''
+        client = CvpClient()
+        protocol, _, rest_url = self.__wim_url.rpartition("://")
+        host, _, port = rest_url.partition(":")
+        if port and port.endswith("/"):
+            port = int(port[:-1])
+        elif port:
+            port = int(port)
+        else:
+            port = 443
+
+        client.connect([host],
+                       self.__user,
+                       self.__passwd,
+                       protocol=protocol or "https",
+                       port=port,
+                       connect_timeout=2)
+        client.api = CvpApi(client, request_timeout=self.__API_REQUEST_TOUT)
+        self.taskC = AristaCVPTask(client.api)
+        return client
+
+    def __compare(self, fromText, toText, lines=10):
+        """ Compare text string in 'fromText' with 'toText' and produce
+        diffRatio - a score as a float in the range [0, 1] 2.0*M / T
+          T is the total number of elements in both sequences,
+          M is the number of matches.
+          Score - 1.0 if the sequences are identical, and
+                  0.0 if they have nothing in common.
+        unified diff list
+          Code Meaning
+          '- ' line unique to sequence 1
+          '+ ' line unique to sequence 2
+          '  ' line common to both sequences
+          '? ' line not present in either input sequence
+        """
+        fromlines = fromText.splitlines(1)
+        tolines = toText.splitlines(1)
+        diff = list(difflib.unified_diff(fromlines, tolines, n=lines))
+        textComp = difflib.SequenceMatcher(None, fromText, toText)
+        diffRatio = round(textComp.quick_ratio()*100, 2)
+        return [diffRatio, diff]
+
+    def __load_inventory(self):
+        """ Get Inventory Data for All Devices (aka switches) from the Arista CloudVision
+        """
+        if not self.cvp_inventory:
+            self.cvp_inventory = self.client.api.get_inventory()
+        self.allDeviceFacts = []
+        for device in self.cvp_inventory:
+            self.allDeviceFacts.append(device)
+
+    def __get_tags(self, name, value):
+        if not self.cvp_tags:
+            self.cvp_tags = []
+            url = '/api/v1/rest/analytics/tags/labels/devices/{}/value/{}/elements'.format(name, value)
+            self.logger.debug('get_tags: URL {}'.format(url))
+            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
+            for dev in data['notifications']:
+                for elem in dev['updates']:
+                    self.cvp_tags.append(elem)
+        self.logger.debug('Available devices with tag_name {} - value {}: {} '.format(name, value, self.cvp_tags))
+
+    def __get_interface_ip(self, device_id, interface):
+        url = '/api/v1/rest/{}/Sysdb/ip/config/ipIntfConfig/{}/'.format(device_id, interface)
+        self.logger.debug('get_interface_ip: URL {}'.format(url))
+        try:
+            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
+            return data['notifications'][0]['updates']['addrWithMask']['value'].split('/')[0]
+        except Exception:
+            raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data))
+
+    def __get_device_ASN(self, device_id):
+        url = '/api/v1/rest/{}/Sysdb/routing/bgp/config/'.format(device_id)
+        self.logger.debug('get_device_ASN: URL {}'.format(url))
+        try:
+            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
+            return data['notifications'][0]['updates']['asNumber']['value']['value']['int']
+        except Exception:
+            raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data))
+
+    def __get_peer_MLAG(self, device_id):
+        peer = None
+        url = '/api/v1/rest/{}/Sysdb/mlag/status/'.format(device_id)
+        self.logger.debug('get_MLAG_status: URL {}'.format(url))
+        try:
+            data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
+            if data['notifications']:
+                found = False
+                for notification in data['notifications']:
+                    for update in notification['updates']:
+                        if update == 'systemId':
+                            mlagSystemId = notification['updates'][update]['value']
+                            found = True
+                            break
+                    if found:
+                        break
+                # search the MLAG System Id
+                if found:
+                    for s in self.switches:
+                        if self.switches[s]['serialNumber'] == device_id:
+                            continue
+                        url = '/api/v1/rest/{}/Sysdb/mlag/status/'.format(self.switches[s]['serialNumber'])
+                        self.logger.debug('Searching for MLAG system id {} in switch {}'.format(mlagSystemId, s))
+                        data = self.client.get(url, timeout=self.__API_REQUEST_TOUT)
+                        found = False
+                        for notification in data['notifications']:
+                            for update in notification['updates']:
+                                if update == 'systemId':
+                                    if mlagSystemId == notification['updates'][update]['value']:
+                                        peer = s
+                                        found = True
+                                        break
+                            if found:
+                                break
+                        if found:
+                            break
+            if peer == None:
+                self.logger.error('No Peer device found for device {} with MLAG address {}'.format(device_id, mlagSystemId))
+            else:
+                self.logger.debug('Peer MLAG for device {} - value {}'.format(device_id, peer))
+            return peer
+        except Exception:
+            raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data))
+
+    def is_valid_destination(self, url):
+        """ Check that the provided WIM URL is correct
+        """
+        if re.match(self.__regex, url):
+            return True
+        elif self.is_valid_ipv4_address(url):
+            return True
+        else:
+            return self.is_valid_ipv6_address(url)
+
+    def is_valid_ipv4_address(self, address):
+        """ Checks that the given IP is IPv4 valid
+        """
+        try:
+            socket.inet_pton(socket.AF_INET, address)
+        except AttributeError:  # no inet_pton here, sorry
+            try:
+                socket.inet_aton(address)
+            except socket.error:
+                return False
+            return address.count('.') == 3
+        except socket.error:  # not a valid address
+            return False
+        return True
+
+    def is_valid_ipv6_address(self, address):
+        """ Checks that the given IP is IPv6 valid
+        """
+        try:
+            socket.inet_pton(socket.AF_INET6, address)
+        except socket.error:  # not a valid address
+            return False
+        return True
+
+    def delete_keys_from_dict(self, dict_del, lst_keys):
+        if dict_del == None:
+            return dict_del
+        dict_copy = {k: v for k, v in dict_del.items() if k not in lst_keys}
+        for k, v in dict_copy.items():
+            if isinstance(v, dict):
+                dict_copy[k] = self.delete_keys_from_dict(v, lst_keys)
+        return dict_copy
diff --git a/RO-SDN-arista_cloudvision/requirements.txt b/RO-SDN-arista_cloudvision/requirements.txt
new file mode 100644 (file)
index 0000000..cd1edfe
--- /dev/null
@@ -0,0 +1,20 @@
+##
+# 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
+uuid
+cvprac
+git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro
+
diff --git a/RO-SDN-arista_cloudvision/setup.py b/RO-SDN-arista_cloudvision/setup.py
new file mode 100644 (file)
index 0000000..ab8385b
--- /dev/null
@@ -0,0 +1,56 @@
+#!/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_arista_cloudvision"
+
+README = """
+===========
+osm-rosdn_arista_cloudvision
+===========
+
+osm-ro pluging for arista_cloudvision SDN
+"""
+
+setup(
+    name=_name,
+    description='OSM ro sdn plugin for arista with CloudVision',
+    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='oscarluis.peral@atos.net',  # 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,
+    install_requires=["requests",
+                      "uuid",
+                      "cvprac",
+                      "osm-ro @ git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro&subdirectory=RO"],
+    setup_requires=['setuptools-version-command'],
+    entry_points={
+        'osm_rosdn.plugins': ['rosdn_arista_cloudvision = osm_rosdn_arista_cloudvision.'
+                              'wimconn_arista:AristaSdnConnector']
+    },
+)
diff --git a/RO-SDN-arista_cloudvision/stdeb.cfg b/RO-SDN-arista_cloudvision/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-arista_cloudvision/tox.ini b/RO-SDN-arista_cloudvision/tox.ini
new file mode 100644 (file)
index 0000000..d534123
--- /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 = flake8
+toxworkdir={toxinidir}/.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_arista_cloudvision --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_arista_cloudvision.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-ietfl2vpn/Makefile b/RO-SDN-ietfl2vpn/Makefile
new file mode 100644 (file)
index 0000000..c88b074
--- /dev/null
@@ -0,0 +1,26 @@
+##
+# 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_ietfl2vpn-*.tar.gz osm_rosdn_ietfl2vpn.egg-info .eggs
+       rm -rf osm_rosdn_tapi-*.tar.gz osm_rosdn_tapi.egg-info
+
+package:
+       python3 setup.py --command-packages=stdeb.command sdist_dsc
+       cd deb_dist/osm-rosdn-ietfl2vpn*/ && dpkg-buildpackage -rfakeroot -uc -us
+       for pkg in deb_dist/python3-osm-rosdn-ietfl2vpn*.deb; do cp $$pkg deb_dist/python3-osm-rosdn-tapi$${pkg#*-osm-rosdn-ietfl2vpn} ; done
+
diff --git a/RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn/wimconn_ietfl2vpn.py b/RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn/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-ietfl2vpn/requirements.txt b/RO-SDN-ietfl2vpn/requirements.txt
new file mode 100644 (file)
index 0000000..a6f6d65
--- /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&subdirectory=RO
+
diff --git a/RO-SDN-ietfl2vpn/setup.py b/RO-SDN-ietfl2vpn/setup.py
new file mode 100644 (file)
index 0000000..9db91ef
--- /dev/null
@@ -0,0 +1,56 @@
+#!/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_ietfl2vpn"
+
+README = """
+===========
+osm-rosdn_ietfl2vpn
+===========
+
+osm-ro pluging for ietfl2vpn SDN
+"""
+
+setup(
+    name=_name,
+    description='OSM ro sdn plugin for 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 @ git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro&subdirectory=RO"
+    ],
+    setup_requires=['setuptools-version-command'],
+    entry_points={
+        'osm_rosdn.plugins': ['rosdn_ietfl2vpn = osm_rosdn_ietfl2vpn.wimconn_ietfl2vpn:WimconnectorIETFL2VPN'],
+    },
+)
diff --git a/RO-SDN-ietfl2vpn/stdeb.cfg b/RO-SDN-ietfl2vpn/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-ietfl2vpn/tox.ini b/RO-SDN-ietfl2vpn/tox.ini
new file mode 100644 (file)
index 0000000..040210c
--- /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_ietfl2vpn --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_ietfl2vpn.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
deleted file mode 100644 (file)
index 2e05280..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-##
-# 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
deleted file mode 100644 (file)
index 26680b5..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 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
deleted file mode 100644 (file)
index a6f6d65..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-##
-# 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&subdirectory=RO
-
diff --git a/RO-SDN-tapi/setup.py b/RO-SDN-tapi/setup.py
deleted file mode 100644 (file)
index 9217492..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/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 @ git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro&subdirectory=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
deleted file mode 100644 (file)
index 0c718e4..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# 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
deleted file mode 100644 (file)
index 7d643cd..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-##
-# 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 0e6d32c..b2eb3ff 100755 (executable)
@@ -2263,7 +2263,7 @@ def main():
     wim_create_parser.add_argument("url", action="store",
                                    help="url for the wim")
     wim_create_parser.add_argument("--type", action="store",
-                                   help="wim type: tapi, onos, dynpac or odl (default)")
+                                   help="wim type: ietfl2vpn, dynpac, ...")
     wim_create_parser.add_argument("--config", action="store",
                                    help="additional configuration in json/yaml format")
     wim_create_parser.add_argument("--description", action="store",
index fc8bde1..a72c73e 100644 (file)
@@ -602,7 +602,7 @@ class openmanoclient():
                 must be a dictionary or a json/yaml text.
             name: the wim name. Overwrite descriptor name if any
             wim_url: the wim URL. Overwrite descriptor vim_url if any
-            wim_type: the WIM type, can be tapi, odl, onos. Overwrite descriptor type if any
+            wim_type: the WIM type, can be ietfl2vpn, odl, onos. Overwrite descriptor type if any
             public: boolean, by default not public
             description: wim description. Overwrite descriptor description if any
             config: dictionary with extra configuration for the concrete wim
@@ -639,7 +639,7 @@ class openmanoclient():
             parameters to change can be supplied by the descriptor or as parameters:
                 new_name: the wim name
                 wim_url: the wim URL
-                wim_type: the wim type, can be tapi, onos, odl
+                wim_type: the wim type, can be ietfl2vpn, onos, odl
                 public: boolean, available to other tenants
                 description: wim description
         Return: Raises an exception on error, not found or found several
index f20d418..6c0a848 100644 (file)
@@ -39,7 +39,7 @@ from ..openmano_schemas import (
 )
 
 # WIM -------------------------------------------------------------------------
-wim_types = ["tapi", "onos", "onos_vpls", "odl", "dynpac", "dummy"]
+wim_types = ["ietfl2vpn", "dynpac", "arista_cloudvision", "floodlightof", "onosof", "onos_vpls", "odlof", "dummy"]
 
 dpid_type = {
     "type": "string",
@@ -96,7 +96,7 @@ wim_schema_properties = {
     "description": description_schema,
     "type": {
         "type": "string",
-        # "enum": ["tapi", "onos", "odl", "dynpac", "dummy"]
+        # "enum": ["ietfl2vpn", "onos", "odl", "dynpac", "dummy", ...]
     },
     "wim_url": description_schema,
     "config": {
index 4280a46..6e36b55 100644 (file)
@@ -53,7 +53,7 @@ def wim(identifier=0):
     return {'name': 'wim%d' % identifier,
             'uuid': uuid('wim%d' % identifier),
             'wim_url': 'localhost',
-            'type': 'tapi'}
+            'type': 'ietfl2vpn'}
 
 
 def tenant(identifier=0):
index 28ad87c..aea4749 100755 (executable)
@@ -24,18 +24,18 @@ cp RO/deb_dist/python3-osm-ro_*.deb deb_dist/
 make -C RO-client clean package
 cp RO-client/deb_dist/python3-osm-roclient_*.deb deb_dist/
 
-# VIM plugings:  vmware openstack AWS fos azure Opennebula 
+# VIM plugings:  vmware, openstack, AWS, fos, azure, Opennebula, 
 for vim_plugin in RO-VIM-*
 do
     make -C $vim_plugin clean package
     cp ${vim_plugin}/deb_dist/python3-osm-rovim*.deb deb_dist/
 done
 
-# SDN plugins
-
-# SDN plugins: Dynpack Tapi Onosof Floodlightof
+# SDN plugins: DynPac, Ietfl2vpn, Onosof Floodlightof
 for sdn_plugin in RO-SDN-*
 do
+    [[ "$sdn_plugin" == RO-SDN-tapi ]] && continue  # tapi folder appears at Jenkins due to container reuse
+    [[ "$sdn_plugin" == RO-SDN-arista ]] && continue  # arista folder appears at Jenkins due to container reuse
     make -C $sdn_plugin clean package
     cp ${sdn_plugin}/deb_dist/python3-osm-rosdn*.deb deb_dist/
 done