Return 'DeleteOnTermination' in VolumeAttachment struct

Change-Id: I0e5bbad355a6879209cfd59dddfa6fbdf817dfbe
This commit is contained in:
Andrey Pavlov 2015-07-09 17:08:40 +03:00 committed by Andrey Pavlov
parent a136181b3c
commit 3fc223c1e7
5 changed files with 54 additions and 12 deletions

View File

@ -172,7 +172,6 @@ Volume related:
- ModifyVolumeAttribute
- kmsKeyId Volume property
- iops Volume property
- deleteOnTermination property (supported for describing instances only)
- volumeType (current implementation isn't AWS compatible) Volume property
VPC related:

View File

@ -19,6 +19,7 @@ from oslo_log import log as logging
from ec2api.api import clients
from ec2api.api import common
from ec2api.api import ec2utils
from ec2api import context as ec2_context
from ec2api.db import api as db_api
from ec2api import exception
from ec2api.i18n import _
@ -71,8 +72,11 @@ def attach_volume(context, volume_id, instance_id, device):
raise exception.UnsupportedOperation()
cinder = clients.cinder(context)
os_volume = cinder.volumes.get(volume['os_id'])
return _format_attachment(context, volume, os_volume,
instance_id=instance_id)
attachment = _format_attachment(context, volume, os_volume,
instance_id=instance_id)
# NOTE(andrey-mp): nova sets deleteOnTermination=False for attached volume
attachment['deleteOnTermination'] = False
return attachment
def detach_volume(context, volume_id, instance_id=None, device=None,
@ -129,7 +133,8 @@ class VolumeDescriber(common.TaggableItemsDescriber):
def format(self, volume, os_volume):
return _format_volume(self.context, volume, os_volume,
self.instances, self.snapshots)
self.instances, self.os_instances,
self.snapshots)
def get_db_items(self):
self.instances = {i['os_id']: i
@ -139,6 +144,11 @@ class VolumeDescriber(common.TaggableItemsDescriber):
return super(VolumeDescriber, self).get_db_items()
def get_os_items(self):
nova = clients.nova(ec2_context.get_os_admin_context())
os_instances = nova.servers.list(
search_opts={'all_tenants': True,
'project_id': self.context.project_id})
self.os_instances = {i.id: i for i in os_instances}
return clients.cinder(self.context).volumes.list()
def get_name(self, os_item):
@ -162,7 +172,7 @@ def describe_volumes(context, volume_id=None, filter=None,
return result
def _format_volume(context, volume, os_volume, instances={},
def _format_volume(context, volume, os_volume, instances={}, os_instances={},
snapshots={}, snapshot_id=None):
valid_ec2_api_volume_status_map = {
'attaching': 'in-use',
@ -180,7 +190,8 @@ def _format_volume(context, volume, os_volume, instances={},
}
if ec2_volume['status'] == 'in-use':
ec2_volume['attachmentSet'] = (
[_format_attachment(context, volume, os_volume, instances)])
[_format_attachment(context, volume, os_volume, instances,
os_instances)])
else:
ec2_volume['attachmentSet'] = {}
if snapshot_id is None and os_volume.snapshot_id:
@ -193,7 +204,7 @@ def _format_volume(context, volume, os_volume, instances={},
def _format_attachment(context, volume, os_volume, instances={},
instance_id=None):
os_instances={}, instance_id=None):
os_attachment = next(iter(os_volume.attachments), {})
os_instance_id = os_attachment.get('server_id')
if not instance_id and os_instance_id:
@ -207,4 +218,13 @@ def _format_attachment(context, volume, os_volume, instances={},
if os_volume.status in ('attaching', 'detaching') else
'attached' if os_attachment else 'detached'),
'volumeId': volume['id']}
if os_instance_id in os_instances:
os_instance = os_instances[os_instance_id]
volumes_attached = getattr(os_instance,
'os-extended-volumes:volumes_attached', [])
volume_attached = next((va for va in volumes_attached
if va['id'] == volume['os_id']), None)
if volume_attached and 'delete_on_termination' in volume_attached:
ec2_attachment['deleteOnTermination'] = (
volume_attached['delete_on_termination'])
return ec2_attachment

View File

@ -169,8 +169,7 @@ class VolumeTest(base.EC2TestCase):
self.assertEqual('in-use', volume['State'])
self.assertEqual(1, len(volume['Attachments']))
attachment = volume['Attachments'][0]
if CONF.aws.run_incompatible_tests:
self.assertFalse(attachment['DeleteOnTermination'])
self.assertFalse(attachment['DeleteOnTermination'])
self.assertIsNotNone(attachment['Device'])
self.assertEqual(instance_id, attachment['InstanceId'])
self.assertEqual(volume_id, attachment['VolumeId'])

View File

@ -1540,7 +1540,8 @@ EC2_VOLUME_2 = {
'attachmentSet': [{'status': 'attached',
'instanceId': ID_EC2_INSTANCE_2,
'volumeId': ID_EC2_VOLUME_2,
'device': ROOT_DEVICE_NAME_INSTANCE_2}],
'device': ROOT_DEVICE_NAME_INSTANCE_2,
'deleteOnTermination': False}],
'encrypted': False,
'volumeType': None,
}

View File

@ -14,6 +14,7 @@
import mock
import ec2api.api.clients
from ec2api.tests.unit import base
from ec2api.tests.unit import fakes
from ec2api.tests.unit import matchers
@ -22,11 +23,24 @@ from ec2api.tests.unit import tools
class VolumeTestCase(base.ApiTestCase):
def test_describe_volumes(self):
def setUp(self):
super(VolumeTestCase, self).setUp()
get_os_admin_context_patcher = (
mock.patch('ec2api.context.get_os_admin_context'))
self.get_os_admin_context = get_os_admin_context_patcher.start()
self.addCleanup(get_os_admin_context_patcher.stop)
self.get_os_admin_context.return_value = (
self._create_context(auth_token='admin_token'))
@mock.patch('ec2api.api.clients.nova', wraps=ec2api.api.clients.nova)
def test_describe_volumes(self, nova_client_getter):
self.cinder.volumes.list.return_value = [
fakes.OSVolume(fakes.OS_VOLUME_1),
fakes.OSVolume(fakes.OS_VOLUME_2),
fakes.OSVolume(fakes.OS_VOLUME_3)]
self.nova.servers.list.return_value = [
fakes.OSInstance_full(fakes.OS_INSTANCE_1),
fakes.OSInstance_full(fakes.OS_INSTANCE_2)]
self.set_mock_db_items(fakes.DB_VOLUME_1, fakes.DB_VOLUME_2,
fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2,
@ -40,6 +54,8 @@ class VolumeTestCase(base.ApiTestCase):
fakes.EC2_VOLUME_3]},
orderless_lists=True))
nova_client_getter.assert_called_with(
self.get_os_admin_context.return_value)
self.db_api.get_items.assert_any_call(mock.ANY, 'vol')
self.db_api.get_items_by_ids = tools.CopyingMock(
@ -49,6 +65,8 @@ class VolumeTestCase(base.ApiTestCase):
self.assertThat(resp, matchers.DictMatches(
{'volumeSet': [fakes.EC2_VOLUME_1]},
orderless_lists=True))
nova_client_getter.assert_called_with(
self.get_os_admin_context.return_value)
self.db_api.get_items_by_ids.assert_any_call(
mock.ANY, set([fakes.ID_EC2_VOLUME_1]))
@ -73,6 +91,7 @@ class VolumeTestCase(base.ApiTestCase):
def test_describe_volumes_auto_remove(self):
self.cinder.volumes.list.return_value = []
self.nova.servers.list.return_value = []
self.set_mock_db_items(fakes.DB_VOLUME_1, fakes.DB_VOLUME_2)
resp = self.execute('DescribeVolumes', {})
self.assertThat(resp, matchers.DictMatches(
@ -87,6 +106,8 @@ class VolumeTestCase(base.ApiTestCase):
self.cinder.volumes.list.return_value = [
fakes.OSVolume(fakes.OS_VOLUME_1),
fakes.OSVolume(fakes.OS_VOLUME_2)]
self.nova.servers.list.return_value = [
fakes.OSInstance_full(fakes.OS_INSTANCE_2)]
self.assert_execution_error(
'InvalidVolume.NotFound', 'DescribeVolumes',
@ -150,6 +171,7 @@ class VolumeTestCase(base.ApiTestCase):
def test_format_volume_maps_status(self):
fake_volume = fakes.OSVolume(fakes.OS_VOLUME_1)
self.cinder.volumes.list.return_value = [fake_volume]
self.nova.servers.list.return_value = []
self.set_mock_db_items(fakes.DB_VOLUME_1)
fake_volume.status = 'creating'
@ -180,7 +202,8 @@ class VolumeTestCase(base.ApiTestCase):
{'VolumeId': fakes.ID_EC2_VOLUME_3,
'InstanceId': fakes.ID_EC2_INSTANCE_2,
'Device': '/dev/vdf'})
self.assertEqual({'device': '/dev/vdf',
self.assertEqual({'deleteOnTermination': False,
'device': '/dev/vdf',
'instanceId': fakes.ID_EC2_INSTANCE_2,
'status': 'attaching',
'volumeId': fakes.ID_EC2_VOLUME_3},