]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Updated HpSanISCSIDriver to use initialize/terminate methods.
authorZhiteng Huang <zhiteng.huang@intel.com>
Thu, 5 Jul 2012 07:08:34 +0000 (15:08 +0800)
committerZhiteng Huang <zhiteng.huang@intel.com>
Thu, 5 Jul 2012 15:14:11 +0000 (23:14 +0800)
This is based on Nirmal Ranganathan's patch for Nova-volume, which
added a new key 'host' for initialize/terminate volume connection
information in nova to fix bug 992729.

HpSanISCSIDriver should adopt the change in order to work properly.
Also added missing unit tests for the HpSanISCSIDriver.

Fixes bug 992729.

Change-Id: I9584811a340799161d8c9ae88b1def282380c9ad
Signed-off-by: Zhiteng Huang <zhiteng.huang@intel.com>
Authors
cinder/tests/test_HpSanISCSIDriver.py [new file with mode: 0644]
cinder/volume/san.py

diff --git a/Authors b/Authors
index 31f5034592ea2d68905c84d4fffe6d7ce1352fdb..310e6bd7e11ca88a6931523d3be93430e4d29382 100644 (file)
--- a/Authors
+++ b/Authors
@@ -47,4 +47,5 @@ Vladimir Popovski <vladimir@zadarastorage.com>
 Yoshiaki Tamura <yoshi@midokura.jp>
 Yun Mao <yunmao@gmail.com>
 Yuriy Taraday <yorik.sar@gmail.com>
+Zhiteng Huang <zhiteng.huang@intel.com>
 Zhongyue Luo <lzyeval@gmail.com>
diff --git a/cinder/tests/test_HpSanISCSIDriver.py b/cinder/tests/test_HpSanISCSIDriver.py
new file mode 100644 (file)
index 0000000..083383c
--- /dev/null
@@ -0,0 +1,212 @@
+#    Copyright 2012 OpenStack LLC
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+from cinder import exception
+from cinder import log as logging
+from cinder import test
+from cinder.volume import san
+
+LOG = logging.getLogger(__name__)
+
+
+class HpSanISCSITestCase(test.TestCase):
+
+    def setUp(self):
+        super(HpSanISCSITestCase, self).setUp()
+        self.stubs.Set(san.HpSanISCSIDriver, "_cliq_run",
+                       self._fake_cliq_run)
+        self.stubs.Set(san.HpSanISCSIDriver, "_get_iscsi_properties",
+                       self._fake_get_iscsi_properties)
+        self.driver = san.HpSanISCSIDriver()
+        self.volume_name = "fakevolume"
+        self.connector = {'ip': '10.0.0.2',
+                          'initiator': 'iqn.1993-08.org.debian:01:222',
+                          'host': 'fakehost'}
+        self.properties = {'target_discoverd': True,
+                           'target_portal': '10.0.1.6:3260',
+                           'target_iqn':
+                        'iqn.2003-10.com.lefthandnetworks:group01:25366:fakev',
+                           'volume_id': 1}
+
+    def tearDown(self):
+        super(HpSanISCSITestCase, self).tearDown()
+
+    def _fake_get_iscsi_properties(self, volume):
+        return self.properties
+
+    def _fake_cliq_run(self, verb, cliq_args):
+        """Return fake results for the various methods."""
+
+        def create_volume(cliq_args):
+            """
+            input = "createVolume description="fake description"
+                                  clusterName=Cluster01 volumeName=fakevolume
+                                  thinProvision=0 output=XML size=1GB"
+            """
+            output = """<gauche version="1.0">
+                <response description="Operation succeeded."
+                          name="CliqSuccess" processingTime="181" result="0"/>
+                </gauche>"""
+            self.assertEqual(cliq_args['volumeName'], self.volume_name)
+            self.assertEqual(cliq_args['thinProvision'], '1')
+            self.assertEqual(cliq_args['size'], '1GB')
+            return output, None
+
+        def delete_volume(cliq_args):
+            """
+            input = "deleteVolume volumeName=fakevolume prompt=false
+                                  output=XML"
+            """
+            output = """<gauche version="1.0">
+                <response description="Operation succeeded."
+                          name="CliqSuccess" processingTime="164" result="0"/>
+                </gauche>"""
+            self.assertEqual(cliq_args['volumeName'], self.volume_name)
+            self.assertEqual(cliq_args['prompt'], 'false')
+            return output, None
+
+        def assign_volume(cliq_args):
+            """
+            input = "assignVolumeToServer volumeName=fakevolume
+                                          serverName=fakehost
+                                          output=XML"
+            """
+            output = """<gauche version="1.0">
+                <response description="Operation succeeded."
+                          name="CliqSuccess" processingTime="174" result="0"/>
+                </gauche>"""
+            self.assertEqual(cliq_args['volumeName'], self.volume_name)
+            self.assertEqual(cliq_args['serverName'], self.connector['host'])
+            return output, None
+
+        def unassign_volume(cliq_args):
+            """
+            input = "unassignVolumeToServer volumeName=fakevolume
+                                            serverName=fakehost output=XML
+            """
+            output = """<gauche version="1.0">
+                <response description="Operation succeeded."
+                          name="CliqSuccess" processingTime="205" result="0"/>
+                </gauche>"""
+            self.assertEqual(cliq_args['volumeName'], self.volume_name)
+            self.assertEqual(cliq_args['serverName'], self.connector['host'])
+            return output, None
+
+        def get_cluster_info(cliq_args):
+            """
+            input = "getClusterInfo clusterName=Cluster01 searchDepth=1
+                                    verbose=0 output=XML"
+            """
+            output = """<gauche version="1.0">
+                <response description="Operation succeeded." name="CliqSuccess"
+                          processingTime="1164" result="0">
+                <cluster blockSize="1024" description=""
+                         maxVolumeSizeReplication1="622957690"
+                         maxVolumeSizeReplication2="311480287"
+                         minVolumeSize="262144" name="Cluster01"
+                         pageSize="262144" spaceTotal="633697992"
+                         storageNodeCount="2" unprovisionedSpace="622960574"
+                         useVip="true">
+                <nsm ipAddress="10.0.1.7" name="111-vsa"/>
+                <nsm ipAddress="10.0.1.8" name="112-vsa"/>
+                <vip ipAddress="10.0.1.6" subnetMask="255.255.255.0"/>
+                </cluster></response></gauche>"""
+            return output, None
+
+        def get_volume_info(cliq_args):
+            """
+            input = "getVolumeInfo volumeName=fakevolume output=XML"
+            """
+            output = """<gauche version="1.0">
+                <response description="Operation succeeded." name="CliqSuccess"
+                          processingTime="87" result="0">
+                <volume autogrowPages="4" availability="online"
+                        blockSize="1024" bytesWritten="0" checkSum="false"
+                        clusterName="Cluster01" created="2011-02-08T19:56:53Z"
+                        deleting="false" description="" groupName="Group01"
+                        initialQuota="536870912" isPrimary="true"
+                iscsiIqn="iqn.2003-10.com.lefthandnetworks:group01:25366:fakev"
+                maxSize="6865387257856" md5="9fa5c8b2cca54b2948a63d833097e1ca"
+                minReplication="1" name="vol-b" parity="0" replication="2"
+                reserveQuota="536870912" scratchQuota="4194304"
+                serialNumber="9fa5c8b2cca54b2948a63d8"
+                size="1073741824" stridePages="32" thinProvision="true">
+                <status description="OK" value="2"/>
+                <permission access="rw" authGroup="api-1"
+                            chapName="chapusername" chapRequired="true"
+                            id="25369" initiatorSecret="" iqn=""
+                            iscsiEnabled="true" loadBalance="true"
+                            targetSecret="supersecret"/>
+                </volume></response></gauche>"""
+            return output, None
+
+        def test_error(cliq_args):
+            output = """<gauche version="1.0">
+                <response description="Volume '134234' not found."
+                name="CliqVolumeNotFound" processingTime="1083"
+                result="8000100c"/>
+                </gauche>"""
+            return output, None
+
+        self.assertEqual(cliq_args['output'], 'XML')
+        try:
+            verbs = {'createVolume': create_volume,
+                     'deleteVolume': delete_volume,
+                     'assignVolumeToServer': assign_volume,
+                     'unassignVolumeToServer': unassign_volume,
+                     'getClusterInfo': get_cluster_info,
+                     'getVolumeInfo': get_volume_info,
+                     'testError': test_error}
+        except KeyError:
+            raise NotImplementedError()
+
+        return verbs[verb](cliq_args)
+
+    def test_create_volume(self):
+        volume = {'name': self.volume_name, 'size': 1}
+        model_update = self.driver.create_volume(volume)
+        expected_iqn = "iqn.2003-10.com.lefthandnetworks:group01:25366:fakev"
+        expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
+        self.assertEqual(model_update['provider_location'], expected_location)
+
+    def test_delete_volume(self):
+        volume = {'name': self.volume_name}
+        self.driver.delete_volume(volume)
+
+    def test_initialize_connection(self):
+        volume = {'name': self.volume_name}
+        result = self.driver.initialize_connection(volume, self.connector)
+        self.assertEqual(result['driver_volume_type'], 'iscsi')
+        self.assertDictMatch(result['data'], self.properties)
+
+    def test_terminate_connection(self):
+        volume = {'name': self.volume_name}
+        self.driver.terminate_connection(volume, self.connector)
+
+    def test_create_snapshot(self):
+        try:
+            self.driver.create_snapshot("")
+        except NotImplementedError:
+            pass
+
+    def test_create_volume_from_snapshot(self):
+        try:
+            self.driver.create_volume_from_snapshot("", "")
+        except NotImplementedError:
+            pass
+
+    def test_cliq_error(self):
+        try:
+            self.driver._cliq_run_xml("testError", {})
+        except exception.Error:
+            pass
index 4edc81addbf2dbab4fe80aa42454e8fee6584ef5..26e214222df3eec62bc31c71c2f0ea18610bce29 100644 (file)
@@ -582,6 +582,14 @@ class HpSanISCSIDriver(SanISCSIDriver):
 
         return model_update
 
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Creates a volume from a snapshot."""
+        raise NotImplementedError()
+
+    def create_snapshot(self, snapshot):
+        """Creates a snapshot."""
+        raise NotImplementedError()
+
     def delete_volume(self, volume):
         """Deletes a volume."""
         cliq_args = {}
@@ -594,64 +602,45 @@ class HpSanISCSIDriver(SanISCSIDriver):
         # TODO(justinsb): Is this needed here?
         raise exception.Error(_("local_path not supported"))
 
-    def ensure_export(self, context, volume):
-        """Synchronously recreates an export for a logical volume."""
-        return self._do_export(context, volume, force_create=False)
-
-    def create_export(self, context, volume):
-        return self._do_export(context, volume, force_create=True)
+    def initialize_connection(self, volume, connector):
+        """Assigns the volume to a server.
 
-    def _do_export(self, context, volume, force_create):
-        """Supports ensure_export and create_export"""
-        volume_info = self._cliq_get_volume_info(volume['name'])
+        Assign any created volume to a compute node/host so that it can be
+        used from that host. HP VSA requires a volume to be assigned
+        to a server.
 
-        is_shared = 'permission.authGroup' in volume_info
+        This driver returns a driver_volume_type of 'iscsi'.
+        The format of the driver data is defined in _get_iscsi_properties.
+        Example return value:
 
-        model_update = {}
+            {
+                'driver_volume_type': 'iscsi'
+                'data': {
+                    'target_discovered': True,
+                    'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
+                    'target_protal': '127.0.0.1:3260',
+                    'volume_id': 1,
+                }
+            }
 
-        should_export = False
-
-        if force_create or not is_shared:
-            should_export = True
-            # Check that we have a project_id
-            project_id = volume['project_id']
-            if not project_id:
-                project_id = context.project_id
-
-            if project_id:
-                #TODO(justinsb): Use a real per-project password here
-                chap_username = 'proj_' + project_id
-                # HP/Lefthand requires that the password be >= 12 characters
-                chap_password = 'project_secret_' + project_id
-            else:
-                msg = (_("Could not determine project for volume %s, "
-                         "can't export") %
-                         (volume['name']))
-                if force_create:
-                    raise exception.Error(msg)
-                else:
-                    LOG.warn(msg)
-                    should_export = False
-
-        if should_export:
-            cliq_args = {}
-            cliq_args['volumeName'] = volume['name']
-            cliq_args['chapName'] = chap_username
-            cliq_args['targetSecret'] = chap_password
-
-            self._cliq_run_xml("assignVolumeChap", cliq_args)
-
-            model_update['provider_auth'] = ("CHAP %s %s" %
-                                             (chap_username, chap_password))
+        """
+        cliq_args = {}
+        cliq_args['volumeName'] = volume['name']
+        cliq_args['serverName'] = connector['host']
+        self._cliq_run_xml("assignVolumeToServer", cliq_args)
 
-        return model_update
+        iscsi_properties = self._get_iscsi_properties(volume)
+        return {
+            'driver_volume_type': 'iscsi',
+            'data': iscsi_properties
+        }
 
-    def remove_export(self, context, volume):
-        """Removes an export for a logical volume."""
+    def terminate_connection(self, volume, connector):
+        """Unassign the volume from the host."""
         cliq_args = {}
         cliq_args['volumeName'] = volume['name']
-
-        self._cliq_run_xml("unassignVolume", cliq_args)
+        cliq_args['serverName'] = connector['host']
+        self._cliq_run_xml("unassignVolumeToServer", cliq_args)
 
 
 class SolidFireSanISCSIDriver(SanISCSIDriver):