--- /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))
+