From fb4b0b86e98ff480eafd09ed33f7b7f6494b07d4 Mon Sep 17 00:00:00 2001 From: digvijay2016 Date: Thu, 22 Sep 2016 16:36:02 +0530 Subject: [PATCH] Add support for manage/unmanage in GPFS driver Added support for manage/unmanage in GPFS NFS driver. This patch added functions that allow share on Spectrum Scale node to be managed by OpenStack if existing fileset is an independent fileset and doesn't have any NFS export over the fileset path. Also, share can be unmanaged from OpenStack but still left in Spectrum Scale cluster. Implements: blueprint gpfs-manage-support Change-Id: I9134408b59c30ac4bc593f287294741f6e996136 --- ...hare_back_ends_feature_support_mapping.rst | 2 +- etc/manila/rootwrap.d/share.filters | 6 + manila/share/drivers/ibm/gpfs.py | 205 +++++++++- manila/tests/share/drivers/ibm/test_gpfs.py | 360 +++++++++++++++++- ...-gpfs-manage-support-c110120c350728e3.yaml | 8 + 5 files changed, 564 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/ibm-gpfs-manage-support-c110120c350728e3.yaml diff --git a/doc/source/devref/share_back_ends_feature_support_mapping.rst b/doc/source/devref/share_back_ends_feature_support_mapping.rst index 3ab154b144..cddec61975 100644 --- a/doc/source/devref/share_back_ends_feature_support_mapping.rst +++ b/doc/source/devref/share_back_ends_feature_support_mapping.rst @@ -61,7 +61,7 @@ Mapping of share drivers and share features support +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ | Huawei | K | L | L | L | K | M | \- | +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ -| IBM GPFS | K | \- | L | \- | K | K | \- | +| IBM GPFS | K | O | L | \- | K | K | \- | +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ | LVM | M | \- | M | \- | M | M | \- | +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+ diff --git a/etc/manila/rootwrap.d/share.filters b/etc/manila/rootwrap.d/share.filters index 0941275749..6c5229a6fe 100644 --- a/etc/manila/rootwrap.d/share.filters +++ b/etc/manila/rootwrap.d/share.filters @@ -119,6 +119,12 @@ df: CommandFilter, df, root chmod: CommandFilter, chmod, root # manila/share/drivers/ibm/gpfs.py: 'mmnfs', 'export', '%s', '%s' mmnfs: CommandFilter, mmnfs, root +# manila/share/drivers/ibm/gpfs.py: 'mmlsfileset', '%s', '-J', '%s', '-L' +mmlsfileset: CommandFilter, mmlsfileset, root +# manila/share/drivers/ibm/gpfs.py: 'mmchfileset', '%s', '-J', '%s', '-j', '%s' +mmchfileset: CommandFilter, mmchfileset, root +# manila/share/drivers/ibm/gpfs.py: 'mmlsquota', '-j', '-J', '%s', '%s' +mmlsquota: CommandFilter, mmlsquota, root # manila/share/drivers/ganesha/manager.py: 'mv', '%s', '%s' mv: CommandFilter, mv, root diff --git a/manila/share/drivers/ibm/gpfs.py b/manila/share/drivers/ibm/gpfs.py index 621e767c09..13a48ffb5a 100644 --- a/manila/share/drivers/ibm/gpfs.py +++ b/manila/share/drivers/ibm/gpfs.py @@ -44,7 +44,7 @@ import six from manila.common import constants from manila import exception -from manila.i18n import _ +from manila.i18n import _, _LI from manila.share import driver from manila.share.drivers.helpers import NFSHelper from manila.share import share_types @@ -564,6 +564,178 @@ class GPFSShareDriver(driver.ExecuteMixin, driver.GaneshaMixin, LOG.error(msg) raise exception.InvalidParameterValue(err=msg) + def _is_share_valid(self, fsdev, location): + try: + out, __ = self._gpfs_execute('mmlsfileset', fsdev, '-J', + location, '-L', '-Y') + except exception.ProcessExecutionError: + msg = (_('Given share path %(share_path)s does not exist at ' + 'mount point %(mount_point)s.') + % {'share_path': location, 'mount_point': fsdev}) + LOG.exception(msg) + raise exception.ManageInvalidShare(reason=msg) + + lines = out.splitlines() + try: + validation_token = lines[0].split(':').index('allocInodes') + alloc_inodes = lines[1].split(':')[validation_token] + except (IndexError, ValueError): + msg = (_('Failed to check share at %s.') % location) + LOG.exception(msg) + raise exception.GPFSException(msg) + + return alloc_inodes != '0' + + def _get_share_name(self, fsdev, location): + try: + out, __ = self._gpfs_execute('mmlsfileset', fsdev, '-J', + location, '-L', '-Y') + except exception.ProcessExecutionError: + msg = (_('Given share path %(share_path)s does not exist at ' + 'mount point %(mount_point)s.') + % {'share_path': location, 'mount_point': fsdev}) + LOG.exception(msg) + raise exception.ManageInvalidShare(reason=msg) + + lines = out.splitlines() + try: + validation_token = lines[0].split(':').index('filesetName') + share_name = lines[1].split(':')[validation_token] + except (IndexError, ValueError): + msg = (_('Failed to check share at %s.') % location) + LOG.exception(msg) + raise exception.GPFSException(msg) + + return share_name + + def _manage_existing(self, fsdev, share, old_share_name): + new_share_name = share['name'] + new_export_location = self._local_path(new_share_name) + try: + self._gpfs_execute('mmunlinkfileset', fsdev, old_share_name, '-f') + except exception.ProcessExecutionError: + msg = _('Failed to unlink fileset for share %s.') % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + LOG.debug('Unlinked the fileset of share %s.', old_share_name) + + try: + self._gpfs_execute('mmchfileset', fsdev, old_share_name, + '-j', new_share_name) + except exception.ProcessExecutionError: + msg = _('Failed to rename fileset for share %s.') % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + LOG.debug('Renamed the fileset from %(old_share)s to %(new_share)s.', + {'old_share': old_share_name, 'new_share': new_share_name}) + + try: + self._gpfs_execute('mmlinkfileset', fsdev, new_share_name, '-J', + new_export_location) + except exception.ProcessExecutionError: + msg = _('Failed to link fileset for the share %s.' + ) % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + LOG.debug('Linked the fileset of share %(share_name)s at location ' + '%(export_location)s.', + {'share_name': new_share_name, + 'export_location': new_export_location}) + + try: + self._gpfs_execute('chmod', '777', new_export_location) + except exception.ProcessExecutionError: + msg = _('Failed to set permissions for share %s.') % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + LOG.debug('Changed the permission of share %s.', new_share_name) + + try: + out, __ = self._gpfs_execute('mmlsquota', '-j', new_share_name, + '-Y', fsdev) + except exception.ProcessExecutionError: + msg = _('Failed to check size for share %s.') % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + + lines = out.splitlines() + try: + quota_limit = lines[0].split(':').index('blockLimit') + quota_status = lines[1].split(':')[quota_limit] + except (IndexError, ValueError): + msg = _('Failed to check quota for share %s.') % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + + share_size = int(quota_status) + # Note: since share_size returns integer value in KB, + # we are checking whether share is less than 1GiB. + # (units.Mi * KB = 1GB) + if share_size < units.Mi: + try: + self._gpfs_execute('mmsetquota', fsdev + ':' + new_share_name, + '--block', '0:1G') + except exception.ProcessExecutionError: + msg = _('Failed to set quota for share %s.') % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + LOG.info(_LI('Existing share %(shr)s has size %(size)s KB ' + 'which is below 1GiB, so extended it to 1GiB.') % + {'shr': new_share_name, 'size': share_size}) + share_size = 1 + else: + orig_share_size = share_size + share_size = int(math.ceil(float(share_size) / units.Mi)) + if orig_share_size != share_size * units.Mi: + try: + self._gpfs_execute('mmsetquota', fsdev + ':' + + new_share_name, '--block', '0:' + + str(share_size) + 'G') + except exception.ProcessExecutionError: + msg = _('Failed to set quota for share %s.' + ) % new_share_name + LOG.exception(msg) + raise exception.GPFSException(msg) + + new_export_location = self._get_helper(share).create_export( + new_export_location) + return share_size, new_export_location + + def manage_existing(self, share, driver_options): + + old_export = share['export_location'].split(':') + try: + ces_ip = old_export[0] + old_export_location = old_export[1] + except IndexError: + msg = _('Incorrect export path. Expected format: ' + 'IP:/gpfs_mount_point_base/share_id.') + LOG.exception(msg) + raise exception.ShareBackendException(msg=msg) + + if ces_ip not in self.configuration.gpfs_nfs_server_list: + msg = _('The CES IP %s is not present in the ' + 'configuration option "gpfs_nfs_server_list".') % ces_ip + raise exception.ShareBackendException(msg=msg) + + fsdev = self._get_gpfs_device() + if not self._is_share_valid(fsdev, old_export_location): + err_msg = _('Given share path %s does not have a valid ' + 'share.') % old_export_location + raise exception.ManageInvalidShare(reason=err_msg) + + share_name = self._get_share_name(fsdev, old_export_location) + + out = self._get_helper(share)._has_client_access(old_export_location) + if out: + err_msg = _('Clients have access to %s share currently. Evict any ' + 'clients before trying again.') % share_name + raise exception.ManageInvalidShare(reason=err_msg) + + share_size, new_export_location = self._manage_existing( + fsdev, share, share_name) + return {"size": share_size, "export_locations": new_export_location} + def _update_share_stats(self): """Retrieve stats info from share volume group.""" @@ -679,6 +851,23 @@ class KNFSHelper(NASHelperBase): LOG.error(msg) raise exception.GPFSException(msg) + def _has_client_access(self, local_path, access_to=None): + try: + out, __ = self._execute('exportfs', run_as_root=True) + except exception.ProcessExecutionError: + msg = _('Failed to check exports on the systems.') + LOG.exception(msg) + raise exception.GPFSException(msg) + + if access_to: + if (re.search(re.escape(local_path) + '[\s\n]*' + + re.escape(access_to), out)): + return True + else: + if re.findall(local_path + '\\b', ''.join(out)): + return True + return False + def _publish_access(self, *cmd, **kwargs): check_exit_code = kwargs.get('check_exit_code', True) @@ -739,19 +928,9 @@ class KNFSHelper(NASHelperBase): raise exception.InvalidShareAccess(reason='Only ip access type ' 'supported.') - # check if present in export - try: - out, __ = self._execute('exportfs', run_as_root=True) - except exception.ProcessExecutionError as e: - msg = (_('Failed to check exports on the systems. ' - ' Error: %s.') % e) - LOG.error(msg) - raise exception.GPFSException(msg) + out = self._has_client_access(local_path, access['access_to']) - out = re.search(re.escape(local_path) + '[\s\n]*' - + re.escape(access['access_to']), out) - - if out is not None: + if out: access_type = access['access_type'] access_to = access['access_to'] raise exception.ShareAccessExists(access_type=access_type, diff --git a/manila/tests/share/drivers/ibm/test_gpfs.py b/manila/tests/share/drivers/ibm/test_gpfs.py index 0c4b1f429c..728a1d3155 100644 --- a/manila/tests/share/drivers/ibm/test_gpfs.py +++ b/manila/tests/share/drivers/ibm/test_gpfs.py @@ -45,6 +45,7 @@ class GPFSShareDriverTestCase(test.TestCase): self._helper_fake = mock.Mock() CONF.set_default('driver_handles_share_servers', False) + CONF.set_default('share_backend_name', 'GPFS') self.fake_conf = config.Configuration(None) self._driver = gpfs.GPFSShareDriver(execute=self._gpfs_execute, configuration=self.fake_conf) @@ -55,6 +56,7 @@ class GPFSShareDriverTestCase(test.TestCase): self.fakedev = "/dev/gpfs0" self.fakefspath = "/gpfs0" self.fakesharepath = "/gpfs0/share-fakeid" + self.fakeexistingshare = "existingshare" self.fakesnapshotpath = "/gpfs0/.snapshots/snapshot-fakesnapshotid" self.fake_ces_exports = """ @@ -74,7 +76,8 @@ mmcesnfslsexport:nfsexports:HEADER:version:reserved:reserved:Path:Delegations:Cl self._driver._helpers = { 'KNFS': self._helper_fake } - self.share = fake_share.fake_share(share_proto='NFS') + self.share = fake_share.fake_share(share_proto='NFS', + host='fakehost@fakehost#GPFS') self.server = { 'backend_details': { 'ip': '1.2.3.4', @@ -86,7 +89,8 @@ mmcesnfslsexport:nfsexports:HEADER:version:reserved:reserved:Path:Delegations:Cl self.local_ip = "192.11.22.1" self.remote_ip = "192.11.22.2" self.remote_ip2 = "2.2.2.2" - gpfs_nfs_server_list = [self.remote_ip, self.local_ip, self.remote_ip2] + gpfs_nfs_server_list = [self.remote_ip, self.local_ip, self.remote_ip2, + "fake_location"] self._knfs_helper.configuration.gpfs_nfs_server_list = \ gpfs_nfs_server_list self._ces_helper.configuration.gpfs_nfs_server_list = \ @@ -671,6 +675,333 @@ mmcesnfslsexport:nfsexports:HEADER:version:reserved:reserved:Path:Delegations:Cl 'rsync', '-rp', self.fakesnapshotpath + '/', self.fakesharepath ) + @ddt.data("mmlsfileset::allocInodes:\nmmlsfileset::100096:", + "mmlsfileset::allocInodes:\nmmlsfileset::0:") + def test__is_share_valid_with_quota(self, fakeout): + self._driver._gpfs_execute = mock.Mock(return_value=(fakeout, '')) + + result = self._driver._is_share_valid(self.fakedev, self.fakesharepath) + + self._driver._gpfs_execute.assert_called_once_with( + 'mmlsfileset', self.fakedev, '-J', self.fakesharepath, '-L', '-Y') + if fakeout == "mmlsfileset::allocInodes:\nmmlsfileset::100096:": + self.assertTrue(result) + else: + self.assertFalse(result) + + def test__is_share_valid_exception(self): + self._driver._gpfs_execute = mock.Mock( + side_effect=exception.ProcessExecutionError) + + self.assertRaises(exception.ManageInvalidShare, + self._driver._is_share_valid, self.fakedev, + self.fakesharepath) + + self._driver._gpfs_execute.assert_called_once_with( + 'mmlsfileset', self.fakedev, '-J', self.fakesharepath, '-L', '-Y') + + def test__is_share_valid_no_share_exist_exception(self): + fakeout = "mmlsfileset::allocInodes:" + self._driver._gpfs_execute = mock.Mock(return_value=(fakeout, '')) + + self.assertRaises(exception.GPFSException, + self._driver._is_share_valid, self.fakedev, + self.fakesharepath) + + self._driver._gpfs_execute.assert_called_once_with( + 'mmlsfileset', self.fakedev, '-J', self.fakesharepath, '-L', '-Y') + + def test__get_share_name(self): + fakeout = "mmlsfileset::filesetName:\nmmlsfileset::existingshare:" + self._driver._gpfs_execute = mock.Mock(return_value=(fakeout, '')) + + result = self._driver._get_share_name(self.fakedev, self.fakesharepath) + + self.assertEqual('existingshare', result) + + def test__get_share_name_exception(self): + self._driver._gpfs_execute = mock.Mock( + side_effect=exception.ProcessExecutionError) + + self.assertRaises(exception.ManageInvalidShare, + self._driver._get_share_name, self.fakedev, + self.fakesharepath) + + self._driver._gpfs_execute.assert_called_once_with( + 'mmlsfileset', self.fakedev, '-J', self.fakesharepath, '-L', '-Y') + + def test__get_share_name_no_share_exist_exception(self): + fakeout = "mmlsfileset::filesetName:" + self._driver._gpfs_execute = mock.Mock(return_value=(fakeout, '')) + + self.assertRaises(exception.GPFSException, + self._driver._get_share_name, self.fakedev, + self.fakesharepath) + + self._driver._gpfs_execute.assert_called_once_with( + 'mmlsfileset', self.fakedev, '-J', self.fakesharepath, '-L', '-Y') + + @ddt.data("mmlsquota::blockLimit:\nmmlsquota::1048577", + "mmlsquota::blockLimit:\nmmlsquota::1048576", + "mmlsquota::blockLimit:\nmmlsquota::0") + def test__manage_existing(self, fakeout): + self._driver._gpfs_execute = mock.Mock(return_value=(fakeout, '')) + self._helper_fake.create_export.return_value = 'fakelocation' + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + + actual_size, actual_path = self._driver._manage_existing( + self.fakedev, self.share, self.fakeexistingshare) + + self._driver._gpfs_execute.assert_any_call('mmunlinkfileset', + self.fakedev, + self.fakeexistingshare, + '-f') + self._driver._gpfs_execute.assert_any_call('mmchfileset', + self.fakedev, + self.fakeexistingshare, + '-j', self.share['name']) + self._driver._gpfs_execute.assert_any_call('mmlinkfileset', + self.fakedev, + self.share['name'], + '-J', self.fakesharepath) + self._driver._gpfs_execute.assert_any_call('chmod', + '777', + self.fakesharepath) + if fakeout == "mmlsquota::blockLimit:\nmmlsquota::1048577": + self._driver._gpfs_execute.assert_called_with('mmsetquota', + self.fakedev + ':' + + self.share['name'], + '--block', + '0:2G') + self.assertEqual(2, actual_size) + self.assertEqual('fakelocation', actual_path) + elif fakeout == "mmlsquota::blockLimit:\nmmlsquota::0": + self._driver._gpfs_execute.assert_called_with('mmsetquota', + self.fakedev + ':' + + self.share['name'], + '--block', + '0:1G') + self.assertEqual(1, actual_size) + self.assertEqual('fakelocation', actual_path) + else: + self.assertEqual(1, actual_size) + self.assertEqual('fakelocation', actual_path) + + def test__manage_existing_fileset_unlink_exception(self): + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self._driver._gpfs_execute = mock.Mock( + side_effect=exception.ProcessExecutionError) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_called_once_with(self.share['name']) + self._driver._gpfs_execute.assert_called_once_with( + 'mmunlinkfileset', self.fakedev, self.fakeexistingshare, '-f') + + def test__manage_existing_fileset_creation_exception(self): + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self.mock_object(self._driver, '_gpfs_execute', mock.Mock( + side_effect=['', exception.ProcessExecutionError])) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_any_call(self.share['name']) + self._driver._gpfs_execute.assert_has_calls([ + mock.call('mmunlinkfileset', self.fakedev, self.fakeexistingshare, + '-f'), + mock.call('mmchfileset', self.fakedev, self.fakeexistingshare, + '-j', self.share['name'])]) + + def test__manage_existing_fileset_relink_exception(self): + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self.mock_object(self._driver, '_gpfs_execute', mock.Mock( + side_effect=['', '', exception.ProcessExecutionError])) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_any_call(self.share['name']) + self._driver._gpfs_execute.assert_has_calls([ + mock.call('mmunlinkfileset', self.fakedev, self.fakeexistingshare, + '-f'), + mock.call('mmchfileset', self.fakedev, self.fakeexistingshare, + '-j', self.share['name']), + mock.call('mmlinkfileset', self.fakedev, self.share['name'], '-J', + self.fakesharepath)]) + + def test__manage_existing_permission_change_exception(self): + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self.mock_object(self._driver, '_gpfs_execute', mock.Mock( + side_effect=['', '', '', exception.ProcessExecutionError])) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_any_call(self.share['name']) + self._driver._gpfs_execute.assert_has_calls([ + mock.call('mmunlinkfileset', self.fakedev, self.fakeexistingshare, + '-f'), + mock.call('mmchfileset', self.fakedev, self.fakeexistingshare, + '-j', self.share['name']), + mock.call('mmlinkfileset', self.fakedev, self.share['name'], '-J', + self.fakesharepath), + mock.call('chmod', '777', self.fakesharepath)]) + + def test__manage_existing_checking_quota_of_fileset_exception(self): + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self.mock_object(self._driver, '_gpfs_execute', mock.Mock( + side_effect=['', '', '', '', exception.ProcessExecutionError])) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_any_call(self.share['name']) + self._driver._gpfs_execute.assert_has_calls([ + mock.call('mmunlinkfileset', self.fakedev, self.fakeexistingshare, + '-f'), + mock.call('mmchfileset', self.fakedev, self.fakeexistingshare, + '-j', self.share['name']), + mock.call('mmlinkfileset', self.fakedev, self.share['name'], '-J', + self.fakesharepath), + mock.call('chmod', '777', self.fakesharepath), + mock.call('mmlsquota', '-j', self.share['name'], '-Y', + self.fakedev)]) + + def test__manage_existing_unable_to_get_quota_of_fileset_exception(self): + fakeout = "mmlsquota::blockLimit:" + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self._driver._gpfs_execute = mock.Mock(return_value=(fakeout, '')) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_any_call(self.share['name']) + self._driver._gpfs_execute.assert_any_call('mmunlinkfileset', + self.fakedev, + self.fakeexistingshare, + '-f') + self._driver._gpfs_execute.assert_any_call('mmchfileset', + self.fakedev, + self.fakeexistingshare, + '-j', self.share['name']) + self._driver._gpfs_execute.assert_any_call('mmlinkfileset', + self.fakedev, + self.share['name'], + '-J', self.fakesharepath) + self._driver._gpfs_execute.assert_any_call('chmod', + '777', + self.fakesharepath) + self._driver._gpfs_execute.assert_called_with( + 'mmlsquota', '-j', self.share['name'], '-Y', self.fakedev) + + def test__manage_existing_set_quota_of_fileset_less_than_1G_exception( + self): + sizestr = '1G' + mock_out = "mmlsquota::blockLimit:\nmmlsquota::0:", None + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self.mock_object(self._driver, '_gpfs_execute', mock.Mock( + side_effect=['', '', '', '', mock_out, + exception.ProcessExecutionError])) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_any_call(self.share['name']) + self._driver._gpfs_execute.assert_has_calls([ + mock.call('mmunlinkfileset', self.fakedev, self.fakeexistingshare, + '-f'), + mock.call('mmchfileset', self.fakedev, self.fakeexistingshare, + '-j', self.share['name']), + mock.call('mmlinkfileset', self.fakedev, self.share['name'], '-J', + self.fakesharepath), + mock.call('chmod', '777', self.fakesharepath), + mock.call('mmlsquota', '-j', self.share['name'], '-Y', + self.fakedev), + mock.call('mmsetquota', self.fakedev + ':' + self.share['name'], + '--block', '0:' + sizestr)]) + + def test__manage_existing_set_quota_of_fileset_grater_than_1G_exception( + self): + sizestr = '2G' + mock_out = "mmlsquota::blockLimit:\nmmlsquota::1048577:", None + self._driver._local_path = mock.Mock(return_value=self.fakesharepath) + self.mock_object(self._driver, '_gpfs_execute', mock.Mock( + side_effect=['', '', '', '', mock_out, + exception.ProcessExecutionError])) + + self.assertRaises(exception.GPFSException, + self._driver._manage_existing, self.fakedev, + self.share, self.fakeexistingshare) + + self._driver._local_path.assert_any_call(self.share['name']) + self._driver._gpfs_execute.assert_has_calls([ + mock.call('mmunlinkfileset', self.fakedev, self.fakeexistingshare, + '-f'), + mock.call('mmchfileset', self.fakedev, self.fakeexistingshare, + '-j', self.share['name']), + mock.call('mmlinkfileset', self.fakedev, self.share['name'], '-J', + self.fakesharepath), + mock.call('chmod', '777', self.fakesharepath), + mock.call('mmlsquota', '-j', self.share['name'], '-Y', + self.fakedev), + mock.call('mmsetquota', self.fakedev + ':' + self.share['name'], + '--block', '0:' + sizestr)]) + + def test_manage_existing(self): + self._driver._manage_existing = mock.Mock(return_value=('1', + 'fakelocation')) + self._driver._get_gpfs_device = mock.Mock(return_value=self.fakedev) + self._driver._is_share_valid = mock.Mock(return_value=True) + self._driver._get_share_name = mock.Mock(return_value=self. + fakeexistingshare) + self._helper_fake._has_client_access = mock.Mock(return_value=[]) + + result = self._driver.manage_existing(self.share, {}) + + self.assertEqual('1', result['size']) + self.assertEqual('fakelocation', result['export_locations']) + + def test_manage_existing_incorrect_path_exception(self): + share = fake_share.fake_share(export_location="wrong_ip::wrong_path") + + self.assertRaises(exception.ShareBackendException, + self._driver.manage_existing, share, {}) + + def test_manage_existing_incorrect_ip_exception(self): + share = fake_share.fake_share(export_location="wrong_ip:wrong_path") + + self.assertRaises(exception.ShareBackendException, + self._driver.manage_existing, share, {}) + + def test__manage_existing_invalid_export_exception(self): + share = fake_share.fake_share(export_location="wrong_ip/wrong_path") + + self.assertRaises(exception.ShareBackendException, + self._driver.manage_existing, share, {}) + + @ddt.data(True, False) + def test_manage_existing_invalid_share_exception(self, valid_share): + self._driver._get_gpfs_device = mock.Mock(return_value=self.fakedev) + self._driver._is_share_valid = mock.Mock(return_value=valid_share) + if valid_share: + self._driver._get_share_name = mock.Mock(return_value=self. + fakeexistingshare) + self._helper_fake._has_client_access = mock.Mock() + else: + self.assertFalse(self._helper_fake._has_client_access.called) + + self.assertRaises(exception.ManageInvalidShare, + self._driver.manage_existing, self.share, {}) + def test__gpfs_local_execute(self): self.mock_object(utils, 'execute', mock.Mock(return_value=True)) cmd = "testcmd" @@ -730,6 +1061,28 @@ mmcesnfslsexport:nfsexports:HEADER:version:reserved:reserved:Path:Delegations:Cl self._knfs_helper.get_export_options, share, access, 'KNFS', options_not_allowed) + @ddt.data(("/gpfs0/share-fakeid\t10.0.0.1", None), + ("", None), + ("/gpfs0/share-fakeid\t10.0.0.1", "10.0.0.1"), + ("/gpfs0/share-fakeid\t10.0.0.1", "10.0.0.2")) + @ddt.unpack + def test_knfs__has_client_access(self, mock_out, access_to): + self._knfs_helper._execute = mock.Mock(return_value=[mock_out, 0]) + + result = self._knfs_helper._has_client_access(self.fakesharepath, + access_to) + + self._ces_helper._execute.assert_called_once_with('exportfs', + check_exit_code=True, + run_as_root=True) + if mock_out == "/gpfs0/share-fakeid\t10.0.0.1": + if access_to in (None, "10.0.0.1"): + self.assertTrue(result) + else: + self.assertFalse(result) + else: + self.assertFalse(result) + def test_knfs_allow_access(self): self._knfs_helper._execute = mock.Mock( return_value=['/fs0 ', 0] @@ -1041,7 +1394,8 @@ mmcesnfslsexport:nfsexports:HEADER:version:reserved:reserved:Path:Delegations:Cl access = self.access local_path = self.fakesharepath - self._ces_helper.allow_access(local_path, self.share, access) + self._ces_helper.allow_access(self.fakesharepath, self.share, + self.access) self._ces_helper._execute.assert_has_calls([ mock.call('mmnfs', 'export', 'list', '-n', local_path, '-Y'), diff --git a/releasenotes/notes/ibm-gpfs-manage-support-c110120c350728e3.yaml b/releasenotes/notes/ibm-gpfs-manage-support-c110120c350728e3.yaml new file mode 100644 index 0000000000..086cd07434 --- /dev/null +++ b/releasenotes/notes/ibm-gpfs-manage-support-c110120c350728e3.yaml @@ -0,0 +1,8 @@ +--- +features: + - Added manila manage/unmanage feature support for + GPFS driver. + The existing fileset should be an independent fileset + and should not have any NFS export over the fileset + path. With this prerequisite existing GPFS filesets + can be brought under Manila management.