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