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 self
.headers
= {"Content-Type": "application/json"}
53 m
["service_endpoint_id"]: m
for m
in self
.service_endpoint_mapping
55 self
.user
= wim_account
.get("user")
56 self
.passwd
= wim_account
.get("passwordd")
58 if self
.user
and self
.passwd
is not None:
59 self
.auth
= (self
.user
, self
.passwd
)
63 self
.logger
.info("IETFL2VPN Connector Initialized.")
65 def check_credentials(self
):
66 endpoint
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
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)
77 raise SdnConnectorError("Failed while authenticating", http_code
=http_code
)
79 self
.logger
.info("Credentials checked")
81 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
82 """Monitor the status of the connectivity service stablished
85 service_uuid: Connectivity service unique identifier
89 {'sdn_status': 'ACTIVE'}
90 {'sdn_status': 'INACTIVE'}
91 {'sdn_status': 'DOWN'}
92 {'sdn_status': 'ERROR'}
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
99 response
= requests
.get(servicepoint
, auth
=self
.auth
)
101 if response
.status_code
!= requests
.codes
.ok
:
102 raise SdnConnectorError(
103 "Unable to obtain connectivity servcice status",
104 http_code
=response
.status_code
,
107 service_status
= {"sdn_status": "ACTIVE"}
109 return service_status
110 except requests
.exceptions
.ConnectionError
:
111 raise SdnConnectorError("Request Timeout", http_code
=408)
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)))
118 return self
.mappings
[id]
120 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
121 """Stablish WAN connectivity between the endpoints
124 service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
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::
133 "service_endpoint_id": ..., (str[uuid])
134 "service_endpoint_encapsulation_type": ...,
135 (enum: none, dot1q, ...)
136 "service_endpoint_encapsulation_info": {
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)
145 The service endpoint ID should be previously informed to the WIM
146 engine in the RO when the WIM port mapping is registered.
149 bandwidth (int): value in kilobytes
150 latency (int): value in milliseconds
152 Other QoS might be passed as keyword arguments.
155 tuple: ``(service_id, conn_info)`` containing:
156 - *service_uuid* (str): UUID of the established connectivity
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).
164 SdnConnectorException: In case of error.
166 if service_type
== "ELINE":
167 if len(connection_points
) > 2:
168 raise SdnConnectorError(
169 "Connections between more than 2 endpoints are not supported"
172 if len(connection_points
) < 2:
173 raise SdnConnectorError("Connections must be of at least 2 endpoints")
175 """First step, create the vpn service"""
176 uuid_l2vpn
= str(uuid
.uuid4())
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
187 self
.logger
.info("Sending vpn-service :{}".format(vpn_service_l
))
190 endpoint_service_creation
= (
191 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
195 response_service_creation
= requests
.post(
196 endpoint_service_creation
,
197 headers
=self
.headers
,
201 except requests
.exceptions
.ConnectionError
:
202 raise SdnConnectorError(
203 "Request to create service Timeout", http_code
=408
206 if response_service_creation
.status_code
== 409:
207 raise SdnConnectorError(
208 "Service already exists",
209 http_code
=response_service_creation
.status_code
,
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
,
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
= {}
223 if connection_point
["service_endpoint_encapsulation_type"] != "none":
225 connection_point
["service_endpoint_encapsulation_type"]
228 """The connection is a VLAN"""
229 connection
["encapsulation-type"] = "dot1q-vlan-tagged"
232 service_endpoint_encapsulation_info
= connection_point
[
233 "service_endpoint_encapsulation_info"
236 if service_endpoint_encapsulation_info
["vlan"] is None:
237 raise SdnConnectorError("VLAN must be provided")
239 tagged_interf
["cvlan-id"] = service_endpoint_encapsulation_info
[
242 tagged
["dot1q-vlan-tagged"] = tagged_interf
243 connection
["tagged-interface"] = tagged
245 raise NotImplementedError("Encapsulation type not implemented")
247 site_network_access
["connection"] = connection
248 self
.logger
.info("Sending connection:{}".format(connection
))
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"
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
266 conn_info_d
["site"] = connection_point_wan_info
["service_mapping_info"][
269 conn_info_d
["site-network-access-id"] = site_network_access
[
272 conn_info_d
["mapping"] = None
273 conn_info
.append(conn_info_d
)
276 endpoint_site_network_access_creation
= (
277 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
278 "sites/site={}/site-network-accesses/".format(
280 connection_point_wan_info
["service_mapping_info"][
285 response_endpoint_site_network_access_creation
= requests
.post(
286 endpoint_site_network_access_creation
,
287 headers
=self
.headers
,
288 json
=site_network_accesses
,
293 response_endpoint_site_network_access_creation
.status_code
296 self
.delete_connectivity_service(vpn_service
["vpn-id"])
298 raise SdnConnectorError(
299 "Site_Network_Access with ID '{}' already exists".format(
300 site_network_access
["network-access-id"]
302 http_code
=response_endpoint_site_network_access_creation
.status_code
,
305 response_endpoint_site_network_access_creation
.status_code
308 self
.delete_connectivity_service(vpn_service
["vpn-id"])
310 raise SdnConnectorError(
311 "Site {} does not exist".format(
312 connection_point_wan_info
["service_mapping_info"][
316 http_code
=response_endpoint_site_network_access_creation
.status_code
,
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
324 self
.delete_connectivity_service(vpn_service
["vpn-id"])
326 raise SdnConnectorError(
327 "Request no accepted",
328 http_code
=response_endpoint_site_network_access_creation
.status_code
,
330 except requests
.exceptions
.ConnectionError
:
331 self
.delete_connectivity_service(vpn_service
["vpn-id"])
333 raise SdnConnectorError("Request Timeout", http_code
=408)
335 return uuid_l2vpn
, conn_info
337 raise NotImplementedError
339 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
340 """Disconnect multi-site endpoints previously connected
342 This method should receive as the first argument the UUID generated by
343 the ``create_connectivity_service``
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
350 response
= requests
.delete(servicepoint
, auth
=self
.auth
)
352 if response
.status_code
!= requests
.codes
.no_content
:
353 raise SdnConnectorError(
354 "Error in the request", http_code
=response
.status_code
356 except requests
.exceptions
.ConnectionError
:
357 raise SdnConnectorError("Request Timeout", http_code
=408)
359 def edit_connectivity_service(
360 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
362 """Change an existing connectivity service, see
363 ``create_connectivity_service``"""
364 # sites = {"sites": {}}
367 vpn_service
["svc-topo"] = "any-to-any"
370 for connection_point
in connection_points
:
371 site_network_access
= {}
372 connection_point_wan_info
= self
.search_mapp(connection_point
)
374 params_site
["site-id"] = connection_point_wan_info
["service_mapping_info"][
377 params_site
["site-vpn-flavor"] = "site-vpn-flavor-single"
379 device_site
["device-id"] = connection_point_wan_info
["device-id"]
380 params_site
["devices"] = device_site
381 # network_access = {}
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"
390 service_endpoint_encapsulation_info
= connection_point
[
391 "service_endpoint_encapsulation_info"
394 if service_endpoint_encapsulation_info
["vlan"] is None:
395 raise SdnConnectorError("VLAN must be provided")
397 tagged_interf
["cvlan-id"] = service_endpoint_encapsulation_info
[
400 tagged
["dot1q-vlan-tagged"] = tagged_interf
401 connection
["tagged-interface"] = tagged
403 raise NotImplementedError("Encapsulation type not implemented")
405 site_network_access
["connection"] = connection
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"
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
423 endpoint_site_network_access_edit
= (
424 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
425 "sites/site={}/site-network-accesses/".format(
427 connection_point_wan_info
["service_mapping_info"]["site-id"],
430 response_endpoint_site_network_access_creation
= requests
.put(
431 endpoint_site_network_access_edit
,
432 headers
=self
.headers
,
433 json
=site_network_accesses
,
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
,
443 response_endpoint_site_network_access_creation
.status_code
!= 201
444 and response_endpoint_site_network_access_creation
.status_code
447 raise SdnConnectorError(
448 "Request no accepted",
449 http_code
=response_endpoint_site_network_access_creation
.status_code
,
451 except requests
.exceptions
.ConnectionError
:
452 raise SdnConnectorError("Request Timeout", http_code
=408)
458 def clear_all_connectivity_services(self
):
459 """Delete all WAN Links corresponding to a WIM"""
461 self
.logger
.info("Sending clear all connectivity services")
463 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
467 response
= requests
.delete(servicepoint
, auth
=self
.auth
)
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
,
474 except requests
.exceptions
.ConnectionError
:
475 raise SdnConnectorError("Request Timeout", http_code
=408)
477 def get_all_active_connectivity_services(self
):
478 """Provide information about all active connections provisioned by a
482 self
.logger
.info("Sending get all connectivity services")
484 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
488 response
= requests
.get(servicepoint
, auth
=self
.auth
)
490 if response
.status_code
!= requests
.codes
.ok
:
491 raise SdnConnectorError(
492 "Unable to get all connectivity services",
493 http_code
=response
.status_code
,
497 except requests
.exceptions
.ConnectionError
:
498 raise SdnConnectorError("Request Timeout", http_code
=408)