c02ee15de8b3a48ed2945ecb0df7ffdcd66c3c6e
[osm/RO.git] / RO-SDN-onos_vpls / osm_rosdn_onos_vpls / sdn_assist_onos_vpls.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright 2018 Whitestack, LLC
4 # *************************************************************
5
6 # This file is part of OSM RO module
7 # All Rights Reserved to Whitestack, LLC
8
9 # Licensed under the Apache License, Version 2.0 (the "License"); you may
10 # not use this file except in compliance with the License. You may obtain
11 # a copy of the License at
12
13 # http://www.apache.org/licenses/LICENSE-2.0
14
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18 # License for the specific language governing permissions and limitations
19 # under the License.
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact: bdiaz@whitestack.com or glavado@whitestack.com
22 ##
23
24 import copy
25 import logging
26 import uuid
27
28 from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
29 import requests
30 from requests.auth import HTTPBasicAuth
31
32
33 class OnosVpls(SdnConnectorBase):
34 """
35 https://wiki.onosproject.org/display/ONOS/VPLS+User+Guide
36 """
37
38 _WIM_LOGGER = "ro.sdn.onosvpls"
39
40 def __init__(self, wim, wim_account, config=None, logger=None):
41 self.logger = logger or logging.getLogger(self._WIM_LOGGER)
42 super().__init__(wim, wim_account, config, logger)
43 self.user = wim_account.get("user")
44 self.password = wim_account.get("password")
45 url = wim.get("wim_url")
46
47 if not url:
48 raise SdnConnectorError("'url' must be provided")
49
50 if not url.startswith("http"):
51 url = "http://" + url
52
53 if not url.endswith("/"):
54 url = url + "/"
55
56 self.url = url + "onos/v1/network/configuration"
57 self.logger.info("ONOS VPLS Connector Initialized.")
58
59 def check_credentials(self):
60 status_code = 503
61 onos_config_req = None
62
63 try:
64 onos_config_req = requests.get(
65 self.url, auth=HTTPBasicAuth(self.user, self.password)
66 )
67 onos_config_req.raise_for_status()
68 except Exception as e:
69 if onos_config_req:
70 status_code = onos_config_req.status_code
71
72 self.logger.exception("Error checking credentials: {}".format(e))
73
74 raise SdnConnectorError(
75 "Error checking credentials: {}".format(e), http_code=status_code
76 )
77
78 def get_connectivity_service_status(self, service_uuid, conn_info=None):
79 try:
80 onos_config = self._get_onos_netconfig()
81 vpls_config = onos_config.get("apps", {}).get("org.onosproject.vpls")
82 if vpls_config:
83 for vpls in vpls_config.get("vpls", {}).get("vplsList"):
84 if vpls.get("name") == service_uuid:
85 return {"sdn_status": "ACTIVE", "sdn_info": vpls}
86
87 return {"sdn_status": "ERROR", "sdn_info": "not found"}
88 except Exception as e:
89 self.logger.error("Exception getting connectivity service info: %s", e)
90
91 return {"sdn_status": "ERROR", "error_msg": str(e)}
92
93 def _get_onos_netconfig(self):
94 try:
95 onos_config_req = requests.get(
96 self.url, auth=HTTPBasicAuth(self.user, self.password)
97 )
98 status_code = onos_config_req.status_code
99
100 if status_code == requests.codes.ok:
101 return onos_config_req.json()
102 else:
103 self.logger.info(
104 "Error obtaining network config, status code: {}".format(
105 status_code
106 )
107 )
108
109 raise SdnConnectorError(
110 "Error obtaining network config status code: {}".format(
111 status_code
112 ),
113 http_code=status_code,
114 )
115 except requests.exceptions.ConnectionError as e:
116 self.logger.info("Exception connecting to onos: %s", e)
117
118 raise SdnConnectorError("Error connecting to onos: {}".format(e))
119 except Exception as e:
120 self.logger.error("Exception getting onos network config: %s", e)
121
122 raise SdnConnectorError(
123 "Exception getting onos network config: {}".format(e)
124 )
125
126 def _post_onos_netconfig(self, onos_config):
127 try:
128 onos_config_resp = requests.post(
129 self.url, json=onos_config, auth=HTTPBasicAuth(self.user, self.password)
130 )
131 status_code = onos_config_resp.status_code
132
133 if status_code != requests.codes.ok:
134 self.logger.info(
135 "Error updating network config, status code: {}".format(status_code)
136 )
137
138 raise SdnConnectorError(
139 "Error obtaining network config status code: {}".format(
140 status_code
141 ),
142 http_code=status_code,
143 )
144 except requests.exceptions.ConnectionError as e:
145 self.logger.info("Exception connecting to onos: %s", e)
146
147 raise SdnConnectorError("Error connecting to onos: {}".format(e))
148 except Exception as e:
149 self.logger.info("Exception posting onos network config: %s", e)
150
151 raise SdnConnectorError(
152 "Exception posting onos network config: {}".format(e)
153 )
154
155 def create_connectivity_service(self, service_type, connection_points, **kwargs):
156 self.logger.debug(
157 "create_connectivity_service, service_type: {}, connection_points: {}".format(
158 service_type, connection_points
159 )
160 )
161
162 if service_type.lower() == "etree":
163 raise SdnConnectorError(
164 "Only ELINE/ELAN network type is supported by ONOS VPLS."
165 )
166
167 # FIXME ¿must check number of connection_points?
168 service_uuid = str(uuid.uuid4())
169
170 # Obtain current configuration
171 onos_config_orig = self._get_onos_netconfig()
172 # self.logger.debug("onos config: %s", onos_config_orig)
173 onos_config = copy.deepcopy(onos_config_orig)
174
175 try:
176 # Create missing interfaces, append to created_items if returned, append_port_to_onos_config
177 # returns null if it was already created
178 created_items = []
179
180 for port in connection_points:
181 created_ifz = self._append_port_to_onos_config(port, onos_config)
182 if created_ifz:
183 created_items.append(created_ifz[1])
184
185 self._post_onos_netconfig(onos_config)
186
187 # Add vpls service to config
188 encapsulation = self._get_encapsulation(connection_points)
189 interfaces = [port.get("service_endpoint_id") for port in connection_points]
190
191 if "org.onosproject.vpls" in onos_config["apps"]:
192 if "vpls" not in onos_config["apps"]["org.onosproject.vpls"]:
193 onos_config["apps"]["org.onosproject.vpls"]["vpls"] = {
194 "vplsList": []
195 }
196
197 for vpls in onos_config["apps"]["org.onosproject.vpls"]["vpls"][
198 "vplsList"
199 ]:
200 if vpls["name"] == service_uuid:
201 raise SdnConnectorError(
202 "Network {} already exists.".format(service_uuid)
203 )
204
205 onos_config["apps"]["org.onosproject.vpls"]["vpls"]["vplsList"].append(
206 {
207 "name": service_uuid,
208 "interfaces": interfaces,
209 "encapsulation": encapsulation,
210 }
211 )
212 self._pop_last_update_time(onos_config)
213 else:
214 onos_config["apps"] = {
215 "org.onosproject.vpls": {
216 "vpls": {
217 "vplsList": [
218 {
219 "name": service_uuid,
220 "interfaces": interfaces,
221 "encapsulation": encapsulation,
222 }
223 ]
224 }
225 }
226 }
227 # self.logger.debug("original config: %s", onos_config_orig)
228 # self.logger.debug("original config: %s", onos_config)
229 self._post_onos_netconfig(onos_config)
230
231 self.logger.debug(
232 "created connectivity_service, service_uuid: {}, created_items: {}".format(
233 service_uuid, created_items
234 )
235 )
236
237 return service_uuid, {"interfaces": created_items}
238 except Exception as e:
239 self.logger.error("Exception add connection_service: %s", e)
240
241 # try to rollback push original config
242 try:
243 self._post_onos_netconfig(onos_config_orig)
244 except Exception as rollback_e:
245 self.logger.error(
246 "Exception rolling back to original config: %s", rollback_e
247 )
248
249 # raise exception
250 if isinstance(e, SdnConnectorError):
251 raise
252 else:
253 raise SdnConnectorError(
254 "Exception create_connectivity_service: {}".format(e)
255 )
256
257 def _get_encapsulation(self, connection_points):
258 """
259 Obtains encapsulation for the vpls service from the connection_points
260 FIXME: Encapsulation is defined for the connection points but for the VPLS service the encapsulation is
261 defined at the service level so only one can be assigned
262 """
263 # check if encapsulation is vlan, check just one connection point
264 encapsulation = "NONE"
265 for connection_point in connection_points:
266 if connection_point.get("service_endpoint_encapsulation_type") == "dot1q":
267 encapsulation = "VLAN"
268 break
269
270 return encapsulation
271
272 def edit_connectivity_service(
273 self, service_uuid, conn_info=None, connection_points=None, **kwargs
274 ):
275 self.logger.debug(
276 "edit connectivity service, service_uuid: {}, conn_info: {}, "
277 "connection points: {} ".format(service_uuid, conn_info, connection_points)
278 )
279
280 conn_info = conn_info or {}
281 created_ifs = conn_info.get("interfaces", [])
282
283 # Obtain current configuration
284 onos_config_orig = self._get_onos_netconfig()
285 onos_config = copy.deepcopy(onos_config_orig)
286
287 # get current service data and check if it does not exists
288 for vpls in (
289 onos_config.get("apps", {})
290 .get("org.onosproject.vpls", {})
291 .get("vpls", {})
292 .get("vplsList", {})
293 ):
294 if vpls["name"] == service_uuid:
295 self.logger.debug("service exists")
296 curr_interfaces = vpls.get("interfaces", [])
297 curr_encapsulation = vpls.get("encapsulation")
298 break
299 else:
300 raise SdnConnectorError(
301 "service uuid: {} does not exist".format(service_uuid)
302 )
303
304 self.logger.debug("current interfaces: {}".format(curr_interfaces))
305 self.logger.debug("current encapsulation: {}".format(curr_encapsulation))
306
307 # new interfaces names
308 new_interfaces = [port["service_endpoint_id"] for port in connection_points]
309
310 # obtain interfaces to delete, list will contain port
311 ifs_delete = list(set(curr_interfaces) - set(new_interfaces))
312 ifs_add = list(set(new_interfaces) - set(curr_interfaces))
313 self.logger.debug("interfaces to delete: {}".format(ifs_delete))
314 self.logger.debug("interfaces to add: {}".format(ifs_add))
315
316 # check if some data of the interfaces that already existed has changed
317 # in that case delete it and add it again
318 ifs_remain = list(set(new_interfaces) & set(curr_interfaces))
319 for port in connection_points:
320 if port["service_endpoint_id"] in ifs_remain:
321 # check if there are some changes
322 curr_port_name, curr_vlan = self._get_current_port_data(
323 onos_config, port["service_endpoint_id"]
324 )
325 new_port_name = "of:{}/{}".format(
326 port["service_endpoint_encapsulation_info"]["switch_dpid"],
327 port["service_endpoint_encapsulation_info"]["switch_port"],
328 )
329 new_vlan = port["service_endpoint_encapsulation_info"]["vlan"]
330
331 if curr_port_name != new_port_name or curr_vlan != new_vlan:
332 self.logger.debug(
333 "TODO: must update data interface: {}".format(
334 port["service_endpoint_id"]
335 )
336 )
337 ifs_delete.append(port["service_endpoint_id"])
338 ifs_add.append(port["service_endpoint_id"])
339
340 new_encapsulation = self._get_encapsulation(connection_points)
341
342 try:
343 # Delete interfaces, only will delete interfaces that are in provided conn_info
344 # because these are the ones that have been created for this service
345 if ifs_delete:
346 for port in onos_config["ports"].values():
347 for port_interface in port["interfaces"]:
348 interface_name = port_interface["name"]
349 self.logger.debug(
350 "interface name: {}".format(port_interface["name"])
351 )
352
353 if (
354 interface_name in ifs_delete
355 and interface_name in created_ifs
356 ):
357 self.logger.debug(
358 "delete interface name: {}".format(interface_name)
359 )
360 port["interfaces"].remove(port_interface)
361 created_ifs.remove(interface_name)
362
363 # Add new interfaces
364 for port in connection_points:
365 if port["service_endpoint_id"] in ifs_add:
366 created_ifz = self._append_port_to_onos_config(port, onos_config)
367 if created_ifz:
368 created_ifs.append(created_ifz[1])
369
370 self._pop_last_update_time(onos_config)
371 self._post_onos_netconfig(onos_config)
372
373 self.logger.debug(
374 "onos config after updating interfaces: {}".format(onos_config)
375 )
376 self.logger.debug(
377 "created_ifs after updating interfaces: {}".format(created_ifs)
378 )
379
380 # Update interfaces list in vpls service
381 for vpls in (
382 onos_config.get("apps", {})
383 .get("org.onosproject.vpls", {})
384 .get("vpls", {})
385 .get("vplsList", {})
386 ):
387 if vpls["name"] == service_uuid:
388 vpls["interfaces"] = new_interfaces
389 vpls["encapsulation"] = new_encapsulation
390
391 self._pop_last_update_time(onos_config)
392 self._post_onos_netconfig(onos_config)
393
394 return {"interfaces": created_ifs}
395 except Exception as e:
396 self.logger.error("Exception add connection_service: %s", e)
397 # try to rollback push original config
398 try:
399 self._post_onos_netconfig(onos_config_orig)
400 except Exception as e2:
401 self.logger.error("Exception rolling back to original config: %s", e2)
402 # raise exception
403 if isinstance(e, SdnConnectorError):
404 raise
405 else:
406 raise SdnConnectorError(
407 "Exception create_connectivity_service: {}".format(e)
408 )
409
410 def delete_connectivity_service(self, service_uuid, conn_info=None):
411 self.logger.debug("delete_connectivity_service uuid: {}".format(service_uuid))
412
413 conn_info = conn_info or {}
414 created_ifs = conn_info.get("interfaces", [])
415 # Obtain current config
416 onos_config = self._get_onos_netconfig()
417
418 try:
419 # Removes ports used by network from onos config
420 for vpls in (
421 onos_config.get("apps", {})
422 .get("org.onosproject.vpls", {})
423 .get("vpls", {})
424 .get("vplsList", {})
425 ):
426 if vpls["name"] == service_uuid:
427 # iterate interfaces to check if must delete them
428 for interface in vpls["interfaces"]:
429 for port in onos_config["ports"].values():
430 for port_interface in port["interfaces"]:
431 if port_interface["name"] == interface:
432 # Delete only created ifzs
433 if port_interface["name"] in created_ifs:
434 self.logger.debug(
435 "Delete ifz: {}".format(
436 port_interface["name"]
437 )
438 )
439 port["interfaces"].remove(port_interface)
440 onos_config["apps"]["org.onosproject.vpls"]["vpls"][
441 "vplsList"
442 ].remove(vpls)
443 break
444 else:
445 raise SdnConnectorError(
446 "service uuid: {} does not exist".format(service_uuid)
447 )
448
449 self._pop_last_update_time(onos_config)
450 self._post_onos_netconfig(onos_config)
451 self.logger.debug(
452 "deleted connectivity service uuid: {}".format(service_uuid)
453 )
454 except SdnConnectorError:
455 raise
456 except Exception as e:
457 self.logger.error(
458 "Exception delete connection_service: %s", e, exc_info=True
459 )
460
461 raise SdnConnectorError(
462 "Exception delete connectivity service: {}".format(str(e))
463 )
464
465 def _pop_last_update_time(self, onos_config):
466 """
467 Needed before post when there are already configured vpls services to apply changes
468 """
469 onos_config["apps"]["org.onosproject.vpls"]["vpls"].pop("lastUpdateTime", None)
470
471 def _get_current_port_data(self, onos_config, interface_name):
472 for port_name, port in onos_config["ports"].items():
473 for port_interface in port["interfaces"]:
474 if port_interface["name"] == interface_name:
475 return port_name, port_interface["vlan"]
476
477 def _append_port_to_onos_config(self, port, onos_config):
478 created_item = None
479 port_name = "of:{}/{}".format(
480 port["service_endpoint_encapsulation_info"]["switch_dpid"],
481 port["service_endpoint_encapsulation_info"]["switch_port"],
482 )
483 interface_config = {"name": port["service_endpoint_id"]}
484
485 if (
486 "vlan" in port["service_endpoint_encapsulation_info"]
487 and port["service_endpoint_encapsulation_info"]["vlan"]
488 ):
489 interface_config["vlan"] = port["service_endpoint_encapsulation_info"][
490 "vlan"
491 ]
492
493 if (
494 port_name in onos_config["ports"]
495 and "interfaces" in onos_config["ports"][port_name]
496 ):
497 for interface in onos_config["ports"][port_name]["interfaces"]:
498 if interface["name"] == port["service_endpoint_id"]:
499 # self.logger.debug("interface with same name and port exits")
500 # interface already exists TODO ¿check vlan? ¿delete and recreate?
501 # by the moment use and do not touch
502 # onos_config['ports'][port_name]['interfaces'].remove(interface)
503 break
504 else:
505 # self.logger.debug("port with same name exits but not interface")
506 onos_config["ports"][port_name]["interfaces"].append(interface_config)
507 created_item = (port_name, port["service_endpoint_id"])
508 else:
509 # self.logger.debug("create port and interface")
510 onos_config["ports"][port_name] = {"interfaces": [interface_config]}
511 created_item = (port_name, port["service_endpoint_id"])
512
513 return created_item
514
515
516 if __name__ == "__main__":
517 logger = logging.getLogger("ro.sdn.onos_vpls")
518 logging.basicConfig()
519 logger.setLevel(getattr(logging, "DEBUG"))
520 # wim_url = "http://10.95.172.251:8181"
521 wim_url = "http://192.168.56.106:8181"
522 user = "karaf"
523 password = "karaf"
524 wim = {"wim_url": wim_url}
525 wim_account = {"user": user, "password": password}
526 onos_vpls = OnosVpls(wim=wim, wim_account=wim_account, logger=logger)
527 # conn_service = onos_vpls.get_connectivity_service_status("4e1f4c8a-a874-425d-a9b5-955cb77178f8")
528 # print(conn_service)
529 service_type = "ELAN"
530 conn_point_0 = {
531 "service_endpoint_id": "switch1:ifz1",
532 "service_endpoint_encapsulation_type": "dot1q",
533 "service_endpoint_encapsulation_info": {
534 "switch_dpid": "0000000000000011",
535 "switch_port": "1",
536 "vlan": "600",
537 },
538 }
539 conn_point_1 = {
540 "service_endpoint_id": "switch3:ifz1",
541 "service_endpoint_encapsulation_type": "dot1q",
542 "service_endpoint_encapsulation_info": {
543 "switch_dpid": "0000000000000031",
544 "switch_port": "3",
545 "vlan": "600",
546 },
547 }
548 connection_points = [conn_point_0, conn_point_1]
549 # service_uuid, conn_info = onos_vpls.create_connectivity_service(service_type, connection_points)
550 # print(service_uuid)
551 # print(conn_info)
552
553 # conn_info = None
554 conn_info = {"interfaces": ["switch1:ifz1", "switch3:ifz1"]}
555 # onos_vpls.delete_connectivity_service("70248a41-11cb-44f3-9039-c41387394a30", conn_info)
556
557 conn_point_0 = {
558 "service_endpoint_id": "switch1:ifz1",
559 "service_endpoint_encapsulation_type": "dot1q",
560 "service_endpoint_encapsulation_info": {
561 "switch_dpid": "0000000000000011",
562 "switch_port": "1",
563 "vlan": "500",
564 },
565 }
566 conn_point_2 = {
567 "service_endpoint_id": "switch1:ifz3",
568 "service_endpoint_encapsulation_type": "dot1q",
569 "service_endpoint_encapsulation_info": {
570 "switch_dpid": "0000000000000011",
571 "switch_port": "3",
572 "vlan": "500",
573 },
574 }
575 conn_point_3 = {
576 "service_endpoint_id": "switch2:ifz2",
577 "service_endpoint_encapsulation_type": "dot1q",
578 "service_endpoint_encapsulation_info": {
579 "switch_dpid": "0000000000000022",
580 "switch_port": "2",
581 "vlan": "500",
582 },
583 }
584 connection_points_2 = [conn_point_0, conn_point_3]
585 # conn_info = onos_vpls.edit_connectivity_service("c65d88be-73aa-4933-927d-57ec6bee6b41",
586 # conn_info, connection_points_2)
587 # print(conn_info)
588
589 service_status = onos_vpls.get_connectivity_service_status(
590 "c65d88be-73aa-4933-927d-57ec6bee6b41", conn_info
591 )
592 print("service status")
593 print(service_status)