Add encryption_key_id to volume and backup details

Add microversion 3.64 for including the encryption_key_id in the
volume and backup details when the associated volume is encrypted.
This facilitates associating encryption keys (typically stored in
Barbican) with the volume or backup that uses it.

The encryption_key_id is included in the details only when the
associated volume is encrypted, and it isn't using the all-zeros
key ID used by the legacy fixed-key ConfKeyMgr.

APIImpact
DocImpact

Implements: blueprint include-encryption-key-id-in-details
Change-Id: I16f54e6722cdbcbad4af1eb0d30264b0039412fd
This commit is contained in:
Alan Bishop 2021-01-15 12:41:09 -08:00
parent 876ac4e79f
commit f91aec5869
14 changed files with 112 additions and 6 deletions

View File

@ -98,6 +98,7 @@ Response Parameters
- count: count - count: count
- metadata: metadata_backup - metadata: metadata_backup
- user_id: user_id_backup - user_id: user_id_backup
- encryption_key_id: encryption_key_id
Response Example Response Example
---------------- ----------------
@ -157,6 +158,7 @@ Response Parameters
- os-backup-project-attr:project_id: os-backup-project-attr:project_id - os-backup-project-attr:project_id: os-backup-project-attr:project_id
- metadata: metadata_backup - metadata: metadata_backup
- user_id: user_id_backup - user_id: user_id_backup
- encryption_key_id: encryption_key_id
Response Example Response Example
---------------- ----------------

View File

@ -1140,6 +1140,12 @@ encryption_id_body:
in: body in: body
required: true required: true
type: string type: string
encryption_key_id:
description: |
The UUID of the encryption key.
in: body
required: false
type: string
event_id: event_id:
description: | description: |
The id of the event to this message, this id could The id of the event to this message, this id could

View File

@ -21,8 +21,8 @@
], ],
"min_version": "3.0", "min_version": "3.0",
"status": "CURRENT", "status": "CURRENT",
"updated": "2020-11-19T08:56:00Z", "updated": "2021-02-03T00:00:00Z",
"version": "3.63" "version": "3.64"
} }
] ]
} }

View File

@ -45,8 +45,8 @@
], ],
"min_version": "3.0", "min_version": "3.0",
"status": "CURRENT", "status": "CURRENT",
"updated": "2020-11-19T08:56:00Z", "updated": "2021-02-03T00:00:00Z",
"version": "3.63" "version": "3.64"
} }
] ]
} }

View File

@ -109,6 +109,7 @@ Response Parameters
- availability_zone: availability_zone - availability_zone: availability_zone
- os-vol-host-attr:host: os-vol-host-attr:host - os-vol-host-attr:host: os-vol-host-attr:host
- encrypted: encrypted - encrypted: encrypted
- encryption_key_id: encryption_key_id
- updated_at: updated_at - updated_at: updated_at
- replication_status: replication_status - replication_status: replication_status
- snapshot_id: snapshot_id - snapshot_id: snapshot_id
@ -355,6 +356,7 @@ Response Parameters
- availability_zone: availability_zone - availability_zone: availability_zone
- os-vol-host-attr:host: os-vol-host-attr:host - os-vol-host-attr:host: os-vol-host-attr:host
- encrypted: encrypted - encrypted: encrypted
- encryption_key_id: encryption_key_id
- updated_at: updated_at - updated_at: updated_at
- replication_status: replication_status - replication_status: replication_status
- snapshot_id: snapshot_id - snapshot_id: snapshot_id

View File

@ -167,6 +167,8 @@ DEFAULT_TYPE_OVERRIDES = '3.62'
VOLUME_TYPE_ID_IN_VOLUME_DETAIL = '3.63' VOLUME_TYPE_ID_IN_VOLUME_DETAIL = '3.63'
ENCRYPTION_KEY_ID_IN_DETAILS = '3.64'
def get_mv_header(version): def get_mv_header(version):
"""Gets a formatted HTTP microversion header. """Gets a formatted HTTP microversion header.

View File

@ -147,6 +147,7 @@ REST_API_VERSION_HISTORY = """
in the volume details. This MV affects the volume detail list in the volume details. This MV affects the volume detail list
("GET /v3/{project_id}/volumes/detail") and volume-show ("GET /v3/{project_id}/volumes/detail") and volume-show
("GET /v3/{project_id}/volumes/{volume_id}") calls. ("GET /v3/{project_id}/volumes/{volume_id}") calls.
* 3.64 - Include 'encryption_key_id' in volume and backup details
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
@ -154,9 +155,9 @@ REST_API_VERSION_HISTORY = """
# minimum version of the API supported. # minimum version of the API supported.
# Explicitly using /v2 endpoints will still work # Explicitly using /v2 endpoints will still work
_MIN_API_VERSION = "3.0" _MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.63" _MAX_API_VERSION = "3.64"
_LEGACY_API_VERSION2 = "2.0" _LEGACY_API_VERSION2 = "2.0"
UPDATED = "2020-11-19T08:56:00Z" UPDATED = "2021-02-03T00:00:00Z"
# NOTE(cyeoh): min and max versions declared as functions so we can # NOTE(cyeoh): min and max versions declared as functions so we can

View File

@ -485,3 +485,8 @@ value.
Includes volume type ID in the volume-show and volume-detail-list JSON Includes volume type ID in the volume-show and volume-detail-list JSON
responses. Before this microversion, Cinder returns only the volume type name responses. Before this microversion, Cinder returns only the volume type name
in the volume details. in the volume details.
3.64
----
Include the ``encryption_key_id`` in volume and backup details when the
associated volume is encrypted.

View File

@ -15,6 +15,7 @@
from cinder.api import microversions as mv from cinder.api import microversions as mv
from cinder.api.views import backups as views_v2 from cinder.api.views import backups as views_v2
from cinder.common import constants as cinder_constants
class ViewBuilder(views_v2.ViewBuilder): class ViewBuilder(views_v2.ViewBuilder):
@ -29,4 +30,11 @@ class ViewBuilder(views_v2.ViewBuilder):
req_version = request.api_version_request req_version = request.api_version_request
if req_version.matches(mv.BACKUP_METADATA): if req_version.matches(mv.BACKUP_METADATA):
backup_ref['backup']['metadata'] = backup.metadata backup_ref['backup']['metadata'] = backup.metadata
if req_version.matches(mv.ENCRYPTION_KEY_ID_IN_DETAILS, None):
encryption_key_id = backup.get('encryption_key_id', None)
if (encryption_key_id and
encryption_key_id != cinder_constants.FIXED_KEY_ID):
backup_ref['backup']['encryption_key_id'] = encryption_key_id
return backup_ref return backup_ref

View File

@ -15,6 +15,7 @@
from cinder.api import microversions as mv from cinder.api import microversions as mv
from cinder.api.v2.views import volumes as views_v2 from cinder.api.v2.views import volumes as views_v2
from cinder.common import constants as cinder_constants
class ViewBuilder(views_v2.ViewBuilder): class ViewBuilder(views_v2.ViewBuilder):
@ -70,6 +71,12 @@ class ViewBuilder(views_v2.ViewBuilder):
volume_ref[ volume_ref[
'volume']["volume_type_id"] = volume['volume_type'].get('id') 'volume']["volume_type_id"] = volume['volume_type'].get('id')
if req_version.matches(mv.ENCRYPTION_KEY_ID_IN_DETAILS, None):
encryption_key_id = volume.get('encryption_key_id', None)
if (encryption_key_id and
encryption_key_id != cinder_constants.FIXED_KEY_ID):
volume_ref['volume']['encryption_key_id'] = encryption_key_id
return volume_ref return volume_ref
def _list_view(self, func, request, volumes, volume_count, def _list_view(self, func, request, volumes, volume_count,

View File

@ -26,3 +26,6 @@ SCHEDULER_TOPIC = SCHEDULER_BINARY
VOLUME_TOPIC = VOLUME_BINARY VOLUME_TOPIC = VOLUME_BINARY
BACKUP_TOPIC = BACKUP_BINARY BACKUP_TOPIC = BACKUP_BINARY
LOG_BINARIES = (SCHEDULER_BINARY, VOLUME_BINARY, BACKUP_BINARY, API_BINARY) LOG_BINARIES = (SCHEDULER_BINARY, VOLUME_BINARY, BACKUP_BINARY, API_BINARY)
# The encryption key ID used by the legacy fixed-key ConfKeyMgr
FIXED_KEY_ID = '00000000-0000-0000-0000-000000000000'

View File

@ -32,6 +32,7 @@ from cinder import context
from cinder import exception from cinder import exception
from cinder.objects import fields from cinder.objects import fields
from cinder.tests.unit.api import fakes from cinder.tests.unit.api import fakes
from cinder.tests.unit.api.v3.test_volumes import ENCRYPTION_KEY_ID_IN_DETAILS
from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import test from cinder.tests.unit import test
from cinder.tests.unit import utils as test_utils from cinder.tests.unit import utils as test_utils
@ -286,6 +287,25 @@ class BackupsControllerAPITestCase(test.TestCase):
else: else:
self.assertIn('metadata', backup_get) self.assertIn('metadata', backup_get)
@ddt.data(*ENCRYPTION_KEY_ID_IN_DETAILS)
@ddt.unpack
def test_backup_show_with_encryption_key_id(self,
expected_in_details,
encryption_key_id,
version):
backup = test_utils.create_backup(self.ctxt,
encryption_key_id=encryption_key_id)
self.addCleanup(backup.destroy)
url = '/v3/%s/backups/%s' % (fake.PROJECT_ID, backup.id)
req = fakes.HTTPRequest.blank(url, version=version)
backup_details = self.controller.show(req, backup.id)['backup']
if expected_in_details:
self.assertIn('encryption_key_id', backup_details)
else:
self.assertNotIn('encryption_key_id', backup_details)
def test_backup_update_with_null_validate(self): def test_backup_update_with_null_validate(self):
backup = test_utils.create_backup( backup = test_utils.create_backup(
self.ctxt, self.ctxt,

View File

@ -31,6 +31,7 @@ from cinder.api import microversions as mv
from cinder.api.v2.views.volumes import ViewBuilder from cinder.api.v2.views.volumes import ViewBuilder
from cinder.api.v3 import volumes from cinder.api.v3 import volumes
from cinder.backup import api as backup_api from cinder.backup import api as backup_api
from cinder.common import constants as cinder_constants
from cinder import context from cinder import context
from cinder import db from cinder import db
from cinder import exception from cinder import exception
@ -50,6 +51,29 @@ from cinder.volume import api as vol_get
DEFAULT_AZ = "zone1:host1" DEFAULT_AZ = "zone1:host1"
# DDT data for testing whether an 'encryption_key_id' should appear in a
# volume's or backup's details (also used by test_backups.py).
ENCRYPTION_KEY_ID_IN_DETAILS = {
'expected_in_details': True,
'encryption_key_id': fake.ENCRYPTION_KEY_ID,
'version': mv.ENCRYPTION_KEY_ID_IN_DETAILS,
}, {
# No encryption ID to display
'expected_in_details': False,
'encryption_key_id': None,
'version': mv.ENCRYPTION_KEY_ID_IN_DETAILS,
}, {
# Fixed key ID should not be displayed
'expected_in_details': False,
'encryption_key_id': cinder_constants.FIXED_KEY_ID,
'version': mv.ENCRYPTION_KEY_ID_IN_DETAILS,
}, {
# Unsupported microversion
'expected_in_details': False,
'encryption_key_id': fake.ENCRYPTION_KEY_ID,
'version': mv.get_prior_version(mv.ENCRYPTION_KEY_ID_IN_DETAILS),
}
@ddt.ddt @ddt.ddt
class VolumeApiTest(test.TestCase): class VolumeApiTest(test.TestCase):
@ -856,6 +880,26 @@ class VolumeApiTest(test.TestCase):
else: else:
self.assertNotIn('provider_id', res_dict['volume']) self.assertNotIn('provider_id', res_dict['volume'])
@ddt.data(*ENCRYPTION_KEY_ID_IN_DETAILS)
@ddt.unpack
def test_volume_show_with_encryption_key_id(self,
expected_in_details,
encryption_key_id,
version):
volume = test_utils.create_volume(self.ctxt,
testcase_instance=self,
volume_type_id=None,
encryption_key_id=encryption_key_id)
req = fakes.HTTPRequest.blank('/v3/volumes/%s' % volume.id,
version=version)
volume_details = self.controller.show(req, volume.id)['volume']
if expected_in_details:
self.assertIn('encryption_key_id', volume_details)
else:
self.assertNotIn('encryption_key_id', volume_details)
def _fake_create_volume(self, size=1): def _fake_create_volume(self, size=1):
vol = { vol = {
'display_name': 'fake_volume1', 'display_name': 'fake_volume1',

View File

@ -0,0 +1,6 @@
---
features:
- |
Starting with API microversion 3.64, an ``encryption_key_id`` attribute
is included in the response body of volume and backup details when the
associated volume is encrypted.