1 # -*- coding: utf-8 -*-
3 #######################################################################################
4 # This file is part of OSM RO module
6 # Copyright ETSI Contributors and Others.
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
19 #######################################################################################
20 # This work has been performed in the context of the TeraFlow Project -
21 # funded by the European Commission under Grant number 101015857 through the
22 # Horizon 2020 program.
24 # - Lluis Gifre <lluis.gifre@cttc.es>
25 # - Ricard Vilalta <ricard.vilalta@cttc.es>
26 #######################################################################################
28 """The SDN/WIM connector is responsible for establishing wide area network
31 This SDN/WIM connector implements the standard ONF Transport API (TAPI).
33 It receives the endpoints and the necessary details to request the Layer 2
34 service through the use of the ONF Transport API.
40 from osm_ro_plugin
.sdnconn
import SdnConnectorBase
42 from .conn_info
import (
43 conn_info_compose_bidirectional
,
44 conn_info_compose_unidirectional
,
46 from .exceptions
import (
47 WimTapiConnectionPointsBadFormat
,
48 WimTapiMissingConnPointField
,
49 WimTapiUnsupportedServiceType
,
51 from .services_composer
import ServicesComposer
52 from .tapi_client
import TransportApiClient
55 class WimconnectorTAPI(SdnConnectorBase
):
56 """ONF TAPI WIM connector"""
58 def __init__(self
, wim
, wim_account
, config
=None, logger
=None):
59 """ONF TAPI WIM connector
62 wim (dict): WIM record, as stored in the database
63 wim_account (dict): WIM account record, as stored in the database
64 config (optional dict): optional configuration from the configuration database
65 logger (optional Logger): logger to use with this WIM connector
66 The arguments of the constructor are converted to object attributes.
67 An extra property, ``service_endpoint_mapping`` is created from ``config``.
69 logger
= logger
or logging
.getLogger("ro.sdn.tapi")
71 super().__init
__(wim
, wim_account
, config
, logger
)
73 self
.logger
.debug("self.config={:s}".format(str(self
.config
)))
75 if len(self
.service_endpoint_mapping
) == 0 and self
.config
.get(
78 self
.service_endpoint_mapping
= self
.config
.get("wim_port_mapping", [])
81 m
["service_endpoint_id"]: m
for m
in self
.service_endpoint_mapping
84 self
.logger
.debug("self.mappings={:s}".format(str(self
.mappings
)))
86 self
.tapi_client
= TransportApiClient(self
.logger
, wim
, wim_account
, config
)
88 self
.logger
.info("TAPI WIM Connector Initialized.")
90 def check_credentials(self
):
91 """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
92 user (wim_account.user), and password (wim_account.password)
95 SdnConnectorError: Issues regarding authorization, access to
96 external URLs, etc are detected.
98 _
= self
.tapi_client
.get_root_context()
99 self
.logger
.info("Credentials checked")
101 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
102 """Monitor the status of the connectivity service established
105 service_uuid (str): UUID of the connectivity service
106 conn_info (dict or None): Information returned by the connector
107 during the service creation/edition and subsequently stored in
111 dict: JSON/YAML-serializable dict that contains a mandatory key
112 ``sdn_status`` associated with one of the following values::
114 {'sdn_status': 'ACTIVE'}
115 # The service is up and running.
117 {'sdn_status': 'INACTIVE'}
118 # The service was created, but the connector
119 # cannot determine yet if connectivity exists
120 # (ideally, the caller needs to wait and check again).
122 {'sdn_status': 'DOWN'}
123 # Connection was previously established,
124 # but an error/failure was detected.
126 {'sdn_status': 'ERROR'}
127 # An error occurred when trying to create the service/
128 # establish the connectivity.
130 {'sdn_status': 'BUILD'}
131 # Still trying to create the service, the caller
132 # needs to wait and check again.
134 Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
135 keys can be used to provide additional status explanation or
136 new information available for the connectivity service.
139 bidirectional
= conn_info
["bidirectional"]
141 tapi_client
= self
.tapi_client
143 service_uuid
= conn_info
["uuid"]
144 service_status
= tapi_client
.get_service_status("<>", service_uuid
)
145 sdn_status
.add(service_status
["sdn_status"])
147 service_az_uuid
= conn_info
["az"]["uuid"]
148 service_za_uuid
= conn_info
["za"]["uuid"]
149 service_az_status
= tapi_client
.get_service_status(">>", service_az_uuid
)
150 service_za_status
= tapi_client
.get_service_status("<<", service_za_uuid
)
151 sdn_status
.add(service_az_status
["sdn_status"])
152 sdn_status
.add(service_za_status
["sdn_status"])
154 if len(sdn_status
) == 1 and "ACTIVE" in sdn_status
:
155 service_status
= {"sdn_status": "ACTIVE"}
157 service_status
= {"sdn_status": "ERROR"}
159 return service_status
161 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
163 Establish SDN/WAN connectivity between the endpoints
164 :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
165 :param connection_points: (list): each point corresponds to
166 an entry point to be connected. For WIM: from the DC to the transport network.
167 For SDN: Compute/PCI to the transport network. One
168 connection point serves to identify the specific access and
169 some other service parameters, such as encapsulation type.
170 Each item of the list is a dict with:
171 "service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__)
172 In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
173 it will contain the string "device_id:device_interface_id"
174 "service_endpoint_encapsulation_type": None, "dot1q", ...
175 "service_endpoint_encapsulation_info": (dict) with:
176 "vlan": ..., (int, present if encapsulation is dot1q)
177 "vni": ... (int, present if encapsulation is vxlan),
178 "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
180 "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
181 "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
182 "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
183 "swith_port": ... present if mapping has been found for this device_id,device_interface_id
184 "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
185 :param kwargs: For future versions:
186 bandwidth (int): value in kilobytes
187 latency (int): value in milliseconds
188 Other QoS might be passed as keyword arguments.
189 :return: tuple: ``(service_id, conn_info)`` containing:
190 - *service_uuid* (str): UUID of the established connectivity service
191 - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
192 This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
193 **MUST** be JSON/YAML-serializable (plain data structures).
194 :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
195 Provide the parameter http_code
197 supported_service_types
= {"ELINE"}
198 if service_type
not in supported_service_types
:
199 raise WimTapiUnsupportedServiceType(service_type
, supported_service_types
)
201 self
.logger
.debug("connection_points={:s}".format(str(connection_points
)))
203 if not isinstance(connection_points
, (list, tuple)):
204 raise WimTapiConnectionPointsBadFormat(connection_points
)
206 if len(connection_points
) != 2:
207 raise WimTapiConnectionPointsBadFormat(connection_points
)
209 sips
= self
.tapi_client
.get_service_interface_points()
210 services_composer
= ServicesComposer(sips
)
212 for connection_point
in connection_points
:
213 service_endpoint_id
= connection_point
.get("service_endpoint_id")
214 if service_endpoint_id
is None:
215 raise WimTapiMissingConnPointField(
216 connection_point
, "service_endpoint_id"
219 mapping
= self
.mappings
.get(service_endpoint_id
, {})
220 services_composer
.add_service_endpoint(service_endpoint_id
, mapping
)
222 services_composer
.dump(self
.logger
)
224 service_uuid
, conn_info
= self
._create
_services
_and
_conn
_info
(services_composer
)
225 return service_uuid
, conn_info
227 def _create_services_and_conn_info(self
, services_composer
: ServicesComposer
):
228 services
= services_composer
.services
229 requested_capacity
= services_composer
.requested_capacity
230 vlan_constraint
= services_composer
.vlan_constraint
232 service_uuid
= str(uuid
.uuid4())
234 if services_composer
.is_bidirectional():
235 service_endpoints
= services
[0]
236 self
.tapi_client
.create_service(
241 requested_capacity
=requested_capacity
,
242 vlan_constraint
=vlan_constraint
,
244 conn_info
= conn_info_compose_bidirectional(
247 requested_capacity
=requested_capacity
,
248 vlan_constraint
=vlan_constraint
,
252 service_uuid
= service_uuid
[0 : len(service_uuid
) - 4] + "00**"
253 service_az_uuid
= service_uuid
.replace("**", "af")
254 service_az_endpoints
= services
[0]
255 service_za_uuid
= service_uuid
.replace("**", "fa")
256 service_za_endpoints
= services
[1]
258 self
.tapi_client
.create_service(
261 service_az_endpoints
,
263 requested_capacity
=requested_capacity
,
264 vlan_constraint
=vlan_constraint
,
266 self
.tapi_client
.create_service(
269 service_za_endpoints
,
271 requested_capacity
=requested_capacity
,
272 vlan_constraint
=vlan_constraint
,
274 conn_info
= conn_info_compose_unidirectional(
276 service_az_endpoints
,
278 service_za_endpoints
,
279 requested_capacity
=requested_capacity
,
280 vlan_constraint
=vlan_constraint
,
283 return service_uuid
, conn_info
285 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
287 Disconnect multi-site endpoints previously connected
289 :param service_uuid: The one returned by create_connectivity_service
290 :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
291 if they do not return None
293 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
295 bidirectional
= conn_info
["bidirectional"]
297 service_uuid
= conn_info
["uuid"]
298 self
.tapi_client
.delete_service("<>", service_uuid
)
300 service_az_uuid
= conn_info
["az"]["uuid"]
301 service_za_uuid
= conn_info
["za"]["uuid"]
302 self
.tapi_client
.delete_service(">>", service_az_uuid
)
303 self
.tapi_client
.delete_service("<<", service_za_uuid
)
305 def edit_connectivity_service(
306 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
308 """Change an existing connectivity service.
310 This method's arguments and return value follow the same convention as
311 :meth:`~.create_connectivity_service`.
313 :param service_uuid: UUID of the connectivity service.
314 :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
315 or edit_connectivity_service
316 :param connection_points: (list): If provided, the old list of connection points will be replaced.
317 :param kwargs: Same meaning that create_connectivity_service
318 :return: dict or None: Information to be updated and stored at the database.
319 When ``None`` is returned, no information should be changed.
320 When an empty dict is returned, the database record will be deleted.
321 **MUST** be JSON/YAML-serializable (plain data structures).
323 SdnConnectorException: In case of error.
325 raise NotImplementedError
327 def clear_all_connectivity_services(self
):
328 """Delete all WAN Links in a WIM.
330 This method is intended for debugging only, and should delete all the
331 connections controlled by the WIM/SDN, not only the connections that
332 a specific RO is aware of.
335 SdnConnectorException: In case of error.
337 raise NotImplementedError
339 def get_all_active_connectivity_services(self
):
340 """Provide information about all active connections provisioned by a
344 SdnConnectorException: In case of error.
346 raise NotImplementedError