--- /dev/null
+# 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.
+
+"""
+Unit tests for Windows Server 2012 OpenStack Cinder volume driver
+"""
+import sys
+
+import cinder.flags
+from cinder.volume import windows
+from cinder.tests.windows import basetestcase
+from cinder.tests.windows import db_fakes
+from cinder.tests.windows import windowsutils
+
+FLAGS = cinder.flags.FLAGS
+
+
+class TestWindowsDriver(basetestcase.BaseTestCase):
+
+ def __init__(self, method):
+ super(TestWindowsDriver, self).__init__(method)
+
+ def setUp(self):
+ super(TestWindowsDriver, self).setUp()
+ self.flags(
+ windows_iscsi_lun_path='D:\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()
+
+ self._drv = windows.WindowsDriver()
+ self._drv.do_setup({})
+ self._wutils = windowsutils.WindowsUtils()
+
+ def _setup_stubs(self):
+
+ # Modules to mock
+ modules_to_mock = [
+ 'wmi',
+ 'os',
+ 'subprocess',
+ 'multiprocessing'
+ ]
+
+ 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" % (FLAGS.iscsi_target_prefix,
+ self._volume_data['name']),
+ self._connector_data['initiator']):
+ target_name = "%s%s" % (FLAGS.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" % (FLAGS.iscsi_target_prefix,
+ self._volume_data['name'])):
+ self._wutils.delete_export("%s%s" % (FLAGS.iscsi_target_prefix,
+ self._volume_data['name']))
+
+ finally:
+ super(TestWindowsDriver, self).tearDown()
+
+ def test_check_for_setup_errors(self):
+ self._drv.check_for_setup_error()
+
+ def test_create_volume(self):
+ self._create_volume()
+
+ wt_disks = self._wutils.find_vhd_by_name(self._volume_data['name'])
+ self.assertEquals(len(wt_disks), 1)
+
+ def _create_volume(self):
+ self._volume_data = db_fakes.get_fake_volume_info(self._volume_id)
+ self._drv.create_volume(self._volume_data)
+
+ def test_delete_volume(self):
+ self._create_volume()
+
+ self._drv.delete_volume(self._volume_data)
+
+ wt_disks = self._wutils.find_vhd_by_name(self._volume_data['name'])
+ self.assertEquals(len(wt_disks), 0)
+
+ def test_create_snapshot(self):
+ #Create a volume
+ self._create_volume()
+
+ 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()
+
+ snapshot_name = self._snapshot_data['name']
+ wt_snapshots = self._wutils.find_snapshot_by_name(snapshot_name)
+ self.assertEquals(len(wt_snapshots), 1)
+
+ 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)
+
+ def test_create_volume_from_snapshot(self):
+ #Create a volume
+ self._create_volume()
+ #Create a snapshot from the previous volume
+ self._create_snapshot()
+
+ self._volume_data_2 = db_fakes.get_fake_volume_info(self._volume_id_2)
+
+ self._drv.create_volume_from_snapshot(self._volume_data_2,
+ self._snapshot_data)
+
+ wt_disks = self._wutils.find_vhd_by_name(self._volume_data_2['name'])
+ self.assertEquals(len(wt_disks), 1)
+
+ def test_delete_snapshot(self):
+ #Create a volume
+ self._create_volume()
+ #Create a snapshot from the previous volume
+ self._create_snapshot()
+
+ self._drv.delete_snapshot(self._snapshot_data)
+
+ snapshot_name = self._snapshot_data['name']
+ wt_snapshots = self._wutils.find_snapshot_by_name(snapshot_name)
+ self.assertEquals(len(wt_snapshots), 0)
+
+ def test_create_export(self):
+ #Create a volume
+ self._create_volume()
+
+ retval = self._drv.create_export({}, self._volume_data)
+
+ volume_name = self._volume_data['name']
+ self.assertEquals(retval,
+ {'provider_location':
+ "%s%s" % (FLAGS.iscsi_target_prefix, volume_name)})
+
+ def test_initialize_connection(self):
+ #Create a volume
+ self._create_volume()
+
+ self._drv.create_export({}, self._volume_data)
+
+ self._connector_data = db_fakes.get_fake_connector_info(self._iqn)
+
+ 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']
+
+ wt_initiator_ids = self._wutils.find_initiator_ids(target_name,
+ initiator_name)
+ self.assertEquals(len(wt_initiator_ids), 1)
+
+ properties = init_data['data']
+ self.assertNotEqual(properties['target_iqn'], None)
+
+ def test_ensure_export(self):
+ #Create a volume
+ self._create_volume()
+
+ self._drv.ensure_export({}, self._volume_data)
+
+ def test_remove_export(self):
+ #Create a volume
+ self._create_volume()
+
+ self._drv.create_export({}, self._volume_data)
+
+ self._drv.remove_export({}, self._volume_data)
--- /dev/null
+# 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 gzip
+import os
+import pickle
+import cinder.test
+
+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()
+
+ has_errors = len([test for (test, msgs) in self._currentResult.errors
+ if test.id() == self.id()]) > 0
+ failed = len([test for (test, msgs) in self._currentResult.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)
--- /dev/null
+# 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.
+
+"""
+Stubouts, mocks and fixtures for windows volume test suite
+"""
+
+
+def get_fake_volume_info(name):
+ return {
+ 'name': name,
+ 'size': 1,
+ 'provider_location': 'iqn.2010-10.org.openstack:' + 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_connector_info(initiator):
+ return {
+ 'initiator': initiator,
+ }
--- /dev/null
+# 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
+
+"""
+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 not name 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
--- /dev/null
+Files with extension p.gz are compressed pickle files containing serialized
+mocks used during unit testing
--- /dev/null
+# 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
+import sys
+
+from cinder import flags
+
+# Check needed for unit testing on Unix
+if os.name == 'nt':
+ import wmi
+
+FLAGS = flags.FLAGS
+
+
+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 = FLAGS.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_()
--- /dev/null
+# 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
+import sys
+
+from cinder import exception
+from cinder import flags
+from cinder.openstack.common import cfg
+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("cinder.volume.windows.volume")
+
+FLAGS = flags.FLAGS
+
+windows_opts = [
+ cfg.StrOpt('windows_iscsi_lun_path',
+ default='C:\iSCSIVirtualDisks',
+ help='Path to store VHD backed volumes'),
+]
+
+FLAGS.register_opts(windows_opts)
+
+
+class WindowsDriver(driver.ISCSIDriver):
+ """Executes volume driver commands on Windows Storage server."""
+
+ def __init__(self):
+ super(WindowsDriver, self).__init__()
+
+ 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):
+ """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']
+ #DesAssigning target to initiators
+ wt_idmethod = self._conn_wmi.WT_IDMethod(HostName=volume['name'],
+ Method=4,
+ Value=initiator_name)
+ 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 = FLAGS.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" % (FLAGS.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']
+ wt_disk = self._conn_wmi.WT_Disk(Description=vol_name)[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" % (FLAGS.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_()