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 requests
.auth
import HTTPBasicAuth
30 from osm_ro
.wim
.sdnconn
import SdnConnectorBase
, SdnConnectorError
33 class OnosVpls(SdnConnectorBase
):
35 https://wiki.onosproject.org/display/ONOS/VPLS+User+Guide
37 _WIM_LOGGER
= "openmano.sdnconn.onosvpls"
39 def __init__(self
, wim
, wim_account
, config
=None, logger
=None):
40 self
.logger
= logger
or logging
.getLogger(self
._WIM
_LOGGER
)
41 super().__init
__(wim
, wim_account
, config
, logger
)
42 self
.user
= wim_account
.get("user")
43 self
.password
= wim_account
.get("password")
44 url
= wim
.get("wim_url")
46 raise SdnConnectorError("'url' must be provided")
47 if not url
.startswith("http"):
49 if not url
.endswith("/"):
51 self
.url
= url
+ "onos/v1/network/configuration"
52 self
.logger
.info("ONOS VPLS Connector Initialized.")
54 def check_credentials(self
):
56 onos_config_req
= None
58 onos_config_req
= requests
.get(self
.url
, auth
=HTTPBasicAuth(self
.user
, self
.password
))
59 onos_config_req
.raise_for_status()
60 except Exception as e
:
62 status_code
= onos_config_req
.status_code
63 self
.logger
.exception('Error checking credentials')
64 raise SdnConnectorError('Error checking credentials', http_code
=status_code
)
66 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
68 onos_config
= self
._get
_onos
_netconfig
()
69 vpls_config
= onos_config
.get('apps', {}).get('org.onosproject.vpls')
71 for vpls
in vpls_config
.get('vpls', {}).get('vplsList'):
72 if vpls
.get('name') == service_uuid
:
73 return {'sdn_status': 'ACTIVE', 'sdn_info': vpls
}
75 return {'sdn_status': 'ERROR', 'sdn_info': 'not found'}
77 except Exception as e
:
78 self
.logger
.error('Exception getting connectivity service info: %s', e
)
79 return {'sdn_status': 'ERROR', 'error_msg': str(e
)}
81 def _get_onos_netconfig(self
):
83 onos_config_req
= requests
.get(self
.url
, auth
=HTTPBasicAuth(self
.user
, self
.password
))
84 status_code
= onos_config_req
.status_code
85 if status_code
== requests
.codes
.ok
:
86 return onos_config_req
.json()
88 self
.logger
.info("Error obtaining network config, status code: {}".format(status_code
))
89 raise SdnConnectorError("Error obtaining network config status code: {}".format(status_code
),
90 http_code
=status_code
)
91 except requests
.exceptions
.ConnectionError
as e
:
92 self
.logger
.info('Exception connecting to onos: %s', e
)
93 raise SdnConnectorError("Error connecting to onos: {}".format(e
))
94 except Exception as e
:
95 self
.logger
.error('Exception getting onos network config: %s', e
)
96 raise SdnConnectorError("Exception getting onos network config: {}".format(e
))
98 def _post_onos_netconfig(self
, onos_config
):
100 onos_config_resp
= requests
.post(self
.url
, json
=onos_config
, auth
=HTTPBasicAuth(self
.user
, self
.password
))
101 status_code
= onos_config_resp
.status_code
102 if status_code
!= requests
.codes
.ok
:
103 self
.logger
.info("Error updating network config, status code: {}".format(status_code
))
104 raise SdnConnectorError("Error obtaining network config status code: {}".format(status_code
),
105 http_code
=status_code
)
106 except requests
.exceptions
.ConnectionError
as e
:
107 self
.logger
.info('Exception connecting to onos: %s', e
)
108 raise SdnConnectorError("Error connecting to onos: {}".format(e
))
109 except Exception as e
:
110 self
.logger
.info('Exception posting onos network config: %s', e
)
111 raise SdnConnectorError("Exception posting onos network config: {}".format(e
))
113 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
114 self
.logger
.debug("create_connectivity_service, service_type: {}, connection_points: {}".
115 format(service_type
, connection_points
))
116 if service_type
.lower() != 'elan':
117 raise SdnConnectorError('Only ELAN network type is supported by ONOS VPLS.')
119 # FIXME ¿must check number of connection_points?
120 service_uuid
= str(uuid
.uuid4())
122 # Obtain current configuration
123 onos_config_orig
= self
._get
_onos
_netconfig
()
124 # self.logger.debug("onos config: %s", onos_config_orig)
125 onos_config
= copy
.deepcopy(onos_config_orig
)
128 # Create missing interfaces, append to created_items if returned, append_port_to_onos_config
129 # returns null if it was already created
131 for port
in connection_points
:
132 created_ifz
= self
._append
_port
_to
_onos
_config
(port
, onos_config
)
134 created_items
.append(created_ifz
[1])
135 self
._post
_onos
_netconfig
(onos_config
)
137 # Add vpls service to config
138 encapsulation
= self
._get
_encapsulation
(connection_points
)
139 interfaces
= [port
.get("service_endpoint_id") for port
in connection_points
]
140 if 'org.onosproject.vpls' in onos_config
['apps']:
141 if 'vpls' not in onos_config
['apps']['org.onosproject.vpls']:
142 onos_config
['apps']['org.onosproject.vpls']['vpls'] = {
145 for vpls
in onos_config
['apps']['org.onosproject.vpls']['vpls']['vplsList']:
146 if vpls
['name'] == service_uuid
:
147 raise SdnConnectorError('Network {} already exists.'.format(service_uuid
))
148 onos_config
['apps']['org.onosproject.vpls']['vpls']['vplsList'].append({
149 'name': service_uuid
,
150 'interfaces': interfaces
,
151 'encapsulation': encapsulation
153 self
._pop
_last
_update
_time
(onos_config
)
155 onos_config
['apps'] = {
156 'org.onosproject.vpls': {
160 'name': service_uuid
,
161 'interfaces': interfaces
,
162 'encapsulation': encapsulation
168 # self.logger.debug("original config: %s", onos_config_orig)
169 # self.logger.debug("original config: %s", onos_config)
170 self
._post
_onos
_netconfig
(onos_config
)
172 self
.logger
.debug("created connectivity_service, service_uuid: {}, created_items: {}".
173 format(service_uuid
, created_items
))
174 return service_uuid
, {"interfaces": created_items
}
175 except Exception as e
:
176 self
.logger
.error('Exception add connection_service: %s', e
)
177 # try to rollback push original config
179 self
._post
_onos
_netconfig
(onos_config_orig
)
180 except Exception as e
:
181 self
.logger
.error('Exception rolling back to original config: %s', e
)
183 if isinstance(e
, SdnConnectorError
):
186 raise SdnConnectorError("Exception create_connectivity_service: {}".format(e
))
188 def _get_encapsulation(self
, connection_points
):
190 Obtains encapsulation for the vpls service from the connection_points
191 FIXME: Encapsulation is defined for the connection points but for the VPLS service the encapsulation is
192 defined at the service level so only one can be assigned
194 # check if encapsulation is vlan, check just one connection point
195 encapsulation
= "NONE"
196 for connection_point
in connection_points
:
197 if connection_point
.get("service_endpoint_encapsulation_type") == "dot1q":
198 encapsulation
= "VLAN"
202 def edit_connectivity_service(self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
):
203 self
.logger
.debug("edit connectivity service, service_uuid: {}, conn_info: {}, "
204 "connection points: {} ".format(service_uuid
, conn_info
, connection_points
))
206 conn_info
= conn_info
or {}
207 created_ifs
= conn_info
.get("interfaces", [])
209 # Obtain current configuration
210 onos_config_orig
= self
._get
_onos
_netconfig
()
211 onos_config
= copy
.deepcopy(onos_config_orig
)
213 # get current service data and check if it does not exists
214 for vpls
in onos_config
.get('apps', {}).get('org.onosproject.vpls', {}).get('vpls', {}).get('vplsList', {}):
215 if vpls
['name'] == service_uuid
:
216 self
.logger
.debug("service exists")
217 curr_interfaces
= vpls
.get("interfaces", [])
218 curr_encapsulation
= vpls
.get("encapsulation")
221 raise SdnConnectorError("service uuid: {} does not exist".format(service_uuid
))
223 self
.logger
.debug("current interfaces: {}".format(curr_interfaces
))
224 self
.logger
.debug("current encapsulation: {}".format(curr_encapsulation
))
226 # new interfaces names
227 new_interfaces
= [port
['service_endpoint_id'] for port
in connection_points
]
229 # obtain interfaces to delete, list will contain port
230 ifs_delete
= list(set(curr_interfaces
) - set(new_interfaces
))
231 ifs_add
= list(set(new_interfaces
) - set(curr_interfaces
))
232 self
.logger
.debug("interfaces to delete: {}".format(ifs_delete
))
233 self
.logger
.debug("interfaces to add: {}".format(ifs_add
))
235 # check if some data of the interfaces that already existed has changed
236 # in that case delete it and add it again
237 ifs_remain
= list(set(new_interfaces
) & set(curr_interfaces
))
238 for port
in connection_points
:
239 if port
['service_endpoint_id'] in ifs_remain
:
240 # check if there are some changes
241 curr_port_name
, curr_vlan
= self
._get
_current
_port
_data
(onos_config
, port
['service_endpoint_id'])
242 new_port_name
= 'of:{}/{}'.format(port
['service_endpoint_encapsulation_info']['switch_dpid'],
243 port
['service_endpoint_encapsulation_info']['switch_port'])
244 new_vlan
= port
['service_endpoint_encapsulation_info']['vlan']
245 if (curr_port_name
!= new_port_name
or curr_vlan
!= new_vlan
):
246 self
.logger
.debug("TODO: must update data interface: {}".format(port
['service_endpoint_id']))
247 ifs_delete
.append(port
['service_endpoint_id'])
248 ifs_add
.append(port
['service_endpoint_id'])
250 new_encapsulation
= self
._get
_encapsulation
(connection_points
)
253 # Delete interfaces, only will delete interfaces that are in provided conn_info
254 # because these are the ones that have been created for this service
256 for port
in onos_config
['ports'].values():
257 for port_interface
in port
['interfaces']:
258 interface_name
= port_interface
['name']
259 self
.logger
.debug("interface name: {}".format(port_interface
['name']))
260 if interface_name
in ifs_delete
and interface_name
in created_ifs
:
261 self
.logger
.debug("delete interface name: {}".format(interface_name
))
262 port
['interfaces'].remove(port_interface
)
263 created_ifs
.remove(interface_name
)
266 for port
in connection_points
:
267 if port
['service_endpoint_id'] in ifs_add
:
268 created_ifz
= self
._append
_port
_to
_onos
_config
(port
, onos_config
)
270 created_ifs
.append(created_ifz
[1])
271 self
._pop
_last
_update
_time
(onos_config
)
272 self
._post
_onos
_netconfig
(onos_config
)
274 self
.logger
.debug("onos config after updating interfaces: {}".format(onos_config
))
275 self
.logger
.debug("created_ifs after updating interfaces: {}".format(created_ifs
))
277 # Update interfaces list in vpls service
278 for vpls
in onos_config
.get('apps', {}).get('org.onosproject.vpls', {}).get('vpls', {}).get('vplsList', {}):
279 if vpls
['name'] == service_uuid
:
280 vpls
['interfaces'] = new_interfaces
281 vpls
['encapsulation'] = new_encapsulation
283 self
._pop
_last
_update
_time
(onos_config
)
284 self
._post
_onos
_netconfig
(onos_config
)
285 return {"interfaces": created_ifs
}
286 except Exception as e
:
287 self
.logger
.error('Exception add connection_service: %s', e
)
288 # try to rollback push original config
290 self
._post
_onos
_netconfig
(onos_config_orig
)
291 except Exception as e2
:
292 self
.logger
.error('Exception rolling back to original config: %s', e2
)
294 if isinstance(e
, SdnConnectorError
):
297 raise SdnConnectorError("Exception create_connectivity_service: {}".format(e
))
299 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
300 self
.logger
.debug("delete_connectivity_service uuid: {}".format(service_uuid
))
302 conn_info
= conn_info
or {}
303 created_ifs
= conn_info
.get("interfaces", [])
304 # Obtain current config
305 onos_config
= self
._get
_onos
_netconfig
()
308 # Removes ports used by network from onos config
309 for vpls
in onos_config
.get('apps', {}).get('org.onosproject.vpls', {}).get('vpls', {}).get('vplsList', {}):
310 if vpls
['name'] == service_uuid
:
311 # iterate interfaces to check if must delete them
312 for interface
in vpls
['interfaces']:
313 for port
in onos_config
['ports'].values():
314 for port_interface
in port
['interfaces']:
315 if port_interface
['name'] == interface
:
316 # Delete only created ifzs
317 if port_interface
['name'] in created_ifs
:
318 self
.logger
.debug("Delete ifz: {}".format(port_interface
['name']))
319 port
['interfaces'].remove(port_interface
)
320 onos_config
['apps']['org.onosproject.vpls']['vpls']['vplsList'].remove(vpls
)
323 raise SdnConnectorError("service uuid: {} does not exist".format(service_uuid
))
325 self
._pop
_last
_update
_time
(onos_config
)
326 self
._post
_onos
_netconfig
(onos_config
)
327 self
.logger
.debug("deleted connectivity service uuid: {}".format(service_uuid
))
328 except SdnConnectorError
:
330 except Exception as e
:
331 self
.logger
.error('Exception delete connection_service: %s', e
, exc_info
=True)
332 raise SdnConnectorError("Exception delete connectivity service: {}".format(str(e
)))
334 def _pop_last_update_time(self
, onos_config
):
336 Needed before post when there are already configured vpls services to apply changes
338 onos_config
['apps']['org.onosproject.vpls']['vpls'].pop('lastUpdateTime', None)
340 def _get_current_port_data(self
, onos_config
, interface_name
):
341 for port_name
, port
in onos_config
['ports'].items():
342 for port_interface
in port
['interfaces']:
343 if port_interface
['name'] == interface_name
:
344 return port_name
, port_interface
['vlan']
346 def _append_port_to_onos_config(self
, port
, onos_config
):
348 port_name
= 'of:{}/{}'.format(port
['service_endpoint_encapsulation_info']['switch_dpid'],
349 port
['service_endpoint_encapsulation_info']['switch_port'])
350 interface_config
= {'name': port
['service_endpoint_id']}
351 if 'vlan' in port
['service_endpoint_encapsulation_info'] \
352 and port
['service_endpoint_encapsulation_info']['vlan']:
353 interface_config
['vlan'] = port
['service_endpoint_encapsulation_info']['vlan']
354 if port_name
in onos_config
['ports'] and 'interfaces' in onos_config
['ports'][port_name
]:
355 for interface
in onos_config
['ports'][port_name
]['interfaces']:
356 if interface
['name'] == port
['service_endpoint_id']:
357 #self.logger.debug("interface with same name and port exits")
358 # interface already exists TODO ¿check vlan? ¿delete and recreate?
359 # by the moment use and do not touch
360 #onos_config['ports'][port_name]['interfaces'].remove(interface)
363 #self.logger.debug("port with same name exits but not interface")
364 onos_config
['ports'][port_name
]['interfaces'].append(interface_config
)
365 created_item
= (port_name
, port
['service_endpoint_id'])
367 #self.logger.debug("create port and interface")
368 onos_config
['ports'][port_name
] = {
369 'interfaces': [interface_config
]
371 created_item
= (port_name
, port
['service_endpoint_id'])
375 if __name__
== '__main__':
376 logger
= logging
.getLogger('openmano.sdn.onos_vpls')
377 logging
.basicConfig()
378 logger
.setLevel(getattr(logging
, "DEBUG"))
379 # wim_url = "http://10.95.172.251:8181"
380 wim_url
= "http://192.168.56.106:8181"
383 wim
= {'wim_url': wim_url
}
384 wim_account
= {'user': user
, 'password': password
}
385 onos_vpls
= OnosVpls(wim
=wim
, wim_account
=wim_account
, logger
=logger
)
386 #conn_service = onos_vpls.get_connectivity_service_status("4e1f4c8a-a874-425d-a9b5-955cb77178f8")
388 service_type
= 'ELAN'
390 "service_endpoint_id": "switch1:ifz1",
391 "service_endpoint_encapsulation_type": "dot1q",
392 "service_endpoint_encapsulation_info": {
393 "switch_dpid": "0000000000000011",
399 "service_endpoint_id": "switch3:ifz1",
400 "service_endpoint_encapsulation_type": "dot1q",
401 "service_endpoint_encapsulation_info": {
402 "switch_dpid": "0000000000000031",
407 connection_points
= [conn_point_0
, conn_point_1
]
408 #service_uuid, conn_info = onos_vpls.create_connectivity_service(service_type, connection_points)
413 conn_info
= {"interfaces": ['switch1:ifz1', 'switch3:ifz1']}
414 #onos_vpls.delete_connectivity_service("70248a41-11cb-44f3-9039-c41387394a30", conn_info)
417 "service_endpoint_id": "switch1:ifz1",
418 "service_endpoint_encapsulation_type": "dot1q",
419 "service_endpoint_encapsulation_info": {
420 "switch_dpid": "0000000000000011",
426 "service_endpoint_id": "switch1:ifz3",
427 "service_endpoint_encapsulation_type": "dot1q",
428 "service_endpoint_encapsulation_info": {
429 "switch_dpid": "0000000000000011",
435 "service_endpoint_id": "switch2:ifz2",
436 "service_endpoint_encapsulation_type": "dot1q",
437 "service_endpoint_encapsulation_info": {
438 "switch_dpid": "0000000000000022",
443 connection_points_2
= [conn_point_0
, conn_point_3
]
444 #conn_info = onos_vpls.edit_connectivity_service("c65d88be-73aa-4933-927d-57ec6bee6b41", conn_info, connection_points_2)
447 service_status
= onos_vpls
.get_connectivity_service_status("c65d88be-73aa-4933-927d-57ec6bee6b41", conn_info
)
448 print("service status")
449 print(service_status
)