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
85 auth_url
= config
.get("auth_url")
86 self
.project
= config
.get("project")
87 self
.domain
= config
.get("domain")
88 self
.asn
= config
.get("asn")
89 self
.fabric
= config
.get("fabric")
90 self
.overlay_url
= config
.get("overlay_url")
91 self
.vni_range
= config
.get("vni_range")
94 raise SdnConnectorError("'url' must be provided")
96 if not url
.startswith("http"):
99 if not url
.endswith("/"):
104 if not self
.vni_range
:
105 self
.vni_range
= ["1000001-2000000"]
106 self
.logger
.info("No vni_range was provided. Using ['1000001-2000000']")
108 self
.used_vni
= set()
111 if not auth_url
.startswith("http"):
112 auth_url
= "http://" + auth_url
114 if not auth_url
.endswith("/"):
115 auth_url
= auth_url
+ "/"
117 self
.auth_url
= auth_url
120 if not overlay_url
.startswith("http"):
121 overlay_url
= "http://" + overlay_url
123 if not overlay_url
.endswith("/"):
124 overlay_url
= overlay_url
+ "/"
126 self
.overlay_url
= overlay_url
129 raise SdnConnectorError("'project' must be provided")
132 # TODO: Get ASN from controller config; otherwise raise ERROR for the moment
133 raise SdnConnectorError(
134 "'asn' was not provided and it was not possible to obtain it"
138 # TODO: Get FABRIC from controller config; otherwise raise ERROR for the moment
139 raise SdnConnectorError(
140 "'fabric' was not provided and was not possible to obtain it"
144 self
.domain
= "default-domain"
145 self
.logger
.info("No domain was provided. Using 'default-domain'")
147 underlay_api_config
= {
148 "auth_url": self
.auth_url
,
149 "project": self
.project
,
150 "domain": self
.domain
,
152 "fabric": self
.fabric
,
154 self
.underlay_api
= UnderlayApi(
158 password
=self
.password
,
162 self
._max
_duplicate
_retry
= 2
163 self
.logger
.info("Juniper Contrail Connector Initialized.")
165 def _generate_vni(self
):
167 Method to get unused VxLAN Network Identifier (VNI)
173 # find unused VLAN ID
174 for vlanID_range
in self
.vni_range
:
176 start_vni
, end_vni
= map(int, vlanID_range
.replace(" ", "").split("-"))
178 for i
in range(start_vni
, end_vni
+ 1):
179 vni
= random
.randrange(start_vni
, end_vni
, 1)
181 if vni
not in self
.used_vni
:
183 except Exception as exp
:
184 raise SdnConnectorError(
185 "Exception {} occurred while searching a free VNI.".format(exp
)
188 raise SdnConnectorError(
189 "Unable to create the virtual network."
190 " All VNI in VNI range {} are in use.".format(self
.vni_range
)
193 # Aux functions for testing
197 def get_overlay_url(self
):
198 return self
.overlay_url
200 def _create_port(self
, switch_id
, switch_port
, network
, vlan
):
202 1 - Look for virtual port groups for provided switch_id, switch_port using name
203 2 - It the virtual port group does not exist, create it
204 3 - Create virtual machine interface for the indicated network and vlan
207 "create_port: switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
208 switch_id
, switch_port
, network
, vlan
212 # 1 - Check if the vpg exists
213 vpg_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
214 vpg
= self
.underlay_api
.get_vpg_by_name(vpg_name
)
217 # 2 - If it does not exist create it
218 vpg_id
, _
= self
.underlay_api
.create_vpg(switch_id
, switch_port
)
220 # Assign vpg_id from vpg
221 vpg_id
= vpg
.get("uuid")
223 # 3 - Check if the vmi alreaady exists
224 vmi_id
, _
= self
.underlay_api
.create_vmi(switch_id
, switch_port
, network
, vlan
)
225 self
.logger
.debug("port created")
227 return vpg_id
, vmi_id
229 def _delete_port(self
, switch_id
, switch_port
, vlan
):
231 "delete port, switch_id: {}, switch_port: {}, vlan: {}".format(
232 switch_id
, switch_port
, vlan
236 vpg_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
237 vmi_name
= self
.underlay_api
.get_vmi_name(switch_id
, switch_port
, vlan
)
239 # 1 - Obtain vpg by id (if not vpg_id must have been error creating ig, nothing to be done)
240 vpg_fqdn
= ["default-global-system-config", self
.fabric
, vpg_name
]
241 vpg
= self
.underlay_api
.get_by_fq_name("virtual-port-group", vpg_fqdn
)
244 self
.logger
.warning("vpg: {} to be deleted not found".format(vpg_name
))
246 # 2 - Get vmi interfaces from vpg
247 vmi_list
= vpg
.get("virtual_machine_interface_refs")
250 # must have been an error during port creation when vmi is created
251 # may happen if there has been an error during creation
253 "vpg: {} has not vmi, will delete nothing".format(vpg
)
256 num_vmis
= len(vmi_list
)
262 if fqdn
[2] == vmi_name
:
263 self
.underlay_api
.unref_vmi_vpg(
264 vpg
.get("uuid"), vmi
.get("uuid"), fqdn
266 self
.underlay_api
.delete_vmi(vmi
.get("uuid"))
267 num_vmis
= num_vmis
- 1
269 # 3 - If there are no more vmi delete the vpg
270 if not vmi_list
or num_vmis
== 0:
271 self
.underlay_api
.delete_vpg(vpg
.get("uuid"))
273 def check_credentials(self
):
274 """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
275 user (wim_account.user), and password (wim_account.password)
278 SdnConnectorError: Issues regarding authorization, access to
279 external URLs, etc are detected.
281 self
.logger
.debug("")
284 resp
= self
.underlay_api
.check_auth()
286 raise SdnConnectorError("Empty response")
287 except Exception as e
:
288 self
.logger
.error("Error checking credentials")
290 raise SdnConnectorError("Error checking credentials: {}".format(str(e
)))
292 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
293 """Monitor the status of the connectivity service established
296 service_uuid (str): UUID of the connectivity service
297 conn_info (dict or None): Information returned by the connector
298 during the service creation/edition and subsequently stored in
302 dict: JSON/YAML-serializable dict that contains a mandatory key
303 ``sdn_status`` associated with one of the following values::
305 {'sdn_status': 'ACTIVE'}
306 # The service is up and running.
308 {'sdn_status': 'INACTIVE'}
309 # The service was created, but the connector
310 # cannot determine yet if connectivity exists
311 # (ideally, the caller needs to wait and check again).
313 {'sdn_status': 'DOWN'}
314 # Connection was previously established,
315 # but an error/failure was detected.
317 {'sdn_status': 'ERROR'}
318 # An error occurred when trying to create the service/
319 # establish the connectivity.
321 {'sdn_status': 'BUILD'}
322 # Still trying to create the service, the caller
323 # needs to wait and check again.
325 Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
326 keys can be used to provide additional status explanation or
327 new information available for the connectivity service.
329 self
.logger
.debug("")
332 resp
= self
.underlay_api
.get_virtual_network(service_uuid
)
334 raise SdnConnectorError("Empty response")
339 # Check if conn_info reports error
340 if conn_info
.get("sdn_status") == "ERROR":
341 return {"sdn_status": "ERROR", "sdn_info": conn_info
}
343 return {"sdn_status": "ACTIVE", "sdn_info": vnet_info
}
345 return {"sdn_status": "ERROR", "sdn_info": "not found"}
346 except SdnConnectorError
:
348 except HttpException
as e
:
349 self
.logger
.error("Error getting connectivity service: {}".format(e
))
351 raise SdnConnectorError(
352 "Exception deleting connectivity service: {}".format(str(e
))
354 except Exception as e
:
356 "Exception getting connectivity service info: %s", e
, exc_info
=True
359 return {"sdn_status": "ERROR", "error_msg": str(e
)}
361 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
363 Establish SDN/WAN connectivity between the endpoints
364 :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
365 :param connection_points: (list): each point corresponds to
366 an entry point to be connected. For WIM: from the DC to the transport network.
367 For SDN: Compute/PCI to the transport network. One
368 connection point serves to identify the specific access and
369 some other service parameters, such as encapsulation type.
370 Each item of the list is a dict with:
371 "service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__)
372 In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
373 it will contain the string "device_id:device_interface_id"
374 "service_endpoint_encapsulation_type": None, "dot1q", ...
375 "service_endpoint_encapsulation_info": (dict) with:
376 "vlan": ..., (int, present if encapsulation is dot1q)
377 "vni": ... (int, present if encapsulation is vxlan),
378 "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
380 "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
381 "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
382 "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
383 "switch_port": ... present if mapping has been found for this device_id,device_interface_id
384 "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
385 :param kwargs: For future versions:
386 bandwidth (int): value in kilobytes
387 latency (int): value in milliseconds
388 Other QoS might be passed as keyword arguments.
389 :return: tuple: ``(service_id, conn_info)`` containing:
390 - *service_uuid* (str): UUID of the established connectivity service
391 - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
392 This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
393 **MUST** be JSON/YAML-serializable (plain data structures).
394 :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
395 Provide the parameter http_code
397 # Step 1. Check in the overlay controller the virtual network created by the VIM
398 # Best option: get network id of the VIM as param (if the VIM already created the network),
399 # and do a request to the controller of the virtual networks whose VIM network id is the provided
400 # Next best option: obtain the network by doing a request to the controller
401 # of the virtual networks using the VLAN ID of any service endpoint.
402 # 1.1 Read VLAN ID from a service endpoint
403 # 1.2 Look for virtual networks with "Provider Network" including a VLAN ID.
404 # 1.3 If more than one, ERROR
405 # Step 2. Modify the existing virtual network in the overlay controller
406 # 2.1 Add VNI (VxLAN Network Identifier - one free from the provided range)
407 # 2.2 Add RouteTarget (RT) ('ASN:VNI', ASN = Autonomous System Number, provided as param or read from
409 # Step 3. Create a virtual network in the underlay controller
410 # 3.1 Create virtual network (name, VNI, RT)
411 # If the network already existed in the overlay controller, we should use the same name
412 # name = 'osm-plugin-' + overlay_name
414 # name = 'osm-plugin-' + VNI
416 "create_connectivity_service, service_type: {}, connection_points: {}".format(
417 service_type
, connection_points
421 if service_type
.lower() != "elan":
422 raise SdnConnectorError(
423 "Only ELAN network type is supported by Juniper Contrail."
430 # 1 - Filter connection_points (transform cp to a dictionary with no duplicates)
431 # This data will be returned even if no cp can be created if something is created
433 for cp
in connection_points
:
434 switch_id
= cp
.get("service_endpoint_encapsulation_info").get(
437 switch_port
= cp
.get("service_endpoint_encapsulation_info").get(
440 service_endpoint_id
= cp
.get("service_endpoint_id")
441 cp_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
442 add_cp
= work_cps
.get(cp_name
)
446 vlan
= cp
.get("service_endpoint_encapsulation_info").get("vlan")
450 service_endpoint_ids
= []
451 service_endpoint_ids
.append(service_endpoint_id
)
453 "service_endpoint_ids": service_endpoint_ids
,
454 "switch_dpid": switch_id
,
455 "switch_port": switch_port
,
458 work_cps
[cp_name
] = add_cp
461 "cp service_endpoint_id : {} has no vlan, ignore".format(
466 # add service_endpoint_id to list
467 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
468 service_endpoint_ids
.append(service_endpoint_id
)
470 # 2 - Obtain free VNI
471 vni
= self
._generate
_vni
()
472 self
.logger
.debug("VNI: {}".format(vni
))
474 # 3 - Create virtual network (name, VNI, RT), by the moment the name will use VNI
476 while retry
< self
._max
_duplicate
_retry
:
478 vnet_name
= "osm-plugin-" + str(vni
)
479 vnet_id
, _
= self
.underlay_api
.create_virtual_network(
482 self
.used_vni
.add(vni
)
484 except DuplicateFound
as e
:
486 "Duplicate error for vnet_name: {}".format(vnet_name
)
488 self
.used_vni
.add(vni
)
491 if retry
>= self
._max
_duplicate
_retry
:
494 # Try to obtain a new vni
495 vni
= self
._generate
_vni
()
503 "connection_points": work_cps
, # dict with port_name as key
506 # 4 - Create a port for each endpoint
507 for cp
in work_cps
.values():
508 switch_id
= cp
.get("switch_dpid")
509 switch_port
= cp
.get("switch_port")
510 vlan
= cp
.get("vlan")
511 vpg_id
, vmi_id
= self
._create
_port
(
512 switch_id
, switch_port
, vnet_name
, vlan
514 cp
["vpg_id"] = vpg_id
515 cp
["vmi_id"] = vmi_id
518 "created connectivity service, uuid: {}, name: {}".format(
523 return vnet_id
, conn_info
524 except Exception as e
:
526 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
527 self
.logger
.error("Error creating connectivity service: {}".format(e
))
530 "Error creating connectivity service: {}".format(e
), exc_info
=True
533 # If nothing is created raise error else return what has been created and mask as error
535 raise SdnConnectorError(
536 "Exception create connectivity service: {}".format(str(e
))
539 conn_info
["sdn_status"] = "ERROR"
540 conn_info
["sdn_info"] = repr(e
)
541 # iterate over not added connection_points and add but marking them as error
542 for cp
in work_cps
.values():
543 if not cp
.get("vmi_id") or not cp
.get("vpg_id"):
544 cp
["sdn_status"] = "ERROR"
546 return vnet_id
, conn_info
548 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
550 Disconnect multi-site endpoints previously connected
552 :param service_uuid: The one returned by create_connectivity_service
553 :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
554 if they do not return None
556 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
559 "delete_connectivity_service vnet_name: {}, connection_points: {}".format(
560 service_uuid
, conn_info
565 vnet_uuid
= service_uuid
566 # vnet_name = conn_info["vnet"]["name"]
567 # always should exist as the network is the first thing created
568 work_cps
= conn_info
["connection_points"]
570 # 1: For each connection point delete vlan from vpg and it is is the
571 # last one, delete vpg
572 for cp
in work_cps
.values():
574 cp
.get("switch_dpid"), cp
.get("switch_port"), cp
.get("vlan")
578 self
.underlay_api
.delete_virtual_network(vnet_uuid
)
580 "deleted connectivity_service vnet_uuid: {}, connection_points: {}".format(
581 service_uuid
, conn_info
584 except SdnConnectorError
:
586 except HttpException
as e
:
587 self
.logger
.error("Error deleting connectivity service: {}".format(e
))
589 raise SdnConnectorError(
590 "Exception deleting connectivity service: {}".format(str(e
))
592 except Exception as e
:
594 "Error deleting connectivity service: {}".format(e
),
598 raise SdnConnectorError(
599 "Exception deleting connectivity service: {}".format(str(e
))
602 def edit_connectivity_service(
603 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
605 """Change an existing connectivity service.
607 This method's arguments and return value follow the same convention as
608 :meth:`~.create_connectivity_service`.
610 :param service_uuid: UUID of the connectivity service.
611 :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
612 or edit_connectivity_service
613 :param connection_points: (list): If provided, the old list of connection points will be replaced.
614 :param kwargs: Same meaning that create_connectivity_service
615 :return: dict or None: Information to be updated and stored at the database.
616 When ``None`` is returned, no information should be changed.
617 When an empty dict is returned, the database record will be deleted.
618 **MUST** be JSON/YAML-serializable (plain data structures).
620 SdnConnectorException: In case of error.
622 # 0 - Check if there are connection_points marked as error and delete them
623 # 1 - Compare conn_info (old connection points) and connection_points (new ones to be applied):
624 # Obtain list of connection points to be added and to be deleted
625 # Obtain vlan and check it has not changed
626 # 2 - Obtain network: Check vnet exists and obtain name
627 # 3 - Delete unnecesary ports
630 "edit connectivity service, service_uuid: {}, conn_info: {}, "
631 "connection points: {} ".format(service_uuid
, conn_info
, connection_points
)
634 # conn_info should always exist and have connection_points and vnet elements
635 old_cp
= conn_info
.get("connection_points", {})
637 # Check if an element of old_cp is marked as error, in case it is delete it
638 # Not return a new conn_info in this case because it is only partial information
639 # Current conn_info already marks ports as error
642 for cp
in old_cp
.values():
643 if cp
.get("sdn_status") == "ERROR":
644 switch_id
= cp
.get("switch_dpid")
645 switch_port
= cp
.get("switch_port")
646 old_vlan
= cp
.get("vlan")
647 self
._delete
_port
(switch_id
, switch_port
, old_vlan
)
648 deleted_ports
.append(
649 self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
652 for port
in deleted_ports
:
655 # Delete sdn_status and sdn_info if exists (possibly marked as error)
656 if conn_info
.get("vnet", {}).get("sdn_status"):
657 del conn_info
["vnet"]["sdn_status"]
658 except HttpException
as e
:
660 "Error trying to delete old ports marked as error: {}".format(e
)
663 raise SdnConnectorError(e
)
664 except SdnConnectorError
as e
:
666 "Error trying to delete old ports marked as error: {}".format(e
)
670 except Exception as e
:
672 "Error trying to delete old ports marked as error: {}".format(e
),
676 raise SdnConnectorError(
677 "Error trying to delete old ports marked as error: {}".format(e
)
680 if connection_points
:
681 # Check and obtain what should be added and deleted, if there is an error here raise an exception
684 for cp
in connection_points
:
685 switch_id
= cp
.get("service_endpoint_encapsulation_info").get(
688 switch_port
= cp
.get("service_endpoint_encapsulation_info").get(
691 service_endpoint_id
= cp
.get("service_endpoint_id")
692 cp_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
693 add_cp
= work_cps
.get(cp_name
)
698 vlan
= cp
.get("service_endpoint_encapsulation_info").get("vlan")
701 service_endpoint_ids
= []
702 service_endpoint_ids
.append(service_endpoint_id
)
704 "service_endpoint_ids": service_endpoint_ids
,
705 "switch_dpid": switch_id
,
706 "switch_port": switch_port
,
709 work_cps
[cp_name
] = add_cp
712 "cp service_endpoint_id : {} has no vlan, ignore".format(
717 # add service_endpoint_id to list
718 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
719 service_endpoint_ids
.append(service_endpoint_id
)
721 old_port_list
= list(old_cp
.keys())
722 port_list
= list(work_cps
.keys())
723 to_delete_ports
= list(set(old_port_list
) - set(port_list
))
724 to_add_ports
= list(set(port_list
) - set(old_port_list
))
725 self
.logger
.debug("ports to delete: {}".format(to_delete_ports
))
726 self
.logger
.debug("ports to add: {}".format(to_add_ports
))
728 # Obtain network (check it is correctly created)
729 vnet
= self
.underlay_api
.get_virtual_network(service_uuid
)
731 vnet_name
= vnet
["name"]
733 raise SdnConnectorError(
734 "vnet uuid: {} not found".format(service_uuid
)
736 except SdnConnectorError
:
738 except Exception as e
:
740 "Error edit connectivity service: {}".format(e
), exc_info
=True
743 raise SdnConnectorError(
744 "Exception edit connectivity service: {}".format(str(e
))
747 # Delete unneeded ports and add new ones: if there is an error return conn_info
749 # Connection points returned in con_info should reflect what has (and should as ERROR) be done
750 # Start with old cp dictionary and modify it as we work
751 conn_info_cp
= old_cp
753 # Delete unneeded ports
755 for port_name
in conn_info_cp
.keys():
756 if port_name
in to_delete_ports
:
757 cp
= conn_info_cp
[port_name
]
758 switch_id
= cp
.get("switch_dpid")
759 switch_port
= cp
.get("switch_port")
761 "delete port switch_id={}, switch_port={}".format(
762 switch_id
, switch_port
765 self
._delete
_port
(switch_id
, switch_port
, vlan
)
766 deleted_ports
.append(port_name
)
769 for port_name
in deleted_ports
:
770 del conn_info_cp
[port_name
]
773 for port_name
, cp
in work_cps
.items():
774 if port_name
in to_add_ports
:
775 switch_id
= cp
.get("switch_dpid")
776 switch_port
= cp
.get("switch_port")
777 vlan
= cp
.get("vlan")
779 "add port switch_id={}, switch_port={}".format(
780 switch_id
, switch_port
783 vpg_id
, vmi_id
= self
._create
_port
(
784 switch_id
, switch_port
, vnet_name
, vlan
787 cp_added
["vpg_id"] = vpg_id
788 cp_added
["vmi_id"] = vmi_id
789 conn_info_cp
[port_name
] = cp_added
791 # replace endpoints in case they have changed
792 conn_info_cp
[port_name
]["service_endpoint_ids"] = cp
[
793 "service_endpoint_ids"
796 conn_info
["connection_points"] = conn_info_cp
799 except Exception as e
:
801 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
803 "Error edit connectivity service: {}".format(e
), exc_info
=True
806 self
.logger
.error("Error edit connectivity service: {}".format(e
))
808 # There has been an error mount conn_info_cp marking as error cp that should
809 # have been deleted but have not or should have been added
810 for port_name
, cp
in conn_info_cp
.items():
811 if port_name
in to_delete_ports
:
812 cp
["sdn_status"] = "ERROR"
814 for port_name
, cp
in work_cps
.items():
815 curr_cp
= conn_info_cp
.get(port_name
)
818 cp_error
= work_cps
.get(port_name
).copy()
819 cp_error
["sdn_status"] = "ERROR"
820 conn_info_cp
[port_name
] = cp_error
822 conn_info_cp
[port_name
]["service_endpoint_ids"] = cp
[
823 "service_endpoint_ids"
826 conn_info
["sdn_status"] = "ERROR"
827 conn_info
["sdn_info"] = repr(e
)
828 conn_info
["connection_points"] = conn_info_cp
832 # Connection points have not changed, so do nothing
833 self
.logger
.info("no new connection_points provided, nothing to be done")
838 if __name__
== "__main__":
840 log_format
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
841 log_formatter
= logging
.Formatter(log_format
, datefmt
="%Y-%m-%dT%H:%M:%S")
842 handler
= logging
.StreamHandler()
843 handler
.setFormatter(log_formatter
)
844 logger
= logging
.getLogger("ro.sdn.junipercontrail")
845 # logger.setLevel(level=logging.ERROR)
846 # logger.setLevel(level=logging.INFO)
847 logger
.setLevel(level
=logging
.DEBUG
)
848 logger
.addHandler(handler
)
851 with
open("test.yaml") as f
:
852 config
= yaml
.safe_load(f
.read())
854 wim
= {"wim_url": config
.pop("wim_url")}
855 wim_account
= {"user": config
.pop("user"), "password": config
.pop("password")}
856 logger
.info("wim: {}, wim_account: {}, config: {}".format(wim
, wim_account
, config
))
859 juniper_contrail
= JuniperContrail(
860 wim
=wim
, wim_account
=wim_account
, config
=config
, logger
=logger
866 vni
= juniper_contrail
._generate
_vni
()
867 juniper_contrail
.used_vni
.add(vni
)
869 print(juniper_contrail
.used_vni
)
870 # juniper_contrail.used_vni.remove(1000003)
871 print(juniper_contrail
.used_vni
)
874 vni
= juniper_contrail
._generate
_vni
()
875 juniper_contrail
.used_vni
.add(vni
)
877 print(juniper_contrail
.used_vni
)
879 # 0. Check credentials
880 print("0. Check credentials")
881 # juniper_contrail.check_credentials()
883 # 1 - Create and delete connectivity service
885 "service_endpoint_id": "0000:83:11.4",
886 "service_endpoint_encapsulation_type": "dot1q",
887 "service_endpoint_encapsulation_info": {
888 "switch_dpid": "LEAF-1",
889 "switch_port": "xe-0/0/17",
894 "service_endpoint_id": "0000:81:10.3",
895 "service_endpoint_encapsulation_type": "dot1q",
896 "service_endpoint_encapsulation_info": {
897 "switch_dpid": "LEAF-2",
898 "switch_port": "xe-0/0/16",
903 "service_endpoint_id": "0000:08:11.7",
904 "service_endpoint_encapsulation_type": "dot1q",
905 "service_endpoint_encapsulation_info": {
906 "switch_dpid": "LEAF-2",
907 "switch_port": "xe-0/0/16",
912 "service_endpoint_id": "0000:83:10.4",
913 "service_endpoint_encapsulation_type": "dot1q",
914 "service_endpoint_encapsulation_info": {
915 "switch_dpid": "LEAF-1",
916 "switch_port": "xe-0/0/17",
921 # 1 - Define connection points
922 logger
.debug("create first connection service")
923 print("Create connectivity service")
924 connection_points
= [conn_point_0
, conn_point_1
]
925 service_id
, conn_info
= juniper_contrail
.create_connectivity_service(
926 "ELAN", connection_points
928 logger
.info("Created connectivity service 1")
929 logger
.info(service_id
)
930 logger
.info(yaml
.safe_dump(conn_info
, indent
=4, default_flow_style
=False))
932 logger
.debug("create second connection service")
933 print("Create connectivity service")
934 connection_points
= [conn_point_2
, conn_point_3
]
935 service_id2
, conn_info2
= juniper_contrail
.create_connectivity_service(
936 "ELAN", connection_points
938 logger
.info("Created connectivity service 2")
939 logger
.info(service_id2
)
940 logger
.info(yaml
.safe_dump(conn_info2
, indent
=4, default_flow_style
=False))
942 logger
.debug("Delete connectivity service 1")
943 juniper_contrail
.delete_connectivity_service(service_id
, conn_info
)
944 logger
.debug("Delete Ok")
946 logger
.debug("Delete connectivity service 2")
947 juniper_contrail
.delete_connectivity_service(service_id2
, conn_info2
)
948 logger
.debug("Delete Ok")