nova/nova/virt/hyperv/volumeops.py

319 lines
14 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 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.
"""
Management class for Storage-related functions (attach, detach, etc).
"""
import time
from nova import block_device
from nova.openstack.common import cfg
from nova.openstack.common import log as logging
from nova.virt import driver
from nova.virt.hyperv import baseops
from nova.virt.hyperv import vmutils
from nova.virt.hyperv import volumeutils
from nova.virt.hyperv import volumeutilsV2
LOG = logging.getLogger(__name__)
hyper_volumeops_opts = [
cfg.IntOpt('hyperv_attaching_volume_retry_count',
default=10,
help='The number of times we retry on attaching volume '),
cfg.IntOpt('hyperv_wait_between_attach_retry',
default=5,
help='The seconds to wait between an volume attachment attempt'),
cfg.BoolOpt('force_volumeutils_v1',
default=False,
help='Force volumeutils v1'),
]
CONF = cfg.CONF
CONF.register_opts(hyper_volumeops_opts)
CONF.import_opt('my_ip', 'nova.config')
class VolumeOps(baseops.BaseOps):
"""
Management class for Volume-related tasks
"""
def __init__(self):
super(VolumeOps, self).__init__()
self._vmutils = vmutils.VMUtils()
self._driver = driver
self._block_device = block_device
self._time = time
self._initiator = None
self._default_root_device = 'vda'
self._attaching_volume_retry_count = \
CONF.hyperv_attaching_volume_retry_count
self._wait_between_attach_retry = \
CONF.hyperv_wait_between_attach_retry
self._volutils = self._get_volume_utils()
def _get_volume_utils(self):
if(not CONF.force_volumeutils_v1) and \
(self._get_hypervisor_version() >= 6.2):
return volumeutilsV2.VolumeUtilsV2(
self._conn_storage, self._conn_wmi)
else:
return volumeutils.VolumeUtils(self._conn_wmi)
def _get_hypervisor_version(self):
"""Get hypervisor version.
:returns: hypervisor version (ex. 12003)
"""
version = self._conn_cimv2.Win32_OperatingSystem()[0]\
.Version
LOG.info(_('Windows version: %s ') % version)
return version
def attach_boot_volume(self, block_device_info, vm_name):
"""Attach the boot volume to the IDE controller."""
LOG.debug(_("block device info: %s"), block_device_info)
ebs_root = self._driver.block_device_info_get_mapping(
block_device_info)[0]
connection_info = ebs_root['connection_info']
data = connection_info['data']
target_lun = data['target_lun']
target_iqn = data['target_iqn']
target_portal = data['target_portal']
self._volutils.login_storage_target(target_lun, target_iqn,
target_portal)
try:
#Getting the mounted disk
mounted_disk = self._get_mounted_disk_from_lun(target_iqn,
target_lun)
#Attach to IDE controller
#Find the IDE controller for the vm.
vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name)
vm = vms[0]
vmsettings = vm.associators(
wmi_result_class='Msvm_VirtualSystemSettingData')
rasds = vmsettings[0].associators(
wmi_result_class='MSVM_ResourceAllocationSettingData')
ctrller = [r for r in rasds
if r.ResourceSubType == 'Microsoft Emulated IDE Controller'
and r.Address == "0"]
#Attaching to the same slot as the VHD disk file
self._attach_volume_to_controller(ctrller, 0, mounted_disk, vm)
except Exception as exn:
LOG.exception(_('Attach boot from volume failed: %s'), exn)
self._volutils.logout_storage_target(target_iqn)
raise vmutils.HyperVException(
_('Unable to attach boot volume to instance %s')
% vm_name)
def volume_in_mapping(self, mount_device, block_device_info):
return self._volutils.volume_in_mapping(mount_device,
block_device_info)
def attach_volume(self, connection_info, instance_name, mountpoint):
"""Attach a volume to the SCSI controller."""
LOG.debug(_("Attach_volume: %(connection_info)s, %(instance_name)s,"
" %(mountpoint)s") % locals())
data = connection_info['data']
target_lun = data['target_lun']
target_iqn = data['target_iqn']
target_portal = data['target_portal']
self._volutils.login_storage_target(target_lun, target_iqn,
target_portal)
try:
#Getting the mounted disk
mounted_disk = self._get_mounted_disk_from_lun(target_iqn,
target_lun)
#Find the SCSI controller for the vm
vms = self._conn.MSVM_ComputerSystem(ElementName=instance_name)
vm = vms[0]
vmsettings = vm.associators(
wmi_result_class='Msvm_VirtualSystemSettingData')
rasds = vmsettings[0].associators(
wmi_result_class='MSVM_ResourceAllocationSettingData')
ctrller = [r for r in rasds
if r.ResourceSubType == 'Microsoft Synthetic SCSI Controller']
self._attach_volume_to_controller(
ctrller, self._get_free_controller_slot(ctrller[0]),
mounted_disk, vm)
except Exception as exn:
LOG.exception(_('Attach volume failed: %s'), exn)
self._volutils.logout_storage_target(target_iqn)
raise vmutils.HyperVException(
_('Unable to attach volume to instance %s')
% instance_name)
def _attach_volume_to_controller(self, controller, address, mounted_disk,
instance):
"""Attach a volume to a controller."""
#Find the default disk drive object for the vm and clone it.
diskdflt = self._conn.query(
"SELECT * FROM Msvm_ResourceAllocationSettingData \
WHERE ResourceSubType LIKE 'Microsoft Physical Disk Drive'\
AND InstanceID LIKE '%Default%'")[0]
diskdrive = self._vmutils.clone_wmi_obj(self._conn,
'Msvm_ResourceAllocationSettingData', diskdflt)
diskdrive.Address = address
diskdrive.Parent = controller[0].path_()
diskdrive.HostResource = [mounted_disk[0].path_()]
new_resources = self._vmutils.add_virt_resource(self._conn, diskdrive,
instance)
if new_resources is None:
raise vmutils.HyperVException(_('Failed to add volume to VM %s') %
instance)
def _get_free_controller_slot(self, scsi_controller):
#Getting volumes mounted in the SCSI controller
volumes = self._conn.query(
"SELECT * FROM Msvm_ResourceAllocationSettingData \
WHERE ResourceSubType LIKE 'Microsoft Physical Disk Drive'\
AND Parent = '" + scsi_controller.path_() + "'")
#Slots starts from 0, so the lenght of the disks gives us the free slot
return len(volumes)
def detach_volume(self, connection_info, instance_name, mountpoint):
"""Dettach a volume to the SCSI controller."""
LOG.debug(_("Detach_volume: %(connection_info)s, %(instance_name)s,"
" %(mountpoint)s") % locals())
data = connection_info['data']
target_lun = data['target_lun']
target_iqn = data['target_iqn']
#Getting the mounted disk
mounted_disk = self._get_mounted_disk_from_lun(target_iqn, target_lun)
physical_list = self._conn.query(
"SELECT * FROM Msvm_ResourceAllocationSettingData \
WHERE ResourceSubType LIKE 'Microsoft Physical Disk Drive'")
physical_disk = 0
for phydisk in physical_list:
host_resource_list = phydisk.HostResource
if host_resource_list is None:
continue
host_resource = str(host_resource_list[0].lower())
mounted_disk_path = str(mounted_disk[0].path_().lower())
LOG.debug(_("Mounted disk to detach is: %s"), mounted_disk_path)
LOG.debug(_("host_resource disk detached is: %s"), host_resource)
if host_resource == mounted_disk_path:
physical_disk = phydisk
LOG.debug(_("Physical disk detached is: %s"), physical_disk)
vms = self._conn.MSVM_ComputerSystem(ElementName=instance_name)
vm = vms[0]
remove_result = self._vmutils.remove_virt_resource(self._conn,
physical_disk, vm)
if remove_result is False:
raise vmutils.HyperVException(
_('Failed to remove volume from VM %s') %
instance_name)
#Sending logout
self._volutils.logout_storage_target(target_iqn)
def get_volume_connector(self, instance):
if not self._initiator:
self._initiator = self._get_iscsi_initiator()
if not self._initiator:
LOG.warn(_('Could not determine iscsi initiator name'),
instance=instance)
return {
'ip': CONF.my_ip,
'initiator': self._initiator,
}
def _get_iscsi_initiator(self):
return self._volutils.get_iscsi_initiator(self._conn_cimv2)
def _get_mounted_disk_from_lun(self, target_iqn, target_lun):
initiator_session = self._conn_wmi.query(
"SELECT * FROM MSiSCSIInitiator_SessionClass \
WHERE TargetName='" + target_iqn + "'")[0]
devices = initiator_session.Devices
device_number = None
for device in devices:
LOG.debug(_("device.InitiatorName: %s"), device.InitiatorName)
LOG.debug(_("device.TargetName: %s"), device.TargetName)
LOG.debug(_("device.ScsiPortNumber: %s"), device.ScsiPortNumber)
LOG.debug(_("device.ScsiPathId: %s"), device.ScsiPathId)
LOG.debug(_("device.ScsiTargetId): %s"), device.ScsiTargetId)
LOG.debug(_("device.ScsiLun: %s"), device.ScsiLun)
LOG.debug(_("device.DeviceInterfaceGuid :%s"),
device.DeviceInterfaceGuid)
LOG.debug(_("device.DeviceInterfaceName: %s"),
device.DeviceInterfaceName)
LOG.debug(_("device.LegacyName: %s"), device.LegacyName)
LOG.debug(_("device.DeviceType: %s"), device.DeviceType)
LOG.debug(_("device.DeviceNumber %s"), device.DeviceNumber)
LOG.debug(_("device.PartitionNumber :%s"), device.PartitionNumber)
scsi_lun = device.ScsiLun
if scsi_lun == target_lun:
device_number = device.DeviceNumber
if device_number is None:
raise vmutils.HyperVException(
_('Unable to find a mounted disk for'
' target_iqn: %s') % target_iqn)
LOG.debug(_("Device number : %s"), device_number)
LOG.debug(_("Target lun : %s"), target_lun)
#Finding Mounted disk drive
for i in range(1, self._attaching_volume_retry_count):
mounted_disk = self._conn.query(
"SELECT * FROM Msvm_DiskDrive WHERE DriveNumber=" +
str(device_number) + "")
LOG.debug(_("Mounted disk is: %s"), mounted_disk)
if len(mounted_disk) > 0:
break
self._time.sleep(self._wait_between_attach_retry)
mounted_disk = self._conn.query(
"SELECT * FROM Msvm_DiskDrive WHERE DriveNumber=" +
str(device_number) + "")
LOG.debug(_("Mounted disk is: %s"), mounted_disk)
if len(mounted_disk) == 0:
raise vmutils.HyperVException(
_('Unable to find a mounted disk for'
' target_iqn: %s') % target_iqn)
return mounted_disk
def disconnect_volume(self, physical_drive_path):
#Get the session_id of the ISCSI connection
session_id = self._get_session_id_from_mounted_disk(
physical_drive_path)
#Logging out the target
self._volutils.execute_log_out(session_id)
def _get_session_id_from_mounted_disk(self, physical_drive_path):
drive_number = self._get_drive_number_from_disk_path(
physical_drive_path)
LOG.debug(_("Drive number to disconnect is: %s"), drive_number)
initiator_sessions = self._conn_wmi.query(
"SELECT * FROM MSiSCSIInitiator_SessionClass")
for initiator_session in initiator_sessions:
devices = initiator_session.Devices
for device in devices:
deviceNumber = str(device.DeviceNumber)
LOG.debug(_("DeviceNumber : %s"), deviceNumber)
if deviceNumber == drive_number:
return initiator_session.SessionId
def _get_drive_number_from_disk_path(self, disk_path):
LOG.debug(_("Disk path to parse: %s"), disk_path)
start_device_id = disk_path.find('"', disk_path.find('DeviceID'))
LOG.debug(_("start_device_id: %s"), start_device_id)
end_device_id = disk_path.find('"', start_device_id + 1)
LOG.debug(_("end_device_id: %s"), end_device_id)
deviceID = disk_path[start_device_id + 1:end_device_id]
return deviceID[deviceID.find("\\") + 2:]
def get_default_root_device(self):
return self._default_root_device