1 # -*- coding: utf-8 -*-
3 # Copyright 2020 ETSI OSM
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
11 # http://www.apache.org/licenses/LICENSE-2.0
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
24 from osm_ro_plugin
.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
32 class JuniperContrail(SdnConnectorBase
):
34 Juniper Contrail SDN plugin. The plugin interacts with Juniper Contrail Controller,
35 whose API details can be found in these links:
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
42 _WIM_LOGGER
= "ro.sdn.junipercontrail"
44 def __init__(self
, wim
, wim_account
, config
=None, logger
=None):
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 'ro.sdn.sdnconn' is used.
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
)
71 self
.user
= wim_account
.get("user")
72 self
.password
= wim_account
.get("password")
74 url
= wim
.get("wim_url") # underlay url
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")
92 raise SdnConnectorError("'url' must be provided")
93 if not url
.startswith("http"):
95 if not url
.endswith("/"):
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()
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
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
119 raise SdnConnectorError("'project' must be provided")
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")
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")
127 self
.domain
= 'default-domain'
128 self
.logger
.info("No domain was provided. Using 'default-domain'")
130 underlay_api_config
= {
131 "auth_url": self
.auth_url
,
132 "project": self
.project
,
133 "domain": self
.domain
,
135 "fabric": self
.fabric
137 self
.underlay_api
= UnderlayApi(url
, underlay_api_config
, user
=self
.user
, password
=self
.password
, logger
=logger
)
139 self
._max
_duplicate
_retry
= 2
140 self
.logger
.info("Juniper Contrail Connector Initialized.")
142 def _generate_vni(self
):
144 Method to get unused VxLAN Network Identifier (VNI)
150 # find unused VLAN ID
151 for vlanID_range
in self
.vni_range
:
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
:
158 except Exception as exp
:
159 raise SdnConnectorError("Exception {} occurred while searching a free VNI.".format(exp
))
161 raise SdnConnectorError("Unable to create the virtual network."
162 " All VNI in VNI range {} are in use.".format(self
.vni_range
))
164 # Aux functions for testing
168 def get_overlay_url(self
):
169 return self
.overlay_url
171 def _create_port(self
, switch_id
, switch_port
, network
, vlan
):
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
177 self
.logger
.debug("create_port: switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
178 switch_id
, switch_port
, network
, vlan
))
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
)
184 # 2 - If it does not exist create it
185 vpg_id
, _
= self
.underlay_api
.create_vpg(switch_id
, switch_port
)
187 # Assign vpg_id from vpg
188 vpg_id
= vpg
.get("uuid")
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")
194 return vpg_id
, vmi_id
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
))
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
)
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
)
206 self
.logger
.warning("vpg: {} to be deleted not found".format(vpg_name
))
208 # 2 - Get vmi interfaces from vpg
209 vmi_list
= vpg
.get("virtual_machine_interface_refs")
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
))
215 num_vmis
= len(vmi_list
)
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
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"))
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)
233 SdnConnectorError: Issues regarding authorization, access to
234 external URLs, etc are detected.
236 self
.logger
.debug("")
238 resp
= self
.underlay_api
.check_auth()
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
)))
245 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
246 """Monitor the status of the connectivity service established
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
255 dict: JSON/YAML-serializable dict that contains a mandatory key
256 ``sdn_status`` associated with one of the following values::
258 {'sdn_status': 'ACTIVE'}
259 # The service is up and running.
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).
266 {'sdn_status': 'DOWN'}
267 # Connection was previously established,
268 # but an error/failure was detected.
270 {'sdn_status': 'ERROR'}
271 # An error occurred when trying to create the service/
272 # establish the connectivity.
274 {'sdn_status': 'BUILD'}
275 # Still trying to create the service, the caller
276 # needs to wait and check again.
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.
282 self
.logger
.debug("")
284 resp
= self
.underlay_api
.get_virtual_network(service_uuid
)
286 raise SdnConnectorError('Empty response')
290 # Check if conn_info reports error
291 if conn_info
.get("sdn_status") == "ERROR":
292 return {'sdn_status': 'ERROR', 'sdn_info': conn_info
}
294 return {'sdn_status': 'ACTIVE', 'sdn_info': vnet_info
}
296 return {'sdn_status': 'ERROR', 'sdn_info': 'not found'}
297 except SdnConnectorError
:
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
)}
306 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
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)
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
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
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
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.')
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
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
)
380 vlan
= cp
.get("service_endpoint_encapsulation_info").get("vlan")
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
,
389 work_cps
[cp_name
] = add_cp
391 self
.logger
.warning("cp service_endpoint_id : {} has no vlan, ignore".format(
392 service_endpoint_id
))
394 # add service_endpoint_id to list
395 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
396 service_endpoint_ids
.append(service_endpoint_id
)
398 # 2 - Obtain free VNI
399 vni
= self
._generate
_vni
()
400 self
.logger
.debug("VNI: {}".format(vni
))
402 # 3 - Create virtual network (name, VNI, RT), by the moment the name will use VNI
404 while retry
< self
._max
_duplicate
_retry
:
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
)
410 except DuplicateFound
as e
:
411 self
.logger
.debug("Duplicate error for vnet_name: {}".format(vnet_name
))
412 self
.used_vni
.add(vni
)
414 if retry
>= self
._max
_duplicate
_retry
:
417 # Try to obtain a new vni
418 vni
= self
._generate
_vni
()
425 "connection_points": work_cps
# dict with port_name as key
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
437 self
.logger
.info("created connectivity service, uuid: {}, name: {}".format(vnet_id
, vnet_name
))
438 return vnet_id
, conn_info
440 except Exception as e
:
442 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
443 self
.logger
.error("Error creating connectivity service: {}".format(e
))
445 self
.logger
.error("Error creating connectivity service: {}".format(e
), exc_info
=True)
447 # If nothing is created raise error else return what has been created and mask as error
449 raise SdnConnectorError("Exception create connectivity service: {}".format(str(e
)))
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
459 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
461 Disconnect multi-site endpoints previously connected
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
467 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
469 self
.logger
.info("delete_connectivity_service vnet_name: {}, connection_points: {}".
470 format(service_uuid
, conn_info
))
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"]
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"))
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
:
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
)))
495 def edit_connectivity_service(self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
):
496 """ Change an existing connectivity service.
498 This method's arguments and return value follow the same convention as
499 :meth:`~.create_connectivity_service`.
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).
511 SdnConnectorException: In case of error.
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
520 self
.logger
.info("edit connectivity service, service_uuid: {}, conn_info: {}, "
521 "connection points: {} ".format(service_uuid
, conn_info
, connection_points
))
523 # conn_info should always exist and have connection_points and vnet elements
524 old_cp
= conn_info
.get("connection_points", {})
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
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
))
539 for port
in deleted_ports
:
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
))
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
))
555 if connection_points
:
557 # Check and obtain what should be added and deleted, if there is an error here raise an exception
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
)
569 vlan
= cp
.get("service_endpoint_encapsulation_info").get("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
,
577 work_cps
[cp_name
] = add_cp
579 self
.logger
.warning("cp service_endpoint_id : {} has no vlan, ignore".
580 format(service_endpoint_id
))
582 # add service_endpoint_id to list
583 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
584 service_endpoint_ids
.append(service_endpoint_id
)
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
))
593 # Obtain network (check it is correctly created)
594 vnet
= self
.underlay_api
.get_virtual_network(service_uuid
)
596 vnet_name
= vnet
["name"]
598 raise SdnConnectorError("vnet uuid: {} not found".format(service_uuid
))
600 except SdnConnectorError
:
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
)))
606 # Delete unneeded ports and add new ones: if there is an error return conn_info
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
612 # Delete unneeded 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
)
624 for port_name
in deleted_ports
:
625 del conn_info_cp
[port_name
]
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
)
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"]
642 conn_info
["connection_points"] = conn_info_cp
645 except Exception as e
:
647 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
648 self
.logger
.error("Error edit connectivity service: {}".format(e
), exc_info
=True)
650 self
.logger
.error("Error edit connectivity service: {}".format(e
))
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"
658 for port_name
, cp
in work_cps
.items():
659 curr_cp
= conn_info_cp
.get(port_name
)
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"]
666 conn_info
["sdn_status"] = "ERROR"
667 conn_info
["sdn_info"] = repr(e
)
668 conn_info
["connection_points"] = conn_info_cp
672 # Connection points have not changed, so do nothing
673 self
.logger
.info("no new connection_points provided, nothing to be done")
677 if __name__
== '__main__':
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('ro.sdn.junipercontrail')
684 # logger.setLevel(level=logging.ERROR)
685 # logger.setLevel(level=logging.INFO)
686 logger
.setLevel(level
=logging
.DEBUG
)
687 logger
.addHandler(handler
)
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
))
697 juniper_contrail
= JuniperContrail(wim
=wim
, wim_account
=wim_account
, config
=config
, logger
=logger
)
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
)
708 vni
= juniper_contrail
._generate
_vni
()
709 juniper_contrail
.used_vni
.add(vni
)
710 print(juniper_contrail
.used_vni
)
712 # 0. Check credentials
713 print('0. Check credentials')
714 # juniper_contrail.check_credentials()
716 # 1 - Create and delete connectivity service
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",
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",
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",
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",
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))
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))
771 logger
.debug("Delete connectivity service 1")
772 juniper_contrail
.delete_connectivity_service(service_id
, conn_info
)
773 logger
.debug("Delete Ok")
775 logger
.debug("Delete connectivity service 2")
776 juniper_contrail
.delete_connectivity_service(service_id2
, conn_info2
)
777 logger
.debug("Delete Ok")