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
36 from osm_ro
.wim
.sdnconn
import SdnConnectorBase
, SdnConnectorError
37 """CHeck layer where we move it"""
40 class WimconnectorIETFL2VPN(SdnConnectorBase
):
42 def __init__(self
, wim
, wim_account
, config
=None, logger
=None):
43 """IETF L2VPM 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('openmano.sdnconn.ietfl2vpn')
50 super().__init
__(wim
, wim_account
, config
, logger
)
51 self
.headers
= {'Content-Type': 'application/json'}
52 self
.mappings
= {m
['service_endpoint_id']: m
53 for m
in self
.service_endpoint_mapping
}
54 self
.user
= wim_account
.get("user")
55 self
.passwd
= wim_account
.get("passwordd")
56 if self
.user
and self
.passwd
is not None:
57 self
.auth
= (self
.user
, self
.passwd
)
60 self
.logger
.info("IETFL2VPN Connector Initialized.")
62 def check_credentials(self
):
63 endpoint
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self
.wim
["wim_url"])
65 response
= requests
.get(endpoint
, auth
=self
.auth
)
66 http_code
= response
.status_code
67 except requests
.exceptions
.RequestException
as e
:
68 raise SdnConnectorError(e
.message
, http_code
=503)
71 raise SdnConnectorError("Failed while authenticating", http_code
=http_code
)
72 self
.logger
.info("Credentials checked")
74 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
75 """Monitor the status of the connectivity service stablished
78 service_uuid: Connectivity service unique identifier
82 {'sdn_status': 'ACTIVE'}
83 {'sdn_status': 'INACTIVE'}
84 {'sdn_status': 'DOWN'}
85 {'sdn_status': 'ERROR'}
88 self
.logger
.info("Sending get connectivity service stuatus")
89 servicepoint
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
90 self
.wim
["wim_url"], service_uuid
)
91 response
= requests
.get(servicepoint
, auth
=self
.auth
)
92 if response
.status_code
!= requests
.codes
.ok
:
93 raise SdnConnectorError("Unable to obtain connectivity servcice status", http_code
=response
.status_code
)
94 service_status
= {'sdn_status': 'ACTIVE'}
96 except requests
.exceptions
.ConnectionError
:
97 raise SdnConnectorError("Request Timeout", http_code
=408)
99 def search_mapp(self
, connection_point
):
100 id = connection_point
['service_endpoint_id']
101 if id not in self
.mappings
:
102 raise SdnConnectorError("Endpoint {} not located".format(str(id)))
104 return self
.mappings
[id]
106 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
107 """Stablish WAN connectivity between the endpoints
110 service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
112 connection_points (list): each point corresponds to
113 an entry point from the DC to the transport network. One
114 connection point serves to identify the specific access and
115 some other service parameters, such as encapsulation type.
116 Represented by a dict as follows::
119 "service_endpoint_id": ..., (str[uuid])
120 "service_endpoint_encapsulation_type": ...,
121 (enum: none, dot1q, ...)
122 "service_endpoint_encapsulation_info": {
124 "vlan": ..., (int, present if encapsulation is dot1q)
125 "vni": ... (int, present if encapsulation is vxlan),
126 "peers": [(ipv4_1), (ipv4_2)]
127 (present if encapsulation is vxlan)
131 The service endpoint ID should be previously informed to the WIM
132 engine in the RO when the WIM port mapping is registered.
135 bandwidth (int): value in kilobytes
136 latency (int): value in milliseconds
138 Other QoS might be passed as keyword arguments.
141 tuple: ``(service_id, conn_info)`` containing:
142 - *service_uuid* (str): UUID of the established connectivity
144 - *conn_info* (dict or None): Information to be stored at the
145 database (or ``None``). This information will be provided to
146 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
147 **MUST** be JSON/YAML-serializable (plain data structures).
150 SdnConnectorException: In case of error.
152 if service_type
== "ELINE":
153 if len(connection_points
) > 2:
154 raise SdnConnectorError('Connections between more than 2 endpoints are not supported')
155 if len(connection_points
) < 2:
156 raise SdnConnectorError('Connections must be of at least 2 endpoints')
157 """ First step, create the vpn service """
158 uuid_l2vpn
= str(uuid
.uuid4())
160 vpn_service
["vpn-id"] = uuid_l2vpn
161 vpn_service
["vpn-scv-type"] = "vpws"
162 vpn_service
["svc-topo"] = "any-to-any"
163 vpn_service
["customer-name"] = "osm"
164 vpn_service_list
= []
165 vpn_service_list
.append(vpn_service
)
166 vpn_service_l
= {"ietf-l2vpn-svc:vpn-service": vpn_service_list
}
167 response_service_creation
= None
169 self
.logger
.info("Sending vpn-service :{}".format(vpn_service_l
))
171 endpoint_service_creation
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
173 response_service_creation
= requests
.post(endpoint_service_creation
, headers
=self
.headers
,
174 json
=vpn_service_l
, auth
=self
.auth
)
175 except requests
.exceptions
.ConnectionError
:
176 raise SdnConnectorError("Request to create service Timeout", http_code
=408)
177 if response_service_creation
.status_code
== 409:
178 raise SdnConnectorError("Service already exists", http_code
=response_service_creation
.status_code
)
179 elif response_service_creation
.status_code
!= requests
.codes
.created
:
180 raise SdnConnectorError("Request to create service not accepted",
181 http_code
=response_service_creation
.status_code
)
182 """ Second step, create the connections and vpn attachments """
183 for connection_point
in connection_points
:
184 connection_point_wan_info
= self
.search_mapp(connection_point
)
185 site_network_access
= {}
187 if connection_point
["service_endpoint_encapsulation_type"] != "none":
188 if connection_point
["service_endpoint_encapsulation_type"] == "dot1q":
189 """ The connection is a VLAN """
190 connection
["encapsulation-type"] = "dot1q-vlan-tagged"
193 service_endpoint_encapsulation_info
= connection_point
["service_endpoint_encapsulation_info"]
194 if service_endpoint_encapsulation_info
["vlan"] is None:
195 raise SdnConnectorError("VLAN must be provided")
196 tagged_interf
["cvlan-id"] = service_endpoint_encapsulation_info
["vlan"]
197 tagged
["dot1q-vlan-tagged"] = tagged_interf
198 connection
["tagged-interface"] = tagged
200 raise NotImplementedError("Encapsulation type not implemented")
201 site_network_access
["connection"] = connection
202 self
.logger
.info("Sending connection:{}".format(connection
))
204 vpn_attach
["vpn-id"] = uuid_l2vpn
205 vpn_attach
["site-role"] = vpn_service
["svc-topo"]+"-role"
206 site_network_access
["vpn-attachment"] = vpn_attach
207 self
.logger
.info("Sending vpn-attachement :{}".format(vpn_attach
))
208 uuid_sna
= str(uuid
.uuid4())
209 site_network_access
["network-access-id"] = uuid_sna
210 site_network_access
["bearer"] = connection_point_wan_info
["service_mapping_info"]["bearer"]
211 site_network_accesses
= {}
212 site_network_access_list
= []
213 site_network_access_list
.append(site_network_access
)
214 site_network_accesses
["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
216 conn_info_d
["site"] = connection_point_wan_info
["service_mapping_info"]["site-id"]
217 conn_info_d
["site-network-access-id"] = site_network_access
["network-access-id"]
218 conn_info_d
["mapping"] = None
219 conn_info
.append(conn_info_d
)
221 endpoint_site_network_access_creation
= \
222 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
223 self
.wim
["wim_url"], connection_point_wan_info
["service_mapping_info"]["site-id"])
224 response_endpoint_site_network_access_creation
= requests
.post(
225 endpoint_site_network_access_creation
,
226 headers
=self
.headers
,
227 json
=site_network_accesses
,
230 if response_endpoint_site_network_access_creation
.status_code
== 409:
231 self
.delete_connectivity_service(vpn_service
["vpn-id"])
232 raise SdnConnectorError("Site_Network_Access with ID '{}' already exists".format(
233 site_network_access
["network-access-id"]),
234 http_code
=response_endpoint_site_network_access_creation
.status_code
)
236 elif response_endpoint_site_network_access_creation
.status_code
== 400:
237 self
.delete_connectivity_service(vpn_service
["vpn-id"])
238 raise SdnConnectorError("Site {} does not exist".format(
239 connection_point_wan_info
["service_mapping_info"]["site-id"]),
240 http_code
=response_endpoint_site_network_access_creation
.status_code
)
242 elif response_endpoint_site_network_access_creation
.status_code
!= requests
.codes
.created
and \
243 response_endpoint_site_network_access_creation
.status_code
!= requests
.codes
.no_content
:
244 self
.delete_connectivity_service(vpn_service
["vpn-id"])
245 raise SdnConnectorError("Request no accepted",
246 http_code
=response_endpoint_site_network_access_creation
.status_code
)
248 except requests
.exceptions
.ConnectionError
:
249 self
.delete_connectivity_service(vpn_service
["vpn-id"])
250 raise SdnConnectorError("Request Timeout", http_code
=408)
251 return uuid_l2vpn
, conn_info
254 raise NotImplementedError
256 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
257 """Disconnect multi-site endpoints previously connected
259 This method should receive as the first argument the UUID generated by
260 the ``create_connectivity_service``
263 self
.logger
.info("Sending delete")
264 servicepoint
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
265 self
.wim
["wim_url"], service_uuid
)
266 response
= requests
.delete(servicepoint
, auth
=self
.auth
)
267 if response
.status_code
!= requests
.codes
.no_content
:
268 raise SdnConnectorError("Error in the request", http_code
=response
.status_code
)
269 except requests
.exceptions
.ConnectionError
:
270 raise SdnConnectorError("Request Timeout", http_code
=408)
272 def edit_connectivity_service(self
, service_uuid
, conn_info
=None,
273 connection_points
=None, **kwargs
):
274 """Change an existing connectivity service, see
275 ``create_connectivity_service``"""
277 # sites = {"sites": {}}
280 vpn_service
["svc-topo"] = "any-to-any"
282 for connection_point
in connection_points
:
283 site_network_access
= {}
284 connection_point_wan_info
= self
.search_mapp(connection_point
)
286 params_site
["site-id"] = connection_point_wan_info
["service_mapping_info"]["site-id"]
287 params_site
["site-vpn-flavor"] = "site-vpn-flavor-single"
289 device_site
["device-id"] = connection_point_wan_info
["device-id"]
290 params_site
["devices"] = device_site
291 # network_access = {}
293 if connection_point
["service_endpoint_encapsulation_type"] != "none":
294 if connection_point
["service_endpoint_encapsulation_type"] == "dot1q":
295 """ The connection is a VLAN """
296 connection
["encapsulation-type"] = "dot1q-vlan-tagged"
299 service_endpoint_encapsulation_info
= connection_point
["service_endpoint_encapsulation_info"]
300 if service_endpoint_encapsulation_info
["vlan"] is None:
301 raise SdnConnectorError("VLAN must be provided")
302 tagged_interf
["cvlan-id"] = service_endpoint_encapsulation_info
["vlan"]
303 tagged
["dot1q-vlan-tagged"] = tagged_interf
304 connection
["tagged-interface"] = tagged
306 raise NotImplementedError("Encapsulation type not implemented")
307 site_network_access
["connection"] = connection
309 vpn_attach
["vpn-id"] = service_uuid
310 vpn_attach
["site-role"] = vpn_service
["svc-topo"]+"-role"
311 site_network_access
["vpn-attachment"] = vpn_attach
312 uuid_sna
= conn_info
[counter
]["site-network-access-id"]
313 site_network_access
["network-access-id"] = uuid_sna
314 site_network_access
["bearer"] = connection_point_wan_info
["service_mapping_info"]["bearer"]
315 site_network_accesses
= {}
316 site_network_access_list
= []
317 site_network_access_list
.append(site_network_access
)
318 site_network_accesses
["ietf-l2vpn-svc:site-network-access"] = site_network_access_list
320 endpoint_site_network_access_edit
= \
321 "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(
322 self
.wim
["wim_url"], connection_point_wan_info
["service_mapping_info"]["site-id"])
323 response_endpoint_site_network_access_creation
= requests
.put(endpoint_site_network_access_edit
,
324 headers
=self
.headers
,
325 json
=site_network_accesses
,
327 if response_endpoint_site_network_access_creation
.status_code
== 400:
328 raise SdnConnectorError("Service does not exist",
329 http_code
=response_endpoint_site_network_access_creation
.status_code
)
330 elif response_endpoint_site_network_access_creation
.status_code
!= 201 and \
331 response_endpoint_site_network_access_creation
.status_code
!= 204:
332 raise SdnConnectorError("Request no accepted",
333 http_code
=response_endpoint_site_network_access_creation
.status_code
)
334 except requests
.exceptions
.ConnectionError
:
335 raise SdnConnectorError("Request Timeout", http_code
=408)
339 def clear_all_connectivity_services(self
):
340 """Delete all WAN Links corresponding to a WIM"""
342 self
.logger
.info("Sending clear all connectivity services")
343 servicepoint
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self
.wim
["wim_url"])
344 response
= requests
.delete(servicepoint
, auth
=self
.auth
)
345 if response
.status_code
!= requests
.codes
.no_content
:
346 raise SdnConnectorError("Unable to clear all connectivity services", http_code
=response
.status_code
)
347 except requests
.exceptions
.ConnectionError
:
348 raise SdnConnectorError("Request Timeout", http_code
=408)
350 def get_all_active_connectivity_services(self
):
351 """Provide information about all active connections provisioned by a
355 self
.logger
.info("Sending get all connectivity services")
356 servicepoint
= "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self
.wim
["wim_url"])
357 response
= requests
.get(servicepoint
, auth
=self
.auth
)
358 if response
.status_code
!= requests
.codes
.ok
:
359 raise SdnConnectorError("Unable to get all connectivity services", http_code
=response
.status_code
)
361 except requests
.exceptions
.ConnectionError
:
362 raise SdnConnectorError("Request Timeout", http_code
=408)