From: Pedro Navarro Perez Date: Thu, 18 Oct 2012 20:44:59 +0000 (+0200) Subject: Adds support for Windows 2012 Storage Server X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=1a7c6f4a09ef9980ead0c46d080078a7bbc81d10;p=openstack-build%2Fcinder-build.git Adds support for Windows 2012 Storage Server blueprint windows2012driver https://blueprints.launchpad.net/cinder/+spec/windows2012driver Change-Id: I3f7efb1b976fedc4afb736b87d550a34c330c839 --- diff --git a/cinder/tests/test_windows.py b/cinder/tests/test_windows.py new file mode 100644 index 000000000..1b1bfcbb0 --- /dev/null +++ b/cinder/tests/test_windows.py @@ -0,0 +1,217 @@ +# 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) diff --git a/cinder/tests/windows/__init__.py b/cinder/tests/windows/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cinder/tests/windows/basetestcase.py b/cinder/tests/windows/basetestcase.py new file mode 100644 index 000000000..24fcb91c2 --- /dev/null +++ b/cinder/tests/windows/basetestcase.py @@ -0,0 +1,96 @@ +# 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) diff --git a/cinder/tests/windows/db_fakes.py b/cinder/tests/windows/db_fakes.py new file mode 100644 index 000000000..938b240a8 --- /dev/null +++ b/cinder/tests/windows/db_fakes.py @@ -0,0 +1,42 @@ +# 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, + } diff --git a/cinder/tests/windows/mockproxy.py b/cinder/tests/windows/mockproxy.py new file mode 100644 index 000000000..ff04ea709 --- /dev/null +++ b/cinder/tests/windows/mockproxy.py @@ -0,0 +1,234 @@ +# 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 diff --git a/cinder/tests/windows/stubs/README.rst b/cinder/tests/windows/stubs/README.rst new file mode 100644 index 000000000..150fd3ad1 --- /dev/null +++ b/cinder/tests/windows/stubs/README.rst @@ -0,0 +1,2 @@ +Files with extension p.gz are compressed pickle files containing serialized +mocks used during unit testing diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_check_for_setup_errors_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_check_for_setup_errors_wmi.p.gz new file mode 100644 index 000000000..32a00b3d1 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_check_for_setup_errors_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_os.p.gz new file mode 100644 index 000000000..a511d37bf Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_wmi.p.gz new file mode 100644 index 000000000..a12005af1 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_os.p.gz new file mode 100644 index 000000000..a8b094f0c Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_wmi.p.gz new file mode 100644 index 000000000..a862e77f8 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_os.p.gz new file mode 100644 index 000000000..f8e073691 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_wmi.p.gz new file mode 100644 index 000000000..3cd1f0add Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_os.p.gz new file mode 100644 index 000000000..f3f75ab7b Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_wmi.p.gz new file mode 100644 index 000000000..3fe0c038f Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_os.p.gz new file mode 100644 index 000000000..50522d795 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_wmi.p.gz new file mode 100644 index 000000000..6013757f2 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_os.p.gz new file mode 100644 index 000000000..b4494a845 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_wmi.p.gz new file mode 100644 index 000000000..48df24bc2 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_os.p.gz new file mode 100644 index 000000000..36e30e6ec Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_wmi.p.gz new file mode 100644 index 000000000..d561a318a Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_os.p.gz new file mode 100644 index 000000000..0bc36f254 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_wmi.p.gz new file mode 100644 index 000000000..273068866 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_wmi.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_os.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_os.p.gz new file mode 100644 index 000000000..d1d4eda1b Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_os.p.gz differ diff --git a/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_wmi.p.gz b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_wmi.p.gz new file mode 100644 index 000000000..008d530f4 Binary files /dev/null and b/cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_wmi.p.gz differ diff --git a/cinder/tests/windows/windowsutils.py b/cinder/tests/windows/windowsutils.py new file mode 100644 index 000000000..3da6294cb --- /dev/null +++ b/cinder/tests/windows/windowsutils.py @@ -0,0 +1,145 @@ +# 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_() diff --git a/cinder/volume/windows.py b/cinder/volume/windows.py new file mode 100644 index 000000000..28dbdbcdc --- /dev/null +++ b/cinder/volume/windows.py @@ -0,0 +1,232 @@ +# 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_()