#
import logging
-import uuid
-import copy
-import json
import yaml
+import random
-#import requests
-#from requests.auth import HTTPBasicAuth
-from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError
-from osm_rosdn_juniper_contrail.rest_lib import Http
+from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
-from io import BytesIO
-import pycurl
+# from osm_rosdn_juniper_contrail.rest_lib import ContrailHttp
+# from osm_rosdn_juniper_contrail.rest_lib import NotFound
+from osm_rosdn_juniper_contrail.rest_lib import DuplicateFound
+from osm_rosdn_juniper_contrail.rest_lib import HttpException
+from osm_rosdn_juniper_contrail.sdn_api import UnderlayApi
class JuniperContrail(SdnConnectorBase):
whose API details can be found in these links:
- https://github.com/tonyliu0592/contrail/wiki/API-Configuration-REST
- - https://www.juniper.net/documentation/en_US/contrail19/information-products/pathway-pages/api-guide-1910/tutorial_with_rest.html
+ - https://www.juniper.net/documentation/en_US/contrail19/information-products/pathway-pages/api-guide-1910/
+ tutorial_with_rest.html
- https://github.com/tonyliu0592/contrail-toolbox/blob/master/sriov/sriov
"""
- _WIM_LOGGER = "openmano.sdnconn.junipercontrail"
+
+ _WIM_LOGGER = "ro.sdn.junipercontrail"
def __init__(self, wim, wim_account, config=None, logger=None):
"""
datacenter_id vim_account vim_account
id: (internal, do not use)
wim_id: (internal, do not use)
- :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
+ :param logger (logging.Logger): optional logger object. If none is passed 'ro.sdn.sdnconn' is used.
"""
self.logger = logger or logging.getLogger(self._WIM_LOGGER)
- self.logger.debug('wim: {}, wim_account: {}, config: {}'.format(wim, wim_account, config))
+ self.logger.debug(
+ "wim: {}, wim_account: {}, config: {}".format(wim, wim_account, config)
+ )
super().__init__(wim, wim_account, config, logger)
self.user = wim_account.get("user")
self.password = wim_account.get("password")
- url = wim.get("wim_url")
+ url = wim.get("wim_url") # underlay url
auth_url = None
- overlay_url = None
self.project = None
self.domain = None
self.asn = None
self.fabric = None
+ overlay_url = None
self.vni_range = None
+
if config:
auth_url = config.get("auth_url")
- overlay_url = config.get("overlay_url")
self.project = config.get("project")
self.domain = config.get("domain")
self.asn = config.get("asn")
self.fabric = config.get("fabric")
+ self.overlay_url = config.get("overlay_url")
self.vni_range = config.get("vni_range")
if not url:
raise SdnConnectorError("'url' must be provided")
+
if not url.startswith("http"):
url = "http://" + url
+
if not url.endswith("/"):
url = url + "/"
+
self.url = url
- if not self.project:
- raise SdnConnectorError("'project' must be provided")
- if not self.asn:
- # TODO: Get ASN from controller config; otherwise raise ERROR for the moment
- raise SdnConnectorError("'asn' was not provided and was not possible to obtain it")
- if not self.fabric:
- # TODO: Get FABRIC from controller config; otherwise raise ERROR for the moment
- raise SdnConnectorError("'fabric' was not provided and was not possible to obtain it")
- if not self.domain:
- self.domain = 'default'
- self.logger.info("No domain was provided. Using 'default'")
if not self.vni_range:
- self.vni_range = ['1000001-2000000']
+ self.vni_range = ["1000001-2000000"]
self.logger.info("No vni_range was provided. Using ['1000001-2000000']")
+
self.used_vni = set()
+ if auth_url:
+ if not auth_url.startswith("http"):
+ auth_url = "http://" + auth_url
+
+ if not auth_url.endswith("/"):
+ auth_url = auth_url + "/"
+
+ self.auth_url = auth_url
+
if overlay_url:
if not overlay_url.startswith("http"):
overlay_url = "http://" + overlay_url
+
if not overlay_url.endswith("/"):
overlay_url = overlay_url + "/"
+
self.overlay_url = overlay_url
- if auth_url:
- if not auth_url.startswith("http"):
- auth_url = "http://" + auth_url
- if not auth_url.endswith("/"):
- auth_url = auth_url + "/"
- self.auth_url = auth_url
+ if not self.project:
+ raise SdnConnectorError("'project' must be provided")
- # Init http lib
- self.http = Http(self.logger)
-
- # Init http headers for all requests
- self.headers = {'Content-Type': 'application/json'}
- self.http_header = ['{}: {}'.format(key, val)
- for (key, val) in list(self.headers.items())]
-
- auth_dict = {}
- auth_dict['auth'] = {}
- auth_dict['auth']['scope'] = {}
- auth_dict['auth']['scope']['project'] = {}
- auth_dict['auth']['scope']['project']['domain'] = {}
- auth_dict['auth']['scope']['project']['domain']["id"] = self.domain
- auth_dict['auth']['scope']['project']['name'] = self.project
- auth_dict['auth']['identity'] = {}
- auth_dict['auth']['identity']['methods'] = ['password']
- auth_dict['auth']['identity']['password'] = {}
- auth_dict['auth']['identity']['password']['user'] = {}
- auth_dict['auth']['identity']['password']['user']['name'] = self.user
- auth_dict['auth']['identity']['password']['user']['password'] = self.password
- auth_dict['auth']['identity']['password']['user']['domain'] = {}
- auth_dict['auth']['identity']['password']['user']['domain']['id'] = self.domain
- self.auth_dict = auth_dict
- self.token = None
+ if not self.asn:
+ # TODO: Get ASN from controller config; otherwise raise ERROR for the moment
+ raise SdnConnectorError(
+ "'asn' was not provided and it was not possible to obtain it"
+ )
- self.logger.info("Juniper Contrail Connector Initialized.")
+ if not self.fabric:
+ # TODO: Get FABRIC from controller config; otherwise raise ERROR for the moment
+ raise SdnConnectorError(
+ "'fabric' was not provided and was not possible to obtain it"
+ )
+ if not self.domain:
+ self.domain = "default-domain"
+ self.logger.info("No domain was provided. Using 'default-domain'")
+
+ underlay_api_config = {
+ "auth_url": self.auth_url,
+ "project": self.project,
+ "domain": self.domain,
+ "asn": self.asn,
+ "fabric": self.fabric,
+ }
+ self.underlay_api = UnderlayApi(
+ url,
+ underlay_api_config,
+ user=self.user,
+ password=self.password,
+ logger=logger,
+ )
+
+ self._max_duplicate_retry = 2
+ self.logger.info("Juniper Contrail Connector Initialized.")
def _generate_vni(self):
"""
- Method to get unused VxLAN Network Identifier (VNI)
+ Method to get unused VxLAN Network Identifier (VNI)
Args:
None
Returns:
VNI
"""
- #find unused VLAN ID
+ # find unused VLAN ID
for vlanID_range in self.vni_range:
try:
- start_vni , end_vni = map(int, vlanID_range.replace(" ", "").split("-"))
- for vni in range(start_vni, end_vni + 1):
+ start_vni, end_vni = map(int, vlanID_range.replace(" ", "").split("-"))
+
+ for i in range(start_vni, end_vni + 1):
+ vni = random.randrange(start_vni, end_vni, 1)
+
if vni not in self.used_vni:
return vni
except Exception as exp:
- raise SdnConnectorError("Exception {} occurred while searching a free VNI.".format(exp))
+ raise SdnConnectorError(
+ "Exception {} occurred while searching a free VNI.".format(exp)
+ )
else:
- raise SdnConnectorError("Unable to create the virtual network."\
- " All VNI in VNI range {} are in use.".format(self.vni_range))
-
-
- def _get_token(self):
- self.logger.debug('Current Token:'.format(str(self.token)))
- auth_url = self.auth_url + 'auth/tokens'
- if self.token is None:
- if not self.auth_url:
- self.token = ""
- http_code, resp = self.http.post_cmd(url=auth_url, headers=self.http_header,
- postfields_dict=self.auth_dict,
- return_header = 'x-subject-token')
- self.token = resp
- self.logger.debug('Token: '.format(self.token))
-
- if self.token:
- self.headers['X-Auth-Token'] = self.token
- else:
- self.headers.pop('X-Auth-Token', None)
- http_header = ['{}: {}'.format(key, val)
- for (key, val) in list(self.headers.items())]
+ raise SdnConnectorError(
+ "Unable to create the virtual network."
+ " All VNI in VNI range {} are in use.".format(self.vni_range)
+ )
-
# Aux functions for testing
def get_url(self):
return self.url
-
def get_overlay_url(self):
return self.overlay_url
- # Virtual network operations
-
- def _create_virtual_network(self, controller_url, name, vni):
- routetarget = '{}:{}'.format(self.asn,vni)
- vnet_dict = {
- "virtual-network": {
- "virtual_network_properties": {
- "vxlan_network_identifier": vni,
- },
- "parent_type": "project",
- "fq_name": [
- "default-domain",
- "admin",
- name
- ],
- "route_target_list": {
- "route_target": [
- "target:" + routetarget
- ]
- }
- }
- }
- self._get_token()
- endpoint = controller_url + 'virtual-networks'
- http_code, resp = self.http.post_cmd(url = endpoint,
- headers = self.http_header,
- postfields_dict = vnet_dict)
- if http_code not in (200, 201, 202, 204) or not resp:
- raise SdnConnectorError('Unexpected http status code, or empty response')
- vnet_info = json.loads(resp)
- self.logger.debug("vnet_info: {}".format(vnet_info))
- return vnet_info.get("virtual-network").get('uuid'), vnet_info.get("virtual-network")
-
-
- def _get_virtual_networks(self, controller_url):
- self._get_token()
- endpoint = controller_url + 'virtual-networks'
- print(endpoint)
- http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
- if http_code not in (200, 201, 202, 204) or not resp:
- raise SdnConnectorError('Unexpected http status code, or empty response')
- vnets_info = json.loads(resp)
- self.logger.debug("vnets_info: {}".format(vnets_info))
- return vnets_info.get('virtual-networks')
-
-
- def _get_virtual_network(self, controller_url, network_id):
- self._get_token()
- endpoint = controller_url + 'virtual-network/{}'.format(network_id)
- http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
- if http_code not in (200, 201, 202, 204) or not resp:
- if http_code == 404:
- return None
- raise SdnConnectorError('Unexpected http status code, or empty response')
- vnet_info = json.loads(resp)
- self.logger.debug("vnet_info: {}".format(vnet_info))
- return vnet_info.get("virtual-network")
-
-
- def _delete_virtual_network(self, controller_url, network_id):
- self._get_token()
- endpoint = controller_url + 'virtual-network/{}'.format(network_id)
- http_code, _ = self.http.delete_cmd(url=endpoint, headers=self.http_header)
- if http_code not in (200, 201, 202, 204):
- raise SdnConnectorError('Unexpected http status code')
- return
-
-
- # Virtual port group operations
-
- def _create_vpg(self, controller_url, switch_id, switch_port, network, vlan):
- vpg_dict = {
- "virtual-port-group": {
- }
- }
- self._get_token()
- endpoint = controller_url + 'virtual-port-groups'
- http_code, resp = self.http.post_cmd(url = endpoint,
- headers = self.http_header,
- postfields_dict = vpg_dict)
- if http_code not in (200, 201, 202, 204) or not resp:
- raise SdnConnectorError('Unexpected http status code, or empty response')
- vpg_info = json.loads(resp)
- self.logger.debug("vpg_info: {}".format(vpg_info))
- return vpg_info.get("virtual-port-group").get('uuid'), vpg_info.get("virtual-port-group")
-
-
- def _get_vpgs(self, controller_url):
- self._get_token()
- endpoint = controller_url + 'virtual-port-groups'
- http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
- if http_code not in (200, 201, 202, 204) or not resp:
- raise SdnConnectorError('Unexpected http status code, or empty response')
- vpgs_info = json.loads(resp)
- self.logger.debug("vpgs_info: {}".format(vpgs_info))
- return vpgs_info.get('virtual-port-groups')
-
-
- def _get_vpg(self, controller_url, vpg_id):
- self._get_token()
- endpoint = controller_url + 'virtual-port-group/{}'.format(vpg_id)
- http_code, resp = self.http.get_cmd(url=endpoint, headers=self.http_header)
- if http_code not in (200, 201, 202, 204) or not resp:
- if http_code == 404:
- return None
- raise SdnConnectorError('Unexpected http status code, or empty response')
- vpg_info = json.loads(resp)
- self.logger.debug("vpg_info: {}".format(vpg_info))
- return vpg_info.get("virtual-port-group")
-
-
- def _delete_vpg(self, controller_url, vpg_id):
- self._get_token()
- endpoint = controller_url + 'virtual-port-group/{}'.format(vpg_id)
- http_code, resp = self.http.delete_cmd(url=endpoint, headers=self.http_header)
- if http_code not in (200, 201, 202, 204):
- raise SdnConnectorError('Unexpected http status code')
- return
+ def _create_port(self, switch_id, switch_port, network, vlan):
+ """
+ 1 - Look for virtual port groups for provided switch_id, switch_port using name
+ 2 - It the virtual port group does not exist, create it
+ 3 - Create virtual machine interface for the indicated network and vlan
+ """
+ self.logger.debug(
+ "create_port: switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
+ switch_id, switch_port, network, vlan
+ )
+ )
+
+ # 1 - Check if the vpg exists
+ vpg_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
+ vpg = self.underlay_api.get_vpg_by_name(vpg_name)
+
+ if not vpg:
+ # 2 - If it does not exist create it
+ vpg_id, _ = self.underlay_api.create_vpg(switch_id, switch_port)
+ else:
+ # Assign vpg_id from vpg
+ vpg_id = vpg.get("uuid")
+
+ # 3 - Check if the vmi alreaady exists
+ vmi_id, _ = self.underlay_api.create_vmi(switch_id, switch_port, network, vlan)
+ self.logger.debug("port created")
+
+ return vpg_id, vmi_id
+
+ def _delete_port(self, switch_id, switch_port, vlan):
+ self.logger.debug(
+ "delete port, switch_id: {}, switch_port: {}, vlan: {}".format(
+ switch_id, switch_port, vlan
+ )
+ )
+
+ vpg_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
+ vmi_name = self.underlay_api.get_vmi_name(switch_id, switch_port, vlan)
+
+ # 1 - Obtain vpg by id (if not vpg_id must have been error creating ig, nothing to be done)
+ vpg_fqdn = ["default-global-system-config", self.fabric, vpg_name]
+ vpg = self.underlay_api.get_by_fq_name("virtual-port-group", vpg_fqdn)
+
+ if not vpg:
+ self.logger.warning("vpg: {} to be deleted not found".format(vpg_name))
+ else:
+ # 2 - Get vmi interfaces from vpg
+ vmi_list = vpg.get("virtual_machine_interface_refs")
+
+ if not vmi_list:
+ # must have been an error during port creation when vmi is created
+ # may happen if there has been an error during creation
+ self.logger.warning(
+ "vpg: {} has not vmi, will delete nothing".format(vpg)
+ )
+ else:
+ num_vmis = len(vmi_list)
+
+ for vmi in vmi_list:
+ fqdn = vmi.get("to")
+ # check by name
+ if fqdn[2] == vmi_name:
+ self.underlay_api.unref_vmi_vpg(
+ vpg.get("uuid"), vmi.get("uuid"), fqdn
+ )
+ self.underlay_api.delete_vmi(vmi.get("uuid"))
+ num_vmis = num_vmis - 1
+
+ # 3 - If there are no more vmi delete the vpg
+ if not vmi_list or num_vmis == 0:
+ self.underlay_api.delete_vpg(vpg.get("uuid"))
def check_credentials(self):
"""Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
external URLs, etc are detected.
"""
self.logger.debug("")
- self._get_token()
+
try:
- http_code, resp = self.http.get_cmd(url=self.auth_url, headers=self.http_header)
- if http_code not in (200, 201, 202, 204) or not resp:
- raise SdnConnectorError('Unexpected http status code, or empty response')
+ resp = self.underlay_api.check_auth()
+ if not resp:
+ raise SdnConnectorError("Empty response")
except Exception as e:
- self.logger.error('Error checking credentials')
- raise SdnConnectorError('Error checking credentials', http_code=http_code)
+ self.logger.error("Error checking credentials")
+ raise SdnConnectorError("Error checking credentials: {}".format(str(e)))
def get_connectivity_service_status(self, service_uuid, conn_info=None):
"""Monitor the status of the connectivity service established
new information available for the connectivity service.
"""
self.logger.debug("")
- self._get_token()
+
try:
- http_code, resp = self.http.get_cmd(endpoint='virtual-network/{}'.format(service_uuid))
- if http_code not in (200, 201, 202, 204) or not resp:
- raise SdnConnectorError('Unexpected http status code, or empty response')
+ resp = self.underlay_api.get_virtual_network(service_uuid)
+ if not resp:
+ raise SdnConnectorError("Empty response")
+
if resp:
- vnet_info = json.loads(resp)
- return {'sdn_status': 'ACTIVE', 'sdn_info': vnet_info['virtual-network']}
+ vnet_info = resp
+
+ # Check if conn_info reports error
+ if conn_info.get("sdn_status") == "ERROR":
+ return {"sdn_status": "ERROR", "sdn_info": conn_info}
+ else:
+ return {"sdn_status": "ACTIVE", "sdn_info": vnet_info}
else:
- return {'sdn_status': 'ERROR', 'sdn_info': 'not found'}
+ return {"sdn_status": "ERROR", "sdn_info": "not found"}
+ except SdnConnectorError:
+ raise
+ except HttpException as e:
+ self.logger.error("Error getting connectivity service: {}".format(e))
+
+ raise SdnConnectorError(
+ "Exception deleting connectivity service: {}".format(str(e))
+ )
except Exception as e:
- self.logger.error('Exception getting connectivity service info: %s', e)
- return {'sdn_status': 'ERROR', 'error_msg': str(e)}
+ self.logger.error(
+ "Exception getting connectivity service info: %s", e, exc_info=True
+ )
+ return {"sdn_status": "ERROR", "error_msg": str(e)}
def create_connectivity_service(self, service_type, connection_points, **kwargs):
"""
:raises: SdnConnectorException: In case of error. Nothing should be created in this case.
Provide the parameter http_code
"""
- self.logger.debug("create_connectivity_service, service_type: {}, connection_points: {}".
- format(service_type, connection_points))
- if service_type.lower() != 'elan':
- raise SdnConnectorError('Only ELAN network type is supported by Juniper Contrail.')
-
# Step 1. Check in the overlay controller the virtual network created by the VIM
# Best option: get network id of the VIM as param (if the VIM already created the network),
# and do a request to the controller of the virtual networks whose VIM network id is the provided
# 1.3 If more than one, ERROR
# Step 2. Modify the existing virtual network in the overlay controller
# 2.1 Add VNI (VxLAN Network Identifier - one free from the provided range)
- # 2.2 Add RouteTarget (RT) ('ASN:VNI', ASN = Autonomous System Number, provided as param or read from controller config)
+ # 2.2 Add RouteTarget (RT) ('ASN:VNI', ASN = Autonomous System Number, provided as param or read from
+ # controller config)
# Step 3. Create a virtual network in the underlay controller
# 3.1 Create virtual network (name, VNI, RT)
# If the network already existed in the overlay controller, we should use the same name
# name = 'osm-plugin-' + overlay_name
# Else:
# name = 'osm-plugin-' + VNI
+ self.logger.info(
+ "create_connectivity_service, service_type: {}, connection_points: {}".format(
+ service_type, connection_points
+ )
+ )
+
+ if service_type.lower() != "elan":
+ raise SdnConnectorError(
+ "Only ELAN network type is supported by Juniper Contrail."
+ )
+
try:
- name = 'test-test-1'
- vni = 999999
- network_id, network_info = self._create_virtual_network(self.url, name, vni)
+ # Initialize data
+ conn_info = None
+
+ # 1 - Filter connection_points (transform cp to a dictionary with no duplicates)
+ # This data will be returned even if no cp can be created if something is created
+ work_cps = {}
+ for cp in connection_points:
+ switch_id = cp.get("service_endpoint_encapsulation_info").get(
+ "switch_dpid"
+ )
+ switch_port = cp.get("service_endpoint_encapsulation_info").get(
+ "switch_port"
+ )
+ service_endpoint_id = cp.get("service_endpoint_id")
+ cp_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
+ add_cp = work_cps.get(cp_name)
+
+ if not add_cp:
+ # check cp has vlan
+ vlan = cp.get("service_endpoint_encapsulation_info").get("vlan")
+
+ if vlan:
+ # add cp to dict
+ service_endpoint_ids = []
+ service_endpoint_ids.append(service_endpoint_id)
+ add_cp = {
+ "service_endpoint_ids": service_endpoint_ids,
+ "switch_dpid": switch_id,
+ "switch_port": switch_port,
+ "vlan": vlan,
+ }
+ work_cps[cp_name] = add_cp
+ else:
+ self.logger.warning(
+ "cp service_endpoint_id : {} has no vlan, ignore".format(
+ service_endpoint_id
+ )
+ )
+ else:
+ # add service_endpoint_id to list
+ service_endpoint_ids = add_cp["service_endpoint_ids"]
+ service_endpoint_ids.append(service_endpoint_id)
+
+ # 2 - Obtain free VNI
+ vni = self._generate_vni()
+ self.logger.debug("VNI: {}".format(vni))
+
+ # 3 - Create virtual network (name, VNI, RT), by the moment the name will use VNI
+ retry = 0
+ while retry < self._max_duplicate_retry:
+ try:
+ vnet_name = "osm-plugin-" + str(vni)
+ vnet_id, _ = self.underlay_api.create_virtual_network(
+ vnet_name, vni
+ )
+ self.used_vni.add(vni)
+ break
+ except DuplicateFound as e:
+ self.logger.debug(
+ "Duplicate error for vnet_name: {}".format(vnet_name)
+ )
+ self.used_vni.add(vni)
+ retry += 1
+
+ if retry >= self._max_duplicate_retry:
+ raise e
+ else:
+ # Try to obtain a new vni
+ vni = self._generate_vni()
+ continue
+
+ conn_info = {
+ "vnet": {
+ "uuid": vnet_id,
+ "name": vnet_name,
+ },
+ "connection_points": work_cps, # dict with port_name as key
+ }
+
+ # 4 - Create a port for each endpoint
+ for cp in work_cps.values():
+ switch_id = cp.get("switch_dpid")
+ switch_port = cp.get("switch_port")
+ vlan = cp.get("vlan")
+ vpg_id, vmi_id = self._create_port(
+ switch_id, switch_port, vnet_name, vlan
+ )
+ cp["vpg_id"] = vpg_id
+ cp["vmi_id"] = vmi_id
+
+ self.logger.info(
+ "created connectivity service, uuid: {}, name: {}".format(
+ vnet_id, vnet_name
+ )
+ )
+
+ return vnet_id, conn_info
+ except Exception as e:
+ # Log error
+ if isinstance(e, SdnConnectorError) or isinstance(e, HttpException):
+ self.logger.error("Error creating connectivity service: {}".format(e))
+ else:
+ self.logger.error(
+ "Error creating connectivity service: {}".format(e), exc_info=True
+ )
+
+ # If nothing is created raise error else return what has been created and mask as error
+ if not conn_info:
+ raise SdnConnectorError(
+ "Exception create connectivity service: {}".format(str(e))
+ )
+ else:
+ conn_info["sdn_status"] = "ERROR"
+ conn_info["sdn_info"] = repr(e)
+ # iterate over not added connection_points and add but marking them as error
+ for cp in work_cps.values():
+ if not cp.get("vmi_id") or not cp.get("vpg_id"):
+ cp["sdn_status"] = "ERROR"
+
+ return vnet_id, conn_info
+
+ def delete_connectivity_service(self, service_uuid, conn_info=None):
+ """
+ Disconnect multi-site endpoints previously connected
+
+ :param service_uuid: The one returned by create_connectivity_service
+ :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
+ if they do not return None
+ :return: None
+ :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
+ """
+ self.logger.info(
+ "delete_connectivity_service vnet_name: {}, connection_points: {}".format(
+ service_uuid, conn_info
+ )
+ )
+
+ try:
+ vnet_uuid = service_uuid
+ # vnet_name = conn_info["vnet"]["name"]
+ # always should exist as the network is the first thing created
+ work_cps = conn_info["connection_points"]
+
+ # 1: For each connection point delete vlan from vpg and it is is the
+ # last one, delete vpg
+ for cp in work_cps.values():
+ self._delete_port(
+ cp.get("switch_dpid"), cp.get("switch_port"), cp.get("vlan")
+ )
+
+ # 2: Delete vnet
+ self.underlay_api.delete_virtual_network(vnet_uuid)
+ self.logger.info(
+ "deleted connectivity_service vnet_uuid: {}, connection_points: {}".format(
+ service_uuid, conn_info
+ )
+ )
except SdnConnectorError:
- raise SdnConnectorError('Failed to create connectivity service {}'.format(name))
+ raise
+ except HttpException as e:
+ self.logger.error("Error deleting connectivity service: {}".format(e))
+
+ raise SdnConnectorError(
+ "Exception deleting connectivity service: {}".format(str(e))
+ )
except Exception as e:
- self.logger.error('Exception creating connection_service: %s', e, exc_info=True)
- raise SdnConnectorError("Exception creating connectivity service: {}".format(str(e)))
- return service_id
+ self.logger.error(
+ "Error deleting connectivity service: {}".format(e),
+ exc_info=True,
+ )
+ raise SdnConnectorError(
+ "Exception deleting connectivity service: {}".format(str(e))
+ )
- def edit_connectivity_service(self, service_uuid, conn_info = None, connection_points = None, **kwargs):
- """ Change an existing connectivity service.
+ def edit_connectivity_service(
+ self, service_uuid, conn_info=None, connection_points=None, **kwargs
+ ):
+ """Change an existing connectivity service.
This method's arguments and return value follow the same convention as
:meth:`~.create_connectivity_service`.
Raises:
SdnConnectorException: In case of error.
"""
- #TODO: to be done. This comes from ONOS VPLS plugin
- self.logger.debug("edit connectivity service, service_uuid: {}, conn_info: {}, "
- "connection points: {} ".format(service_uuid, conn_info, connection_points))
-
- conn_info = conn_info or []
- # Obtain current configuration
- config_orig = self._get_onos_netconfig()
- config = copy.deepcopy(config_orig)
-
- # get current service data and check if it does not exists
- #TODO: update
- for vpls in config.get('apps', {}).get('org.onosproject.vpls', {}).get('vpls', {}).get('vplsList', {}):
- if vpls['name'] == service_uuid:
- self.logger.debug("service exists")
- curr_interfaces = vpls.get("interfaces", [])
- curr_encapsulation = vpls.get("encapsulation")
- break
- else:
- raise SdnConnectorError("service uuid: {} does not exist".format(service_uuid))
-
- self.logger.debug("current interfaces: {}".format(curr_interfaces))
- self.logger.debug("current encapsulation: {}".format(curr_encapsulation))
-
- # new interfaces names
- new_interfaces = [port['service_endpoint_id'] for port in new_connection_points]
-
- # obtain interfaces to delete, list will contain port
- ifs_delete = list(set(curr_interfaces) - set(new_interfaces))
- ifs_add = list(set(new_interfaces) - set(curr_interfaces))
- self.logger.debug("interfaces to delete: {}".format(ifs_delete))
- self.logger.debug("interfaces to add: {}".format(ifs_add))
-
- # check if some data of the interfaces that already existed has changed
- # in that case delete it and add it again
- ifs_remain = list(set(new_interfaces) & set(curr_interfaces))
- for port in connection_points:
- if port['service_endpoint_id'] in ifs_remain:
- # check if there are some changes
- curr_port_name, curr_vlan = self._get_current_port_data(config, port['service_endpoint_id'])
- new_port_name = 'of:{}/{}'.format(port['service_endpoint_encapsulation_info']['switch_dpid'],
- port['service_endpoint_encapsulation_info']['switch_port'])
- new_vlan = port['service_endpoint_encapsulation_info']['vlan']
- if (curr_port_name != new_port_name or curr_vlan != new_vlan):
- self.logger.debug("TODO: must update data interface: {}".format(port['service_endpoint_id']))
- ifs_delete.append(port['service_endpoint_id'])
- ifs_add.append(port['service_endpoint_id'])
-
- new_encapsulation = self._get_encapsulation(connection_points)
-
+ # 0 - Check if there are connection_points marked as error and delete them
+ # 1 - Compare conn_info (old connection points) and connection_points (new ones to be applied):
+ # Obtain list of connection points to be added and to be deleted
+ # Obtain vlan and check it has not changed
+ # 2 - Obtain network: Check vnet exists and obtain name
+ # 3 - Delete unnecesary ports
+ # 4 - Add new ports
+ self.logger.info(
+ "edit connectivity service, service_uuid: {}, conn_info: {}, "
+ "connection points: {} ".format(service_uuid, conn_info, connection_points)
+ )
+
+ # conn_info should always exist and have connection_points and vnet elements
+ old_cp = conn_info.get("connection_points", {})
+
+ # Check if an element of old_cp is marked as error, in case it is delete it
+ # Not return a new conn_info in this case because it is only partial information
+ # Current conn_info already marks ports as error
try:
- # Delete interfaces, only will delete interfaces that are in provided conn_info
- # because these are the ones that have been created for this service
- if ifs_delete:
- for port in config['ports'].values():
- for port_interface in port['interfaces']:
- interface_name = port_interface['name']
- self.logger.debug("interface name: {}".format(port_interface['name']))
- if interface_name in ifs_delete and interface_name in conn_info:
- self.logger.debug("delete interface name: {}".format(interface_name))
- port['interfaces'].remove(port_interface)
- conn_info.remove(interface_name)
-
- # Add new interfaces
- for port in connection_points:
- if port['service_endpoint_id'] in ifs_add:
- created_ifz = self._append_port_to_config(port, config)
- if created_ifz:
- conn_info.append(created_ifz[1])
- self._pop_last_update_time(config)
- self._post_netconfig(config)
-
- self.logger.debug("contrail config after updating interfaces: {}".format(config))
- self.logger.debug("conn_info after updating interfaces: {}".format(conn_info))
-
- # Update interfaces list in vpls service
- for vpls in config.get('apps', {}).get('org.onosproject.vpls', {}).get('vpls', {}).get('vplsList', {}):
- if vpls['name'] == service_uuid:
- vpls['interfaces'] = new_interfaces
- vpls['encapsulation'] = new_encapsulation
-
- self._pop_last_update_time(config)
- self._post_netconfig(config)
- return conn_info
+ deleted_ports = []
+ for cp in old_cp.values():
+ if cp.get("sdn_status") == "ERROR":
+ switch_id = cp.get("switch_dpid")
+ switch_port = cp.get("switch_port")
+ old_vlan = cp.get("vlan")
+ self._delete_port(switch_id, switch_port, old_vlan)
+ deleted_ports.append(
+ self.underlay_api.get_vpg_name(switch_id, switch_port)
+ )
+
+ for port in deleted_ports:
+ del old_cp[port]
+
+ # Delete sdn_status and sdn_info if exists (possibly marked as error)
+ if conn_info.get("vnet", {}).get("sdn_status"):
+ del conn_info["vnet"]["sdn_status"]
+ except HttpException as e:
+ self.logger.error(
+ "Error trying to delete old ports marked as error: {}".format(e)
+ )
+
+ raise SdnConnectorError(e)
+ except SdnConnectorError as e:
+ self.logger.error(
+ "Error trying to delete old ports marked as error: {}".format(e)
+ )
+
+ raise
except Exception as e:
- self.logger.error('Exception add connection_service: %s', e)
- # try to rollback push original config
+ self.logger.error(
+ "Error trying to delete old ports marked as error: {}".format(e),
+ exc_info=True,
+ )
+
+ raise SdnConnectorError(
+ "Error trying to delete old ports marked as error: {}".format(e)
+ )
+
+ if connection_points:
+ # Check and obtain what should be added and deleted, if there is an error here raise an exception
try:
- self._post_netconfig(config_orig)
- except Exception as e2:
- self.logger.error('Exception rolling back to original config: %s', e2)
- # raise exception
- if isinstance(e, SdnConnectorError):
+ work_cps = {}
+ for cp in connection_points:
+ switch_id = cp.get("service_endpoint_encapsulation_info").get(
+ "switch_dpid"
+ )
+ switch_port = cp.get("service_endpoint_encapsulation_info").get(
+ "switch_port"
+ )
+ service_endpoint_id = cp.get("service_endpoint_id")
+ cp_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
+ add_cp = work_cps.get(cp_name)
+
+ if not add_cp:
+ # add cp to dict
+ # check cp has vlan
+ vlan = cp.get("service_endpoint_encapsulation_info").get("vlan")
+
+ if vlan:
+ service_endpoint_ids = []
+ service_endpoint_ids.append(service_endpoint_id)
+ add_cp = {
+ "service_endpoint_ids": service_endpoint_ids,
+ "switch_dpid": switch_id,
+ "switch_port": switch_port,
+ "vlan": vlan,
+ }
+ work_cps[cp_name] = add_cp
+ else:
+ self.logger.warning(
+ "cp service_endpoint_id : {} has no vlan, ignore".format(
+ service_endpoint_id
+ )
+ )
+ else:
+ # add service_endpoint_id to list
+ service_endpoint_ids = add_cp["service_endpoint_ids"]
+ service_endpoint_ids.append(service_endpoint_id)
+
+ old_port_list = list(old_cp.keys())
+ port_list = list(work_cps.keys())
+ to_delete_ports = list(set(old_port_list) - set(port_list))
+ to_add_ports = list(set(port_list) - set(old_port_list))
+ self.logger.debug("ports to delete: {}".format(to_delete_ports))
+ self.logger.debug("ports to add: {}".format(to_add_ports))
+
+ # Obtain network (check it is correctly created)
+ vnet = self.underlay_api.get_virtual_network(service_uuid)
+ if vnet:
+ vnet_name = vnet["name"]
+ else:
+ raise SdnConnectorError(
+ "vnet uuid: {} not found".format(service_uuid)
+ )
+ except SdnConnectorError:
raise
- else:
- raise SdnConnectorError("Exception create_connectivity_service: {}".format(e))
+ except Exception as e:
+ self.logger.error(
+ "Error edit connectivity service: {}".format(e), exc_info=True
+ )
+ raise SdnConnectorError(
+ "Exception edit connectivity service: {}".format(str(e))
+ )
- def delete_connectivity_service(self, service_uuid, conn_info=None):
- """
- Disconnect multi-site endpoints previously connected
+ # Delete unneeded ports and add new ones: if there is an error return conn_info
+ try:
+ # Connection points returned in con_info should reflect what has (and should as ERROR) be done
+ # Start with old cp dictionary and modify it as we work
+ conn_info_cp = old_cp
+
+ # Delete unneeded ports
+ deleted_ports = []
+ for port_name in conn_info_cp.keys():
+ if port_name in to_delete_ports:
+ cp = conn_info_cp[port_name]
+ switch_id = cp.get("switch_dpid")
+ switch_port = cp.get("switch_port")
+ self.logger.debug(
+ "delete port switch_id={}, switch_port={}".format(
+ switch_id, switch_port
+ )
+ )
+ self._delete_port(switch_id, switch_port, vlan)
+ deleted_ports.append(port_name)
+
+ # Delete ports
+ for port_name in deleted_ports:
+ del conn_info_cp[port_name]
+
+ # Add needed ports
+ for port_name, cp in work_cps.items():
+ if port_name in to_add_ports:
+ switch_id = cp.get("switch_dpid")
+ switch_port = cp.get("switch_port")
+ vlan = cp.get("vlan")
+ self.logger.debug(
+ "add port switch_id={}, switch_port={}".format(
+ switch_id, switch_port
+ )
+ )
+ vpg_id, vmi_id = self._create_port(
+ switch_id, switch_port, vnet_name, vlan
+ )
+ cp_added = cp.copy()
+ cp_added["vpg_id"] = vpg_id
+ cp_added["vmi_id"] = vmi_id
+ conn_info_cp[port_name] = cp_added
+
+ # replace endpoints in case they have changed
+ conn_info_cp[port_name]["service_endpoint_ids"] = cp[
+ "service_endpoint_ids"
+ ]
+
+ conn_info["connection_points"] = conn_info_cp
+ return conn_info
+
+ except Exception as e:
+ # Log error
+ if isinstance(e, SdnConnectorError) or isinstance(e, HttpException):
+ self.logger.error(
+ "Error edit connectivity service: {}".format(e), exc_info=True
+ )
+ else:
+ self.logger.error("Error edit connectivity service: {}".format(e))
+
+ # There has been an error mount conn_info_cp marking as error cp that should
+ # have been deleted but have not or should have been added
+ for port_name, cp in conn_info_cp.items():
+ if port_name in to_delete_ports:
+ cp["sdn_status"] = "ERROR"
+
+ for port_name, cp in work_cps.items():
+ curr_cp = conn_info_cp.get(port_name)
+
+ if not curr_cp:
+ cp_error = work_cps.get(port_name).copy()
+ cp_error["sdn_status"] = "ERROR"
+ conn_info_cp[port_name] = cp_error
+
+ conn_info_cp[port_name]["service_endpoint_ids"] = cp[
+ "service_endpoint_ids"
+ ]
+
+ conn_info["sdn_status"] = "ERROR"
+ conn_info["sdn_info"] = repr(e)
+ conn_info["connection_points"] = conn_info_cp
+
+ return conn_info
+ else:
+ # Connection points have not changed, so do nothing
+ self.logger.info("no new connection_points provided, nothing to be done")
- :param service_uuid: The one returned by create_connectivity_service
- :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
- if they do not return None
- :return: None
- :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
- """
- self.logger.debug("delete_connectivity_service uuid: {}".format(service_uuid))
- try:
- #TO DO: check if virtual port groups have to be deleted
- self._delete_virtual_network(self.url, service_uuid)
- except SdnConnectorError:
- raise SdnConnectorError('Failed to delete service uuid {}'.format(service_uuid))
- except Exception as e:
- self.logger.error('Exception deleting connection_service: %s', e, exc_info=True)
- raise SdnConnectorError("Exception deleting connectivity service: {}".format(str(e)))
+ return
-if __name__ == '__main__':
+if __name__ == "__main__":
# Init logger
log_format = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
- log_formatter = logging.Formatter(log_format, datefmt='%Y-%m-%dT%H:%M:%S')
+ log_formatter = logging.Formatter(log_format, datefmt="%Y-%m-%dT%H:%M:%S")
handler = logging.StreamHandler()
handler.setFormatter(log_formatter)
- logger = logging.getLogger('openmano.sdnconn.junipercontrail')
- #logger.setLevel(level=logging.ERROR)
- logger.setLevel(level=logging.INFO)
- #logger.setLevel(level=logging.DEBUG)
+ logger = logging.getLogger("ro.sdn.junipercontrail")
+ # logger.setLevel(level=logging.ERROR)
+ # logger.setLevel(level=logging.INFO)
+ logger.setLevel(level=logging.DEBUG)
logger.addHandler(handler)
# Read config
- with open('test.yaml') as f:
+ with open("test.yaml") as f:
config = yaml.safe_load(f.read())
- wim = {'wim_url': config.pop('wim_url')}
- wim_account = {'user': config.pop('user'), 'password': config.pop('password')}
- logger.info('wim: {}, wim_account: {}, config: {}'.format(wim, wim_account, config))
+
+ wim = {"wim_url": config.pop("wim_url")}
+ wim_account = {"user": config.pop("user"), "password": config.pop("password")}
+ logger.info("wim: {}, wim_account: {}, config: {}".format(wim, wim_account, config))
# Init controller
- juniper_contrail = JuniperContrail(wim=wim, wim_account=wim_account, config=config, logger=logger)
+ juniper_contrail = JuniperContrail(
+ wim=wim, wim_account=wim_account, config=config, logger=logger
+ )
# Tests
# Generate VNI
for i in range(5):
vni = juniper_contrail._generate_vni()
juniper_contrail.used_vni.add(vni)
+
print(juniper_contrail.used_vni)
- juniper_contrail.used_vni.remove(1000003)
+ # juniper_contrail.used_vni.remove(1000003)
print(juniper_contrail.used_vni)
+
for i in range(2):
vni = juniper_contrail._generate_vni()
juniper_contrail.used_vni.add(vni)
+
print(juniper_contrail.used_vni)
- # 0. Check credentials
- print('0. Check credentials')
- juniper_contrail.check_credentials()
-
- underlay_url = juniper_contrail.get_url()
- overlay_url = juniper_contrail.get_overlay_url()
- # 1. Read virtual networks from overlay controller
- print('1. Read virtual networks from overlay controller')
- try:
- vnets = juniper_contrail._get_virtual_networks(overlay_url)
- logger.debug(yaml.safe_dump(vnets, indent=4, default_flow_style=False))
- print('OK')
- except Exception as e:
- logger.error('Exception reading virtual networks from overlay controller: %s', e)
- print('FAILED')
-
- # 2. Read virtual networks from underlay controller
- print('2. Read virtual networks from underlay controller')
- vnets = juniper_contrail._get_virtual_networks(underlay_url)
- logger.debug(yaml.safe_dump(vnets, indent=4, default_flow_style=False))
- print('OK')
- # 3. Delete virtual networks gerardoX from underlay controller
- print('3. Delete virtual networks gerardoX from underlay controller')
- for vn in vnets:
- name = vn['fq_name'][2]
- logger.debug('Virtual network: {}'.format(name))
- for vn in vnets:
- name = vn['fq_name'][2]
- if 'gerardo' in name:
- logger.info('Virtual Network *gerardo*: {}, {}'.format(name,vn['uuid']))
- if name != "gerardo":
- print('Deleting Virtual Network: {}, {}'.format(name,vn['uuid']))
- logger.info('Deleting Virtual Network: {}, {}'.format(name,vn['uuid']))
- juniper_contrail._delete_virtual_network(underlay_url, vn['uuid'])
- print('OK')
- # 4. Get virtual network (gerardo) from underlay controller
- print('4. Get virtual network (gerardo) from underlay controller')
- vnet1_info = juniper_contrail._get_virtual_network(underlay_url, 'c5d332f7-420a-4e2b-a7b1-b56a59f20c97')
- print(yaml.safe_dump(vnet1_info, indent=4, default_flow_style=False))
- print('OK')
- # 5. Create virtual network in underlay controller
- print('5. Create virtual network in underlay controller')
- myname = 'gerardo4'
- myvni = 20004
- vnet2_id, _ = juniper_contrail._create_virtual_network(underlay_url, myname, myvni)
- vnet2_info = juniper_contrail._get_virtual_network(underlay_url, vnet2_id)
- print(yaml.safe_dump(vnet2_info, indent=4, default_flow_style=False))
- print('OK')
- # 6. Delete virtual network in underlay controller
- print('6. Delete virtual network in underlay controller')
- juniper_contrail._delete_virtual_network(underlay_url, vnet2_id)
- print('OK')
- # 7. Read previously deleted virtual network in underlay controller
- print('7. Read previously deleted virtual network in underlay controller')
- try:
- vnet2_info = juniper_contrail._get_virtual_network(underlay_url, vnet2_id)
- if vnet2_info:
- print('FAILED. Network {} exists'.format(vnet2_id))
- else:
- print('OK. Network {} does not exist because it has been deleted'.format(vnet2_id))
- except Exception as e:
- logger.info('Exception reading virtual networks from overlay controller: %s', e)
- exit(0)
+ # 0. Check credentials
+ print("0. Check credentials")
+ # juniper_contrail.check_credentials()
- #TODO: to be deleted (it comes from ONOS VPLS plugin)
- service_type = 'ELAN'
+ # 1 - Create and delete connectivity service
conn_point_0 = {
- "service_endpoint_id": "switch1:ifz1",
+ "service_endpoint_id": "0000:83:11.4",
"service_endpoint_encapsulation_type": "dot1q",
"service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000011",
- "switch_port": "1",
- "vlan": "600"
- }
+ "switch_dpid": "LEAF-1",
+ "switch_port": "xe-0/0/17",
+ "vlan": "501",
+ },
}
conn_point_1 = {
- "service_endpoint_id": "switch3:ifz1",
- "service_endpoint_encapsulation_type": "dot1q",
- "service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000031",
- "switch_port": "3",
- "vlan": "600"
- }
- }
- connection_points = [conn_point_0, conn_point_1]
- # service_uuid, created_items = juniper_contrail.create_connectivity_service(service_type, connection_points)
- #print(service_uuid)
- #print(created_items)
- #sleep(10)
- #juniper_contrail.delete_connectivity_service("5496dfea-27dc-457d-970d-b82bac266e5c"))
-
-
- conn_info = None
- conn_info = ['switch1:ifz1', 'switch3_ifz3']
- juniper_contrail.delete_connectivity_service("f7afc4de-556d-4b5a-8a12-12b5ef97d269", conn_info)
-
- conn_point_0 = {
- "service_endpoint_id": "switch1:ifz1",
+ "service_endpoint_id": "0000:81:10.3",
"service_endpoint_encapsulation_type": "dot1q",
"service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000011",
- "switch_port": "1",
- "vlan": "500"
- }
+ "switch_dpid": "LEAF-2",
+ "switch_port": "xe-0/0/16",
+ "vlan": "501",
+ },
}
conn_point_2 = {
- "service_endpoint_id": "switch1:ifz3",
+ "service_endpoint_id": "0000:08:11.7",
"service_endpoint_encapsulation_type": "dot1q",
"service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000011",
- "switch_port": "3",
- "vlan": "500"
- }
+ "switch_dpid": "LEAF-2",
+ "switch_port": "xe-0/0/16",
+ "vlan": "502",
+ },
}
conn_point_3 = {
- "service_endpoint_id": "switch3_ifz3",
+ "service_endpoint_id": "0000:83:10.4",
"service_endpoint_encapsulation_type": "dot1q",
"service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000033",
- "switch_port": "3",
- "vlan": "500"
- }
+ "switch_dpid": "LEAF-1",
+ "switch_port": "xe-0/0/17",
+ "vlan": "502",
+ },
}
- new_connection_points = [conn_point_0, conn_point_3]
- #conn_info = juniper_contrail.edit_connectivity_service("f7afc4de-556d-4b5a-8a12-12b5ef97d269", conn_info, new_connection_points)
- #print(conn_info)
+
+ # 1 - Define connection points
+ logger.debug("create first connection service")
+ print("Create connectivity service")
+ connection_points = [conn_point_0, conn_point_1]
+ service_id, conn_info = juniper_contrail.create_connectivity_service(
+ "ELAN", connection_points
+ )
+ logger.info("Created connectivity service 1")
+ logger.info(service_id)
+ logger.info(yaml.safe_dump(conn_info, indent=4, default_flow_style=False))
+
+ logger.debug("create second connection service")
+ print("Create connectivity service")
+ connection_points = [conn_point_2, conn_point_3]
+ service_id2, conn_info2 = juniper_contrail.create_connectivity_service(
+ "ELAN", connection_points
+ )
+ logger.info("Created connectivity service 2")
+ logger.info(service_id2)
+ logger.info(yaml.safe_dump(conn_info2, indent=4, default_flow_style=False))
+
+ logger.debug("Delete connectivity service 1")
+ juniper_contrail.delete_connectivity_service(service_id, conn_info)
+ logger.debug("Delete Ok")
+
+ logger.debug("Delete connectivity service 2")
+ juniper_contrail.delete_connectivity_service(service_id2, conn_info2)
+ logger.debug("Delete Ok")