Feature 10937: Transport API (TAPI) WIM connector for RO
[osm/RO.git] / RO-SDN-tapi / osm_rosdn_tapi / wimconn_tapi.py
1 # -*- coding: utf-8 -*-
2
3 #######################################################################################
4 # This file is part of OSM RO module
5 #
6 # Copyright ETSI Contributors and Others.
7 #
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
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
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
18 # under the License.
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.
23 # Contributors:
24 # - Lluis Gifre <lluis.gifre@cttc.es>
25 # - Ricard Vilalta <ricard.vilalta@cttc.es>
26 #######################################################################################
27
28 """The SDN/WIM connector is responsible for establishing wide area network
29 connectivity.
30
31 This SDN/WIM connector implements the standard ONF Transport API (TAPI).
32
33 It receives the endpoints and the necessary details to request the Layer 2
34 service through the use of the ONF Transport API.
35 """
36
37 import logging
38 import uuid
39
40 from osm_ro_plugin.sdnconn import SdnConnectorBase
41
42 from .conn_info import (
43 conn_info_compose_bidirectional,
44 conn_info_compose_unidirectional,
45 )
46 from .exceptions import (
47 WimTapiConnectionPointsBadFormat,
48 WimTapiMissingConnPointField,
49 WimTapiUnsupportedServiceType,
50 )
51 from .services_composer import ServicesComposer
52 from .tapi_client import TransportApiClient
53
54
55 class WimconnectorTAPI(SdnConnectorBase):
56 """ONF TAPI WIM connector"""
57
58 def __init__(self, wim, wim_account, config=None, logger=None):
59 """ONF TAPI WIM connector
60
61 Arguments:
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``.
68 """
69 logger = logger or logging.getLogger("ro.sdn.tapi")
70
71 super().__init__(wim, wim_account, config, logger)
72
73 self.logger.debug("self.config={:s}".format(str(self.config)))
74
75 if len(self.service_endpoint_mapping) == 0 and self.config.get(
76 "wim_port_mapping"
77 ):
78 self.service_endpoint_mapping = self.config.get("wim_port_mapping", [])
79
80 self.mappings = {
81 m["service_endpoint_id"]: m for m in self.service_endpoint_mapping
82 }
83
84 self.logger.debug("self.mappings={:s}".format(str(self.mappings)))
85
86 self.tapi_client = TransportApiClient(self.logger, wim, wim_account, config)
87
88 self.logger.info("TAPI WIM Connector Initialized.")
89
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)
93
94 Raises:
95 SdnConnectorError: Issues regarding authorization, access to
96 external URLs, etc are detected.
97 """
98 _ = self.tapi_client.get_root_context()
99 self.logger.info("Credentials checked")
100
101 def get_connectivity_service_status(self, service_uuid, conn_info=None):
102 """Monitor the status of the connectivity service established
103
104 Arguments:
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
108 the database.
109
110 Returns:
111 dict: JSON/YAML-serializable dict that contains a mandatory key
112 ``sdn_status`` associated with one of the following values::
113
114 {'sdn_status': 'ACTIVE'}
115 # The service is up and running.
116
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).
121
122 {'sdn_status': 'DOWN'}
123 # Connection was previously established,
124 # but an error/failure was detected.
125
126 {'sdn_status': 'ERROR'}
127 # An error occurred when trying to create the service/
128 # establish the connectivity.
129
130 {'sdn_status': 'BUILD'}
131 # Still trying to create the service, the caller
132 # needs to wait and check again.
133
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.
137 """
138 sdn_status = set()
139 bidirectional = conn_info["bidirectional"]
140
141 tapi_client = self.tapi_client
142 if bidirectional:
143 service_uuid = conn_info["uuid"]
144 service_status = tapi_client.get_service_status("<>", service_uuid)
145 sdn_status.add(service_status["sdn_status"])
146 else:
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"])
153
154 if len(sdn_status) == 1 and "ACTIVE" in sdn_status:
155 service_status = {"sdn_status": "ACTIVE"}
156 else:
157 service_status = {"sdn_status": "ERROR"}
158
159 return service_status
160
161 def create_connectivity_service(self, service_type, connection_points, **kwargs):
162 """
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)
179 "mac": ...
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
196 """
197 supported_service_types = {"ELINE"}
198 if service_type not in supported_service_types:
199 raise WimTapiUnsupportedServiceType(service_type, supported_service_types)
200
201 self.logger.debug("connection_points={:s}".format(str(connection_points)))
202
203 if not isinstance(connection_points, (list, tuple)):
204 raise WimTapiConnectionPointsBadFormat(connection_points)
205
206 if len(connection_points) != 2:
207 raise WimTapiConnectionPointsBadFormat(connection_points)
208
209 sips = self.tapi_client.get_service_interface_points()
210 services_composer = ServicesComposer(sips)
211
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"
217 )
218
219 mapping = self.mappings.get(service_endpoint_id, {})
220 services_composer.add_service_endpoint(service_endpoint_id, mapping)
221
222 services_composer.dump(self.logger)
223
224 service_uuid, conn_info = self._create_services_and_conn_info(services_composer)
225 return service_uuid, conn_info
226
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
231
232 service_uuid = str(uuid.uuid4())
233
234 if services_composer.is_bidirectional():
235 service_endpoints = services[0]
236 self.tapi_client.create_service(
237 "<>",
238 service_uuid,
239 service_endpoints,
240 bidirectional=True,
241 requested_capacity=requested_capacity,
242 vlan_constraint=vlan_constraint,
243 )
244 conn_info = conn_info_compose_bidirectional(
245 service_uuid,
246 service_endpoints,
247 requested_capacity=requested_capacity,
248 vlan_constraint=vlan_constraint,
249 )
250
251 else:
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]
257
258 self.tapi_client.create_service(
259 ">>",
260 service_az_uuid,
261 service_az_endpoints,
262 bidirectional=False,
263 requested_capacity=requested_capacity,
264 vlan_constraint=vlan_constraint,
265 )
266 self.tapi_client.create_service(
267 "<<",
268 service_za_uuid,
269 service_za_endpoints,
270 bidirectional=False,
271 requested_capacity=requested_capacity,
272 vlan_constraint=vlan_constraint,
273 )
274 conn_info = conn_info_compose_unidirectional(
275 service_az_uuid,
276 service_az_endpoints,
277 service_za_uuid,
278 service_za_endpoints,
279 requested_capacity=requested_capacity,
280 vlan_constraint=vlan_constraint,
281 )
282
283 return service_uuid, conn_info
284
285 def delete_connectivity_service(self, service_uuid, conn_info=None):
286 """
287 Disconnect multi-site endpoints previously connected
288
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
292 :return: None
293 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
294 """
295 bidirectional = conn_info["bidirectional"]
296 if bidirectional:
297 service_uuid = conn_info["uuid"]
298 self.tapi_client.delete_service("<>", service_uuid)
299 else:
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)
304
305 def edit_connectivity_service(
306 self, service_uuid, conn_info=None, connection_points=None, **kwargs
307 ):
308 """Change an existing connectivity service.
309
310 This method's arguments and return value follow the same convention as
311 :meth:`~.create_connectivity_service`.
312
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).
322 Raises:
323 SdnConnectorException: In case of error.
324 """
325 raise NotImplementedError
326
327 def clear_all_connectivity_services(self):
328 """Delete all WAN Links in a WIM.
329
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.
333
334 Raises:
335 SdnConnectorException: In case of error.
336 """
337 raise NotImplementedError
338
339 def get_all_active_connectivity_services(self):
340 """Provide information about all active connections provisioned by a
341 WIM.
342
343 Raises:
344 SdnConnectorException: In case of error.
345 """
346 raise NotImplementedError