From aa4a89eda8941bce22ead9db2dd5f7bc6ca90e04 Mon Sep 17 00:00:00 2001 From: Andrew Kerr Date: Tue, 11 Mar 2014 10:28:55 -0400 Subject: [PATCH] Allow NetApp iSCSI driver to sub-clone large volumes 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 | 0 cinder/tests/volume/drivers/__init__.py | 0 .../tests/volume/drivers/netapp/__init__.py | 0 .../tests/volume/drivers/netapp/test_iscsi.py | 138 +++++++++++++++++ cinder/volume/drivers/netapp/iscsi.py | 141 +++++++++++------- 5 files changed, 221 insertions(+), 58 deletions(-) create mode 100644 cinder/tests/volume/__init__.py create mode 100644 cinder/tests/volume/drivers/__init__.py create mode 100644 cinder/tests/volume/drivers/netapp/__init__.py create mode 100644 cinder/tests/volume/drivers/netapp/test_iscsi.py diff --git a/cinder/tests/volume/__init__.py b/cinder/tests/volume/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cinder/tests/volume/drivers/__init__.py b/cinder/tests/volume/drivers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cinder/tests/volume/drivers/netapp/__init__.py b/cinder/tests/volume/drivers/netapp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cinder/tests/volume/drivers/netapp/test_iscsi.py b/cinder/tests/volume/drivers/netapp/test_iscsi.py new file mode 100644 index 000000000..fd35f0b72 --- /dev/null +++ b/cinder/tests/volume/drivers/netapp/test_iscsi.py @@ -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) diff --git a/cinder/volume/drivers/netapp/iscsi.py b/cinder/volume/drivers/netapp/iscsi.py index f5d8e32d3..d5615207a 100644 --- a/cinder/volume/drivers/netapp/iscsi.py +++ b/cinder/volume/drivers/netapp/iscsi.py @@ -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: -- 2.45.2