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