# License for the specific language governing permissions and limitations
# under the License.
-from io import BytesIO
-import pycurl
+import requests
import json
import copy
+import logging
+
+from time import time
+from requests.exceptions import ConnectionError
class HttpException(Exception):
pass
+
class NotFound(HttpException):
pass
-class Http(object):
- def __init__(self, logger):
- self._logger = logger
- self._response_headers = None
+class AuthError(HttpException):
+ pass
+
+class DuplicateFound(HttpException):
+ pass
- def _check_http_response(self, http_code, data):
- if http_code >= 300:
- resp = ""
- if data.getvalue():
- data_text = data.getvalue().decode()
- self._logger.info("Response {} DATA: {}".format(http_code, data_text))
- resp = ": " + data_text
- else:
- self._logger.info("Response {}".format(http_code))
- if http_code == 404:
- raise NotFound("Error {}{}".format(http_code, resp))
- raise HttpException("Error {}{}".format(http_code, resp))
+class ServiceUnavailableException(HttpException):
+ pass
- def _get_curl_cmd(self, url, headers):
- self._logger.debug("")
- curl_cmd = pycurl.Curl()
- curl_cmd.setopt(pycurl.URL, url)
- curl_cmd.setopt(pycurl.SSL_VERIFYPEER, 0)
- curl_cmd.setopt(pycurl.SSL_VERIFYHOST, 0)
- if headers:
- curl_cmd.setopt(pycurl.HTTPHEADER, headers)
- return curl_cmd
+class ContrailHttp(object):
+
+ def __init__(self, auth_info, logger):
+ self._logger = logger
+ # default don't verify client cert
+ self._ssl_verify = False
+ # auth info: must contain auth_url and auth_dict
+ self.auth_url = auth_info["auth_url"]
+ self.auth_dict = auth_info["auth_dict"]
+
+ self.max_retries = 3
+
+ # Default token timeout
+ self.token_timeout = 3500
+ self.token = None
+ # TODO - improve configuration timeouts
def get_cmd(self, url, headers):
self._logger.debug("")
- data = BytesIO()
- curl_cmd = self._get_curl_cmd(url, headers)
- curl_cmd.setopt(pycurl.HTTPGET, 1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- self._logger.info("Request METHOD: {} URL: {}".format("GET", url))
- curl_cmd.perform()
- http_code = curl_cmd.getinfo(pycurl.HTTP_CODE)
- self._logger.info("Response HTTPCODE: {}".format(http_code))
- curl_cmd.close()
- if data.getvalue():
- data_text = data.getvalue().decode()
- self._logger.debug("Response DATA: {}".format(data_text))
- return http_code, data_text
- return http_code, None
-
+ resp = self._request("GET", url, headers)
+ return resp.json()
- def delete_cmd(self, url, headers):
+ def post_headers_cmd(self, url, headers, post_fields_dict=None):
self._logger.debug("")
- data = BytesIO()
- curl_cmd = self._get_curl_cmd(url, headers)
- curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE")
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- self._logger.info("Request METHOD: {} URL: {}".format("DELETE", url))
- curl_cmd.perform()
- http_code = curl_cmd.getinfo(pycurl.HTTP_CODE)
- self._logger.info("Response HTTPCODE: {}".format(http_code))
- curl_cmd.close()
- self._check_http_response(http_code, data)
- # TODO 202 accepted should be returned somehow
- if data.getvalue():
- data_text = data.getvalue().decode()
- self._logger.debug("Response DATA: {}".format(data_text))
- return http_code, data_text
+ # obfuscate password before logging dict
+ if post_fields_dict.get('auth', {}).get('identity', {}).get('password', {}).get('user', {}).get('password'):
+ post_fields_dict_copy = copy.deepcopy(post_fields_dict)
+ post_fields_dict['auth']['identity']['password']['user']['password'] = '******'
+ json_data_log = post_fields_dict_copy
else:
- self._logger.debug("Response DATA: NONE")
- return http_code, None
-
-
- def header_function(self, header_line):
- header_line = header_line.decode('iso-8859-1')
- if ':' not in header_line:
- return
- name, value = header_line.split(':', 1)
- name = name.strip()
- value = value.strip()
- name = name.lower()
- self._response_headers[name] = value
-
-
- def post_cmd(self, url, headers, postfields_dict=None, return_header=None):
- self._logger.debug('url: {}, headers: {}, postfields_dict: {}, return_header: {}'.format(url, headers, postfields_dict, return_header))
- data = BytesIO()
- curl_cmd = self._get_curl_cmd(url, headers)
- curl_cmd.setopt(pycurl.POST, 1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- if return_header:
- self._response_headers = {}
- curl_cmd.setopt(pycurl.HEADERFUNCTION, self.header_function)
-
- jsondata = json.dumps(postfields_dict)
- if postfields_dict.get('auth',{}).get('identity',{}).get('password',{}).get('user',{}).get('password'):
- postfields_dict_copy = copy.deepcopy(postfields_dict)
- postfields_dict_copy['auth']['identity']['password']['user']['password'] = '******'
- jsondata_log = json.dumps(postfields_dict_copy)
+ json_data_log = post_fields_dict
+ self._logger.debug("Request POSTFIELDS: {}".format(json.dumps(json_data_log)))
+ resp = self._request("POST_HEADERS", url, headers, data=post_fields_dict)
+ return resp.text
+
+ def post_cmd(self, url, headers, post_fields_dict=None):
+ self._logger.debug("")
+ # obfuscate password before logging dict
+ if post_fields_dict.get('auth', {}).get('identity', {}).get('password', {}).get('user', {}).get('password'):
+ post_fields_dict_copy = copy.deepcopy(post_fields_dict)
+ post_fields_dict['auth']['identity']['password']['user']['password'] = '******'
+ json_data_log = post_fields_dict_copy
else:
- jsondata_log = jsondata
- self._logger.debug("Request POSTFIELDS: {}".format(jsondata_log))
- curl_cmd.setopt(pycurl.POSTFIELDS, jsondata)
-
- self._logger.info("Request METHOD: {} URL: {}".format("POST", url))
- curl_cmd.perform()
- http_code = curl_cmd.getinfo(pycurl.HTTP_CODE)
- self._logger.info("Response HTTPCODE: {}".format(http_code))
- curl_cmd.close()
- if return_header:
- data_text = self._response_headers.get(return_header)
- self._logger.debug("Response HEADER: {}".format(data_text))
- return http_code, data_text
- if data.getvalue():
- data_text = data.getvalue().decode()
- self._logger.debug("Response DATA: {}".format(data_text))
- return http_code, data_text
+ json_data_log = post_fields_dict
+ self._logger.debug("Request POSTFIELDS: {}".format(json.dumps(json_data_log)))
+ resp = self._request("POST", url, headers, data=post_fields_dict)
+ return resp.text
+
+ def delete_cmd(self, url, headers):
+ self._logger.debug("")
+ resp = self._request("DELETE", url, headers)
+ return resp.text
+
+ def _get_token(self, headers):
+ self._logger.debug('Current Token:'.format(self.token))
+ auth_url = self.auth_url + 'auth/tokens'
+ if self.token is None or self._token_expired():
+ if not self.auth_url:
+ self.token = ""
+ resp = self._request_noauth(url=auth_url, op="POST", headers=headers,
+ data=self.auth_dict)
+ self.token = resp.headers.get('x-subject-token')
+ self.last_token_time = time.time()
+ self._logger.debug('Obtained token: '.format(self.token))
+
+ return self.token
+
+ def _token_expired(self):
+ current_time = time.time()
+ if self.last_token_time and (current_time - self.last_token_time < self.token_timeout):
+ return False
else:
- return http_code, None
+ return True
+
+ def _request(self, op, url, http_headers, data=None, retry_auth_error=True):
+ headers = http_headers.copy()
+
+ # Get authorization (include authentication headers)
+ # todo - aƱadir token de nuevo
+ #token = self._get_token(headers)
+ token = None
+ if token:
+ headers['X-Auth-Token'] = token
+ try:
+ return self._request_noauth(op, url, headers, data)
+ except AuthError:
+ # If there is an auth error retry just once
+ if retry_auth_error:
+ return self._request(self, op, url, headers, data, retry_auth_error=False)
+
+ def _request_noauth(self, op, url, headers, data=None):
+ # Method to execute http requests with error control
+ # Authentication error, always make just one retry
+ # ConnectionError or ServiceUnavailable make configured retries with sleep between them
+ # Other errors to raise:
+ # - NotFound
+ # - Conflict
+
+ retry = 0
+ while retry < self.max_retries:
+ retry += 1
+
+ # Execute operation
+ try:
+ self._logger.info("Request METHOD: {} URL: {}".format(op, url))
+ if (op == "GET"):
+ resp = self._http_get(url, headers, query_params=data)
+ elif (op == "POST"):
+ resp = self._http_post(url, headers, json_data=data)
+ elif (op == "POST_HEADERS"):
+ resp = self._http_post_headers(url, headers, json_data=data)
+ elif (op == "DELETE"):
+ resp = self._http_delete(url, headers, json_data=data)
+ else:
+ raise HttpException("Unsupported operation: {}".format(op))
+ self._logger.info("Response HTTPCODE: {}".format(resp.status_code))
+
+ # Check http return code
+ if resp:
+ return resp
+ else:
+ status_code = resp.status_code
+ if status_code == 401:
+ # Auth Error - set token to None to reload it and raise AuthError
+ self.token = None
+ raise AuthError("Auth error executing operation")
+ elif status_code == 409:
+ raise DuplicateFound("Duplicate resource url: {}, response: {}".format(url, resp.text))
+ elif status_code == 404:
+ raise NotFound("Not found resource url: {}, response: {}".format(url, resp.text))
+ elif resp.status_code in [502, 503]:
+ if not self.max_retries or retry >= self.max_retries:
+ raise ServiceUnavailableException("Service unavailable error url: {}".format(url))
+
+ continue
+ else:
+ raise HttpException("Error status_code: {}, error_text: {}".format(resp.status_code, resp.text))
+
+ except ConnectionError as e:
+ self._logger.error("Connection error executing request: {}".format(repr(e)))
+ if not self.max_retries or retry >= self.max_retries:
+ raise ConnectionError
+ continue
+ except Exception as e:
+ self._logger.error("Error executing request: {}".format(repr(e)))
+ raise e
+
+ def _http_get(self, url, headers, query_params=None):
+ return requests.get(url, headers=headers, params=query_params)
+
+ def _http_post_headers(self, url, headers, json_data=None):
+ return requests.head(url, json=json_data, headers=headers, verify=False)
+
+ def _http_post(self, url, headers, json_data=None):
+ return requests.post(url, json=json_data, headers=headers, verify=False)
+
+ def _http_delete(self, url, headers, json_data=None):
+ return requests.delete(url, json=json_data, headers=headers)
--- /dev/null
+# Copyright 2020 ETSI
+#
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import json
+
+from osm_ro.wim.sdnconn import SdnConnectorError
+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
+
+class UnderlayApi:
+ """ Class with CRUD operations for the underlay API """
+
+ def __init__(self, url, config=None, user=None, password=None, logger=None):
+
+ self.logger = logger or logging.getLogger("openmano.sdnconn.junipercontrail.sdnapi")
+ self.controller_url = url
+
+ 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
+
+ auth_url = None
+ self.project = None
+ self.domain = None
+ self.asn = None
+ self.fabric = None
+ if config:
+ self.auth_url = config.get("auth_url")
+ self.project = config.get("project")
+ self.domain = config.get("domain")
+ self.asn = config.get("asn")
+ self.fabric = config.get("fabric")
+
+ # Init http headers for all requests
+ self.http_header = {'Content-Type': 'application/json'}
+
+ if user:
+ self.user = user
+
+ if password:
+ self.password = password
+
+ 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
+
+ # Init http lib
+ auth_info = {"auth_url": self.auth_url, "auth_dict": auth_dict}
+ self.http = ContrailHttp(auth_info, self.logger)
+
+ def check_auth(self):
+ response = self.http.get_cmd(url=self.auth_url, headers=self.http_header)
+ return response
+
+ # Helper methods for CRUD operations
+ def get_all_by_type(self, controller_url, type):
+ endpoint = controller_url + type
+ response = self.http.get_cmd(url=endpoint, headers=self.http_header)
+ return response.get(type)
+
+ def get_by_uuid(self, type, uuid):
+ try:
+ endpoint = self.controller_url + type + "/{}".format(uuid)
+ response = self.http.get_cmd(url=endpoint, headers=self.http_header)
+ return response.get(type)
+ except NotFound:
+ return None
+
+ def delete_by_uuid(self, controller_url, type, uuid):
+ endpoint = controller_url + type + "/{}".format(uuid)
+ self.http.delete_cmd(url=endpoint, headers=self.http_header)
+
+ def get_uuid_from_fqname(self, type, fq_name):
+ """
+ Obtain uuid from fqname
+ Returns: If resource not found returns None
+ In case of error raises an Exception
+ """
+ payload = {
+ "type": type,
+ "fq_name": fq_name
+ }
+ try:
+ endpoint = self.controller_url + "fqname-to-id"
+ resp = self.http.post_cmd(url=endpoint,
+ headers=self.http_header,
+ post_fields_dict=payload)
+ return json.loads(resp).get("uuid")
+ except NotFound:
+ return None
+
+ def get_by_fq_name(self, type, fq_name):
+ # Obtain uuid by fqdn and then get data by uuid
+ uuid = self.get_uuid_from_fqname(type, fq_name)
+ if uuid:
+ return self.get_by_uuid(type, uuid)
+ else:
+ return None
+
+
+ # Aux methods to avoid code duplication of name conventions
+ def get_vpg_name(self, switch_id, switch_port):
+ return "{}_{}".format(switch_id, switch_port)
+
+ def get_vmi_name(self, switch_id, switch_port, vlan):
+ return "{}_{}-{}".format(switch_id, switch_port, vlan)
+
+ # Virtual network operations
+
+ def create_virtual_network(self, name, vni):
+ self.logger.debug("create vname, name: {}, vni: {}".format(name, vni))
+ routetarget = '{}:{}'.format(self.asn, vni)
+ vnet_dict = {
+ "virtual-network": {
+ "virtual_network_properties": {
+ "vxlan_network_identifier": vni,
+ },
+ "parent_type": "project",
+ "fq_name": [
+ self.domain,
+ self.project,
+ name
+ ],
+ "route_target_list": {
+ "route_target": [
+ "target:" + routetarget
+ ]
+ }
+ }
+ }
+ endpoint = self.controller_url + 'virtual-networks'
+ resp = self.http.post_cmd(url=endpoint,
+ headers=self.http_header,
+ post_fields_dict=vnet_dict)
+ if not resp:
+ raise SdnConnectorError('Error creating virtual network: empty response')
+ vnet_info = json.loads(resp)
+ self.logger.debug("created vnet, vnet_info: {}".format(vnet_info))
+ return vnet_info.get("virtual-network").get('uuid'), vnet_info.get("virtual-network")
+
+ def get_virtual_networks(self):
+ return self.get_all_by_type('virtual-networks')
+
+ def get_virtual_network(self, network_id):
+ return self.get_by_uuid('virtual-network', network_id)
+
+ def delete_virtual_network(self, network_id):
+ self.logger.debug("delete vnet uuid: {}".format(network_id))
+ self.delete_by_uuid(self.controller_url, 'virtual-network', network_id)
+ self.logger.debug("deleted vnet uuid: {}".format(network_id))
+
+ # Vpg operations
+
+ def create_vpg(self, switch_id, switch_port):
+ self.logger.debug("create vpg, switch_id: {}, switch_port: {}".format(switch_id, switch_port))
+ vpg_name = self.get_vpg_name(switch_id, switch_port)
+ vpg_dict = {
+ "virtual-port-group": {
+ "parent_type": "fabric",
+ "fq_name": [
+ "default-global-system-config",
+ self.fabric,
+ vpg_name
+ ]
+ }
+ }
+ endpoint = self.controller_url + 'virtual-port-groups'
+ resp = self.http.post_cmd(url=endpoint,
+ headers=self.http_header,
+ post_fields_dict=vpg_dict)
+ if not resp:
+ raise SdnConnectorError('Error creating virtual port group: empty response')
+ vpg_info = json.loads(resp)
+ self.logger.debug("created vpg, vpg_info: {}".format(vpg_info))
+ return vpg_info.get("virtual-port-group").get('uuid'), vpg_info.get("virtual-port-group")
+
+ def get_vpgs(self):
+ return self.get_all_by_type(self.controller_url, 'virtual-port-groups')
+
+ def get_vpg(self, vpg_id):
+ return self.get_by_uuid(self.controller_url, "virtual-port-group", vpg_id)
+
+ def get_vpg_by_name(self, vpg_name):
+ fq_name = [
+ "default-global-system-config",
+ self.fabric,
+ vpg_name
+ ]
+ return self.get_by_fq_name("virtual-port-group", fq_name)
+
+ def delete_vpg(self, vpg_id):
+ self.logger.debug("delete vpg, uuid: {}".format(vpg_id))
+ self.delete_by_uuid(self.controller_url, 'virtual-port-group', vpg_id)
+ self.logger.debug("deleted vpg, uuid: {}".format(vpg_id))
+
+ def create_vmi(self, switch_id, switch_port, network, vlan):
+ self.logger.debug("create vmi, switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
+ switch_id, switch_port, network, vlan))
+ vmi_name = self.get_vmi_name(switch_id, switch_port, vlan)
+ vpg_name = self.get_vpg_name(switch_id, switch_port)
+ profile_dict = {
+ "local_link_information": [
+ {
+ "port_id": switch_port,
+ "switch_id": switch_port,
+ "switch_info": switch_id,
+ "fabric": self.fabric
+ }
+ ]
+
+ }
+ vmi_dict = {
+ "virtual-machine-interface": {
+ "parent_type": "project",
+ "fq_name": [
+ self.domain,
+ self.project,
+ vmi_name
+ ],
+ "virtual_network_refs": [
+ {
+ "to": [
+ self.domain,
+ self.project,
+ network
+ ]
+ }
+ ],
+ "virtual_machine_interface_properties": {
+ "sub_interface_vlan_tag": vlan
+ },
+ "virtual_machine_interface_bindings": {
+ "key_value_pair": [
+ {
+ "key": "vnic_type",
+ "value": "baremetal"
+ },
+ {
+ "key": "vif_type",
+ "value": "vrouter"
+ },
+ {
+ "key": "vpg",
+ "value": vpg_name
+ },
+ {
+ "key": "profile",
+ "value": json.dumps(profile_dict)
+ }
+ ]
+ }
+ }
+ }
+ endpoint = self.controller_url + 'virtual-machine-interfaces'
+ self.logger.debug("vmi_dict: {}".format(vmi_dict))
+ resp = self.http.post_cmd(url=endpoint,
+ headers=self.http_header,
+ post_fields_dict=vmi_dict)
+ if not resp:
+ raise SdnConnectorError('Error creating vmi: empty response')
+ vmi_info = json.loads(resp)
+ self.logger.debug("created vmi, info: {}".format(vmi_info))
+ return vmi_info.get("virtual-machine-interface").get('uuid'), vmi_info.get("virtual-machine-interface")
+
+ def get_vmi(self, vmi_uuid):
+ return self.get_by_uuid(self.controller_url, 'virtual-machine-interface', vmi_uuid)
+
+ def delete_vmi(self, uuid):
+ self.logger.debug("delete vmi uuid: {}".format(uuid))
+ self.delete_by_uuid(self.controller_url, 'virtual-machine-interface', uuid)
+ self.logger.debug("deleted vmi: {}".format(uuid))
+
#
import logging
-import uuid
-import copy
import json
import yaml
-#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_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 io import BytesIO
-import pycurl
+from osm_rosdn_juniper_contrail.sdn_api import UnderlayApi
class JuniperContrail(SdnConnectorBase):
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:
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.logger.info("No vni_range was provided. Using ['1000001-2000000']")
self.used_vni = set()
- 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
auth_url = auth_url + "/"
self.auth_url = auth_url
- # 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 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
- self.logger.info("Juniper Contrail Connector Initialized.")
+ 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 it 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-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):
"""
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())]
-
# 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 - Create vmi
+ 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, vpg_id, vmi_id):
+ self.logger.debug("delete port, vpg_id: {}, vmi_id: {}".format(vpg_id, vmi_id))
+ # 1 - Obtain vpg by id (if not vpg_id must have been error creating ig, nothing to be done)
+ if vpg_id:
+ vpg = self.underlay_api.get_by_uuid("virtual-port-group", vpg_id)
+ if not vpg:
+ self.logger.warning("vpg: {} to be deleted not found".format(vpg_id))
+ 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:
+ uuid = vmi.get("uuid")
+ if uuid == vmi_id:
+ 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)
-
+ 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.http.get_cmd(endpoint='virtual-network/{}'.format(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']}
+
+ # Check if conn_info reports error
+ if conn_info.get("sdn_status") == "ERROR":
+ return {'sdn_status': 'ACTIVE', 'sdn_info': "conn_info indicates pending error"}
+ else:
+ return {'sdn_status': 'ACTIVE', 'sdn_info': vnet_info['virtual-network']}
else:
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)
+ self.logger.error('Exception getting connectivity service info: %s', e, exc_info=True)
return {'sdn_status': 'ERROR', 'error_msg': str(e)}
: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
# 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:
+ # Initialize data
+ conn_info = None
+ conn_info_cp = {}
+
+ # 1 - Obtain VLAN-ID
+ vlan = self._get_vlan(connection_points)
+ self.logger.debug("Provided vlan: {}".format(vlan))
+
+ # 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": conn_info_cp # dict with port_name as key
+ }
+
+ # 4 - Create a port for each endpoint
+ 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")
+ 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[self.underlay_api.get_vpg_name(switch_id, switch_port)] = cp_added
+
+ return vnet_id, conn_info
+ self.logger.info("created connectivity service, uuid: {}, name: {}".format(vnet_id, vnet_name))
+ 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"
+ # iterate over not added connection_points and add but marking them as error
+ for cp in connection_points[len(conn_info_cp):]:
+ cp_error = cp.copy()
+ cp_error["sdn_status"] = "ERROR"
+ switch_id = cp.get("service_endpoint_encapsulation_info").get("switch_dpid")
+ switch_port = cp.get("service_endpoint_encapsulation_info").get("switch_port")
+ conn_info_cp[self.underlay_api.get_vpg_name(switch_id, switch_port)] = cp_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:
- name = 'test-test-1'
- vni = 999999
- network_id, network_info = self._create_virtual_network(self.url, name, vni)
+ vnet_uuid = service_uuid
+ vnet_name = conn_info["vnet"]["name"] # always should exist as the network is the first thing created
+ connection_points = conn_info["connection_points"].values()
+ vlan = self._get_vlan(connection_points)
+
+ # 1: For each connection point delete vlan from vpg and it is is the
+ # last one, delete vpg
+ for cp in connection_points:
+ self._delete_port(cp.get("vpg_id"), cp.get("vmi_id"))
+
+ # 2: Delete vnet
+ self.underlay_api.delete_virtual_network(vnet_uuid)
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)))
+ # Helper methods
+ @staticmethod
+ def _get_vlan(connection_points):
+ vlan = None
+ for cp in connection_points:
+ cp_vlan = cp.get("service_endpoint_encapsulation_info").get("vlan")
+ if not vlan:
+ vlan = cp_vlan
+ else:
+ if vlan != cp_vlan:
+ raise SdnConnectorError("More that one cp provided")
+ return vlan
def edit_connectivity_service(self, service_uuid, conn_info = None, connection_points = None, **kwargs):
""" Change an existing 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: {}, "
+ # 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 = 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)
+ # conn_info should always exist and have connection_points and vnet elements
+ old_cp = conn_info.get("connection_points", {})
+ old_vlan = self._get_vlan(old_cp)
+ # 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
+ delete_conn_info = []
+ for cp in old_cp:
+ if cp.get("sdn_status") == "ERROR":
+ switch_id = cp.get("service_endpoint_encapsulation_info").get("switch_dpid")
+ switch_port = cp.get("service_endpoint_encapsulation_info").get("switch_port")
+ self._delete_port(switch_id, switch_port, old_vlan)
+ delete_conn_info.append(self.underlay_api.get_vpg_name(switch_id, switch_port))
+
+ for i in delete_conn_info:
+ del old_cp[i]
+
+ # Delete vnet status 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):
+
+ vlan = self._get_vlan(connection_points)
+
+ old_port_list = ["{}_{}".format(cp["service_endpoint_encapsulation_info"]["switch_dpid"],
+ cp["service_endpoint_encapsulation_info"]["switch_port"])
+ for cp in old_cp.values()]
+ port_list = ["{}_{}".format(cp["service_endpoint_encapsulation_info"]["switch_dpid"],
+ cp["service_endpoint_encapsulation_info"]["switch_port"])
+ for cp in connection_points]
+ to_delete_ports = list(set(old_port_list) - set(port_list))
+ to_add_ports = list(set(port_list) - set(old_port_list))
+
+ # Obtain network
+ vnet = self.underlay_api.get_virtual_network(self.get_url(), service_uuid)
+ vnet_name = vnet["name"]
+
+ 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
+ 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("service_endpoint_encapsulation_info").get("switch_dpid")
+ switch_port = cp.get("service_endpoint_encapsulation_info").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)
+ del conn_info_cp[port_name]
+
+ # Add needed ports
+ for cp in connection_points:
+ if port_name in to_add_ports:
+ switch_id = cp.get("service_endpoint_encapsulation_info").get("switch_dpid")
+ switch_port = cp.get("service_endpoint_encapsulation_info").get("switch_port")
+ self.logger.debug("add port switch_id, switch_port: {}".format(switch_id, switch_port))
+ self._create_port(switch_id, switch_port, vnet_name, vlan)
+ conn_info_cp[port_name]
+
+ 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 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")
+ port_name = self.underlay_api.get_vpg_name(switch_id, switch_port)
+ if port_name in to_add_ports:
+ cp_error = cp.copy()
+ cp_error["sdn_status"] = "ERROR"
+ conn_info_cp[port_name] = cp_error
+
+ conn_info["sdn_status"] = "ERROR"
+ conn_info["connection_points"] = conn_info_cp
+ return conn_info
- :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)))
+
+ else:
+ # Connection points have not changed, so do nothing
+ self.logger.info("no new connection_points provided, nothing to be done")
+ return
if __name__ == '__main__':
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.setLevel(level=logging.INFO)
+ logger.setLevel(level=logging.DEBUG)
logger.addHandler(handler)
# Read config
underlay_url = juniper_contrail.get_url()
overlay_url = juniper_contrail.get_overlay_url()
+ # 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)
+ 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)
# 1. Read virtual networks from overlay controller
print('1. Read virtual networks from overlay controller')
try:
print('OK')
except Exception as e:
logger.error('Exception reading virtual networks from overlay controller: %s', e)
- print('FAILED')
-
+ 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.info('Exception reading virtual networks from overlay controller: %s', e)
exit(0)
+ # Test CRUD:
+ net_name = "gerardo"
+ net_vni = "2000"
+ net_vlan = "501"
+ switch_1 = "LEAF-2"
+ port_1 = "xe-0/0/18"
+ switch_2 = "LEAF-1"
+ port_2 = "xe-0/0/18"
+
+ # 1 - Create a new virtual network
+ vnet2_id, vnet2_created = juniper_contrail._create_virtual_network(underlay_url, net_name, net_vni)
+ print("Created virtual network:")
+ print(vnet2_id)
+ print(yaml.safe_dump(vnet2_created, indent=4, default_flow_style=False))
+ print("Get virtual network:")
+ vnet2_info = juniper_contrail._get_virtual_network(underlay_url, vnet2_id)
+ print(json.dumps(vnet2_info, indent=4))
+ print('OK')
+
+ # 2 - Create a new virtual port group
+ vpg_id, vpg_info = juniper_contrail._create_vpg(underlay_url, switch_1, port_1, net_name, net_vlan)
+ print("Created virtual port group:")
+ print(vpg_id)
+ print(json.dumps(vpg_info, indent=4))
- #TODO: to be deleted (it comes from ONOS VPLS plugin)
- service_type = 'ELAN'
- conn_point_0 = {
- "service_endpoint_id": "switch1:ifz1",
- "service_endpoint_encapsulation_type": "dot1q",
- "service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000011",
- "switch_port": "1",
- "vlan": "600"
- }
- }
- 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_encapsulation_type": "dot1q",
- "service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000011",
- "switch_port": "1",
- "vlan": "500"
- }
- }
- conn_point_2 = {
- "service_endpoint_id": "switch1:ifz3",
- "service_endpoint_encapsulation_type": "dot1q",
- "service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000011",
- "switch_port": "3",
- "vlan": "500"
- }
- }
- conn_point_3 = {
- "service_endpoint_id": "switch3_ifz3",
- "service_endpoint_encapsulation_type": "dot1q",
- "service_endpoint_encapsulation_info": {
- "switch_dpid": "0000000000000033",
- "switch_port": "3",
- "vlan": "500"
- }
- }
- 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)
+ print("Get virtual network:")
+ 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')
+
+ # 3 - Create a new virtual machine interface
+ vmi_id, vmi_info = juniper_contrail._create_vmi(underlay_url, switch_1, port_1, net_name, net_vlan)
+ print("Created virtual machine interface:")
+ print(vmi_id)
+ print(yaml.safe_dump(vmi_info, indent=4, default_flow_style=False))
+
+ # 4 - Create a second virtual port group
+ # 5 - Create a second virtual machine interface
+
+ ### Test rapido de modificaciĆ³n de requests:
+ # Ver que metodos siguen funcionando y cuales no e irlos corrigiendo
+
+ """
+ vnets = juniper_contrail._get_virtual_networks(underlay_url)
+ logger.debug("Virtual networks:")
+ logger.debug(json.dumps(vnets, indent=2))
+
+ vpgs = juniper_contrail._get_vpgs(underlay_url)
+ logger.debug("Virtual port groups:")
+ logger.debug(json.dumps(vpgs, indent=2))
+ """
+ # Get by uuid
+
+ """
+ # 3 - Get vmi
+ vmi_uuid = "dbfd2099-b895-459e-98af-882d77d968c1"
+ vmi = juniper_contrail._get_vmi(underlay_url, vmi_uuid)
+ logger.debug("Virtual machine interface:")
+ logger.debug(json.dumps(vmi, indent=2))
+
+ # Delete vmi
+ logger.debug("Delete vmi")
+ juniper_contrail._delete_vmi(underlay_url, vmi_uuid)
+ """
+
+ """
+ # 2 - Get vpg
+ vpg_uuid = "85156474-d1a5-44c0-9d8b-8f690f39d27e"
+ vpg = juniper_contrail._get_vpg(underlay_url, vpg_uuid)
+ logger.debug("Virtual port group:")
+ logger.debug(json.dumps(vpg, indent=2))
+ # Delete vpg
+ vpg = juniper_contrail._delete_vpg(underlay_url, vpg_uuid)
+ """
+
+ # 1 - Obtain virtual network
+ """
+ vnet_uuid = "68457d61-6558-4d38-a03d-369a9de803ea"
+ vnet = juniper_contrail._get_virtual_network(underlay_url, vnet_uuid)
+ logger.debug("Virtual network:")
+ logger.debug(json.dumps(vnet, indent=2))
+ # Delete virtual network
+ juniper_contrail._delete_virtual_network(underlay_url, vnet_uuid)
+ """