First implementation of a WIM Connector using IETF L2VPN Service model
[osm/RO.git] / osm_ro / wim / 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 WIM connector is responsible for establishing wide area network
25 connectivity.
26
27 This 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 import requests
34 import json
35 import uuid
36 import time
37 import logging
38 from .wimconn import WimConnector, WimConnectorError
39 """CHeck layer where we move it"""
40
41 class WimconnectorIETFL2VPN(WimConnector):
42 """IETF L2VPM WIM connector
43
44 Arguments: (To be completed)
45 wim (dict): WIM record, as stored in the database
46 wim_account (dict): WIM account record, as stored in the database
47 """
48 def __init__(self, wim, wim_account, config=None, logger=None):
49 self.logger = logging.getLogger('openmano.wimconn.ietfl2vpn')
50 super(WimconnectorIETFL2VPN, self).__init__(wim, wim_account, config, logger)
51 self.headers={'Content-Type': 'application/json'}
52 self.mappings = {m['wan_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("passwd")
56 if self.user != None and self.passwd != None:
57 self.auth = (self.user, self.passwd)
58 else:
59 self.auth=None
60 self.logger.info("IETFL2VPN Connector Initialized.")
61
62 def check_credentials(self):
63 endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
64 try:
65 response = requests.get(endpoint, auth=self.auth)
66 http_code = response.status_code
67 except requests.exceptions.RequestException as e:
68 raise WimConnectorError(e.message, http_code=503)
69
70 if http_code != 200:
71 raise WimConnectorError("Failed while authenticating", http_code=http_code)
72 self.logger.info("Credentials checked")
73
74
75 def get_connectivity_service_status(self, service_uuid, conn_info=None):
76 """Monitor the status of the connectivity service stablished
77
78 Arguments:
79 service_uuid: Connectivity service unique identifier
80
81 Returns:
82 Examples::
83 {'wim_status': 'ACTIVE'}
84 {'wim_status': 'INACTIVE'}
85 {'wim_status': 'DOWN'}
86 {'wim_status': 'ERROR'}
87 """
88 try:
89 self.logger.info("Sending get connectivity service stuatus")
90 servicepoint="{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(self.wim["wim_url"],service_uuid)
91 response=requests.get(servicepoint, auth=self.auth)
92 if response.status_code!= requests.codes.ok :
93 raise WimConnectorError("Unable to obtain connectivity servcice status",http_code=response.status_code)
94 service_status={'wim_status': 'ACTIVE'}
95 return service_status
96 except requests.exceptions.ConnectionError:
97 raise WimConnectorError("Request Timeout",http_code=408)
98
99
100 def search_mapp(self,connection_point):
101 id = connection_point['service_endpoint_id']
102 if id not in self.mappings:
103 raise WimConnectorError("Endpoint {} not located".format(str(id)))
104 else:
105 return self.mappings[id]
106
107
108 def create_connectivity_service(self, service_type, connection_points,
109 **kwargs):
110 """Stablish WAN connectivity between the endpoints
111
112 Arguments:
113 service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
114 ``L3``.
115 connection_points (list): each point corresponds to
116 an entry point from the DC to the transport network. One
117 connection point serves to identify the specific access and
118 some other service parameters, such as encapsulation type.
119 Represented by a dict as follows::
120
121 {
122 "service_endpoint_id": ..., (str[uuid])
123 "service_endpoint_encapsulation_type": ...,
124 (enum: none, dot1q, ...)
125 "service_endpoint_encapsulation_info": {
126 ... (dict)
127 "vlan": ..., (int, present if encapsulation is dot1q)
128 "vni": ... (int, present if encapsulation is vxlan),
129 "peers": [(ipv4_1), (ipv4_2)]
130 (present if encapsulation is vxlan)
131 }
132 }
133
134 The service endpoint ID should be previously informed to the WIM
135 engine in the RO when the WIM port mapping is registered.
136
137 Keyword Arguments:
138 bandwidth (int): value in kilobytes
139 latency (int): value in milliseconds
140
141 Other QoS might be passed as keyword arguments.
142
143 Returns:
144 tuple: ``(service_id, conn_info)`` containing:
145 - *service_uuid* (str): UUID of the established connectivity
146 service
147 - *conn_info* (dict or None): Information to be stored at the
148 database (or ``None``). This information will be provided to
149 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
150 **MUST** be JSON/YAML-serializable (plain data structures).
151
152 Raises:
153 WimConnectorException: In case of error.
154 """
155 if service_type=="ELINE":
156 if len(connection_points)>2:
157 raise WimConnectorError('Connections between more than '
158 '2 endpoints are not supported')
159 if len(connection_points)<2:
160 raise WimConnectorError('Connections must be of at least '
161 '2 endpoints')
162 """ First step, create the vpn service """
163 uuid_l2vpn=str(uuid.uuid4())
164 vpn_service={}
165 vpn_service["vpn-id"]= uuid_l2vpn
166 vpn_service["vpn-scv-type"]="vpws"
167 vpn_service["svc-topo"]= "any-to-any"
168 vpn_service["customer-name"]= "osm"
169 vpn_service_list=[]
170 vpn_service_list.append(vpn_service)
171 vpn_service_l={"vpn-service":vpn_service_list}
172 response_service_creation=None
173 conn_info=[]
174 self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
175 try:
176 endpoint_service_creation="{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
177 response_service_creation=requests.post(endpoint_service_creation, headers=self.headers, json=vpn_service_l, auth=self.auth )
178 except requests.exceptions.ConnectionError:
179 raise WimConnectorError("Request to create service Timeout",http_code=408)
180 if response_service_creation.status_code == 409:
181 raise WimConnectorError("Service already exists",http_code=response_service_creation.status_code)
182 elif response_service_creation.status_code != requests.codes.created :
183 raise WimConnectorError("Request to create service not accepted",http_code=response_service_creation.status_code)
184 """ Second step, create the connections and vpn attachments """
185 for connection_point in connection_points:
186 connection_point_wan_info=self.search_mapp(connection_point)
187 site_network_access={}
188 connection={}
189 if connection_point["service_endpoint_encapsulation_type"]!="none":
190 if connection_point["service_endpoint_encapsulation_type"]=="dot1q":
191 """ The connection is a VLAN """
192 connection["encapsulation-type"]="dot1q-vlan-tagged"
193 tagged={}
194 tagged_interf={}
195 service_endpoint_encapsulation_info=connection_point["service_endpoint_encapsulation_info"]
196 if service_endpoint_encapsulation_info["vlan"]==None:
197 raise WimConnectorError("VLAN must be provided")
198 tagged_interf["cvlan-id"]= service_endpoint_encapsulation_info["vlan"]
199 tagged["dot1q-vlan-tagged"]=tagged_interf
200 connection["tagged-interface"]=tagged
201 else:
202 raise NotImplementedError("Encapsulation type not implemented")
203 site_network_access["connection"]=connection
204 self.logger.info("Sending connection:{}".format(connection))
205 vpn_attach={}
206 vpn_attach["vpn-id"]=uuid_l2vpn
207 vpn_attach["site-role"]=vpn_service["svc-topo"]+"-role"
208 site_network_access["vpn-attachment"]=vpn_attach
209 self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
210 uuid_sna=str(uuid.uuid4())
211 site_network_access["network-access-id"]=uuid_sna
212 site_network_accesses={}
213 site_network_access_list=[]
214 site_network_access_list.append(site_network_access)
215 site_network_accesses["site-network-access"]=site_network_access_list
216 conn_info_d={}
217 conn_info_d["site"] = connection_point_wan_info["site-id"]
218 conn_info_d["site-network-access-id"] = site_network_access["network-access-id"]
219 conn_info_d["mapping"] = None
220 conn_info.append(conn_info_d)
221 try:
222 endpoint_site_network_access_creation="{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(self.wim["wim_url"],connection_point_wan_info["site-id"])
223 response_endpoint_site_network_access_creation = requests.post(endpoint_site_network_access_creation, headers=self.headers, json=site_network_accesses, auth=self.auth )
224
225 if response_endpoint_site_network_access_creation.status_code == 409:
226 self.delete_connectivity_service(vpn_service["vpn-id"])
227 raise WimConnectorError("Site_Network_Access with ID '{}' already exists".format(site_network_access["network-access-id"]),http_code=response_endpoint_site_network_access_creation.status_code)
228
229 elif response_endpoint_site_network_access_creation.status_code == 400:
230 self.delete_connectivity_service(vpn_service["vpn-id"])
231 raise WimConnectorError("Site {} does not exist".format(connection_point_wan_info["site-id"]),http_code=response_endpoint_site_network_access_creation.status_code)
232
233 elif response_endpoint_site_network_access_creation.status_code!= requests.codes.created and response_endpoint_site_network_access_creation.status_code!= requests.codes.no_content:
234 self.delete_connectivity_service(vpn_service["vpn-id"])
235 raise WimConnectorError("Request no accepted",http_code=response_endpoint_site_network_access_creation.status_code)
236
237 except requests.exceptions.ConnectionError:
238 self.delete_connectivity_service(vpn_service["vpn-id"])
239 raise WimConnectorError("Request Timeout",http_code=408)
240 return uuid_l2vpn, conn_info
241
242 else:
243 raise NotImplementedError
244
245
246
247 def delete_connectivity_service(self, service_uuid, conn_info=None):
248 """Disconnect multi-site endpoints previously connected
249
250 This method should receive as the first argument the UUID generated by
251 the ``create_connectivity_service``
252 """
253 try:
254 self.logger.info("Sending delete")
255 servicepoint="{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(self.wim["wim_url"],service_uuid)
256 response=requests.delete(servicepoint, auth=self.auth)
257 if response.status_code!=requests.codes.no_content :
258 raise WimConnectorError("Error in the request",http_code=response.status_code)
259 except requests.exceptions.ConnectionError:
260 raise WimConnectorError("Request Timeout",http_code=408)
261
262
263
264 def edit_connectivity_service(self, service_uuid, conn_info=None,
265 connection_points=None, **kwargs):
266 """Change an existing connectivity service, see
267 ``create_connectivity_service``"""
268
269 sites={"sites":{}}
270 site_list=[]
271 vpn_service={}
272 vpn_service["svc-topo"]= "any-to-any"
273 counter = 0
274 for connection_point in connection_points:
275 site_network_access={}
276 connection_point_wan_info=self.search_mapp(connection_point)
277 params_site={}
278 params_site["site-id"]= connection_point_wan_info["site-id"]
279 params_site["site-vpn-flavor"]= "site-vpn-flavor-single"
280 device_site={}
281 device_site["device-id"]=connection_point_wan_info["device-id"]
282 params_site["devices"]=device_site
283 network_access={}
284 connection={}
285 if connection_point["service_endpoint_encapsulation_type"]!="none":
286 if connection_point["service_endpoint_encapsulation_type"]=="dot1q":
287 """ The connection is a VLAN """
288 connection["encapsulation-type"]="dot1q-vlan-tagged"
289 tagged={}
290 tagged_interf={}
291 service_endpoint_encapsulation_info=connection_point["service_endpoint_encapsulation_info"]
292 if service_endpoint_encapsulation_info["vlan"]==None:
293 raise WimConnectorError("VLAN must be provided")
294 tagged_interf["cvlan-id"]= service_endpoint_encapsulation_info["vlan"]
295 tagged["dot1q-vlan-tagged"]=tagged_interf
296 connection["tagged-interface"]=tagged
297 else:
298 raise NotImplementedError("Encapsulation type not implemented")
299 site_network_access["connection"]=connection
300 vpn_attach={}
301 vpn_attach["vpn-id"]=service_uuid
302 vpn_attach["site-role"]=vpn_service["svc-topo"]+"-role"
303 site_network_access["vpn-attachment"]=vpn_attach
304 uuid_sna=conn_info[counter]["site-network-access-id"]
305 site_network_access["network-access-id"]=uuid_sna
306 site_network_accesses={}
307 site_network_access_list=[]
308 site_network_access_list.append(site_network_access)
309 site_network_accesses["site-network-access"]=site_network_access_list
310 try:
311 endpoint_site_network_access_edit="{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/sites/site={}/site-network-accesses/".format(self.wim["wim_url"],connection_point_wan_info["site-id"]) #MODIF
312 response_endpoint_site_network_access_creation = requests.put(endpoint_site_network_access_edit, headers=self.headers, json=site_network_accesses, auth=self.auth )
313 if response_endpoint_site_network_access_creation.status_code==400:
314 raise WimConnectorError("Service does not exist",http_code=response_endpoint_site_network_access_creation.status_code)
315 elif response_endpoint_site_network_access_creation.status_code!=201 and response_endpoint_site_network_access_creation.status_code!=204:
316 raise WimConnectorError("Request no accepted",http_code=response_endpoint_site_network_access_creation.status_code)
317 except requests.exceptions.ConnectionError:
318 raise WimConnectorError("Request Timeout",http_code=408)
319 counter += 1
320 return None
321
322 def clear_all_connectivity_services(self):
323 """Delete all WAN Links corresponding to a WIM"""
324 try:
325 self.logger.info("Sending clear all connectivity services")
326 servicepoint="{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
327 response=requests.delete(servicepoint, auth=self.auth)
328 if response.status_code!=requests.codes.no_content :
329 raise WimConnectorError("Unable to clear all connectivity services",http_code=response.status_code)
330 except requests.exceptions.ConnectionError:
331 raise WimConnectorError("Request Timeout",http_code=408)
332
333 def get_all_active_connectivity_services(self):
334 """Provide information about all active connections provisioned by a
335 WIM
336 """
337 try:
338 self.logger.info("Sending get all connectivity services")
339 servicepoint="{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(self.wim["wim_url"])
340 response=requests.get(servicepoint, auth=self.auth)
341 if response.status_code!= requests.codes.ok :
342 raise WimConnectorError("Unable to get all connectivity services",http_code=response.status_code)
343 return response
344 except requests.exceptions.ConnectionError:
345 raise WimConnectorError("Request Timeout",http_code=408)