From 204c3a30afd5c4d2f9e595a7c64b187e1a0531c2 Mon Sep 17 00:00:00 2001 From: Navneet Singh Date: Tue, 30 Jul 2013 11:04:20 +0530 Subject: [PATCH] NetApp fix create vol different size than snapshot This fixes the issue of creating vol of different size than snapshot and creating cloned vol of different size than original. This change adds a lot of additional LUN geometry and sub-LUN cloning code which is neccesary to support extreme resizes, since normal resize operations are limitted to be within a certain factor of the original LUN size. Closes-Bug:#1098581 Change-Id: I8652bfaa67c12c790fa80650a051497f70279b9c --- cinder/tests/test_netapp.py | 104 ++++++--- cinder/tests/test_netapp_nfs.py | 26 +-- cinder/volume/drivers/netapp/api.py | 6 +- cinder/volume/drivers/netapp/iscsi.py | 310 +++++++++++++++++++++----- cinder/volume/drivers/netapp/nfs.py | 57 +++-- 5 files changed, 381 insertions(+), 122 deletions(-) diff --git a/cinder/tests/test_netapp.py b/cinder/tests/test_netapp.py index 252d749b6..d56cf6901 100644 --- a/cinder/tests/test_netapp.py +++ b/cinder/tests/test_netapp.py @@ -136,7 +136,7 @@ class FakeDirectCMODEServerHandler(FakeHTTPRequestHandler): falselinux - true/vol/navneet/lun2 + true/vol/navneet/lun1 0 false2FfGI$APyN68 none20971520 @@ -389,6 +389,28 @@ class FakeDirectCMODEServerHandler(FakeHTTPRequestHandler): 1""" elif 'ems-autosupport-log' == api: body = """""" + elif 'lun-resize' == api: + body = """""" + elif 'lun-get-geometry' == api: + body = """ + 1 + 2 + 8 + 2 + 4 + 5 + """ + elif 'volume-options-list-info' == api: + body = """ + + + + """ + elif 'lun-move' == api: + body = """""" else: # Unknown API s.send_response(500) @@ -477,12 +499,18 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase): 'id': 'lun1', 'provider_auth': None, 'project_id': 'project', 'display_name': None, 'display_description': 'lun1', 'volume_type_id': None} - volume_clone_fail = {'name': 'cl_fail', 'size': 1, 'volume_name': 'fail', - 'os_type': 'linux', 'provider_location': 'cl_fail', - 'id': 'lun1', 'provider_auth': None, - 'project_id': 'project', 'display_name': None, - 'display_description': 'lun1', - 'volume_type_id': None} + volume_clone = {'name': 'cl_sm', 'size': 3, 'volume_name': 'lun1', + 'os_type': 'linux', 'provider_location': 'cl_sm', + 'id': 'lun1', 'provider_auth': None, + 'project_id': 'project', 'display_name': None, + 'display_description': 'lun1', + 'volume_type_id': None} + volume_clone_large = {'name': 'cl_lg', 'size': 6, 'volume_name': 'lun1', + 'os_type': 'linux', 'provider_location': 'cl_lg', + 'id': 'lun1', 'provider_auth': None, + 'project_id': 'project', 'display_name': None, + 'display_description': 'lun1', + 'volume_type_id': None} connector = {'initiator': 'iqn.1993-08.org.debian:01:10'} vol_fail = {'name': 'lun_fail', 'size': 10000, 'volume_name': 'lun1', 'os_type': 'linux', 'provider_location': 'lun1', @@ -567,34 +595,12 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase): self.driver.terminate_connection(self.volume, self.connector) self.driver.delete_volume(self.volume) - def test_fail_vol_from_snapshot_creation(self): - self.driver.create_volume(self.volume) - try: - self.driver.create_volume_from_snapshot(self.volume, - self.snapshot_fail) - raise AssertionError() - except exception.VolumeBackendAPIException: - pass - finally: - self.driver.delete_volume(self.volume) - def test_cloned_volume_destroy(self): self.driver.create_volume(self.volume) self.driver.create_cloned_volume(self.snapshot, self.volume) self.driver.delete_volume(self.snapshot) self.driver.delete_volume(self.volume) - def test_fail_cloned_volume_creation(self): - self.driver.create_volume(self.volume) - try: - self.driver.create_cloned_volume(self.volume_clone_fail, - self.volume) - raise AssertionError() - except exception.VolumeBackendAPIException: - pass - finally: - self.driver.delete_volume(self.volume) - def test_map_by_creating_igroup(self): self.driver.create_volume(self.volume) updates = self.driver.create_export(None, self.volume) @@ -615,6 +621,22 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase): def test_vol_stats(self): self.driver.get_volume_stats(refresh=True) + def test_create_vol_snapshot_diff_size_resize(self): + self.driver.create_volume(self.volume) + self.driver.create_snapshot(self.snapshot) + self.driver.create_volume_from_snapshot( + self.volume_clone, self.snapshot) + self.driver.delete_snapshot(self.snapshot) + self.driver.delete_volume(self.volume) + + def test_create_vol_snapshot_diff_size_subclone(self): + self.driver.create_volume(self.volume) + self.driver.create_snapshot(self.snapshot) + self.driver.create_volume_from_snapshot( + self.volume_clone_large, self.snapshot) + self.driver.delete_snapshot(self.snapshot) + self.driver.delete_volume(self.volume) + class NetAppDriverNegativeTestCase(test.TestCase): """Test case for NetAppDriver""" @@ -691,7 +713,7 @@ class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler): false - /vol/vol1/clone1 + /vol/vol1/lun1 20971520 true false @@ -997,6 +1019,28 @@ class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler): body = """""" elif 'ems-autosupport-log' == api: body = """""" + elif 'lun-resize' == api: + body = """""" + elif 'lun-get-geometry' == api: + body = """ + 1 + 2 + 8 + 2 + 4 + 5 + """ + elif 'volume-options-list-info' == api: + body = """ + + + + """ + elif 'lun-move' == api: + body = """""" else: # Unknown API s.send_response(500) diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py index 4bdf855d2..d56aee78d 100644 --- a/cinder/tests/test_netapp_nfs.py +++ b/cinder/tests/test_netapp_nfs.py @@ -51,6 +51,9 @@ class FakeVolume(object): def __getitem__(self, key): return self.__dict__[key] + def __setitem__(self, key, val): + self.__dict__[key] = val + class FakeSnapshot(object): def __init__(self, volume_size=0): @@ -100,21 +103,20 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase): drv = self._driver mox = self.mox volume = FakeVolume(1) - snapshot = FakeSnapshot(2) - - self.assertRaises(exception.CinderException, - drv.create_volume_from_snapshot, - volume, - snapshot) - snapshot = FakeSnapshot(1) location = '127.0.0.1:/nfs' expected_result = {'provider_location': location} mox.StubOutWithMock(drv, '_clone_volume') mox.StubOutWithMock(drv, '_get_volume_location') + mox.StubOutWithMock(drv, 'local_path') + mox.StubOutWithMock(drv, '_discover_file_till_timeout') + mox.StubOutWithMock(drv, '_set_rw_permissions_for_all') drv._clone_volume(IgnoreArg(), IgnoreArg(), IgnoreArg()) drv._get_volume_location(IgnoreArg()).AndReturn(location) + drv.local_path(IgnoreArg()).AndReturn('/mnt') + drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True) + drv._set_rw_permissions_for_all(IgnoreArg()) mox.ReplayAll() @@ -163,16 +165,6 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase): mox.VerifyAll() - def test_cloned_volume_size_fail(self): - volume_clone_fail = FakeVolume(1) - volume_src = FakeVolume(2) - try: - self._driver.create_cloned_volume(volume_clone_fail, - volume_src) - raise AssertionError() - except exception.CinderException: - pass - def _custom_setup(self): kwargs = {} kwargs['netapp_mode'] = 'proxy' diff --git a/cinder/volume/drivers/netapp/api.py b/cinder/volume/drivers/netapp/api.py index 4212601d5..5d0716092 100644 --- a/cinder/volume/drivers/netapp/api.py +++ b/cinder/volume/drivers/netapp/api.py @@ -125,10 +125,10 @@ class NaServer(object): self._refresh_conn = True def get_api_version(self): - """Gets the api version.""" + """Gets the api version tuple.""" if hasattr(self, '_api_version'): - return self._api_version - return self._api_version + return (self._api_major_version, self._api_minor_version) + return None def set_port(self, port): """Set the server communication port.""" diff --git a/cinder/volume/drivers/netapp/iscsi.py b/cinder/volume/drivers/netapp/iscsi.py index da9859092..193d00ea7 100644 --- a/cinder/volume/drivers/netapp/iscsi.py +++ b/cinder/volume/drivers/netapp/iscsi.py @@ -28,6 +28,7 @@ import time import uuid from cinder import exception +from cinder.openstack.common import excutils from cinder.openstack.common import log as logging from cinder import units from cinder import utils @@ -185,12 +186,19 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): msg_fmt = {'name': name} LOG.warn(msg % msg_fmt) return + self._destroy_lun(metadata['Path']) + self.lun_table.pop(name) + + def _destroy_lun(self, path, force=True): + """Destroys the lun at the path.""" lun_destroy = NaElement.create_node_with_children( 'lun-destroy', - **{'path': metadata['Path'], 'force': 'true'}) + **{'path': path}) + if force: + lun_destroy.add_new_child('force', 'true') self.client.invoke_successfully(lun_destroy, True) - LOG.debug(_("Destroyed LUN %s") % name) - self.lun_table.pop(name) + seg = path.split("/") + LOG.debug(_("Destroyed LUN %s") % seg[-1]) def ensure_export(self, context, volume): """Driver entry point to get the export info for an existing volume.""" @@ -300,14 +308,17 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): vol_size = volume['size'] snap_size = snapshot['volume_size'] - if vol_size != snap_size: - msg = _('Cannot create volume of size %(vol_size)s from ' - 'snapshot of size %(snap_size)s') - msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size} - raise exception.VolumeBackendAPIException(data=msg % msg_fmt) snapshot_name = snapshot['name'] new_name = volume['name'] self._clone_lun(snapshot_name, new_name, 'true') + if vol_size != snap_size: + try: + self.extend_volume(volume, volume['size']) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error( + _("Resizing %s failed. Cleaning volume."), new_name) + self.delete_volume(volume) def terminate_connection(self, volume, connector, **kwargs): """Driver entry point to unattach a volume from an instance. @@ -339,6 +350,16 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): """Creates an actual lun on filer.""" raise NotImplementedError() + def _create_lun(self, volume, lun, size, metadata): + """Issues api request for creating lun on volume.""" + path = '/vol/%s/%s' % (volume, lun) + lun_create = NaElement.create_node_with_children( + 'lun-create-by-size', + **{'path': path, 'size': size, + 'ostype': metadata['OsType'], + 'space-reservation-enabled': metadata['SpaceReserved']}) + self.client.invoke_successfully(lun_create, True) + def _get_iscsi_service_details(self): """Returns iscsi iqn.""" raise NotImplementedError() @@ -501,12 +522,13 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): raise exception.VolumeBackendAPIException(data=msg) self.lun_table[lun.name] = lun - def _clone_lun(self, name, new_name, space_reserved): + def _clone_lun(self, name, new_name, space_reserved='true', + start_block=0, end_block=0, block_count=0): """Clone LUN with the given name to the new name.""" raise NotImplementedError() def _get_lun_by_args(self, **args): - """Retrives lun with specified args.""" + """Retrives luns with specified args.""" raise NotImplementedError() def _get_lun_attr(self, name, attr): @@ -525,13 +547,16 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): vol_size = volume['size'] src_vol = self.lun_table[src_vref['name']] src_vol_size = src_vref['size'] - if vol_size != src_vol_size: - msg = _('Cannot clone volume of size %(vol_size)s from ' - 'src volume of size %(src_vol_size)s') - msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size} - raise exception.VolumeBackendAPIException(data=msg % msg_fmt) new_name = volume['name'] self._clone_lun(src_vol.name, new_name, 'true') + if vol_size != src_vol_size: + try: + self.extend_volume(volume, volume['size']) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error( + _("Resizing %s failed. Cleaning volume."), new_name) + self.delete_volume(volume) def get_volume_stats(self, refresh=False): """Get volume stats. @@ -548,6 +573,172 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver): """Retrieve stats info from volume group.""" raise NotImplementedError() + def extend_volume(self, volume, new_size): + """Extend an existing volume to the new size.""" + name = volume['name'] + path = self.lun_table[name].metadata['Path'] + curr_size_bytes = str(self.lun_table[name].size) + new_size_bytes = str(int(new_size) * units.GiB) + # Reused by clone scenarios. + # Hence comparing the stored size. + if curr_size_bytes != new_size_bytes: + lun_geometry = self._get_lun_geometry(path) + if (lun_geometry and lun_geometry.get("max_resize") + and lun_geometry.get("max_resize") >= new_size_bytes): + self._do_direct_resize(path, new_size_bytes) + else: + self._do_sub_clone_resize(path, new_size_bytes) + self.lun_table[name].size = new_size_bytes + else: + LOG.info(_("No need to extend volume %s" + " as it is already the requested new size."), name) + + def _do_direct_resize(self, path, new_size_bytes, force=True): + """Uses the resize api to resize the lun.""" + seg = path.split("/") + LOG.info(_("Resizing lun %s directly to new size."), seg[-1]) + lun_resize = NaElement("lun-resize") + lun_resize.add_new_child('path', path) + lun_resize.add_new_child('size', new_size_bytes) + if force: + lun_resize.add_new_child('force', 'true') + self.client.invoke_successfully(lun_resize, True) + + def _get_lun_geometry(self, path): + """Gets the lun geometry.""" + geometry = {} + lun_geo = NaElement("lun-get-geometry") + lun_geo.add_new_child('path', path) + try: + result = self.client.invoke_successfully(lun_geo, True) + geometry['size'] = result.get_child_content("size") + geometry['bytes_per_sector'] =\ + result.get_child_content("bytes-per-sector") + geometry['sectors_per_track'] =\ + result.get_child_content("sectors-per-track") + geometry['tracks_per_cylinder'] =\ + result.get_child_content("tracks-per-cylinder") + geometry['cylinders'] =\ + result.get_child_content("cylinders") + geometry['max_resize'] =\ + result.get_child_content("max-resize-size") + except Exception as e: + LOG.error(_("Lun %(path)s geometry failed. Message - %(msg)s") + % {'path': path, 'msg': e.message}) + return geometry + + def _get_volume_options(self, volume_name): + """Get the value for the volume option.""" + opts = [] + vol_option_list = NaElement("volume-options-list-info") + vol_option_list.add_new_child('volume', volume_name) + result = self.client.invoke_successfully(vol_option_list, True) + options = result.get_child_by_name("options") + if options: + opts = options.get_children() + return opts + + def _get_vol_option(self, volume_name, option_name): + """Get the value for the volume option.""" + value = None + options = self._get_volume_options(volume_name) + for opt in options: + if opt.get_child_content('name') == option_name: + value = opt.get_child_content('value') + break + return value + + def _move_lun(self, path, new_path): + """Moves the lun at path to new path.""" + seg = path.split("/") + new_seg = new_path.split("/") + LOG.debug(_("Moving lun %(name)s to %(new_name)s.") + % {'name': seg[-1], 'new_name': new_seg[-1]}) + lun_move = NaElement("lun-move") + lun_move.add_new_child("path", path) + lun_move.add_new_child("new-path", new_path) + self.client.invoke_successfully(lun_move, True) + + def _do_sub_clone_resize(self, path, new_size_bytes): + """Does sub lun clone after verification. + + Clones the block ranges and swaps + the luns also deletes older lun + after a successful clone. + """ + seg = path.split("/") + LOG.info(_("Resizing lun %s using sub clone to new size."), seg[-1]) + name = seg[-1] + vol_name = seg[2] + lun = self.lun_table[name] + metadata = lun.metadata + compression = self._get_vol_option(vol_name, 'compression') + if compression == "on": + msg = _('%s cannot be sub clone resized' + ' as it is hosted on compressed volume') + raise exception.VolumeBackendAPIException(data=msg % name) + else: + block_count = self._get_lun_block_count(path) + if block_count == 0: + msg = _('%s cannot be sub clone resized' + ' as it contains no blocks.') + raise exception.VolumeBackendAPIException(data=msg % name) + new_lun = 'new-%s' % (name) + self._create_lun(vol_name, new_lun, new_size_bytes, metadata) + try: + self._clone_lun(name, new_lun, block_count=block_count) + self._post_sub_clone_resize(path) + except Exception: + with excutils.save_and_reraise_exception(): + new_path = '/vol/%s/%s' % (vol_name, new_lun) + self._destroy_lun(new_path) + + def _post_sub_clone_resize(self, path): + """Try post sub clone resize in a transactional manner.""" + st_tm_mv, st_nw_mv, st_del_old = None, None, None + seg = path.split("/") + LOG.info(_("Post clone resize lun %s"), seg[-1]) + new_lun = 'new-%s' % (seg[-1]) + tmp_lun = 'tmp-%s' % (seg[-1]) + tmp_path = "/vol/%s/%s" % (seg[2], tmp_lun) + new_path = "/vol/%s/%s" % (seg[2], new_lun) + try: + st_tm_mv = self._move_lun(path, tmp_path) + st_nw_mv = self._move_lun(new_path, path) + st_del_old = self._destroy_lun(tmp_path) + except Exception as e: + if st_tm_mv is None: + msg = _("Failure staging lun %s to tmp.") + raise exception.VolumeBackendAPIException(data=msg % (seg[-1])) + else: + if st_nw_mv is None: + self._move_lun(tmp_path, path) + msg = _("Failure moving new cloned lun to %s.") + raise exception.VolumeBackendAPIException( + data=msg % (seg[-1])) + elif st_del_old is None: + LOG.error(_("Failure deleting staged tmp lun %s."), + tmp_lun) + else: + LOG.error(_("Unknown exception in" + " post clone resize lun %s."), seg[-1]) + LOG.error(_("Exception details: %s") % (e.__str__())) + + def _get_lun_block_count(self, path): + """Gets block counts for the lun.""" + LOG.debug(_("Getting lun block count.")) + block_count = 0 + lun_infos = self._get_lun_by_args(path=path) + if not lun_infos: + seg = path.split('/') + msg = _('Failure getting lun info for %s.') + raise exception.VolumeBackendAPIException(data=msg % seg[-1]) + lun_info = lun_infos[-1] + bs = int(lun_info.get_child_content('block-size')) + ls = int(lun_info.get_child_content('size')) + block_count = ls / bs + return block_count + class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): """NetApp C-mode iSCSI volume driver.""" @@ -585,12 +776,7 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): raise exception.VolumeBackendAPIException(data=msg % name) for volume in volumes: try: - path = '/vol/%s/%s' % (volume.id['name'], name) - lun_create = NaElement.create_node_with_children( - 'lun-create-by-size', - **{'path': path, 'size': size, - 'ostype': metadata['OsType']}) - self.client.invoke_successfully(lun_create, True) + self._create_lun(volume.id['name'], name, size, metadata) metadata['Path'] = '/vol/%s/%s' % (volume.id['name'], name) metadata['Volume'] = volume.id['name'] metadata['Qtree'] = None @@ -763,7 +949,8 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): break return igroup_list - def _clone_lun(self, name, new_name, space_reserved): + def _clone_lun(self, name, new_name, space_reserved='true', + start_block=0, end_block=0, block_count=0): """Clone LUN with the given handle to the new name.""" metadata = self._get_lun_attr(name, 'metadata') volume = metadata['Volume'] @@ -771,6 +958,15 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver): 'clone-create', **{'volume': volume, 'source-path': name, 'destination-path': new_name, 'space-reserve': space_reserved}) + if block_count > 0: + block_ranges = NaElement("block-ranges") + block_range = NaElement.create_node_with_children( + 'block-range', + **{'source-block-number': str(start_block), + 'destination-block-number': str(end_block), + 'block-count': str(block_count)}) + block_ranges.add_child_elem(block_range) + 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' @@ -903,9 +1099,9 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): if self.volume_list: self.volume_list = self.volume_list.split(',') self.volume_list = [el.strip() for el in self.volume_list] + (major, minor) = self._get_ontapi_version() + self.client.set_api_version(major, minor) if self.vfiler: - (major, minor) = self._get_ontapi_version() - self.client.set_api_version(major, minor) self.client.set_vfiler(self.vfiler) def _create_lun_on_eligible_vol(self, name, size, metadata, @@ -917,12 +1113,7 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): if not volume: msg = _('Failed to get vol with required size for volume: %s') raise exception.VolumeBackendAPIException(data=msg % name) - path = '/vol/%s/%s' % (volume['name'], name) - lun_create = NaElement.create_node_with_children( - 'lun-create-by-size', - **{'path': path, 'size': size, 'ostype': metadata['OsType'], - 'space-reservation-enabled': metadata['SpaceReserved']}) - self.client.invoke_successfully(lun_create, True) + self._create_lun(volume['name'], name, size, metadata) metadata['Path'] = '/vol/%s/%s' % (volume['name'], name) metadata['Volume'] = volume['name'] metadata['Qtree'] = None @@ -945,24 +1136,10 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): if self.volume_list: if avl_vol['name'] in self.volume_list: return avl_vol - else: - if self._check_vol_not_root(avl_vol): + elif self._get_vol_option(avl_vol['name'], 'root') != 'true': return avl_vol return None - def _check_vol_not_root(self, vol): - """Checks if a volume is not root.""" - vol_options = NaElement.create_node_with_children( - 'volume-options-list-info', **{'volume': vol['name']}) - result = self.client.invoke_successfully(vol_options, True) - options = result.get_child_by_name('options') - ops = options.get_children() - for op in ops: - if op.get_child_content('name') == 'root' and\ - op.get_child_content('value') == 'true': - return False - return True - def _get_igroup_by_initiator(self, initiator): """Get igroups by initiator.""" igroup_list = NaElement('igroup-list-info') @@ -1075,16 +1252,26 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): break return (igroup, lun_id) - def _clone_lun(self, name, new_name, space_reserved): + def _clone_lun(self, name, new_name, space_reserved='true', + start_block=0, end_block=0, block_count=0): """Clone LUN with the given handle to the new name.""" metadata = self._get_lun_attr(name, 'metadata') 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'}) + 'clone-start', **{'source-path': path, + 'destination-path': clone_path, + 'no-snap': 'true'}) + if block_count > 0: + block_ranges = NaElement("block-ranges") + block_range = NaElement.create_node_with_children( + 'block-range', + **{'source-block-number': str(start_block), + 'destination-block-number': str(end_block), + 'block-count': str(block_count)}) + block_ranges.add_child_elem(block_range) + 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') @@ -1092,8 +1279,9 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): 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) - cloned_lun = self._get_lun_by_args(path=clone_path) - if cloned_lun: + luns = self._get_lun_by_args(path=clone_path) + if luns: + cloned_lun = luns[0] self._set_space_reserve(clone_path, space_reserved) clone_meta = self._create_lun_meta(cloned_lun) handle = self._create_lun_handle(clone_meta) @@ -1149,15 +1337,11 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): clone_ops_info.get_child_content('reason')) def _get_lun_by_args(self, **args): - """Retrives lun with specified args.""" + """Retrives luns with specified args.""" lun_info = NaElement.create_node_with_children('lun-list-info', **args) result = self.client.invoke_successfully(lun_info, True) luns = result.get_child_by_name('luns') - if luns: - infos = luns.get_children() - if infos: - return infos[0] - return None + return luns.get_children() def _create_lun_meta(self, lun): """Creates lun metadata dictionary.""" @@ -1189,3 +1373,15 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver): provide_ems(self, self.client, data, netapp_backend, server_type="7mode") self._stats = data + + def _get_lun_block_count(self, path): + """Gets block counts for the lun.""" + bs = super( + NetAppDirect7modeISCSIDriver, self)._get_lun_block_count(path) + api_version = self.client.get_api_version() + if api_version: + major = api_version[0] + minor = api_version[1] + if major == 1 and minor < 15: + bs = bs - 1 + return bs diff --git a/cinder/volume/drivers/netapp/nfs.py b/cinder/volume/drivers/netapp/nfs.py index e5599a3fe..dad6a2fe7 100644 --- a/cinder/volume/drivers/netapp/nfs.py +++ b/cinder/volume/drivers/netapp/nfs.py @@ -30,6 +30,7 @@ from oslo.config import cfg from cinder import exception from cinder.image import image_utils +from cinder.openstack.common import excutils from cinder.openstack.common import log as logging from cinder.openstack.common import processutils from cinder import units @@ -93,16 +94,27 @@ class NetAppNFSDriver(nfs.NfsDriver): vol_size = volume.size snap_size = snapshot.volume_size - if vol_size != snap_size: - msg = _('Cannot create volume of size %(vol_size)s from ' - 'snapshot of size %(snap_size)s') - msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size} - raise exception.CinderException(msg % msg_fmt) - self._clone_volume(snapshot.name, volume.name, snapshot.volume_id) share = self._get_volume_location(snapshot.volume_id) + volume['provider_location'] = share + path = self.local_path(volume) + + if self._discover_file_till_timeout(path): + self._set_rw_permissions_for_all(path) + if vol_size != snap_size: + try: + self.extend_volume(volume, vol_size) + except Exception as e: + with excutils.save_and_reraise_exception(): + LOG.error( + _("Resizing %s failed. Cleaning volume."), + volume.name) + self._execute('rm', path, run_as_root=True) + else: + raise exception.CinderException( + _("NFS file %s not discovered.") % volume['name']) - return {'provider_location': share} + return {'provider_location': volume['provider_location']} def create_snapshot(self, snapshot): """Creates a snapshot.""" @@ -190,17 +202,26 @@ class NetAppNFSDriver(nfs.NfsDriver): """Creates a clone of the specified volume.""" vol_size = volume.size src_vol_size = src_vref.size - - if vol_size != src_vol_size: - msg = _('Cannot create clone of size %(vol_size)s from ' - 'volume of size %(src_vol_size)s') - msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size} - raise exception.CinderException(msg % msg_fmt) - self._clone_volume(src_vref.name, volume.name, src_vref.id) share = self._get_volume_location(src_vref.id) + volume['provider_location'] = share + path = self.local_path(volume) + + if self._discover_file_till_timeout(path): + self._set_rw_permissions_for_all(path) + if vol_size != src_vol_size: + try: + self.extend_volume(volume, vol_size) + except Exception as e: + LOG.error( + _("Resizing %s failed. Cleaning volume."), volume.name) + self._execute('rm', path, run_as_root=True) + raise e + else: + raise exception.CinderException( + _("NFS file %s not discovered.") % volume['name']) - return {'provider_location': share} + return {'provider_location': volume['provider_location']} def _update_volume_stats(self): """Retrieve stats info from volume group.""" @@ -584,6 +605,12 @@ class NetAppNFSDriver(nfs.NfsDriver): direct_url = "%s/%s" % (share_location, rel_path) return direct_url + def extend_volume(self, volume, new_size): + """Extend an existing volume to the new size.""" + LOG.info(_('Extending volume %s.'), volume['name']) + path = self.local_path(volume) + self._resize_image_file(path, new_size) + class NetAppDirectNfsDriver (NetAppNFSDriver): """Executes commands related to volumes on NetApp filer.""" -- 2.45.2