From 4696390be43dcac2598c99d4f0ea3d6cc2f8f75d Mon Sep 17 00:00:00 2001 From: Bill Owen Date: Mon, 5 Aug 2013 11:32:27 -0700 Subject: [PATCH] GPFS Verify min release level for mmclone command The gpfs driver makes heavy use of mmclone command. This command was added fairly recently. Verify that the GPFS cluster is operating at a level that supports mmclone at startup during prerequisite checking in check_for_setup_error. Need to verify: 1. That gpfs cluster is at required level 2. That filesystem(s) are also operating at required level. Change-Id: Ie71d0b814d77b544e1a456d110882c1a02a5b2e5 Fixes: bug #1204266 --- cinder/tests/test_gpfs.py | 48 +++++++++++++++++++++++++++ cinder/volume/drivers/gpfs.py | 49 ++++++++++++++++++++++++++++ etc/cinder/rootwrap.d/volume.filters | 2 ++ 3 files changed, 99 insertions(+) diff --git a/cinder/tests/test_gpfs.py b/cinder/tests/test_gpfs.py index f8ab970f6..171649e8b 100644 --- a/cinder/tests/test_gpfs.py +++ b/cinder/tests/test_gpfs.py @@ -342,6 +342,10 @@ class GPFSDriverTestCase(test.TestCase): self._fake_gpfs_get_state_active) self.stubs.Set(GPFSDriver, '_is_gpfs_path', self._fake_is_gpfs_path) + self.stubs.Set(GPFSDriver, '_get_gpfs_cluster_release_level', + self._fake_gpfs_compatible_cluster_release_level) + self.stubs.Set(GPFSDriver, '_get_gpfs_filesystem_release_level', + self._fake_gpfs_compatible_filesystem_release_level) self.driver.check_for_setup_error() def test_check_for_setup_error_gpfs_not_active(self): @@ -357,6 +361,32 @@ class GPFSDriverTestCase(test.TestCase): self._fake_gpfs_get_state_active) self.stubs.Set(GPFSDriver, '_is_gpfs_path', self._fake_is_not_gpfs_path) + self.stubs.Set(GPFSDriver, '_get_gpfs_cluster_release_level', + self._fake_gpfs_compatible_cluster_release_level) + self.stubs.Set(GPFSDriver, '_get_gpfs_filesystem_release_level', + self._fake_gpfs_compatible_filesystem_release_level) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.check_for_setup_error) + + def test_check_for_setup_error_incompatible_cluster_version(self): + self.stubs.Set(GPFSDriver, '_get_gpfs_state', + self._fake_gpfs_get_state_active) + self.stubs.Set(GPFSDriver, '_is_gpfs_path', + self._fake_is_gpfs_path) + self.stubs.Set(GPFSDriver, '_get_gpfs_cluster_release_level', + self._fake_gpfs_incompatible_cluster_release_level) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.check_for_setup_error) + + def test_check_for_setup_error_incompatible_filesystem_version(self): + self.stubs.Set(GPFSDriver, '_get_gpfs_state', + self._fake_gpfs_get_state_active) + self.stubs.Set(GPFSDriver, '_is_gpfs_path', + self._fake_is_gpfs_path) + self.stubs.Set(GPFSDriver, '_get_gpfs_cluster_release_level', + self._fake_gpfs_compatible_cluster_release_level) + self.stubs.Set(GPFSDriver, '_get_gpfs_filesystem_release_level', + self._fake_gpfs_incompatible_filesystem_release_level) self.assertRaises(exception.VolumeBackendAPIException, self.driver.check_for_setup_error) @@ -405,6 +435,24 @@ class GPFSDriverTestCase(test.TestCase): '1:quorum node:(undefined):') return inactive_txt + def _fake_gpfs_compatible_cluster_release_level(self): + release = 1400 + return release + + def _fake_gpfs_incompatible_cluster_release_level(self): + release = 1105 + return release + + def _fake_gpfs_compatible_filesystem_release_level(self, path=None): + release = 1400 + fs = '/dev/gpfs' + return fs, release + + def _fake_gpfs_incompatible_filesystem_release_level(self, path=None): + release = 1105 + fs = '/dev/gpfs' + return fs, release + def _fake_is_gpfs_path(self, path): pass diff --git a/cinder/volume/drivers/gpfs.py b/cinder/volume/drivers/gpfs.py index ecaf572f8..fe4577ce4 100644 --- a/cinder/volume/drivers/gpfs.py +++ b/cinder/volume/drivers/gpfs.py @@ -32,6 +32,8 @@ from cinder import units from cinder.volume import driver VERSION = 1.0 +GPFS_CLONE_MIN_RELEASE = 1200 + LOG = logging.getLogger(__name__) gpfs_opts = [ @@ -91,6 +93,32 @@ class GPFSDriver(driver.VolumeDriver): gpfs_state) raise exception.VolumeBackendAPIException(data=exception_message) + def _get_filesystem_from_path(self, path): + (out, _) = self._execute('df', path, run_as_root=True) + lines = out.splitlines() + fs = lines[1].split()[0] + return fs + + def _get_gpfs_filesystem_release_level(self, path): + fs = self._get_filesystem_from_path(path) + (out, _) = self._execute('mmlsfs', fs, '-V', '-Y', + run_as_root=True) + lines = out.splitlines() + value_token = lines[0].split(':').index('data') + fs_release_level_str = lines[1].split(':')[value_token] + # at this point, release string looks like "13.23 (3.5.0.7)" + # extract first token and convert to whole number value + fs_release_level = int(float(fs_release_level_str.split()[0]) * 100) + return fs, fs_release_level + + def _get_gpfs_cluster_release_level(self): + (out, _) = self._execute('mmlsconfig', 'minreleaseLeveldaemon', '-Y', + run_as_root=True) + lines = out.splitlines() + value_token = lines[0].split(':').index('value') + min_release_level = lines[1].split(':')[value_token] + return int(min_release_level) + def _is_gpfs_path(self, directory): self._execute('mmlsattr', directory, run_as_root=True) @@ -131,6 +159,16 @@ class GPFSDriver(driver.VolumeDriver): LOG.warn(msg) raise exception.VolumeBackendAPIException(data=msg) + _gpfs_cluster_release_level = self._get_gpfs_cluster_release_level() + if not _gpfs_cluster_release_level >= GPFS_CLONE_MIN_RELEASE: + msg = (_('Downlevel GPFS Cluster Detected. GPFS Clone feature ' + 'not enabled in cluster daemon level %(cur)s - must ' + 'be at least at level %(min)s.') % + {'cur': _gpfs_cluster_release_level, + 'min': GPFS_CLONE_MIN_RELEASE}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + for directory in [self.configuration.gpfs_mount_point_base, self.configuration.gpfs_images_dir]: if directory is None: @@ -155,6 +193,17 @@ class GPFSDriver(driver.VolumeDriver): LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) + fs, fslevel = self._get_gpfs_filesystem_release_level(directory) + if not fslevel >= GPFS_CLONE_MIN_RELEASE: + msg = (_('The GPFS filesystem %(fs)s is not at the required ' + 'release level. Current level is %(cur)s, must be ' + 'at least %(min)s.') % + {'fs': fs, + 'cur': fslevel, + 'min': GPFS_CLONE_MIN_RELEASE}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + def _create_sparse_file(self, path, size): """Creates file with 0 disk usage.""" diff --git a/etc/cinder/rootwrap.d/volume.filters b/etc/cinder/rootwrap.d/volume.filters index 53c7a8bfa..b095475b9 100644 --- a/etc/cinder/rootwrap.d/volume.filters +++ b/etc/cinder/rootwrap.d/volume.filters @@ -73,5 +73,7 @@ blockdev: CommandFilter, blockdev, root mmgetstate: CommandFilter, /usr/lpp/mmfs/bin/mmgetstate, root mmclone: CommandFilter, /usr/lpp/mmfs/bin/mmclone, root mmlsattr: CommandFilter, /usr/lpp/mmfs/bin/mmlsattr, root +mmlsconfig: CommandFilter, /usr/lpp/mmfs/bin/mmlsconfig, root +mmlsfs: CommandFilter, /usr/lpp/mmfs/bin/mmlsfs, root find: CommandFilter, find, root mkfs: CommandFilter, mkfs, root -- 2.45.2