feature8030 move WIM connector to plugins
Change-Id: I2e08ce7aa04f3e61adcf866925bf235b53e15baf
Signed-off-by: tierno <alfonso.tiernosepulveda@telefonica.com>
diff --git a/RO-SDN-tapi/Makefile b/RO-SDN-tapi/Makefile
new file mode 100644
index 0000000..2e05280
--- /dev/null
+++ b/RO-SDN-tapi/Makefile
@@ -0,0 +1,24 @@
+##
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+all: clean package
+
+clean:
+ rm -rf dist deb_dist osm_rosdn_tapi-*.tar.gz osm_rosdn_tapi.egg-info .eggs
+
+package:
+ python3 setup.py --command-packages=stdeb.command sdist_dsc
+ cd deb_dist/osm-rosdn-tapi*/ && dpkg-buildpackage -rfakeroot -uc -us
+
diff --git a/RO-SDN-tapi/osm_rosdn_tapi/wimconn_ietfl2vpn.py b/RO-SDN-tapi/osm_rosdn_tapi/wimconn_ietfl2vpn.py
new file mode 100644
index 0000000..26680b5
--- /dev/null
+++ b/RO-SDN-tapi/osm_rosdn_tapi/wimconn_ietfl2vpn.py
@@ -0,0 +1,362 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 Telefonica
+# All Rights Reserved.
+#
+# Contributors: Oscar Gonzalez de Dios, Manuel Lopez Bravo, Guillermo Pajares Martin
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# This work has been performed in the context of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 program.
+##
+"""The SDN/WIM connector is responsible for establishing wide area network
+connectivity.
+
+This SDN/WIM connector implements the standard IETF RFC 8466 "A YANG Data
+ Model for Layer 2 Virtual Private Network (L2VPN) Service Delivery"
+
+It receives the endpoints and the necessary details to request
+the Layer 2 service.
+"""
+import requests
+import uuid
+import logging
+from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
+"""CHeck layer where we move it"""
+
+
+class WimconnectorIETFL2VPN(SdnConnectorBase):
+
+ def __init__(self, wim, wim_account, config=None, logger=None):
+ """IETF L2VPM WIM connector
+
+ Arguments: (To be completed)
+ wim (dict): WIM record, as stored in the database
+ wim_account (dict): WIM account record, as stored in the database
+ """
+ self.logger = logging.getLogger('openmano.sdnconn.ietfl2vpn')
+ super().__init__(wim, wim_account, config, logger)
+ self.headers = {'Content-Type': 'application/json'}
+ self.mappings = {m['service_endpoint_id']: m
+ for m in self.service_endpoint_mapping}
+ self.user = wim_account.get("user")
+ self.passwd = wim_account.get("passwordd")
+ if self.user and self.passwd is not None:
+ self.auth = (self.user, self.passwd)
+ else:
+ self.auth = None
+ self.logger.info("IETFL2VPN Connector Initialized.")
+
+ def check_credentials(self):
+ endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
+ try:
+ response = requests.get(endpoint, auth=self.auth)
+ http_code = response.status_code
+ except requests.exceptions.RequestException as e:
+ raise SdnConnectorError(e.message, http_code=503)
+
+ if http_code != 200:
+ raise SdnConnectorError("Failed while authenticating", http_code=http_code)
+ self.logger.info("Credentials checked")
+
+ def get_connectivity_service_status(self, service_uuid, conn_info=None):
+ """Monitor the status of the connectivity service stablished
+
+ Arguments:
+ service_uuid: Connectivity service unique identifier
+
+ Returns:
+ Examples::
+ {'sdn_status': 'ACTIVE'}
+ {'sdn_status': 'INACTIVE'}
+ {'sdn_status': 'DOWN'}
+ {'sdn_status': 'ERROR'}
+ """
+ try:
+ self.logger.info("Sending get connectivity service stuatus")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid)
+ response = requests.get(servicepoint, auth=self.auth)
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError("Unable to obtain connectivity servcice status", http_code=response.status_code)
+ service_status = {'sdn_status': 'ACTIVE'}
+ return service_status
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def search_mapp(self, connection_point):
+ id = connection_point['service_endpoint_id']
+ if id not in self.mappings:
+ raise SdnConnectorError("Endpoint {} not located".format(str(id)))
+ else:
+ return self.mappings[id]
+
+ def create_connectivity_service(self, service_type, connection_points, **kwargs):
+ """Stablish WAN connectivity between the endpoints
+
+ Arguments:
+ service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
+ ``L3``.
+ connection_points (list): each point corresponds to
+ an entry point from the DC to the transport network. One
+ connection point serves to identify the specific access and
+ some other service parameters, such as encapsulation type.
+ Represented by a dict as follows::
+
+ {
+ "service_endpoint_id": ..., (str[uuid])
+ "service_endpoint_encapsulation_type": ...,
+ (enum: none, dot1q, ...)
+ "service_endpoint_encapsulation_info": {
+ ... (dict)
+ "vlan": ..., (int, present if encapsulation is dot1q)
+ "vni": ... (int, present if encapsulation is vxlan),
+ "peers": [(ipv4_1), (ipv4_2)]
+ (present if encapsulation is vxlan)
+ }
+ }
+
+ The service endpoint ID should be previously informed to the WIM
+ engine in the RO when the WIM port mapping is registered.
+
+ Keyword Arguments:
+ bandwidth (int): value in kilobytes
+ latency (int): value in milliseconds
+
+ Other QoS might be passed as keyword arguments.
+
+ Returns:
+ tuple: ``(service_id, conn_info)`` containing:
+ - *service_uuid* (str): UUID of the established connectivity
+ service
+ - *conn_info* (dict or None): Information to be stored at the
+ database (or ``None``). This information will be provided to
+ the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
+ **MUST** be JSON/YAML-serializable (plain data structures).
+
+ Raises:
+ SdnConnectorException: In case of error.
+ """
+ if service_type == "ELINE":
+ if len(connection_points) > 2:
+ raise SdnConnectorError('Connections between more than 2 endpoints are not supported')
+ if len(connection_points) < 2:
+ raise SdnConnectorError('Connections must be of at least 2 endpoints')
+ """ First step, create the vpn service """
+ uuid_l2vpn = str(uuid.uuid4())
+ vpn_service = {}
+ vpn_service["vpn-id"] = uuid_l2vpn
+ vpn_service["vpn-scv-type"] = "vpws"
+ vpn_service["svc-topo"] = "any-to-any"
+ vpn_service["customer-name"] = "osm"
+ vpn_service_list = []
+ vpn_service_list.append(vpn_service)
+ vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
+ response_service_creation = None
+ conn_info = []
+ self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
+ try:
+ endpoint_service_creation = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"])
+ response_service_creation = requests.post(endpoint_service_creation, headers=self.headers,
+ json=vpn_service_l, auth=self.auth)
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request to create service Timeout", http_code=408)
+ if response_service_creation.status_code == 409:
+ raise SdnConnectorError("Service already exists", http_code=response_service_creation.status_code)
+ elif response_service_creation.status_code != requests.codes.created:
+ raise SdnConnectorError("Request to create service not accepted",
+ http_code=response_service_creation.status_code)
+ """ Second step, create the connections and vpn attachments """
+ for connection_point in connection_points:
+ connection_point_wan_info = self.search_mapp(connection_point)
+ site_network_access = {}
+ connection = {}
+ if connection_point["service_endpoint_encapsulation_type"] != "none":
+ if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
+ """ The connection is a VLAN """
+ connection["encapsulation-type"] = "dot1q-vlan-tagged"
+ tagged = {}
+ tagged_interf = {}
+ service_endpoint_encapsulation_info = connection_point["service_endpoint_encapsulation_info"]
+ if service_endpoint_encapsulation_info["vlan"] is None:
+ raise SdnConnectorError("VLAN must be provided")
+ tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info["vlan"]
+ tagged["dot1q-vlan-tagged"] = tagged_interf
+ connection["tagged-interface"] = tagged
+ else:
+ raise NotImplementedError("Encapsulation type not implemented")
+ site_network_access["connection"] = connection
+ self.logger.info("Sending connection:{}".format(connection))
+ vpn_attach = {}
+ vpn_attach["vpn-id"] = uuid_l2vpn
+ vpn_attach["site-role"] = vpn_service["svc-topo"]+"-role"
+ site_network_access["vpn-attachment"] = vpn_attach
+ self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
+ uuid_sna = str(uuid.uuid4())
+ site_network_access["network-access-id"] = uuid_sna
+ site_network_access["bearer"] = connection_point_wan_info["service_mapping_info"]["bearer"]
+ site_network_accesses = {}
+ site_network_access_list = []
+ site_network_access_list.append(site_network_access)
+ site_network_accesses["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
+ conn_info_d = {}
+ conn_info_d["site"] = connection_point_wan_info["service_mapping_info"]["site-id"]
+ conn_info_d["site-network-access-id"] = site_network_access["network-access-id"]
+ conn_info_d["mapping"] = None
+ conn_info.append(conn_info_d)
+ try:
+ endpoint_site_network_access_creation = \
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
+ self.wim["wim_url"], connection_point_wan_info["service_mapping_info"]["site-id"])
+ response_endpoint_site_network_access_creation = requests.post(
+ endpoint_site_network_access_creation,
+ headers=self.headers,
+ json=site_network_accesses,
+ auth=self.auth)
+
+ if response_endpoint_site_network_access_creation.status_code == 409:
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+ raise SdnConnectorError("Site_Network_Access with ID '{}' already exists".format(
+ site_network_access["network-access-id"]),
+ http_code=response_endpoint_site_network_access_creation.status_code)
+
+ elif response_endpoint_site_network_access_creation.status_code == 400:
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+ raise SdnConnectorError("Site {} does not exist".format(
+ connection_point_wan_info["service_mapping_info"]["site-id"]),
+ http_code=response_endpoint_site_network_access_creation.status_code)
+
+ elif response_endpoint_site_network_access_creation.status_code != requests.codes.created and \
+ response_endpoint_site_network_access_creation.status_code != requests.codes.no_content:
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+ raise SdnConnectorError("Request no accepted",
+ http_code=response_endpoint_site_network_access_creation.status_code)
+
+ except requests.exceptions.ConnectionError:
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+ raise SdnConnectorError("Request Timeout", http_code=408)
+ return uuid_l2vpn, conn_info
+
+ else:
+ raise NotImplementedError
+
+ def delete_connectivity_service(self, service_uuid, conn_info=None):
+ """Disconnect multi-site endpoints previously connected
+
+ This method should receive as the first argument the UUID generated by
+ the ``create_connectivity_service``
+ """
+ try:
+ self.logger.info("Sending delete")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid)
+ response = requests.delete(servicepoint, auth=self.auth)
+ if response.status_code != requests.codes.no_content:
+ raise SdnConnectorError("Error in the request", http_code=response.status_code)
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def edit_connectivity_service(self, service_uuid, conn_info=None,
+ connection_points=None, **kwargs):
+ """Change an existing connectivity service, see
+ ``create_connectivity_service``"""
+
+ # sites = {"sites": {}}
+ # site_list = []
+ vpn_service = {}
+ vpn_service["svc-topo"] = "any-to-any"
+ counter = 0
+ for connection_point in connection_points:
+ site_network_access = {}
+ connection_point_wan_info = self.search_mapp(connection_point)
+ params_site = {}
+ params_site["site-id"] = connection_point_wan_info["service_mapping_info"]["site-id"]
+ params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
+ device_site = {}
+ device_site["device-id"] = connection_point_wan_info["device-id"]
+ params_site["devices"] = device_site
+ # network_access = {}
+ connection = {}
+ if connection_point["service_endpoint_encapsulation_type"] != "none":
+ if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
+ """ The connection is a VLAN """
+ connection["encapsulation-type"] = "dot1q-vlan-tagged"
+ tagged = {}
+ tagged_interf = {}
+ service_endpoint_encapsulation_info = connection_point["service_endpoint_encapsulation_info"]
+ if service_endpoint_encapsulation_info["vlan"] is None:
+ raise SdnConnectorError("VLAN must be provided")
+ tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info["vlan"]
+ tagged["dot1q-vlan-tagged"] = tagged_interf
+ connection["tagged-interface"] = tagged
+ else:
+ raise NotImplementedError("Encapsulation type not implemented")
+ site_network_access["connection"] = connection
+ vpn_attach = {}
+ vpn_attach["vpn-id"] = service_uuid
+ vpn_attach["site-role"] = vpn_service["svc-topo"]+"-role"
+ site_network_access["vpn-attachment"] = vpn_attach
+ uuid_sna = conn_info[counter]["site-network-access-id"]
+ site_network_access["network-access-id"] = uuid_sna
+ site_network_access["bearer"] = connection_point_wan_info["service_mapping_info"]["bearer"]
+ site_network_accesses = {}
+ site_network_access_list = []
+ site_network_access_list.append(site_network_access)
+ site_network_accesses["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
+ try:
+ endpoint_site_network_access_edit = \
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
+ self.wim["wim_url"], connection_point_wan_info["service_mapping_info"]["site-id"])
+ response_endpoint_site_network_access_creation = requests.put(endpoint_site_network_access_edit,
+ headers=self.headers,
+ json=site_network_accesses,
+ auth=self.auth)
+ if response_endpoint_site_network_access_creation.status_code == 400:
+ raise SdnConnectorError("Service does not exist",
+ http_code=response_endpoint_site_network_access_creation.status_code)
+ elif response_endpoint_site_network_access_creation.status_code != 201 and \
+ response_endpoint_site_network_access_creation.status_code != 204:
+ raise SdnConnectorError("Request no accepted",
+ http_code=response_endpoint_site_network_access_creation.status_code)
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+ counter += 1
+ return None
+
+ def clear_all_connectivity_services(self):
+ """Delete all WAN Links corresponding to a WIM"""
+ try:
+ self.logger.info("Sending clear all connectivity services")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
+ response = requests.delete(servicepoint, auth=self.auth)
+ if response.status_code != requests.codes.no_content:
+ raise SdnConnectorError("Unable to clear all connectivity services", http_code=response.status_code)
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def get_all_active_connectivity_services(self):
+ """Provide information about all active connections provisioned by a
+ WIM
+ """
+ try:
+ self.logger.info("Sending get all connectivity services")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
+ response = requests.get(servicepoint, auth=self.auth)
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError("Unable to get all connectivity services", http_code=response.status_code)
+ return response
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
diff --git a/RO-SDN-tapi/requirements.txt b/RO-SDN-tapi/requirements.txt
new file mode 100644
index 0000000..44c797f
--- /dev/null
+++ b/RO-SDN-tapi/requirements.txt
@@ -0,0 +1,18 @@
+##
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+requests
+git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro
+
diff --git a/RO-SDN-tapi/setup.py b/RO-SDN-tapi/setup.py
new file mode 100644
index 0000000..931dd66
--- /dev/null
+++ b/RO-SDN-tapi/setup.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+##
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from setuptools import setup
+
+_name = "osm_rosdn_tapi"
+
+README = """
+===========
+osm-rosdn_tapi
+===========
+
+osm-ro pluging for tapi (ietfl2vpn) SDN
+"""
+
+setup(
+ name=_name,
+ description='OSM ro sdn plugin for tapi (ietfl2vpn)',
+ long_description=README,
+ version_command=('git describe --match v* --tags --long --dirty', 'pep440-git-full'),
+ # version=VERSION,
+ # python_requires='>3.5.0',
+ author='ETSI OSM',
+ # TODO py3 author_email='',
+ maintainer='OSM_TECH@LIST.ETSI.ORG', # TODO py3
+ # TODO py3 maintainer_email='',
+ url='https://osm.etsi.org/gitweb/?p=osm/RO.git;a=summary',
+ license='Apache 2.0',
+
+ packages=[_name],
+ include_package_data=True,
+ dependency_links=["git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro"],
+ install_requires=["requests", "osm-ro"],
+ setup_requires=['setuptools-version-command'],
+ entry_points={
+ 'osm_rosdn.plugins': ['rosdn_tapi = osm_rosdn_tapi.wimconn_ietfl2vpn:WimconnectorIETFL2VPN'],
+ },
+)
diff --git a/RO-SDN-tapi/stdeb.cfg b/RO-SDN-tapi/stdeb.cfg
new file mode 100644
index 0000000..0c718e4
--- /dev/null
+++ b/RO-SDN-tapi/stdeb.cfg
@@ -0,0 +1,19 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[DEFAULT]
+X-Python3-Version : >= 3.5
+Depends3: python3-requests, python3-osm-ro
+
diff --git a/RO-SDN-tapi/tox.ini b/RO-SDN-tapi/tox.ini
new file mode 100644
index 0000000..7d643cd
--- /dev/null
+++ b/RO-SDN-tapi/tox.ini
@@ -0,0 +1,41 @@
+##
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+[tox]
+envlist = py3
+toxworkdir={homedir}/.tox
+
+[testenv]
+basepython = python3
+install_command = python3 -m pip install -r requirements.txt -U {opts} {packages}
+# deps = -r{toxinidir}/test-requirements.txt
+commands=python3 -m unittest discover -v
+
+[testenv:flake8]
+basepython = python3
+deps = flake8
+commands = flake8 osm_rosdn_tapi --max-line-length 120 \
+ --exclude .svn,CVS,.gz,.git,__pycache__,.tox,local,temp --ignore W291,W293,E226,W504
+
+[testenv:unittest]
+basepython = python3
+commands = python3 -m unittest osm_rosdn_tapi.tests
+
+[testenv:build]
+basepython = python3
+deps = stdeb
+ setuptools-version-command
+commands = python3 setup.py --command-packages=stdeb.command bdist_deb
+