Glance: attach volume encryption key id to image
This is required to be able to handle encrypted volumes that have been uploaded to Glance. Clone the volume's encryption key, and store the new encryption key id in the image metadata in a property called "cinder_encryption_key_id". This allows the key to be retrieved when creating a new volume from this image. Related-Bug: #1485449 Related bp: improve-encrypted-volume Change-Id: Ia1771817e6a06cc51c5357536915a2c5f9f6248e
This commit is contained in:
parent
b1ebfa4f5d
commit
d96e078f37
@ -27,6 +27,7 @@ from cinder.api.openstack import wsgi
|
|||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
|
from cinder import keymgr
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder import volume
|
from cinder import volume
|
||||||
|
|
||||||
@ -42,8 +43,17 @@ def authorize(context, action_name):
|
|||||||
class VolumeActionsController(wsgi.Controller):
|
class VolumeActionsController(wsgi.Controller):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(VolumeActionsController, self).__init__(*args, **kwargs)
|
super(VolumeActionsController, self).__init__(*args, **kwargs)
|
||||||
|
self._key_mgr = None
|
||||||
self.volume_api = volume.API()
|
self.volume_api = volume.API()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _key_manager(self):
|
||||||
|
# Allows for lazy initialization of the key manager
|
||||||
|
if self._key_mgr is None:
|
||||||
|
self._key_mgr = keymgr.API(CONF)
|
||||||
|
|
||||||
|
return self._key_mgr
|
||||||
|
|
||||||
@wsgi.action('os-attach')
|
@wsgi.action('os-attach')
|
||||||
def _attach(self, req, id, body):
|
def _attach(self, req, id, body):
|
||||||
"""Add attachment metadata."""
|
"""Add attachment metadata."""
|
||||||
@ -246,6 +256,19 @@ class VolumeActionsController(wsgi.Controller):
|
|||||||
"disk_format": disk_format,
|
"disk_format": disk_format,
|
||||||
"name": params["image_name"]}
|
"name": params["image_name"]}
|
||||||
|
|
||||||
|
if volume.encryption_key_id:
|
||||||
|
# Clone volume encryption key: the current key cannot
|
||||||
|
# be reused because it will be deleted when the volume is
|
||||||
|
# deleted.
|
||||||
|
# TODO(eharney): Currently, there is no mechanism to remove
|
||||||
|
# these keys, because Glance will not delete the key from
|
||||||
|
# Barbican when the image is deleted.
|
||||||
|
encryption_key_id = self._key_manager.store(
|
||||||
|
context,
|
||||||
|
self._key_manager.get(context, volume.encryption_key_id))
|
||||||
|
|
||||||
|
image_metadata['cinder_encryption_key_id'] = encryption_key_id
|
||||||
|
|
||||||
if req_version >= api_version_request.APIVersionRequest('3.1'):
|
if req_version >= api_version_request.APIVersionRequest('3.1'):
|
||||||
|
|
||||||
image_metadata['visibility'] = params.get('visibility', 'private')
|
image_metadata['visibility'] = params.get('visibility', 'private')
|
||||||
|
@ -749,6 +749,22 @@ def fake_volume_get(self, context, volume_id):
|
|||||||
return volume
|
return volume
|
||||||
|
|
||||||
|
|
||||||
|
def fake_volume_get_obj(self, context, volume_id, **kwargs):
|
||||||
|
volume = fake_volume.fake_volume_obj(context,
|
||||||
|
id=volume_id,
|
||||||
|
display_description='displaydesc',
|
||||||
|
**kwargs)
|
||||||
|
if volume_id == fake.VOLUME3_ID:
|
||||||
|
volume.status = 'in-use'
|
||||||
|
else:
|
||||||
|
volume.status = 'available'
|
||||||
|
|
||||||
|
volume.volume_type = fake_volume.fake_volume_type_obj(
|
||||||
|
context,
|
||||||
|
name=v2_fakes.DEFAULT_VOL_TYPE)
|
||||||
|
return volume
|
||||||
|
|
||||||
|
|
||||||
def fake_upload_volume_to_image_service(self, context, volume, metadata,
|
def fake_upload_volume_to_image_service(self, context, volume, metadata,
|
||||||
force):
|
force):
|
||||||
ret = {"id": volume['id'],
|
ret = {"id": volume['id'],
|
||||||
@ -770,6 +786,7 @@ class VolumeImageActionsTest(test.TestCase):
|
|||||||
self.controller = volume_actions.VolumeActionsController()
|
self.controller = volume_actions.VolumeActionsController()
|
||||||
self.context = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
self.context = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
||||||
is_admin=False)
|
is_admin=False)
|
||||||
|
self.maxDiff = 2000
|
||||||
|
|
||||||
def _get_os_volume_upload_image(self):
|
def _get_os_volume_upload_image(self):
|
||||||
vol = {
|
vol = {
|
||||||
@ -823,16 +840,16 @@ class VolumeImageActionsTest(test.TestCase):
|
|||||||
def fake_rpc_copy_volume_to_image(self, *args):
|
def fake_rpc_copy_volume_to_image(self, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@mock.patch.object(volume_api.API, 'get', fake_volume_get)
|
@mock.patch.object(volume_api.API, 'get', fake_volume_get_obj)
|
||||||
@mock.patch.object(volume_api.API, "copy_volume_to_image",
|
@mock.patch.object(volume_api.API, "copy_volume_to_image",
|
||||||
fake_upload_volume_to_image_service)
|
fake_upload_volume_to_image_service)
|
||||||
def test_copy_volume_to_image(self):
|
def test_copy_volume_to_image(self):
|
||||||
id = fake.VOLUME_ID
|
id = fake.VOLUME_ID
|
||||||
vol = {"container_format": 'bare',
|
img = {"container_format": 'bare',
|
||||||
"disk_format": 'raw',
|
"disk_format": 'raw',
|
||||||
"image_name": 'image_name',
|
"image_name": 'image_name',
|
||||||
"force": True}
|
"force": True}
|
||||||
body = {"os-volume_upload_image": vol}
|
body = {"os-volume_upload_image": img}
|
||||||
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' %
|
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' %
|
||||||
(fake.PROJECT_ID, id))
|
(fake.PROJECT_ID, id))
|
||||||
res_dict = self.controller._volume_upload_image(req, id, body)
|
res_dict = self.controller._volume_upload_image(req, id, body)
|
||||||
@ -842,7 +859,8 @@ class VolumeImageActionsTest(test.TestCase):
|
|||||||
'status': 'uploading',
|
'status': 'uploading',
|
||||||
'display_description': 'displaydesc',
|
'display_description': 'displaydesc',
|
||||||
'size': 1,
|
'size': 1,
|
||||||
'volume_type': fake_volume.fake_db_volume_type(
|
'volume_type': fake_volume.fake_volume_type_obj(
|
||||||
|
context,
|
||||||
name='vol_type_name'),
|
name='vol_type_name'),
|
||||||
'image_id': fake.IMAGE_ID,
|
'image_id': fake.IMAGE_ID,
|
||||||
'container_format': 'bare',
|
'container_format': 'bare',
|
||||||
@ -870,7 +888,7 @@ class VolumeImageActionsTest(test.TestCase):
|
|||||||
id,
|
id,
|
||||||
body)
|
body)
|
||||||
|
|
||||||
@mock.patch.object(volume_api.API, 'get', fake_volume_get)
|
@mock.patch.object(volume_api.API, 'get', fake_volume_get_obj)
|
||||||
@mock.patch.object(volume_api.API, 'copy_volume_to_image',
|
@mock.patch.object(volume_api.API, 'copy_volume_to_image',
|
||||||
side_effect=exception.InvalidVolume(reason='blah'))
|
side_effect=exception.InvalidVolume(reason='blah'))
|
||||||
def test_copy_volume_to_image_invalidvolume(self, mock_copy):
|
def test_copy_volume_to_image_invalidvolume(self, mock_copy):
|
||||||
@ -904,7 +922,7 @@ class VolumeImageActionsTest(test.TestCase):
|
|||||||
id,
|
id,
|
||||||
body)
|
body)
|
||||||
|
|
||||||
@mock.patch.object(volume_api.API, 'get', fake_volume_get)
|
@mock.patch.object(volume_api.API, 'get', fake_volume_get_obj)
|
||||||
@mock.patch.object(volume_api.API, 'copy_volume_to_image',
|
@mock.patch.object(volume_api.API, 'copy_volume_to_image',
|
||||||
side_effect=ValueError)
|
side_effect=ValueError)
|
||||||
def test_copy_volume_to_image_valueerror(self, mock_copy):
|
def test_copy_volume_to_image_valueerror(self, mock_copy):
|
||||||
@ -922,7 +940,7 @@ class VolumeImageActionsTest(test.TestCase):
|
|||||||
id,
|
id,
|
||||||
body)
|
body)
|
||||||
|
|
||||||
@mock.patch.object(volume_api.API, 'get', fake_volume_get)
|
@mock.patch.object(volume_api.API, 'get', fake_volume_get_obj)
|
||||||
@mock.patch.object(volume_api.API, 'copy_volume_to_image',
|
@mock.patch.object(volume_api.API, 'copy_volume_to_image',
|
||||||
side_effect=messaging.RemoteError)
|
side_effect=messaging.RemoteError)
|
||||||
def test_copy_volume_to_image_remoteerror(self, mock_copy):
|
def test_copy_volume_to_image_remoteerror(self, mock_copy):
|
||||||
@ -1045,7 +1063,7 @@ class VolumeImageActionsTest(test.TestCase):
|
|||||||
self.assertEqual('uploading', vol_db.status)
|
self.assertEqual('uploading', vol_db.status)
|
||||||
self.assertEqual('available', vol_db.previous_status)
|
self.assertEqual('available', vol_db.previous_status)
|
||||||
|
|
||||||
@mock.patch.object(volume_api.API, 'get', fake_volume_get)
|
@mock.patch.object(volume_api.API, 'get', fake_volume_get_obj)
|
||||||
def test_copy_volume_to_image_public_not_authorized(self):
|
def test_copy_volume_to_image_public_not_authorized(self):
|
||||||
"""Test unauthorized create public image from volume."""
|
"""Test unauthorized create public image from volume."""
|
||||||
id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||||
|
Loading…
Reference in New Issue
Block a user