]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Driver for IBM XIV storage.
authorAvishay Traeger <avishay@il.ibm.com>
Sun, 12 Aug 2012 11:55:04 +0000 (14:55 +0300)
committerAvishay Traeger <avishay@il.ibm.com>
Mon, 13 Aug 2012 08:42:21 +0000 (11:42 +0300)
Volume driver for IBM XIV storage systems, along with unit tests. The
driver imports the xiv_openstack_box class, which is a closed-source
proxy available separately to IBM XIV customers.

Change-Id: I3a3ec8b1d3e3adf1895b4a0bbd86993010d2e447

cinder/tests/fake_flags.py
cinder/tests/test_xiv.py [new file with mode: 0644]
cinder/volume/xiv.py [new file with mode: 0644]

index 5d0d1f864ac2988db523efa0a512b7d17b7a95cd..5c247302a8ca193735e933b0fa7d81603d606c79 100644 (file)
@@ -35,3 +35,4 @@ def set_defaults(conf):
     conf.set_default('sql_connection', "sqlite://")
     conf.set_default('sqlite_synchronous', False)
     conf.set_default('policy_file', 'cinder/tests/policy.json')
+    conf.set_default('xiv_proxy', 'cinder.tests.test_xiv.XIVFakeProxyDriver')
diff --git a/cinder/tests/test_xiv.py b/cinder/tests/test_xiv.py
new file mode 100644 (file)
index 0000000..6328fa9
--- /dev/null
@@ -0,0 +1,242 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 IBM, Inc.
+# Copyright (c) 2012 OpenStack LLC.
+# 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.
+#
+# Authors:
+#   Erik Zaadi <erikz@il.ibm.com>
+#   Avishay Traeger <avishay@il.ibm.com>
+
+from cinder import exception
+from cinder import flags
+from cinder import test
+from cinder.volume import xiv
+
+
+FLAGS = flags.FLAGS
+FAKE = "fake"
+VOLUME = {
+        'size': 16,
+        'name': FAKE,
+        'id': 1
+        }
+
+CONNECTOR = {
+        'initiator': "iqn.2012-07.org.fake:01:948f189c4695",
+        }
+
+
+class XIVFakeProxyDriver(object):
+    """Fake XIV Proxy Driver."""
+
+    def __init__(self, xiv_info, logger, expt):
+        """
+        Initialize Proxy
+        """
+
+        self.xiv_info = xiv_info
+        self.logger = logger
+        self.exception = expt
+        self.xiv_portal = \
+            self.xiv_iqn = FAKE
+
+        self.volumes = {}
+
+    def setup(self, context):
+        if self.xiv_info['xiv_user'] != FLAGS.san_login:
+            raise self.exception.NotAuthorized()
+
+        if self.xiv_info['xiv_address'] != FLAGS.san_ip:
+            raise self.exception.HostNotFound()
+
+    def create_volume(self, volume):
+        if volume['size'] > 100:
+            raise self.exception.VolumeBackendAPIException()
+        self.volumes[volume['name']] = volume
+
+    def volume_exists(self, volume):
+        return self.volumes.get(volume['name'], None) != None
+
+    def delete_volume(self, volume):
+        if self.volumes.get(volume['name'], None) is not None:
+            del self.volumes[volume['name']]
+
+    def initialize_connection(self, volume, connector):
+        if not self.volume_exists(volume):
+            raise self.exception.VolumeNotFound()
+        lun_id = volume['id']
+
+        self.volumes[volume['name']]['attached'] = connector
+
+        return {
+                'driver_volume_type': 'iscsi',
+                'data': {
+                    'target_discovered': True,
+                    'target_portal': self.xiv_portal,
+                    'target_iqn': self.xiv_iqn,
+                    'target_lun': lun_id,
+                    'volume_id': volume['id'],
+                    'multipath': True,
+                    # part of a patch to nova-compute to enable iscsi multipath
+                    'provider_location': "%s,1 %s %s" % (
+                        self.xiv_portal,
+                        self.xiv_iqn,
+                        lun_id),
+                    },
+                }
+
+    def terminate_connection(self, volume, connector):
+        if not self.volume_exists(volume):
+            raise self.exception.VolumeNotFound()
+        if not self.is_volume_attached(volume, connector):
+            raise self.exception.VolumeNotFoundForInstance()
+        del self.volumes[volume['name']]['attached']
+
+    def is_volume_attached(self, volume, connector):
+        if not self.volume_exists(volume):
+            raise self.exception.VolumeNotFound()
+
+        return self.volumes[volume['name']].get('attached', None) \
+                == connector
+
+
+class XIVVolumeDriverTest(test.TestCase):
+    """Test IBM XIV volume driver."""
+
+    def setUp(self):
+        """Initialize IVM XIV Driver."""
+        super(XIVVolumeDriverTest, self).setUp()
+
+        self.driver = xiv.XIVDriver()
+
+    def test_initialized_should_set_xiv_info(self):
+        """Test that the san flags are passed to the XIV proxy."""
+
+        self.assertEquals(
+                self.driver.xiv_proxy.xiv_info['xiv_user'],
+                FLAGS.san_login)
+        self.assertEquals(
+                self.driver.xiv_proxy.xiv_info['xiv_pass'],
+                FLAGS.san_password)
+        self.assertEquals(
+                self.driver.xiv_proxy.xiv_info['xiv_address'],
+                FLAGS.san_ip)
+        self.assertEquals(
+                self.driver.xiv_proxy.xiv_info['xiv_vol_pool'],
+                FLAGS.san_clustername)
+
+    def test_setup_should_fail_if_credentials_are_invalid(self):
+        """Test that the xiv_proxy validates credentials."""
+
+        self.driver.xiv_proxy.xiv_info['xiv_user'] = 'invalid'
+        self.assertRaises(exception.NotAuthorized, self.driver.do_setup, None)
+
+    def test_setup_should_fail_if_connection_is_invalid(self):
+        """Test that the xiv_proxy validates connection."""
+
+        self.driver.xiv_proxy.xiv_info['xiv_address'] = 'invalid'
+        self.assertRaises(exception.HostNotFound, self.driver.do_setup, None)
+
+    def test_create_volume(self):
+        """Test creating a volume."""
+
+        self.driver.do_setup(None)
+        self.driver.create_volume(VOLUME)
+        has_volume = self.driver.xiv_proxy.volume_exists(VOLUME)
+        self.assertTrue(has_volume)
+        self.driver.delete_volume(VOLUME)
+
+    def test_volume_exists(self):
+        """Test the volume exist method with a volume that doesn't exist."""
+
+        self.driver.do_setup(None)
+        self.assertFalse(self.driver.xiv_proxy.volume_exists({'name': FAKE}))
+
+    def test_delete_volume(self):
+        """Verify that a volume is deleted."""
+
+        self.driver.do_setup(None)
+        self.driver.create_volume(VOLUME)
+        self.driver.delete_volume(VOLUME)
+        has_volume = self.driver.xiv_proxy.volume_exists(VOLUME)
+        self.assertFalse(has_volume)
+
+    def test_delete_volume_should_fail_for_not_existing_volume(self):
+        """Verify that deleting a non-existing volume is OK."""
+
+        self.driver.do_setup(None)
+        self.driver.delete_volume(VOLUME)
+
+    def test_create_volume_should_fail_if_no_pool_space_left(self):
+        """Vertify that the xiv_proxy validates volume pool space."""
+
+        self.driver.do_setup(None)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                self.driver.create_volume,
+                    {'name': FAKE, 'id': 1, 'size': 12000})
+
+    def test_initialize_connection(self):
+        """Test that inititialize connection attaches volume to host."""
+
+        self.driver.do_setup(None)
+        self.driver.create_volume(VOLUME)
+        self.driver.initialize_connection(VOLUME, CONNECTOR)
+
+        self.assertTrue(
+                self.driver.xiv_proxy.is_volume_attached(VOLUME, CONNECTOR))
+
+        self.driver.terminate_connection(VOLUME, CONNECTOR)
+        self.driver.delete_volume(VOLUME)
+
+    def test_initialize_connection_should_fail_for_non_existing_volume(self):
+        """Verify that initialize won't work for non-existing volume."""
+
+        self.driver.do_setup(None)
+        self.assertRaises(exception.VolumeNotFound,
+                self.driver.initialize_connection, VOLUME, CONNECTOR)
+
+    def test_terminate_connection(self):
+        """Test terminating a connection."""
+
+        self.driver.do_setup(None)
+        self.driver.create_volume(VOLUME)
+        self.driver.initialize_connection(VOLUME, CONNECTOR)
+        self.driver.terminate_connection(VOLUME, CONNECTOR)
+
+        self.assertFalse(
+                self.driver.xiv_proxy.is_volume_attached(
+                    VOLUME,
+                    CONNECTOR))
+
+        self.driver.delete_volume(VOLUME)
+
+    def test_terminate_connection_should_fail_on_non_existing_volume(self):
+        """Test that terminate won't work for non-existing volumes."""
+
+        self.driver.do_setup(None)
+        self.assertRaises(exception.VolumeNotFound,
+                self.driver.terminate_connection, VOLUME, CONNECTOR)
+
+    def test_terminate_connection_should_fail_on_non_attached_volume(self):
+        """Test that terminate won't work for volumes that are not attached."""
+
+        self.driver.do_setup(None)
+        self.driver.create_volume(VOLUME)
+
+        self.assertRaises(exception.VolumeNotFoundForInstance,
+                self.driver.terminate_connection, VOLUME, CONNECTOR)
+
+        self.driver.delete_volume(VOLUME)
diff --git a/cinder/volume/xiv.py b/cinder/volume/xiv.py
new file mode 100644 (file)
index 0000000..e440392
--- /dev/null
@@ -0,0 +1,128 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 IBM, Inc.
+# Copyright (c) 2012 OpenStack LLC.
+# 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.
+#
+# Authors:
+#   Erik Zaadi <erikz@il.ibm.com>
+#   Avishay Traeger <avishay@il.ibm.com>
+
+"""
+Volume driver for IBM XIV storage systems.
+"""
+
+from cinder import exception
+from cinder import flags
+from cinder.openstack.common import cfg
+from cinder.openstack.common import importutils
+from cinder.openstack.common import log as logging
+from cinder.volume import san
+
+ibm_xiv_opts = [
+    cfg.StrOpt('xiv_proxy',
+               default='xiv_openstack.nova_proxy.XIVNovaProxy',
+               help='Proxy driver'),
+]
+
+FLAGS = flags.FLAGS
+FLAGS.register_opts(ibm_xiv_opts)
+
+LOG = logging.getLogger('nova.volume.xiv')
+
+
+class XIVDriver(san.SanISCSIDriver):
+    """IBM XIV volume driver."""
+
+    def __init__(self, *args, **kwargs):
+        """Initialize the driver."""
+
+        proxy = importutils.import_class(FLAGS.xiv_proxy)
+
+        self.xiv_proxy = proxy({
+                "xiv_user": FLAGS.san_login,
+                "xiv_pass": FLAGS.san_password,
+                "xiv_address": FLAGS.san_ip,
+                "xiv_vol_pool": FLAGS.san_clustername
+                },
+                LOG,
+                exception)
+        san.SanISCSIDriver.__init__(self, *args, **kwargs)
+
+    def do_setup(self, context):
+        """Setup and verify IBM XIV storage connection."""
+
+        self.xiv_proxy.setup(context)
+
+    def ensure_export(self, context, volume):
+        """Ensure an export."""
+
+        return self.xiv_proxy.ensure_export(context, volume)
+
+    def create_export(self, context, volume):
+        """Create an export."""
+
+        return self.xiv_proxy.create_export(context, volume)
+
+    def create_volume(self, volume):
+        """Create a volume on the IBM XIV storage system."""
+
+        return self.xiv_proxy.create_volume(volume)
+
+    def delete_volume(self, volume):
+        """Delete a volume on the IBM XIV storage system."""
+
+        self.xiv_proxy.delete_volume(volume)
+
+    def remove_export(self, context, volume):
+        """Disconnect a volume from an attached instance."""
+
+        return self.xiv_proxy.remove_export(context, volume)
+
+    def initialize_connection(self, volume, connector):
+        """Map the created volume."""
+
+        return self.xiv_proxy.initialize_connection(
+                volume,
+                connector)
+
+    def terminate_connection(self, volume, connector):
+        """Terminate a connection to a volume."""
+
+        return self.xiv_proxy.terminate_connection(
+                volume,
+                connector)
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create a volume from a snapshot."""
+
+        return self.xiv_proxy.create_volume_from_snapshot(
+                volume,
+                snapshot)
+
+    def create_snapshot(self, snapshot):
+        """Create a snapshot."""
+
+        return self.xiv_proxy.create_snapshot(snapshot)
+
+    def delete_snapshot(self, snapshot):
+        """Delete a snapshot."""
+
+        return self.xiv_proxy.delete_snapshot(snapshot)
+
+    def get_volume_stats(self, refresh=False):
+        """Get volume stats."""
+
+        return self.xiv_proxy.get_volume_stats(refresh)