1 # -*- coding: utf-8 -*-
3 # Copyright 2018 Telefonica
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
11 # http://www.apache.org/licenses/LICENSE-2.0
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
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
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.
24 """The SDN/WIM connector is responsible for establishing wide area network
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"
30 It receives the endpoints and the necessary details to request
37 from osm_ro_plugin
.sdnconn
import SdnConnectorBase
, SdnConnectorError
41 class WimconnectorIETFL2VPN(SdnConnectorBase
):
42 def __init__(self
, wim
, wim_account
, config
=None, logger
=None):
43 """IETF L2VPN WIM connector
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
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(
54 self
.service_endpoint_mapping
= self
.config
.get("wim_port_mapping", [])
55 self
.headers
= {"Content-Type": "application/json"}
57 m
["service_endpoint_id"]: m
for m
in self
.service_endpoint_mapping
59 self
.user
= wim_account
.get("user")
60 self
.passwd
= wim_account
.get("password")
62 if self
.user
and self
.passwd
is not None:
63 self
.auth
= (self
.user
, self
.passwd
)
67 self
.logger
.info("IETFL2VPN Connector Initialized.")
69 def check_credentials(self
):
70 endpoint
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
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)
81 raise SdnConnectorError("Failed while authenticating", http_code
=http_code
)
83 self
.logger
.info("Credentials checked")
85 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
86 """Monitor the status of the connectivity service stablished
89 service_uuid: Connectivity service unique identifier
93 {'sdn_status': 'ACTIVE'}
94 {'sdn_status': 'INACTIVE'}
95 {'sdn_status': 'DOWN'}
96 {'sdn_status': 'ERROR'}
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
103 response
= requests
.get(servicepoint
, auth
=self
.auth
)
105 if response
.status_code
!= requests
.codes
.ok
:
106 raise SdnConnectorError(
107 "Unable to obtain connectivity servcice status",
108 http_code
=response
.status_code
,
111 service_status
= {"sdn_status": "ACTIVE"}
113 return service_status
114 except requests
.exceptions
.ConnectionError
:
115 raise SdnConnectorError("Request Timeout", http_code
=408)
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)))
122 return self
.mappings
[id]
124 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
125 """Stablish WAN connectivity between the endpoints
128 service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
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::
137 "service_endpoint_id": ..., (str[uuid])
138 "service_endpoint_encapsulation_type": ...,
139 (enum: none, dot1q, ...)
140 "service_endpoint_encapsulation_info": {
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)
149 The service endpoint ID should be previously informed to the WIM
150 engine in the RO when the WIM port mapping is registered.
153 bandwidth (int): value in kilobytes
154 latency (int): value in milliseconds
156 Other QoS might be passed as keyword arguments.
159 tuple: ``(service_id, conn_info)`` containing:
160 - *service_uuid* (str): UUID of the established connectivity
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).
168 SdnConnectorException: In case of error.
170 if service_type
== "ELINE":
171 if len(connection_points
) > 2:
172 raise SdnConnectorError(
173 "Connections between more than 2 endpoints are not supported"
176 if len(connection_points
) < 2:
177 raise SdnConnectorError("Connections must be of at least 2 endpoints")
179 """First step, create the vpn service"""
180 uuid_l2vpn
= str(uuid
.uuid4())
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
191 self
.logger
.info("Sending vpn-service :{}".format(vpn_service_l
))
194 endpoint_service_creation
= (
195 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
199 response_service_creation
= requests
.post(
200 endpoint_service_creation
,
201 headers
=self
.headers
,
205 except requests
.exceptions
.ConnectionError
:
206 raise SdnConnectorError(
207 "Request to create service Timeout", http_code
=408
210 if response_service_creation
.status_code
== 409:
211 raise SdnConnectorError(
212 "Service already exists",
213 http_code
=response_service_creation
.status_code
,
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
,
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
= {}
227 if connection_point
["service_endpoint_encapsulation_type"] != "none":
229 connection_point
["service_endpoint_encapsulation_type"]
232 """The connection is a VLAN"""
233 connection
["encapsulation-type"] = "dot1q-vlan-tagged"
236 service_endpoint_encapsulation_info
= connection_point
[
237 "service_endpoint_encapsulation_info"
240 if service_endpoint_encapsulation_info
["vlan"] is None:
241 raise SdnConnectorError("VLAN must be provided")
243 tagged_interf
["cvlan-id"] = service_endpoint_encapsulation_info
[
246 tagged
["dot1q-vlan-tagged"] = tagged_interf
247 connection
["tagged-interface"] = tagged
249 raise NotImplementedError("Encapsulation type not implemented")
251 site_network_access
["connection"] = connection
252 self
.logger
.info("Sending connection:{}".format(connection
))
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"
263 site_network_accesses
= {}
264 site_network_access_list
= []
265 site_network_access_list
.append(site_network_access
)
266 site_network_accesses
[
267 "ietf-l2vpn-svc:site-network-access"
268 ] = site_network_access_list
270 conn_info_d
["site"] = connection_point_wan_info
["service_mapping_info"][
273 conn_info_d
["site-network-access-id"] = site_network_access
[
276 conn_info_d
["mapping"] = None
277 conn_info
.append(conn_info_d
)
280 endpoint_site_network_access_creation
= (
281 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
282 "sites/site={}/site-network-accesses/".format(
284 connection_point_wan_info
["service_mapping_info"][
289 response_endpoint_site_network_access_creation
= requests
.post(
290 endpoint_site_network_access_creation
,
291 headers
=self
.headers
,
292 json
=site_network_accesses
,
297 response_endpoint_site_network_access_creation
.status_code
300 self
.delete_connectivity_service(vpn_service
["vpn-id"])
302 raise SdnConnectorError(
303 "Site_Network_Access with ID '{}' already exists".format(
304 site_network_access
["network-access-id"]
306 http_code
=response_endpoint_site_network_access_creation
.status_code
,
309 response_endpoint_site_network_access_creation
.status_code
312 self
.delete_connectivity_service(vpn_service
["vpn-id"])
314 raise SdnConnectorError(
315 "Site {} does not exist".format(
316 connection_point_wan_info
["service_mapping_info"][
320 http_code
=response_endpoint_site_network_access_creation
.status_code
,
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
328 self
.delete_connectivity_service(vpn_service
["vpn-id"])
330 raise SdnConnectorError(
331 "Request no accepted",
332 http_code
=response_endpoint_site_network_access_creation
.status_code
,
334 except requests
.exceptions
.ConnectionError
:
335 self
.delete_connectivity_service(vpn_service
["vpn-id"])
337 raise SdnConnectorError("Request Timeout", http_code
=408)
339 return uuid_l2vpn
, conn_info
341 raise NotImplementedError
343 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
344 """Disconnect multi-site endpoints previously connected
346 This method should receive as the first argument the UUID generated by
347 the ``create_connectivity_service``
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
354 response
= requests
.delete(servicepoint
, auth
=self
.auth
)
356 if response
.status_code
!= requests
.codes
.no_content
:
357 raise SdnConnectorError(
358 "Error in the request", http_code
=response
.status_code
360 except requests
.exceptions
.ConnectionError
:
361 raise SdnConnectorError("Request Timeout", http_code
=408)
363 def edit_connectivity_service(
364 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
366 """Change an existing connectivity service, see
367 ``create_connectivity_service``"""
368 # sites = {"sites": {}}
371 vpn_service
["svc-topo"] = "any-to-any"
374 for connection_point
in connection_points
:
375 site_network_access
= {}
376 connection_point_wan_info
= self
.search_mapp(connection_point
)
378 params_site
["site-id"] = connection_point_wan_info
["service_mapping_info"][
381 params_site
["site-vpn-flavor"] = "site-vpn-flavor-single"
383 device_site
["device-id"] = connection_point_wan_info
["device-id"]
384 params_site
["devices"] = device_site
385 # network_access = {}
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"
394 service_endpoint_encapsulation_info
= connection_point
[
395 "service_endpoint_encapsulation_info"
398 if service_endpoint_encapsulation_info
["vlan"] is None:
399 raise SdnConnectorError("VLAN must be provided")
401 tagged_interf
["cvlan-id"] = service_endpoint_encapsulation_info
[
404 tagged
["dot1q-vlan-tagged"] = tagged_interf
405 connection
["tagged-interface"] = tagged
407 raise NotImplementedError("Encapsulation type not implemented")
409 site_network_access
["connection"] = connection
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"
419 site_network_accesses
= {}
420 site_network_access_list
= []
421 site_network_access_list
.append(site_network_access
)
422 site_network_accesses
[
423 "ietf-l2vpn-svc:site-network-access"
424 ] = site_network_access_list
427 endpoint_site_network_access_edit
= (
428 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
429 "sites/site={}/site-network-accesses/".format(
431 connection_point_wan_info
["service_mapping_info"]["site-id"],
434 response_endpoint_site_network_access_creation
= requests
.put(
435 endpoint_site_network_access_edit
,
436 headers
=self
.headers
,
437 json
=site_network_accesses
,
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
,
447 response_endpoint_site_network_access_creation
.status_code
!= 201
448 and response_endpoint_site_network_access_creation
.status_code
451 raise SdnConnectorError(
452 "Request no accepted",
453 http_code
=response_endpoint_site_network_access_creation
.status_code
,
455 except requests
.exceptions
.ConnectionError
:
456 raise SdnConnectorError("Request Timeout", http_code
=408)
462 def clear_all_connectivity_services(self
):
463 """Delete all WAN Links corresponding to a WIM"""
465 self
.logger
.info("Sending clear all connectivity services")
467 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
471 response
= requests
.delete(servicepoint
, auth
=self
.auth
)
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
,
478 except requests
.exceptions
.ConnectionError
:
479 raise SdnConnectorError("Request Timeout", http_code
=408)
481 def get_all_active_connectivity_services(self
):
482 """Provide information about all active connections provisioned by a
486 self
.logger
.info("Sending get all connectivity services")
488 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
492 response
= requests
.get(servicepoint
, auth
=self
.auth
)
494 if response
.status_code
!= requests
.codes
.ok
:
495 raise SdnConnectorError(
496 "Unable to get all connectivity services",
497 http_code
=response
.status_code
,
501 except requests
.exceptions
.ConnectionError
:
502 raise SdnConnectorError("Request Timeout", http_code
=408)