ada0d72f4e489543470a0c7e20d08f5316cabc0e
[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 self.headers = {"Content-Type": "application/json"}
52 self.mappings = {
53 m["service_endpoint_id"]: m for m in self.service_endpoint_mapping
54 }
55 self.user = wim_account.get("user")
56 self.passwd = wim_account.get("passwordd")
57
58 if self.user and self.passwd is not None:
59 self.auth = (self.user, self.passwd)
60 else:
61 self.auth = None
62
63 self.logger.info("IETFL2VPN Connector Initialized.")
64
65 def check_credentials(self):
66 endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
67 self.wim["wim_url"]
68 )
69
70 try:
71 response = requests.get(endpoint, auth=self.auth)
72 http_code = response.status_code
73 except requests.exceptions.RequestException as e:
74 raise SdnConnectorError(e.message, http_code=503)
75
76 if http_code != 200:
77 raise SdnConnectorError("Failed while authenticating", http_code=http_code)
78
79 self.logger.info("Credentials checked")
80
81 def get_connectivity_service_status(self, service_uuid, conn_info=None):
82 """Monitor the status of the connectivity service stablished
83
84 Arguments:
85 service_uuid: Connectivity service unique identifier
86
87 Returns:
88 Examples::
89 {'sdn_status': 'ACTIVE'}
90 {'sdn_status': 'INACTIVE'}
91 {'sdn_status': 'DOWN'}
92 {'sdn_status': 'ERROR'}
93 """
94 try:
95 self.logger.info("Sending get connectivity service stuatus")
96 servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
97 self.wim["wim_url"], service_uuid
98 )
99 response = requests.get(servicepoint, auth=self.auth)
100
101 if response.status_code != requests.codes.ok:
102 raise SdnConnectorError(
103 "Unable to obtain connectivity servcice status",
104 http_code=response.status_code,
105 )
106
107 service_status = {"sdn_status": "ACTIVE"}
108
109 return service_status
110 except requests.exceptions.ConnectionError:
111 raise SdnConnectorError("Request Timeout", http_code=408)
112
113 def search_mapp(self, connection_point):
114 id = connection_point["service_endpoint_id"]
115 if id not in self.mappings:
116 raise SdnConnectorError("Endpoint {} not located".format(str(id)))
117 else:
118 return self.mappings[id]
119
120 def create_connectivity_service(self, service_type, connection_points, **kwargs):
121 """Stablish WAN connectivity between the endpoints
122
123 Arguments:
124 service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
125 ``L3``.
126 connection_points (list): each point corresponds to
127 an entry point from the DC to the transport network. One
128 connection point serves to identify the specific access and
129 some other service parameters, such as encapsulation type.
130 Represented by a dict as follows::
131
132 {
133 "service_endpoint_id": ..., (str[uuid])
134 "service_endpoint_encapsulation_type": ...,
135 (enum: none, dot1q, ...)
136 "service_endpoint_encapsulation_info": {
137 ... (dict)
138 "vlan": ..., (int, present if encapsulation is dot1q)
139 "vni": ... (int, present if encapsulation is vxlan),
140 "peers": [(ipv4_1), (ipv4_2)]
141 (present if encapsulation is vxlan)
142 }
143 }
144
145 The service endpoint ID should be previously informed to the WIM
146 engine in the RO when the WIM port mapping is registered.
147
148 Keyword Arguments:
149 bandwidth (int): value in kilobytes
150 latency (int): value in milliseconds
151
152 Other QoS might be passed as keyword arguments.
153
154 Returns:
155 tuple: ``(service_id, conn_info)`` containing:
156 - *service_uuid* (str): UUID of the established connectivity
157 service
158 - *conn_info* (dict or None): Information to be stored at the
159 database (or ``None``). This information will be provided to
160 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
161 **MUST** be JSON/YAML-serializable (plain data structures).
162
163 Raises:
164 SdnConnectorException: In case of error.
165 """
166 if service_type == "ELINE":
167 if len(connection_points) > 2:
168 raise SdnConnectorError(
169 "Connections between more than 2 endpoints are not supported"
170 )
171
172 if len(connection_points) < 2:
173 raise SdnConnectorError("Connections must be of at least 2 endpoints")
174
175 """First step, create the vpn service"""
176 uuid_l2vpn = str(uuid.uuid4())
177 vpn_service = {}
178 vpn_service["vpn-id"] = uuid_l2vpn
179 vpn_service["vpn-scv-type"] = "vpws"
180 vpn_service["svc-topo"] = "any-to-any"
181 vpn_service["customer-name"] = "osm"
182 vpn_service_list = []
183 vpn_service_list.append(vpn_service)
184 vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
185 response_service_creation = None
186 conn_info = []
187 self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
188
189 try:
190 endpoint_service_creation = (
191 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
192 self.wim["wim_url"]
193 )
194 )
195 response_service_creation = requests.post(
196 endpoint_service_creation,
197 headers=self.headers,
198 json=vpn_service_l,
199 auth=self.auth,
200 )
201 except requests.exceptions.ConnectionError:
202 raise SdnConnectorError(
203 "Request to create service Timeout", http_code=408
204 )
205
206 if response_service_creation.status_code == 409:
207 raise SdnConnectorError(
208 "Service already exists",
209 http_code=response_service_creation.status_code,
210 )
211 elif response_service_creation.status_code != requests.codes.created:
212 raise SdnConnectorError(
213 "Request to create service not accepted",
214 http_code=response_service_creation.status_code,
215 )
216
217 """Second step, create the connections and vpn attachments"""
218 for connection_point in connection_points:
219 connection_point_wan_info = self.search_mapp(connection_point)
220 site_network_access = {}
221 connection = {}
222
223 if connection_point["service_endpoint_encapsulation_type"] != "none":
224 if (
225 connection_point["service_endpoint_encapsulation_type"]
226 == "dot1q"
227 ):
228 """The connection is a VLAN"""
229 connection["encapsulation-type"] = "dot1q-vlan-tagged"
230 tagged = {}
231 tagged_interf = {}
232 service_endpoint_encapsulation_info = connection_point[
233 "service_endpoint_encapsulation_info"
234 ]
235
236 if service_endpoint_encapsulation_info["vlan"] is None:
237 raise SdnConnectorError("VLAN must be provided")
238
239 tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
240 "vlan"
241 ]
242 tagged["dot1q-vlan-tagged"] = tagged_interf
243 connection["tagged-interface"] = tagged
244 else:
245 raise NotImplementedError("Encapsulation type not implemented")
246
247 site_network_access["connection"] = connection
248 self.logger.info("Sending connection:{}".format(connection))
249 vpn_attach = {}
250 vpn_attach["vpn-id"] = uuid_l2vpn
251 vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
252 site_network_access["vpn-attachment"] = vpn_attach
253 self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
254 uuid_sna = str(uuid.uuid4())
255 site_network_access["network-access-id"] = uuid_sna
256 site_network_access["bearer"] = connection_point_wan_info[
257 "service_mapping_info"
258 ]["bearer"]
259 site_network_accesses = {}
260 site_network_access_list = []
261 site_network_access_list.append(site_network_access)
262 site_network_accesses[
263 "ietf-l2vpn-svc:site-network-access"
264 ] = site_network_access_list
265 conn_info_d = {}
266 conn_info_d["site"] = connection_point_wan_info["service_mapping_info"][
267 "site-id"
268 ]
269 conn_info_d["site-network-access-id"] = site_network_access[
270 "network-access-id"
271 ]
272 conn_info_d["mapping"] = None
273 conn_info.append(conn_info_d)
274
275 try:
276 endpoint_site_network_access_creation = (
277 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
278 "sites/site={}/site-network-accesses/".format(
279 self.wim["wim_url"],
280 connection_point_wan_info["service_mapping_info"][
281 "site-id"
282 ],
283 )
284 )
285 response_endpoint_site_network_access_creation = requests.post(
286 endpoint_site_network_access_creation,
287 headers=self.headers,
288 json=site_network_accesses,
289 auth=self.auth,
290 )
291
292 if (
293 response_endpoint_site_network_access_creation.status_code
294 == 409
295 ):
296 self.delete_connectivity_service(vpn_service["vpn-id"])
297
298 raise SdnConnectorError(
299 "Site_Network_Access with ID '{}' already exists".format(
300 site_network_access["network-access-id"]
301 ),
302 http_code=response_endpoint_site_network_access_creation.status_code,
303 )
304 elif (
305 response_endpoint_site_network_access_creation.status_code
306 == 400
307 ):
308 self.delete_connectivity_service(vpn_service["vpn-id"])
309
310 raise SdnConnectorError(
311 "Site {} does not exist".format(
312 connection_point_wan_info["service_mapping_info"][
313 "site-id"
314 ]
315 ),
316 http_code=response_endpoint_site_network_access_creation.status_code,
317 )
318 elif (
319 response_endpoint_site_network_access_creation.status_code
320 != requests.codes.created
321 and response_endpoint_site_network_access_creation.status_code
322 != requests.codes.no_content
323 ):
324 self.delete_connectivity_service(vpn_service["vpn-id"])
325
326 raise SdnConnectorError(
327 "Request no accepted",
328 http_code=response_endpoint_site_network_access_creation.status_code,
329 )
330 except requests.exceptions.ConnectionError:
331 self.delete_connectivity_service(vpn_service["vpn-id"])
332
333 raise SdnConnectorError("Request Timeout", http_code=408)
334
335 return uuid_l2vpn, conn_info
336 else:
337 raise NotImplementedError
338
339 def delete_connectivity_service(self, service_uuid, conn_info=None):
340 """Disconnect multi-site endpoints previously connected
341
342 This method should receive as the first argument the UUID generated by
343 the ``create_connectivity_service``
344 """
345 try:
346 self.logger.info("Sending delete")
347 servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
348 self.wim["wim_url"], service_uuid
349 )
350 response = requests.delete(servicepoint, auth=self.auth)
351
352 if response.status_code != requests.codes.no_content:
353 raise SdnConnectorError(
354 "Error in the request", http_code=response.status_code
355 )
356 except requests.exceptions.ConnectionError:
357 raise SdnConnectorError("Request Timeout", http_code=408)
358
359 def edit_connectivity_service(
360 self, service_uuid, conn_info=None, connection_points=None, **kwargs
361 ):
362 """Change an existing connectivity service, see
363 ``create_connectivity_service``"""
364 # sites = {"sites": {}}
365 # site_list = []
366 vpn_service = {}
367 vpn_service["svc-topo"] = "any-to-any"
368 counter = 0
369
370 for connection_point in connection_points:
371 site_network_access = {}
372 connection_point_wan_info = self.search_mapp(connection_point)
373 params_site = {}
374 params_site["site-id"] = connection_point_wan_info["service_mapping_info"][
375 "site-id"
376 ]
377 params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
378 device_site = {}
379 device_site["device-id"] = connection_point_wan_info["device-id"]
380 params_site["devices"] = device_site
381 # network_access = {}
382 connection = {}
383
384 if connection_point["service_endpoint_encapsulation_type"] != "none":
385 if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
386 """The connection is a VLAN"""
387 connection["encapsulation-type"] = "dot1q-vlan-tagged"
388 tagged = {}
389 tagged_interf = {}
390 service_endpoint_encapsulation_info = connection_point[
391 "service_endpoint_encapsulation_info"
392 ]
393
394 if service_endpoint_encapsulation_info["vlan"] is None:
395 raise SdnConnectorError("VLAN must be provided")
396
397 tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
398 "vlan"
399 ]
400 tagged["dot1q-vlan-tagged"] = tagged_interf
401 connection["tagged-interface"] = tagged
402 else:
403 raise NotImplementedError("Encapsulation type not implemented")
404
405 site_network_access["connection"] = connection
406 vpn_attach = {}
407 vpn_attach["vpn-id"] = service_uuid
408 vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
409 site_network_access["vpn-attachment"] = vpn_attach
410 uuid_sna = conn_info[counter]["site-network-access-id"]
411 site_network_access["network-access-id"] = uuid_sna
412 site_network_access["bearer"] = connection_point_wan_info[
413 "service_mapping_info"
414 ]["bearer"]
415 site_network_accesses = {}
416 site_network_access_list = []
417 site_network_access_list.append(site_network_access)
418 site_network_accesses[
419 "ietf-l2vpn-svc:site-network-access"
420 ] = site_network_access_list
421
422 try:
423 endpoint_site_network_access_edit = (
424 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
425 "sites/site={}/site-network-accesses/".format(
426 self.wim["wim_url"],
427 connection_point_wan_info["service_mapping_info"]["site-id"],
428 )
429 )
430 response_endpoint_site_network_access_creation = requests.put(
431 endpoint_site_network_access_edit,
432 headers=self.headers,
433 json=site_network_accesses,
434 auth=self.auth,
435 )
436
437 if response_endpoint_site_network_access_creation.status_code == 400:
438 raise SdnConnectorError(
439 "Service does not exist",
440 http_code=response_endpoint_site_network_access_creation.status_code,
441 )
442 elif (
443 response_endpoint_site_network_access_creation.status_code != 201
444 and response_endpoint_site_network_access_creation.status_code
445 != 204
446 ):
447 raise SdnConnectorError(
448 "Request no accepted",
449 http_code=response_endpoint_site_network_access_creation.status_code,
450 )
451 except requests.exceptions.ConnectionError:
452 raise SdnConnectorError("Request Timeout", http_code=408)
453
454 counter += 1
455
456 return None
457
458 def clear_all_connectivity_services(self):
459 """Delete all WAN Links corresponding to a WIM"""
460 try:
461 self.logger.info("Sending clear all connectivity services")
462 servicepoint = (
463 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
464 self.wim["wim_url"]
465 )
466 )
467 response = requests.delete(servicepoint, auth=self.auth)
468
469 if response.status_code != requests.codes.no_content:
470 raise SdnConnectorError(
471 "Unable to clear all connectivity services",
472 http_code=response.status_code,
473 )
474 except requests.exceptions.ConnectionError:
475 raise SdnConnectorError("Request Timeout", http_code=408)
476
477 def get_all_active_connectivity_services(self):
478 """Provide information about all active connections provisioned by a
479 WIM
480 """
481 try:
482 self.logger.info("Sending get all connectivity services")
483 servicepoint = (
484 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
485 self.wim["wim_url"]
486 )
487 )
488 response = requests.get(servicepoint, auth=self.auth)
489
490 if response.status_code != requests.codes.ok:
491 raise SdnConnectorError(
492 "Unable to get all connectivity services",
493 http_code=response.status_code,
494 )
495
496 return response
497 except requests.exceptions.ConnectionError:
498 raise SdnConnectorError("Request Timeout", http_code=408)