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
This commit is contained in:
parent
2ba4af4de5
commit
aa4a89eda8
|
@ -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)
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue