From 45b9bf616158588360d5bab34360e94a734a72fd Mon Sep 17 00:00:00 2001 From: Sasikanth Date: Thu, 25 Jun 2015 16:02:29 +0530 Subject: [PATCH] GPFS volume encryption-at-rest support This patch extends the capability of gpfs driver to support volume encryption at rest using GPFS Native Encryption feature (https://ibm.biz/BdXPBm). This includes: 1. Verification of configured gpfs backend to support encryption at rest. 2. Driver exposes gpfs_encryption_rest capability via volume type convention. Usage - Cloud admin creates a volume type "encrypted" with extra-specs gpfs_encryption_rest=True Every volume created using type "encrypted", the volumes will be encrypted at rest. DocImpact Needs an update in gpfs driver documentation, as this patch introduces encrypted backend support. Change-Id: I83949ca51668834ecbce52e90527cf06f95bd001 Implements: blueprint gpfs-volume-encryption-rest --- cinder/tests/unit/test_gpfs.py | 55 ++++++++++++++++++++++++++++++- cinder/volume/drivers/ibm/gpfs.py | 33 ++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/cinder/tests/unit/test_gpfs.py b/cinder/tests/unit/test_gpfs.py index 20cc33a37..f55631b53 100644 --- a/cinder/tests/unit/test_gpfs.py +++ b/cinder/tests/unit/test_gpfs.py @@ -69,6 +69,7 @@ class GPFSDriverTestCase(test.TestCase): self.driver._cluster_id = '123456' self.driver._gpfs_device = '/dev/gpfs' self.driver._storage_pool = 'system' + self.driver._encryption_state = 'yes' self.flags(volume_driver=self.driver_name, gpfs_mount_point_base=self.volumes_path) @@ -410,6 +411,10 @@ class GPFSDriverTestCase(test.TestCase): host = {'host': 'foo', 'capabilities': cap} self.assertEqual('testpath', self.driver._can_migrate_locally(host)) + @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.' + '_get_gpfs_encryption_status') + @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.' + '_get_gpfs_cluster_release_level') @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._verify_gpfs_pool') @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.' '_get_filesystem_from_path') @@ -420,11 +425,36 @@ class GPFSDriverTestCase(test.TestCase): mock_exec, mock_get_gpfs_cluster_id, mock_get_filesystem_from_path, - mock_verify_gpfs_pool): + mock_verify_gpfs_pool, + mock_get_gpfs_fs_rel_lev, + mock_verify_encryption_state): + ctxt = self.context + mock_get_gpfs_cluster_id.return_value = self.driver._cluster_id + mock_get_filesystem_from_path.return_value = '/dev/gpfs' + mock_verify_gpfs_pool.return_value = True + mock_get_gpfs_fs_rel_lev.return_value = 1405 + mock_verify_encryption_state.return_value = 'Yes' + self.driver.do_setup(ctxt) + + @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.' + '_get_gpfs_cluster_release_level') + @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._verify_gpfs_pool') + @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.' + '_get_filesystem_from_path') + @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.' + '_get_gpfs_cluster_id') + @mock.patch('cinder.utils.execute') + def test_do_setup_no_encryption(self, + mock_exec, + mock_get_gpfs_cluster_id, + mock_get_filesystem_from_path, + mock_verify_gpfs_pool, + mock_get_gpfs_fs_rel_lev): ctxt = self.context mock_get_gpfs_cluster_id.return_value = self.driver._cluster_id mock_get_filesystem_from_path.return_value = '/dev/gpfs' mock_verify_gpfs_pool.return_value = True + mock_get_gpfs_fs_rel_lev.return_value = 1403 self.driver.do_setup(ctxt) @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver._verify_gpfs_pool') @@ -1108,9 +1138,32 @@ class GPFSDriverTestCase(test.TestCase): stats = self.driver.get_volume_stats() self.assertEqual('GPFS', stats['volume_backend_name']) self.assertEqual('file', stats['storage_protocol']) + self.assertTrue(stats['gpfs_encryption_rest']) stats = self.driver.get_volume_stats(True) self.assertEqual('GPFS', stats['volume_backend_name']) self.assertEqual('file', stats['storage_protocol']) + self.assertTrue(stats['gpfs_encryption_rest']) + + @mock.patch('cinder.utils.execute') + def test_get_gpfs_encryption_status_true(self, mock_exec): + mock_exec.return_value = ('mmlsfs::HEADER:version:reserved:reserved:' + 'deviceName:fieldName:data:remarks:\n' + 'mmlsfs::0:1:::gpfs:encryption:Yes:', '') + self.assertEqual('Yes', self.driver._get_gpfs_encryption_status()) + + @mock.patch('cinder.utils.execute') + def test_get_gpfs_encryption_status_false(self, mock_exec): + mock_exec.return_value = ('mmlsfs::HEADER:version:reserved:reserved:' + 'deviceName:fieldName:data:remarks:\n' + 'mmlsfs::0:1:::gpfs:encryption:No:', '') + self.assertEqual('No', self.driver._get_gpfs_encryption_status()) + + @mock.patch('cinder.utils.execute') + def test_get_gpfs_encryption_status_fail(self, mock_exec): + mock_exec.side_effect = ( + processutils.ProcessExecutionError(stdout='test', stderr='test')) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver._get_gpfs_encryption_status) @mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.' '_update_volume_stats') diff --git a/cinder/volume/drivers/ibm/gpfs.py b/cinder/volume/drivers/ibm/gpfs.py index 1849c30cc..62a6c997f 100644 --- a/cinder/volume/drivers/ibm/gpfs.py +++ b/cinder/volume/drivers/ibm/gpfs.py @@ -39,6 +39,7 @@ from cinder.volume.drivers import remotefs from cinder.volume.drivers.san import san GPFS_CLONE_MIN_RELEASE = 1200 +GPFS_ENC_MIN_RELEASE = 1404 MIGRATION_ALLOWED_DEST_TYPE = ['GPFSDriver', 'GPFSNFSDriver'] LOG = logging.getLogger(__name__) @@ -119,9 +120,10 @@ class GPFSDriver(driver.ConsistencyGroupVD, driver.ExtendVD, 1.1.0 - Add volume retype, refactor volume migration 1.2.0 - Add consistency group support 1.3.0 - Add NFS based GPFS storage backend support + 1.3.1 - Add GPFS native encryption (encryption of data at rest) support """ - VERSION = "1.3.0" + VERSION = "1.3.1" def __init__(self, *args, **kwargs): super(GPFSDriver, self).__init__(*args, **kwargs) @@ -355,6 +357,17 @@ class GPFSDriver(driver.ConsistencyGroupVD, driver.ExtendVD, LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) + _gpfs_cluster_release_level = self._get_gpfs_cluster_release_level() + if _gpfs_cluster_release_level >= GPFS_ENC_MIN_RELEASE: + self._encryption_state = self._get_gpfs_encryption_status() + else: + LOG.info(_LI('Downlevel GPFS Cluster Detected. GPFS ' + 'encryption-at-rest feature not enabled in cluster ' + 'daemon level %(cur)s - must be at least at ' + 'level %(min)s.'), + {'cur': _gpfs_cluster_release_level, + 'min': GPFS_ENC_MIN_RELEASE}) + def check_for_setup_error(self): """Returns an error if prerequisites aren't met.""" self._check_gpfs_state() @@ -738,6 +751,20 @@ class GPFSDriver(driver.ConsistencyGroupVD, driver.ExtendVD, ) return volume_path + def _get_gpfs_encryption_status(self): + """Determine if the backend is configured with key manager.""" + try: + (out, err) = self.gpfs_execute('mmlsfs', self._gpfs_device, + '--encryption', '-Y') + lines = out.splitlines() + value_token = lines[0].split(':').index('data') + encryption_status = lines[1].split(':')[value_token] + return encryption_status + except processutils.ProcessExecutionError as exc: + LOG.error(_LE('Failed to issue mmlsfs command, error: %s.'), + exc.stderr) + raise exception.VolumeBackendAPIException(data=exc.stderr) + def ensure_export(self, context, volume): """Synchronously recreates an export for a logical volume.""" pass @@ -796,6 +823,10 @@ class GPFSDriver(driver.ConsistencyGroupVD, driver.ExtendVD, 'root_path': gpfs_base}) data['consistencygroup_support'] = 'True' + + if self._encryption_state.lower() == 'yes': + data['gpfs_encryption_rest'] = 'True' + self._stats = data def clone_image(self, context, volume, -- 2.45.2