# Copyright (c) 2010 Cloud.com, Inc # Copyright 2012 Cloudbase Solutions Srl / Pedro Navarro Perez # 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. """ Utility class for VM related operations. Based on the "root/virtualization/v2" namespace available starting with Hyper-V Server / Windows Server 2012. """ import functools import time import uuid from eventlet import patcher from eventlet import tpool from oslo_log import log as logging from oslo_utils import uuidutils from six.moves import range # noqa from os_win._i18n import _ from os_win import _utils from os_win import constants from os_win import exceptions from os_win.utils import _wqlutils from os_win.utils import baseutils from os_win.utils import jobutils from os_win.utils import pathutils LOG = logging.getLogger(__name__) # TODO(claudiub): remove the is_planned_vm argument from methods once it is not # used anymore. class VMUtils(baseutils.BaseUtilsVirt): # These constants can be overridden by inherited classes _PHYS_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Physical Disk Drive' _DISK_DRIVE_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic Disk Drive' _DVD_DRIVE_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic DVD Drive' _HARD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual Hard Disk' _DVD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual CD/DVD Disk' _IDE_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:Emulated IDE Controller' _SCSI_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic SCSI Controller' _SERIAL_PORT_RES_SUB_TYPE = 'Microsoft:Hyper-V:Serial Port' _SETTINGS_DEFINE_STATE_CLASS = 'Msvm_SettingsDefineState' _VIRTUAL_SYSTEM_SETTING_DATA_CLASS = 'Msvm_VirtualSystemSettingData' _RESOURCE_ALLOC_SETTING_DATA_CLASS = 'Msvm_ResourceAllocationSettingData' _PROCESSOR_SETTING_DATA_CLASS = 'Msvm_ProcessorSettingData' _MEMORY_SETTING_DATA_CLASS = 'Msvm_MemorySettingData' _SERIAL_PORT_SETTING_DATA_CLASS = _RESOURCE_ALLOC_SETTING_DATA_CLASS _STORAGE_ALLOC_SETTING_DATA_CLASS = 'Msvm_StorageAllocationSettingData' _SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS = ( 'Msvm_SyntheticEthernetPortSettingData') _AFFECTED_JOB_ELEMENT_CLASS = "Msvm_AffectedJobElement" _CIM_RES_ALLOC_SETTING_DATA_CLASS = 'Cim_ResourceAllocationSettingData' _COMPUTER_SYSTEM_CLASS = "Msvm_ComputerSystem" _LOGICAL_IDENTITY_CLASS = 'Msvm_LogicalIdentity' _VIRTUAL_SYSTEM_SNAP_ASSOC_CLASS = 'Msvm_SnapshotOfVirtualSystem' _S3_DISP_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:S3 Display Controller' _SYNTH_DISP_CTRL_RES_SUB_TYPE = ('Microsoft:Hyper-V:Synthetic Display ' 'Controller') _REMOTEFX_DISP_CTRL_RES_SUB_TYPE = ('Microsoft:Hyper-V:Synthetic 3D ' 'Display Controller') _SYNTH_DISP_ALLOCATION_SETTING_DATA_CLASS = ( 'Msvm_SyntheticDisplayControllerSettingData') _REMOTEFX_DISP_ALLOCATION_SETTING_DATA_CLASS = ( 'Msvm_Synthetic3DDisplayControllerSettingData') _VIRTUAL_SYSTEM_SUBTYPE = 'VirtualSystemSubType' _VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized' _VIRTUAL_SYSTEM_TYPE_PLANNED = 'Microsoft:Hyper-V:System:Planned' _VIRTUAL_SYSTEM_SUBTYPE_GEN2 = 'Microsoft:Hyper-V:SubType:2' _SNAPSHOT_FULL = 2 _VM_ENABLED_STATE_PROP = "EnabledState" _SHUTDOWN_COMPONENT = "Msvm_ShutdownComponent" _VIRTUAL_SYSTEM_CURRENT_SETTINGS = 3 _AUTOMATIC_STARTUP_ACTION_NONE = 2 _remote_fx_res_map = { constants.REMOTEFX_MAX_RES_1024x768: 0, constants.REMOTEFX_MAX_RES_1280x1024: 1, constants.REMOTEFX_MAX_RES_1600x1200: 2, constants.REMOTEFX_MAX_RES_1920x1200: 3, constants.REMOTEFX_MAX_RES_2560x1600: 4 } _remotefx_max_monitors_map = { # defines the maximum number of monitors for a given # resolution constants.REMOTEFX_MAX_RES_1024x768: 4, constants.REMOTEFX_MAX_RES_1280x1024: 4, constants.REMOTEFX_MAX_RES_1600x1200: 3, constants.REMOTEFX_MAX_RES_1920x1200: 2, constants.REMOTEFX_MAX_RES_2560x1600: 1 } _DISP_CTRL_ADDRESS_DX_11 = "02C1,00000000,01" _DISP_CTRL_ADDRESS = "5353,00000000,00" _vm_power_states_map = {constants.HYPERV_VM_STATE_ENABLED: 2, constants.HYPERV_VM_STATE_DISABLED: 3, constants.HYPERV_VM_STATE_REBOOT: 11, constants.HYPERV_VM_STATE_PAUSED: 9, constants.HYPERV_VM_STATE_SUSPENDED: 6} _disk_ctrl_type_mapping = { _SCSI_CTRL_RES_SUB_TYPE: constants.CTRL_TYPE_SCSI, _IDE_CTRL_RES_SUB_TYPE: constants.CTRL_TYPE_IDE } _DEFAULT_EVENT_CHECK_TIMEFRAME = 60 # seconds def __init__(self, host='.'): super(VMUtils, self).__init__(host) self._jobutils = jobutils.JobUtils(host) self._pathutils = pathutils.PathUtils() self._enabled_states_map = {v: k for k, v in self._vm_power_states_map.items()} def list_instance_notes(self): instance_notes = [] for vs in self._conn.Msvm_VirtualSystemSettingData( ['ElementName', 'Notes'], VirtualSystemType=self._VIRTUAL_SYSTEM_TYPE_REALIZED): vs_notes = vs.Notes vs_name = vs.ElementName if vs_notes is not None and vs_name: instance_notes.append( (vs_name, [v for v in vs_notes if v])) return instance_notes def list_instances(self): """Return the names of all the instances known to Hyper-V.""" return [v.ElementName for v in self._conn.Msvm_VirtualSystemSettingData( ['ElementName'], VirtualSystemType=self._VIRTUAL_SYSTEM_TYPE_REALIZED)] @_utils.not_found_decorator( translated_exc=exceptions.HyperVVMNotFoundException) def get_vm_summary_info(self, vm_name): vmsettings = self._lookup_vm_check(vm_name) settings_paths = [vmsettings.path_()] # See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx (ret_val, summary_info) = self._vs_man_svc.GetSummaryInformation( [constants.VM_SUMMARY_NUM_PROCS, constants.VM_SUMMARY_ENABLED_STATE, constants.VM_SUMMARY_MEMORY_USAGE, constants.VM_SUMMARY_UPTIME], settings_paths) if ret_val: raise exceptions.HyperVException( _('Cannot get VM summary data for: %s') % vm_name) si = summary_info[0] memory_usage = None if si.MemoryUsage is not None: memory_usage = int(si.MemoryUsage) up_time = None if si.UpTime is not None: up_time = int(si.UpTime) # Nova requires a valid state to be returned. Hyper-V has more # states than Nova, typically intermediate ones and since there is # no direct mapping for those, ENABLED is the only reasonable option # considering that in all the non mappable states the instance # is running. enabled_state = self._enabled_states_map.get(si.EnabledState, constants. HYPERV_VM_STATE_ENABLED) summary_info_dict = {'NumberOfProcessors': si.NumberOfProcessors, 'EnabledState': enabled_state, 'MemoryUsage': memory_usage, 'UpTime': up_time} return summary_info_dict def get_vm_state(self, vm_name): settings = self.get_vm_summary_info(vm_name) return settings['EnabledState'] def _lookup_vm_check(self, vm_name, as_vssd=True, for_update=False): vm = self._lookup_vm(vm_name, as_vssd, for_update) if not vm: raise exceptions.HyperVVMNotFoundException(vm_name=vm_name) return vm def _lookup_vm(self, vm_name, as_vssd=True, for_update=False): if as_vssd: conn = self._compat_conn if for_update else self._conn vms = conn.Msvm_VirtualSystemSettingData(ElementName=vm_name) vms = [v for v in vms if v.VirtualSystemType in [self._VIRTUAL_SYSTEM_TYPE_PLANNED, self._VIRTUAL_SYSTEM_TYPE_REALIZED]] else: vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) n = len(vms) if n == 0: return None elif n > 1: raise exceptions.HyperVException( _('Duplicate VM name found: %s') % vm_name) else: return vms[0] def vm_exists(self, vm_name): """Checks if the Realized VM with the given name exists on the host.""" # NOTE(claudiub): A planned VM and a realized VM cannot exist at the # same time on the same host. The 2 types must be treated separately, # thus, this will only check if the Realized VM exits. return self._lookup_vm(vm_name, False) is not None def get_vm_id(self, vm_name): vm = self._lookup_vm_check(vm_name, as_vssd=False) return vm.Name def get_vm_memory_info(self, vm_name): vmsetting = self._lookup_vm_check(vm_name) memory = self._get_vm_memory(vmsetting) memory_info_dict = { 'DynamicMemoryEnabled': memory.DynamicMemoryEnabled, 'Reservation': memory.Reservation, 'Limit': memory.Limit, 'Weight': memory.Weight, 'MaxMemoryBlocksPerNumaNode': memory.MaxMemoryBlocksPerNumaNode, } return memory_info_dict def _get_vm_memory(self, vmsetting): mem_settings = _wqlutils.get_element_associated_class( self._compat_conn, self._MEMORY_SETTING_DATA_CLASS, element_instance_id=vmsetting.InstanceID)[0] return mem_settings def _set_vm_memory(self, vmsetting, memory_mb, memory_per_numa_node, dynamic_memory_ratio): mem_settings = self._get_vm_memory(vmsetting) max_mem = int(memory_mb) mem_settings.Limit = max_mem if dynamic_memory_ratio > 1: mem_settings.DynamicMemoryEnabled = True # Must be a multiple of 2 reserved_mem = min( int(max_mem / dynamic_memory_ratio) >> 1 << 1, max_mem) else: mem_settings.DynamicMemoryEnabled = False reserved_mem = max_mem mem_settings.Reservation = reserved_mem # Start with the minimum memory mem_settings.VirtualQuantity = reserved_mem if memory_per_numa_node: # One memory block is 1 MB. mem_settings.MaxMemoryBlocksPerNumaNode = memory_per_numa_node self._jobutils.modify_virt_resource(mem_settings) def _set_vm_vcpus(self, vmsetting, vcpus_num, vcpus_per_numa_node, limit_cpu_features): procsetting = _wqlutils.get_element_associated_class( self._compat_conn, self._PROCESSOR_SETTING_DATA_CLASS, element_instance_id=vmsetting.InstanceID)[0] vcpus = int(vcpus_num) procsetting.VirtualQuantity = vcpus procsetting.Reservation = vcpus procsetting.Limit = 100000 # static assignment to 100% procsetting.LimitProcessorFeatures = limit_cpu_features if vcpus_per_numa_node: procsetting.MaxProcessorsPerNumaNode = vcpus_per_numa_node self._jobutils.modify_virt_resource(procsetting) def set_nested_virtualization(self, vm_name, state): """Enables nested virtualization for the given VM. :raises NotImplemented: Nested virtualization is supported on Windows / Hyper-V Server 2016 or newer. """ raise NotImplementedError(_('Nested virtualization is supported on ' 'Windows / Hyper-V Server 2016 or newer.')) def update_vm(self, vm_name, memory_mb, memory_per_numa_node, vcpus_num, vcpus_per_numa_node, limit_cpu_features, dynamic_mem_ratio, configuration_root_dir=None, snapshot_dir=None, host_shutdown_action=None, vnuma_enabled=None, snapshot_type=None, is_planned_vm=False, chassis_asset_tag=None): vmsetting = self._lookup_vm_check(vm_name, for_update=True) if host_shutdown_action: vmsetting.AutomaticShutdownAction = host_shutdown_action if configuration_root_dir: # Created VMs must have their *DataRoot paths in the same location # as the VM's path. vmsetting.ConfigurationDataRoot = configuration_root_dir vmsetting.LogDataRoot = configuration_root_dir vmsetting.SnapshotDataRoot = configuration_root_dir vmsetting.SuspendDataRoot = configuration_root_dir vmsetting.SwapFileDataRoot = configuration_root_dir if vnuma_enabled is not None: vmsetting.VirtualNumaEnabled = vnuma_enabled self._set_vm_memory(vmsetting, memory_mb, memory_per_numa_node, dynamic_mem_ratio) self._set_vm_vcpus(vmsetting, vcpus_num, vcpus_per_numa_node, limit_cpu_features) if snapshot_type: self._set_vm_snapshot_type(vmsetting, snapshot_type) if chassis_asset_tag: vmsetting.ChassisAssetTag = chassis_asset_tag self._modify_virtual_system(vmsetting) def check_admin_permissions(self): if not self._compat_conn.Msvm_VirtualSystemManagementService(): raise exceptions.HyperVAuthorizationException() def create_vm(self, vm_name, vnuma_enabled, vm_gen, instance_path, notes=None): LOG.debug('Creating VM %s', vm_name) vs_data = self._compat_conn.Msvm_VirtualSystemSettingData.new() vs_data.ElementName = vm_name vs_data.Notes = notes # Don't start automatically on host boot vs_data.AutomaticStartupAction = self._AUTOMATIC_STARTUP_ACTION_NONE vs_data.VirtualNumaEnabled = vnuma_enabled if vm_gen == constants.VM_GEN_2: vs_data.VirtualSystemSubType = self._VIRTUAL_SYSTEM_SUBTYPE_GEN2 vs_data.SecureBootEnabled = False # Created VMs must have their *DataRoot paths in the same location as # the instances' path. vs_data.ConfigurationDataRoot = instance_path vs_data.LogDataRoot = instance_path vs_data.SnapshotDataRoot = instance_path vs_data.SuspendDataRoot = instance_path vs_data.SwapFileDataRoot = instance_path (job_path, vm_path, ret_val) = self._vs_man_svc.DefineSystem( ResourceSettings=[], ReferenceConfiguration=None, SystemSettings=vs_data.GetText_(1)) self._jobutils.check_ret_val(ret_val, job_path) @_utils.retry_decorator(exceptions=exceptions.HyperVException) def _modify_virtual_system(self, vmsetting): (job_path, ret_val) = self._vs_man_svc.ModifySystemSettings( SystemSettings=vmsetting.GetText_(1)) self._jobutils.check_ret_val(ret_val, job_path) def get_vm_scsi_controller(self, vm_name): vmsettings = self._lookup_vm_check(vm_name) return self._get_vm_scsi_controller(vmsettings) def _get_vm_scsi_controller(self, vmsettings): res = self._get_vm_disk_controllers(vmsettings, self._SCSI_CTRL_RES_SUB_TYPE) return res[0].path_() if res else None def _get_vm_disk_controllers(self, vmsettings, ctrl_res_sub_type): rasds = _wqlutils.get_element_associated_class( self._conn, self._RESOURCE_ALLOC_SETTING_DATA_CLASS, element_instance_id=vmsettings.InstanceID) res = [r for r in rasds if r.ResourceSubType == ctrl_res_sub_type] return res def _get_vm_ide_controller(self, vmsettings, ctrller_addr): ide_ctrls = self._get_vm_disk_controllers(vmsettings, self._IDE_CTRL_RES_SUB_TYPE) ctrl = [r for r in ide_ctrls if r.Address == str(ctrller_addr)] return ctrl[0].path_() if ctrl else None def get_vm_ide_controller(self, vm_name, ctrller_addr): vmsettings = self._lookup_vm_check(vm_name) return self._get_vm_ide_controller(vmsettings, ctrller_addr) def _get_disk_ctrl_addr(self, controller_path): ctrl = self._get_wmi_obj(controller_path) if ctrl.ResourceSubType == self._IDE_CTRL_RES_SUB_TYPE: return ctrl.Address vmsettings = ctrl.associators( wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)[0] # The powershell commandlets rely on the controller index as SCSI # controllers are missing the 'Address' attribute. We'll do the # same. scsi_ctrls = self._get_vm_disk_controllers( vmsettings, self._SCSI_CTRL_RES_SUB_TYPE) ctrl_paths = [rasd.path_().upper() for rasd in scsi_ctrls] if controller_path.upper() in ctrl_paths: return ctrl_paths.index(controller_path.upper()) def get_attached_disks(self, scsi_controller_path): volumes = self._conn.query( self._get_attached_disks_query_string(scsi_controller_path)) return volumes def _get_attached_disks_query_string(self, scsi_controller_path): # DVD Drives can be attached to SCSI as well, if the VM Generation is 2 return ("SELECT * FROM Msvm_ResourceAllocationSettingData WHERE (" "ResourceSubType='%(res_sub_type)s' OR " "ResourceSubType='%(res_sub_type_virt)s' OR " "ResourceSubType='%(res_sub_type_dvd)s') AND " "Parent = '%(parent)s'" % { 'res_sub_type': self._PHYS_DISK_RES_SUB_TYPE, 'res_sub_type_virt': self._DISK_DRIVE_RES_SUB_TYPE, 'res_sub_type_dvd': self._DVD_DRIVE_RES_SUB_TYPE, 'parent': scsi_controller_path.replace("'", "''")}) def _get_new_setting_data(self, class_name): obj = self._compat_conn.query("SELECT * FROM %s WHERE InstanceID " "LIKE '%%\\Default'" % class_name)[0] return obj def _get_new_resource_setting_data(self, resource_sub_type, class_name=None): if class_name is None: class_name = self._RESOURCE_ALLOC_SETTING_DATA_CLASS obj = self._compat_conn.query("SELECT * FROM %(class_name)s " "WHERE ResourceSubType = " "'%(res_sub_type)s' AND " "InstanceID LIKE '%%\\Default'" % {"class_name": class_name, "res_sub_type": resource_sub_type})[0] return obj def attach_scsi_drive(self, vm_name, path, drive_type=constants.DISK): vmsettings = self._lookup_vm_check(vm_name) ctrller_path = self._get_vm_scsi_controller(vmsettings) drive_addr = self.get_free_controller_slot(ctrller_path) self.attach_drive(vm_name, path, ctrller_path, drive_addr, drive_type) def attach_ide_drive(self, vm_name, path, ctrller_addr, drive_addr, drive_type=constants.DISK): vmsettings = self._lookup_vm_check(vm_name) ctrller_path = self._get_vm_ide_controller(vmsettings, ctrller_addr) self.attach_drive(vm_name, path, ctrller_path, drive_addr, drive_type) def attach_drive(self, vm_name, path, ctrller_path, drive_addr, drive_type=constants.DISK): """Create a drive and attach it to the vm.""" vm = self._lookup_vm_check(vm_name, as_vssd=False) if drive_type == constants.DISK: res_sub_type = self._DISK_DRIVE_RES_SUB_TYPE elif drive_type == constants.DVD: res_sub_type = self._DVD_DRIVE_RES_SUB_TYPE drive = self._get_new_resource_setting_data(res_sub_type) # Set the ctrller as parent. drive.Parent = ctrller_path drive.Address = drive_addr drive.AddressOnParent = drive_addr # Add the cloned disk drive object to the vm. new_resources = self._jobutils.add_virt_resource(drive, vm) drive_path = new_resources[0] if drive_type == constants.DISK: res_sub_type = self._HARD_DISK_RES_SUB_TYPE elif drive_type == constants.DVD: res_sub_type = self._DVD_DISK_RES_SUB_TYPE res = self._get_new_resource_setting_data( res_sub_type, self._STORAGE_ALLOC_SETTING_DATA_CLASS) res.Parent = drive_path res.HostResource = [path] try: # Add the new vhd object as a virtual hard disk to the vm. self._jobutils.add_virt_resource(res, vm) except Exception: LOG.exception("Failed to attach disk image %(disk_path)s " "to vm %(vm_name)s. Reverting attachment.", dict(disk_path=path, vm_name=vm_name)) drive = self._get_wmi_obj(drive_path) self._jobutils.remove_virt_resource(drive) raise def get_disk_attachment_info(self, attached_disk_path=None, is_physical=True, serial=None): res = self._get_mounted_disk_resource_from_path(attached_disk_path, is_physical, serial=serial) if not res: err_msg = _("Disk '%s' is not attached to a vm.") raise exceptions.DiskNotFound(err_msg % attached_disk_path) if is_physical: drive = res else: drive = self._get_wmi_obj(res.Parent) ctrl_slot = int(drive.AddressOnParent) ctrl_path = drive.Parent ctrl_type = self._get_disk_controller_type(ctrl_path) ctrl_addr = self._get_disk_ctrl_addr(ctrl_path) attachment_info = dict(controller_slot=ctrl_slot, controller_path=ctrl_path, controller_type=ctrl_type, controller_addr=ctrl_addr) return attachment_info def _get_disk_controller_type(self, controller_path): ctrl = self._get_wmi_obj(controller_path) res_sub_type = ctrl.ResourceSubType ctrl_type = self._disk_ctrl_type_mapping[res_sub_type] return ctrl_type def create_scsi_controller(self, vm_name): """Create an iscsi controller ready to mount volumes.""" vmsettings = self._lookup_vm_check(vm_name) scsicontrl = self._get_new_resource_setting_data( self._SCSI_CTRL_RES_SUB_TYPE) scsicontrl.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] self._jobutils.add_virt_resource(scsicontrl, vmsettings) def attach_volume_to_controller(self, vm_name, controller_path, address, mounted_disk_path, serial=None): """Attach a volume to a controller.""" vmsettings = self._lookup_vm_check(vm_name) diskdrive = self._get_new_resource_setting_data( self._PHYS_DISK_RES_SUB_TYPE) diskdrive.AddressOnParent = address diskdrive.Parent = controller_path diskdrive.HostResource = [mounted_disk_path] diskdrive_path = self._jobutils.add_virt_resource(diskdrive, vmsettings)[0] if serial: # Apparently this can't be set when the resource is added. diskdrive = self._get_wmi_obj(diskdrive_path, True) diskdrive.ElementName = serial self._jobutils.modify_virt_resource(diskdrive) def get_vm_physical_disk_mapping(self, vm_name, is_planned_vm=False): mapping = {} physical_disks = ( self.get_vm_disks(vm_name)[1]) for diskdrive in physical_disks: mapping[diskdrive.ElementName] = dict( resource_path=diskdrive.path_(), mounted_disk_path=diskdrive.HostResource[0]) return mapping def _get_disk_resource_address(self, disk_resource): return disk_resource.AddressOnParent def set_disk_host_res(self, disk_res_path, mounted_disk_path): diskdrive = self._get_wmi_obj(disk_res_path, True) diskdrive.HostResource = [mounted_disk_path] self._jobutils.modify_virt_resource(diskdrive) def _get_nic_data_by_name(self, name): nics = self._conn.Msvm_SyntheticEthernetPortSettingData( ElementName=name) if nics: return nics[0] raise exceptions.HyperVvNicNotFound(vnic_name=name) def create_nic(self, vm_name, nic_name, mac_address=None): """Create a (synthetic) nic and attach it to the vm. :param vm_name: The VM name to which the NIC will be attached to. :param nic_name: The name of the NIC to be attached. :param mac_address: The VM NIC's MAC address. If None, a Dynamic MAC address will be used instead. """ # Create a new nic new_nic_data = self._get_new_setting_data( self._SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS) # Configure the nic new_nic_data.ElementName = nic_name new_nic_data.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] if mac_address: new_nic_data.Address = mac_address.replace(':', '') new_nic_data.StaticMacAddress = 'True' # Add the new nic to the vm vmsettings = self._lookup_vm_check(vm_name) self._jobutils.add_virt_resource(new_nic_data, vmsettings) def destroy_nic(self, vm_name, nic_name): """Destroys the NIC with the given nic_name from the given VM. :param vm_name: The name of the VM which has the NIC to be destroyed. :param nic_name: The NIC's ElementName. """ # TODO(claudiub): remove vm_name argument, no longer used. try: nic_data = self._get_nic_data_by_name(nic_name) self._jobutils.remove_virt_resource(nic_data) except exceptions.NotFound: LOG.debug("Ignoring NotFound exception while attempting " "to remove vm nic: '%s'. It may have been already " "deleted.", nic_name) def _get_vm_nics(self, vm_name): vmsettings = self._lookup_vm_check(vm_name) nics = _wqlutils.get_element_associated_class( self._compat_conn, self._SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS, element_instance_id=vmsettings.InstanceId) return nics def get_vm_nic_names(self, vm_name): nics = self._get_vm_nics(vm_name) return [nic.ElementName for nic in nics] def soft_shutdown_vm(self, vm_name): try: vm = self._lookup_vm_check(vm_name, as_vssd=False) shutdown_component = self._conn.Msvm_ShutdownComponent( SystemName=vm.Name) if not shutdown_component: # If no shutdown_component is found, it means the VM is already # in a shutdown state. return reason = 'Soft shutdown requested by OpenStack Nova.' (ret_val, ) = shutdown_component[0].InitiateShutdown(Force=False, Reason=reason) self._jobutils.check_ret_val(ret_val, None) except exceptions.x_wmi as ex: # This operation is expected to fail while the instance is booting. # In some cases, InitiateShutdown immediately throws an error # instead of returning an asynchronous job reference. msg = _("Soft shutdown failed. VM name: %s. Error: %s.") raise exceptions.HyperVException(msg % (vm_name, ex)) @_utils.retry_decorator(exceptions=exceptions.WMIJobFailed) def set_vm_state(self, vm_name, req_state): """Set the desired state of the VM.""" vm = self._lookup_vm_check(vm_name, as_vssd=False) (job_path, ret_val) = vm.RequestStateChange(self._vm_power_states_map[req_state]) # Invalid state for current operation (32775) typically means that # the VM is already in the state requested self._jobutils.check_ret_val(ret_val, job_path, [0, 32775]) LOG.debug("Successfully changed vm state of %(vm_name)s " "to %(req_state)s", {'vm_name': vm_name, 'req_state': req_state}) def _get_disk_resource_disk_path(self, disk_resource): return disk_resource.HostResource def get_vm_config_root_dir(self, vm_name): vmsettings = self._lookup_vm_check(vm_name) return vmsettings.ConfigurationDataRoot def get_vm_storage_paths(self, vm_name, is_planned_vm=False): vmsettings = self._lookup_vm_check(vm_name) (disk_resources, volume_resources) = self._get_vm_disks(vmsettings) volume_drives = [] for volume_resource in volume_resources: drive_path = volume_resource.HostResource[0] volume_drives.append(drive_path) disk_files = [] for disk_resource in disk_resources: disk_files.extend( [c for c in self._get_disk_resource_disk_path(disk_resource)]) return (disk_files, volume_drives) def get_vm_disks(self, vm_name, is_planned_vm=False): vmsettings = self._lookup_vm_check(vm_name) return self._get_vm_disks(vmsettings) def _get_vm_disks(self, vmsettings): rasds = _wqlutils.get_element_associated_class( self._compat_conn, self._STORAGE_ALLOC_SETTING_DATA_CLASS, element_instance_id=vmsettings.InstanceID) disk_resources = [r for r in rasds if r.ResourceSubType in [self._HARD_DISK_RES_SUB_TYPE, self._DVD_DISK_RES_SUB_TYPE]] if (self._RESOURCE_ALLOC_SETTING_DATA_CLASS != self._STORAGE_ALLOC_SETTING_DATA_CLASS): rasds = _wqlutils.get_element_associated_class( self._compat_conn, self._RESOURCE_ALLOC_SETTING_DATA_CLASS, element_instance_id=vmsettings.InstanceID) volume_resources = [r for r in rasds if r.ResourceSubType == self._PHYS_DISK_RES_SUB_TYPE] return (disk_resources, volume_resources) def destroy_vm(self, vm_name): vm = self._lookup_vm_check(vm_name, as_vssd=False) # Remove the VM. It does not destroy any associated virtual disk. (job_path, ret_val) = self._vs_man_svc.DestroySystem(vm.path_()) self._jobutils.check_ret_val(ret_val, job_path) def take_vm_snapshot(self, vm_name, snapshot_name=None): vm = self._lookup_vm_check(vm_name, as_vssd=False) vs_snap_svc = self._compat_conn.Msvm_VirtualSystemSnapshotService()[0] (job_path, snp_setting_data, ret_val) = vs_snap_svc.CreateSnapshot( AffectedSystem=vm.path_(), SnapshotType=self._SNAPSHOT_FULL) job = self._jobutils.check_ret_val(ret_val, job_path) snp_setting_data = job.associators( wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS, wmi_association_class=self._AFFECTED_JOB_ELEMENT_CLASS)[0] if snapshot_name is not None: snp_setting_data.ElementName = snapshot_name self._modify_virtual_system(snp_setting_data) return snp_setting_data.path_() def get_vm_snapshots(self, vm_name, snapshot_name=None): vm = self._lookup_vm_check(vm_name, as_vssd=False) snapshots = vm.associators( wmi_association_class=self._VIRTUAL_SYSTEM_SNAP_ASSOC_CLASS, wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS) return [ s.path_() for s in snapshots if snapshot_name is None or s.ElementName == snapshot_name] def remove_vm_snapshot(self, snapshot_path): vs_snap_svc = self._compat_conn.Msvm_VirtualSystemSnapshotService()[0] (job_path, ret_val) = vs_snap_svc.DestroySnapshot(snapshot_path) self._jobutils.check_ret_val(ret_val, job_path) def get_vm_dvd_disk_paths(self, vm_name): vmsettings = self._lookup_vm_check(vm_name) sasds = _wqlutils.get_element_associated_class( self._conn, self._STORAGE_ALLOC_SETTING_DATA_CLASS, element_instance_id=vmsettings.InstanceID) dvd_paths = [sasd.HostResource[0] for sasd in sasds if sasd.ResourceSubType == self._DVD_DISK_RES_SUB_TYPE] return dvd_paths def is_disk_attached(self, disk_path, is_physical=True): disk_resource = self._get_mounted_disk_resource_from_path(disk_path, is_physical) return disk_resource is not None def detach_vm_disk(self, vm_name, disk_path=None, is_physical=True, serial=None): # TODO(claudiub): remove vm_name argument, no longer used. disk_resource = self._get_mounted_disk_resource_from_path( disk_path, is_physical, serial=serial) if disk_resource: parent = self._conn.query("SELECT * FROM " "Msvm_ResourceAllocationSettingData " "WHERE __PATH = '%s'" % disk_resource.Parent)[0] self._jobutils.remove_virt_resource(disk_resource) if not is_physical: self._jobutils.remove_virt_resource(parent) def _get_mounted_disk_resource_from_path(self, disk_path, is_physical, serial=None): if is_physical: class_name = self._RESOURCE_ALLOC_SETTING_DATA_CLASS else: class_name = self._STORAGE_ALLOC_SETTING_DATA_CLASS query = ("SELECT * FROM %(class_name)s WHERE (" "ResourceSubType='%(res_sub_type)s' OR " "ResourceSubType='%(res_sub_type_virt)s' OR " "ResourceSubType='%(res_sub_type_dvd)s')" % { 'class_name': class_name, 'res_sub_type': self._PHYS_DISK_RES_SUB_TYPE, 'res_sub_type_virt': self._HARD_DISK_RES_SUB_TYPE, 'res_sub_type_dvd': self._DVD_DISK_RES_SUB_TYPE}) if serial: query += " AND ElementName='%s'" % serial disk_resources = self._compat_conn.query(query) for disk_resource in disk_resources: if serial: return disk_resource if disk_resource.HostResource: if disk_resource.HostResource[0].lower() == disk_path.lower(): return disk_resource def get_mounted_disk_by_drive_number(self, device_number): mounted_disks = self._conn.query("SELECT * FROM Msvm_DiskDrive " "WHERE DriveNumber=" + str(device_number)) if len(mounted_disks): return mounted_disks[0].path_() def get_controller_volume_paths(self, controller_path): disks = self._conn.query("SELECT * FROM %(class_name)s " "WHERE ResourceSubType = '%(res_sub_type)s' " "AND Parent='%(parent)s'" % {"class_name": self._RESOURCE_ALLOC_SETTING_DATA_CLASS, "res_sub_type": self._PHYS_DISK_RES_SUB_TYPE, "parent": controller_path}) disk_data = {} for disk in disks: if disk.HostResource: disk_data[disk.path().RelPath] = disk.HostResource[0] return disk_data def get_free_controller_slot(self, scsi_controller_path): attached_disks = self.get_attached_disks(scsi_controller_path) used_slots = [int(disk.AddressOnParent) for disk in attached_disks] for slot in range(constants.SCSI_CONTROLLER_SLOTS_NUMBER): if slot not in used_slots: return slot raise exceptions.HyperVException( _("Exceeded the maximum number of slots")) def enable_vm_full_scsi_command_set(self, vm_name): """Enables the full SCSI command set for the specified VM.""" vs_data = self._lookup_vm_check(vm_name) vs_data.AllowFullSCSICommandSet = True self._modify_virtual_system(vs_data) def _get_vm_serial_ports(self, vmsettings): rasds = _wqlutils.get_element_associated_class( self._compat_conn, self._SERIAL_PORT_SETTING_DATA_CLASS, element_instance_id=vmsettings.InstanceID) serial_ports = ( [r for r in rasds if r.ResourceSubType == self._SERIAL_PORT_RES_SUB_TYPE] ) return serial_ports def set_vm_serial_port_connection(self, vm_name, port_number, pipe_path): vmsettings = self._lookup_vm_check(vm_name) serial_port = self._get_vm_serial_ports(vmsettings)[port_number - 1] serial_port.Connection = [pipe_path] self._jobutils.modify_virt_resource(serial_port) def get_vm_serial_port_connections(self, vm_name): vmsettings = self._lookup_vm_check(vm_name) serial_ports = self._get_vm_serial_ports(vmsettings) conns = [serial_port.Connection[0] for serial_port in serial_ports if serial_port.Connection and serial_port.Connection[0]] return conns def get_active_instances(self): """Return the names of all the active instances known to Hyper-V.""" vm_names = self.list_instances() vms = [self._lookup_vm(vm_name, as_vssd=False) for vm_name in vm_names] active_vm_names = [v.ElementName for v in vms if v.EnabledState == constants.HYPERV_VM_STATE_ENABLED] return active_vm_names def get_vm_power_state_change_listener( self, timeframe=_DEFAULT_EVENT_CHECK_TIMEFRAME, event_timeout=constants.DEFAULT_WMI_EVENT_TIMEOUT_MS, filtered_states=None, get_handler=False): field = self._VM_ENABLED_STATE_PROP query = self._get_event_wql_query(cls=self._COMPUTER_SYSTEM_CLASS, field=field, timeframe=timeframe, filtered_states=filtered_states) listener = self._conn.Msvm_ComputerSystem.watch_for(raw_wql=query, fields=[field]) def _handle_events(callback): if patcher.is_monkey_patched('thread'): # Retrieve one by one all the events that occurred in # the checked interval. # # We use eventlet.tpool for retrieving the events in # order to avoid issues caused by greenthread/thread # communication. Note that PyMI must use the unpatched # threading module. listen = functools.partial(tpool.execute, listener, event_timeout) else: listen = functools.partial(listener, event_timeout) while True: try: event = listen() vm_name = event.ElementName vm_state = event.EnabledState vm_power_state = self.get_vm_power_state(vm_state) try: callback(vm_name, vm_power_state) except Exception: err_msg = ("Executing VM power state change " "event callback failed. " "VM name: %(vm_name)s, " "VM power state: %(vm_power_state)s.") LOG.exception(err_msg, dict(vm_name=vm_name, vm_power_state=vm_power_state)) except exceptions.x_wmi_timed_out: pass except Exception: LOG.exception( "The VM power state change event listener " "encountered an unexpected exception.") time.sleep(event_timeout / 1000) return _handle_events if get_handler else listener def _get_event_wql_query(self, cls, field, timeframe, filtered_states=None): """Return a WQL query used for polling WMI events. :param cls: the WMI class polled for events :param field: the field checked :param timeframe: check for events that occurred in the specified timeframe :param filtered_states: only catch events triggered when a WMI object transitioned into one of those states. """ query = ("SELECT %(field)s, TargetInstance " "FROM __InstanceModificationEvent " "WITHIN %(timeframe)s " "WHERE TargetInstance ISA '%(class)s' " "AND TargetInstance.%(field)s != " "PreviousInstance.%(field)s" % {'class': cls, 'field': field, 'timeframe': timeframe}) if filtered_states: checks = ["TargetInstance.%s = '%s'" % (field, state) for state in filtered_states] query += " AND (%s)" % " OR ".join(checks) return query def _get_instance_notes(self, vm_name): vmsettings = self._lookup_vm_check(vm_name) vm_notes = vmsettings.Notes or [] return [note for note in vm_notes if note] def get_instance_uuid(self, vm_name): instance_notes = self._get_instance_notes(vm_name) if instance_notes and uuidutils.is_uuid_like(instance_notes[0]): return instance_notes[0] def get_vm_power_state(self, vm_enabled_state): return self._enabled_states_map.get(vm_enabled_state, constants.HYPERV_VM_STATE_OTHER) def get_vm_generation(self, vm_name): vssd = self._lookup_vm_check(vm_name) try: # expected format: 'Microsoft:Hyper-V:SubType:2' return int(vssd.VirtualSystemSubType.split(':')[-1]) except Exception: # NOTE(claudiub): The Msvm_VirtualSystemSettingData object does not # contain the VirtualSystemSubType field on Windows Hyper-V / # Server 2012. pass return constants.VM_GEN_1 def stop_vm_jobs(self, vm_name, timeout=None): vm = self._lookup_vm_check(vm_name, as_vssd=False) self._jobutils.stop_jobs(vm, timeout) def enable_secure_boot(self, vm_name, msft_ca_required): """Enables Secure Boot for the instance with the given name. :param vm_name: The name of the VM for which Secure Boot will be enabled. :param msft_ca_required: boolean specifying whether the VM will require Microsoft UEFI Certificate Authority for Secure Boot. Only Linux guests require this CA. """ vs_data = self._lookup_vm_check(vm_name) self._set_secure_boot(vs_data, msft_ca_required) self._modify_virtual_system(vs_data) def _set_secure_boot(self, vs_data, msft_ca_required): vs_data.SecureBootEnabled = True if msft_ca_required: raise exceptions.HyperVException( _('UEFI SecureBoot is supported only on Windows instances for ' 'this Hyper-V version.')) def set_disk_qos_specs(self, disk_path, max_iops=None, min_iops=None): """Hyper-V disk QoS policy. This feature is supported on Windows / Hyper-V Server 2012 R2 or newer. :raises os_win.exceptions.UnsupportedOperation: if the given max_iops or min_iops have non-zero values. """ if min_iops or max_iops: raise exceptions.UnsupportedOperation( reason=_("Virtual disk QoS is not supported on this " "hypervisor version.")) def _drive_to_boot_source(self, drive_path): # We expect the drive path to be the one that was passed to the # 'attach_drive' or 'attach_volume_to_controller' methods. In case of # passthrough disks, the drive path will be a Msvm_DiskDrive WMI # object path while for image files it will be the actual image path. # # Note that Msvm_DiskDrive objects will also exist for attached disk # images, but that's not what we'll get in this situation. If we ever # need to accept Msvm_DiskDrive object paths for image files as well, # an extra check will be needed, but that may lead to some other # inconsistencies. is_physical = (r'root\virtualization\v2:Msvm_DiskDrive'.lower() in drive_path.lower()) drive = self._get_mounted_disk_resource_from_path( drive_path, is_physical=is_physical) rasd_path = drive.path_() if is_physical else drive.Parent bssd = self._conn.Msvm_LogicalIdentity( SystemElement=rasd_path)[0].SameElement return bssd.path_() def set_boot_order(self, vm_name, device_boot_order): if self.get_vm_generation(vm_name) == constants.VM_GEN_1: self._set_boot_order_gen1(vm_name, device_boot_order) else: self._set_boot_order_gen2(vm_name, device_boot_order) def _set_boot_order_gen1(self, vm_name, device_boot_order): vssd = self._lookup_vm_check(vm_name, for_update=True) vssd.BootOrder = tuple(device_boot_order) self._modify_virtual_system(vssd) def _set_boot_order_gen2(self, vm_name, device_boot_order): new_boot_order = [(self._drive_to_boot_source(device)) for device in device_boot_order if device] vssd = self._lookup_vm_check(vm_name) old_boot_order = vssd.BootSourceOrder # NOTE(abalutoiu): new_boot_order will contain ROOT uppercase # in the device paths while old_boot_order will contain root # lowercase, which will cause the tuple addition result to contain # each device path twice because of the root lowercase and uppercase. # Forcing all the device paths to uppercase fixes the issue. new_boot_order = [x.upper() for x in new_boot_order] old_boot_order = [x.upper() for x in old_boot_order] network_boot_devs = set(old_boot_order) ^ set(new_boot_order) vssd.BootSourceOrder = tuple(new_boot_order) + tuple(network_boot_devs) self._modify_virtual_system(vssd) def vm_gen_supports_remotefx(self, vm_gen): """RemoteFX is supported only for generation 1 virtual machines on Windows 8 / Windows Server 2012 and 2012R2. :returns: True if the given vm_gen is 1, False otherwise """ return vm_gen == constants.VM_GEN_1 def _validate_remotefx_params(self, monitor_count, max_resolution, vram_bytes=None): max_res_value = self._remote_fx_res_map.get(max_resolution) if max_res_value is None: raise exceptions.HyperVRemoteFXException( _("Unsupported RemoteFX resolution: %s") % max_resolution) if monitor_count > self._remotefx_max_monitors_map[max_resolution]: raise exceptions.HyperVRemoteFXException( _("Unsuported RemoteFX monitor count: %(count)s for " "this resolution %(res)s. Hyper-V supports a maximum " "of %(max_monitors)s monitors for this resolution.") % {'count': monitor_count, 'res': max_resolution, 'max_monitors': self._remotefx_max_monitors_map[max_resolution]}) def _set_remotefx_display_controller(self, vm, remotefx_disp_ctrl_res, monitor_count, max_resolution, vram_bytes=None): new_wmi_obj = False if not remotefx_disp_ctrl_res: new_wmi_obj = True remotefx_disp_ctrl_res = self._get_new_resource_setting_data( self._REMOTEFX_DISP_CTRL_RES_SUB_TYPE, self._REMOTEFX_DISP_ALLOCATION_SETTING_DATA_CLASS) remotefx_disp_ctrl_res.MaximumMonitors = monitor_count remotefx_disp_ctrl_res.MaximumScreenResolution = max_resolution self._set_remotefx_vram(remotefx_disp_ctrl_res, vram_bytes) if new_wmi_obj: self._jobutils.add_virt_resource(remotefx_disp_ctrl_res, vm) else: self._jobutils.modify_virt_resource(remotefx_disp_ctrl_res) def _set_remotefx_vram(self, remotefx_disp_ctrl_res, vram_bytes): pass def enable_remotefx_video_adapter(self, vm_name, monitor_count, max_resolution, vram_bytes=None): self._validate_remotefx_params(monitor_count, max_resolution, vram_bytes=vram_bytes) vm = self._lookup_vm_check(vm_name) rasds = _wqlutils.get_element_associated_class( self._compat_conn, self._CIM_RES_ALLOC_SETTING_DATA_CLASS, element_instance_id=vm.InstanceID) synth_disp_ctrl_res_list = [r for r in rasds if r.ResourceSubType == self._SYNTH_DISP_CTRL_RES_SUB_TYPE] if synth_disp_ctrl_res_list: # we need to remove the generic display controller first. self._jobutils.remove_virt_resource(synth_disp_ctrl_res_list[0]) remotefx_disp_ctrl_res = [r for r in rasds if r.ResourceSubType == self._REMOTEFX_DISP_CTRL_RES_SUB_TYPE] remotefx_disp_ctrl_res = (remotefx_disp_ctrl_res[0] if remotefx_disp_ctrl_res else None) max_res_value = self._remote_fx_res_map.get(max_resolution) self._set_remotefx_display_controller( vm, remotefx_disp_ctrl_res, monitor_count, max_res_value, vram_bytes) if self._vm_has_s3_controller(vm_name): s3_disp_ctrl_res = [r for r in rasds if r.ResourceSubType == self._S3_DISP_CTRL_RES_SUB_TYPE][0] if s3_disp_ctrl_res.Address != self._DISP_CTRL_ADDRESS_DX_11: s3_disp_ctrl_res.Address = self._DISP_CTRL_ADDRESS_DX_11 self._jobutils.modify_virt_resource(s3_disp_ctrl_res) def disable_remotefx_video_adapter(self, vm_name): vm = self._lookup_vm_check(vm_name) rasds = _wqlutils.get_element_associated_class( self._compat_conn, self._CIM_RES_ALLOC_SETTING_DATA_CLASS, element_instance_id=vm.InstanceID) remotefx_disp_ctrl_res = [r for r in rasds if r.ResourceSubType == self._REMOTEFX_DISP_CTRL_RES_SUB_TYPE] if not remotefx_disp_ctrl_res: # VM does not have RemoteFX configured. return # we need to remove the RemoteFX display controller first. self._jobutils.remove_virt_resource(remotefx_disp_ctrl_res[0]) synth_disp_ctrl_res = self._get_new_resource_setting_data( self._SYNTH_DISP_CTRL_RES_SUB_TYPE, self._SYNTH_DISP_ALLOCATION_SETTING_DATA_CLASS) self._jobutils.add_virt_resource(synth_disp_ctrl_res, vm) if self._vm_has_s3_controller(vm_name): s3_disp_ctrl_res = [r for r in rasds if r.ResourceSubType == self._S3_DISP_CTRL_RES_SUB_TYPE][0] s3_disp_ctrl_res.Address = self._DISP_CTRL_ADDRESS self._jobutils.modify_virt_resource(s3_disp_ctrl_res) def _vm_has_s3_controller(self, vm_name): return True def is_secure_vm(self, instance_name): return False def update_vm_disk_path(self, disk_path, new_disk_path, is_physical=True): disk_resource = self._get_mounted_disk_resource_from_path( disk_path=disk_path, is_physical=is_physical) disk_resource.HostResource = [new_disk_path] self._jobutils.modify_virt_resource(disk_resource) def add_pci_device(self, vm_name, vendor_id, product_id): """Adds the given PCI device to the given VM. :raises NotImplemented: PCI passthrough is supported on Windows / Hyper-V Server 2016 or newer. """ raise NotImplementedError(_('PCI passthrough is supported on ' 'Windows / Hyper-V Server 2016 or newer.')) def remove_pci_device(self, vm_name, vendor_id, product_id): """Removes the given PCI device from the given VM. :raises NotImplemented: PCI passthrough is supported on Windows / Hyper-V Server 2016 or newer. """ raise NotImplementedError(_('PCI passthrough is supported on ' 'Windows / Hyper-V Server 2016 or newer.')) def remove_all_pci_devices(self, vm_name): """Removes all the PCI devices from the given VM. There are no PCI devices attached to Windows / Hyper-V Server 2012 R2 or older VMs. """ def _set_vm_snapshot_type(self, vmsettings, snapshot_type): # Supported on Windows Server 2016 or newer. pass def populate_fsk(self, fsk_filepath, fsk_pairs): """Writes the given FSK pairs into the give file. :raises NotImplementedError: This method is required for Shielded VMs, which is supported on Windows / Hyper-V Server 2016 or newer. """ raise NotImplementedError(_('This method is supported on Windows / ' 'Hyper-V Server 2016 or newer')) def add_vtpm(self, vm_name, pdk_filepath, shielded): """Adds a vtpm and enables it with encryption or shielded option. :raises NotImplementedError: This method is required for Shielded VMs, which is supported on Windows / Hyper-V Server 2016 or newer. """ raise NotImplementedError(_('This method is supported on Windows / ' 'Hyper-V Server 2016 or newer')) def provision_vm(self, vm_name, fsk_filepath, pdk_filepath): """Provisions the given VM with the given FSK and PDK files. :raises NotImplementedError: This method is required for Shielded VMs, which is supported on Windows / Hyper-V Server 2016 or newer. """ raise NotImplementedError(_('This method is supported on Windows / ' 'Hyper-V Server 2016 or newer')) class VMUtils6_3(VMUtils): def set_disk_qos_specs(self, disk_path, max_iops=None, min_iops=None): """Sets the disk's QoS policy.""" if min_iops is None and max_iops is None: LOG.debug("Skipping setting disk QoS specs as no " "value was provided.") return disk_resource = self._get_mounted_disk_resource_from_path( disk_path, is_physical=False) if max_iops is not None: disk_resource.IOPSLimit = max_iops if min_iops is not None: disk_resource.IOPSReservation = min_iops self._jobutils.modify_virt_resource(disk_resource)