]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Allow NetApp iSCSI driver to sub-clone large volumes
authorAndrew Kerr <andrew.kerr@netapp.com>
Tue, 11 Mar 2014 14:28:55 +0000 (10:28 -0400)
committerAlex Meade <mr.alex.meade@gmail.com>
Wed, 19 Mar 2014 20:51:52 +0000 (16:51 -0400)
The NetApp zapi used during certain extend operations has several limits
imposed on it.  Each block-range provided can only be 2^24 in size, and
there can only be 32 block-ranges per zapi call.  This fix allows the
NetApp iSCSI driver to send multiple zapi calls if necessary, to allow
for extend operations on volumes of an arbitrary size.

Closes-Bug: #1288962
Change-Id: I981d22f32cb2182112fbea3ea9880d1e8c8c91ab

cinder/tests/volume/__init__.py [new file with mode: 0644]
cinder/tests/volume/drivers/__init__.py [new file with mode: 0644]
cinder/tests/volume/drivers/netapp/__init__.py [new file with mode: 0644]
cinder/tests/volume/drivers/netapp/test_iscsi.py [new file with mode: 0644]
cinder/volume/drivers/netapp/iscsi.py

diff --git a/cinder/tests/volume/__init__.py b/cinder/tests/volume/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/tests/volume/drivers/__init__.py b/cinder/tests/volume/drivers/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/tests/volume/drivers/netapp/__init__.py b/cinder/tests/volume/drivers/netapp/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/tests/volume/drivers/netapp/test_iscsi.py b/cinder/tests/volume/drivers/netapp/test_iscsi.py
new file mode 100644 (file)
index 0000000..fd35f0b
--- /dev/null
@@ -0,0 +1,138 @@
+# Copyright (c) 2014 NetApp, Inc.
+# 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.
+"""
+Mock unit tests for the NetApp iSCSI driver
+"""
+
+import mock
+
+from cinder import test
+import cinder.volume.drivers.netapp.api as ntapi
+import cinder.volume.drivers.netapp.iscsi as ntap_iscsi
+
+
+class NetAppiSCSICModeTestCase(test.TestCase):
+    """Test case for NetApp's C-Mode iSCSI driver."""
+
+    def setUp(self):
+        super(NetAppiSCSICModeTestCase, self).setUp()
+        self.driver = ntap_iscsi.NetAppDirectCmodeISCSIDriver(
+            configuration=mock.Mock())
+        self.driver.client = mock.Mock()
+        self.driver.vserver = mock.Mock()
+
+    def tearDown(self):
+        super(NetAppiSCSICModeTestCase, self).tearDown()
+
+    def test_clone_lun_multiple_zapi_calls(self):
+        """Test for when lun clone requires more than one zapi call."""
+
+        # Max block-ranges per call = 32, max blocks per range = 2^24
+        # Force 2 calls
+        bc = 2 ** 24 * 32 * 2
+        self.driver._get_lun_attr = mock.Mock(return_value={'Volume':
+                                                            'fakeLUN'})
+        self.driver.client.invoke_successfully = mock.Mock()
+        lun = ntapi.NaElement.create_node_with_children(
+            'lun-info',
+            **{'alignment': 'indeterminate',
+               'block-size': '512',
+               'comment': '',
+               'creation-timestamp': '1354536362',
+               'is-space-alloc-enabled': 'false',
+               'is-space-reservation-enabled': 'true',
+               'mapped': 'false',
+               'multiprotocol-type': 'linux',
+               'online': 'true',
+               'path': '/vol/fakeLUN/lun1',
+               'prefix-size': '0',
+               'qtree': '',
+               'read-only': 'false',
+               'serial-number': '2FfGI$APyN68',
+               'share-state': 'none',
+               'size': '20971520',
+               'size-used': '0',
+               'staging': 'false',
+               'suffix-size': '0',
+               'uuid': 'cec1f3d7-3d41-11e2-9cf4-123478563412',
+               'volume': 'fakeLUN',
+               'vserver': 'fake_vserver'})
+        self.driver._get_lun_by_args = mock.Mock(return_value=[lun])
+        self.driver._add_lun_to_table = mock.Mock()
+        self.driver._update_stale_vols = mock.Mock()
+
+        self.driver._clone_lun('fakeLUN', 'newFakeLUN', block_count=bc)
+
+        self.assertEqual(2, self.driver.client.invoke_successfully.call_count)
+
+
+class NetAppiSCSI7ModeTestCase(test.TestCase):
+    """Test case for NetApp's 7-Mode iSCSI driver."""
+
+    def setUp(self):
+        super(NetAppiSCSI7ModeTestCase, self).setUp()
+        self.driver = ntap_iscsi.NetAppDirect7modeISCSIDriver(
+            configuration=mock.Mock())
+        self.driver.client = mock.Mock()
+        self.driver.vfiler = mock.Mock()
+
+    def tearDown(self):
+        super(NetAppiSCSI7ModeTestCase, self).tearDown()
+
+    def test_clone_lun_multiple_zapi_calls(self):
+        """Test for when lun clone requires more than one zapi call."""
+
+        # Max block-ranges per call = 32, max blocks per range = 2^24
+        # Force 2 calls
+        bc = 2 ** 24 * 32 * 2
+        self.driver._get_lun_attr = mock.Mock(return_value={'Volume':
+                                                            'fakeLUN',
+                                                            'Path':
+                                                            '/vol/fake/lun1'})
+        self.driver.client.invoke_successfully = mock.Mock(
+            return_value=mock.MagicMock())
+        lun = ntapi.NaElement.create_node_with_children(
+            'lun-info',
+            **{'alignment': 'indeterminate',
+               'block-size': '512',
+               'comment': '',
+               'creation-timestamp': '1354536362',
+               'is-space-alloc-enabled': 'false',
+               'is-space-reservation-enabled': 'true',
+               'mapped': 'false',
+               'multiprotocol-type': 'linux',
+               'online': 'true',
+               'path': '/vol/fakeLUN/lun1',
+               'prefix-size': '0',
+               'qtree': '',
+               'read-only': 'false',
+               'serial-number': '2FfGI$APyN68',
+               'share-state': 'none',
+               'size': '20971520',
+               'size-used': '0',
+               'staging': 'false',
+               'suffix-size': '0',
+               'uuid': 'cec1f3d7-3d41-11e2-9cf4-123478563412',
+               'volume': 'fakeLUN',
+               'vserver': 'fake_vserver'})
+        self.driver._get_lun_by_args = mock.Mock(return_value=[lun])
+        self.driver._add_lun_to_table = mock.Mock()
+        self.driver._update_stale_vols = mock.Mock()
+        self.driver._check_clone_status = mock.Mock()
+        self.driver._set_space_reserve = mock.Mock()
+
+        self.driver._clone_lun('fakeLUN', 'newFakeLUN', block_count=bc)
+
+        self.assertEqual(2, self.driver.client.invoke_successfully.call_count)
index f5d8e32d3e02fbfad489675813fff33d06eb1480..d5615207a81d0f6e500742ba2bc218b42e78ca49 100644 (file)
@@ -956,32 +956,44 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
         """Clone LUN with the given handle to the new name."""
         metadata = self._get_lun_attr(name, 'metadata')
         volume = metadata['Volume']
-        clone_create = NaElement.create_node_with_children(
-            'clone-create',
-            **{'volume': volume, 'source-path': name,
-                'destination-path': new_name, 'space-reserve': space_reserved})
-        if block_count > 0:
-            block_ranges = NaElement("block-ranges")
-            # zAPI can only handle 2^24 block ranges
-            bc_limit = 2 ** 24  # 8GB
-            segments = int(math.ceil(float(block_count) / float(bc_limit)))
-            bc = block_count
-            for segment in range(0, segments):
-                if bc > bc_limit:
-                    block_count = bc_limit
-                    bc -= bc_limit
-                else:
-                    block_count = bc
-                block_range = NaElement.create_node_with_children(
-                    'block-range',
-                    **{'source-block-number': str(src_block),
-                       'destination-block-number': str(dest_block),
-                       'block-count': str(block_count)})
-                block_ranges.add_child_elem(block_range)
-                src_block = int(src_block) + int(block_count)
-                dest_block = int(dest_block) + int(block_count)
-            clone_create.add_child_elem(block_ranges)
-        self.client.invoke_successfully(clone_create, True)
+        # zAPI can only handle 2^24 blocks per range
+        bc_limit = 2 ** 24  # 8GB
+        # zAPI can only handle 32 block ranges per call
+        br_limit = 32
+        z_limit = br_limit * bc_limit  # 256 GB
+        z_calls = int(math.ceil(block_count / float(z_limit)))
+        zbc = block_count
+        for call in range(0, z_calls):
+            if zbc > z_limit:
+                block_count = z_limit
+                zbc -= z_limit
+            else:
+                block_count = zbc
+            clone_create = NaElement.create_node_with_children(
+                'clone-create',
+                **{'volume': volume, 'source-path': name,
+                   'destination-path': new_name,
+                   'space-reserve': space_reserved})
+            if block_count > 0:
+                block_ranges = NaElement("block-ranges")
+                segments = int(math.ceil(block_count / float(bc_limit)))
+                bc = block_count
+                for segment in range(0, segments):
+                    if bc > bc_limit:
+                        block_count = bc_limit
+                        bc -= bc_limit
+                    else:
+                        block_count = bc
+                    block_range = NaElement.create_node_with_children(
+                        'block-range',
+                        **{'source-block-number': str(src_block),
+                           'destination-block-number': str(dest_block),
+                           'block-count': str(block_count)})
+                    block_ranges.add_child_elem(block_range)
+                    src_block += int(block_count)
+                    dest_block += int(block_count)
+                clone_create.add_child_elem(block_ranges)
+            self.client.invoke_successfully(clone_create, True)
         LOG.debug(_("Cloned LUN with new name %s") % new_name)
         lun = self._get_lun_by_args(vserver=self.vserver, path='/vol/%s/%s'
                                     % (volume, new_name))
@@ -1321,38 +1333,51 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
         path = metadata['Path']
         (parent, splitter, name) = path.rpartition('/')
         clone_path = '%s/%s' % (parent, new_name)
-        clone_start = NaElement.create_node_with_children(
-            'clone-start', **{'source-path': path,
-                              'destination-path': clone_path,
-                              'no-snap': 'true'})
-        if block_count > 0:
-            block_ranges = NaElement("block-ranges")
-            # zAPI can only handle 2^24 block ranges
-            bc_limit = 2 ** 24  # 8GB
-            segments = int(math.ceil(float(block_count) / float(bc_limit)))
-            bc = block_count
-            for segment in range(0, segments):
-                if bc > bc_limit:
-                    block_count = bc_limit
-                    bc -= bc_limit
-                else:
-                    block_count = bc
-                block_range = NaElement.create_node_with_children(
-                    'block-range',
-                    **{'source-block-number': str(src_block),
-                        'destination-block-number': str(dest_block),
-                        'block-count': str(block_count)})
-                block_ranges.add_child_elem(block_range)
-                src_block = int(src_block) + int(block_count)
-                dest_block = int(dest_block) + int(block_count)
-            clone_start.add_child_elem(block_ranges)
-        result = self.client.invoke_successfully(clone_start, True)
-        clone_id_el = result.get_child_by_name('clone-id')
-        cl_id_info = clone_id_el.get_child_by_name('clone-id-info')
-        vol_uuid = cl_id_info.get_child_content('volume-uuid')
-        clone_id = cl_id_info.get_child_content('clone-op-id')
-        if vol_uuid:
-            self._check_clone_status(clone_id, vol_uuid, name, new_name)
+        # zAPI can only handle 2^24 blocks per range
+        bc_limit = 2 ** 24  # 8GB
+        # zAPI can only handle 32 block ranges per call
+        br_limit = 32
+        z_limit = br_limit * bc_limit  # 256 GB
+        z_calls = int(math.ceil(block_count / float(z_limit)))
+        zbc = block_count
+        for call in range(0, z_calls):
+            if zbc > z_limit:
+                block_count = z_limit
+                zbc -= z_limit
+            else:
+                block_count = zbc
+            clone_start = NaElement.create_node_with_children(
+                'clone-start', **{'source-path': path,
+                                  'destination-path': clone_path,
+                                  'no-snap': 'true'})
+            if block_count > 0:
+                block_ranges = NaElement("block-ranges")
+                # zAPI can only handle 2^24 block ranges
+                bc_limit = 2 ** 24  # 8GB
+                segments = int(math.ceil(block_count / float(bc_limit)))
+                bc = block_count
+                for segment in range(0, segments):
+                    if bc > bc_limit:
+                        block_count = bc_limit
+                        bc -= bc_limit
+                    else:
+                        block_count = bc
+                    block_range = NaElement.create_node_with_children(
+                        'block-range',
+                        **{'source-block-number': str(src_block),
+                            'destination-block-number': str(dest_block),
+                            'block-count': str(block_count)})
+                    block_ranges.add_child_elem(block_range)
+                    src_block += int(block_count)
+                    dest_block += int(block_count)
+                clone_start.add_child_elem(block_ranges)
+            result = self.client.invoke_successfully(clone_start, True)
+            clone_id_el = result.get_child_by_name('clone-id')
+            cl_id_info = clone_id_el.get_child_by_name('clone-id-info')
+            vol_uuid = cl_id_info.get_child_content('volume-uuid')
+            clone_id = cl_id_info.get_child_content('clone-op-id')
+            if vol_uuid:
+                self._check_clone_status(clone_id, vol_uuid, name, new_name)
         self.vol_refresh_voluntary = True
         luns = self._get_lun_by_args(path=clone_path)
         if luns: