Disable the check of the release notes
[osm/RO.git] / RO-SDN-ietfl2vpn / osm_rosdn_ietfl2vpn / wimconn_ietfl2vpn.py
1 # -*- coding: utf-8 -*-
2 ##
3 # Copyright 2018 Telefonica
4 # All Rights Reserved.
5 #
6 # Contributors: Oscar Gonzalez de Dios, Manuel Lopez Bravo, Guillermo Pajares Martin
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16 # implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
19 #
20 # This work has been performed in the context of the Metro-Haul project -
21 # funded by the European Commission under Grant number 761727 through the
22 # Horizon 2020 program.
23 ##
24 """The SDN/WIM connector is responsible for establishing wide area network
25 connectivity.
26
27 This SDN/WIM connector implements the standard IETF RFC 8466 "A YANG Data
28 Model for Layer 2 Virtual Private Network (L2VPN) Service Delivery"
29
30 It receives the endpoints and the necessary details to request
31 the Layer 2 service.
32 """
33
34 import logging
35 import uuid
36
37 from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
38 import requests
39
40
41 class WimconnectorIETFL2VPN(SdnConnectorBase):
42 def __init__(self, wim, wim_account, config=None, logger=None):
43 """IETF L2VPN WIM connector
44
45 Arguments: (To be completed)
46 wim (dict): WIM record, as stored in the database
47 wim_account (dict): WIM account record, as stored in the database
48 """
49 self.logger = logging.getLogger("ro.sdn.ietfl2vpn")
50 super().__init__(wim, wim_account, config, logger)
51 if len(self.service_endpoint_mapping) == 0 and self.config.get(
52 "wim_port_mapping"
53 ):
54 self.service_endpoint_mapping = self.config.get("wim_port_mapping", [])
55 self.headers = {"Content-Type": "application/json"}
56 self.mappings = {
57 m["service_endpoint_id"]: m for m in self.service_endpoint_mapping
58 }
59 self.user = wim_account.get("user")
60 self.passwd = wim_account.get("password")
61
62 if self.user and self.passwd is not None:
63 self.auth = (self.user, self.passwd)
64 else:
65 self.auth = None
66
67 self.logger.info("IETFL2VPN Connector Initialized.")
68
69 def check_credentials(self):
70 endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
71 self.wim["wim_url"]
72 )
73
74 try:
75 response = requests.get(endpoint, auth=self.auth)
76 http_code = response.status_code
77 except requests.exceptions.RequestException as e:
78 raise SdnConnectorError(e.message, http_code=503)
79
80 if http_code != 200:
81 raise SdnConnectorError("Failed while authenticating", http_code=http_code)
82
83 self.logger.info("Credentials checked")
84
85 def get_connectivity_service_status(self, service_uuid, conn_info=None):
86 """Monitor the status of the connectivity service stablished
87
88 Arguments:
89 service_uuid: Connectivity service unique identifier
90
91 Returns:
92 Examples::
93 {'sdn_status': 'ACTIVE'}
94 {'sdn_status': 'INACTIVE'}
95 {'sdn_status': 'DOWN'}
96 {'sdn_status': 'ERROR'}
97 """
98 try:
99 self.logger.info("Sending get connectivity service stuatus")
100 servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
101 self.wim["wim_url"], service_uuid
102 )
103 response = requests.get(servicepoint, auth=self.auth)
104
105 if response.status_code != requests.codes.ok:
106 raise SdnConnectorError(
107 "Unable to obtain connectivity servcice status",
108 http_code=response.status_code,
109 )
110
111 service_status = {"sdn_status": "ACTIVE"}
112
113 return service_status
114 except requests.exceptions.ConnectionError:
115 raise SdnConnectorError("Request Timeout", http_code=408)
116
117 def search_mapp(self, connection_point):
118 id = connection_point["service_endpoint_id"]
119 if id not in self.mappings:
120 raise SdnConnectorError("Endpoint {} not located".format(str(id)))
121 else:
122 return self.mappings[id]
123
124 def create_connectivity_service(self, service_type, connection_points, **kwargs):
125 """Stablish WAN connectivity between the endpoints
126
127 Arguments:
128 service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
129 ``L3``.
130 connection_points (list): each point corresponds to
131 an entry point from the DC to the transport network. One
132 connection point serves to identify the specific access and
133 some other service parameters, such as encapsulation type.
134 Represented by a dict as follows::
135
136 {
137 "service_endpoint_id": ..., (str[uuid])
138 "service_endpoint_encapsulation_type": ...,
139 (enum: none, dot1q, ...)
140 "service_endpoint_encapsulation_info": {
141 ... (dict)
142 "vlan": ..., (int, present if encapsulation is dot1q)
143 "vni": ... (int, present if encapsulation is vxlan),
144 "peers": [(ipv4_1), (ipv4_2)]
145 (present if encapsulation is vxlan)
146 }
147 }
148
149 The service endpoint ID should be previously informed to the WIM
150 engine in the RO when the WIM port mapping is registered.
151
152 Keyword Arguments:
153 bandwidth (int): value in kilobytes
154 latency (int): value in milliseconds
155
156 Other QoS might be passed as keyword arguments.
157
158 Returns:
159 tuple: ``(service_id, conn_info)`` containing:
160 - *service_uuid* (str): UUID of the established connectivity
161 service
162 - *conn_info* (dict or None): Information to be stored at the
163 database (or ``None``). This information will be provided to
164 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
165 **MUST** be JSON/YAML-serializable (plain data structures).
166
167 Raises:
168 SdnConnectorException: In case of error.
169 """
170 if service_type == "ELINE":
171 if len(connection_points) > 2:
172 raise SdnConnectorError(
173 "Connections between more than 2 endpoints are not supported"
174 )
175
176 if len(connection_points) < 2:
177 raise SdnConnectorError("Connections must be of at least 2 endpoints")
178
179 """First step, create the vpn service"""
180 uuid_l2vpn = str(uuid.uuid4())
181 vpn_service = {}
182 vpn_service["vpn-id"] = uuid_l2vpn
183 vpn_service["vpn-svc-type"] = "vpws"
184 vpn_service["svc-topo"] = "any-to-any"
185 vpn_service["customer-name"] = "osm"
186 vpn_service_list = []
187 vpn_service_list.append(vpn_service)
188 vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
189 response_service_creation = None
190 conn_info = []
191 self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
192
193 try:
194 endpoint_service_creation = (
195 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
196 self.wim["wim_url"]
197 )
198 )
199 response_service_creation = requests.post(
200 endpoint_service_creation,
201 headers=self.headers,
202 json=vpn_service_l,
203 auth=self.auth,
204 )
205 except requests.exceptions.ConnectionError:
206 raise SdnConnectorError(
207 "Request to create service Timeout", http_code=408
208 )
209
210 if response_service_creation.status_code == 409:
211 raise SdnConnectorError(
212 "Service already exists",
213 http_code=response_service_creation.status_code,
214 )
215 elif response_service_creation.status_code != requests.codes.created:
216 raise SdnConnectorError(
217 "Request to create service not accepted",
218 http_code=response_service_creation.status_code,
219 )
220
221 """Second step, create the connections and vpn attachments"""
222 for connection_point in connection_points:
223 connection_point_wan_info = self.search_mapp(connection_point)
224 site_network_access = {}
225 connection = {}
226
227 if connection_point["service_endpoint_encapsulation_type"] != "none":
228 if (
229 connection_point["service_endpoint_encapsulation_type"]
230 == "dot1q"
231 ):
232 """The connection is a VLAN"""
233 connection["encapsulation-type"] = "dot1q-vlan-tagged"
234 tagged = {}
235 tagged_interf = {}
236 service_endpoint_encapsulation_info = connection_point[
237 "service_endpoint_encapsulation_info"
238 ]
239
240 if service_endpoint_encapsulation_info["vlan"] is None:
241 raise SdnConnectorError("VLAN must be provided")
242
243 tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
244 "vlan"
245 ]
246 tagged["dot1q-vlan-tagged"] = tagged_interf
247 connection["tagged-interface"] = tagged
248 else:
249 raise NotImplementedError("Encapsulation type not implemented")
250
251 site_network_access["connection"] = connection
252 self.logger.info("Sending connection:{}".format(connection))
253 vpn_attach = {}
254 vpn_attach["vpn-id"] = uuid_l2vpn
255 vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
256 site_network_access["vpn-attachment"] = vpn_attach
257 self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
258 uuid_sna = str(uuid.uuid4())
259 site_network_access["network-access-id"] = uuid_sna
260 site_network_access["bearer"] = connection_point_wan_info[
261 "service_mapping_info"
262 ]["bearer"]
263 site_network_accesses = {}
264 site_network_access_list = []
265 site_network_access_list.append(site_network_access)
266 site_network_accesses["ietf-l2vpn-svc:site-network-access"] = (
267 site_network_access_list
268 )
269 conn_info_d = {}
270 conn_info_d["site"] = connection_point_wan_info["service_mapping_info"][
271 "site-id"
272 ]
273 conn_info_d["site-network-access-id"] = site_network_access[
274 "network-access-id"
275 ]
276 conn_info_d["mapping"] = None
277 conn_info.append(conn_info_d)
278
279 try:
280 endpoint_site_network_access_creation = (
281 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
282 "sites/site={}/site-network-accesses/".format(
283 self.wim["wim_url"],
284 connection_point_wan_info["service_mapping_info"][
285 "site-id"
286 ],
287 )
288 )
289 response_endpoint_site_network_access_creation = requests.post(
290 endpoint_site_network_access_creation,
291 headers=self.headers,
292 json=site_network_accesses,
293 auth=self.auth,
294 )
295
296 if (
297 response_endpoint_site_network_access_creation.status_code
298 == 409
299 ):
300 self.delete_connectivity_service(vpn_service["vpn-id"])
301
302 raise SdnConnectorError(
303 "Site_Network_Access with ID '{}' already exists".format(
304 site_network_access["network-access-id"]
305 ),
306 http_code=response_endpoint_site_network_access_creation.status_code,
307 )
308 elif (
309 response_endpoint_site_network_access_creation.status_code
310 == 400
311 ):
312 self.delete_connectivity_service(vpn_service["vpn-id"])
313
314 raise SdnConnectorError(
315 "Site {} does not exist".format(
316 connection_point_wan_info["service_mapping_info"][
317 "site-id"
318 ]
319 ),
320 http_code=response_endpoint_site_network_access_creation.status_code,
321 )
322 elif (
323 response_endpoint_site_network_access_creation.status_code
324 != requests.codes.created
325 and response_endpoint_site_network_access_creation.status_code
326 != requests.codes.no_content
327 ):
328 self.delete_connectivity_service(vpn_service["vpn-id"])
329
330 raise SdnConnectorError(
331 "Request no accepted",
332 http_code=response_endpoint_site_network_access_creation.status_code,
333 )
334 except requests.exceptions.ConnectionError:
335 self.delete_connectivity_service(vpn_service["vpn-id"])
336
337 raise SdnConnectorError("Request Timeout", http_code=408)
338
339 return uuid_l2vpn, conn_info
340 else:
341 raise NotImplementedError
342
343 def delete_connectivity_service(self, service_uuid, conn_info=None):
344 """Disconnect multi-site endpoints previously connected
345
346 This method should receive as the first argument the UUID generated by
347 the ``create_connectivity_service``
348 """
349 try:
350 self.logger.info("Sending delete")
351 servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
352 self.wim["wim_url"], service_uuid
353 )
354 response = requests.delete(servicepoint, auth=self.auth)
355
356 if response.status_code != requests.codes.no_content:
357 raise SdnConnectorError(
358 "Error in the request", http_code=response.status_code
359 )
360 except requests.exceptions.ConnectionError:
361 raise SdnConnectorError("Request Timeout", http_code=408)
362
363 def edit_connectivity_service(
364 self, service_uuid, conn_info=None, connection_points=None, **kwargs
365 ):
366 """Change an existing connectivity service, see
367 ``create_connectivity_service``"""
368 # sites = {"sites": {}}
369 # site_list = []
370 vpn_service = {}
371 vpn_service["svc-topo"] = "any-to-any"
372 counter = 0
373
374 for connection_point in connection_points:
375 site_network_access = {}
376 connection_point_wan_info = self.search_mapp(connection_point)
377 params_site = {}
378 params_site["site-id"] = connection_point_wan_info["service_mapping_info"][
379 "site-id"
380 ]
381 params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
382 device_site = {}
383 device_site["device-id"] = connection_point_wan_info["device-id"]
384 params_site["devices"] = device_site
385 # network_access = {}
386 connection = {}
387
388 if connection_point["service_endpoint_encapsulation_type"] != "none":
389 if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
390 """The connection is a VLAN"""
391 connection["encapsulation-type"] = "dot1q-vlan-tagged"
392 tagged = {}
393 tagged_interf = {}
394 service_endpoint_encapsulation_info = connection_point[
395 "service_endpoint_encapsulation_info"
396 ]
397
398 if service_endpoint_encapsulation_info["vlan"] is None:
399 raise SdnConnectorError("VLAN must be provided")
400
401 tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
402 "vlan"
403 ]
404 tagged["dot1q-vlan-tagged"] = tagged_interf
405 connection["tagged-interface"] = tagged
406 else:
407 raise NotImplementedError("Encapsulation type not implemented")
408
409 site_network_access["connection"] = connection
410 vpn_attach = {}
411 vpn_attach["vpn-id"] = service_uuid
412 vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
413 site_network_access["vpn-attachment"] = vpn_attach
414 uuid_sna = conn_info[counter]["site-network-access-id"]
415 site_network_access["network-access-id"] = uuid_sna
416 site_network_access["bearer"] = connection_point_wan_info[
417 "service_mapping_info"
418 ]["bearer"]
419 site_network_accesses = {}
420 site_network_access_list = []
421 site_network_access_list.append(site_network_access)
422 site_network_accesses["ietf-l2vpn-svc:site-network-access"] = (
423 site_network_access_list
424 )
425
426 try:
427 endpoint_site_network_access_edit = (
428 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
429 "sites/site={}/site-network-accesses/".format(
430 self.wim["wim_url"],
431 connection_point_wan_info["service_mapping_info"]["site-id"],
432 )
433 )
434 response_endpoint_site_network_access_creation = requests.put(
435 endpoint_site_network_access_edit,
436 headers=self.headers,
437 json=site_network_accesses,
438 auth=self.auth,
439 )
440
441 if response_endpoint_site_network_access_creation.status_code == 400:
442 raise SdnConnectorError(
443 "Service does not exist",
444 http_code=response_endpoint_site_network_access_creation.status_code,
445 )
446 elif (
447 response_endpoint_site_network_access_creation.status_code != 201
448 and response_endpoint_site_network_access_creation.status_code
449 != 204
450 ):
451 raise SdnConnectorError(
452 "Request no accepted",
453 http_code=response_endpoint_site_network_access_creation.status_code,
454 )
455 except requests.exceptions.ConnectionError:
456 raise SdnConnectorError("Request Timeout", http_code=408)
457
458 counter += 1
459
460 return None
461
462 def clear_all_connectivity_services(self):
463 """Delete all WAN Links corresponding to a WIM"""
464 try:
465 self.logger.info("Sending clear all connectivity services")
466 servicepoint = (
467 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
468 self.wim["wim_url"]
469 )
470 )
471 response = requests.delete(servicepoint, auth=self.auth)
472
473 if response.status_code != requests.codes.no_content:
474 raise SdnConnectorError(
475 "Unable to clear all connectivity services",
476 http_code=response.status_code,
477 )
478 except requests.exceptions.ConnectionError:
479 raise SdnConnectorError("Request Timeout", http_code=408)
480
481 def get_all_active_connectivity_services(self):
482 """Provide information about all active connections provisioned by a
483 WIM
484 """
485 try:
486 self.logger.info("Sending get all connectivity services")
487 servicepoint = (
488 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
489 self.wim["wim_url"]
490 )
491 )
492 response = requests.get(servicepoint, auth=self.auth)
493
494 if response.status_code != requests.codes.ok:
495 raise SdnConnectorError(
496 "Unable to get all connectivity services",
497 http_code=response.status_code,
498 )
499
500 return response
501 except requests.exceptions.ConnectionError:
502 raise SdnConnectorError("Request Timeout", http_code=408)