CAL refactoring
[osm/SO.git] / rwcal / plugins / vala / rwcal_openstack / rift / rwcal / openstack / utils / compute.py
diff --git a/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/utils/compute.py b/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/utils/compute.py
new file mode 100644 (file)
index 0000000..d7658da
--- /dev/null
@@ -0,0 +1,610 @@
+#!/usr/bin/python
+
+# 
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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 uuid
+import gi
+gi.require_version('RwcalYang', '1.0')
+from gi.repository import RwcalYang
+
+
+class ImageValidateError(Exception):
+    pass
+
+class VolumeValidateError(Exception):
+    pass
+
+class AffinityGroupError(Exception):
+    pass
+
+
+class ComputeUtils(object):
+    """
+    Utility class for compute operations
+    """
+    epa_types = ['vm_flavor',
+                 'guest_epa',
+                 'host_epa',
+                 'host_aggregate',
+                 'hypervisor_epa',
+                 'vswitch_epa']
+    def __init__(self, driver):
+        """
+        Constructor for class
+        Arguments:
+           driver: object of OpenstackDriver()
+        """
+        self._driver = driver
+        self.log = driver.log
+
+    @property
+    def driver(self):
+        return self._driver
+
+    def search_vdu_flavor(self, vdu_params):
+        """
+        Function to search a matching flavor for VDU instantiation
+        from already existing flavors
+        
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+
+        Returns:
+           flavor_id(string): Flavor id for VDU instantiation
+           None if no flavor could be found
+        """
+        kwargs = { 'vcpus': vdu_params.vm_flavor.vcpu_count,
+                   'ram'  : vdu_params.vm_flavor.memory_mb,
+                   'disk' : vdu_params.vm_flavor.storage_gb,}
+        
+        flavors = self.driver.nova_flavor_find(**kwargs)
+        flavor_list = list()
+        for flv in flavors:
+            flavor_list.append(self.driver.utils.flavor.parse_flavor_info(flv))
+            
+        flavor_id = self.driver.utils.flavor.match_resource_flavor(vdu_params, flavor_list)
+        return flavor_id
+        
+    def select_vdu_flavor(self, vdu_params):
+        """
+        This function attempts to find a pre-existing flavor matching required 
+        parameters for VDU instantiation. If no such flavor is found, a new one
+        is created.
+        
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+
+        Returns:
+           flavor_id(string): Flavor id for VDU instantiation
+        """
+        flavor_id = self.search_vdu_flavor(vdu_params)
+        if flavor_id is not None:
+            self.log.info("Found flavor with id: %s matching requirements for VDU: %s",
+                          flavor_id, vdu_params.name)
+            return flavor_id
+
+        flavor = RwcalYang.FlavorInfoItem()
+        flavor.name = str(uuid.uuid4())
+        
+        epa_dict = { k: v for k, v in vdu_params.as_dict().items()
+                     if k in ComputeUtils.epa_types }
+        
+        flavor.from_dict(epa_dict)
+
+        flavor_id = self.driver.nova_flavor_create(name      = flavor.name,
+                                                   ram       = flavor.vm_flavor.memory_mb,
+                                                   vcpus     = flavor.vm_flavor.vcpu_count,
+                                                   disk      = flavor.vm_flavor.storage_gb,
+                                                   epa_specs = self.driver.utils.flavor.get_extra_specs(flavor))
+        return flavor_id
+
+    def make_vdu_flavor_args(self, vdu_params):
+        """
+        Creates flavor related arguments for VDU operation
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+
+        Returns:
+           A dictionary {'flavor_id': <flavor-id>}
+        """
+        return {'flavor_id': self.select_vdu_flavor(vdu_params)}
+
+
+    def make_vdu_image_args(self, vdu_params):
+        """
+        Creates image related arguments for VDU operation
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+
+        Returns:
+           A dictionary {'image_id': <image-id>}
+
+        """
+        kwargs = dict()
+        if vdu_params.has_field('image_name'):
+            kwargs['image_id'] = self.resolve_image_n_validate(vdu_params.image_name,
+                                                               vdu_params.image_checksum)
+        elif vdu_params.has_field('image_id'):
+            kwargs['image_id'] = vdu_params.image_id
+            
+        return kwargs
+
+    def resolve_image_n_validate(self, image_name, checksum = None):
+        """
+        Resolve the image_name to image-object by matching image_name and checksum
+        
+        Arguments:
+          image_name (string): Name of image
+          checksums  (string): Checksum associated with image
+
+        Raises ImageValidateError in case of Errors
+        """
+        image_info = [ i for i in self.driver._glance_image_list if i['name'] == image_name]
+
+        if not image_info:
+            self.log.error("No image with name: %s found", image_name)
+            raise ImageValidateError("No image with name %s found" %(image_name))
+        
+        for image in image_info:
+            if 'status' not in image or image['status'] != 'active':
+                self.log.error("Image %s not in active state. Current state: %s",
+                               image_name, image['status'])
+                raise ImageValidateError("Image with name %s found in incorrect (%s) state"
+                                         %(image_name, image['status']))
+            if not checksum or checksum == image['checksum']:
+                break
+        else:
+            self.log.info("No image found with matching name: %s and checksum: %s",
+                          image_name, checksum)
+            raise ImageValidateError("No image found with matching name: %s and checksum: %s"
+                                     %(image_name, checksum))
+        return image['id']
+        
+    def make_vdu_volume_args(self, volume, vdu_params):
+        """
+        Arguments:
+           volume:   Protobuf GI object RwcalYang.VDUInitParams_Volumes()
+           vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+        
+        Returns:
+           A dictionary required to create volume for VDU
+
+        Raises VolumeValidateError in case of Errors
+        """
+        kwargs = dict()
+
+        if volume.has_field('volume_ref'):
+            self.log.error("Unsupported option <Volume Reference> found for volume: %s", volume.name)
+            raise VolumeValidateError("Unsupported option <Volume Reference> found for volume: %s"
+                                      %(volume.name))
+        
+        kwargs['boot_index'] = volume.boot_priority
+        if "image" in volume:
+            # Support image->volume
+            if volume.image is not None:
+                kwargs['source_type'] = "image"
+                kwargs['uuid'] = self.resolve_image_n_validate(volume.image, volume.image_checksum)
+            else:
+                # Support blank->volume
+                kwargs['source_type'] = "blank"
+        kwargs['device_name'] = volume.name
+        kwargs['destination_type'] = "volume"
+        kwargs['volume_size'] = volume.size
+        kwargs['delete_on_termination'] = True
+
+        if volume.has_field('device_type'):
+            if volume.device_type == 'cdrom':
+                kwargs['device_type'] = 'cdrom'
+            elif volume.device_bus == 'ide':
+                kwargs['disk_bus'] = 'ide'
+            else:
+                self.log.error("Unsupported device_type <%s> found for volume: %s",
+                               volume.device_type, volume.name)
+                raise VolumeValidateError("Unsupported device_type <%s> found for volume: %s"
+                                          %(volume.device_type, volume.name))        
+        else:
+            self.log.error("Mandatory field <device_type> not specified for volume: %s",
+                           volume.name)
+            raise VolumeValidateError("Mandatory field <device_type> not specified for volume: %s"
+                                      %(volume.name))
+        return kwargs
+            
+    def make_vdu_storage_args(self, vdu_params):
+        """
+        Creates volume related arguments for VDU operation
+        
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+
+        Returns:
+           A dictionary required for volumes creation for VDU instantiation
+        """
+        kwargs = dict()
+        if vdu_params.has_field('volumes'):
+            kwargs['block_device_mapping_v2'] = list()
+            for volume in vdu_params.volumes:
+                kwargs['block_device_mapping_v2'].append(self.make_vdu_volume_args(volume, vdu_params))
+        return kwargs
+
+    def make_vdu_network_args(self, vdu_params):
+        """
+        Creates VDU network related arguments for VDU operation
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+
+        Returns:
+           A dictionary {'port_list' : [ports], 'network_list': [networks]}
+
+        """
+        kwargs = dict()
+        kwargs['port_list'], kwargs['network_list'] = self.driver.utils.network.setup_vdu_networking(vdu_params)
+        return kwargs
+
+    
+    def make_vdu_boot_config_args(self, vdu_params):
+        """
+        Creates VDU boot config related arguments for VDU operation
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+
+        Returns:
+          A dictionary {
+                         'userdata'    :  <cloud-init> , 
+                         'config_drive':  True/False, 
+                         'files'       :  [ file name ],
+                         'metadata'    :  <metadata string>
+        }
+        """
+        kwargs = dict()
+        if vdu_params.has_field('vdu_init') and vdu_params.vdu_init.has_field('userdata'):
+            kwargs['userdata'] = vdu_params.vdu_init.userdata
+        else:
+            kwargs['userdata'] = ''
+
+        if not vdu_params.has_field('supplemental_boot_data'):
+            return kwargs
+        
+        if vdu_params.supplemental_boot_data.has_field('config_file'):
+            files = dict()
+            for cf in vdu_params.supplemental_boot_data.config_file:
+                files[cf.dest] = cf.source
+            kwargs['files'] = files
+
+        if vdu_params.supplemental_boot_data.has_field('boot_data_drive'):
+            kwargs['config_drive'] = vdu_params.supplemental_boot_data.boot_data_drive
+        else:
+            kwargs['config_drive'] = False
+
+        if vdu_params.supplemental_boot_data.has_field('custom_meta_data'):
+            metadata = dict()
+            for cm in vdu_params.supplemental_boot_data.custom_meta_data:
+                metadata[cm] = cm.value                
+            kwargs['metadata'] = metadata
+
+        return kwargs
+
+    def _select_affinity_group(self, group_name):
+        """
+        Selects the affinity group based on name and return its id
+        Arguments:
+          group_name (string): Name of the Affinity/Anti-Affinity group
+        Returns:
+          Id of the matching group
+
+        Raises exception AffinityGroupError if no matching group is found
+        """
+        groups = [g['id'] for g in self.driver._nova_affinity_group if g['name'] == group_name]
+        if not groups:
+            self.log.error("No affinity/anti-affinity group with name: %s found", group_name)
+            raise AffinityGroupError("No affinity/anti-affinity group with name: %s found" %(group_name))
+        return groups[0]
+
+        
+    def make_vdu_server_placement_args(self, vdu_params):
+        """
+        Function to create kwargs required for nova server placement
+        
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+        
+        Returns:
+         A dictionary { 'availability_zone' : < Zone >, 'scheduler_hints': <group-id> } 
+
+        """
+        kwargs = dict()
+        
+        if vdu_params.has_field('availability_zone') \
+           and vdu_params.availability_zone.has_field('name'):
+            kwargs['availability_zone']  = vdu_params.availability_zone
+
+        if vdu_params.has_field('server_group'):
+            kwargs['scheduler_hints'] = {
+                'group': self._select_affinity_group(vdu_params.server_group)
+            }            
+        return kwargs
+
+    def make_vdu_server_security_args(self, vdu_params, account):
+        """
+        Function to create kwargs required for nova security group
+
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+          account: Protobuf GI object RwcalYang.CloudAccount()
+        
+        Returns:
+          A dictionary {'security_groups' : < group > }
+        """
+        kwargs = dict()
+        if account.openstack.security_groups:
+            kwargs['security_groups'] = account.openstack.security_groups
+        return kwargs
+    
+    
+    def make_vdu_create_args(self, vdu_params, account):
+        """
+        Function to create kwargs required for nova_server_create API
+        
+        Arguments:
+          vdu_params: Protobuf GI object RwcalYang.VDUInitParams()
+          account: Protobuf GI object RwcalYang.CloudAccount()
+
+        Returns:
+          A kwargs dictionary for VDU create operation
+        """
+        kwargs = dict()
+        
+        kwargs['name'] = vdu_params.name
+
+        kwargs.update(self.make_vdu_flavor_args(vdu_params))
+        kwargs.update(self.make_vdu_storage_args(vdu_params))
+        kwargs.update(self.make_vdu_image_args(vdu_params))
+        kwargs.update(self.make_vdu_network_args(vdu_params))
+        kwargs.update(self.make_vdu_boot_config_args(vdu_params))
+        kwargs.update(self.make_vdu_server_placement_args(vdu_params))
+        kwargs.update(self.make_vdu_server_security_args(vdu_params, account))
+        return kwargs
+        
+    
+    def _parse_vdu_mgmt_address_info(self, vm_info):
+        """
+        Get management_ip and public_ip for VDU
+        
+        Arguments:
+          vm_info : A dictionary object return by novaclient library listing VM attributes
+        
+        Returns:
+          A tuple of mgmt_ip (string) and public_ip (string)
+        """
+        mgmt_ip = None
+        public_ip = None
+        if 'addresses' in vm_info:
+            for network_name, network_info in vm_info['addresses'].items():
+                if network_info and network_name == self.driver.mgmt_network:
+                    for interface in network_info:
+                        if 'OS-EXT-IPS:type' in interface:
+                            if interface['OS-EXT-IPS:type'] == 'fixed':
+                                mgmt_ip = interface['addr']
+                            elif interface['OS-EXT-IPS:type'] == 'floating':
+                                public_ip = interface['addr']
+        return (mgmt_ip, public_ip)
+
+    def get_vdu_epa_info(self, vm_info):
+        """
+        Get flavor information (including EPA) for VDU
+
+        Arguments:
+          vm_info : A dictionary returned by novaclient library listing VM attributes
+        Returns:
+          flavor_info: A dictionary object returned by novaclient library listing flavor attributes
+        """
+        if 'flavor' in vm_info and 'id' in vm_info['flavor']:
+            try:
+                flavor_info = self.driver.nova_flavor_get(vm_info['flavor']['id'])
+                return flavor_info
+            except Exception as e:
+                self.log.exception("Exception %s occured during get-flavor", str(e))
+        return dict()
+
+    def _parse_vdu_cp_info(self, vdu_id):
+        """
+        Get connection point information for VDU identified by vdu_id
+        Arguments:
+        vdu_id (string) : VDU Id (vm_info['id']) 
+        Returns:
+        A List of object RwcalYang.VDUInfoParams_ConnectionPoints()
+
+        """
+        cp_list = []
+        # Fill the port information
+        port_list = self.driver.neutron_port_list(**{'device_id': vdu_id})
+        for port in port_list:
+            cp_info = self.driver.utils.network._parse_cp(port)
+            cp = RwcalYang.VDUInfoParams_ConnectionPoints()
+            cp.from_dict(cp_info.as_dict())
+            cp_list.append(cp)
+        return cp_list
+
+    def _parse_vdu_state_info(self, vm_info):
+        """
+        Get VDU state information
+
+        Arguments:
+          vm_info : A dictionary returned by novaclient library listing VM attributes
+
+        Returns:
+          state (string): State of the VDU
+        """
+        if 'status' in vm_info:
+            if vm_info['status'] == 'ACTIVE':
+                vdu_state = 'active'
+            elif vm_info['status'] == 'ERROR':
+                vdu_state = 'failed'
+            else:
+                vdu_state = 'inactive'
+        else:
+            vdu_state = 'unknown'
+        return vdu_state
+
+    def _parse_vdu_server_group_info(self, vm_info):
+        """
+        Get VDU server group information
+        Arguments:
+          vm_info : A dictionary returned by novaclient library listing VM attributes
+
+        Returns:
+          server_group_name (string): Name of the server group to which VM belongs, else empty string
+        
+        """
+        server_group = [ v['name']
+                         for v in self.driver.nova_server_group_list()
+                         if vm_info['id'] in v['members'] ]
+        if server_group:
+            return server_group[0]
+        else:
+            return str()
+
+    def _parse_vdu_volume_info(self, vm_info):
+        """
+        Get VDU server group information
+        Arguments:
+          vm_info : A dictionary returned by novaclient library listing VM attributes
+
+        Returns:
+          List of RwcalYang.VDUInfoParams_Volumes()
+        """
+        volumes = list()
+        
+        try:
+            volume_list = self.driver.nova_volume_list(vm_info['id'])
+        except Exception as e:
+            self.log.exception("Exception %s occured during nova-volume-list", str(e))
+            return volumes
+
+        for v in volume_list:
+            volume = RwcalYang.VDUInfoParams_Volumes()
+            try:
+                volume.name = (v['device']).split('/')[2]
+                volume.volume_id = v['volumeId']
+                details = self.driver.cinder_volume_get(volume.volume_id)
+                for k, v in details.metadata.items():
+                    vd = volume.custom_meta_data.add()
+                    vd.name = k
+                    vd.value = v
+            except Exception as e:
+                self.log.exception("Exception %s occured during volume list parsing", str(e))
+                continue
+            else:
+                volumes.append(volume)
+        return volumes
+    
+    def _parse_vdu_console_url(self, vm_info):
+        """
+        Get VDU console URL
+        Arguments:
+          vm_info : A dictionary returned by novaclient library listing VM attributes
+
+        Returns:
+          console_url(string): Console URL for VM
+        """
+        console_url = None
+        if self._parse_vdu_state_info(vm_info) == 'ACTIVE':
+            try:
+                console_url = self.driver.nova_server_console(vm_info['id'])
+            except Exception as e:
+                self.log.exception("Exception %s occured during volume list parsing", str(e))
+        return console_url
+
+    def parse_cloud_vdu_info(self, vm_info):
+        """
+        Parse vm_info dictionary (return by python-client) and put values in GI object for VDU
+
+        Arguments:
+           vm_info : A dictionary object return by novaclient library listing VM attributes
+        
+        Returns:
+           Protobuf GI Object of type RwcalYang.VDUInfoParams()
+        """
+        vdu = RwcalYang.VDUInfoParams()
+        vdu.name = vm_info['name']
+        vdu.vdu_id = vm_info['id']
+        vdu.cloud_type  = 'openstack'
+
+        if 'config_drive' in vm_info:
+            vdu.supplemental_boot_data.boot_data_drive = vm_info['config_drive']
+
+        if 'image' in vm_info and 'id' in vm_info['image']:
+            vdu.image_id = vm_info['image']['id']
+
+        if 'availability_zone' in vm_info:
+            vdu.availability_zone = vm_info['availability_zone']
+
+        vdu.state = self._parse_vdu_state_info(vm_info)
+        management_ip,public_ip = self._parse_vdu_mgmt_address_info(vm_info)
+
+        if management_ip:
+            vdu.management_ip = management_ip
+
+        if public_ip:
+            vdu.public_ip = public_ip
+
+        if 'flavor' in vm_info and 'id' in vm_info['flavor']:
+            vdu.flavor_id = vm_info['flavor']['id']
+            flavor_info = self.get_vdu_epa_info(vm_info)
+            vm_flavor = self.driver.utils.flavor.parse_vm_flavor_epa_info(flavor_info)
+            guest_epa = self.driver.utils.flavor.parse_guest_epa_info(flavor_info)
+            host_epa = self.driver.utils.flavor.parse_host_epa_info(flavor_info)
+            host_aggregates = self.driver.utils.flavor.parse_host_aggregate_epa_info(flavor_info)
+
+            vdu.vm_flavor.from_dict(vm_flavor.as_dict())
+            vdu.guest_epa.from_dict(guest_epa.as_dict())
+            vdu.host_epa.from_dict(host_epa.as_dict())
+            for aggr in host_aggregates:
+                ha = vdu.host_aggregate.add()
+                ha.from_dict(aggr.as_dict())
+
+        cp_list = self._parse_vdu_cp_info(vdu.vdu_id)
+        for cp in cp_list:
+            vdu.connection_points.append(cp)
+        
+        vdu.server_group.name = self._parse_vdu_server_group_info(vm_info)
+
+        for v in self._parse_vdu_volume_info(vm_info):
+            vdu.volumes.append(v)
+
+        vdu.console_url = self._parse_vdu_console_url(vm_info)
+        return vdu
+
+
+    def perform_vdu_network_cleanup(self, vdu_id):
+        """
+        This function cleans up networking resources related to VDU
+        Arguments:
+           vdu_id(string): VDU id 
+        Returns:
+           None
+        """
+        ### Get list of floating_ips associated with this instance and delete them
+        floating_ips = [ f for f in self.driver.nova_floating_ip_list() if f.instance_id == vdu_id ]
+        for f in floating_ips:
+            self.driver.nova_floating_ip_delete(f)
+
+        ### Get list of port on VM and delete them.
+        port_list = self.driver.neutron_port_list(**{'device_id': vdu_id})
+
+        for port in port_list:
+            if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
+                self.driver.neutron_port_delete(port['id'])
+