]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Adds support for Windows 2012 Storage Server
authorPedro Navarro Perez <pednape@gmail.com>
Thu, 18 Oct 2012 20:44:59 +0000 (22:44 +0200)
committerPedro Navarro Perez <pednape@gmail.com>
Thu, 18 Oct 2012 20:44:59 +0000 (22:44 +0200)
blueprint windows2012driver
https://blueprints.launchpad.net/cinder/+spec/windows2012driver

Change-Id: I3f7efb1b976fedc4afb736b87d550a34c330c839

27 files changed:
cinder/tests/test_windows.py [new file with mode: 0644]
cinder/tests/windows/__init__.py [new file with mode: 0644]
cinder/tests/windows/basetestcase.py [new file with mode: 0644]
cinder/tests/windows/db_fakes.py [new file with mode: 0644]
cinder/tests/windows/mockproxy.py [new file with mode: 0644]
cinder/tests/windows/stubs/README.rst [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_check_for_setup_errors_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_export_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_snapshot_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_from_snapshot_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_create_volume_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_snapshot_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_delete_volume_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_ensure_export_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_initialize_connection_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_os.p.gz [new file with mode: 0644]
cinder/tests/windows/stubs/test_windows.TestWindowsDriver.test_remove_export_wmi.p.gz [new file with mode: 0644]
cinder/tests/windows/windowsutils.py [new file with mode: 0644]
cinder/volume/windows.py [new file with mode: 0644]

diff --git a/cinder/tests/test_windows.py b/cinder/tests/test_windows.py
new file mode 100644 (file)
index 0000000..1b1bfcb
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/cinder/tests/windows/basetestcase.py b/cinder/tests/windows/basetestcase.py
new file mode 100644 (file)
index 0000000..24fcb91
--- /dev/null
@@ -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 (file)
index 0000000..938b240
--- /dev/null
@@ -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 (file)
index 0000000..ff04ea7
--- /dev/null
@@ -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 (file)
index 0000000..150fd3a
--- /dev/null
@@ -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 (file)
index 0000000..32a00b3
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 (file)
index 0000000..a511d37
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 (file)
index 0000000..a12005a
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 (file)
index 0000000..a8b094f
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 (file)
index 0000000..a862e77
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 (file)
index 0000000..f8e0736
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 (file)
index 0000000..3cd1f0a
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 (file)
index 0000000..f3f75ab
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 (file)
index 0000000..3fe0c03
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 (file)
index 0000000..50522d7
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 (file)
index 0000000..6013757
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 (file)
index 0000000..b4494a8
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 (file)
index 0000000..48df24b
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 (file)
index 0000000..36e30e6
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 (file)
index 0000000..d561a31
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 (file)
index 0000000..0bc36f2
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 (file)
index 0000000..2730688
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 (file)
index 0000000..d1d4eda
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 (file)
index 0000000..008d530
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 (file)
index 0000000..3da6294
--- /dev/null
@@ -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 (file)
index 0000000..28dbdbc
--- /dev/null
@@ -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_()