298 lines
13 KiB
Python
298 lines
13 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2013 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 Windows Storage Server 2012 volume related operations.
|
|
"""
|
|
|
|
import os
|
|
|
|
from cinder import exception
|
|
from cinder.openstack.common import log as logging
|
|
|
|
# Check needed for unit testing on Unix
|
|
if os.name == 'nt':
|
|
import wmi
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class WindowsUtils(object):
|
|
"""Executes volume driver commands on Windows Storage server."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
# Set the flags
|
|
self._conn_wmi = wmi.WMI(moniker='//./root/wmi')
|
|
self._conn_cimv2 = wmi.WMI(moniker='//./root/cimv2')
|
|
|
|
def check_for_setup_error(self):
|
|
"""Check that the driver is working and can communicate.
|
|
Invokes the portal and checks that is listening ISCSI traffic.
|
|
"""
|
|
try:
|
|
wt_portal = self._conn_wmi.WT_Portal()[0]
|
|
listen = wt_portal.Listen
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_('check_for_setup_error: the state of the WT Portal '
|
|
'could not be verified. WMI exception: %s'))
|
|
LOG.error(err_msg % exc)
|
|
raise exception.VolumeBackendAPIException(data=err_msg % exc)
|
|
|
|
if not listen:
|
|
err_msg = (_('check_for_setup_error: there is no ISCSI traffic '
|
|
'listening.'))
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def get_host_information(self, volume, target_name):
|
|
"""Getting the portal and port information."""
|
|
try:
|
|
wt_portal = self._conn_wmi.WT_Portal()[0]
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_('get_host_information: the state of the WT Portal '
|
|
'could not be verified. WMI exception: %s'))
|
|
LOG.error(err_msg % exc)
|
|
raise exception.VolumeBackendAPIException(data=err_msg % exc)
|
|
(address, port) = (wt_portal.Address, wt_portal.Port)
|
|
# Getting the host information
|
|
try:
|
|
hosts = self._conn_wmi.WT_Host(Hostname=target_name)
|
|
host = hosts[0]
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_('get_host_information: the ISCSI target information '
|
|
'could not be retrieved. WMI exception: %s'))
|
|
LOG.error(err_msg % exc)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
properties = {}
|
|
properties['target_discovered'] = False
|
|
properties['target_portal'] = '%s:%s' % (address, port)
|
|
properties['target_iqn'] = host.TargetIQN
|
|
properties['target_lun'] = 0
|
|
properties['volume_id'] = volume['id']
|
|
|
|
auth = volume['provider_auth']
|
|
if auth:
|
|
(auth_method, auth_username, auth_secret) = auth.split()
|
|
|
|
properties['auth_method'] = auth_method
|
|
properties['auth_username'] = auth_username
|
|
properties['auth_password'] = auth_secret
|
|
|
|
def associate_initiator_with_iscsi_target(self, initiator_name,
|
|
target_name):
|
|
"""Sets information used by the iSCSI target entry."""
|
|
try:
|
|
cl = self._conn_wmi.__getattr__("WT_IDMethod")
|
|
wt_idmethod = cl.new()
|
|
wt_idmethod.HostName = target_name
|
|
# Identification method is IQN
|
|
wt_idmethod.Method = 4
|
|
wt_idmethod.Value = initiator_name
|
|
wt_idmethod.put()
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_('associate_initiator_with_iscsi_target: an '
|
|
'association between initiator: %(init)s and '
|
|
'target name: %(target)s could not be established. '
|
|
'WMI exception: %(wmi_exc)s') %
|
|
{'init': initiator_name, 'target': target_name,
|
|
'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def delete_iscsi_target(self, initiator_name, target_name):
|
|
"""Removes iSCSI targets to hosts."""
|
|
|
|
try:
|
|
wt_idmethod = self._conn_wmi.WT_IDMethod(HostName=target_name,
|
|
Method=4,
|
|
Value=initiator_name)[0]
|
|
wt_idmethod.Delete_()
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'delete_iscsi_target: error when deleting the iscsi target '
|
|
'associated with target name: %(target)s . '
|
|
'WMI exception: %(wmi_exc)s') % {'target': target_name,
|
|
'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def create_volume(self, vhd_path, vol_name, vol_size):
|
|
"""Creates a volume"""
|
|
try:
|
|
cl = self._conn_wmi.__getattr__("WT_Disk")
|
|
cl.NewWTDisk(DevicePath=vhd_path,
|
|
Description=vol_name,
|
|
SizeInMB=vol_size * 1024)
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'create_volume: error when creating the volume name: '
|
|
'%(vol_name)s . WMI exception: '
|
|
'%(wmi_exc)s') % {'vol_name': vol_name, 'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def delete_volume(self, vol_name, vhd_path):
|
|
"""Driver entry point for destroying existing volumes."""
|
|
try:
|
|
wt_disk = self._conn_wmi.WT_Disk(Description=vol_name)[0]
|
|
wt_disk.Delete_()
|
|
vhdfiles = self._conn_cimv2.query(
|
|
"Select * from CIM_DataFile where Name = '" +
|
|
vhd_path + "'")
|
|
if len(vhdfiles) > 0:
|
|
vhdfiles[0].Delete()
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'delete_volume: error when deleting the volume name: '
|
|
'%(vol_name)s . WMI exception: '
|
|
'%(wmi_exc)s') % {'vol_name': vol_name, 'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def create_snapshot(self, vol_name, snapshot_name):
|
|
"""Driver entry point for creating a snapshot."""
|
|
try:
|
|
wt_disk = self._conn_wmi.WT_Disk(Description=vol_name)[0]
|
|
# API Calls gets Generic Failure
|
|
cl = self._conn_wmi.__getattr__("WT_Snapshot")
|
|
disk_id = wt_disk.WTD
|
|
out = cl.Create(WTD=disk_id)
|
|
# Setting description since it used as a KEY
|
|
wt_snapshot_created = self._conn_wmi.WT_Snapshot(Id=out[0])[0]
|
|
wt_snapshot_created.Description = snapshot_name
|
|
wt_snapshot_created.put()
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'create_snapshot: error when creating the snapshot name: '
|
|
'%(vol_name)s . WMI exception: '
|
|
'%(wmi_exc)s') % {'vol_name': snapshot_name, 'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def create_volume_from_snapshot(self, vol_name, snap_name):
|
|
"""Driver entry point for exporting snapshots as volumes."""
|
|
try:
|
|
wt_snapshot = self._conn_wmi.WT_Snapshot(Description=snap_name)[0]
|
|
disk_id = wt_snapshot.Export()[0]
|
|
wt_disk = self._conn_wmi.WT_Disk(WTD=disk_id)[0]
|
|
wt_disk.Description = vol_name
|
|
wt_disk.put()
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'create_volume_from_snapshot: error when creating the volume '
|
|
'name: %(vol_name)s from snapshot name: %(snap_name)s. '
|
|
'WMI exception: %(wmi_exc)s') % {'vol_name': vol_name,
|
|
'snap_name': snap_name,
|
|
'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def delete_snapshot(self, snap_name):
|
|
"""Driver entry point for deleting a snapshot."""
|
|
try:
|
|
wt_snapshot = self._conn_wmi.WT_Snapshot(Description=snap_name)[0]
|
|
wt_snapshot.Delete_()
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'delete_snapshot: error when deleting the snapshot name: '
|
|
'%(snap_name)s . WMI exception: '
|
|
'%(wmi_exc)s') % {'snap_name': snap_name, 'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def create_iscsi_target(self, target_name, ensure):
|
|
"""Creates ISCSI target."""
|
|
try:
|
|
cl = self._conn_wmi.__getattr__("WT_Host")
|
|
cl.NewHost(HostName=target_name)
|
|
except wmi.x_wmi as exc:
|
|
excep_info = exc.com_error.excepinfo[2]
|
|
if not ensure or excep_info.find(u'The file exists') == -1:
|
|
err_msg = (_(
|
|
'create_iscsi_target: error when creating iscsi target: '
|
|
'%(tar_name)s . WMI exception: '
|
|
'%(wmi_exc)s') % {'tar_name': target_name, 'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
else:
|
|
LOG.info(_('Ignored target creation error "%s"'
|
|
' while ensuring export'), exc)
|
|
|
|
def remove_iscsi_target(self, target_name):
|
|
"""Removes ISCSI target."""
|
|
try:
|
|
wt_host = self._conn_wmi.WT_Host(HostName=target_name)[0]
|
|
wt_host.RemoveAllWTDisks()
|
|
wt_host.Delete_()
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'remove_iscsi_target: error when deleting iscsi target: '
|
|
'%(tar_name)s . WMI exception: '
|
|
'%(wmi_exc)s') % {'tar_name': target_name, 'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def add_disk_to_target(self, vol_name, target_name):
|
|
"""Adds the disk to the target"""
|
|
try:
|
|
q = self._conn_wmi.WT_Disk(Description=vol_name)
|
|
wt_disk = q[0]
|
|
wt_host = self._conn_wmi.WT_Host(HostName=target_name)[0]
|
|
wt_host.AddWTDisk(wt_disk.WTD)
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'add_disk_to_target: error adding disk associated to volume : '
|
|
'%(vol_name)s to the target name: %(tar_name)s '
|
|
'. WMI exception: %(wmi_exc)s') % {'tar_name': target_name,
|
|
'vol_name': vol_name,
|
|
'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def copy_vhd_disk(self, source_path, destination_path):
|
|
"""Copy the vhd disk from source path to destination path."""
|
|
try:
|
|
vhdfiles = self._conn_cimv2.query(
|
|
"Select * from CIM_DataFile where Name = '" +
|
|
source_path + "'")
|
|
if len(vhdfiles) > 0:
|
|
vhdfiles[0].Copy(destination_path)
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'copy_vhd_disk: error when copying disk from source path : '
|
|
'%(src_path)s to destination path: %(dest_path)s '
|
|
'. WMI exception: '
|
|
'%(wmi_exc)s') % {'src_path': source_path,
|
|
'dest_path': destination_path,
|
|
'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|
|
|
|
def extend(self, vol_name, additional_size):
|
|
"""Extend an existing volume."""
|
|
try:
|
|
q = self._conn_wmi.WT_Disk(Description=vol_name)
|
|
wt_disk = q[0]
|
|
wt_disk.Extend(additional_size)
|
|
except wmi.x_wmi as exc:
|
|
err_msg = (_(
|
|
'extend: error when extending the volumne: %(vol_name)s '
|
|
'.WMI exception: %(wmi_exc)s') % {'vol_name': vol_name,
|
|
'wmi_exc': exc})
|
|
LOG.error(err_msg)
|
|
raise exception.VolumeBackendAPIException(data=err_msg)
|