X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=RO-VIM-openstack%2Fosm_rovim_openstack%2Fvimconn_openstack.py;h=3aaedf60b9d99be35b17e32dfd681a929468cb03;hb=refs%2Fchanges%2F86%2F12586%2F5;hp=3d54903e174c87b36f4090baaa659539c7893933;hpb=247cc43aff9f3a6fe2ec3f083eeb5ec8276c7774;p=osm%2FRO.git diff --git a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py index 3d54903e..3aaedf60 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 @@ -1282,13 +1283,7 @@ class vimconnector(vimconn.VimConnector): if numas: numa_nodes = len(numas) - if numa_nodes > 1: - return -1, "Can not add flavor with more than one numa" - extra_specs["hw:numa_nodes"] = str(numa_nodes) - extra_specs["hw:mem_page_size"] = "large" - extra_specs["hw:cpu_policy"] = "dedicated" - extra_specs["hw:numa_mempolicy"] = "strict" if self.vim_type == "VIO": extra_specs[ @@ -1297,13 +1292,25 @@ class vimconnector(vimconn.VimConnector): extra_specs["vmware:latency_sensitivity_level"] = "high" for numa in numas: + if "id" in numa: + node_id = numa["id"] + + if "memory" in numa: + memory_mb = numa["memory"] * 1024 + memory = "hw:numa_mem.{}".format(node_id) + extra_specs[memory] = int(memory_mb) + + if "vcpu" in numa: + vcpu = numa["vcpu"] + cpu = "hw:numa_cpus.{}".format(node_id) + vcpu = ",".join(map(str, vcpu)) + extra_specs[cpu] = vcpu + # overwrite ram and vcpus # check if key "memory" is present in numa else use ram value at flavor - if "memory" in numa: - ram = numa["memory"] * 1024 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/ # implemented/virt-driver-cpu-thread-pinning.html - extra_specs["hw:cpu_sockets"] = 1 + extra_specs["hw:cpu_sockets"] = str(numa_nodes) if "paired-threads" in numa: vcpus = numa["paired-threads"] * 2 @@ -1349,6 +1356,42 @@ 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"), + ) + if extended.get("cpu-pinning-policy"): + extra_specs["hw:cpu_policy"] = extended.get( + "cpu-pinning-policy" + ).lower() + + # Set the cpu thread pinning policy as specified in the descriptor + if extended.get("cpu-thread-pinning-policy"): + extra_specs["hw:cpu_thread_policy"] = extended.get( + "cpu-thread-pinning-policy" + ).lower() + + # Set the mem policy as specified in the descriptor + if extended.get("mem-policy"): + extra_specs["hw:numa_mempolicy"] = extended.get( + "mem-policy" + ).lower() + # create flavor new_flavor = self.nova.flavors.create( name=name, @@ -1877,32 +1920,61 @@ class vimconnector(vimconn.VimConnector): if disk_list: block_device_mapping = {} for disk in disk_list: - if disk.get("vim_id"): - block_device_mapping["_vd" + chr(base_disk_index)] = disk[ - "vim_id" - ] - existing_vim_volumes.append({"id": disk["vim_id"]}) - else: - if "image_id" in disk: - base_disk_index = ord("a") + if "image_id" in disk: + # persistent root volume + base_disk_index = ord("a") + image_id = "" + # use existing persistent root volume + if disk.get("vim_volume_id"): + block_device_mapping["vd" + chr(base_disk_index)] = disk[ + "vim_volume_id" + ] + existing_vim_volumes.append({"id": disk["vim_volume_id"]}) + # use existing persistent root volume + elif disk.get("vim_id"): + block_device_mapping["vd" + chr(base_disk_index)] = disk[ + "vim_id" + ] + existing_vim_volumes.append({"id": disk["vim_id"]}) + else: + # create persistent root volume volume = self.cinder.volumes.create( size=disk["size"], - name=name + "_vd" + chr(base_disk_index), + 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 + created_items["volume:" + str(volume.id)] = True + block_device_mapping[ + "vd" + chr(base_disk_index) + ] = volume.id + else: + # non-root persistent volume + key_id = ( + "vim_volume_id" + if "vim_volume_id" in disk.keys() + else "vim_id" + ) + if disk.get(key_id): + # use existing persistent volume + block_device_mapping["vd" + chr(base_disk_index)] = disk[ + key_id + ] + existing_vim_volumes.append({"id": disk[key_id]}) else: + # create persistent volume volume = self.cinder.volumes.create( size=disk["size"], - name=name + "_vd" + chr(base_disk_index), + 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 - block_device_mapping["_vd" + chr(base_disk_index)] = volume.id + created_items["volume:" + str(volume.id)] = True + block_device_mapping[ + "vd" + chr(base_disk_index) + ] = volume.id base_disk_index += 1 @@ -1967,9 +2039,9 @@ class vimconnector(vimconn.VimConnector): ) ) server = self.nova.servers.create( - name, - image_id, - flavor_id, + name=name, + image=image_id, + flavor=flavor_id, nics=net_list_vim, security_groups=self.config.get("security_groups"), # TODO remove security_groups in future versions. Already at neutron port @@ -2274,7 +2346,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) @@ -2493,12 +2570,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: @@ -3538,3 +3632,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