Add support for Havana missing features in Windows driver

The following features were added:

-copy_volume_to_image (Havana)
-copy_image_to_volume (Havana)
-get_volume_stats (Havana)
-extend_volume (Icehouse)

The test mocking based on binary pickled files were replaced by mox.

Change-Id: I92c681280d6f7b85bcba7a5e3513f2bdb6c13f1d
Implements: blueprint windows-storage-driver-extended
This commit is contained in:
Pedro Navarro Perez 2013-08-26 13:09:41 +02:00
parent b67caf3256
commit 106bf560e2
33 changed files with 896 additions and 897 deletions

View File

@ -183,9 +183,10 @@ class QemuImgInfo(object):
def qemu_img_info(path):
"""Return a object containing the parsed output from qemu-img info."""
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
'qemu-img', 'info', path,
run_as_root=True)
cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path)
if os.name == 'nt':
cmd = cmd[3:]
out, err = utils.execute(*cmd, run_as_root=True)
return QemuImgInfo(out)
@ -232,9 +233,23 @@ def fetch_verify_image(context, image_service, image_id, dest,
{'fmt': fmt, 'backing_file': backing_file}))
def fetch_to_vhd(context, image_service,
image_id, dest,
user_id=None, project_id=None):
fetch_to_volume_format(context, image_service, image_id, dest, 'vpc',
user_id, project_id)
def fetch_to_raw(context, image_service,
image_id, dest,
user_id=None, project_id=None):
fetch_to_volume_format(context, image_service, image_id, dest, 'raw',
user_id, project_id)
def fetch_to_volume_format(context, image_service,
image_id, dest, volume_format,
user_id=None, project_id=None):
if (CONF.image_conversion_dir and not
os.path.exists(CONF.image_conversion_dir)):
os.makedirs(CONF.image_conversion_dir)
@ -273,22 +288,29 @@ def fetch_to_raw(context, image_service,
# check via 'qemu-img info' that what we copied was in fact a raw
# image and not a different format with a backing file, which may be
# malicious.
LOG.debug("%s was %s, converting to raw" % (image_id, fmt))
convert_image(tmp, dest, 'raw')
LOG.debug("%s was %s, converting to %s " % (image_id, fmt,
volume_format))
convert_image(tmp, dest, volume_format)
data = qemu_img_info(dest)
if data.file_format != "raw":
if data.file_format != volume_format:
raise exception.ImageUnacceptable(
image_id=image_id,
reason=_("Converted to raw, but format is now %s") %
data.file_format)
reason=_("Converted to %(vol_format)s, but format is "
"now %(file_format)s") % {'vol_format': volume_format,
'file_format': data.
file_format})
def upload_volume(context, image_service, image_meta, volume_path):
def upload_volume(context, image_service, image_meta, volume_path,
volume_format='raw'):
image_id = image_meta['id']
if (image_meta['disk_format'] == 'raw'):
LOG.debug("%s was raw, no need to convert to %s" %
(image_id, image_meta['disk_format']))
if (image_meta['disk_format'] == volume_format):
LOG.debug("%s was %s, no need to convert to %s" %
(image_id, volume_format, image_meta['disk_format']))
if os.name == 'nt':
with fileutils.file_open(volume_path) as image_file:
image_service.update(context, image_id, {}, image_file)
with utils.temporary_chown(volume_path):
with fileutils.file_open(volume_path) as image_file:
image_service.update(context, image_id, {}, image_file)
@ -301,8 +323,8 @@ def upload_volume(context, image_service, image_meta, volume_path):
fd, tmp = tempfile.mkstemp(dir=CONF.image_conversion_dir)
os.close(fd)
with fileutils.remove_path_on_error(tmp):
LOG.debug("%s was raw, converting to %s" %
(image_id, image_meta['disk_format']))
LOG.debug("%s was %s, converting to %s" %
(image_id, volume_format, image_meta['disk_format']))
convert_image(volume_path, tmp, image_meta['disk_format'])
data = qemu_img_info(tmp)

View File

@ -32,7 +32,7 @@ LEFTHAND_MODULE = "cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver"
NFS_MODULE = "cinder.volume.drivers.nfs.NfsDriver"
SOLIDFIRE_MODULE = "cinder.volume.drivers.solidfire.SolidFireDriver"
STORWIZE_SVC_MODULE = "cinder.volume.drivers.storwize_svc.StorwizeSVCDriver"
WINDOWS_MODULE = "cinder.volume.drivers.windows.WindowsDriver"
WINDOWS_MODULE = "cinder.volume.drivers.windows.windows.WindowsDriver"
XIV_DS8K_MODULE = "cinder.volume.drivers.xiv_ds8k.XIVDS8KDriver"
ZADARA_MODULE = "cinder.volume.drivers.zadara.ZadaraVPSAISCSIDriver"
NETAPP_MODULE = "cinder.volume.drivers.netapp.common.Deprecated"

View File

@ -20,205 +20,365 @@ Unit tests for Windows Server 2012 OpenStack Cinder volume driver
"""
import sys
import os
from oslo.config import cfg
from cinder.tests.windows import basetestcase
import mox as mox_lib
from mox import IgnoreArg
from mox import stubout
from cinder import test
from cinder.image import image_utils
from cinder.tests.windows import db_fakes
from cinder.tests.windows import windowsutils
from cinder.volume.drivers import windows
from cinder.volume import configuration as conf
from cinder.volume.drivers.windows import windows
from cinder.volume.drivers.windows import windows_utils
CONF = cfg.CONF
class TestWindowsDriver(basetestcase.BaseTestCase):
class TestWindowsDriver(test.TestCase):
def __init__(self, method):
super(TestWindowsDriver, self).__init__(method)
def setUp(self):
super(TestWindowsDriver, self).setUp()
self._mox = mox_lib.Mox()
self.stubs = stubout.StubOutForTesting()
self.flags(
windows_iscsi_lun_path='C:\iSCSIVirtualDisks',
)
self._volume_data = None
self._volume_data_2 = None
self._snapshot_data = None
self._connector_data = None
self._volume_id = '10958016-e196-42e3-9e7f-5d8927ae3099'
self._volume_id_2 = '20958016-e196-42e3-9e7f-5d8927ae3098'
self._snapshot_id = '30958016-e196-42e3-9e7f-5d8927ae3097'
self._iqn = "iqn.1991-05.com.microsoft:dell1160dsy"
self._setup_stubs()
configuration = conf.Configuration(None)
configuration.append_config_values(windows.windows_opts)
self._drv = windows.WindowsDriver()
self._drv.do_setup({})
self._wutils = windowsutils.WindowsUtils()
self._driver = windows.WindowsDriver(configuration=configuration)
self._driver.do_setup({})
def tearDown(self):
self._mox.UnsetStubs()
self.stubs.UnsetAll()
super(TestWindowsDriver, self).tearDown()
def _setup_stubs(self):
# Modules to mock
modules_to_mock = [
'wmi',
'os',
'subprocess',
'multiprocessing'
]
def fake_wutils__init__(self):
pass
windows_utils.WindowsUtils.__init__ = fake_wutils__init__
modules_to_test = [
windows,
windowsutils,
sys.modules[__name__]
]
self._inject_mocks_in_modules(modules_to_mock, modules_to_test)
def tearDown(self):
try:
if (self._volume_data_2 and
self._wutils.volume_exists(self._volume_data_2['name'])):
self._wutils.delete_volume(self._volume_data_2['name'])
if (self._volume_data and
self._wutils.volume_exists(
self._volume_data['name'])):
self._wutils.delete_volume(self._volume_data['name'])
if (self._snapshot_data and
self._wutils.snapshot_exists(
self._snapshot_data['name'])):
self._wutils.delete_snapshot(self._snapshot_data['name'])
if (self._connector_data and
self._wutils.initiator_id_exists(
"%s%s" % (CONF.iscsi_target_prefix,
self._volume_data['name']),
self._connector_data['initiator'])):
target_name = "%s%s" % (CONF.iscsi_target_prefix,
self._volume_data['name'])
initiator_name = self._connector_data['initiator']
self._wutils.delete_initiator_id(target_name, initiator_name)
if (self._volume_data and
self._wutils.export_exists("%s%s" %
(CONF.iscsi_target_prefix,
self._volume_data['name']))):
self._wutils.delete_export(
"%s%s" % (CONF.iscsi_target_prefix,
self._volume_data['name']))
finally:
super(TestWindowsDriver, self).tearDown()
def fake_local_path(self, volume):
return os.path.join(CONF.windows_iscsi_lun_path,
str(volume['name']) + ".vhd")
def test_check_for_setup_errors(self):
self._drv.check_for_setup_error()
mox = self._mox
drv = self._driver
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'check_for_setup_error')
windows_utils.WindowsUtils.check_for_setup_error()
mox.ReplayAll()
drv.check_for_setup_error()
mox.VerifyAll()
def test_create_volume(self):
self._create_volume()
mox = self._mox
drv = self._driver
vol = db_fakes.get_fake_volume_info()
wt_disks = self._wutils.find_vhd_by_name(self._volume_data['name'])
self.assertEquals(len(wt_disks), 1)
self.stubs.Set(drv, 'local_path', self.fake_local_path)
def _create_volume(self):
self._volume_data = db_fakes.get_fake_volume_info(self._volume_id)
self._drv.create_volume(self._volume_data)
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'create_volume')
windows_utils.WindowsUtils.create_volume(self.fake_local_path(vol),
vol['name'], vol['size'])
mox.ReplayAll()
drv.create_volume(vol)
mox.VerifyAll()
def test_delete_volume(self):
self._create_volume()
"""delete_volume simple test case."""
mox = self._mox
drv = self._driver
self._drv.delete_volume(self._volume_data)
vol = db_fakes.get_fake_volume_info()
wt_disks = self._wutils.find_vhd_by_name(self._volume_data['name'])
self.assertEquals(len(wt_disks), 0)
mox.StubOutWithMock(drv, 'local_path')
drv.local_path(vol).AndReturn(self.fake_local_path(vol))
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'delete_volume')
windows_utils.WindowsUtils.delete_volume(vol['name'],
self.fake_local_path(vol))
mox.ReplayAll()
drv.delete_volume(vol)
mox.VerifyAll()
def test_create_snapshot(self):
#Create a volume
self._create_volume()
mox = self._mox
drv = self._driver
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'create_snapshot')
volume = db_fakes.get_fake_volume_info()
snapshot = db_fakes.get_fake_snapshot_info()
wt_disks = self._wutils.find_vhd_by_name(self._volume_data['name'])
self.assertEquals(len(wt_disks), 1)
#Create a snapshot from the previous volume
self._create_snapshot()
self.stubs.Set(drv, 'local_path', self.fake_local_path(snapshot))
snapshot_name = self._snapshot_data['name']
wt_snapshots = self._wutils.find_snapshot_by_name(snapshot_name)
self.assertEquals(len(wt_snapshots), 1)
windows_utils.WindowsUtils.create_snapshot(volume['name'],
snapshot['name'])
def _create_snapshot(self):
volume_name = self._volume_data['name']
snapshot_id = self._snapshot_id
self._snapshot_data = db_fakes.get_fake_snapshot_info(volume_name,
snapshot_id)
self._drv.create_snapshot(self._snapshot_data)
mox.ReplayAll()
drv.create_snapshot(snapshot)
mox.VerifyAll()
def test_create_volume_from_snapshot(self):
#Create a volume
self._create_volume()
#Create a snapshot from the previous volume
self._create_snapshot()
mox = self._mox
drv = self._driver
self._volume_data_2 = db_fakes.get_fake_volume_info(self._volume_id_2)
snapshot = db_fakes.get_fake_snapshot_info()
volume = db_fakes.get_fake_volume_info()
self._drv.create_volume_from_snapshot(self._volume_data_2,
self._snapshot_data)
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'create_volume_from_snapshot')
windows_utils.WindowsUtils.\
create_volume_from_snapshot(volume['name'], snapshot['name'])
wt_disks = self._wutils.find_vhd_by_name(self._volume_data_2['name'])
self.assertEquals(len(wt_disks), 1)
mox.ReplayAll()
drv.create_volume_from_snapshot(volume, snapshot)
mox.VerifyAll()
def test_delete_snapshot(self):
#Create a volume
self._create_volume()
#Create a snapshot from the previous volume
self._create_snapshot()
mox = self._mox
drv = self._driver
self._drv.delete_snapshot(self._snapshot_data)
snapshot = db_fakes.get_fake_snapshot_info()
snapshot_name = self._snapshot_data['name']
wt_snapshots = self._wutils.find_snapshot_by_name(snapshot_name)
self.assertEquals(len(wt_snapshots), 0)
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'delete_snapshot')
windows_utils.WindowsUtils.delete_snapshot(snapshot['name'])
mox.ReplayAll()
drv.delete_snapshot(snapshot)
mox.VerifyAll()
def test_create_export(self):
#Create a volume
self._create_volume()
mox = self._mox
drv = self._driver
retval = self._drv.create_export({}, self._volume_data)
volume = db_fakes.get_fake_volume_info()
volume_name = self._volume_data['name']
self.assertEquals(
retval,
{'provider_location': "%s%s" % (CONF.iscsi_target_prefix,
volume_name)})
initiator_name = "%s%s" % (CONF.iscsi_target_prefix, volume['name'])
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'create_iscsi_target')
windows_utils.WindowsUtils.create_iscsi_target(initiator_name,
IgnoreArg())
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'add_disk_to_target')
windows_utils.WindowsUtils.add_disk_to_target(volume['name'],
initiator_name)
mox.ReplayAll()
export_info = drv.create_export(None, volume)
mox.VerifyAll()
self.assertEquals(export_info['provider_location'], initiator_name)
def test_initialize_connection(self):
#Create a volume
self._create_volume()
mox = self._mox
drv = self._driver
self._drv.create_export({}, self._volume_data)
volume = db_fakes.get_fake_volume_info()
initiator_name = "%s%s" % (CONF.iscsi_target_prefix, volume['name'])
self._connector_data = db_fakes.get_fake_connector_info(self._iqn)
connector = db_fakes.get_fake_connector_info()
init_data = self._drv.initialize_connection(self._volume_data,
self._connector_data)
target_name = self._volume_data['provider_location']
initiator_name = self._connector_data['initiator']
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'associate_initiator_with_iscsi_target')
windows_utils.WindowsUtils.associate_initiator_with_iscsi_target(
volume['provider_location'], initiator_name, )
wt_initiator_ids = self._wutils.find_initiator_ids(target_name,
initiator_name)
self.assertEquals(len(wt_initiator_ids), 1)
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'get_host_information')
windows_utils.WindowsUtils.get_host_information(
volume, volume['provider_location'])
properties = init_data['data']
self.assertNotEqual(properties['target_iqn'], None)
mox.ReplayAll()
drv.initialize_connection(volume, connector)
mox.VerifyAll()
def test_terminate_connection(self):
mox = self._mox
drv = self._driver
volume = db_fakes.get_fake_volume_info()
initiator_name = "%s%s" % (CONF.iscsi_target_prefix, volume['name'])
connector = db_fakes.get_fake_connector_info()
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'delete_iscsi_target')
windows_utils.WindowsUtils.delete_iscsi_target(
initiator_name, volume['provider_location'])
mox.ReplayAll()
drv.terminate_connection(volume, connector)
mox.VerifyAll()
def test_ensure_export(self):
#Create a volume
self._create_volume()
mox = self._mox
drv = self._driver
self._drv.ensure_export({}, self._volume_data)
volume = db_fakes.get_fake_volume_info()
initiator_name = "%s%s" % (CONF.iscsi_target_prefix, volume['name'])
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'create_iscsi_target')
windows_utils.WindowsUtils.create_iscsi_target(initiator_name, True)
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'add_disk_to_target')
windows_utils.WindowsUtils.add_disk_to_target(volume['name'],
initiator_name)
mox.ReplayAll()
drv.ensure_export(None, volume)
mox.VerifyAll()
def test_remove_export(self):
#Create a volume
self._create_volume()
mox = self._mox
drv = self._driver
self._drv.create_export({}, self._volume_data)
volume = db_fakes.get_fake_volume_info()
self._drv.remove_export({}, self._volume_data)
target_name = volume['provider_location']
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'remove_iscsi_target')
windows_utils.WindowsUtils.remove_iscsi_target(target_name)
mox.ReplayAll()
drv.remove_export(None, volume)
mox.VerifyAll()
def test_copy_image_to_volume(self):
"""resize_image common case usage."""
mox = self._mox
drv = self._driver
volume = db_fakes.get_fake_volume_info()
self.stubs.Set(drv, 'local_path', self.fake_local_path)
mox.StubOutWithMock(image_utils, 'fetch_to_vhd')
image_utils.fetch_to_vhd(None, None, None,
self.fake_local_path(volume))
mox.ReplayAll()
drv.copy_image_to_volume(None, volume, None, None)
mox.VerifyAll()
def test_copy_volume_to_image(self):
mox = self._mox
drv = self._driver
vol = db_fakes.get_fake_volume_info()
image_meta = db_fakes.get_fake_image_meta()
self.stubs.Set(drv, 'local_path', self.fake_local_path)
mox.StubOutWithMock(image_utils, 'upload_volume')
temp_vhd_path = os.path.join(CONF.image_conversion_dir,
str(image_meta['id']) + ".vhd")
image_utils.upload_volume(None, None, image_meta, temp_vhd_path, 'vpc')
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'copy_vhd_disk')
windows_utils.WindowsUtils.copy_vhd_disk(self.fake_local_path(vol),
temp_vhd_path)
mox.ReplayAll()
drv.copy_volume_to_image(None, vol, None, image_meta)
mox.VerifyAll()
def test_create_cloned_volume(self):
mox = self._mox
drv = self._driver
volume = db_fakes.get_fake_volume_info()
volume_cloned = db_fakes.get_fake_volume_info_cloned()
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'create_volume')
windows_utils.WindowsUtils.create_volume(IgnoreArg(), IgnoreArg(),
IgnoreArg())
self._mox.StubOutWithMock(windows_utils.WindowsUtils,
'copy_vhd_disk')
windows_utils.WindowsUtils.copy_vhd_disk(self.fake_local_path(
volume_cloned), self.fake_local_path(volume))
mox.ReplayAll()
drv.create_cloned_volume(volume, volume_cloned)
mox.VerifyAll()
def test_extend_volume(self):
mox = self._mox
drv = self._driver
volume = db_fakes.get_fake_volume_info()
TEST_VOLUME_ADDITIONAL_SIZE_MB = 1024
TEST_VOLUME_ADDITIONAL_SIZE_GB = 1
self._mox.StubOutWithMock(windows_utils.WindowsUtils, 'extend')
windows_utils.WindowsUtils.extend(volume['name'],
TEST_VOLUME_ADDITIONAL_SIZE_MB)
new_size = volume['size'] + TEST_VOLUME_ADDITIONAL_SIZE_GB
mox.ReplayAll()
drv.extend_volume(volume, new_size)
mox.VerifyAll()

View File

@ -1,103 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Cloudbase Solutions Srl
#
# 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.
"""
TestCase for MockProxy based tests and related classes.
"""
import cinder.test
import gzip
import os
import pickle
from cinder.tests.windows import mockproxy
gen_test_mocks_key = 'CINDER_GENERATE_TEST_MOCKS'
class BaseTestCase(cinder.test.TestCase):
"""TestCase for MockProxy based tests."""
def run(self, result=None):
self._currentResult = result
super(BaseTestCase, self).run(result)
def setUp(self):
super(BaseTestCase, self).setUp()
self._mps = {}
def tearDown(self):
super(BaseTestCase, self).tearDown()
# python-subunit will wrap test results with a decorator.
# Need to access the decorated member of results to get the
# actual test result when using python-subunit.
if hasattr(self._currentResult, 'decorated'):
result = self._currentResult.decorated
else:
result = self._currentResult
has_errors = len([test for (test, msgs) in result.errors
if test.id() == self.id()]) > 0
failed = len([test for (test, msgs) in result.failures
if test.id() == self.id()]) > 0
if not has_errors and not failed:
self._save_mock_proxies()
def _save_mock(self, name, mock):
path = self._get_stub_file_path(self.id(), name)
pickle.dump(mock, gzip.open(path, 'wb'))
def _get_stub_file_path(self, test_name, mock_name):
# test naming differs between platforms
prefix = 'cinder.tests.'
if test_name.startswith(prefix):
test_name = test_name[len(prefix):]
file_name = '{0}_{1}.p.gz'.format(test_name, mock_name)
return os.path.join(os.path.dirname(mockproxy.__file__),
"stubs", file_name)
def _load_mock(self, name):
path = self._get_stub_file_path(self.id(), name)
if os.path.exists(path):
return pickle.load(gzip.open(path, 'rb'))
else:
return None
def _load_mock_or_create_proxy(self, module_name):
m = None
if (not gen_test_mocks_key in os.environ or
os.environ[gen_test_mocks_key].lower()
not in ['true', 'yes', '1']):
m = self._load_mock(module_name)
else:
module = __import__(module_name)
m = mockproxy.MockProxy(module)
self._mps[module_name] = m
return m
def _inject_mocks_in_modules(self, objects_to_mock, modules_to_test):
for module_name in objects_to_mock:
mp = self._load_mock_or_create_proxy(module_name)
for mt in modules_to_test:
module_local_name = module_name.split('.')[-1]
setattr(mt, module_local_name, mp)
def _save_mock_proxies(self):
for name, mp in self._mps.items():
m = mp.get_mock()
if m.has_values():
self._save_mock(name, m)

View File

@ -19,18 +19,32 @@ Stubouts, mocks and fixtures for windows volume test suite
"""
def get_fake_volume_info(name):
return {'name': name,
def get_fake_volume_info():
return {'name': 'volume_name',
'size': 1,
'provider_location': 'iqn.2010-10.org.openstack:' + name,
'provider_location': 'iqn.2010-10.org.openstack:' + 'volume_name',
'id': 1,
'provider_auth': None}
def get_fake_snapshot_info(volume_name, snapshot_name):
return {'name': snapshot_name,
'volume_name': volume_name, }
def get_fake_volume_info_cloned():
return {'name': 'volume_name_cloned',
'size': 1,
'provider_location': 'iqn.2010-10.org.openstack:' +
'volume_name_cloned',
'id': 1,
'provider_auth': None}
def get_fake_connector_info(initiator):
return {'initiator': initiator, }
def get_fake_image_meta():
return {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'
}
def get_fake_snapshot_info():
return {'name': 'snapshot_name',
'volume_name': 'volume_name', }
def get_fake_connector_info():
return {'initiator': 'iqn.2010-10.org.openstack:' + 'volume_name', }

View File

@ -1,241 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Cloudbase Solutions Srl
#
# 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.
#
"""
Classes for dynamic generation of mock objects.
"""
import inspect
def serialize_obj(obj):
if isinstance(obj, float):
val = str(round(obj, 10))
elif isinstance(obj, dict):
d = {}
for k1, v1 in obj.items():
d[k1] = serialize_obj(v1)
val = str(d)
elif isinstance(obj, list):
l1 = []
for i1 in obj:
l1.append(serialize_obj(i1))
val = str(l1)
elif isinstance(obj, tuple):
l1 = ()
for i1 in obj:
l1 = l1 + (serialize_obj(i1),)
val = str(l1)
else:
val = str(obj)
return val
def serialize_args(*args, **kwargs):
"""Workaround for float string conversion issues in Python 2.6."""
return serialize_obj((args, kwargs))
class Mock(object):
def _get_next_value(self, name):
c = self._access_count.get(name)
if c is None:
c = 0
else:
c = c + 1
self._access_count[name] = c
return self._values[name][c]
def _get_next_ret_value(self, name, params):
d = self._access_count.get(name)
if d is None:
d = {}
self._access_count[name] = d
c = d.get(params)
if c is None:
c = 0
else:
c = c + 1
d[params] = c
return self._values[name][params][c]
def __init__(self, values):
self._values = values
self._access_count = {}
def has_values(self):
return len(self._values) > 0
def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'):
return object.__getattribute__(self, name)
else:
if isinstance(self._values[name], dict):
def newfunc(*args, **kwargs):
params = serialize_args(args, kwargs)
return self._get_next_ret_value(name, params)
return newfunc
else:
return self._get_next_value(name)
def __str__(self):
return self._get_next_value('__str__')
def __iter__(self):
return getattr(self._get_next_value('__iter__'), '__iter__')()
def __len__(self):
return self._get_next_value('__len__')
def __getitem__(self, key):
return self._get_next_ret_value('__getitem__', str(key))
def __call__(self, *args, **kwargs):
params = serialize_args(args, kwargs)
return self._get_next_ret_value('__call__', params)
class MockProxy(object):
def __init__(self, wrapped):
self._wrapped = wrapped
self._recorded_values = {}
def _get_proxy_object(self, obj):
if (hasattr(obj, '__dict__') or
isinstance(obj, tuple) or
isinstance(obj, list) or
isinstance(obj, dict)):
p = MockProxy(obj)
else:
p = obj
return p
def __getattr__(self, name):
if name in ['_wrapped']:
return object.__getattribute__(self, name)
else:
attr = getattr(self._wrapped, name)
if (inspect.isfunction(attr) or
inspect.ismethod(attr) or
inspect.isbuiltin(attr)):
def newfunc(*args, **kwargs):
result = attr(*args, **kwargs)
p = self._get_proxy_object(result)
params = serialize_args(args, kwargs)
self._add_recorded_ret_value(name, params, p)
return p
return newfunc
elif (hasattr(attr, '__dict__') or
(hasattr(attr, '__getitem__') and not
(isinstance(attr, str) or isinstance(attr, unicode)))):
p = MockProxy(attr)
else:
p = attr
self._add_recorded_value(name, p)
return p
def __setattr__(self, name, value):
if name in ['_wrapped', '_recorded_values']:
object.__setattr__(self, name, value)
else:
setattr(self._wrapped, name, value)
def _add_recorded_ret_value(self, name, params, val):
d = self._recorded_values.get(name)
if d is None:
d = {}
self._recorded_values[name] = d
l = d.get(params)
if l is None:
l = []
d[params] = l
l.append(val)
def _add_recorded_value(self, name, val):
if name not in self._recorded_values:
self._recorded_values[name] = []
self._recorded_values[name].append(val)
def get_mock(self):
values = {}
for k, v in self._recorded_values.items():
if isinstance(v, dict):
d = {}
values[k] = d
for k1, v1 in v.items():
l = []
d[k1] = l
for i1 in v1:
if isinstance(i1, MockProxy):
l.append(i1.get_mock())
else:
l.append(i1)
else:
l = []
values[k] = l
for i in v:
if isinstance(i, MockProxy):
l.append(i.get_mock())
elif isinstance(i, dict):
d = {}
for k1, v1 in v.items():
if isinstance(v1, MockProxy):
d[k1] = v1.get_mock()
else:
d[k1] = v1
l.append(d)
elif isinstance(i, list):
l1 = []
for i1 in i:
if isinstance(i1, MockProxy):
l1.append(i1.get_mock())
else:
l1.append(i1)
l.append(l1)
else:
l.append(i)
return Mock(values)
def __str__(self):
s = str(self._wrapped)
self._add_recorded_value('__str__', s)
return s
def __len__(self):
l = len(self._wrapped)
self._add_recorded_value('__len__', l)
return l
def __iter__(self):
it = []
for i in self._wrapped:
it.append(self._get_proxy_object(i))
self._add_recorded_value('__iter__', it)
return iter(it)
def __getitem__(self, key):
p = self._get_proxy_object(self._wrapped[key])
self._add_recorded_ret_value('__getitem__', str(key), p)
return p
def __call__(self, *args, **kwargs):
c = self._wrapped(*args, **kwargs)
p = self._get_proxy_object(c)
params = serialize_args(args, kwargs)
self._add_recorded_ret_value('__call__', params, p)
return p

View File

@ -1,2 +0,0 @@
Files with extension p.gz are compressed pickle files containing serialized
mocks used during unit testing

View File

@ -1,145 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Pedro Navarro Perez
#
# 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.
"""
Windows storage classes to be used in testing.
"""
import os
from oslo.config import cfg
# Check needed for unit testing on Unix
if os.name == 'nt':
import wmi
CONF = cfg.CONF
class WindowsUtils(object):
def __init__(self):
self.__conn_cimv2 = None
self.__conn_wmi = None
@property
def _conn_cimv2(self):
if self.__conn_cimv2 is None:
self.__conn_cimv2 = wmi.WMI(moniker='//./root/cimv2')
return self.__conn_cimv2
@property
def _conn_wmi(self):
if self.__conn_wmi is None:
self.__conn_wmi = wmi.WMI(moniker='//./root/wmi')
return self.__conn_wmi
def find_vhd_by_name(self, name):
'''Finds a volume by its name.'''
wt_disks = self._conn_wmi.WT_Disk(Description=name)
return wt_disks
def volume_exists(self, name):
'''Checks if a volume exists.'''
wt_disks = self.find_vhd_by_name(name)
if len(wt_disks) > 0:
return True
return False
def snapshot_exists(self, name):
'''Checks if a snapshot exists.'''
wt_snapshots = self.find_snapshot_by_name(name)
if len(wt_snapshots) > 0:
return True
return False
def find_snapshot_by_name(self, name):
'''Finds a snapshot by its name.'''
wt_snapshots = self._conn_wmi.WT_Snapshot(Description=name)
return wt_snapshots
def delete_volume(self, name):
'''Deletes a volume.'''
wt_disk = self._conn_wmi.WT_Disk(Description=name)[0]
wt_disk.Delete_()
vhdfiles = self._conn_cimv2.query(
"Select * from CIM_DataFile where Name = '" +
self._get_vhd_path(name) + "'")
if len(vhdfiles) > 0:
vhdfiles[0].Delete()
def _get_vhd_path(self, volume_name):
'''Gets the path disk of the volume.'''
base_vhd_folder = CONF.windows_iscsi_lun_path
return os.path.join(base_vhd_folder, volume_name + ".vhd")
def delete_snapshot(self, name):
'''Deletes a snapshot.'''
wt_snapshot = self._conn_wmi.WT_Snapshot(Description=name)[0]
wt_snapshot.Delete_()
vhdfile = self._conn_cimv2.query(
"Select * from CIM_DataFile where Name = '" +
self._get_vhd_path(name) + "'")[0]
vhdfile.Delete()
def find_initiator_ids(self, target_name, initiator_name):
'''Finds a initiator id by its name.'''
wt_idmethod = self._conn_wmi.WT_IDMethod(HostName=target_name,
Method=4,
Value=initiator_name)
return wt_idmethod
def initiator_id_exists(self, target_name, initiator_name):
'''Checks if a initiatorId exists.'''
wt_idmethod = self.find_initiator_ids(target_name, initiator_name)
if len(wt_idmethod) > 0:
return True
return False
def find_exports(self, target_name):
'''Finds a export id by its name.'''
wt_host = self._conn_wmi.WT_Host(HostName=target_name)
return wt_host
def export_exists(self, target_name):
'''Checks if a export exists.'''
wt_host = self.find_exports(target_name)
if len(wt_host) > 0:
return True
return False
def delete_initiator_id(self, target_name, initiator_name):
'''Deletes a initiatorId.'''
wt_init_id = self.find_initiator_ids(target_name, initiator_name)[0]
wt_init_id.Delete_()
def delete_export(self, target_name):
'''Deletes an export.'''
wt_host = self.find_exports(target_name)[0]
wt_host.RemoveAllWTDisks()
wt_host.Delete_()

View File

@ -1,245 +0,0 @@
# 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.
"""
Volume driver for Windows Server 2012
This driver requires ISCSI target role installed
"""
import os
from oslo.config import cfg
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.volume import driver
# Check needed for unit testing on Unix
if os.name == 'nt':
import wmi
LOG = logging.getLogger(__name__)
windows_opts = [
cfg.StrOpt('windows_iscsi_lun_path',
default='C:\iSCSIVirtualDisks',
help='Path to store VHD backed volumes'),
]
CONF = cfg.CONF
CONF.register_opts(windows_opts)
class WindowsDriver(driver.ISCSIDriver):
"""Executes volume driver commands on Windows Storage server."""
def __init__(self, *args, **kwargs):
super(WindowsDriver, self).__init__(*args, **kwargs)
def do_setup(self, context):
"""Setup the Windows Volume driver.
Called one time by the manager after the driver is loaded.
Validate the flags we care about
"""
#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.
"""
#Invoking the portal an checking that is listening
wt_portal = self._conn_wmi.WT_Portal()[0]
listen = wt_portal.Listen
if not listen:
raise exception.VolumeBackendAPIException()
def initialize_connection(self, volume, connector):
"""Driver entry point to attach a volume to an instance.
"""
initiator_name = connector['initiator']
target_name = volume['provider_location']
cl = self._conn_wmi.__getattr__("WT_IDMethod")
wt_idmethod = cl.new()
wt_idmethod.HostName = target_name
wt_idmethod.Method = 4
wt_idmethod.Value = initiator_name
wt_idmethod.put()
#Getting the portal and port information
wt_portal = self._conn_wmi.WT_Portal()[0]
(address, port) = (wt_portal.Address, wt_portal.Port)
#Getting the host information
hosts = self._conn_wmi.WT_Host(Hostname=target_name)
host = hosts[0]
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
return {
'driver_volume_type': 'iscsi',
'data': properties,
}
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to unattach a volume from an instance.
Unmask the LUN on the storage system so the given intiator can no
longer access it.
"""
initiator_name = connector['initiator']
provider_location = volume['provider_location']
#DesAssigning target to initiators
wt_idmethod = self._conn_wmi.WT_IDMethod(HostName=provider_location,
Method=4,
Value=initiator_name)[0]
wt_idmethod.Delete_()
def create_volume(self, volume):
"""Driver entry point for creating a new volume."""
vhd_path = self._get_vhd_path(volume)
vol_name = volume['name']
#The WMI procedure returns a Generic failure
cl = self._conn_wmi.__getattr__("WT_Disk")
cl.NewWTDisk(DevicePath=vhd_path,
Description=vol_name,
SizeInMB=volume['size'] * 1024)
def _get_vhd_path(self, volume):
base_vhd_folder = CONF.windows_iscsi_lun_path
if not os.path.exists(base_vhd_folder):
LOG.debug(_('Creating folder %s '), base_vhd_folder)
os.makedirs(base_vhd_folder)
return os.path.join(base_vhd_folder, str(volume['name']) + ".vhd")
def delete_volume(self, volume):
"""Driver entry point for destroying existing volumes."""
vol_name = volume['name']
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 = '" +
self._get_vhd_path(volume) + "'")
if len(vhdfiles) > 0:
vhdfiles[0].Delete()
def create_snapshot(self, snapshot):
"""Driver entry point for creating a snapshot.
"""
#Getting WT_Snapshot class
vol_name = snapshot['volume_name']
snapshot_name = snapshot['name']
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()
def create_volume_from_snapshot(self, volume, snapshot):
"""Driver entry point for exporting snapshots as volumes."""
snapshot_name = snapshot['name']
wt_snapshot = self._conn_wmi.WT_Snapshot(Description=snapshot_name)[0]
disk_id = wt_snapshot.Export()[0]
wt_disk = self._conn_wmi.WT_Disk(WTD=disk_id)[0]
wt_disk.Description = volume['name']
wt_disk.put()
def delete_snapshot(self, snapshot):
"""Driver entry point for deleting a snapshot."""
snapshot_name = snapshot['name']
wt_snapshot = self._conn_wmi.WT_Snapshot(Description=snapshot_name)[0]
wt_snapshot.Delete_()
def _do_export(self, _ctx, volume, ensure=False):
"""Do all steps to get disk exported as LUN 0 at separate target.
:param volume: reference of volume to be exported
:param ensure: if True, ignore errors caused by already existing
resources
:return: iscsiadm-formatted provider location string
"""
target_name = "%s%s" % (CONF.iscsi_target_prefix, volume['name'])
#ISCSI target creation
try:
cl = self._conn_wmi.__getattr__("WT_Host")
cl.NewHost(HostName=target_name)
except Exception as exc:
excep_info = exc.com_error.excepinfo[2]
if not ensure or excep_info.find(u'The file exists') == -1:
raise
else:
LOG.info(_('Ignored target creation error "%s"'
' while ensuring export'), exc)
#Get the disk to add
vol_name = volume['name']
q = self._conn_wmi.WT_Disk(Description=vol_name)
if not len(q):
LOG.debug(_('Disk not found: %s'), vol_name)
return None
wt_disk = q[0]
wt_host = self._conn_wmi.WT_Host(HostName=target_name)[0]
wt_host.AddWTDisk(wt_disk.WTD)
return target_name
def ensure_export(self, context, volume):
"""Driver entry point to get the export info for an existing volume."""
self._do_export(context, volume, ensure=True)
def create_export(self, context, volume):
"""Driver entry point to get the export info for a new volume."""
loc = self._do_export(context, volume, ensure=False)
return {'provider_location': loc}
def remove_export(self, context, volume):
"""Driver exntry point to remove an export for a volume.
"""
target_name = "%s%s" % (CONF.iscsi_target_prefix, volume['name'])
#Get ISCSI target
wt_host = self._conn_wmi.WT_Host(HostName=target_name)[0]
wt_host.RemoveAllWTDisks()
wt_host.Delete_()
def copy_image_to_volume(self, context, volume, image_service, image_id):
"""Fetch the image from image_service and write it to the volume."""
raise NotImplementedError()
def copy_volume_to_image(self, context, volume, image_service, image_meta):
"""Copy the volume to the specified image."""
raise NotImplementedError()

View File

@ -0,0 +1,13 @@
# Copyright 2013 OpenStack LLC
#
# 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.

View File

@ -0,0 +1,227 @@
# 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.
"""
Volume driver for Windows Server 2012
This driver requires ISCSI target role installed
"""
import os
from oslo.config import cfg
from cinder.image import image_utils
from cinder.openstack.common import log as logging
from cinder.volume import driver
from cinder.volume.drivers.windows import windows_utils
LOG = logging.getLogger(__name__)
windows_opts = [
cfg.StrOpt('windows_iscsi_lun_path',
default='C:\iSCSIVirtualDisks',
help='Path to store VHD backed volumes'),
]
CONF = cfg.CONF
CONF.register_opts(windows_opts)
class WindowsDriver(driver.ISCSIDriver):
"""Executes volume driver commands on Windows Storage server."""
VERSION = '1.0.0'
def __init__(self, *args, **kwargs):
super(WindowsDriver, self).__init__(*args, **kwargs)
self.configuration = kwargs.get('configuration', None)
if self.configuration:
self.configuration.append_config_values(windows_opts)
def do_setup(self, context):
"""Setup the Windows Volume driver.
Called one time by the manager after the driver is loaded.
Validate the flags we care about
"""
self.utils = windows_utils.WindowsUtils()
def check_for_setup_error(self):
"""Check that the driver is working and can communicate."""
self.utils.check_for_setup_error()
def initialize_connection(self, volume, connector):
"""Driver entry point to attach a volume to an instance."""
initiator_name = connector['initiator']
target_name = volume['provider_location']
self.utils.associate_initiator_with_iscsi_target(target_name,
initiator_name)
properties = self.utils.get_host_information(volume, target_name)
return {
'driver_volume_type': 'iscsi',
'data': properties,
}
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to unattach a volume from an instance.
Unmask the LUN on the storage system so the given initiator can no
longer access it.
"""
initiator_name = connector['initiator']
target_name = volume['provider_location']
self.utils.delete_iscsi_target(initiator_name, target_name)
def create_volume(self, volume):
"""Driver entry point for creating a new volume."""
vhd_path = self.local_path(volume)
vol_name = volume['name']
vol_size = volume['size']
self.utils.create_volume(vhd_path, vol_name, vol_size)
def local_path(self, volume):
base_vhd_folder = self.configuration.windows_iscsi_lun_path
if not os.path.exists(base_vhd_folder):
LOG.debug(_('Creating folder %s '), base_vhd_folder)
os.makedirs(base_vhd_folder)
return os.path.join(base_vhd_folder, str(volume['name']) + ".vhd")
def delete_volume(self, volume):
"""Driver entry point for destroying existing volumes."""
vol_name = volume['name']
vhd_path = self.local_path(volume)
self.utils.delete_volume(vol_name, vhd_path)
def create_snapshot(self, snapshot):
"""Driver entry point for creating a snapshot."""
# Getting WT_Snapshot class
vol_name = snapshot['volume_name']
snapshot_name = snapshot['name']
self.utils.create_snapshot(vol_name, snapshot_name)
def create_volume_from_snapshot(self, volume, snapshot):
"""Driver entry point for exporting snapshots as volumes."""
snapshot_name = snapshot['name']
vol_name = volume['name']
self.utils.create_volume_from_snapshot(vol_name, snapshot_name)
def delete_snapshot(self, snapshot):
"""Driver entry point for deleting a snapshot."""
snapshot_name = snapshot['name']
self.utils.delete_snapshot(snapshot_name)
def _do_export(self, _ctx, volume, ensure=False):
"""Do all steps to get disk exported as LUN 0 at separate target.
:param volume: reference of volume to be exported
:param ensure: if True, ignore errors caused by already existing
resources
:return: iscsiadm-formatted provider location string
"""
target_name = "%s%s" % (self.configuration.iscsi_target_prefix,
volume['name'])
self.utils.create_iscsi_target(target_name, ensure)
# Get the disk to add
vol_name = volume['name']
self.utils.add_disk_to_target(vol_name, target_name)
return target_name
def ensure_export(self, context, volume):
"""Driver entry point to get the export info for an existing volume."""
self._do_export(context, volume, ensure=True)
def create_export(self, context, volume):
"""Driver entry point to get the export info for a new volume."""
loc = self._do_export(context, volume, ensure=False)
return {'provider_location': loc}
def remove_export(self, context, volume):
"""Driver entry point to remove an export for a volume.
"""
target_name = "%s%s" % (self.configuration.iscsi_target_prefix,
volume['name'])
self.utils.remove_iscsi_target(target_name)
def copy_image_to_volume(self, context, volume, image_service, image_id):
"""Fetch the image from image_service and write it to the volume."""
# Convert to VHD and file back to VHD
image_utils.fetch_to_vhd(context, image_service, image_id,
self.local_path(volume))
def copy_volume_to_image(self, context, volume, image_service, image_meta):
"""Copy the volume to the specified image."""
# Copy the volume to the image conversion dir
temp_vhd_path = os.path.join(self.configuration.image_conversion_dir,
str(image_meta['id']) + ".vhd")
self.utils.copy_vhd_disk(self.local_path(volume), temp_vhd_path)
image_utils.upload_volume(context, image_service, image_meta,
temp_vhd_path, 'vpc')
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume."""
# Create a new volume
# Copy VHD file of the volume to clone to the created volume
self.create_volume(volume)
self.utils.copy_vhd_disk(self.local_path(src_vref),
self.local_path(volume))
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If 'refresh' is True, run update the stats first.
"""
if refresh:
self._update_volume_stats()
return self._stats
def _update_volume_stats(self):
"""Retrieve stats info for Windows device."""
LOG.debug(_("Updating volume stats"))
data = {}
backend_name = self.__class__.__name__
if self.configuration:
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = backend_name or self.__class__.__name__
data["vendor_name"] = 'Microsoft'
data["driver_version"] = self.VERSION
data["storage_protocol"] = 'iSCSI'
data['total_capacity_gb'] = 'infinite'
data['free_capacity_gb'] = 'infinite'
data['reserved_percentage'] = 100
data['QoS_support'] = False
self._stats = data
def extend_volume(self, volume, new_size):
"""Extend an Existing Volume."""
old_size = volume['size']
LOG.debug(_("Extended volume from %(old_size) to %(new_size)"),
{'old_size': old_size, 'new_size': new_size})
additional_size = (new_size - old_size) * 1024
self.utils.extend(volume['name'], additional_size)

View File

@ -0,0 +1,297 @@
# 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)

View File

@ -102,7 +102,9 @@ MAPPING = {
'cinder.volume.storwize_svc.StorwizeSVCDriver':
'cinder.volume.drivers.storwize_svc.StorwizeSVCDriver',
'cinder.volume.windows.WindowsDriver':
'cinder.volume.drivers.windows.WindowsDriver',
'cinder.volume.drivers.windows.windows.WindowsDriver',
'cinder.volume.drivers.windows.WindowsDriver':
'cinder.volume.drivers.windows.windows.WindowsDriver',
'cinder.volume.xiv.XIVDriver':
'cinder.volume.drivers.xiv_ds8k.XIVDS8KDriver',
'cinder.volume.drivers.xiv.XIVDriver':

View File

@ -1541,7 +1541,7 @@
#
# Options defined in cinder.volume.drivers.windows
# Options defined in cinder.volume.drivers.windows.windows
#
# Path to store VHD backed volumes (string value)