319 lines
14 KiB
Python
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
|