Port tests for instance volume manipulation

Change-Id: I41e61a5e6eb82515847cf0108366a7e72cb220af
This commit is contained in:
Feodor Tersin
2015-01-20 23:16:58 +04:00
parent 8101f44ea7
commit fe949bf49c
5 changed files with 148 additions and 14 deletions

View File

@@ -1240,10 +1240,10 @@ def _cloud_parse_block_device_mapping(context, bdm):
if ec2_id: if ec2_id:
if ec2_id.startswith('snap-'): if ec2_id.startswith('snap-'):
snapshot = ec2utils.get_db_item(context, 'snap', ec2_id) snapshot = ec2utils.get_db_item(context, 'snap', ec2_id)
bdm['snapshot_id'] = snapshot['id'] bdm['snapshot_id'] = snapshot['os_id']
elif ec2_id.startswith('vol-'): elif ec2_id.startswith('vol-'):
volume = ec2utils.get_db_item(context, 'vol', ec2_id) volume = ec2utils.get_db_item(context, 'vol', ec2_id)
bdm['volume_id'] = volume['id'] bdm['volume_id'] = volume['os_id']
else: else:
# NOTE(ft): AWS returns undocumented InvalidSnapshotID.NotFound # NOTE(ft): AWS returns undocumented InvalidSnapshotID.NotFound
raise exception.InvalidSnapshotIDMalformed(snapshot_id=ec2_id) raise exception.InvalidSnapshotIDMalformed(snapshot_id=ec2_id)

View File

@@ -50,6 +50,7 @@ class ApiTestCase(test_base.BaseTestCase):
self.nova_security_groups = nova_mock.return_value.security_groups self.nova_security_groups = nova_mock.return_value.security_groups
self.nova_security_group_rules = ( self.nova_security_group_rules = (
nova_mock.return_value.security_group_rules) nova_mock.return_value.security_group_rules)
self.nova_volumes = nova_mock.return_value.volumes
self.addCleanup(nova_patcher.stop) self.addCleanup(nova_patcher.stop)
glance_patcher = mock.patch('glanceclient.client.Client') glance_patcher = mock.patch('glanceclient.client.Client')

View File

@@ -1119,7 +1119,8 @@ EC2_IMAGE_1 = {
{'deviceName': '/dev/sdb0', {'deviceName': '/dev/sdb0',
'virtualName': 'ephemeral0'}, 'virtualName': 'ephemeral0'},
{'deviceName': '/dev/sdb1', {'deviceName': '/dev/sdb1',
'ebs': {'snapshotId': ID_EC2_SNAPSHOT_1}}, 'ebs': {'snapshotId': ID_EC2_SNAPSHOT_1,
'volumeSize': 22}},
{'deviceName': '/dev/sdb2', {'deviceName': '/dev/sdb2',
'ebs': {'snapshotId': ID_EC2_VOLUME_1}}, 'ebs': {'snapshotId': ID_EC2_VOLUME_1}},
{'deviceName': '/dev/sdb3', {'deviceName': '/dev/sdb3',
@@ -1199,7 +1200,8 @@ OS_IMAGE_1 = {
{'device': 'sdc4', 'virtual': 'swap'}], {'device': 'sdc4', 'virtual': 'swap'}],
'block_device_mapping': [ 'block_device_mapping': [
{'device_name': '/dev/sdb1', {'device_name': '/dev/sdb1',
'snapshot_id': ID_OS_SNAPSHOT_1}, 'snapshot_id': ID_OS_SNAPSHOT_1,
'volume_size': 22},
{'device_name': '/dev/sdb2', {'device_name': '/dev/sdb2',
'volume_id': ID_OS_VOLUME_1}, 'volume_id': ID_OS_VOLUME_1},
{'device_name': '/dev/sdb3', 'virtual_name': 'ephemeral5'}, {'device_name': '/dev/sdb3', 'virtual_name': 'ephemeral5'},

View File

@@ -294,31 +294,39 @@ class InstanceTestCase(base.ApiTestCase):
mock.call(mock.ANY, 'i', tools.purge_dict(db_instance, ['id'])) mock.call(mock.ANY, 'i', tools.purge_dict(db_instance, ['id']))
for db_instance in self.DB_INSTANCES]) for db_instance in self.DB_INSTANCES])
@mock.patch('ec2api.api.instance._parse_block_device_mapping')
@mock.patch('ec2api.api.instance._format_reservation') @mock.patch('ec2api.api.instance._format_reservation')
@mock.patch('ec2api.api.instance.InstanceEngineNeutron.' @mock.patch('ec2api.api.instance.InstanceEngineNeutron.'
'get_ec2_classic_os_network') 'get_ec2_classic_os_network')
def test_run_instances_other_parameters(self, get_ec2_classic_os_network, def test_run_instances_other_parameters(self, get_ec2_classic_os_network,
format_reservation): format_reservation,
parse_block_device_mapping):
self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1) self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1)
get_ec2_classic_os_network.return_value = {'id': fakes.random_os_id()} get_ec2_classic_os_network.return_value = {'id': fakes.random_os_id()}
format_reservation.return_value = {} format_reservation.return_value = {}
parse_block_device_mapping.return_value = 'fake_bdm'
def do_check(engine, extra_kwargs={}, extra_db_instance={}): def do_check(engine, extra_kwargs={}, extra_db_instance={}):
instance_api.instance_engine = engine instance_api.instance_engine = engine
resp = self.execute('RunInstances', resp = self.execute(
{'ImageId': fakes.ID_EC2_IMAGE_1, 'RunInstances',
'InstanceType': 'fake_flavor', {'ImageId': fakes.ID_EC2_IMAGE_1,
'MinCount': '1', 'MaxCount': '1', 'InstanceType': 'fake_flavor',
'SecurityGroup.1': 'Default', 'MinCount': '1', 'MaxCount': '1',
'Placement.AvailabilityZone': 'fake_zone', 'SecurityGroup.1': 'Default',
'ClientToken': 'fake_client_token'}) 'Placement.AvailabilityZone': 'fake_zone',
'ClientToken': 'fake_client_token',
'BlockDeviceMapping.1.DeviceName': '/dev/vdd',
'BlockDeviceMapping.1.Ebs.SnapshotId': (
fakes.ID_EC2_SNAPSHOT_1),
'BlockDeviceMapping.1.Ebs.DeleteOnTermination': 'False'})
self.assertEqual(200, resp['http_status_code']) self.assertEqual(200, resp['http_status_code'])
self.nova_servers.create.assert_called_once_with( self.nova_servers.create.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY, min_count=1, max_count=1, mock.ANY, mock.ANY, mock.ANY, min_count=1, max_count=1,
userdata=None, block_device_mapping=None, userdata=None, kernel_id=None, ramdisk_id=None, key_name=None,
kernel_id=None, ramdisk_id=None, key_name=None, block_device_mapping='fake_bdm',
availability_zone='fake_zone', security_groups=['Default'], availability_zone='fake_zone', security_groups=['Default'],
**extra_kwargs) **extra_kwargs)
self.nova_servers.reset_mock() self.nova_servers.reset_mock()
@@ -330,6 +338,13 @@ class InstanceTestCase(base.ApiTestCase):
self.db_api.add_item.assert_called_once_with( self.db_api.add_item.assert_called_once_with(
mock.ANY, 'i', db_instance) mock.ANY, 'i', db_instance)
self.db_api.reset_mock() self.db_api.reset_mock()
parse_block_device_mapping.assert_called_once_with(
mock.ANY,
[{'device_name': '/dev/vdd',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_1,
'delete_on_termination': False}}],
self.glance.images.get.return_value)
parse_block_device_mapping.reset_mock()
do_check( do_check(
instance_api.InstanceEngineNeutron(), instance_api.InstanceEngineNeutron(),
@@ -1147,6 +1162,61 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
instance_api._parse_image_parameters, instance_api._parse_image_parameters,
fake_context, image_id, None, None) fake_context, image_id, None, None)
@mock.patch('ec2api.db.api.IMPL')
def test_parse_block_device_mapping(self, db_api):
fake_context = mock.Mock(service_catalog=[{'type': 'fake'}])
os_image = fakes.OSImage(fakes.OS_IMAGE_1)
db_api.get_item_by_id.side_effect = fakes.get_db_api_get_item_by_id({
fakes.ID_EC2_VOLUME_1: fakes.DB_VOLUME_1,
fakes.ID_EC2_VOLUME_2: fakes.DB_VOLUME_2,
fakes.ID_EC2_VOLUME_3: fakes.DB_VOLUME_3,
fakes.ID_EC2_SNAPSHOT_1: fakes.DB_SNAPSHOT_1,
fakes.ID_EC2_SNAPSHOT_2: fakes.DB_SNAPSHOT_2})
res = instance_api._parse_block_device_mapping(
fake_context, [], os_image)
self.assertEqual([], res)
res = instance_api._parse_block_device_mapping(
fake_context, [{'device_name': '/dev/vdf',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_1}},
{'device_name': '/dev/vdg',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_2,
'volume_size': 111,
'delete_on_termination': False}},
{'device_name': '/dev/vdh',
'ebs': {'snapshot_id': fakes.ID_EC2_VOLUME_1}},
{'device_name': '/dev/vdi',
'ebs': {'snapshot_id': fakes.ID_EC2_VOLUME_2,
'delete_on_termination': True}},
{'device_name': '/dev/sdb1',
'ebs': {'volume_size': 55}}],
os_image)
self.assertThat(
res,
matchers.ListMatches([{'device_name': '/dev/vdf',
'snapshot_id': fakes.ID_OS_SNAPSHOT_1,
'delete_on_termination': True},
{'device_name': '/dev/vdg',
'snapshot_id': fakes.ID_OS_SNAPSHOT_2,
'volume_size': 111,
'delete_on_termination': False},
{'device_name': '/dev/vdh',
'volume_id': fakes.ID_OS_VOLUME_1,
'delete_on_termination': True},
{'device_name': '/dev/vdi',
'volume_id': fakes.ID_OS_VOLUME_2,
'delete_on_termination': True},
{'device_name': '/dev/sdb1',
'snapshot_id': fakes.ID_OS_SNAPSHOT_1,
'volume_size': 55,
'volume_id': None,
'delete_on_termination': None,
'virtual_name': None,
'no_device': None}],
orderless_lists=True))
@mock.patch('ec2api.api.instance.novadb') @mock.patch('ec2api.api.instance.novadb')
@mock.patch('novaclient.v1_1.client.Client') @mock.patch('novaclient.v1_1.client.Client')
@mock.patch('ec2api.db.api.IMPL') @mock.patch('ec2api.db.api.IMPL')

View File

@@ -149,3 +149,64 @@ class VolumeTestCase(base.ApiTestCase):
resp = self.execute('DescribeVolumes', {}) resp = self.execute('DescribeVolumes', {})
self.assertEqual(200, resp['http_status_code']) self.assertEqual(200, resp['http_status_code'])
self.assertEqual('banana', resp['volumeSet'][0]['status']) self.assertEqual('banana', resp['volumeSet'][0]['status'])
def test_attach_volume(self):
self.db_api.get_item_by_id.side_effect = (
fakes.get_db_api_get_item_by_id({
fakes.ID_EC2_INSTANCE_2: fakes.DB_INSTANCE_2,
fakes.ID_EC2_VOLUME_3: fakes.DB_VOLUME_3}))
os_volume = fakes.CinderVolume(fakes.OS_VOLUME_3)
os_volume.attachments.append({'device': '/dev/vdf',
'server_id': fakes.ID_OS_INSTANCE_2})
os_volume.status = 'attaching'
self.cinder.volumes.get.return_value = os_volume
resp = self.execute('AttachVolume',
{'VolumeId': fakes.ID_EC2_VOLUME_3,
'InstanceId': fakes.ID_EC2_INSTANCE_2,
'Device': '/dev/vdf'})
self.assertEqual({'http_status_code': 200,
'attachTime': None,
'device': '/dev/vdf',
'instanceId': fakes.ID_EC2_INSTANCE_2,
'status': 'attaching',
'volumeId': fakes.ID_EC2_VOLUME_3},
resp)
self.nova_volumes.create_server_volume.assert_called_once_with(
fakes.ID_OS_INSTANCE_2, fakes.ID_OS_VOLUME_3, '/dev/vdf')
@mock.patch.object(fakes.CinderVolume, 'get', autospec=True)
def test_detach_volume(self, os_volume_get):
self.db_api.get_item_by_id.side_effect = (
fakes.get_db_api_get_item_by_id({
fakes.ID_EC2_INSTANCE_2: fakes.DB_INSTANCE_2,
fakes.ID_EC2_VOLUME_2: fakes.DB_VOLUME_2}))
self.db_api.get_items.return_value = [fakes.DB_INSTANCE_1,
fakes.DB_INSTANCE_2]
os_volume = fakes.CinderVolume(fakes.OS_VOLUME_2)
self.cinder.volumes.get.return_value = os_volume
os_volume_get.side_effect = (
lambda vol: setattr(vol, 'status', 'detaching'))
resp = self.execute('DetachVolume',
{'VolumeId': fakes.ID_EC2_VOLUME_2})
self.assertEqual({'http_status_code': 200,
'attachTime': None,
'device': os_volume.attachments[0]['device'],
'instanceId': fakes.ID_EC2_INSTANCE_2,
'status': 'detaching',
'volumeId': fakes.ID_EC2_VOLUME_2},
resp)
self.nova_volumes.delete_server_volume.assert_called_once_with(
fakes.ID_OS_INSTANCE_2, fakes.ID_OS_VOLUME_2)
self.cinder.volumes.get.assert_called_once_with(fakes.ID_OS_VOLUME_2)
def test_detach_volume_invalid_parameters(self):
self.db_api.get_item_by_id.return_value = fakes.DB_VOLUME_1
self.cinder.volumes.get.return_value = (
fakes.CinderVolume(fakes.OS_VOLUME_1))
resp = self.execute('DetachVolume',
{'VolumeId': fakes.ID_EC2_VOLUME_1})
self.assertEqual(400, resp['http_status_code'])
self.assertEqual('IncorrectState', resp['Error']['Code'])