Juniper SDN contrail: used VNI stored in a set in the class
[osm/RO.git] / RO-SDN-juniper_contrail / osm_rosdn_juniper_contrail / sdn_assist_juniper_contrail.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright 2020 ETSI OSM
4 #
5 # All Rights Reserved.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may
8 # not use this file except in compliance with the License. You may obtain
9 # a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 # License for the specific language governing permissions and limitations
17 # under the License.
18 #
19
20 import logging
21 import uuid
22 import copy
23 import json
24 import yaml
25
26 #import requests
27 #from requests.auth import HTTPBasicAuth
28 from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
29 from osm_rosdn_juniper_contrail.rest_lib import Http
30
31 from io import BytesIO
32 import pycurl
33
34
35 class JuniperContrail(SdnConnectorBase):
36 """
37 Juniper Contrail SDN plugin. The plugin interacts with Juniper Contrail Controller,
38 whose API details can be found in these links:
39
40 - https://github.com/tonyliu0592/contrail/wiki/API-Configuration-REST
41 - https://www.juniper.net/documentation/en_US/contrail19/information-products/pathway-pages/api-guide-1910/tutorial_with_rest.html
42 - https://github.com/tonyliu0592/contrail-toolbox/blob/master/sriov/sriov
43 """
44 _WIM_LOGGER = "openmano.sdnconn.junipercontrail"
45
46 def __init__(self, wim, wim_account, config=None, logger=None):
47 """
48
49 :param wim: (dict). Contains among others 'wim_url'
50 :param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
51 'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
52 :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
53 'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
54 'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
55 KEY meaning for WIM meaning for SDN assist
56 -------- -------- --------
57 device_id pop_switch_dpid compute_id
58 device_interface_id pop_switch_port compute_pci_address
59 service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id
60 service_mapping_info wan_service_mapping_info SDN_service_mapping_info
61 contains extra information if needed. Text in Yaml format
62 switch_dpid wan_switch_dpid SDN_switch_dpid
63 switch_port wan_switch_port SDN_switch_port
64 datacenter_id vim_account vim_account
65 id: (internal, do not use)
66 wim_id: (internal, do not use)
67 :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
68 """
69 self.logger = logger or logging.getLogger(self._WIM_LOGGER)
70 self.logger.debug('wim: {}, wim_account: {}, config: {}'.format(wim, wim_account, config))
71 super().__init__(wim, wim_account, config, logger)
72
73 self.user = wim_account.get("user")
74 self.password = wim_account.get("password")
75
76 url = wim.get("wim_url")
77 auth_url = None
78 overlay_url = None
79 self.project = None
80 self.domain = None
81 self.asn = None
82 self.fabric = None
83 self.vni_range = None
84 if config:
85 auth_url = config.get("auth_url")
86 overlay_url = config.get("overlay_url")
87 self.project = config.get("project")
88 self.domain = config.get("domain")
89 self.asn = config.get("asn")
90 self.fabric = config.get("fabric")
91 self.vni_range = config.get("vni_range")
92
93 if not url:
94 raise SdnConnectorError("'url' must be provided")
95 if not url.startswith("http"):
96 url = "http://" + url
97 if not url.endswith("/"):
98 url = url + "/"
99 self.url = url
100
101 if not self.project:
102 raise SdnConnectorError("'project' must be provided")
103 if not self.asn:
104 # TODO: Get ASN from controller config; otherwise raise ERROR for the moment
105 raise SdnConnectorError("'asn' was not provided and was not possible to obtain it")
106 if not self.fabric:
107 # TODO: Get FABRIC from controller config; otherwise raise ERROR for the moment
108 raise SdnConnectorError("'fabric' was not provided and was not possible to obtain it")
109 if not self.domain:
110 self.domain = 'default'
111 self.logger.info("No domain was provided. Using 'default'")
112 if not self.vni_range:
113 self.vni_range = ['1000001-2000000']
114 self.logger.info("No vni_range was provided. Using ['1000001-2000000']")
115 self.used_vni = set()
116
117 if overlay_url:
118 if not overlay_url.startswith("http"):
119 overlay_url = "http://" + overlay_url
120 if not overlay_url.endswith("/"):
121 overlay_url = overlay_url + "/"
122 self.overlay_url = overlay_url
123
124 if auth_url:
125 if not auth_url.startswith("http"):
126 auth_url = "http://" + auth_url
127 if not auth_url.endswith("/"):
128 auth_url = auth_url + "/"
129 self.auth_url = auth_url
130
131 # Init http lib
132 self.http = Http(self.logger)
133
134 # Init http headers for all requests
135 self.headers = {'Content-Type': 'application/json'}
136 self.http_header = ['{}: {}'.format(key, val)
137 for (key, val) in list(self.headers.items())]
138
139 auth_dict = {}
140 auth_dict['auth'] = {}
141 auth_dict['auth']['scope'] = {}
142 auth_dict['auth']['scope']['project'] = {}
143 auth_dict['auth']['scope']['project']['domain'] = {}
144 auth_dict['auth']['scope']['project']['domain']["id"] = self.domain
145 auth_dict['auth']['scope']['project']['name'] = self.project
146 auth_dict['auth']['identity'] = {}
147 auth_dict['auth']['identity']['methods'] = ['password']
148 auth_dict['auth']['identity']['password'] = {}
149 auth_dict['auth']['identity']['password']['user'] = {}
150 auth_dict['auth']['identity']['password']['user']['name'] = self.user
151 auth_dict['auth']['identity']['password']['user']['password'] = self.password
152 auth_dict['auth']['identity']['password']['user']['domain'] = {}
153 auth_dict['auth']['identity']['password']['user']['domain']['id'] = self.domain
154 self.auth_dict = auth_dict
155 self.token = None
156
157 self.logger.info("Juniper Contrail Connector Initialized.")
158
159
160 def _generate_vni(self):
161 """
162 Method to get unused VxLAN Network Identifier (VNI)
163 Args:
164 None
165 Returns:
166 VNI
167 """
168 #find unused VLAN ID
169 for vlanID_range in self.vni_range:
170 try:
171 start_vni , end_vni = map(int, vlanID_range.replace(" ", "").split("-"))
172 for vni in range(start_vni, end_vni + 1):
173 if vni not in self.used_vni:
174 return vni
175 except Exception as exp:
176 raise SdnConnectorError("Exception {} occurred while searching a free VNI.".format(exp))
177 else:
178 raise SdnConnectorError("Unable to create the virtual network."\
179 " All VNI in VNI range {} are in use.".format(self.vni_range))
180
181
182 def _get_token(self):
183 self.logger.debug('Current Token:'.format(str(self.token)))
184 auth_url = self.auth_url + 'auth/tokens'
185 if self.token is None:
186 if not self.auth_url:
187 self.token = ""
188 http_code, resp = self.http.post_cmd(url=auth_url, headers=self.http_header,
189 postfields_dict=self.auth_dict,
190 return_header = 'x-subject-token')
191 self.token = resp
192 self.logger.debug('Token: '.format(self.token))
193
194 if self.token:
195 self.headers['X-Auth-Token'] = self.token
196 else:
197 self.headers.pop('X-Auth-Token', None)
198 http_header = ['{}: {}'.format(key, val)
199 for (key, val) in list(self.headers.items())]
200
201
202 # Aux functions for testing
203 def get_url(self):
204 return self.url
205
206
207 def get_overlay_url(self):
208 return self.overlay_url
209
210 # Virtual network operations
211
212 def _create_virtual_network(self, controller_url, name, vni):
213 routetarget = '{}:{}'.format(self.asn,vni)
214 vnet_dict = {
215 "virtual-network": {
216 "virtual_network_properties": {
217 "vxlan_network_identifier": vni,
218 },
219 "parent_type": "project",
220 "fq_name": [
221 "default-domain",
222 "admin",
223 name
224 ],
225 "route_target_list": {
226 "route_target": [
227 "target:" + routetarget
228 ]
229 }
230 }
231 }
232 self._get_token()
233 endpoint = controller_url + 'virtual-networks'
234 http_code, resp = self.http.post_cmd(url = endpoint,
235 headers = self.http_header,
236 postfields_dict = vnet_dict)
237 if http_code not in (200, 201, 202, 204) or not resp:
238 raise SdnConnectorError('Unexpected http status code, or empty response')
239 vnet_info = json.loads(resp)
240 self.logger.debug("vnet_info: {}".format(vnet_info))
241 return vnet_info.get("virtual-network").get('uuid'), vnet_info.get("virtual-network")
242
243
244 def _get_virtual_networks(self, controller_url):
245 self._get_token()
246 endpoint = controller_url + 'virtual-networks'
247 print(endpoint)
248 http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
249 if http_code not in (200, 201, 202, 204) or not resp:
250 raise SdnConnectorError('Unexpected http status code, or empty response')
251 vnets_info = json.loads(resp)
252 self.logger.debug("vnets_info: {}".format(vnets_info))
253 return vnets_info.get('virtual-networks')
254
255
256 def _get_virtual_network(self, controller_url, network_id):
257 self._get_token()
258 endpoint = controller_url + 'virtual-network/{}'.format(network_id)
259 http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
260 if http_code not in (200, 201, 202, 204) or not resp:
261 if http_code == 404:
262 return None
263 raise SdnConnectorError('Unexpected http status code, or empty response')
264 vnet_info = json.loads(resp)
265 self.logger.debug("vnet_info: {}".format(vnet_info))
266 return vnet_info.get("virtual-network")
267
268
269 def _delete_virtual_network(self, controller_url, network_id):
270 self._get_token()
271 endpoint = controller_url + 'virtual-network/{}'.format(network_id)
272 http_code, _ = self.http.delete_cmd(url=endpoint, headers=self.http_header)
273 if http_code not in (200, 201, 202, 204):
274 raise SdnConnectorError('Unexpected http status code')
275 return
276
277
278 # Virtual port group operations
279
280 def _create_vpg(self, controller_url, switch_id, switch_port, network, vlan):
281 vpg_dict = {
282 "virtual-port-group": {
283 }
284 }
285 self._get_token()
286 endpoint = controller_url + 'virtual-port-groups'
287 http_code, resp = self.http.post_cmd(url = endpoint,
288 headers = self.http_header,
289 postfields_dict = vpg_dict)
290 if http_code not in (200, 201, 202, 204) or not resp:
291 raise SdnConnectorError('Unexpected http status code, or empty response')
292 vpg_info = json.loads(resp)
293 self.logger.debug("vpg_info: {}".format(vpg_info))
294 return vpg_info.get("virtual-port-group").get('uuid'), vpg_info.get("virtual-port-group")
295
296
297 def _get_vpgs(self, controller_url):
298 self._get_token()
299 endpoint = controller_url + 'virtual-port-groups'
300 http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
301 if http_code not in (200, 201, 202, 204) or not resp:
302 raise SdnConnectorError('Unexpected http status code, or empty response')
303 vpgs_info = json.loads(resp)
304 self.logger.debug("vpgs_info: {}".format(vpgs_info))
305 return vpgs_info.get('virtual-port-groups')
306
307
308 def _get_vpg(self, controller_url, vpg_id):
309 self._get_token()
310 endpoint = controller_url + 'virtual-port-group/{}'.format(vpg_id)
311 http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
312 if http_code not in (200, 201, 202, 204) or not resp:
313 if http_code == 404:
314 return None
315 raise SdnConnectorError('Unexpected http status code, or empty response')
316 vpg_info = json.loads(resp)
317 self.logger.debug("vpg_info: {}".format(vpg_info))
318 return vpg_info.get("virtual-port-group")
319
320
321 def _delete_vpg(self, controller_url, vpg_id):
322 self._get_token()
323 endpoint = controller_url + 'virtual-port-group/{}'.format(vpg_id)
324 http_code, resp = self.http.delete_cmd(url=endpoint, headers=self.http_header)
325 if http_code not in (200, 201, 202, 204):
326 raise SdnConnectorError('Unexpected http status code')
327 return
328
329
330 def check_credentials(self):
331 """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
332 user (wim_account.user), and password (wim_account.password)
333
334 Raises:
335 SdnConnectorError: Issues regarding authorization, access to
336 external URLs, etc are detected.
337 """
338 self.logger.debug("")
339 self._get_token()
340 try:
341 http_code, resp = self.http.get_cmd(url=self.auth_url, headers=self.http_header)
342 if http_code not in (200, 201, 202, 204) or not resp:
343 raise SdnConnectorError('Unexpected http status code, or empty response')
344 except Exception as e:
345 self.logger.error('Error checking credentials')
346 raise SdnConnectorError('Error checking credentials', http_code=http_code)
347
348
349 def get_connectivity_service_status(self, service_uuid, conn_info=None):
350 """Monitor the status of the connectivity service established
351
352 Arguments:
353 service_uuid (str): UUID of the connectivity service
354 conn_info (dict or None): Information returned by the connector
355 during the service creation/edition and subsequently stored in
356 the database.
357
358 Returns:
359 dict: JSON/YAML-serializable dict that contains a mandatory key
360 ``sdn_status`` associated with one of the following values::
361
362 {'sdn_status': 'ACTIVE'}
363 # The service is up and running.
364
365 {'sdn_status': 'INACTIVE'}
366 # The service was created, but the connector
367 # cannot determine yet if connectivity exists
368 # (ideally, the caller needs to wait and check again).
369
370 {'sdn_status': 'DOWN'}
371 # Connection was previously established,
372 # but an error/failure was detected.
373
374 {'sdn_status': 'ERROR'}
375 # An error occurred when trying to create the service/
376 # establish the connectivity.
377
378 {'sdn_status': 'BUILD'}
379 # Still trying to create the service, the caller
380 # needs to wait and check again.
381
382 Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
383 keys can be used to provide additional status explanation or
384 new information available for the connectivity service.
385 """
386 self.logger.debug("")
387 self._get_token()
388 try:
389 http_code, resp = self.http.get_cmd(endpoint='virtual-network/{}'.format(service_uuid))
390 if http_code not in (200, 201, 202, 204) or not resp:
391 raise SdnConnectorError('Unexpected http status code, or empty response')
392 if resp:
393 vnet_info = json.loads(resp)
394 return {'sdn_status': 'ACTIVE', 'sdn_info': vnet_info['virtual-network']}
395 else:
396 return {'sdn_status': 'ERROR', 'sdn_info': 'not found'}
397 except Exception as e:
398 self.logger.error('Exception getting connectivity service info: %s', e)
399 return {'sdn_status': 'ERROR', 'error_msg': str(e)}
400
401
402 def create_connectivity_service(self, service_type, connection_points, **kwargs):
403 """
404 Establish SDN/WAN connectivity between the endpoints
405 :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
406 :param connection_points: (list): each point corresponds to
407 an entry point to be connected. For WIM: from the DC to the transport network.
408 For SDN: Compute/PCI to the transport network. One
409 connection point serves to identify the specific access and
410 some other service parameters, such as encapsulation type.
411 Each item of the list is a dict with:
412 "service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__)
413 In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
414 it will contain the string "device_id:device_interface_id"
415 "service_endpoint_encapsulation_type": None, "dot1q", ...
416 "service_endpoint_encapsulation_info": (dict) with:
417 "vlan": ..., (int, present if encapsulation is dot1q)
418 "vni": ... (int, present if encapsulation is vxlan),
419 "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
420 "mac": ...
421 "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
422 "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
423 "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
424 "switch_port": ... present if mapping has been found for this device_id,device_interface_id
425 "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
426 :param kwargs: For future versions:
427 bandwidth (int): value in kilobytes
428 latency (int): value in milliseconds
429 Other QoS might be passed as keyword arguments.
430 :return: tuple: ``(service_id, conn_info)`` containing:
431 - *service_uuid* (str): UUID of the established connectivity service
432 - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
433 This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
434 **MUST** be JSON/YAML-serializable (plain data structures).
435 :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
436 Provide the parameter http_code
437 """
438 self.logger.debug("create_connectivity_service, service_type: {}, connection_points: {}".
439 format(service_type, connection_points))
440 if service_type.lower() != 'elan':
441 raise SdnConnectorError('Only ELAN network type is supported by Juniper Contrail.')
442
443 # Step 1. Check in the overlay controller the virtual network created by the VIM
444 # Best option: get network id of the VIM as param (if the VIM already created the network),
445 # and do a request to the controller of the virtual networks whose VIM network id is the provided
446 # Next best option: obtain the network by doing a request to the controller
447 # of the virtual networks using the VLAN ID of any service endpoint.
448 # 1.1 Read VLAN ID from a service endpoint
449 # 1.2 Look for virtual networks with "Provider Network" including a VLAN ID.
450 # 1.3 If more than one, ERROR
451 # Step 2. Modify the existing virtual network in the overlay controller
452 # 2.1 Add VNI (VxLAN Network Identifier - one free from the provided range)
453 # 2.2 Add RouteTarget (RT) ('ASN:VNI', ASN = Autonomous System Number, provided as param or read from controller config)
454 # Step 3. Create a virtual network in the underlay controller
455 # 3.1 Create virtual network (name, VNI, RT)
456 # If the network already existed in the overlay controller, we should use the same name
457 # name = 'osm-plugin-' + overlay_name
458 # Else:
459 # name = 'osm-plugin-' + VNI
460 try:
461 name = 'test-test-1'
462 vni = 999999
463 network_id, network_info = self._create_virtual_network(self.url, name, vni)
464 except SdnConnectorError:
465 raise SdnConnectorError('Failed to create connectivity service {}'.format(name))
466 except Exception as e:
467 self.logger.error('Exception creating connection_service: %s', e, exc_info=True)
468 raise SdnConnectorError("Exception creating connectivity service: {}".format(str(e)))
469 return service_id
470
471
472 def edit_connectivity_service(self, service_uuid, conn_info = None, connection_points = None, **kwargs):
473 """ Change an existing connectivity service.
474
475 This method's arguments and return value follow the same convention as
476 :meth:`~.create_connectivity_service`.
477
478 :param service_uuid: UUID of the connectivity service.
479 :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
480 or edit_connectivity_service
481 :param connection_points: (list): If provided, the old list of connection points will be replaced.
482 :param kwargs: Same meaning that create_connectivity_service
483 :return: dict or None: Information to be updated and stored at the database.
484 When ``None`` is returned, no information should be changed.
485 When an empty dict is returned, the database record will be deleted.
486 **MUST** be JSON/YAML-serializable (plain data structures).
487 Raises:
488 SdnConnectorException: In case of error.
489 """
490 #TODO: to be done. This comes from ONOS VPLS plugin
491 self.logger.debug("edit connectivity service, service_uuid: {}, conn_info: {}, "
492 "connection points: {} ".format(service_uuid, conn_info, connection_points))
493
494 conn_info = conn_info or []
495 # Obtain current configuration
496 config_orig = self._get_onos_netconfig()
497 config = copy.deepcopy(config_orig)
498
499 # get current service data and check if it does not exists
500 #TODO: update
501 for vpls in config.get('apps', {}).get('org.onosproject.vpls', {}).get('vpls', {}).get('vplsList', {}):
502 if vpls['name'] == service_uuid:
503 self.logger.debug("service exists")
504 curr_interfaces = vpls.get("interfaces", [])
505 curr_encapsulation = vpls.get("encapsulation")
506 break
507 else:
508 raise SdnConnectorError("service uuid: {} does not exist".format(service_uuid))
509
510 self.logger.debug("current interfaces: {}".format(curr_interfaces))
511 self.logger.debug("current encapsulation: {}".format(curr_encapsulation))
512
513 # new interfaces names
514 new_interfaces = [port['service_endpoint_id'] for port in new_connection_points]
515
516 # obtain interfaces to delete, list will contain port
517 ifs_delete = list(set(curr_interfaces) - set(new_interfaces))
518 ifs_add = list(set(new_interfaces) - set(curr_interfaces))
519 self.logger.debug("interfaces to delete: {}".format(ifs_delete))
520 self.logger.debug("interfaces to add: {}".format(ifs_add))
521
522 # check if some data of the interfaces that already existed has changed
523 # in that case delete it and add it again
524 ifs_remain = list(set(new_interfaces) & set(curr_interfaces))
525 for port in connection_points:
526 if port['service_endpoint_id'] in ifs_remain:
527 # check if there are some changes
528 curr_port_name, curr_vlan = self._get_current_port_data(config, port['service_endpoint_id'])
529 new_port_name = 'of:{}/{}'.format(port['service_endpoint_encapsulation_info']['switch_dpid'],
530 port['service_endpoint_encapsulation_info']['switch_port'])
531 new_vlan = port['service_endpoint_encapsulation_info']['vlan']
532 if (curr_port_name != new_port_name or curr_vlan != new_vlan):
533 self.logger.debug("TODO: must update data interface: {}".format(port['service_endpoint_id']))
534 ifs_delete.append(port['service_endpoint_id'])
535 ifs_add.append(port['service_endpoint_id'])
536
537 new_encapsulation = self._get_encapsulation(connection_points)
538
539 try:
540 # Delete interfaces, only will delete interfaces that are in provided conn_info
541 # because these are the ones that have been created for this service
542 if ifs_delete:
543 for port in config['ports'].values():
544 for port_interface in port['interfaces']:
545 interface_name = port_interface['name']
546 self.logger.debug("interface name: {}".format(port_interface['name']))
547 if interface_name in ifs_delete and interface_name in conn_info:
548 self.logger.debug("delete interface name: {}".format(interface_name))
549 port['interfaces'].remove(port_interface)
550 conn_info.remove(interface_name)
551
552 # Add new interfaces
553 for port in connection_points:
554 if port['service_endpoint_id'] in ifs_add:
555 created_ifz = self._append_port_to_config(port, config)
556 if created_ifz:
557 conn_info.append(created_ifz[1])
558 self._pop_last_update_time(config)
559 self._post_netconfig(config)
560
561 self.logger.debug("contrail config after updating interfaces: {}".format(config))
562 self.logger.debug("conn_info after updating interfaces: {}".format(conn_info))
563
564 # Update interfaces list in vpls service
565 for vpls in config.get('apps', {}).get('org.onosproject.vpls', {}).get('vpls', {}).get('vplsList', {}):
566 if vpls['name'] == service_uuid:
567 vpls['interfaces'] = new_interfaces
568 vpls['encapsulation'] = new_encapsulation
569
570 self._pop_last_update_time(config)
571 self._post_netconfig(config)
572 return conn_info
573 except Exception as e:
574 self.logger.error('Exception add connection_service: %s', e)
575 # try to rollback push original config
576 try:
577 self._post_netconfig(config_orig)
578 except Exception as e2:
579 self.logger.error('Exception rolling back to original config: %s', e2)
580 # raise exception
581 if isinstance(e, SdnConnectorError):
582 raise
583 else:
584 raise SdnConnectorError("Exception create_connectivity_service: {}".format(e))
585
586
587 def delete_connectivity_service(self, service_uuid, conn_info=None):
588 """
589 Disconnect multi-site endpoints previously connected
590
591 :param service_uuid: The one returned by create_connectivity_service
592 :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
593 if they do not return None
594 :return: None
595 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
596 """
597 self.logger.debug("delete_connectivity_service uuid: {}".format(service_uuid))
598 try:
599 #TO DO: check if virtual port groups have to be deleted
600 self._delete_virtual_network(self.url, service_uuid)
601 except SdnConnectorError:
602 raise SdnConnectorError('Failed to delete service uuid {}'.format(service_uuid))
603 except Exception as e:
604 self.logger.error('Exception deleting connection_service: %s', e, exc_info=True)
605 raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e)))
606
607
608 if __name__ == '__main__':
609 # Init logger
610 log_format = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
611 log_formatter = logging.Formatter(log_format, datefmt='%Y-%m-%dT%H:%M:%S')
612 handler = logging.StreamHandler()
613 handler.setFormatter(log_formatter)
614 logger = logging.getLogger('openmano.sdnconn.junipercontrail')
615 #logger.setLevel(level=logging.ERROR)
616 logger.setLevel(level=logging.INFO)
617 #logger.setLevel(level=logging.DEBUG)
618 logger.addHandler(handler)
619
620 # Read config
621 with open('test.yaml') as f:
622 config = yaml.safe_load(f.read())
623 wim = {'wim_url': config.pop('wim_url')}
624 wim_account = {'user': config.pop('user'), 'password': config.pop('password')}
625 logger.info('wim: {}, wim_account: {}, config: {}'.format(wim, wim_account, config))
626
627 # Init controller
628 juniper_contrail = JuniperContrail(wim=wim, wim_account=wim_account, config=config, logger=logger)
629
630 # Tests
631 # Generate VNI
632 for i in range(5):
633 vni = juniper_contrail._generate_vni()
634 juniper_contrail.used_vni.add(vni)
635 print(juniper_contrail.used_vni)
636 juniper_contrail.used_vni.remove(1000003)
637 print(juniper_contrail.used_vni)
638 for i in range(2):
639 vni = juniper_contrail._generate_vni()
640 juniper_contrail.used_vni.add(vni)
641 print(juniper_contrail.used_vni)
642 # 0. Check credentials
643 print('0. Check credentials')
644 juniper_contrail.check_credentials()
645
646 underlay_url = juniper_contrail.get_url()
647 overlay_url = juniper_contrail.get_overlay_url()
648 # 1. Read virtual networks from overlay controller
649 print('1. Read virtual networks from overlay controller')
650 try:
651 vnets = juniper_contrail._get_virtual_networks(overlay_url)
652 logger.debug(yaml.safe_dump(vnets, indent=4, default_flow_style=False))
653 print('OK')
654 except Exception as e:
655 logger.error('Exception reading virtual networks from overlay controller: %s', e)
656 print('FAILED')
657
658 # 2. Read virtual networks from underlay controller
659 print('2. Read virtual networks from underlay controller')
660 vnets = juniper_contrail._get_virtual_networks(underlay_url)
661 logger.debug(yaml.safe_dump(vnets, indent=4, default_flow_style=False))
662 print('OK')
663 # 3. Delete virtual networks gerardoX from underlay controller
664 print('3. Delete virtual networks gerardoX from underlay controller')
665 for vn in vnets:
666 name = vn['fq_name'][2]
667 logger.debug('Virtual network: {}'.format(name))
668 for vn in vnets:
669 name = vn['fq_name'][2]
670 if 'gerardo' in name:
671 logger.info('Virtual Network *gerardo*: {}, {}'.format(name,vn['uuid']))
672 if name != "gerardo":
673 print('Deleting Virtual Network: {}, {}'.format(name,vn['uuid']))
674 logger.info('Deleting Virtual Network: {}, {}'.format(name,vn['uuid']))
675 juniper_contrail._delete_virtual_network(underlay_url, vn['uuid'])
676 print('OK')
677 # 4. Get virtual network (gerardo) from underlay controller
678 print('4. Get virtual network (gerardo) from underlay controller')
679 vnet1_info = juniper_contrail._get_virtual_network(underlay_url, 'c5d332f7-420a-4e2b-a7b1-b56a59f20c97')
680 print(yaml.safe_dump(vnet1_info, indent=4, default_flow_style=False))
681 print('OK')
682 # 5. Create virtual network in underlay controller
683 print('5. Create virtual network in underlay controller')
684 myname = 'gerardo4'
685 myvni = 20004
686 vnet2_id, _ = juniper_contrail._create_virtual_network(underlay_url, myname, myvni)
687 vnet2_info = juniper_contrail._get_virtual_network(underlay_url, vnet2_id)
688 print(yaml.safe_dump(vnet2_info, indent=4, default_flow_style=False))
689 print('OK')
690 # 6. Delete virtual network in underlay controller
691 print('6. Delete virtual network in underlay controller')
692 juniper_contrail._delete_virtual_network(underlay_url, vnet2_id)
693 print('OK')
694 # 7. Read previously deleted virtual network in underlay controller
695 print('7. Read previously deleted virtual network in underlay controller')
696 try:
697 vnet2_info = juniper_contrail._get_virtual_network(underlay_url, vnet2_id)
698 if vnet2_info:
699 print('FAILED. Network {} exists'.format(vnet2_id))
700 else:
701 print('OK. Network {} does not exist because it has been deleted'.format(vnet2_id))
702 except Exception as e:
703 logger.info('Exception reading virtual networks from overlay controller: %s', e)
704 exit(0)
705
706
707 #TODO: to be deleted (it comes from ONOS VPLS plugin)
708 service_type = 'ELAN'
709 conn_point_0 = {
710 "service_endpoint_id": "switch1:ifz1",
711 "service_endpoint_encapsulation_type": "dot1q",
712 "service_endpoint_encapsulation_info": {
713 "switch_dpid": "0000000000000011",
714 "switch_port": "1",
715 "vlan": "600"
716 }
717 }
718 conn_point_1 = {
719 "service_endpoint_id": "switch3:ifz1",
720 "service_endpoint_encapsulation_type": "dot1q",
721 "service_endpoint_encapsulation_info": {
722 "switch_dpid": "0000000000000031",
723 "switch_port": "3",
724 "vlan": "600"
725 }
726 }
727 connection_points = [conn_point_0, conn_point_1]
728 # service_uuid, created_items = juniper_contrail.create_connectivity_service(service_type, connection_points)
729 #print(service_uuid)
730 #print(created_items)
731 #sleep(10)
732 #juniper_contrail.delete_connectivity_service("5496dfea-27dc-457d-970d-b82bac266e5c"))
733
734
735 conn_info = None
736 conn_info = ['switch1:ifz1', 'switch3_ifz3']
737 juniper_contrail.delete_connectivity_service("f7afc4de-556d-4b5a-8a12-12b5ef97d269", conn_info)
738
739 conn_point_0 = {
740 "service_endpoint_id": "switch1:ifz1",
741 "service_endpoint_encapsulation_type": "dot1q",
742 "service_endpoint_encapsulation_info": {
743 "switch_dpid": "0000000000000011",
744 "switch_port": "1",
745 "vlan": "500"
746 }
747 }
748 conn_point_2 = {
749 "service_endpoint_id": "switch1:ifz3",
750 "service_endpoint_encapsulation_type": "dot1q",
751 "service_endpoint_encapsulation_info": {
752 "switch_dpid": "0000000000000011",
753 "switch_port": "3",
754 "vlan": "500"
755 }
756 }
757 conn_point_3 = {
758 "service_endpoint_id": "switch3_ifz3",
759 "service_endpoint_encapsulation_type": "dot1q",
760 "service_endpoint_encapsulation_info": {
761 "switch_dpid": "0000000000000033",
762 "switch_port": "3",
763 "vlan": "500"
764 }
765 }
766 new_connection_points = [conn_point_0, conn_point_3]
767 #conn_info = juniper_contrail.edit_connectivity_service("f7afc4de-556d-4b5a-8a12-12b5ef97d269", conn_info, new_connection_points)
768 #print(conn_info)