From 3caa76a2b24a586783e22f045d0c40aeffed91b9 Mon Sep 17 00:00:00 2001 From: Brianna Poulos Date: Mon, 10 Feb 2014 16:13:59 -0500 Subject: [PATCH] Add encrypted flag to volumes Currently, the only way to determine whether a volume is encrypted is by retrieving the encryption metadata about the volume (through cinder.api.contrib.volume_encryption_metadata) and checking the encryption_key_id value. This patch adds an "encrypted" flag to the basic volume api to enable other services (like Horizon: see patch https://review.openstack.org/#/c/71125) to easily tell whether a volume is encrypted using a basic get call,instead of requiring an additional call. Implements blueprint encrypt-cinder-volumes https://blueprints.launchpad.net/nova/+spec/encrypt-cinder-volumes Change-Id: Id8e422135f17795de06589930afd0309fde28fd1 --- cinder/api/v1/volumes.py | 2 ++ cinder/api/v2/views/volumes.py | 7 +++++- cinder/tests/api/v1/test_volumes.py | 36 ++++++++++++++++++++++++++++- cinder/tests/api/v2/stubs.py | 1 + cinder/tests/api/v2/test_volumes.py | 32 ++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 3 deletions(-) diff --git a/cinder/api/v1/volumes.py b/cinder/api/v1/volumes.py index 708152196..1b1f1b0c2 100644 --- a/cinder/api/v1/volumes.py +++ b/cinder/api/v1/volumes.py @@ -106,6 +106,8 @@ def _translate_volume_summary_view(context, vol, image_id=None): d['snapshot_id'] = vol['snapshot_id'] d['source_volid'] = vol['source_volid'] + d['encrypted'] = vol['encryption_key_id'] is not None + if image_id: d['image_id'] = image_id diff --git a/cinder/api/v2/views/volumes.py b/cinder/api/v2/views/volumes.py index c1b45acc6..778be7767 100644 --- a/cinder/api/v2/views/volumes.py +++ b/cinder/api/v2/views/volumes.py @@ -66,10 +66,15 @@ class ViewBuilder(common.ViewBuilder): 'metadata': self._get_volume_metadata(volume), 'links': self._get_links(request, volume['id']), 'user_id': volume.get('user_id'), - 'bootable': str(volume.get('bootable')).lower() + 'bootable': str(volume.get('bootable')).lower(), + 'encrypted': self._is_volume_encrypted(volume) } } + def _is_volume_encrypted(self, volume): + """Determine if volume is encrypted.""" + return volume.get('encryption_key_id') is not None + def _get_attachments(self, volume): """Retrieve the attachments of the volume object.""" attachments = [] diff --git a/cinder/tests/api/v1/test_volumes.py b/cinder/tests/api/v1/test_volumes.py index 2bcf3ee23..fb937bd96 100644 --- a/cinder/tests/api/v1/test_volumes.py +++ b/cinder/tests/api/v1/test_volumes.py @@ -90,6 +90,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'Volume Test Desc', 'availability_zone': 'zone1:host1', 'display_name': 'Volume Test Name', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -104,7 +105,8 @@ class VolumeApiTest(test.TestCase): 'id': '1', 'created_at': datetime.datetime(1, 1, 1, 1, 1, 1), - 'size': 100}} + 'size': 100, + 'encrypted': False}} self.assertEqual(res_dict, expected) def test_volume_create_with_type(self): @@ -180,6 +182,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'Volume Test Desc', 'availability_zone': 'nova', 'display_name': 'Volume Test Name', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -247,6 +250,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'Updated Test Name', + 'encrypted': False, 'attachments': [{ 'id': '1', 'volume_id': '1', @@ -282,6 +286,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{ 'id': '1', 'volume_id': '1', @@ -331,6 +336,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'Updated Test Name', + 'encrypted': False, 'attachments': [{ 'id': '1', 'volume_id': '1', @@ -386,6 +392,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -425,6 +432,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -453,6 +461,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -492,6 +501,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -642,6 +652,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -673,6 +684,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [], 'bootable': 'false', 'volume_type': 'vol_type_name', @@ -698,6 +710,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -772,6 +785,7 @@ class VolumeApiTest(test.TestCase): 'display_description': 'displaydesc', 'availability_zone': 'fakeaz', 'display_name': 'displayname', + 'encrypted': False, 'attachments': [{'device': '/', 'server_id': 'fakeuuid', 'host_name': None, @@ -789,6 +803,26 @@ class VolumeApiTest(test.TestCase): 'size': 1}} self.assertEqual(res_dict, expected) + def test_volume_show_with_encrypted_volume(self): + def stub_volume_get(self, context, volume_id): + return stubs.stub_volume(volume_id, encryption_key_id='fake_id') + + self.stubs.Set(volume_api.API, 'get', stub_volume_get) + + req = fakes.HTTPRequest.blank('/v1/volumes/1') + res_dict = self.controller.show(req, 1) + self.assertEqual(res_dict['volume']['encrypted'], True) + + def test_volume_show_with_unencrypted_volume(self): + def stub_volume_get(self, context, volume_id): + return stubs.stub_volume(volume_id, encryption_key_id=None) + + self.stubs.Set(volume_api.API, 'get', stub_volume_get) + + req = fakes.HTTPRequest.blank('/v1/volumes/1') + res_dict = self.controller.show(req, 1) + self.assertEqual(res_dict['volume']['encrypted'], False) + def test_volume_delete(self): self.stubs.Set(db, 'volume_get', stubs.stub_volume_get_db) diff --git a/cinder/tests/api/v2/stubs.py b/cinder/tests/api/v2/stubs.py index 23384c286..cac93fc8a 100644 --- a/cinder/tests/api/v2/stubs.py +++ b/cinder/tests/api/v2/stubs.py @@ -43,6 +43,7 @@ def stub_volume(id, **kwargs): 'snapshot_id': None, 'source_volid': None, 'volume_type_id': '3e196c20-3c06-11e2-81c1-0800200c9a66', + 'encryption_key_id': None, 'volume_admin_metadata': [{'key': 'attached_mode', 'value': 'rw'}, {'key': 'readonly', 'value': 'False'}], 'bootable': False, diff --git a/cinder/tests/api/v2/test_volumes.py b/cinder/tests/api/v2/test_volumes.py index 9fe16c8e7..4788c78bc 100644 --- a/cinder/tests/api/v2/test_volumes.py +++ b/cinder/tests/api/v2/test_volumes.py @@ -116,7 +116,8 @@ class VolumeApiTest(test.TestCase): 'source_volid': None, 'status': 'fakestatus', 'user_id': 'fakeuser', - 'volume_type': 'vol_type_name'}} + 'volume_type': 'vol_type_name', + 'encrypted': False}} self.assertEqual(res_dict, ex) def test_volume_create_with_type(self): @@ -204,6 +205,7 @@ class VolumeApiTest(test.TestCase): 'bootable': 'false', 'created_at': datetime.datetime(1, 1, 1, 1, 1, 1), 'description': 'Volume Test Desc', + 'encrypted': False, 'id': '1', 'links': [{'href': 'http://localhost/v2/fake/volumes/1', @@ -273,6 +275,7 @@ class VolumeApiTest(test.TestCase): 'volume': { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'Updated Test Name', @@ -322,6 +325,7 @@ class VolumeApiTest(test.TestCase): expected = {'volume': { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'displayname', @@ -382,6 +386,7 @@ class VolumeApiTest(test.TestCase): expected = {'volume': { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'displayname', @@ -483,6 +488,7 @@ class VolumeApiTest(test.TestCase): { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'displayname', @@ -541,6 +547,7 @@ class VolumeApiTest(test.TestCase): { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'displayname', @@ -873,6 +880,7 @@ class VolumeApiTest(test.TestCase): 'volume': { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'displayname', @@ -921,6 +929,7 @@ class VolumeApiTest(test.TestCase): 'volume': { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'displayname', @@ -977,6 +986,7 @@ class VolumeApiTest(test.TestCase): 'volume': { 'status': 'fakestatus', 'description': 'displaydesc', + 'encrypted': False, 'availability_zone': 'fakeaz', 'bootable': 'false', 'name': 'displayname', @@ -1012,6 +1022,26 @@ class VolumeApiTest(test.TestCase): } self.assertEqual(res_dict, expected) + def test_volume_show_with_encrypted_volume(self): + def stub_volume_get(self, context, volume_id): + return stubs.stub_volume(volume_id, encryption_key_id='fake_id') + + self.stubs.Set(volume_api.API, 'get', stub_volume_get) + + req = fakes.HTTPRequest.blank('/v2/volumes/1') + res_dict = self.controller.show(req, 1) + self.assertEqual(res_dict['volume']['encrypted'], True) + + def test_volume_show_with_unencrypted_volume(self): + def stub_volume_get(self, context, volume_id): + return stubs.stub_volume(volume_id, encryption_key_id=None) + + self.stubs.Set(volume_api.API, 'get', stub_volume_get) + + req = fakes.HTTPRequest.blank('/v2/volumes/1') + res_dict = self.controller.show(req, 1) + self.assertEqual(res_dict['volume']['encrypted'], False) + def test_volume_delete(self): self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get) -- 2.45.2