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
23 from osm_ro_plugin
.sdnconn
import SdnConnectorBase
, SdnConnectorError
24 from osm_rosdn_juniper_contrail
.rest_lib
import DuplicateFound
25 from osm_rosdn_juniper_contrail
.rest_lib
import HttpException
26 from osm_rosdn_juniper_contrail
.sdn_api
import UnderlayApi
30 class JuniperContrail(SdnConnectorBase
):
32 Juniper Contrail SDN plugin. The plugin interacts with Juniper Contrail Controller,
33 whose API details can be found in these links:
35 - https://github.com/tonyliu0592/contrail/wiki/API-Configuration-REST
36 - https://www.juniper.net/documentation/en_US/contrail19/information-products/pathway-pages/api-guide-1910/
37 tutorial_with_rest.html
38 - https://github.com/tonyliu0592/contrail-toolbox/blob/master/sriov/sriov
41 _WIM_LOGGER
= "ro.sdn.junipercontrail"
43 def __init__(self
, wim
, wim_account
, config
=None, logger
=None):
46 :param wim: (dict). Contains among others 'wim_url'
47 :param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
48 'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
49 :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
50 'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
51 'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
52 KEY meaning for WIM meaning for SDN assist
53 -------- -------- --------
54 device_id pop_switch_dpid compute_id
55 device_interface_id pop_switch_port compute_pci_address
56 service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id
57 service_mapping_info wan_service_mapping_info SDN_service_mapping_info
58 contains extra information if needed. Text in Yaml format
59 switch_dpid wan_switch_dpid SDN_switch_dpid
60 switch_port wan_switch_port SDN_switch_port
61 datacenter_id vim_account vim_account
62 id: (internal, do not use)
63 wim_id: (internal, do not use)
64 :param logger (logging.Logger): optional logger object. If none is passed 'ro.sdn.sdnconn' is used.
66 self
.logger
= logger
or logging
.getLogger(self
._WIM
_LOGGER
)
68 "wim: {}, wim_account: {}, config: {}".format(wim
, wim_account
, config
)
70 super().__init
__(wim
, wim_account
, config
, logger
)
72 self
.user
= wim_account
.get("user")
73 self
.password
= wim_account
.get("password")
75 url
= wim
.get("wim_url") # underlay url
86 auth_url
= config
.get("auth_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
.overlay_url
= config
.get("overlay_url")
92 self
.vni_range
= config
.get("vni_range")
94 if config
.get("insecure") and config
.get("ca_cert"):
95 raise SdnConnectorError(
96 "options insecure and ca_cert are mutually exclusive"
99 if config
.get("ca_cert"):
100 self
.verify
= config
.get("ca_cert")
102 elif config
.get("insecure"):
106 raise SdnConnectorError(
107 "certificate should provided or ssl verification should be "
108 "disabled by setting insecure as True in sdn/wim config."
112 raise SdnConnectorError("'url' must be provided")
114 if not url
.startswith("http"):
115 url
= "http://" + url
117 if not url
.endswith("/"):
122 if not self
.vni_range
:
123 self
.vni_range
= ["1000001-2000000"]
124 self
.logger
.info("No vni_range was provided. Using ['1000001-2000000']")
126 self
.used_vni
= set()
129 if not auth_url
.startswith("http"):
130 auth_url
= "http://" + auth_url
132 if not auth_url
.endswith("/"):
133 auth_url
= auth_url
+ "/"
135 self
.auth_url
= auth_url
138 if not overlay_url
.startswith("http"):
139 overlay_url
= "http://" + overlay_url
141 if not overlay_url
.endswith("/"):
142 overlay_url
= overlay_url
+ "/"
144 self
.overlay_url
= overlay_url
147 raise SdnConnectorError("'project' must be provided")
150 # TODO: Get ASN from controller config; otherwise raise ERROR for the moment
151 raise SdnConnectorError(
152 "'asn' was not provided and it was not possible to obtain it"
156 # TODO: Get FABRIC from controller config; otherwise raise ERROR for the moment
157 raise SdnConnectorError(
158 "'fabric' was not provided and was not possible to obtain it"
162 self
.domain
= "default-domain"
163 self
.logger
.info("No domain was provided. Using 'default-domain'")
165 underlay_api_config
= {
166 "auth_url": self
.auth_url
,
167 "project": self
.project
,
168 "domain": self
.domain
,
170 "fabric": self
.fabric
,
171 "verify": self
.verify
,
173 self
.underlay_api
= UnderlayApi(
177 password
=self
.password
,
181 self
._max
_duplicate
_retry
= 2
182 self
.logger
.info("Juniper Contrail Connector Initialized.")
184 def _generate_vni(self
):
186 Method to get unused VxLAN Network Identifier (VNI)
192 # find unused VLAN ID
193 for vlanID_range
in self
.vni_range
:
195 start_vni
, end_vni
= map(int, vlanID_range
.replace(" ", "").split("-"))
197 for i
in range(start_vni
, end_vni
+ 1):
198 vni
= random
.randrange(start_vni
, end_vni
, 1)
200 if vni
not in self
.used_vni
:
202 except Exception as exp
:
203 raise SdnConnectorError(
204 "Exception {} occurred while searching a free VNI.".format(exp
)
207 raise SdnConnectorError(
208 "Unable to create the virtual network."
209 " All VNI in VNI range {} are in use.".format(self
.vni_range
)
212 # Aux functions for testing
216 def get_overlay_url(self
):
217 return self
.overlay_url
219 def _create_port(self
, switch_id
, switch_port
, network
, vlan
):
221 1 - Look for virtual port groups for provided switch_id, switch_port using name
222 2 - It the virtual port group does not exist, create it
223 3 - Create virtual machine interface for the indicated network and vlan
226 "create_port: switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
227 switch_id
, switch_port
, network
, vlan
231 # 1 - Check if the vpg exists
232 vpg_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
233 vpg
= self
.underlay_api
.get_vpg_by_name(vpg_name
)
236 # 2 - If it does not exist create it
237 vpg_id
, _
= self
.underlay_api
.create_vpg(switch_id
, switch_port
)
239 # Assign vpg_id from vpg
240 vpg_id
= vpg
.get("uuid")
242 # 3 - Check if the vmi alreaady exists
243 vmi_id
, _
= self
.underlay_api
.create_vmi(switch_id
, switch_port
, network
, vlan
)
244 self
.logger
.debug("port created")
246 return vpg_id
, vmi_id
248 def _delete_port(self
, switch_id
, switch_port
, vlan
):
250 "delete port, switch_id: {}, switch_port: {}, vlan: {}".format(
251 switch_id
, switch_port
, vlan
255 vpg_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
256 vmi_name
= self
.underlay_api
.get_vmi_name(switch_id
, switch_port
, vlan
)
258 # 1 - Obtain vpg by id (if not vpg_id must have been error creating ig, nothing to be done)
259 vpg_fqdn
= ["default-global-system-config", self
.fabric
, vpg_name
]
260 vpg
= self
.underlay_api
.get_by_fq_name("virtual-port-group", vpg_fqdn
)
263 self
.logger
.warning("vpg: {} to be deleted not found".format(vpg_name
))
265 # 2 - Get vmi interfaces from vpg
266 vmi_list
= vpg
.get("virtual_machine_interface_refs")
269 # must have been an error during port creation when vmi is created
270 # may happen if there has been an error during creation
272 "vpg: {} has not vmi, will delete nothing".format(vpg
)
275 num_vmis
= len(vmi_list
)
281 if fqdn
[2] == vmi_name
:
282 self
.underlay_api
.unref_vmi_vpg(
283 vpg
.get("uuid"), vmi
.get("uuid"), fqdn
285 self
.underlay_api
.delete_vmi(vmi
.get("uuid"))
286 num_vmis
= num_vmis
- 1
288 # 3 - If there are no more vmi delete the vpg
289 if not vmi_list
or num_vmis
== 0:
290 self
.underlay_api
.delete_vpg(vpg
.get("uuid"))
292 def check_credentials(self
):
293 """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
294 user (wim_account.user), and password (wim_account.password)
297 SdnConnectorError: Issues regarding authorization, access to
298 external URLs, etc are detected.
300 self
.logger
.debug("")
303 resp
= self
.underlay_api
.check_auth()
305 raise SdnConnectorError("Empty response")
306 except Exception as e
:
307 self
.logger
.error("Error checking credentials")
309 raise SdnConnectorError("Error checking credentials: {}".format(str(e
)))
311 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
312 """Monitor the status of the connectivity service established
315 service_uuid (str): UUID of the connectivity service
316 conn_info (dict or None): Information returned by the connector
317 during the service creation/edition and subsequently stored in
321 dict: JSON/YAML-serializable dict that contains a mandatory key
322 ``sdn_status`` associated with one of the following values::
324 {'sdn_status': 'ACTIVE'}
325 # The service is up and running.
327 {'sdn_status': 'INACTIVE'}
328 # The service was created, but the connector
329 # cannot determine yet if connectivity exists
330 # (ideally, the caller needs to wait and check again).
332 {'sdn_status': 'DOWN'}
333 # Connection was previously established,
334 # but an error/failure was detected.
336 {'sdn_status': 'ERROR'}
337 # An error occurred when trying to create the service/
338 # establish the connectivity.
340 {'sdn_status': 'BUILD'}
341 # Still trying to create the service, the caller
342 # needs to wait and check again.
344 Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
345 keys can be used to provide additional status explanation or
346 new information available for the connectivity service.
348 self
.logger
.debug("")
351 resp
= self
.underlay_api
.get_virtual_network(service_uuid
)
353 raise SdnConnectorError("Empty response")
358 # Check if conn_info reports error
359 if conn_info
.get("sdn_status") == "ERROR":
360 return {"sdn_status": "ERROR", "sdn_info": conn_info
}
362 return {"sdn_status": "ACTIVE", "sdn_info": vnet_info
}
364 return {"sdn_status": "ERROR", "sdn_info": "not found"}
365 except SdnConnectorError
:
367 except HttpException
as e
:
368 self
.logger
.error("Error getting connectivity service: {}".format(e
))
370 raise SdnConnectorError(
371 "Exception deleting connectivity service: {}".format(str(e
))
373 except Exception as e
:
375 "Exception getting connectivity service info: %s", e
, exc_info
=True
378 return {"sdn_status": "ERROR", "error_msg": str(e
)}
380 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
382 Establish SDN/WAN connectivity between the endpoints
383 :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
384 :param connection_points: (list): each point corresponds to
385 an entry point to be connected. For WIM: from the DC to the transport network.
386 For SDN: Compute/PCI to the transport network. One
387 connection point serves to identify the specific access and
388 some other service parameters, such as encapsulation type.
389 Each item of the list is a dict with:
390 "service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__)
391 In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
392 it will contain the string "device_id:device_interface_id"
393 "service_endpoint_encapsulation_type": None, "dot1q", ...
394 "service_endpoint_encapsulation_info": (dict) with:
395 "vlan": ..., (int, present if encapsulation is dot1q)
396 "vni": ... (int, present if encapsulation is vxlan),
397 "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
399 "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
400 "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
401 "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
402 "switch_port": ... present if mapping has been found for this device_id,device_interface_id
403 "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
404 :param kwargs: For future versions:
405 bandwidth (int): value in kilobytes
406 latency (int): value in milliseconds
407 Other QoS might be passed as keyword arguments.
408 :return: tuple: ``(service_id, conn_info)`` containing:
409 - *service_uuid* (str): UUID of the established connectivity service
410 - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
411 This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
412 **MUST** be JSON/YAML-serializable (plain data structures).
413 :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
414 Provide the parameter http_code
416 # Step 1. Check in the overlay controller the virtual network created by the VIM
417 # Best option: get network id of the VIM as param (if the VIM already created the network),
418 # and do a request to the controller of the virtual networks whose VIM network id is the provided
419 # Next best option: obtain the network by doing a request to the controller
420 # of the virtual networks using the VLAN ID of any service endpoint.
421 # 1.1 Read VLAN ID from a service endpoint
422 # 1.2 Look for virtual networks with "Provider Network" including a VLAN ID.
423 # 1.3 If more than one, ERROR
424 # Step 2. Modify the existing virtual network in the overlay controller
425 # 2.1 Add VNI (VxLAN Network Identifier - one free from the provided range)
426 # 2.2 Add RouteTarget (RT) ('ASN:VNI', ASN = Autonomous System Number, provided as param or read from
428 # Step 3. Create a virtual network in the underlay controller
429 # 3.1 Create virtual network (name, VNI, RT)
430 # If the network already existed in the overlay controller, we should use the same name
431 # name = 'osm-plugin-' + overlay_name
433 # name = 'osm-plugin-' + VNI
435 "create_connectivity_service, service_type: {}, connection_points: {}".format(
436 service_type
, connection_points
440 if service_type
.lower() != "elan":
441 raise SdnConnectorError(
442 "Only ELAN network type is supported by Juniper Contrail."
449 # 1 - Filter connection_points (transform cp to a dictionary with no duplicates)
450 # This data will be returned even if no cp can be created if something is created
452 for cp
in connection_points
:
453 switch_id
= cp
.get("service_endpoint_encapsulation_info").get(
456 switch_port
= cp
.get("service_endpoint_encapsulation_info").get(
459 service_endpoint_id
= cp
.get("service_endpoint_id")
460 cp_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
461 add_cp
= work_cps
.get(cp_name
)
465 vlan
= cp
.get("service_endpoint_encapsulation_info").get("vlan")
469 service_endpoint_ids
= []
470 service_endpoint_ids
.append(service_endpoint_id
)
472 "service_endpoint_ids": service_endpoint_ids
,
473 "switch_dpid": switch_id
,
474 "switch_port": switch_port
,
477 work_cps
[cp_name
] = add_cp
480 "cp service_endpoint_id : {} has no vlan, ignore".format(
485 # add service_endpoint_id to list
486 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
487 service_endpoint_ids
.append(service_endpoint_id
)
489 # 2 - Obtain free VNI
490 vni
= self
._generate
_vni
()
491 self
.logger
.debug("VNI: {}".format(vni
))
493 # 3 - Create virtual network (name, VNI, RT), by the moment the name will use VNI
495 while retry
< self
._max
_duplicate
_retry
:
497 vnet_name
= "osm-plugin-" + str(vni
)
498 vnet_id
, _
= self
.underlay_api
.create_virtual_network(
501 self
.used_vni
.add(vni
)
503 except DuplicateFound
as e
:
505 "Duplicate error for vnet_name: {}".format(vnet_name
)
507 self
.used_vni
.add(vni
)
510 if retry
>= self
._max
_duplicate
_retry
:
513 # Try to obtain a new vni
514 vni
= self
._generate
_vni
()
522 "connection_points": work_cps
, # dict with port_name as key
525 # 4 - Create a port for each endpoint
526 for cp
in work_cps
.values():
527 switch_id
= cp
.get("switch_dpid")
528 switch_port
= cp
.get("switch_port")
529 vlan
= cp
.get("vlan")
530 vpg_id
, vmi_id
= self
._create
_port
(
531 switch_id
, switch_port
, vnet_name
, vlan
533 cp
["vpg_id"] = vpg_id
534 cp
["vmi_id"] = vmi_id
537 "created connectivity service, uuid: {}, name: {}".format(
542 return vnet_id
, conn_info
543 except Exception as e
:
545 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
546 self
.logger
.error("Error creating connectivity service: {}".format(e
))
549 "Error creating connectivity service: {}".format(e
), exc_info
=True
552 # If nothing is created raise error else return what has been created and mask as error
554 raise SdnConnectorError(
555 "Exception create connectivity service: {}".format(str(e
))
558 conn_info
["sdn_status"] = "ERROR"
559 conn_info
["sdn_info"] = repr(e
)
560 # iterate over not added connection_points and add but marking them as error
561 for cp
in work_cps
.values():
562 if not cp
.get("vmi_id") or not cp
.get("vpg_id"):
563 cp
["sdn_status"] = "ERROR"
565 return vnet_id
, conn_info
567 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
569 Disconnect multi-site endpoints previously connected
571 :param service_uuid: The one returned by create_connectivity_service
572 :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
573 if they do not return None
575 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
578 "delete_connectivity_service vnet_name: {}, connection_points: {}".format(
579 service_uuid
, conn_info
584 vnet_uuid
= service_uuid
585 # vnet_name = conn_info["vnet"]["name"]
586 # always should exist as the network is the first thing created
587 work_cps
= conn_info
["connection_points"]
589 # 1: For each connection point delete vlan from vpg and it is is the
590 # last one, delete vpg
591 for cp
in work_cps
.values():
593 cp
.get("switch_dpid"), cp
.get("switch_port"), cp
.get("vlan")
597 self
.underlay_api
.delete_virtual_network(vnet_uuid
)
599 "deleted connectivity_service vnet_uuid: {}, connection_points: {}".format(
600 service_uuid
, conn_info
603 except SdnConnectorError
:
605 except HttpException
as e
:
606 self
.logger
.error("Error deleting connectivity service: {}".format(e
))
608 raise SdnConnectorError(
609 "Exception deleting connectivity service: {}".format(str(e
))
611 except Exception as e
:
613 "Error deleting connectivity service: {}".format(e
),
617 raise SdnConnectorError(
618 "Exception deleting connectivity service: {}".format(str(e
))
621 def edit_connectivity_service(
622 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
624 """Change an existing connectivity service.
626 This method's arguments and return value follow the same convention as
627 :meth:`~.create_connectivity_service`.
629 :param service_uuid: UUID of the connectivity service.
630 :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
631 or edit_connectivity_service
632 :param connection_points: (list): If provided, the old list of connection points will be replaced.
633 :param kwargs: Same meaning that create_connectivity_service
634 :return: dict or None: Information to be updated and stored at the database.
635 When ``None`` is returned, no information should be changed.
636 When an empty dict is returned, the database record will be deleted.
637 **MUST** be JSON/YAML-serializable (plain data structures).
639 SdnConnectorException: In case of error.
641 # 0 - Check if there are connection_points marked as error and delete them
642 # 1 - Compare conn_info (old connection points) and connection_points (new ones to be applied):
643 # Obtain list of connection points to be added and to be deleted
644 # Obtain vlan and check it has not changed
645 # 2 - Obtain network: Check vnet exists and obtain name
646 # 3 - Delete unnecesary ports
649 "edit connectivity service, service_uuid: {}, conn_info: {}, "
650 "connection points: {} ".format(service_uuid
, conn_info
, connection_points
)
653 # conn_info should always exist and have connection_points and vnet elements
654 old_cp
= conn_info
.get("connection_points", {})
656 # Check if an element of old_cp is marked as error, in case it is delete it
657 # Not return a new conn_info in this case because it is only partial information
658 # Current conn_info already marks ports as error
661 for cp
in old_cp
.values():
662 if cp
.get("sdn_status") == "ERROR":
663 switch_id
= cp
.get("switch_dpid")
664 switch_port
= cp
.get("switch_port")
665 old_vlan
= cp
.get("vlan")
666 self
._delete
_port
(switch_id
, switch_port
, old_vlan
)
667 deleted_ports
.append(
668 self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
671 for port
in deleted_ports
:
674 # Delete sdn_status and sdn_info if exists (possibly marked as error)
675 if conn_info
.get("vnet", {}).get("sdn_status"):
676 del conn_info
["vnet"]["sdn_status"]
677 except HttpException
as e
:
679 "Error trying to delete old ports marked as error: {}".format(e
)
682 raise SdnConnectorError(e
)
683 except SdnConnectorError
as e
:
685 "Error trying to delete old ports marked as error: {}".format(e
)
689 except Exception as e
:
691 "Error trying to delete old ports marked as error: {}".format(e
),
695 raise SdnConnectorError(
696 "Error trying to delete old ports marked as error: {}".format(e
)
699 if connection_points
:
700 # Check and obtain what should be added and deleted, if there is an error here raise an exception
703 for cp
in connection_points
:
704 switch_id
= cp
.get("service_endpoint_encapsulation_info").get(
707 switch_port
= cp
.get("service_endpoint_encapsulation_info").get(
710 service_endpoint_id
= cp
.get("service_endpoint_id")
711 cp_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
712 add_cp
= work_cps
.get(cp_name
)
717 vlan
= cp
.get("service_endpoint_encapsulation_info").get("vlan")
720 service_endpoint_ids
= []
721 service_endpoint_ids
.append(service_endpoint_id
)
723 "service_endpoint_ids": service_endpoint_ids
,
724 "switch_dpid": switch_id
,
725 "switch_port": switch_port
,
728 work_cps
[cp_name
] = add_cp
731 "cp service_endpoint_id : {} has no vlan, ignore".format(
736 # add service_endpoint_id to list
737 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
738 service_endpoint_ids
.append(service_endpoint_id
)
740 old_port_list
= list(old_cp
.keys())
741 port_list
= list(work_cps
.keys())
742 to_delete_ports
= list(set(old_port_list
) - set(port_list
))
743 to_add_ports
= list(set(port_list
) - set(old_port_list
))
744 self
.logger
.debug("ports to delete: {}".format(to_delete_ports
))
745 self
.logger
.debug("ports to add: {}".format(to_add_ports
))
747 # Obtain network (check it is correctly created)
748 vnet
= self
.underlay_api
.get_virtual_network(service_uuid
)
750 vnet_name
= vnet
["name"]
752 raise SdnConnectorError(
753 "vnet uuid: {} not found".format(service_uuid
)
755 except SdnConnectorError
:
757 except Exception as e
:
759 "Error edit connectivity service: {}".format(e
), exc_info
=True
762 raise SdnConnectorError(
763 "Exception edit connectivity service: {}".format(str(e
))
766 # Delete unneeded ports and add new ones: if there is an error return conn_info
768 # Connection points returned in con_info should reflect what has (and should as ERROR) be done
769 # Start with old cp dictionary and modify it as we work
770 conn_info_cp
= old_cp
772 # Delete unneeded ports
774 for port_name
in conn_info_cp
.keys():
775 if port_name
in to_delete_ports
:
776 cp
= conn_info_cp
[port_name
]
777 switch_id
= cp
.get("switch_dpid")
778 switch_port
= cp
.get("switch_port")
780 "delete port switch_id={}, switch_port={}".format(
781 switch_id
, switch_port
784 self
._delete
_port
(switch_id
, switch_port
, vlan
)
785 deleted_ports
.append(port_name
)
788 for port_name
in deleted_ports
:
789 del conn_info_cp
[port_name
]
792 for port_name
, cp
in work_cps
.items():
793 if port_name
in to_add_ports
:
794 switch_id
= cp
.get("switch_dpid")
795 switch_port
= cp
.get("switch_port")
796 vlan
= cp
.get("vlan")
798 "add port switch_id={}, switch_port={}".format(
799 switch_id
, switch_port
802 vpg_id
, vmi_id
= self
._create
_port
(
803 switch_id
, switch_port
, vnet_name
, vlan
806 cp_added
["vpg_id"] = vpg_id
807 cp_added
["vmi_id"] = vmi_id
808 conn_info_cp
[port_name
] = cp_added
810 # replace endpoints in case they have changed
811 conn_info_cp
[port_name
]["service_endpoint_ids"] = cp
[
812 "service_endpoint_ids"
815 conn_info
["connection_points"] = conn_info_cp
818 except Exception as e
:
820 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
822 "Error edit connectivity service: {}".format(e
), exc_info
=True
825 self
.logger
.error("Error edit connectivity service: {}".format(e
))
827 # There has been an error mount conn_info_cp marking as error cp that should
828 # have been deleted but have not or should have been added
829 for port_name
, cp
in conn_info_cp
.items():
830 if port_name
in to_delete_ports
:
831 cp
["sdn_status"] = "ERROR"
833 for port_name
, cp
in work_cps
.items():
834 curr_cp
= conn_info_cp
.get(port_name
)
837 cp_error
= work_cps
.get(port_name
).copy()
838 cp_error
["sdn_status"] = "ERROR"
839 conn_info_cp
[port_name
] = cp_error
841 conn_info_cp
[port_name
]["service_endpoint_ids"] = cp
[
842 "service_endpoint_ids"
845 conn_info
["sdn_status"] = "ERROR"
846 conn_info
["sdn_info"] = repr(e
)
847 conn_info
["connection_points"] = conn_info_cp
851 # Connection points have not changed, so do nothing
852 self
.logger
.info("no new connection_points provided, nothing to be done")
857 if __name__
== "__main__":
859 log_format
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
860 log_formatter
= logging
.Formatter(log_format
, datefmt
="%Y-%m-%dT%H:%M:%S")
861 handler
= logging
.StreamHandler()
862 handler
.setFormatter(log_formatter
)
863 logger
= logging
.getLogger("ro.sdn.junipercontrail")
864 # logger.setLevel(level=logging.ERROR)
865 # logger.setLevel(level=logging.INFO)
866 logger
.setLevel(level
=logging
.DEBUG
)
867 logger
.addHandler(handler
)
870 with
open("test.yaml") as f
:
871 config
= yaml
.safe_load(f
.read())
873 wim
= {"wim_url": config
.pop("wim_url")}
874 wim_account
= {"user": config
.pop("user"), "password": config
.pop("password")}
875 logger
.info("wim: {}, wim_account: {}, config: {}".format(wim
, wim_account
, config
))
878 juniper_contrail
= JuniperContrail(
879 wim
=wim
, wim_account
=wim_account
, config
=config
, logger
=logger
885 vni
= juniper_contrail
._generate
_vni
()
886 juniper_contrail
.used_vni
.add(vni
)
888 print(juniper_contrail
.used_vni
)
889 # juniper_contrail.used_vni.remove(1000003)
890 print(juniper_contrail
.used_vni
)
893 vni
= juniper_contrail
._generate
_vni
()
894 juniper_contrail
.used_vni
.add(vni
)
896 print(juniper_contrail
.used_vni
)
898 # 0. Check credentials
899 print("0. Check credentials")
900 # juniper_contrail.check_credentials()
902 # 1 - Create and delete connectivity service
904 "service_endpoint_id": "0000:83:11.4",
905 "service_endpoint_encapsulation_type": "dot1q",
906 "service_endpoint_encapsulation_info": {
907 "switch_dpid": "LEAF-1",
908 "switch_port": "xe-0/0/17",
913 "service_endpoint_id": "0000:81:10.3",
914 "service_endpoint_encapsulation_type": "dot1q",
915 "service_endpoint_encapsulation_info": {
916 "switch_dpid": "LEAF-2",
917 "switch_port": "xe-0/0/16",
922 "service_endpoint_id": "0000:08:11.7",
923 "service_endpoint_encapsulation_type": "dot1q",
924 "service_endpoint_encapsulation_info": {
925 "switch_dpid": "LEAF-2",
926 "switch_port": "xe-0/0/16",
931 "service_endpoint_id": "0000:83:10.4",
932 "service_endpoint_encapsulation_type": "dot1q",
933 "service_endpoint_encapsulation_info": {
934 "switch_dpid": "LEAF-1",
935 "switch_port": "xe-0/0/17",
940 # 1 - Define connection points
941 logger
.debug("create first connection service")
942 print("Create connectivity service")
943 connection_points
= [conn_point_0
, conn_point_1
]
944 service_id
, conn_info
= juniper_contrail
.create_connectivity_service(
945 "ELAN", connection_points
947 logger
.info("Created connectivity service 1")
948 logger
.info(service_id
)
949 logger
.info(yaml
.safe_dump(conn_info
, indent
=4, default_flow_style
=False))
951 logger
.debug("create second connection service")
952 print("Create connectivity service")
953 connection_points
= [conn_point_2
, conn_point_3
]
954 service_id2
, conn_info2
= juniper_contrail
.create_connectivity_service(
955 "ELAN", connection_points
957 logger
.info("Created connectivity service 2")
958 logger
.info(service_id2
)
959 logger
.info(yaml
.safe_dump(conn_info2
, indent
=4, default_flow_style
=False))
961 logger
.debug("Delete connectivity service 1")
962 juniper_contrail
.delete_connectivity_service(service_id
, conn_info
)
963 logger
.debug("Delete Ok")
965 logger
.debug("Delete connectivity service 2")
966 juniper_contrail
.delete_connectivity_service(service_id2
, conn_info2
)
967 logger
.debug("Delete Ok")