]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
GPFS volume encryption-at-rest support
authorSasikanth <sasikanth.eda@in.ibm.com>
Thu, 25 Jun 2015 10:32:29 +0000 (16:02 +0530)
committerGaurang Tapase <gaurang.tapase@in.ibm.com>
Thu, 13 Aug 2015 10:42:31 +0000 (06:42 -0400)
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
cinder/volume/drivers/ibm/gpfs.py

index 20cc33a373096658b9042a0c6493a37ac9104018..f55631b532b72b626bb2745fd25ee42c82a1a122 100644 (file)
@@ -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')
index 1849c30ccb733df05e75134d7af1a29c278b6015..62a6c997f9fc5530e54b637b8704b659d75906b5 100644 (file)
@@ -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,