X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FRO.git;a=blobdiff_plain;f=RO-VIM-openstack%2Fosm_rovim_openstack%2Fvimconn_openstack.py;h=0a39041205ce8b06e0836316138cb42ead17b97d;hp=4900dfcc9d003b743ce411000746a860b5058580;hb=36cad669a99ed1655dc39b1c37aea6197ddbab55;hpb=8f2060bb4e1d8eb2f522f09f0970d7b24dfd9ce9 diff --git a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py index 4900dfcc..0a390412 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py @@ -32,6 +32,7 @@ to the VIM connector's SFC resources as follows: import copy from http.client import HTTPException +import json import logging from pprint import pformat import random @@ -1349,6 +1350,26 @@ class vimconnector(vimconn.VimConnector): extended.get("disk-io-quota"), "disk_io", extra_specs ) + # Set the mempage size as specified in the descriptor + if extended.get("mempage-size"): + if extended.get("mempage-size") == "LARGE": + extra_specs["hw:mem_page_size"] = "large" + elif extended.get("mempage-size") == "SMALL": + extra_specs["hw:mem_page_size"] = "small" + elif extended.get("mempage-size") == "SIZE_2MB": + extra_specs["hw:mem_page_size"] = "2MB" + elif extended.get("mempage-size") == "SIZE_1GB": + extra_specs["hw:mem_page_size"] = "1GB" + elif extended.get("mempage-size") == "PREFER_LARGE": + extra_specs["hw:mem_page_size"] = "any" + else: + # The validations in NBI should make reaching here not possible. + # If this message is shown, check validations + self.logger.debug( + "Invalid mempage-size %s. Will be ignored", + extended.get("mempage-size"), + ) + # create flavor new_flavor = self.nova.flavors.create( name=name, @@ -1865,6 +1886,11 @@ class vimconnector(vimconn.VimConnector): # cloud config config_drive, userdata = self._create_user_data(cloud_config) + # get availability Zone + vm_av_zone = self._get_vm_availability_zone( + availability_zone_index, availability_zone_list + ) + # Create additional volumes in case these are present in disk_list existing_vim_volumes = [] base_disk_index = ord("b") @@ -1884,12 +1910,16 @@ class vimconnector(vimconn.VimConnector): size=disk["size"], name=name + "_vd" + chr(base_disk_index), imageRef=disk["image_id"], + # Make sure volume is in the same AZ as the VM to be attached to + availability_zone=vm_av_zone, ) boot_volume_id = volume.id else: volume = self.cinder.volumes.create( size=disk["size"], name=name + "_vd" + chr(base_disk_index), + # Make sure volume is in the same AZ as the VM to be attached to + availability_zone=vm_av_zone, ) created_items["volume:" + str(volume.id)] = True @@ -1931,11 +1961,6 @@ class vimconnector(vimconn.VimConnector): if boot_volume_id: self.cinder.volumes.set_bootable(boot_volume_id, True) - # get availability Zone - vm_av_zone = self._get_vm_availability_zone( - availability_zone_index, availability_zone_list - ) - # Manage affinity groups/server groups server_group_id = None scheduller_hints = {} @@ -2270,7 +2295,12 @@ class vimconnector(vimconn.VimConnector): try: k_item, _, k_id = k.partition(":") if k_item == "port": - self.neutron.delete_port(k_id) + port_dict = self.neutron.list_ports() + existing_ports = [ + port["id"] for port in port_dict["ports"] if port_dict + ] + if k_id in existing_ports: + self.neutron.delete_port(k_id) except Exception as e: self.logger.error( "Error deleting port: {}: {}".format(type(e).__name__, e) @@ -2489,12 +2519,29 @@ class vimconnector(vimconn.VimConnector): server.resume() elif server.status == "SHUTOFF": server.start() + else: + self.logger.debug( + "ERROR : Instance is not in SHUTOFF/PAUSE/SUSPEND state" + ) + raise vimconn.VimConnException( + "Cannot 'start' instance while it is in active state", + http_code=vimconn.HTTP_Bad_Request, + ) + elif "pause" in action_dict: server.pause() elif "resume" in action_dict: server.resume() elif "shutoff" in action_dict or "shutdown" in action_dict: - server.stop() + self.logger.debug("server status %s", server.status) + if server.status == "ACTIVE": + server.stop() + else: + self.logger.debug("ERROR: VM is not in Active state") + raise vimconn.VimConnException( + "VM is not in active state, stop operation is not allowed", + http_code=vimconn.HTTP_Bad_Request, + ) elif "forceOff" in action_dict: server.stop() # TODO elif "terminate" in action_dict: @@ -3534,3 +3581,221 @@ class vimconnector(vimconn.VimConnector): ConnectionError, ) as e: self._format_exception(e) + + def get_vdu_state(self, vm_id): + """ + Getting the state of a vdu + param: + vm_id: ID of an instance + """ + self.logger.debug("Getting the status of VM") + self.logger.debug("VIM VM ID %s", vm_id) + self._reload_connection() + server = self.nova.servers.find(id=vm_id) + server_dict = server.to_dict() + vdu_data = [ + server_dict["status"], + server_dict["flavor"]["id"], + server_dict["OS-EXT-SRV-ATTR:host"], + server_dict["OS-EXT-AZ:availability_zone"], + ] + self.logger.debug("vdu_data %s", vdu_data) + return vdu_data + + def check_compute_availability(self, host, server_flavor_details): + self._reload_connection() + hypervisor_search = self.nova.hypervisors.search( + hypervisor_match=host, servers=True + ) + for hypervisor in hypervisor_search: + hypervisor_id = hypervisor.to_dict()["id"] + hypervisor_details = self.nova.hypervisors.get(hypervisor=hypervisor_id) + hypervisor_dict = hypervisor_details.to_dict() + hypervisor_temp = json.dumps(hypervisor_dict) + hypervisor_json = json.loads(hypervisor_temp) + resources_available = [ + hypervisor_json["free_ram_mb"], + hypervisor_json["disk_available_least"], + hypervisor_json["vcpus"] - hypervisor_json["vcpus_used"], + ] + compute_available = all( + x > y for x, y in zip(resources_available, server_flavor_details) + ) + if compute_available: + return host + + def check_availability_zone( + self, old_az, server_flavor_details, old_host, host=None + ): + self._reload_connection() + az_check = {"zone_check": False, "compute_availability": None} + aggregates_list = self.nova.aggregates.list() + for aggregate in aggregates_list: + aggregate_details = aggregate.to_dict() + aggregate_temp = json.dumps(aggregate_details) + aggregate_json = json.loads(aggregate_temp) + if aggregate_json["availability_zone"] == old_az: + hosts_list = aggregate_json["hosts"] + if host is not None: + if host in hosts_list: + az_check["zone_check"] = True + available_compute_id = self.check_compute_availability( + host, server_flavor_details + ) + if available_compute_id is not None: + az_check["compute_availability"] = available_compute_id + else: + for check_host in hosts_list: + if check_host != old_host: + available_compute_id = self.check_compute_availability( + check_host, server_flavor_details + ) + if available_compute_id is not None: + az_check["zone_check"] = True + az_check["compute_availability"] = available_compute_id + break + else: + az_check["zone_check"] = True + return az_check + + def migrate_instance(self, vm_id, compute_host=None): + """ + Migrate a vdu + param: + vm_id: ID of an instance + compute_host: Host to migrate the vdu to + """ + self._reload_connection() + vm_state = False + instance_state = self.get_vdu_state(vm_id) + server_flavor_id = instance_state[1] + server_hypervisor_name = instance_state[2] + server_availability_zone = instance_state[3] + try: + server_flavor = self.nova.flavors.find(id=server_flavor_id).to_dict() + server_flavor_details = [ + server_flavor["ram"], + server_flavor["disk"], + server_flavor["vcpus"], + ] + if compute_host == server_hypervisor_name: + raise vimconn.VimConnException( + "Unable to migrate instance '{}' to the same host '{}'".format( + vm_id, compute_host + ), + http_code=vimconn.HTTP_Bad_Request, + ) + az_status = self.check_availability_zone( + server_availability_zone, + server_flavor_details, + server_hypervisor_name, + compute_host, + ) + availability_zone_check = az_status["zone_check"] + available_compute_id = az_status.get("compute_availability") + + if availability_zone_check is False: + raise vimconn.VimConnException( + "Unable to migrate instance '{}' to a different availability zone".format( + vm_id + ), + http_code=vimconn.HTTP_Bad_Request, + ) + if available_compute_id is not None: + self.nova.servers.live_migrate( + server=vm_id, + host=available_compute_id, + block_migration=True, + disk_over_commit=False, + ) + state = "MIGRATING" + changed_compute_host = "" + if state == "MIGRATING": + vm_state = self.__wait_for_vm(vm_id, "ACTIVE") + changed_compute_host = self.get_vdu_state(vm_id)[2] + if vm_state and changed_compute_host == available_compute_id: + self.logger.debug( + "Instance '{}' migrated to the new compute host '{}'".format( + vm_id, changed_compute_host + ) + ) + return state, available_compute_id + else: + raise vimconn.VimConnException( + "Migration Failed. Instance '{}' not moved to the new host {}".format( + vm_id, available_compute_id + ), + http_code=vimconn.HTTP_Bad_Request, + ) + else: + raise vimconn.VimConnException( + "Compute '{}' not available or does not have enough resources to migrate the instance".format( + available_compute_id + ), + http_code=vimconn.HTTP_Bad_Request, + ) + except ( + nvExceptions.BadRequest, + nvExceptions.ClientException, + nvExceptions.NotFound, + ) as e: + self._format_exception(e) + + def resize_instance(self, vm_id, new_flavor_id): + """ + For resizing the vm based on the given + flavor details + param: + vm_id : ID of an instance + new_flavor_id : Flavor id to be resized + Return the status of a resized instance + """ + self._reload_connection() + self.logger.debug("resize the flavor of an instance") + instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id) + old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"] + new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"] + try: + if instance_status == "ACTIVE" or instance_status == "SHUTOFF": + if old_flavor_disk > new_flavor_disk: + raise nvExceptions.BadRequest( + 400, + message="Server disk resize failed. Resize to lower disk flavor is not allowed", + ) + else: + self.nova.servers.resize(server=vm_id, flavor=new_flavor_id) + vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE") + if vm_state: + instance_resized_status = self.confirm_resize(vm_id) + return instance_resized_status + else: + raise nvExceptions.BadRequest( + 409, + message="Cannot 'resize' vm_state is in ERROR", + ) + + else: + self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state") + raise nvExceptions.BadRequest( + 409, + message="Cannot 'resize' instance while it is in vm_state resized", + ) + except ( + nvExceptions.BadRequest, + nvExceptions.ClientException, + nvExceptions.NotFound, + ) as e: + self._format_exception(e) + + def confirm_resize(self, vm_id): + """ + Confirm the resize of an instance + param: + vm_id: ID of an instance + """ + self._reload_connection() + self.nova.servers.confirm_resize(server=vm_id) + if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE": + self.__wait_for_vm(vm_id, "ACTIVE") + instance_status = self.get_vdu_state(vm_id)[0] + return instance_status