X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FRO.git;a=blobdiff_plain;f=RO-SDN-juniper_contrail%2Fosm_rosdn_juniper_contrail%2Fsdn_api.py;fp=RO-SDN-juniper_contrail%2Fosm_rosdn_juniper_contrail%2Fsdn_api.py;h=1c5b5285e2fcbc17581db02fcac333ffd88de3bf;hp=0000000000000000000000000000000000000000;hb=a5c26d8be1a5896675f7eb07c6afeb1aa26c7172;hpb=3d0ead412f462595c4399d81af579dee9064a9ec diff --git a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py new file mode 100644 index 00000000..1c5b5285 --- /dev/null +++ b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py @@ -0,0 +1,305 @@ +# 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)) +