os-win/os_win/utils/storage/target/iscsi_target_utils.py

342 lines
14 KiB
Python

# Copyright 2015 Cloudbase Solutions Srl
# 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.
import sys
if sys.platform == 'win32':
import wmi
from oslo_log import log as logging
from os_win._i18n import _, _LI
from os_win import exceptions
from os_win.utils import constants
from os_win.utils import hostutils
from os_win.utils import pathutils
from os_win.utils import win32utils
LOG = logging.getLogger(__name__)
class ISCSITargetUtils(object):
ID_METHOD_DNS_NAME = 1
ID_METHOD_IPV4_ADDR = 2
ID_METHOD_MAC_ADDR = 3
ID_METHOD_IQN = 4
ID_METHOD_IPV6_ADDR = 5
_ERR_FILE_EXISTS = 80
def __init__(self):
self._conn_wmi = wmi.WMI(moniker='//./root/wmi')
self._ensure_wt_provider_available()
self._pathutils = pathutils.PathUtils()
self._hostutils = hostutils.HostUtils()
self._win32utils = win32utils.Win32Utils()
self._win_gteq_6_3 = self._hostutils.check_min_windows_version(6, 3)
def _ensure_wt_provider_available(self):
try:
self._conn_wmi.WT_Portal
except AttributeError:
err_msg = _("The Windows iSCSI target provider is not available.")
raise exceptions.ISCSITargetException(err_msg)
def get_supported_disk_format(self):
return (constants.DISK_FORMAT_VHDX
if self._win_gteq_6_3 else constants.DISK_FORMAT_VHD)
def get_supported_vhd_type(self):
return (constants.VHD_TYPE_DYNAMIC
if self._win_gteq_6_3 else constants.VHD_TYPE_FIXED)
def get_portal_locations(self, available_only=True,
fail_if_none_found=True):
wt_portals = self._conn_wmi.WT_Portal()
if available_only:
wt_portals = list(filter(lambda portal: portal.Listen, wt_portals))
if not wt_portals and fail_if_none_found:
err_msg = _("No valid iSCSI portal was found.")
raise exceptions.ISCSITargetException(err_msg)
portal_locations = [self._get_portal_location(portal)
for portal in wt_portals]
return portal_locations
def _get_portal_location(self, wt_portal):
return '%s:%s' % (wt_portal.Address, wt_portal.Port)
def _get_wt_host(self, target_name, fail_if_not_found=True):
hosts = self._conn_wmi.WT_Host(HostName=target_name)
if hosts:
return hosts[0]
elif fail_if_not_found:
err_msg = _('Could not find iSCSI target %s')
raise exceptions.ISCSITargetException(err_msg % target_name)
def _get_wt_disk(self, description, fail_if_not_found=True):
# We can retrieve WT Disks only by description.
wt_disks = self._conn_wmi.WT_Disk(Description=description)
if wt_disks:
return wt_disks[0]
elif fail_if_not_found:
err_msg = _('Could not find WT Disk: %s')
raise exceptions.ISCSITargetException(err_msg % description)
def _get_wt_snapshot(self, description, fail_if_not_found=True):
wt_snapshots = self._conn_wmi.WT_Snapshot(Description=description)
if wt_snapshots:
return wt_snapshots[0]
elif fail_if_not_found:
err_msg = _('Could not find WT Snapshot: %s')
raise exceptions.ISCSITargetException(err_msg % description)
def _get_wt_idmethod(self, initiator, target_name):
wt_idmethod = self._conn_wmi.WT_IDMethod(HostName=target_name,
Value=initiator)
if wt_idmethod:
return wt_idmethod[0]
@staticmethod
def _wmi_obj_set_attr(wmi_obj, key, value):
# Due to a bug in the python WMI module, some wmi object attributes
# cannot be modified. This method is used as a workaround.
wmi_obj.wmi_property(key).set(value)
def create_iscsi_target(self, target_name, fail_if_exists=False):
"""Creates ISCSI target."""
try:
self._conn_wmi.WT_Host.NewHost(HostName=target_name)
except wmi.x_wmi as wmi_exc:
err_code = self._win32utils.get_com_err_code(wmi_exc.com_error)
target_exists = err_code == self._ERR_FILE_EXISTS
if not target_exists or fail_if_exists:
err_msg = _('Failed to create iSCSI target: %s.')
raise exceptions.ISCSITargetWMIException(err_msg % target_name,
wmi_exc=wmi_exc)
else:
LOG.info(_LI('The iSCSI target %s already exists.'),
target_name)
def delete_iscsi_target(self, target_name):
"""Removes ISCSI target."""
try:
wt_host = self._get_wt_host(target_name, fail_if_not_found=False)
if not wt_host:
LOG.debug('Skipping deleting target %s as it does not '
'exist.', target_name)
return
wt_host.RemoveAllWTDisks()
wt_host.Delete_()
except wmi.x_wmi as wmi_exc:
err_msg = _("Failed to delete ISCSI target %s")
raise exceptions.ISCSITargetWMIException(err_msg % target_name,
wmi_exc=wmi_exc)
def iscsi_target_exists(self, target_name):
wt_host = self._get_wt_host(target_name, fail_if_not_found=False)
return wt_host is not None
def get_target_information(self, target_name):
wt_host = self._get_wt_host(target_name)
info = {}
info['target_iqn'] = wt_host.TargetIQN
info['enabled'] = wt_host.Enabled
info['connected'] = bool(wt_host.Status)
# Note(lpetrut): Cinder uses only one-way CHAP authentication.
if wt_host.EnableCHAP:
info['auth_method'] = 'CHAP'
info['auth_username'] = wt_host.CHAPUserName
info['auth_password'] = wt_host.CHAPSecret
return info
def set_chap_credentials(self, target_name, chap_username, chap_password):
try:
wt_host = self._get_wt_host(target_name)
self._wmi_obj_set_attr(wt_host, 'EnableCHAP', True)
self._wmi_obj_set_attr(wt_host, 'CHAPUserName', chap_username)
self._wmi_obj_set_attr(wt_host, 'CHAPSecret', chap_password)
wt_host.put()
except wmi.x_wmi as wmi_exc:
err_msg = _('Failed to set CHAP credentials on target %s.')
raise exceptions.ISCSITargetWMIException(err_msg % target_name,
wmi_exc=wmi_exc)
def associate_initiator_with_iscsi_target(self, initiator,
target_name,
id_method=ID_METHOD_IQN):
wt_idmethod = self._get_wt_idmethod(initiator, target_name)
if wt_idmethod:
return
try:
wt_idmethod = self._conn_wmi.WT_IDMethod.new()
wt_idmethod.HostName = target_name
wt_idmethod.Method = id_method
wt_idmethod.Value = initiator
wt_idmethod.put()
except wmi.x_wmi as wmi_exc:
err_msg = _('Could not associate initiator %(initiator)s to '
'iSCSI target: %(target_name)s.')
raise exceptions.ISCSITargetWMIException(
err_msg % dict(initiator=initiator,
target_name=target_name),
wmi_exc=wmi_exc)
def deassociate_initiator(self, initiator, target_name):
try:
wt_idmethod = self._get_wt_idmethod(initiator, target_name)
if wt_idmethod:
wt_idmethod.Delete_()
except wmi.x_wmi as wmi_exc:
err_msg = _('Could not deassociate initiator %(initiator)s from '
'iSCSI target: %(target_name)s.')
raise exceptions.ISCSITargetWMIException(
err_msg % dict(initiator=initiator,
target_name=target_name),
wmi_exc=wmi_exc)
def create_wt_disk(self, vhd_path, wtd_name, size_mb=None):
try:
self._conn_wmi.WT_Disk.NewWTDisk(DevicePath=vhd_path,
Description=wtd_name,
SizeInMB=size_mb)
except wmi.x_wmi as wmi_exc:
err_msg = _('Failed to create WT Disk. '
'VHD path: %(vhd_path)s '
'WT disk name: %(wtd_name)s')
raise exceptions.ISCSITargetWMIException(
err_msg % dict(vhd_path=vhd_path,
wtd_name=wtd_name),
wmi_exc=wmi_exc)
def import_wt_disk(self, vhd_path, wtd_name):
"""Import a vhd/x image to be used by Windows iSCSI targets."""
try:
self._conn_wmi.WT_Disk.ImportWTDisk(DevicePath=vhd_path,
Description=wtd_name)
except wmi.x_wmi as wmi_exc:
err_msg = _("Failed to import WT disk: %s.")
raise exceptions.ISCSITargetWMIException(err_msg % vhd_path,
wmi_exc=wmi_exc)
def change_wt_disk_status(self, wtd_name, enabled):
try:
wt_disk = self._get_wt_disk(wtd_name)
wt_disk.Enabled = enabled
wt_disk.put()
except wmi.x_wmi as wmi_exc:
err_msg = _('Could not change disk status. WT Disk name: %s')
raise exceptions.ISCSITargetWMIException(err_msg % wtd_name,
wmi_exc=wmi_exc)
def remove_wt_disk(self, wtd_name):
try:
wt_disk = self._get_wt_disk(wtd_name, fail_if_not_found=False)
if wt_disk:
wt_disk.Delete_()
except wmi.x_wmi as wmi_exc:
err_msg = _("Failed to remove WT disk: %s.")
raise exceptions.ISCSITargetWMIException(err_msg % wtd_name,
wmi_exc=wmi_exc)
def extend_wt_disk(self, wtd_name, additional_mb):
try:
wt_disk = self._get_wt_disk(wtd_name)
wt_disk.Extend(additional_mb)
except wmi.x_wmi as wmi_exc:
err_msg = _('Could not extend WT Disk %(wtd_name)s '
'with additional %(additional_mb)s MB.')
raise exceptions.ISCSITargetWMIException(
err_msg % dict(wtd_name=wtd_name,
additional_mb=additional_mb),
wmi_exc=wmi_exc)
def add_disk_to_target(self, wtd_name, target_name):
"""Adds the disk to the target."""
try:
wt_disk = self._get_wt_disk(wtd_name)
wt_host = self._get_wt_host(target_name)
wt_host.AddWTDisk(wt_disk.WTD)
except wmi.x_wmi as wmi_exc:
err_msg = _('Could not add WTD Disk %(wtd_name)s to '
'iSCSI target %(target_name)s.')
raise exceptions.ISCSITargetWMIException(
err_msg % dict(wtd_name=wtd_name,
target_name=target_name),
wmi_exc=wmi_exc)
def create_snapshot(self, wtd_name, snapshot_name):
"""Driver entry point for creating a snapshot."""
try:
wt_disk = self._get_wt_disk(wtd_name)
snap_id = self._conn_wmi.WT_Snapshot.Create(WTD=wt_disk.WTD)[0]
wt_snap = self._conn_wmi.WT_Snapshot(Id=snap_id)[0]
wt_snap.Description = snapshot_name
wt_snap.put()
except wmi.x_wmi as wmi_exc:
err_msg = _('Failed to create snapshot. '
'WT Disk name: %(wtd_name)s '
'Snapshot name: %(snapshot_name)s')
raise exceptions.ISCSITargetWMIException(
err_msg % dict(wtd_name=wtd_name,
snapshot_name=snapshot_name),
wmi_exc=wmi_exc)
def export_snapshot(self, snapshot_name, dest_path):
"""Driver entry point for exporting snapshots as volumes."""
try:
wt_snap = self._get_wt_snapshot(snapshot_name)
wt_disk_id = wt_snap.Export()[0]
# This export is a read-only shadow copy, needing to be copied
# to another disk.
wt_disk = self._conn_wmi.WT_Disk(WTD=wt_disk_id)[0]
wt_disk.Description = '%s-%s-temp' % (snapshot_name, wt_disk_id)
wt_disk.put()
src_path = wt_disk.DevicePath
self._pathutils.copy(src_path, dest_path)
wt_disk.Delete_()
except wmi.x_wmi as wmi_exc:
err_msg = _('Failed to export snapshot %(snapshot_name)s '
'to %(dest_path)s.')
raise exceptions.ISCSITargetWMIException(
err_msg % dict(snapshot_name=snapshot_name,
dest_path=dest_path),
wmi_exc=wmi_exc)
def delete_snapshot(self, snapshot_name):
"""Driver entry point for deleting a snapshot."""
try:
wt_snapshot = self._get_wt_snapshot(snapshot_name,
fail_if_not_found=False)
if wt_snapshot:
wt_snapshot.Delete_()
except wmi.x_wmi as wmi_exc:
err_msg = _('Failed delete snapshot %s.')
raise exceptions.ISCSITargetWMIException(err_msg % snapshot_name,
wmi_exc=wmi_exc)