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
25 from osm_ro
.wim
.sdnconn
import SdnConnectorBase
, SdnConnectorError
26 from osm_rosdn_juniper_contrail
.rest_lib
import ContrailHttp
27 from osm_rosdn_juniper_contrail
.rest_lib
import NotFound
28 from osm_rosdn_juniper_contrail
.rest_lib
import DuplicateFound
29 from osm_rosdn_juniper_contrail
.rest_lib
import HttpException
31 from osm_rosdn_juniper_contrail
.sdn_api
import UnderlayApi
34 class JuniperContrail(SdnConnectorBase
):
36 Juniper Contrail SDN plugin. The plugin interacts with Juniper Contrail Controller,
37 whose API details can be found in these links:
39 - https://github.com/tonyliu0592/contrail/wiki/API-Configuration-REST
40 - https://www.juniper.net/documentation/en_US/contrail19/information-products/pathway-pages/api-guide-1910/tutorial_with_rest.html
41 - https://github.com/tonyliu0592/contrail-toolbox/blob/master/sriov/sriov
43 _WIM_LOGGER
= "openmano.sdnconn.junipercontrail"
45 def __init__(self
, wim
, wim_account
, config
=None, logger
=None):
48 :param wim: (dict). Contains among others 'wim_url'
49 :param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
50 'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
51 :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
52 'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
53 'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
54 KEY meaning for WIM meaning for SDN assist
55 -------- -------- --------
56 device_id pop_switch_dpid compute_id
57 device_interface_id pop_switch_port compute_pci_address
58 service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id
59 service_mapping_info wan_service_mapping_info SDN_service_mapping_info
60 contains extra information if needed. Text in Yaml format
61 switch_dpid wan_switch_dpid SDN_switch_dpid
62 switch_port wan_switch_port SDN_switch_port
63 datacenter_id vim_account vim_account
64 id: (internal, do not use)
65 wim_id: (internal, do not use)
66 :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
68 self
.logger
= logger
or logging
.getLogger(self
._WIM
_LOGGER
)
69 self
.logger
.debug('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
84 auth_url
= config
.get("auth_url")
85 self
.project
= config
.get("project")
86 self
.domain
= config
.get("domain")
87 self
.asn
= config
.get("asn")
88 self
.fabric
= config
.get("fabric")
89 self
.overlay_url
= config
.get("overlay_url")
90 self
.vni_range
= config
.get("vni_range")
93 raise SdnConnectorError("'url' must be provided")
94 if not url
.startswith("http"):
96 if not url
.endswith("/"):
100 if not self
.vni_range
:
101 self
.vni_range
= ['1000001-2000000']
102 self
.logger
.info("No vni_range was provided. Using ['1000001-2000000']")
103 self
.used_vni
= set()
106 if not auth_url
.startswith("http"):
107 auth_url
= "http://" + auth_url
108 if not auth_url
.endswith("/"):
109 auth_url
= auth_url
+ "/"
110 self
.auth_url
= auth_url
113 if not overlay_url
.startswith("http"):
114 overlay_url
= "http://" + overlay_url
115 if not overlay_url
.endswith("/"):
116 overlay_url
= overlay_url
+ "/"
117 self
.overlay_url
= overlay_url
120 raise SdnConnectorError("'project' must be provided")
122 # TODO: Get ASN from controller config; otherwise raise ERROR for the moment
123 raise SdnConnectorError("'asn' was not provided and it was not possible to obtain it")
125 # TODO: Get FABRIC from controller config; otherwise raise ERROR for the moment
126 raise SdnConnectorError("'fabric' was not provided and was not possible to obtain it")
128 self
.domain
= 'default-domain'
129 self
.logger
.info("No domain was provided. Using 'default-domain'")
131 underlay_api_config
= {
132 "auth_url": self
.auth_url
,
133 "project": self
.project
,
134 "domain": self
.domain
,
136 "fabric": self
.fabric
138 self
.underlay_api
= UnderlayApi(url
, underlay_api_config
, user
=self
.user
, password
=self
.password
, logger
=logger
)
140 self
._max
_duplicate
_retry
= 2
141 self
.logger
.info("Juniper Contrail Connector Initialized.")
143 def _generate_vni(self
):
145 Method to get unused VxLAN Network Identifier (VNI)
152 for vlanID_range
in self
.vni_range
:
154 start_vni
, end_vni
= map(int, vlanID_range
.replace(" ", "").split("-"))
155 for i
in range(start_vni
, end_vni
+ 1):
156 vni
= random
.randrange(start_vni
, end_vni
, 1)
157 if vni
not in self
.used_vni
:
159 except Exception as exp
:
160 raise SdnConnectorError("Exception {} occurred while searching a free VNI.".format(exp
))
162 raise SdnConnectorError("Unable to create the virtual network."\
163 " All VNI in VNI range {} are in use.".format(self
.vni_range
))
166 # Aux functions for testing
170 def get_overlay_url(self
):
171 return self
.overlay_url
173 def _create_port(self
, switch_id
, switch_port
, network
, vlan
):
175 1 - Look for virtual port groups for provided switch_id, switch_port using name
176 2 - It the virtual port group does not exist, create it
177 3 - Create virtual machine interface for the indicated network and vlan
179 self
.logger
.debug("create_port: switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
180 switch_id
, switch_port
, network
, vlan
))
182 # 1 - Check if the vpg exists
183 vpg_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
184 vpg
= self
.underlay_api
.get_vpg_by_name(vpg_name
)
186 # 2 - If it does not exist create it
187 vpg_id
, _
= self
.underlay_api
.create_vpg(switch_id
, switch_port
)
189 # Assign vpg_id from vpg
190 vpg_id
= vpg
.get("uuid")
192 # 3 - Check if the vmi alreaady exists
193 vmi_id
, _
= self
.underlay_api
.create_vmi(switch_id
, switch_port
, network
, vlan
)
194 self
.logger
.debug("port created")
196 return vpg_id
, vmi_id
198 def _delete_port(self
, switch_id
, switch_port
, vlan
):
199 self
.logger
.debug("delete port, switch_id: {}, switch_port: {}, vlan: {}".format(switch_id
, switch_port
, vlan
))
201 vpg_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
202 vmi_name
= self
.underlay_api
.get_vmi_name(switch_id
, switch_port
, vlan
)
204 # 1 - Obtain vpg by id (if not vpg_id must have been error creating ig, nothing to be done)
205 vpg_fqdn
= ["default-global-system-config", self
.fabric
, vpg_name
]
206 vpg
= self
.underlay_api
.get_by_fq_name("virtual-port-group", vpg_fqdn
)
208 self
.logger
.warning("vpg: {} to be deleted not found".format(vpg_name
))
210 # 2 - Get vmi interfaces from vpg
211 vmi_list
= vpg
.get("virtual_machine_interface_refs")
213 # must have been an error during port creation when vmi is created
214 # may happen if there has been an error during creation
215 self
.logger
.warning("vpg: {} has not vmi, will delete nothing".format(vpg
))
217 num_vmis
= len(vmi_list
)
219 uuid
= vmi
.get("uuid")
222 if fqdn
[2] == vmi_name
:
223 self
.underlay_api
.unref_vmi_vpg(vpg
.get("uuid"), vmi
.get("uuid"), fqdn
)
224 self
.underlay_api
.delete_vmi(vmi
.get("uuid"))
225 num_vmis
= num_vmis
- 1
227 # 3 - If there are no more vmi delete the vpg
228 if not vmi_list
or num_vmis
== 0:
229 self
.underlay_api
.delete_vpg(vpg
.get("uuid"))
231 def check_credentials(self
):
232 """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
233 user (wim_account.user), and password (wim_account.password)
236 SdnConnectorError: Issues regarding authorization, access to
237 external URLs, etc are detected.
239 self
.logger
.debug("")
241 resp
= self
.underlay_api
.check_auth()
243 raise SdnConnectorError('Empty response')
244 except Exception as e
:
245 self
.logger
.error('Error checking credentials')
246 raise SdnConnectorError('Error checking credentials: {}'.format(str(e
)))
248 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
249 """Monitor the status of the connectivity service established
252 service_uuid (str): UUID of the connectivity service
253 conn_info (dict or None): Information returned by the connector
254 during the service creation/edition and subsequently stored in
258 dict: JSON/YAML-serializable dict that contains a mandatory key
259 ``sdn_status`` associated with one of the following values::
261 {'sdn_status': 'ACTIVE'}
262 # The service is up and running.
264 {'sdn_status': 'INACTIVE'}
265 # The service was created, but the connector
266 # cannot determine yet if connectivity exists
267 # (ideally, the caller needs to wait and check again).
269 {'sdn_status': 'DOWN'}
270 # Connection was previously established,
271 # but an error/failure was detected.
273 {'sdn_status': 'ERROR'}
274 # An error occurred when trying to create the service/
275 # establish the connectivity.
277 {'sdn_status': 'BUILD'}
278 # Still trying to create the service, the caller
279 # needs to wait and check again.
281 Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
282 keys can be used to provide additional status explanation or
283 new information available for the connectivity service.
285 self
.logger
.debug("")
287 resp
= self
.underlay_api
.get_virtual_network(service_uuid
)
289 raise SdnConnectorError('Empty response')
293 # Check if conn_info reports error
294 if conn_info
.get("sdn_status") == "ERROR":
295 return {'sdn_status': 'ERROR', 'sdn_info': conn_info
}
297 return {'sdn_status': 'ACTIVE', 'sdn_info': vnet_info
}
299 return {'sdn_status': 'ERROR', 'sdn_info': 'not found'}
300 except SdnConnectorError
:
302 except HttpException
as e
:
303 self
.logger
.error("Error getting connectivity service: {}".format(e
))
304 raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e
)))
305 except Exception as e
:
306 self
.logger
.error('Exception getting connectivity service info: %s', e
, exc_info
=True)
307 return {'sdn_status': 'ERROR', 'error_msg': str(e
)}
310 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
312 Establish SDN/WAN connectivity between the endpoints
313 :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
314 :param connection_points: (list): each point corresponds to
315 an entry point to be connected. For WIM: from the DC to the transport network.
316 For SDN: Compute/PCI to the transport network. One
317 connection point serves to identify the specific access and
318 some other service parameters, such as encapsulation type.
319 Each item of the list is a dict with:
320 "service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__)
321 In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
322 it will contain the string "device_id:device_interface_id"
323 "service_endpoint_encapsulation_type": None, "dot1q", ...
324 "service_endpoint_encapsulation_info": (dict) with:
325 "vlan": ..., (int, present if encapsulation is dot1q)
326 "vni": ... (int, present if encapsulation is vxlan),
327 "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
329 "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
330 "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
331 "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
332 "switch_port": ... present if mapping has been found for this device_id,device_interface_id
333 "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
334 :param kwargs: For future versions:
335 bandwidth (int): value in kilobytes
336 latency (int): value in milliseconds
337 Other QoS might be passed as keyword arguments.
338 :return: tuple: ``(service_id, conn_info)`` containing:
339 - *service_uuid* (str): UUID of the established connectivity service
340 - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
341 This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
342 **MUST** be JSON/YAML-serializable (plain data structures).
343 :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
344 Provide the parameter http_code
346 # Step 1. Check in the overlay controller the virtual network created by the VIM
347 # Best option: get network id of the VIM as param (if the VIM already created the network),
348 # and do a request to the controller of the virtual networks whose VIM network id is the provided
349 # Next best option: obtain the network by doing a request to the controller
350 # of the virtual networks using the VLAN ID of any service endpoint.
351 # 1.1 Read VLAN ID from a service endpoint
352 # 1.2 Look for virtual networks with "Provider Network" including a VLAN ID.
353 # 1.3 If more than one, ERROR
354 # Step 2. Modify the existing virtual network in the overlay controller
355 # 2.1 Add VNI (VxLAN Network Identifier - one free from the provided range)
356 # 2.2 Add RouteTarget (RT) ('ASN:VNI', ASN = Autonomous System Number, provided as param or read from controller config)
357 # Step 3. Create a virtual network in the underlay controller
358 # 3.1 Create virtual network (name, VNI, RT)
359 # If the network already existed in the overlay controller, we should use the same name
360 # name = 'osm-plugin-' + overlay_name
362 # name = 'osm-plugin-' + VNI
363 self
.logger
.info("create_connectivity_service, service_type: {}, connection_points: {}".
364 format(service_type
, connection_points
))
365 if service_type
.lower() != 'elan':
366 raise SdnConnectorError('Only ELAN network type is supported by Juniper Contrail.')
373 # 1 - Filter connection_points (transform cp to a dictionary with no duplicates)
374 # This data will be returned even if no cp can be created if something is created
377 for cp
in connection_points
:
378 switch_id
= cp
.get("service_endpoint_encapsulation_info").get("switch_dpid")
379 switch_port
= cp
.get("service_endpoint_encapsulation_info").get("switch_port")
380 service_endpoint_id
= cp
.get("service_endpoint_id")
381 vlans
.add(cp
.get("service_endpoint_encapsulation_info").get("vlan"))
382 cp_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
383 add_cp
= work_cps
.get(cp_name
)
386 service_endpoint_ids
= []
387 service_endpoint_ids
.append(service_endpoint_id
)
388 add_cp
= {"service_endpoint_ids": service_endpoint_ids
,
389 "switch_dpid": switch_id
,
390 "switch_port": switch_port
}
391 work_cps
[cp_name
] = add_cp
393 # add service_endpoint_id to list
394 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
395 service_endpoint_ids
.append(service_endpoint_id
)
400 self
.logger
.debug("Provided vlan: {}".format(vlan
))
402 raise SdnConnectorError("Provided more than one vlan")
404 # 2 - Obtain free VNI
405 vni
= self
._generate
_vni
()
406 self
.logger
.debug("VNI: {}".format(vni
))
408 # 3 - Create virtual network (name, VNI, RT), by the moment the name will use VNI
410 while retry
< self
._max
_duplicate
_retry
:
412 vnet_name
= 'osm-plugin-' + str(vni
)
413 vnet_id
, _
= self
.underlay_api
.create_virtual_network(vnet_name
, vni
)
414 self
.used_vni
.add(vni
)
416 except DuplicateFound
as e
:
417 self
.logger
.debug("Duplicate error for vnet_name: {}".format(vnet_name
))
418 self
.used_vni
.add(vni
)
420 if retry
>= self
._max
_duplicate
_retry
:
423 # Try to obtain a new vni
424 vni
= self
._generate
_vni
()
432 "connection_points": work_cps
# dict with port_name as key
435 # 4 - Create a port for each endpoint
436 for cp
in work_cps
.values():
437 switch_id
= cp
.get("switch_dpid")
438 switch_port
= cp
.get("switch_port")
439 vpg_id
, vmi_id
= self
._create
_port
(switch_id
, switch_port
, vnet_name
, vlan
)
440 cp
["vpg_id"] = vpg_id
441 cp
["vmi_id"] = vmi_id
443 self
.logger
.info("created connectivity service, uuid: {}, name: {}".format(vnet_id
, vnet_name
))
444 return vnet_id
, conn_info
446 except Exception as e
:
448 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
449 self
.logger
.error("Error creating connectivity service: {}".format(e
))
451 self
.logger
.error("Error creating connectivity service: {}".format(e
), exc_info
=True)
454 # If nothing is created raise error else return what has been created and mask as error
456 raise SdnConnectorError("Exception create connectivity service: {}".format(str(e
)))
458 conn_info
["sdn_status"] = "ERROR"
459 conn_info
["sdn_info"] = repr(e
)
460 # iterate over not added connection_points and add but marking them as error
461 for cp
in work_cps
.values():
462 if not cp
.get("vmi_id") or not cp
.get("vpg_id"):
463 cp
["sdn_status"] = "ERROR"
464 return vnet_id
, conn_info
466 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
468 Disconnect multi-site endpoints previously connected
470 :param service_uuid: The one returned by create_connectivity_service
471 :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
472 if they do not return None
474 :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
476 self
.logger
.info("delete_connectivity_service vnet_name: {}, connection_points: {}".
477 format(service_uuid
, conn_info
))
480 vnet_uuid
= service_uuid
481 vnet_name
= conn_info
["vnet"]["name"] # always should exist as the network is the first thing created
482 work_cps
= conn_info
["connection_points"]
483 vlan
= conn_info
["vnet"]["vlan"]
485 # 1: For each connection point delete vlan from vpg and it is is the
486 # last one, delete vpg
487 for cp
in work_cps
.values():
488 self
._delete
_port
(cp
.get("switch_dpid"), cp
.get("switch_port"), vlan
)
491 self
.underlay_api
.delete_virtual_network(vnet_uuid
)
492 self
.logger
.info("deleted connectivity_service vnet_name: {}, connection_points: {}".
493 format(service_uuid
, conn_info
))
494 except SdnConnectorError
:
496 except HttpException
as e
:
497 self
.logger
.error("Error deleting connectivity service: {}".format(e
))
498 raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e
)))
499 except Exception as e
:
500 self
.logger
.error("Error deleting connectivity service: {}".format(e
), exc_info
=True)
501 raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e
)))
505 def _get_vlan(connection_points
):
507 for cp
in connection_points
:
508 cp_vlan
= cp
.get("service_endpoint_encapsulation_info").get("vlan")
513 raise SdnConnectorError("More that one cp provided")
516 def edit_connectivity_service(self
, service_uuid
, conn_info
= None, connection_points
= None, **kwargs
):
517 """ Change an existing connectivity service.
519 This method's arguments and return value follow the same convention as
520 :meth:`~.create_connectivity_service`.
522 :param service_uuid: UUID of the connectivity service.
523 :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
524 or edit_connectivity_service
525 :param connection_points: (list): If provided, the old list of connection points will be replaced.
526 :param kwargs: Same meaning that create_connectivity_service
527 :return: dict or None: Information to be updated and stored at the database.
528 When ``None`` is returned, no information should be changed.
529 When an empty dict is returned, the database record will be deleted.
530 **MUST** be JSON/YAML-serializable (plain data structures).
532 SdnConnectorException: In case of error.
534 # 0 - Check if there are connection_points marked as error and delete them
535 # 1 - Compare conn_info (old connection points) and connection_points (new ones to be applied):
536 # Obtain list of connection points to be added and to be deleted
537 # Obtain vlan and check it has not changed
538 # 2 - Obtain network: Check vnet exists and obtain name
539 # 3 - Delete unnecesary ports
541 self
.logger
.info("edit connectivity service, service_uuid: {}, conn_info: {}, "
542 "connection points: {} ".format(service_uuid
, conn_info
, connection_points
))
544 # conn_info should always exist and have connection_points and vnet elements
545 old_cp
= conn_info
.get("connection_points", {})
546 old_vlan
= conn_info
.get("vnet", {}).get("vlan")
548 # Check if an element of old_cp is marked as error, in case it is delete it
549 # Not return a new conn_info in this case because it is only partial information
550 # Current conn_info already marks ports as error
553 for cp
in old_cp
.values():
554 if cp
.get("sdn_status") == "ERROR":
555 switch_id
= cp
.get("switch_dpid")
556 switch_port
= cp
.get("switch_port")
557 self
._delete
_port
(switch_id
, switch_port
, old_vlan
)
558 deleted_ports
.append(self
.underlay_api
.get_vpg_name(switch_id
, switch_port
))
560 for port
in deleted_ports
:
563 # Delete sdn_status and sdn_info if exists (possibly marked as error)
564 if conn_info
.get("vnet",{}).get("sdn_status"):
565 del conn_info
["vnet"]["sdn_status"]
566 except HttpException
as e
:
567 self
.logger
.error("Error trying to delete old ports marked as error: {}".format(e
))
568 raise SdnConnectorError(e
)
569 except SdnConnectorError
as e
:
570 self
.logger
.error("Error trying to delete old ports marked as error: {}".format(e
))
572 except Exception as e
:
573 self
.logger
.error("Error trying to delete old ports marked as error: {}".format(e
), exc_info
=True)
574 raise SdnConnectorError("Error trying to delete old ports marked as error: {}".format(e
))
576 if connection_points
:
578 # Check and obtain what should be added and deleted, if there is an error here raise an exception
582 for cp
in connection_points
:
583 switch_id
= cp
.get("service_endpoint_encapsulation_info").get("switch_dpid")
584 switch_port
= cp
.get("service_endpoint_encapsulation_info").get("switch_port")
585 service_endpoint_id
= cp
.get("service_endpoint_id")
586 vlans
.add(cp
.get("service_endpoint_encapsulation_info").get("vlan"))
587 cp_name
= self
.underlay_api
.get_vpg_name(switch_id
, switch_port
)
588 add_cp
= work_cps
.get(cp_name
)
591 service_endpoint_ids
= []
592 service_endpoint_ids
.append(service_endpoint_id
)
593 add_cp
= {"service_endpoint_ids": service_endpoint_ids
,
594 "switch_dpid": switch_id
,
595 "switch_port": switch_port
}
596 work_cps
[cp_name
] = add_cp
598 # add service_endpoint_id to list
599 service_endpoint_ids
= add_cp
["service_endpoint_ids"]
600 service_endpoint_ids
.append(service_endpoint_id
)
605 self
.logger
.debug("Provided vlan: {}".format(vlan
))
607 raise SdnConnectorError("Provided more than one vlan")
609 old_port_list
= list(old_cp
.keys())
610 port_list
= list(work_cps
.keys())
611 to_delete_ports
= list(set(old_port_list
) - set(port_list
))
612 to_add_ports
= list(set(port_list
) - set(old_port_list
))
613 self
.logger
.debug("ports to delete: {}".format(to_delete_ports
))
614 self
.logger
.debug("ports to add: {}".format(to_add_ports
))
616 # Obtain network (check it is correctly created)
617 vnet
= self
.underlay_api
.get_virtual_network(service_uuid
)
619 vnet_name
= vnet
["name"]
621 raise SdnConnectorError("vnet uuid: {} not found".format(service_uuid
))
623 except SdnConnectorError
:
625 except Exception as e
:
626 self
.logger
.error("Error edit connectivity service: {}".format(e
), exc_info
=True)
627 raise SdnConnectorError("Exception edit connectivity service: {}".format(str(e
)))
629 # Delete unneeded ports and add new ones: if there is an error return conn_info
631 # Connection points returned in con_info should reflect what has (and should as ERROR) be done
632 # Start with old cp dictionary and modify it as we work
633 conn_info_cp
= old_cp
635 # Delete unneeded ports
637 for port_name
in conn_info_cp
.keys():
638 if port_name
in to_delete_ports
:
639 cp
= conn_info_cp
[port_name
]
640 switch_id
= cp
.get("switch_dpid")
641 switch_port
= cp
.get("switch_port")
642 self
.logger
.debug("delete port switch_id, switch_port: {}".format(switch_id
, switch_port
))
643 self
._delete
_port
(switch_id
, switch_port
, vlan
)
644 deleted_ports
.append(port_name
)
647 for port_name
in deleted_ports
:
648 del conn_info_cp
[port_name
]
651 for port_name
, cp
in work_cps
.items():
652 if port_name
in to_add_ports
:
653 switch_id
= cp
.get("switch_dpid")
654 switch_port
= cp
.get("switch_port")
655 self
.logger
.debug("add port switch_id, switch_port: {}".format(switch_id
, switch_port
))
656 vpg_id
, vmi_id
= self
._create
_port
(switch_id
, switch_port
, vnet_name
, vlan
)
658 cp_added
["vpg_id"] = vpg_id
659 cp_added
["vmi_id"] = vmi_id
660 conn_info_cp
[port_name
] = cp_added
661 # replace endpoints in case they have changed
662 conn_info_cp
[port_name
]["service_endpoint_ids"] = cp
["service_endpoint_ids"]
664 conn_info
["connection_points"] = conn_info_cp
667 except Exception as e
:
669 if isinstance(e
, SdnConnectorError
) or isinstance(e
, HttpException
):
670 self
.logger
.error("Error edit connectivity service: {}".format(e
), exc_info
=True)
672 self
.logger
.error("Error edit connectivity service: {}".format(e
))
674 # There has been an error mount conn_info_cp marking as error cp that should
675 # have been deleted but have not or should have been added
676 for port_name
, cp
in conn_info_cp
.items():
677 if port_name
in to_delete_ports
:
678 cp
["sdn_status"] = "ERROR"
680 for port_name
, cp
in work_cps
.items():
681 curr_cp
= conn_info_cp
.get(port_name
)
683 cp_error
= work_cps
.get(port_name
).copy()
684 cp_error
["sdn_status"] = "ERROR"
685 conn_info_cp
[port_name
] = cp_error
686 conn_info_cp
[port_name
]["service_endpoint_ids"] = cp
["service_endpoint_ids"]
688 conn_info
["sdn_status"] = "ERROR"
689 conn_info
["sdn_info"] = repr(e
)
690 conn_info
["connection_points"] = conn_info_cp
694 # Connection points have not changed, so do nothing
695 self
.logger
.info("no new connection_points provided, nothing to be done")
699 if __name__
== '__main__':
701 log_format
= "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
702 log_formatter
= logging
.Formatter(log_format
, datefmt
='%Y-%m-%dT%H:%M:%S')
703 handler
= logging
.StreamHandler()
704 handler
.setFormatter(log_formatter
)
705 logger
= logging
.getLogger('openmano.sdnconn.junipercontrail')
706 #logger.setLevel(level=logging.ERROR)
707 #logger.setLevel(level=logging.INFO)
708 logger
.setLevel(level
=logging
.DEBUG
)
709 logger
.addHandler(handler
)
712 with
open('test.yaml') as f
:
713 config
= yaml
.safe_load(f
.read())
714 wim
= {'wim_url': config
.pop('wim_url')}
715 wim_account
= {'user': config
.pop('user'), 'password': config
.pop('password')}
716 logger
.info('wim: {}, wim_account: {}, config: {}'.format(wim
, wim_account
, config
))
719 juniper_contrail
= JuniperContrail(wim
=wim
, wim_account
=wim_account
, config
=config
, logger
=logger
)
724 vni
= juniper_contrail
._generate
_vni
()
725 juniper_contrail
.used_vni
.add(vni
)
726 print(juniper_contrail
.used_vni
)
727 #juniper_contrail.used_vni.remove(1000003)
728 print(juniper_contrail
.used_vni
)
730 vni
= juniper_contrail
._generate
_vni
()
731 juniper_contrail
.used_vni
.add(vni
)
732 print(juniper_contrail
.used_vni
)
735 # 0. Check credentials
736 print('0. Check credentials')
737 #juniper_contrail.check_credentials()
739 # 1 - Create and delete connectivity service
741 "service_endpoint_id": "0000:83:11.4",
742 "service_endpoint_encapsulation_type": "dot1q",
743 "service_endpoint_encapsulation_info": {
744 "switch_dpid": "LEAF-1",
745 "switch_port": "xe-0/0/17",
750 "service_endpoint_id": "0000:81:10.3",
751 "service_endpoint_encapsulation_type": "dot1q",
752 "service_endpoint_encapsulation_info": {
753 "switch_dpid": "LEAF-2",
754 "switch_port": "xe-0/0/16",
759 "service_endpoint_id": "0000:08:11.7",
760 "service_endpoint_encapsulation_type": "dot1q",
761 "service_endpoint_encapsulation_info": {
762 "switch_dpid": "LEAF-2",
763 "switch_port": "xe-0/0/16",
768 "service_endpoint_id": "0000:83:10.4",
769 "service_endpoint_encapsulation_type": "dot1q",
770 "service_endpoint_encapsulation_info": {
771 "switch_dpid": "LEAF-1",
772 "switch_port": "xe-0/0/17",
777 # 1 - Define connection points
778 logger
.debug("create first connection service")
779 print("Create connectivity service")
780 connection_points
= [conn_point_0
, conn_point_1
]
781 service_id
, conn_info
= juniper_contrail
.create_connectivity_service("ELAN", connection_points
)
782 logger
.info("Created connectivity service 1")
783 logger
.info(service_id
)
784 logger
.info(yaml
.safe_dump(conn_info
, indent
=4, default_flow_style
=False))
786 logger
.debug("create second connection service")
787 print("Create connectivity service")
788 connection_points
= [conn_point_2
, conn_point_3
]
789 service_id2
, conn_info2
= juniper_contrail
.create_connectivity_service("ELAN", connection_points
)
790 logger
.info("Created connectivity service 2")
791 logger
.info(service_id2
)
792 logger
.info(yaml
.safe_dump(conn_info2
, indent
=4, default_flow_style
=False))
794 logger
.debug("Delete connectivity service 1")
795 juniper_contrail
.delete_connectivity_service(service_id
, conn_info
)
796 logger
.debug("Delete Ok")
798 logger
.debug("Delete connectivity service 2")
799 juniper_contrail
.delete_connectivity_service(service_id2
, conn_info2
)
800 logger
.debug("Delete Ok")