Skip to content
Snippets Groups Projects
Commit 0291c04a authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Hackfest:

- added MockOSM package
parent 45f01d3e
No related branches found
No related tags found
2 merge requests!54Release 2.0.0,!11Merge Hackfest material into Develop
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from .WimconnectorIETFL2VPN import WimconnectorIETFL2VPN
LOGGER = logging.getLogger(__name__)
class MockOSM:
def __init__(self, url, mapping, username, password):
wim = {'wim_url': url}
wim_account = {'user': username, 'password': password}
config = {'mapping_not_needed': False, 'service_endpoint_mapping': mapping}
self.wim = WimconnectorIETFL2VPN(wim, wim_account, config=config)
self.conn_info = {} # internal database emulating OSM storage provided to WIM Connectors
def create_connectivity_service(self, service_type, connection_points):
LOGGER.info('[create_connectivity_service] service_type={:s}'.format(str(service_type)))
LOGGER.info('[create_connectivity_service] connection_points={:s}'.format(str(connection_points)))
self.wim.check_credentials()
result = self.wim.create_connectivity_service(service_type, connection_points)
LOGGER.info('[create_connectivity_service] result={:s}'.format(str(result)))
service_uuid, conn_info = result
self.conn_info[service_uuid] = conn_info
return service_uuid
def get_connectivity_service_status(self, service_uuid):
LOGGER.info('[get_connectivity_service] service_uuid={:s}'.format(str(service_uuid)))
conn_info = self.conn_info.get(service_uuid)
if conn_info is None: raise Exception('ServiceId({:s}) not found'.format(str(service_uuid)))
LOGGER.info('[get_connectivity_service] conn_info={:s}'.format(str(conn_info)))
self.wim.check_credentials()
result = self.wim.get_connectivity_service_status(service_uuid, conn_info=conn_info)
LOGGER.info('[get_connectivity_service] result={:s}'.format(str(result)))
return result
def edit_connectivity_service(self, service_uuid, connection_points):
LOGGER.info('[edit_connectivity_service] service_uuid={:s}'.format(str(service_uuid)))
LOGGER.info('[edit_connectivity_service] connection_points={:s}'.format(str(connection_points)))
conn_info = self.conn_info.get(service_uuid)
if conn_info is None: raise Exception('ServiceId({:s}) not found'.format(str(service_uuid)))
LOGGER.info('[edit_connectivity_service] conn_info={:s}'.format(str(conn_info)))
self.wim.edit_connectivity_service(service_uuid, conn_info=conn_info, connection_points=connection_points)
def delete_connectivity_service(self, service_uuid):
LOGGER.info('[delete_connectivity_service] service_uuid={:s}'.format(str(service_uuid)))
conn_info = self.conn_info.get(service_uuid)
if conn_info is None: raise Exception('ServiceId({:s}) not found'.format(str(service_uuid)))
LOGGER.info('[delete_connectivity_service] conn_info={:s}'.format(str(conn_info)))
self.wim.check_credentials()
self.wim.delete_connectivity_service(service_uuid, conn_info=conn_info)
# -*- 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
import copy
#from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
from .sdnconn import SdnConnectorBase, SdnConnectorError
"""Check layer where we move it"""
class WimconnectorIETFL2VPN(SdnConnectorBase):
def __init__(self, wim, wim_account, config=None, logger=None):
"""IETF L2VPN 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("ro.sdn.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("password") # replace "passwordd" -> "password"
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)
self.logger.warning('response.status_code={:s}'.format(str(response.status_code)))
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.
"""
SETTINGS = { # min_endpoints, max_endpoints, vpn_service_type
'ELINE': (2, 2, 'vpws'), # Virtual Private Wire Service
'ELAN' : (2, None, 'vpls'), # Virtual Private LAN Service
}
settings = SETTINGS.get(service_type)
if settings is None: raise NotImplementedError('Unsupported service_type({:s})'.format(str(service_type)))
min_endpoints, max_endpoints, vpn_service_type = settings
if max_endpoints is not None and len(connection_points) > max_endpoints:
msg = "Connections between more than {:d} endpoints are not supported for service_type {:s}"
raise SdnConnectorError(msg.format(max_endpoints, service_type))
if min_endpoints is not None and len(connection_points) < min_endpoints:
msg = "Connections must be of at least {:d} endpoints for service_type {:s}"
raise SdnConnectorError(msg.format(min_endpoints, service_type))
"""First step, create the vpn service"""
uuid_l2vpn = str(uuid.uuid4())
vpn_service = {}
vpn_service["vpn-id"] = uuid_l2vpn
vpn_service["vpn-svc-type"] = vpn_service_type
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,
)
self.logger.info('connection_points = {:s}'.format(str(connection_points)))
# Check if protected paths are requested
extended_connection_points = []
for connection_point in connection_points:
extended_connection_points.append(connection_point)
connection_point_wan_info = self.search_mapp(connection_point)
service_mapping_info = connection_point_wan_info.get('service_mapping_info', {})
redundant_service_endpoint_ids = service_mapping_info.get('redundant')
if redundant_service_endpoint_ids is None: continue
if len(redundant_service_endpoint_ids) == 0: continue
for redundant_service_endpoint_id in redundant_service_endpoint_ids:
redundant_connection_point = copy.deepcopy(connection_point)
redundant_connection_point['service_endpoint_id'] = redundant_service_endpoint_id
extended_connection_points.append(redundant_connection_point)
self.logger.info('extended_connection_points = {:s}'.format(str(extended_connection_points)))
"""Second step, create the connections and vpn attachments"""
for connection_point in extended_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"]
access_priority = connection_point_wan_info["service_mapping_info"].get("priority")
if access_priority is not None:
availability = {}
availability["access-priority"] = access_priority
availability["single-active"] = [None]
site_network_access["availability"] = availability
constraint = {}
constraint['constraint-type'] = 'end-to-end-diverse'
constraint['target'] = {'all-other-accesses': [None]}
access_diversity = {}
access_diversity['constraints'] = {'constraint': []}
access_diversity['constraints']['constraint'].append(constraint)
site_network_access["access-diversity"] = access_diversity
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 not 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
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)
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import cmd, logging
from .MockOSM import MockOSM
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
WIM_URL = 'http://10.0.2.10:80'
WIM_USERNAME = 'admin'
WIM_PASSWORD = 'admin'
# Ref: https://osm.etsi.org/wikipub/index.php/WIM
def create_port_mapping(device_id, port_id, service_endpoint_id, site_id):
bearer_ref = '{:s}:{:s}'.format(device_id, port_id)
return {
'device-id' : device_id, # pop_switch_dpid
'service_endpoint_id' : service_endpoint_id, # wan_service_endpoint_id
'service_mapping_info': { # wan_service_mapping_info, other extra info
'bearer': {'bearer-reference': bearer_ref},
'site-id': site_id,
},
}
WIM_PORT_MAPPING = [
create_port_mapping('R1', '1/2', 'ep-R1-1/2', '1'),
create_port_mapping('R1', '1/3', 'ep-R1-1/3', '1'),
create_port_mapping('R2', '1/2', 'ep-R2-1/2', '2'),
create_port_mapping('R2', '1/3', 'ep-R2-1/3', '2'),
create_port_mapping('R3', '1/2', 'ep-R3-1/2', '3'),
create_port_mapping('R3', '1/3', 'ep-R3-1/3', '3'),
create_port_mapping('R4', '1/2', 'ep-R4-1/2', '4'),
create_port_mapping('R4', '1/3', 'ep-R4-1/3', '4'),
]
SERVICE_TYPE = 'ELINE'
SERVICE_CONNECTION_POINTS = [
{'service_endpoint_id': 'ep-R1-1/2',
'service_endpoint_encapsulation_type': 'dot1q',
'service_endpoint_encapsulation_info': {'vlan': 1234}},
{'service_endpoint_id': 'ep-R4-1/3',
'service_endpoint_encapsulation_type': 'dot1q',
'service_endpoint_encapsulation_info': {'vlan': 1234}},
]
class MockOSMShell(cmd.Cmd):
intro = 'Welcome to the MockOSM shell.\nType help or ? to list commands.\n'
prompt = '(mock-osm) '
file = None
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.mock_osm = MockOSM(WIM_URL, WIM_PORT_MAPPING, WIM_USERNAME, WIM_PASSWORD)
# ----- basic turtle commands -----
def do_create(self, arg):
'Create an ELINE (L2) service'
service_uuid = self.mock_osm.create_connectivity_service(
SERVICE_TYPE, SERVICE_CONNECTION_POINTS)
print('Service {:s} created'.format(service_uuid))
def do_status(self, arg):
'Retrieve status of services'
service_uuids = list(self.mock_osm.conn_info.keys())
for service_uuid in service_uuids:
status = self.mock_osm.get_connectivity_service_status(service_uuid)
print('Status of Service {:s} is {:s}'.format(service_uuid, str(status)))
def do_delete(self, arg):
'Delete all services'
service_uuids = list(self.mock_osm.conn_info.keys())
for service_uuid in service_uuids:
self.mock_osm.delete_connectivity_service(service_uuid)
print('Service {:s} deleted'.format(service_uuid))
def do_exit(self, arg):
'Exit MockOSM'
print('Bye!')
return True
if __name__ == '__main__':
MockOSMShell().cmdloop()
MockOSM is based on source code taken from:
https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-plugin/osm_ro_plugin/sdnconn.py
https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn/wimconn_ietfl2vpn.py
############
# Mock OSM
############
$ cd ~/tfs-ctrl/hackfest/
$ python -m mock_osm
Welcome to the MockOSM shell.
Type help or ? to list commands.
(mock-osm) create
Service b8c99e2c-39d8-424d-9833-554634269555 created
# Service should be created in TFS. Check "Slice" and "Service" pages on WebUI.
# Check configuration rules in "Devices"
(mock-osm) status
response.status_code=200
Status of Service b8c99e2c-39d8-424d-9833-554634269555 is {'sdn_status': 'ACTIVE'}
(mock-osm) delete
Service b8c99e2c-39d8-424d-9833-554634269555 deleted
# Service should be removed from TFS. Check "Slice" and "Service" pages on WebUI.
# Check configuration rules in "Devices"
(mock-osm) exit
Bye!
# -*- coding: utf-8 -*-
##
# Copyright 2018 University of Bristol - High Performance Networks Research
# Group
# All Rights Reserved.
#
# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# For those usages not covered by the Apache License, Version 2.0 please
# contact with: <highperformance-networks@bristol.ac.uk>
#
# Neither the name of the University of Bristol nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# This work has been performed in the context of DCMS UK 5G Testbeds
# & Trials Programme and in the framework of the Metro-Haul project -
# funded by the European Commission under Grant number 761727 through the
# Horizon 2020 and 5G-PPP programmes.
##
"""The SDN connector is responsible for establishing both wide area network connectivity (WIM)
and intranet SDN connectivity.
It receives information from ports to be connected .
"""
import logging
from http import HTTPStatus
class SdnConnectorError(Exception):
"""Base Exception for all connector related errors
provide the parameter 'http_code' (int) with the error code:
Bad_Request = 400
Unauthorized = 401 (e.g. credentials are not valid)
Not_Found = 404 (e.g. try to edit or delete a non existing connectivity service)
Forbidden = 403
Method_Not_Allowed = 405
Not_Acceptable = 406
Request_Timeout = 408 (e.g timeout reaching server, or cannot reach the server)
Conflict = 409
Service_Unavailable = 503
Internal_Server_Error = 500
"""
def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value):
Exception.__init__(self, message)
self.http_code = http_code
class SdnConnectorBase(object):
"""Abstract base class for all the SDN connectors
Arguments:
wim (dict): WIM record, as stored in the database
wim_account (dict): WIM account record, as stored in the database
config
The arguments of the constructor are converted to object attributes.
An extra property, ``service_endpoint_mapping`` is created from ``config``.
"""
def __init__(self, wim, wim_account, config=None, logger=None):
"""
:param wim: (dict). Contains among others 'wim_url'
:param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
:param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
KEY meaning for WIM meaning for SDN assist
-------- -------- --------
device_id pop_switch_dpid compute_id
device_interface_id pop_switch_port compute_pci_address
service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id
service_mapping_info wan_service_mapping_info SDN_service_mapping_info
contains extra information if needed. Text in Yaml format
switch_dpid wan_switch_dpid SDN_switch_dpid
switch_port wan_switch_port SDN_switch_port
datacenter_id vim_account vim_account
id: (internal, do not use)
wim_id: (internal, do not use)
:param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
"""
self.logger = logger or logging.getLogger("ro.sdn")
self.wim = wim
self.wim_account = wim_account
self.config = config or {}
self.service_endpoint_mapping = self.config.get("service_endpoint_mapping", [])
def check_credentials(self):
"""Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
user (wim_account.user), and password (wim_account.password)
Raises:
SdnConnectorError: Issues regarding authorization, access to
external URLs, etc are detected.
"""
raise NotImplementedError
def get_connectivity_service_status(self, service_uuid, conn_info=None):
"""Monitor the status of the connectivity service established
Arguments:
service_uuid (str): UUID of the connectivity service
conn_info (dict or None): Information returned by the connector
during the service creation/edition and subsequently stored in
the database.
Returns:
dict: JSON/YAML-serializable dict that contains a mandatory key
``sdn_status`` associated with one of the following values::
{'sdn_status': 'ACTIVE'}
# The service is up and running.
{'sdn_status': 'INACTIVE'}
# The service was created, but the connector
# cannot determine yet if connectivity exists
# (ideally, the caller needs to wait and check again).
{'sdn_status': 'DOWN'}
# Connection was previously established,
# but an error/failure was detected.
{'sdn_status': 'ERROR'}
# An error occurred when trying to create the service/
# establish the connectivity.
{'sdn_status': 'BUILD'}
# Still trying to create the service, the caller
# needs to wait and check again.
Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
keys can be used to provide additional status explanation or
new information available for the connectivity service.
"""
raise NotImplementedError
def create_connectivity_service(self, service_type, connection_points, **kwargs):
"""
Establish SDN/WAN connectivity between the endpoints
:param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
:param connection_points: (list): each point corresponds to
an entry point to be connected. For WIM: from the DC to the transport network.
For SDN: Compute/PCI to the transport network. One
connection point serves to identify the specific access and
some other service parameters, such as encapsulation type.
Each item of the list is a dict with:
"service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__)
In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
it will contain the string "device_id:device_interface_id"
"service_endpoint_encapsulation_type": None, "dot1q", ...
"service_endpoint_encapsulation_info": (dict) with:
"vlan": ..., (int, present if encapsulation is dot1q)
"vni": ... (int, present if encapsulation is vxlan),
"peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
"mac": ...
"device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
"device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
"switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
"swith_port": ... present if mapping has been found for this device_id,device_interface_id
"service_mapping_info": present if mapping has been found for this device_id,device_interface_id
:param kwargs: For future versions:
bandwidth (int): value in kilobytes
latency (int): value in milliseconds
Other QoS might be passed as keyword arguments.
:return: tuple: ``(service_id, conn_info)`` containing:
- *service_uuid* (str): UUID of the established connectivity service
- *conn_info* (dict or None): Information to be stored at the database (or ``None``).
This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
**MUST** be JSON/YAML-serializable (plain data structures).
:raises: SdnConnectorException: In case of error. Nothing should be created in this case.
Provide the parameter http_code
"""
raise NotImplementedError
def delete_connectivity_service(self, service_uuid, conn_info=None):
"""
Disconnect multi-site endpoints previously connected
:param service_uuid: The one returned by create_connectivity_service
:param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
if they do not return None
:return: None
:raises: SdnConnectorException: In case of error. The parameter http_code must be filled
"""
raise NotImplementedError
def edit_connectivity_service(
self, service_uuid, conn_info=None, connection_points=None, **kwargs
):
"""Change an existing connectivity service.
This method's arguments and return value follow the same convention as
:meth:`~.create_connectivity_service`.
:param service_uuid: UUID of the connectivity service.
:param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
or edit_connectivity_service
:param connection_points: (list): If provided, the old list of connection points will be replaced.
:param kwargs: Same meaning that create_connectivity_service
:return: dict or None: Information to be updated and stored at the database.
When ``None`` is returned, no information should be changed.
When an empty dict is returned, the database record will be deleted.
**MUST** be JSON/YAML-serializable (plain data structures).
Raises:
SdnConnectorException: In case of error.
"""
def clear_all_connectivity_services(self):
"""Delete all WAN Links in a WIM.
This method is intended for debugging only, and should delete all the
connections controlled by the WIM/SDN, not only the connections that
a specific RO is aware of.
Raises:
SdnConnectorException: In case of error.
"""
raise NotImplementedError
def get_all_active_connectivity_services(self):
"""Provide information about all active connections provisioned by a
WIM.
Raises:
SdnConnectorException: In case of error.
"""
raise NotImplementedError
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment