ced63d2894ee83d0d0a88bde62709d6d44b1a7ac
[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 import logging
24 import uuid
25 import copy
26
27 import requests
28 from requests.auth import HTTPBasicAuth
29
30 from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
31
32
33 class OnosVpls(SdnConnectorBase):
34 """
35 https://wiki.onosproject.org/display/ONOS/VPLS+User+Guide
36 """
37 _WIM_LOGGER = "openmano.sdnconn.onosvpls"
38
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")
45 if not url:
46 raise SdnConnectorError("'url' must be provided")
47 if not url.startswith("http"):
48 url = "http://" + url
49 if not url.endswith("/"):
50 url = url + "/"
51 self.url = url + "onos/v1/network/configuration"
52 self.logger.info("ONOS VPLS Connector Initialized.")
53
54 def check_credentials(self):
55 status_code = 503
56 onos_config_req = None
57 try:
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:
61 if onos_config_req:
62 status_code = onos_config_req.status_code
63 self.logger.exception('Error checking credentials: {}'.format(e))
64 raise SdnConnectorError('Error checking credentials: {}'.format(e), http_code=status_code)
65
66 def get_connectivity_service_status(self, service_uuid, conn_info=None):
67 try:
68 onos_config = self._get_onos_netconfig()
69 vpls_config = onos_config.get('apps', {}).get('org.onosproject.vpls')
70 if vpls_config:
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}
74
75 return {'sdn_status': 'ERROR', 'sdn_info': 'not found'}
76
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)}
80
81 def _get_onos_netconfig(self):
82 try:
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()
87 else:
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))
97
98 def _post_onos_netconfig(self, onos_config):
99 try:
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))
112
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() == 'etree':
117 raise SdnConnectorError('Only ELINE/ELAN network type is supported by ONOS VPLS.')
118
119 # FIXME ¿must check number of connection_points?
120 service_uuid = str(uuid.uuid4())
121
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)
126
127 try:
128 # Create missing interfaces, append to created_items if returned, append_port_to_onos_config
129 # returns null if it was already created
130 created_items = []
131 for port in connection_points:
132 created_ifz = self._append_port_to_onos_config(port, onos_config)
133 if created_ifz:
134 created_items.append(created_ifz[1])
135 self._post_onos_netconfig(onos_config)
136
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'] = {
143 'vplsList': []
144 }
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
152 })
153 self._pop_last_update_time(onos_config)
154 else:
155 onos_config['apps'] = {
156 'org.onosproject.vpls': {
157 'vpls': {
158 "vplsList": [
159 {
160 'name': service_uuid,
161 'interfaces': interfaces,
162 'encapsulation': encapsulation
163 }
164 ]
165 }
166 }
167 }
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)
171
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
178 try:
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)
182 # raise exception
183 if isinstance(e, SdnConnectorError):
184 raise
185 else:
186 raise SdnConnectorError("Exception create_connectivity_service: {}".format(e))
187
188 def _get_encapsulation(self, connection_points):
189 """
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
193 """
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"
199 break
200 return encapsulation
201
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))
205
206 conn_info = conn_info or {}
207 created_ifs = conn_info.get("interfaces", [])
208
209 # Obtain current configuration
210 onos_config_orig = self._get_onos_netconfig()
211 onos_config = copy.deepcopy(onos_config_orig)
212
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")
219 break
220 else:
221 raise SdnConnectorError("service uuid: {} does not exist".format(service_uuid))
222
223 self.logger.debug("current interfaces: {}".format(curr_interfaces))
224 self.logger.debug("current encapsulation: {}".format(curr_encapsulation))
225
226 # new interfaces names
227 new_interfaces = [port['service_endpoint_id'] for port in connection_points]
228
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))
234
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'])
249
250 new_encapsulation = self._get_encapsulation(connection_points)
251
252 try:
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
255 if ifs_delete:
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)
264
265 # Add new interfaces
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)
269 if created_ifz:
270 created_ifs.append(created_ifz[1])
271 self._pop_last_update_time(onos_config)
272 self._post_onos_netconfig(onos_config)
273
274 self.logger.debug("onos config after updating interfaces: {}".format(onos_config))
275 self.logger.debug("created_ifs after updating interfaces: {}".format(created_ifs))
276
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
282
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
289 try:
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)
293 # raise exception
294 if isinstance(e, SdnConnectorError):
295 raise
296 else:
297 raise SdnConnectorError("Exception create_connectivity_service: {}".format(e))
298
299 def delete_connectivity_service(self, service_uuid, conn_info=None):
300 self.logger.debug("delete_connectivity_service uuid: {}".format(service_uuid))
301
302 conn_info = conn_info or {}
303 created_ifs = conn_info.get("interfaces", [])
304 # Obtain current config
305 onos_config = self._get_onos_netconfig()
306
307 try:
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)
321 break
322 else:
323 raise SdnConnectorError("service uuid: {} does not exist".format(service_uuid))
324
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:
329 raise
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)))
333
334 def _pop_last_update_time(self, onos_config):
335 """
336 Needed before post when there are already configured vpls services to apply changes
337 """
338 onos_config['apps']['org.onosproject.vpls']['vpls'].pop('lastUpdateTime', None)
339
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']
345
346 def _append_port_to_onos_config(self, port, onos_config):
347 created_item = None
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)
361 break
362 else:
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'])
366 else:
367 # self.logger.debug("create port and interface")
368 onos_config['ports'][port_name] = {
369 'interfaces': [interface_config]
370 }
371 created_item = (port_name, port['service_endpoint_id'])
372 return created_item
373
374
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"
381 user = "karaf"
382 password = "karaf"
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")
387 # print(conn_service)
388 service_type = 'ELAN'
389 conn_point_0 = {
390 "service_endpoint_id": "switch1:ifz1",
391 "service_endpoint_encapsulation_type": "dot1q",
392 "service_endpoint_encapsulation_info": {
393 "switch_dpid": "0000000000000011",
394 "switch_port": "1",
395 "vlan": "600"
396 }
397 }
398 conn_point_1 = {
399 "service_endpoint_id": "switch3:ifz1",
400 "service_endpoint_encapsulation_type": "dot1q",
401 "service_endpoint_encapsulation_info": {
402 "switch_dpid": "0000000000000031",
403 "switch_port": "3",
404 "vlan": "600"
405 }
406 }
407 connection_points = [conn_point_0, conn_point_1]
408 # service_uuid, conn_info = onos_vpls.create_connectivity_service(service_type, connection_points)
409 # print(service_uuid)
410 # print(conn_info)
411
412 # conn_info = None
413 conn_info = {"interfaces": ['switch1:ifz1', 'switch3:ifz1']}
414 # onos_vpls.delete_connectivity_service("70248a41-11cb-44f3-9039-c41387394a30", conn_info)
415
416 conn_point_0 = {
417 "service_endpoint_id": "switch1:ifz1",
418 "service_endpoint_encapsulation_type": "dot1q",
419 "service_endpoint_encapsulation_info": {
420 "switch_dpid": "0000000000000011",
421 "switch_port": "1",
422 "vlan": "500"
423 }
424 }
425 conn_point_2 = {
426 "service_endpoint_id": "switch1:ifz3",
427 "service_endpoint_encapsulation_type": "dot1q",
428 "service_endpoint_encapsulation_info": {
429 "switch_dpid": "0000000000000011",
430 "switch_port": "3",
431 "vlan": "500"
432 }
433 }
434 conn_point_3 = {
435 "service_endpoint_id": "switch2:ifz2",
436 "service_endpoint_encapsulation_type": "dot1q",
437 "service_endpoint_encapsulation_info": {
438 "switch_dpid": "0000000000000022",
439 "switch_port": "2",
440 "vlan": "500"
441 }
442 }
443 connection_points_2 = [conn_point_0, conn_point_3]
444 # conn_info = onos_vpls.edit_connectivity_service("c65d88be-73aa-4933-927d-57ec6bee6b41",
445 # conn_info, connection_points_2)
446 # print(conn_info)
447
448 service_status = onos_vpls.get_connectivity_service_status("c65d88be-73aa-4933-927d-57ec6bee6b41", conn_info)
449 print("service status")
450 print(service_status)