1 # -*- coding: utf-8 -*-
3 # Copyright 2018 Whitestack, LLC
4 # *************************************************************
6 # This file is part of OSM RO module
7 # All Rights Reserved to Whitestack, LLC
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
13 # http://www.apache.org/licenses/LICENSE-2.0
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
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact: bdiaz@whitestack.com or glavado@whitestack.com
28 from osm_ro_plugin
.sdnconn
import SdnConnectorBase
, SdnConnectorError
30 from requests
.auth
import HTTPBasicAuth
33 class OnosVpls(SdnConnectorBase
):
35 https://wiki.onosproject.org/display/ONOS/VPLS+User+Guide
38 _WIM_LOGGER
= "ro.sdn.onosvpls"
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")
48 raise SdnConnectorError("'url' must be provided")
50 if not url
.startswith("http"):
53 if not url
.endswith("/"):
56 self
.url
= url
+ "onos/v1/network/configuration"
57 self
.hosts_url
= url
+ "onos/v1/hosts"
58 self
.logger
.info("ONOS VPLS Connector Initialized.")
60 def check_credentials(self
):
62 onos_config_req
= None
65 onos_config_req
= requests
.get(
66 self
.url
, auth
=HTTPBasicAuth(self
.user
, self
.password
)
68 onos_config_req
.raise_for_status()
69 except Exception as e
:
71 status_code
= onos_config_req
.status_code
73 self
.logger
.exception("Error checking credentials: {}".format(e
))
75 raise SdnConnectorError(
76 "Error checking credentials: {}".format(e
), http_code
=status_code
79 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
81 onos_config
= self
._get
_onos
_netconfig
()
82 vpls_config
= onos_config
.get("apps", {}).get("org.onosproject.vpls")
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
}
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
)
92 return {"sdn_status": "ERROR", "error_msg": str(e
)}
94 def _get_onos_netconfig(self
):
96 onos_config_req
= requests
.get(
97 self
.url
, auth
=HTTPBasicAuth(self
.user
, self
.password
)
99 status_code
= onos_config_req
.status_code
101 if status_code
== requests
.codes
.ok
:
102 return onos_config_req
.json()
105 "Error obtaining network config, status code: {}".format(
110 raise SdnConnectorError(
111 "Error obtaining network config status code: {}".format(
114 http_code
=status_code
,
116 except requests
.exceptions
.ConnectionError
as e
:
117 self
.logger
.info("Exception connecting to onos: %s", e
)
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
)
123 raise SdnConnectorError(
124 "Exception getting onos network config: {}".format(e
)
127 def _post_onos_netconfig(self
, onos_config
):
129 onos_config_resp
= requests
.post(
130 self
.url
, json
=onos_config
, auth
=HTTPBasicAuth(self
.user
, self
.password
)
132 status_code
= onos_config_resp
.status_code
134 if status_code
!= requests
.codes
.ok
:
136 "Error updating network config, status code: {}".format(status_code
)
139 raise SdnConnectorError(
140 "Error obtaining network config status code: {}".format(
143 http_code
=status_code
,
145 except requests
.exceptions
.ConnectionError
as e
:
146 self
.logger
.info("Exception connecting to onos: %s", e
)
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
)
152 raise SdnConnectorError(
153 "Exception posting onos network config: {}".format(e
)
156 def _delete_onos_hosts(self
, onos_host_list
):
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
)
163 status_code
= onos_resp
.status_code
165 if status_code
!= requests
.codes
.ok
:
167 "Error deleting ONOS host, status code: {}".format(status_code
)
170 raise SdnConnectorError(
171 "Error deleting ONOS host, status code: {}".format(status_code
),
172 http_code
=status_code
,
174 except requests
.exceptions
.ConnectionError
as e
:
175 self
.logger
.info("Exception connecting to onos: %s", e
)
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
)
181 raise SdnConnectorError(
182 "Exception posting onos network config: {}".format(e
)
185 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
187 "create_connectivity_service, service_type: {}, connection_points: {}".format(
188 service_type
, connection_points
192 if service_type
.lower() == "etree":
193 raise SdnConnectorError(
194 "Only ELINE/ELAN network type is supported by ONOS VPLS."
197 # FIXME ¿must check number of connection_points?
198 service_uuid
= str(uuid
.uuid4())
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
)
206 # Create missing interfaces, append to created_items if returned, append_port_to_onos_config
207 # returns null if it was already created
210 for port
in connection_points
:
211 created_ifz
= self
._append
_port
_to
_onos
_config
(port
, onos_config
)
213 created_items
.append(created_ifz
[1])
215 self
._post
_onos
_netconfig
(onos_config
)
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
]
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"] = {
227 for vpls
in onos_config
["apps"]["org.onosproject.vpls"]["vpls"][
230 if vpls
["name"] == service_uuid
:
231 raise SdnConnectorError(
232 "Network {} already exists.".format(service_uuid
)
235 onos_config
["apps"]["org.onosproject.vpls"]["vpls"]["vplsList"].append(
237 "name": service_uuid
,
238 "interfaces": interfaces
,
239 "encapsulation": encapsulation
,
242 self
._pop
_last
_update
_time
(onos_config
)
244 onos_config
["apps"] = {
245 "org.onosproject.vpls": {
249 "name": service_uuid
,
250 "interfaces": interfaces
,
251 "encapsulation": encapsulation
,
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
)
262 "created connectivity_service, service_uuid: {}, created_items: {}".format(
263 service_uuid
, created_items
267 return service_uuid
, {"interfaces": created_items
}
268 except Exception as e
:
269 self
.logger
.error("Exception add connection_service: %s", e
)
271 # try to rollback push original config
273 self
._post
_onos
_netconfig
(onos_config_orig
)
274 except Exception as rollback_e
:
276 "Exception rolling back to original config: %s", rollback_e
280 if isinstance(e
, SdnConnectorError
):
283 raise SdnConnectorError(
284 "Exception create_connectivity_service: {}".format(e
)
287 def _get_encapsulation(self
, connection_points
):
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
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"
302 def edit_connectivity_service(
303 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
306 "edit connectivity service, service_uuid: {}, conn_info: {}, "
307 "connection points: {} ".format(service_uuid
, conn_info
, connection_points
)
310 conn_info
= conn_info
or {}
311 created_ifs
= conn_info
.get("interfaces", [])
313 # Obtain current configuration
314 onos_config_orig
= self
._get
_onos
_netconfig
()
315 onos_config
= copy
.deepcopy(onos_config_orig
)
317 # get current service data and check if it does not exists
319 onos_config
.get("apps", {})
320 .get("org.onosproject.vpls", {})
324 if vpls
["name"] == service_uuid
:
325 self
.logger
.debug("service exists")
326 curr_interfaces
= vpls
.get("interfaces", [])
327 curr_encapsulation
= vpls
.get("encapsulation")
330 raise SdnConnectorError(
331 "service uuid: {} does not exist".format(service_uuid
)
334 self
.logger
.debug("current interfaces: {}".format(curr_interfaces
))
335 self
.logger
.debug("current encapsulation: {}".format(curr_encapsulation
))
337 # new interfaces names
338 new_interfaces
= [port
["service_endpoint_id"] for port
in connection_points
]
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
))
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"]
355 new_port_name
= "of:{}/{}".format(
356 port
["service_endpoint_encapsulation_info"]["switch_dpid"],
357 port
["service_endpoint_encapsulation_info"]["switch_port"],
359 new_vlan
= port
["service_endpoint_encapsulation_info"]["vlan"]
361 if curr_port_name
!= new_port_name
or curr_vlan
!= new_vlan
:
363 "TODO: must update data interface: {}".format(
364 port
["service_endpoint_id"]
367 ifs_delete
.append(port
["service_endpoint_id"])
368 ifs_add
.append(port
["service_endpoint_id"])
370 new_encapsulation
= self
._get
_encapsulation
(connection_points
)
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
376 for port
in onos_config
["ports"].values():
377 for port_interface
in port
["interfaces"]:
378 interface_name
= port_interface
["name"]
380 "interface name: {}".format(port_interface
["name"])
384 interface_name
in ifs_delete
385 and interface_name
in created_ifs
388 "delete interface name: {}".format(interface_name
)
390 port
["interfaces"].remove(port_interface
)
391 created_ifs
.remove(interface_name
)
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
)
398 created_ifs
.append(created_ifz
[1])
400 self
._pop
_last
_update
_time
(onos_config
)
401 self
._post
_onos
_netconfig
(onos_config
)
404 "onos config after updating interfaces: {}".format(onos_config
)
407 "created_ifs after updating interfaces: {}".format(created_ifs
)
410 # Update interfaces list in vpls service
412 onos_config
.get("apps", {})
413 .get("org.onosproject.vpls", {})
417 if vpls
["name"] == service_uuid
:
418 vpls
["interfaces"] = new_interfaces
419 vpls
["encapsulation"] = new_encapsulation
421 self
._pop
_last
_update
_time
(onos_config
)
422 self
._post
_onos
_netconfig
(onos_config
)
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
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
)
433 if isinstance(e
, SdnConnectorError
):
436 raise SdnConnectorError(
437 "Exception create_connectivity_service: {}".format(e
)
440 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
441 self
.logger
.debug("delete_connectivity_service uuid: {}".format(service_uuid
))
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
= []
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
454 onos_config
.get("apps", {})
455 .get("org.onosproject.vpls", {})
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
:
469 "Delete ifz: {}".format(
470 port_interface
["name"]
473 port
["interfaces"].remove(port_interface
)
474 # TODO: store host_id
476 # conn_service_host_list.append(f"{host_id}")
477 onos_config
["apps"]["org.onosproject.vpls"]["vpls"][
482 raise SdnConnectorError(
483 "service uuid: {} does not exist".format(service_uuid
)
486 self
._pop
_last
_update
_time
(onos_config
)
487 self
._post
_onos
_netconfig
(onos_config
)
488 self
._delete
_onos
_hosts
(conn_service_host_list
)
490 "deleted connectivity service uuid: {}".format(service_uuid
)
492 except SdnConnectorError
:
494 except Exception as e
:
496 "Exception delete connection_service: %s", e
, exc_info
=True
499 raise SdnConnectorError(
500 "Exception delete connectivity service: {}".format(str(e
))
503 def _pop_last_update_time(self
, onos_config
):
505 Needed before post when there are already configured vpls services to apply changes
507 onos_config
["apps"]["org.onosproject.vpls"]["vpls"].pop("lastUpdateTime", None)
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"]
515 def _append_port_to_onos_config(self
, port
, onos_config
):
517 port_name
= "of:{}/{}".format(
518 port
["service_endpoint_encapsulation_info"]["switch_dpid"],
519 port
["service_endpoint_encapsulation_info"]["switch_port"],
521 interface_config
= {"name": port
["service_endpoint_id"]}
524 "vlan" in port
["service_endpoint_encapsulation_info"]
525 and port
["service_endpoint_encapsulation_info"]["vlan"]
527 interface_config
["vlan"] = port
["service_endpoint_encapsulation_info"][
532 port_name
in onos_config
["ports"]
533 and "interfaces" in onos_config
["ports"][port_name
]
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)
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"])
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"])
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"
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"
569 "service_endpoint_id": "switch1:ifz1",
570 "service_endpoint_encapsulation_type": "dot1q",
571 "service_endpoint_encapsulation_info": {
572 "switch_dpid": "0000000000000011",
578 "service_endpoint_id": "switch3:ifz1",
579 "service_endpoint_encapsulation_type": "dot1q",
580 "service_endpoint_encapsulation_info": {
581 "switch_dpid": "0000000000000031",
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)
592 conn_info
= {"interfaces": ["switch1:ifz1", "switch3:ifz1"]}
593 # onos_vpls.delete_connectivity_service("70248a41-11cb-44f3-9039-c41387394a30", conn_info)
596 "service_endpoint_id": "switch1:ifz1",
597 "service_endpoint_encapsulation_type": "dot1q",
598 "service_endpoint_encapsulation_info": {
599 "switch_dpid": "0000000000000011",
605 "service_endpoint_id": "switch1:ifz3",
606 "service_endpoint_encapsulation_type": "dot1q",
607 "service_endpoint_encapsulation_info": {
608 "switch_dpid": "0000000000000011",
614 "service_endpoint_id": "switch2:ifz2",
615 "service_endpoint_encapsulation_type": "dot1q",
616 "service_endpoint_encapsulation_info": {
617 "switch_dpid": "0000000000000022",
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)
627 service_status
= onos_vpls
.get_connectivity_service_status(
628 "c65d88be-73aa-4933-927d-57ec6bee6b41", conn_info
630 print("service status")
631 print(service_status
)