fix flake8 for SDN-juniper_contrail
[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 yaml
22 import random
23
24 from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
25 # from osm_rosdn_juniper_contrail.rest_lib import ContrailHttp
26 # from osm_rosdn_juniper_contrail.rest_lib import NotFound
27 from osm_rosdn_juniper_contrail.rest_lib import DuplicateFound
28 from osm_rosdn_juniper_contrail.rest_lib import HttpException
29 from osm_rosdn_juniper_contrail.sdn_api import UnderlayApi
30
31
32 class JuniperContrail(SdnConnectorBase):
33 """
34 Juniper Contrail SDN plugin. The plugin interacts with Juniper Contrail Controller,
35 whose API details can be found in these links:
36
37 - https://github.com/tonyliu0592/contrail/wiki/API-Configuration-REST
38 - https://www.juniper.net/documentation/en_US/contrail19/information-products/pathway-pages/api-guide-1910/
39 tutorial_with_rest.html
40 - https://github.com/tonyliu0592/contrail-toolbox/blob/master/sriov/sriov
41 """
42 _WIM_LOGGER = "openmano.sdnconn.junipercontrail"
43
44 def __init__(self, wim, wim_account, config=None, logger=None):
45 """
46
47 :param wim: (dict). Contains among others 'wim_url'
48 :param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
49 'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
50 :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
51 'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
52 'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
53 KEY meaning for WIM meaning for SDN assist
54 -------- -------- --------
55 device_id pop_switch_dpid compute_id
56 device_interface_id pop_switch_port compute_pci_address
57 service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id
58 service_mapping_info wan_service_mapping_info SDN_service_mapping_info
59 contains extra information if needed. Text in Yaml format
60 switch_dpid wan_switch_dpid SDN_switch_dpid
61 switch_port wan_switch_port SDN_switch_port
62 datacenter_id vim_account vim_account
63 id: (internal, do not use)
64 wim_id: (internal, do not use)
65 :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
66 """
67 self.logger = logger or logging.getLogger(self._WIM_LOGGER)
68 self.logger.debug('wim: {}, wim_account: {}, config: {}'.format(wim, wim_account, config))
69 super().__init__(wim, wim_account, config, logger)
70
71 self.user = wim_account.get("user")
72 self.password = wim_account.get("password")
73
74 url = wim.get("wim_url") # underlay url
75 auth_url = None
76 self.project = None
77 self.domain = None
78 self.asn = None
79 self.fabric = None
80 overlay_url = None
81 self.vni_range = None
82 if config:
83 auth_url = config.get("auth_url")
84 self.project = config.get("project")
85 self.domain = config.get("domain")
86 self.asn = config.get("asn")
87 self.fabric = config.get("fabric")
88 self.overlay_url = config.get("overlay_url")
89 self.vni_range = config.get("vni_range")
90
91 if not url:
92 raise SdnConnectorError("'url' must be provided")
93 if not url.startswith("http"):
94 url = "http://" + url
95 if not url.endswith("/"):
96 url = url + "/"
97 self.url = url
98
99 if not self.vni_range:
100 self.vni_range = ['1000001-2000000']
101 self.logger.info("No vni_range was provided. Using ['1000001-2000000']")
102 self.used_vni = set()
103
104 if auth_url:
105 if not auth_url.startswith("http"):
106 auth_url = "http://" + auth_url
107 if not auth_url.endswith("/"):
108 auth_url = auth_url + "/"
109 self.auth_url = auth_url
110
111 if overlay_url:
112 if not overlay_url.startswith("http"):
113 overlay_url = "http://" + overlay_url
114 if not overlay_url.endswith("/"):
115 overlay_url = overlay_url + "/"
116 self.overlay_url = overlay_url
117
118 if not self.project:
119 raise SdnConnectorError("'project' must be provided")
120 if not self.asn:
121 # TODO: Get ASN from controller config; otherwise raise ERROR for the moment
122 raise SdnConnectorError("'asn' was not provided and it was not possible to obtain it")
123 if not self.fabric:
124 # TODO: Get FABRIC from controller config; otherwise raise ERROR for the moment
125 raise SdnConnectorError("'fabric' was not provided and was not possible to obtain it")
126 if not self.domain:
127 self.domain = 'default-domain'
128 self.logger.info("No domain was provided. Using 'default-domain'")
129
130 underlay_api_config = {
131 "auth_url": self.auth_url,
132 "project": self.project,
133 "domain": self.domain,
134 "asn": self.asn,
135 "fabric": self.fabric
136 }
137 self.underlay_api = UnderlayApi(url, underlay_api_config, user=self.user, password=self.password, logger=logger)
138
139 self._max_duplicate_retry = 2
140 self.logger.info("Juniper Contrail Connector Initialized.")
141
142 def _generate_vni(self):
143 """
144 Method to get unused VxLAN Network Identifier (VNI)
145 Args:
146 None
147 Returns:
148 VNI
149 """
150 # find unused VLAN ID
151 for vlanID_range in self.vni_range:
152 try:
153 start_vni, end_vni = map(int, vlanID_range.replace(" ", "").split("-"))
154 for i in range(start_vni, end_vni + 1):
155 vni = random.randrange(start_vni, end_vni, 1)
156 if vni not in self.used_vni:
157 return vni
158 except Exception as exp:
159 raise SdnConnectorError("Exception {} occurred while searching a free VNI.".format(exp))
160 else:
161 raise SdnConnectorError("Unable to create the virtual network."
162 " All VNI in VNI range {} are in use.".format(self.vni_range))
163
164 # Aux functions for testing
165 def get_url(self):
166 return self.url
167
168 def get_overlay_url(self):
169 return self.overlay_url
170
171 def _create_port(self, switch_id, switch_port, network, vlan):
172 """
173 1 - Look for virtual port groups for provided switch_id, switch_port using name
174 2 - It the virtual port group does not exist, create it
175 3 - Create virtual machine interface for the indicated network and vlan
176 """
177 self.logger.debug("create_port: switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
178 switch_id, switch_port, network, vlan))
179
180 # 1 - Check if the vpg exists
181 vpg_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
182 vpg = self.underlay_api.get_vpg_by_name(vpg_name)
183 if not vpg:
184 # 2 - If it does not exist create it
185 vpg_id, _ = self.underlay_api.create_vpg(switch_id, switch_port)
186 else:
187 # Assign vpg_id from vpg
188 vpg_id = vpg.get("uuid")
189
190 # 3 - Check if the vmi alreaady exists
191 vmi_id, _ = self.underlay_api.create_vmi(switch_id, switch_port, network, vlan)
192 self.logger.debug("port created")
193
194 return vpg_id, vmi_id
195
196 def _delete_port(self, switch_id, switch_port, vlan):
197 self.logger.debug("delete port, switch_id: {}, switch_port: {}, vlan: {}".format(switch_id, switch_port, vlan))
198
199 vpg_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
200 vmi_name = self.underlay_api.get_vmi_name(switch_id, switch_port, vlan)
201
202 # 1 - Obtain vpg by id (if not vpg_id must have been error creating ig, nothing to be done)
203 vpg_fqdn = ["default-global-system-config", self.fabric, vpg_name]
204 vpg = self.underlay_api.get_by_fq_name("virtual-port-group", vpg_fqdn)
205 if not vpg:
206 self.logger.warning("vpg: {} to be deleted not found".format(vpg_name))
207 else:
208 # 2 - Get vmi interfaces from vpg
209 vmi_list = vpg.get("virtual_machine_interface_refs")
210 if not vmi_list:
211 # must have been an error during port creation when vmi is created
212 # may happen if there has been an error during creation
213 self.logger.warning("vpg: {} has not vmi, will delete nothing".format(vpg))
214 else:
215 num_vmis = len(vmi_list)
216 for vmi in vmi_list:
217 fqdn = vmi.get("to")
218 # check by name
219 if fqdn[2] == vmi_name:
220 self.underlay_api.unref_vmi_vpg(vpg.get("uuid"), vmi.get("uuid"), fqdn)
221 self.underlay_api.delete_vmi(vmi.get("uuid"))
222 num_vmis = num_vmis - 1
223
224 # 3 - If there are no more vmi delete the vpg
225 if not vmi_list or num_vmis == 0:
226 self.underlay_api.delete_vpg(vpg.get("uuid"))
227
228 def check_credentials(self):
229 """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
230 user (wim_account.user), and password (wim_account.password)
231
232 Raises:
233 SdnConnectorError: Issues regarding authorization, access to
234 external URLs, etc are detected.
235 """
236 self.logger.debug("")
237 try:
238 resp = self.underlay_api.check_auth()
239 if not resp:
240 raise SdnConnectorError('Empty response')
241 except Exception as e:
242 self.logger.error('Error checking credentials')
243 raise SdnConnectorError('Error checking credentials: {}'.format(str(e)))
244
245 def get_connectivity_service_status(self, service_uuid, conn_info=None):
246 """Monitor the status of the connectivity service established
247
248 Arguments:
249 service_uuid (str): UUID of the connectivity service
250 conn_info (dict or None): Information returned by the connector
251 during the service creation/edition and subsequently stored in
252 the database.
253
254 Returns:
255 dict: JSON/YAML-serializable dict that contains a mandatory key
256 ``sdn_status`` associated with one of the following values::
257
258 {'sdn_status': 'ACTIVE'}
259 # The service is up and running.
260
261 {'sdn_status': 'INACTIVE'}
262 # The service was created, but the connector
263 # cannot determine yet if connectivity exists
264 # (ideally, the caller needs to wait and check again).
265
266 {'sdn_status': 'DOWN'}
267 # Connection was previously established,
268 # but an error/failure was detected.
269
270 {'sdn_status': 'ERROR'}
271 # An error occurred when trying to create the service/
272 # establish the connectivity.
273
274 {'sdn_status': 'BUILD'}
275 # Still trying to create the service, the caller
276 # needs to wait and check again.
277
278 Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
279 keys can be used to provide additional status explanation or
280 new information available for the connectivity service.
281 """
282 self.logger.debug("")
283 try:
284 resp = self.underlay_api.get_virtual_network(service_uuid)
285 if not resp:
286 raise SdnConnectorError('Empty response')
287 if resp:
288 vnet_info = resp
289
290 # Check if conn_info reports error
291 if conn_info.get("sdn_status") == "ERROR":
292 return {'sdn_status': 'ERROR', 'sdn_info': conn_info}
293 else:
294 return {'sdn_status': 'ACTIVE', 'sdn_info': vnet_info}
295 else:
296 return {'sdn_status': 'ERROR', 'sdn_info': 'not found'}
297 except SdnConnectorError:
298 raise
299 except HttpException as e:
300 self.logger.error("Error getting connectivity service: {}".format(e))
301 raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e)))
302 except Exception as e:
303 self.logger.error('Exception getting connectivity service info: %s', e, exc_info=True)
304 return {'sdn_status': 'ERROR', 'error_msg': str(e)}
305
306 def create_connectivity_service(self, service_type, connection_points, **kwargs):
307 """
308 Establish SDN/WAN connectivity between the endpoints
309 :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
310 :param connection_points: (list): each point corresponds to
311 an entry point to be connected. For WIM: from the DC to the transport network.
312 For SDN: Compute/PCI to the transport network. One
313 connection point serves to identify the specific access and
314 some other service parameters, such as encapsulation type.
315 Each item of the list is a dict with:
316 "service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__)
317 In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
318 it will contain the string "device_id:device_interface_id"
319 "service_endpoint_encapsulation_type": None, "dot1q", ...
320 "service_endpoint_encapsulation_info": (dict) with:
321 "vlan": ..., (int, present if encapsulation is dot1q)
322 "vni": ... (int, present if encapsulation is vxlan),
323 "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
324 "mac": ...
325 "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
326 "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
327 "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
328 "switch_port": ... present if mapping has been found for this device_id,device_interface_id
329 "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
330 :param kwargs: For future versions:
331 bandwidth (int): value in kilobytes
332 latency (int): value in milliseconds
333 Other QoS might be passed as keyword arguments.
334 :return: tuple: ``(service_id, conn_info)`` containing:
335 - *service_uuid* (str): UUID of the established connectivity service
336 - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
337 This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
338 **MUST** be JSON/YAML-serializable (plain data structures).
339 :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
340 Provide the parameter http_code
341 """
342 # Step 1. Check in the overlay controller the virtual network created by the VIM
343 # Best option: get network id of the VIM as param (if the VIM already created the network),
344 # and do a request to the controller of the virtual networks whose VIM network id is the provided
345 # Next best option: obtain the network by doing a request to the controller
346 # of the virtual networks using the VLAN ID of any service endpoint.
347 # 1.1 Read VLAN ID from a service endpoint
348 # 1.2 Look for virtual networks with "Provider Network" including a VLAN ID.
349 # 1.3 If more than one, ERROR
350 # Step 2. Modify the existing virtual network in the overlay controller
351 # 2.1 Add VNI (VxLAN Network Identifier - one free from the provided range)
352 # 2.2 Add RouteTarget (RT) ('ASN:VNI', ASN = Autonomous System Number, provided as param or read from
353 # controller config)
354 # Step 3. Create a virtual network in the underlay controller
355 # 3.1 Create virtual network (name, VNI, RT)
356 # If the network already existed in the overlay controller, we should use the same name
357 # name = 'osm-plugin-' + overlay_name
358 # Else:
359 # name = 'osm-plugin-' + VNI
360 self.logger.info("create_connectivity_service, service_type: {}, connection_points: {}".
361 format(service_type, connection_points))
362 if service_type.lower() != 'elan':
363 raise SdnConnectorError('Only ELAN network type is supported by Juniper Contrail.')
364
365 try:
366 # Initialize data
367 conn_info = None
368
369 # 1 - Filter connection_points (transform cp to a dictionary with no duplicates)
370 # This data will be returned even if no cp can be created if something is created
371 work_cps = {}
372 for cp in connection_points:
373 switch_id = cp.get("service_endpoint_encapsulation_info").get("switch_dpid")
374 switch_port = cp.get("service_endpoint_encapsulation_info").get("switch_port")
375 service_endpoint_id = cp.get("service_endpoint_id")
376 cp_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
377 add_cp = work_cps.get(cp_name)
378 if not add_cp:
379 # check cp has vlan
380 vlan = cp.get("service_endpoint_encapsulation_info").get("vlan")
381 if vlan:
382 # add cp to dict
383 service_endpoint_ids = []
384 service_endpoint_ids.append(service_endpoint_id)
385 add_cp = {"service_endpoint_ids": service_endpoint_ids,
386 "switch_dpid": switch_id,
387 "switch_port": switch_port,
388 "vlan": vlan}
389 work_cps[cp_name] = add_cp
390 else:
391 self.logger.warning("cp service_endpoint_id : {} has no vlan, ignore".format(
392 service_endpoint_id))
393 else:
394 # add service_endpoint_id to list
395 service_endpoint_ids = add_cp["service_endpoint_ids"]
396 service_endpoint_ids.append(service_endpoint_id)
397
398 # 2 - Obtain free VNI
399 vni = self._generate_vni()
400 self.logger.debug("VNI: {}".format(vni))
401
402 # 3 - Create virtual network (name, VNI, RT), by the moment the name will use VNI
403 retry = 0
404 while retry < self._max_duplicate_retry:
405 try:
406 vnet_name = 'osm-plugin-' + str(vni)
407 vnet_id, _ = self.underlay_api.create_virtual_network(vnet_name, vni)
408 self.used_vni.add(vni)
409 break
410 except DuplicateFound as e:
411 self.logger.debug("Duplicate error for vnet_name: {}".format(vnet_name))
412 self.used_vni.add(vni)
413 retry += 1
414 if retry >= self._max_duplicate_retry:
415 raise e
416 else:
417 # Try to obtain a new vni
418 vni = self._generate_vni()
419 continue
420 conn_info = {
421 "vnet": {
422 "uuid": vnet_id,
423 "name": vnet_name
424 },
425 "connection_points": work_cps # dict with port_name as key
426 }
427
428 # 4 - Create a port for each endpoint
429 for cp in work_cps.values():
430 switch_id = cp.get("switch_dpid")
431 switch_port = cp.get("switch_port")
432 vlan = cp.get("vlan")
433 vpg_id, vmi_id = self._create_port(switch_id, switch_port, vnet_name, vlan)
434 cp["vpg_id"] = vpg_id
435 cp["vmi_id"] = vmi_id
436
437 self.logger.info("created connectivity service, uuid: {}, name: {}".format(vnet_id, vnet_name))
438 return vnet_id, conn_info
439
440 except Exception as e:
441 # Log error
442 if isinstance(e, SdnConnectorError) or isinstance(e, HttpException):
443 self.logger.error("Error creating connectivity service: {}".format(e))
444 else:
445 self.logger.error("Error creating connectivity service: {}".format(e), exc_info=True)
446
447 # If nothing is created raise error else return what has been created and mask as error
448 if not conn_info:
449 raise SdnConnectorError("Exception create connectivity service: {}".format(str(e)))
450 else:
451 conn_info["sdn_status"] = "ERROR"
452 conn_info["sdn_info"] = repr(e)
453 # iterate over not added connection_points and add but marking them as error
454 for cp in work_cps.values():
455 if not cp.get("vmi_id") or not cp.get("vpg_id"):
456 cp["sdn_status"] = "ERROR"
457 return vnet_id, conn_info
458
459 def delete_connectivity_service(self, service_uuid, conn_info=None):
460 """
461 Disconnect multi-site endpoints previously connected
462
463 :param service_uuid: The one returned by create_connectivity_service
464 :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
465 if they do not return None
466 :return: None
467 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
468 """
469 self.logger.info("delete_connectivity_service vnet_name: {}, connection_points: {}".
470 format(service_uuid, conn_info))
471
472 try:
473 vnet_uuid = service_uuid
474 # vnet_name = conn_info["vnet"]["name"] # always should exist as the network is the first thing created
475 work_cps = conn_info["connection_points"]
476
477 # 1: For each connection point delete vlan from vpg and it is is the
478 # last one, delete vpg
479 for cp in work_cps.values():
480 self._delete_port(cp.get("switch_dpid"), cp.get("switch_port"), cp.get("vlan"))
481
482 # 2: Delete vnet
483 self.underlay_api.delete_virtual_network(vnet_uuid)
484 self.logger.info("deleted connectivity_service vnet_uuid: {}, connection_points: {}".
485 format(service_uuid, conn_info))
486 except SdnConnectorError:
487 raise
488 except HttpException as e:
489 self.logger.error("Error deleting connectivity service: {}".format(e))
490 raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e)))
491 except Exception as e:
492 self.logger.error("Error deleting connectivity service: {}".format(e), exc_info=True)
493 raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e)))
494
495 def edit_connectivity_service(self, service_uuid, conn_info=None, connection_points=None, **kwargs):
496 """ Change an existing connectivity service.
497
498 This method's arguments and return value follow the same convention as
499 :meth:`~.create_connectivity_service`.
500
501 :param service_uuid: UUID of the connectivity service.
502 :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
503 or edit_connectivity_service
504 :param connection_points: (list): If provided, the old list of connection points will be replaced.
505 :param kwargs: Same meaning that create_connectivity_service
506 :return: dict or None: Information to be updated and stored at the database.
507 When ``None`` is returned, no information should be changed.
508 When an empty dict is returned, the database record will be deleted.
509 **MUST** be JSON/YAML-serializable (plain data structures).
510 Raises:
511 SdnConnectorException: In case of error.
512 """
513 # 0 - Check if there are connection_points marked as error and delete them
514 # 1 - Compare conn_info (old connection points) and connection_points (new ones to be applied):
515 # Obtain list of connection points to be added and to be deleted
516 # Obtain vlan and check it has not changed
517 # 2 - Obtain network: Check vnet exists and obtain name
518 # 3 - Delete unnecesary ports
519 # 4 - Add new ports
520 self.logger.info("edit connectivity service, service_uuid: {}, conn_info: {}, "
521 "connection points: {} ".format(service_uuid, conn_info, connection_points))
522
523 # conn_info should always exist and have connection_points and vnet elements
524 old_cp = conn_info.get("connection_points", {})
525
526 # Check if an element of old_cp is marked as error, in case it is delete it
527 # Not return a new conn_info in this case because it is only partial information
528 # Current conn_info already marks ports as error
529 try:
530 deleted_ports = []
531 for cp in old_cp.values():
532 if cp.get("sdn_status") == "ERROR":
533 switch_id = cp.get("switch_dpid")
534 switch_port = cp.get("switch_port")
535 old_vlan = cp.get("vlan")
536 self._delete_port(switch_id, switch_port, old_vlan)
537 deleted_ports.append(self.underlay_api.get_vpg_name(switch_id, switch_port))
538
539 for port in deleted_ports:
540 del old_cp[port]
541
542 # Delete sdn_status and sdn_info if exists (possibly marked as error)
543 if conn_info.get("vnet", {}).get("sdn_status"):
544 del conn_info["vnet"]["sdn_status"]
545 except HttpException as e:
546 self.logger.error("Error trying to delete old ports marked as error: {}".format(e))
547 raise SdnConnectorError(e)
548 except SdnConnectorError as e:
549 self.logger.error("Error trying to delete old ports marked as error: {}".format(e))
550 raise
551 except Exception as e:
552 self.logger.error("Error trying to delete old ports marked as error: {}".format(e), exc_info=True)
553 raise SdnConnectorError("Error trying to delete old ports marked as error: {}".format(e))
554
555 if connection_points:
556
557 # Check and obtain what should be added and deleted, if there is an error here raise an exception
558 try:
559 work_cps = {}
560 for cp in connection_points:
561 switch_id = cp.get("service_endpoint_encapsulation_info").get("switch_dpid")
562 switch_port = cp.get("service_endpoint_encapsulation_info").get("switch_port")
563 service_endpoint_id = cp.get("service_endpoint_id")
564 cp_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
565 add_cp = work_cps.get(cp_name)
566 if not add_cp:
567 # add cp to dict
568 # check cp has vlan
569 vlan = cp.get("service_endpoint_encapsulation_info").get("vlan")
570 if vlan:
571 service_endpoint_ids = []
572 service_endpoint_ids.append(service_endpoint_id)
573 add_cp = {"service_endpoint_ids": service_endpoint_ids,
574 "switch_dpid": switch_id,
575 "switch_port": switch_port,
576 "vlan": vlan}
577 work_cps[cp_name] = add_cp
578 else:
579 self.logger.warning("cp service_endpoint_id : {} has no vlan, ignore".
580 format(service_endpoint_id))
581 else:
582 # add service_endpoint_id to list
583 service_endpoint_ids = add_cp["service_endpoint_ids"]
584 service_endpoint_ids.append(service_endpoint_id)
585
586 old_port_list = list(old_cp.keys())
587 port_list = list(work_cps.keys())
588 to_delete_ports = list(set(old_port_list) - set(port_list))
589 to_add_ports = list(set(port_list) - set(old_port_list))
590 self.logger.debug("ports to delete: {}".format(to_delete_ports))
591 self.logger.debug("ports to add: {}".format(to_add_ports))
592
593 # Obtain network (check it is correctly created)
594 vnet = self.underlay_api.get_virtual_network(service_uuid)
595 if vnet:
596 vnet_name = vnet["name"]
597 else:
598 raise SdnConnectorError("vnet uuid: {} not found".format(service_uuid))
599
600 except SdnConnectorError:
601 raise
602 except Exception as e:
603 self.logger.error("Error edit connectivity service: {}".format(e), exc_info=True)
604 raise SdnConnectorError("Exception edit connectivity service: {}".format(str(e)))
605
606 # Delete unneeded ports and add new ones: if there is an error return conn_info
607 try:
608 # Connection points returned in con_info should reflect what has (and should as ERROR) be done
609 # Start with old cp dictionary and modify it as we work
610 conn_info_cp = old_cp
611
612 # Delete unneeded ports
613 deleted_ports = []
614 for port_name in conn_info_cp.keys():
615 if port_name in to_delete_ports:
616 cp = conn_info_cp[port_name]
617 switch_id = cp.get("switch_dpid")
618 switch_port = cp.get("switch_port")
619 self.logger.debug("delete port switch_id={}, switch_port={}".format(switch_id, switch_port))
620 self._delete_port(switch_id, switch_port, vlan)
621 deleted_ports.append(port_name)
622
623 # Delete ports
624 for port_name in deleted_ports:
625 del conn_info_cp[port_name]
626
627 # Add needed ports
628 for port_name, cp in work_cps.items():
629 if port_name in to_add_ports:
630 switch_id = cp.get("switch_dpid")
631 switch_port = cp.get("switch_port")
632 vlan = cp.get("vlan")
633 self.logger.debug("add port switch_id={}, switch_port={}".format(switch_id, switch_port))
634 vpg_id, vmi_id = self._create_port(switch_id, switch_port, vnet_name, vlan)
635 cp_added = cp.copy()
636 cp_added["vpg_id"] = vpg_id
637 cp_added["vmi_id"] = vmi_id
638 conn_info_cp[port_name] = cp_added
639 # replace endpoints in case they have changed
640 conn_info_cp[port_name]["service_endpoint_ids"] = cp["service_endpoint_ids"]
641
642 conn_info["connection_points"] = conn_info_cp
643 return conn_info
644
645 except Exception as e:
646 # Log error
647 if isinstance(e, SdnConnectorError) or isinstance(e, HttpException):
648 self.logger.error("Error edit connectivity service: {}".format(e), exc_info=True)
649 else:
650 self.logger.error("Error edit connectivity service: {}".format(e))
651
652 # There has been an error mount conn_info_cp marking as error cp that should
653 # have been deleted but have not or should have been added
654 for port_name, cp in conn_info_cp.items():
655 if port_name in to_delete_ports:
656 cp["sdn_status"] = "ERROR"
657
658 for port_name, cp in work_cps.items():
659 curr_cp = conn_info_cp.get(port_name)
660 if not curr_cp:
661 cp_error = work_cps.get(port_name).copy()
662 cp_error["sdn_status"] = "ERROR"
663 conn_info_cp[port_name] = cp_error
664 conn_info_cp[port_name]["service_endpoint_ids"] = cp["service_endpoint_ids"]
665
666 conn_info["sdn_status"] = "ERROR"
667 conn_info["sdn_info"] = repr(e)
668 conn_info["connection_points"] = conn_info_cp
669 return conn_info
670
671 else:
672 # Connection points have not changed, so do nothing
673 self.logger.info("no new connection_points provided, nothing to be done")
674 return
675
676
677 if __name__ == '__main__':
678 # Init logger
679 log_format = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
680 log_formatter = logging.Formatter(log_format, datefmt='%Y-%m-%dT%H:%M:%S')
681 handler = logging.StreamHandler()
682 handler.setFormatter(log_formatter)
683 logger = logging.getLogger('openmano.sdnconn.junipercontrail')
684 # logger.setLevel(level=logging.ERROR)
685 # logger.setLevel(level=logging.INFO)
686 logger.setLevel(level=logging.DEBUG)
687 logger.addHandler(handler)
688
689 # Read config
690 with open('test.yaml') as f:
691 config = yaml.safe_load(f.read())
692 wim = {'wim_url': config.pop('wim_url')}
693 wim_account = {'user': config.pop('user'), 'password': config.pop('password')}
694 logger.info('wim: {}, wim_account: {}, config: {}'.format(wim, wim_account, config))
695
696 # Init controller
697 juniper_contrail = JuniperContrail(wim=wim, wim_account=wim_account, config=config, logger=logger)
698
699 # Tests
700 # Generate VNI
701 for i in range(5):
702 vni = juniper_contrail._generate_vni()
703 juniper_contrail.used_vni.add(vni)
704 print(juniper_contrail.used_vni)
705 # juniper_contrail.used_vni.remove(1000003)
706 print(juniper_contrail.used_vni)
707 for i in range(2):
708 vni = juniper_contrail._generate_vni()
709 juniper_contrail.used_vni.add(vni)
710 print(juniper_contrail.used_vni)
711
712 # 0. Check credentials
713 print('0. Check credentials')
714 # juniper_contrail.check_credentials()
715
716 # 1 - Create and delete connectivity service
717 conn_point_0 = {
718 "service_endpoint_id": "0000:83:11.4",
719 "service_endpoint_encapsulation_type": "dot1q",
720 "service_endpoint_encapsulation_info": {
721 "switch_dpid": "LEAF-1",
722 "switch_port": "xe-0/0/17",
723 "vlan": "501"
724 }
725 }
726 conn_point_1 = {
727 "service_endpoint_id": "0000:81:10.3",
728 "service_endpoint_encapsulation_type": "dot1q",
729 "service_endpoint_encapsulation_info": {
730 "switch_dpid": "LEAF-2",
731 "switch_port": "xe-0/0/16",
732 "vlan": "501"
733 }
734 }
735 conn_point_2 = {
736 "service_endpoint_id": "0000:08:11.7",
737 "service_endpoint_encapsulation_type": "dot1q",
738 "service_endpoint_encapsulation_info": {
739 "switch_dpid": "LEAF-2",
740 "switch_port": "xe-0/0/16",
741 "vlan": "502"
742 }
743 }
744 conn_point_3 = {
745 "service_endpoint_id": "0000:83:10.4",
746 "service_endpoint_encapsulation_type": "dot1q",
747 "service_endpoint_encapsulation_info": {
748 "switch_dpid": "LEAF-1",
749 "switch_port": "xe-0/0/17",
750 "vlan": "502"
751 }
752 }
753
754 # 1 - Define connection points
755 logger.debug("create first connection service")
756 print("Create connectivity service")
757 connection_points = [conn_point_0, conn_point_1]
758 service_id, conn_info = juniper_contrail.create_connectivity_service("ELAN", connection_points)
759 logger.info("Created connectivity service 1")
760 logger.info(service_id)
761 logger.info(yaml.safe_dump(conn_info, indent=4, default_flow_style=False))
762
763 logger.debug("create second connection service")
764 print("Create connectivity service")
765 connection_points = [conn_point_2, conn_point_3]
766 service_id2, conn_info2 = juniper_contrail.create_connectivity_service("ELAN", connection_points)
767 logger.info("Created connectivity service 2")
768 logger.info(service_id2)
769 logger.info(yaml.safe_dump(conn_info2, indent=4, default_flow_style=False))
770
771 logger.debug("Delete connectivity service 1")
772 juniper_contrail.delete_connectivity_service(service_id, conn_info)
773 logger.debug("Delete Ok")
774
775 logger.debug("Delete connectivity service 2")
776 juniper_contrail.delete_connectivity_service(service_id2, conn_info2)
777 logger.debug("Delete Ok")