# Copyright 2014 # The Cloudscaling Group, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import datetime import itertools import random from glanceclient import exc as glance_exception import mock from novaclient import exceptions as nova_exception from oslotest import base as test_base from ec2api.api import instance as instance_api from ec2api import exception from ec2api.tests.unit import base from ec2api.tests.unit import fakes from ec2api.tests.unit import matchers from ec2api.tests.unit import tools class InstanceTestCase(base.ApiTestCase): # TODO(ft): make negative tests on invalid parameters def setUp(self): super(InstanceTestCase, self).setUp() network_interface_api_patcher = mock.patch( 'ec2api.api.instance.network_interface_api') self.network_interface_api = network_interface_api_patcher.start() self.addCleanup(network_interface_api_patcher.stop) address_api_patcher = mock.patch('ec2api.api.address') self.address_api = address_api_patcher.start() self.addCleanup(address_api_patcher.stop) security_group_api_patcher = mock.patch('ec2api.api.security_group') self.security_group_api = security_group_api_patcher.start() self.addCleanup(security_group_api_patcher.stop) utils_generate_uid_patcher = ( mock.patch('ec2api.api.instance._utils_generate_uid')) self.utils_generate_uid = utils_generate_uid_patcher.start() self.addCleanup(utils_generate_uid_patcher.stop) novadb_patcher = (mock.patch('ec2api.api.instance.novadb')) self.novadb = novadb_patcher.start() self.addCleanup(novadb_patcher.stop) format_security_groups_ids_names = ( self.security_group_api.format_security_groups_ids_names) format_security_groups_ids_names.return_value = {} self.fake_flavor = mock.Mock() self.fake_flavor.configure_mock(name='fake_flavor') self.nova_flavors.get.return_value = self.fake_flavor self.nova_flavors.list.return_value = [self.fake_flavor] @mock.patch('ec2api.api.instance.InstanceEngineNeutron.' 'get_ec2_network_interfaces') @mock.patch('ec2api.api.instance.InstanceEngineNeutron.' 'get_vpc_default_security_group_id') def test_run_instances(self, get_vpc_default_security_group_id, get_ec2_network_interfaces): """Run instance with various network interface settings.""" instance_api.instance_engine = ( instance_api.InstanceEngineNeutron()) self.db_api.get_item_by_id.side_effect = ( fakes.get_db_api_get_item_by_id( {fakes.ID_EC2_SUBNET_1: fakes.DB_SUBNET_1, fakes.ID_EC2_NETWORK_INTERFACE_1: copy.deepcopy(fakes.DB_NETWORK_INTERFACE_1), fakes.ID_EC2_IMAGE_1: fakes.DB_IMAGE_1})) self.db_api.get_item_ids.side_effect = ( fakes.get_db_api_get_item_by_id({ (fakes.ID_OS_IMAGE_ARI_1,): [(fakes.ID_EC2_IMAGE_ARI_1, fakes.ID_OS_IMAGE_ARI_1)], (fakes.ID_OS_IMAGE_AKI_1,): [(fakes.ID_EC2_IMAGE_AKI_1, fakes.ID_OS_IMAGE_AKI_1)]})) self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1) self.network_interface_api.create_network_interface.return_value = ( {'networkInterface': fakes.EC2_NETWORK_INTERFACE_1}) self.db_api.add_item.return_value = fakes.DB_INSTANCE_1 self.nova_servers.create.return_value = ( fakes.OSInstance( fakes.ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'}, image={'id': fakes.ID_OS_IMAGE_1})) self.novadb.instance_get_by_uuid.return_value = fakes.NOVADB_INSTANCE_1 self.novadb.block_device_mapping_get_all_by_instance.return_value = [] self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1 get_vpc_default_security_group_id.return_value = None def do_check(params, create_network_interface_kwargs=None, delete_on_termination=None): delete_port_on_termination = ( create_network_interface_kwargs is not None if delete_on_termination is None else delete_on_termination) eni = fakes.gen_ec2_network_interface( fakes.ID_EC2_NETWORK_INTERFACE_1, fakes.EC2_SUBNET_1, [fakes.IP_NETWORK_INTERFACE_1], description=fakes.DESCRIPTION_NETWORK_INTERFACE_1, ec2_instance_id=fakes.ID_EC2_INSTANCE_1, device_index=0, delete_on_termination=delete_port_on_termination) expected_reservation = fakes.gen_ec2_reservation( fakes.ID_EC2_RESERVATION_1, [fakes.gen_ec2_instance( fakes.ID_EC2_INSTANCE_1, private_ip_address=fakes.IP_NETWORK_INTERFACE_1, ec2_network_interfaces=[eni], image_id=fakes.ID_EC2_IMAGE_1, kernel_id=fakes.ID_EC2_IMAGE_AKI_1, ramdisk_id=fakes.ID_EC2_IMAGE_ARI_1, reservation_id=fakes.ID_EC2_RESERVATION_1)]) get_ec2_network_interfaces.return_value = { fakes.ID_EC2_INSTANCE_1: [eni]} params.update({'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'MinCount': '1', 'MaxCount': '1'}) resp = self.execute('RunInstances', params) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') self.assertThat(resp, matchers.DictMatches(expected_reservation)) if create_network_interface_kwargs is not None: (self.network_interface_api. create_network_interface.assert_called_once_with( mock.ANY, fakes.ID_EC2_SUBNET_1, **create_network_interface_kwargs)) self.nova_servers.create.assert_called_once_with( '%s-%s' % (fakes.ID_EC2_RESERVATION_1, 0), fakes.ID_OS_IMAGE_1, self.fake_flavor, min_count=1, max_count=1, kernel_id=None, ramdisk_id=None, availability_zone=None, block_device_mapping={}, security_groups=None, nics=[{'port-id': fakes.ID_OS_PORT_1}], key_name=None, userdata=None) self.db_api.add_item.assert_called_once_with( mock.ANY, 'i', tools.purge_dict(fakes.DB_INSTANCE_1, ('id',))) (self.network_interface_api. _attach_network_interface_item.assert_called_once_with( mock.ANY, fakes.DB_NETWORK_INTERFACE_1, fakes.ID_EC2_INSTANCE_1, 0, delete_on_termination=delete_port_on_termination)) self.novadb.instance_get_by_uuid.assert_called_once_with( mock.ANY, fakes.ID_OS_INSTANCE_1) get_ec2_network_interfaces.assert_called_once_with( mock.ANY, instance_ids=[fakes.ID_EC2_INSTANCE_1]) self.assertEqual(2, self.db_api.get_item_ids.call_count) self.db_api.get_item_ids.assert_any_call( mock.ANY, 'aki', (fakes.ID_OS_IMAGE_AKI_1,)) self.db_api.get_item_ids.assert_any_call( mock.ANY, 'ari', (fakes.ID_OS_IMAGE_ARI_1,)) self.network_interface_api.reset_mock() self.nova_servers.reset_mock() self.db_api.reset_mock() self.novadb.reset_mock() get_ec2_network_interfaces.reset_mock() do_check({'SubnetId': fakes.ID_EC2_SUBNET_1}, create_network_interface_kwargs={}) do_check({'SubnetId': fakes.ID_EC2_SUBNET_1, 'SecurityGroupId.1': fakes.ID_EC2_SECURITY_GROUP_1, 'SecurityGroupId.2': fakes.ID_EC2_SECURITY_GROUP_2}, create_network_interface_kwargs={ 'security_group_id': [fakes.ID_EC2_SECURITY_GROUP_1, fakes.ID_EC2_SECURITY_GROUP_2]}) do_check({'SubnetId': fakes.ID_EC2_SUBNET_1, 'PrivateIpAddress': fakes.IP_FIRST_SUBNET_1}, create_network_interface_kwargs={ 'private_ip_address': fakes.IP_FIRST_SUBNET_1}) do_check({'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1, 'NetworkInterface.1.SecurityGroupId.1': fakes.ID_EC2_SECURITY_GROUP_1, 'NetworkInterface.1.PrivateIpAddress.1': fakes.IP_FIRST_SUBNET_1}, create_network_interface_kwargs={ 'security_group_id': [fakes.ID_EC2_SECURITY_GROUP_1], 'private_ip_address': [fakes.IP_FIRST_SUBNET_1]}) do_check({'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1, 'NetworkInterface.1.DeleteOnTermination': 'False'}, create_network_interface_kwargs={}, delete_on_termination=False) do_check({'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1, 'NetworkInterface.1.SecurityGroupId.1': fakes.ID_EC2_SECURITY_GROUP_1, 'NetworkInterface.1.DeleteOnTermination': 'False'}, create_network_interface_kwargs={ 'security_group_id': [fakes.ID_EC2_SECURITY_GROUP_1]}, delete_on_termination=False) do_check({'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.NetworkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1}) @mock.patch('ec2api.api.instance.InstanceEngineNeutron.' 'get_ec2_network_interfaces') @mock.patch('ec2api.api.instance.InstanceEngineNeutron.' 'get_vpc_default_security_group_id') def test_run_instances_multiple_networks(self, get_vpc_default_security_group_id, get_ec2_network_interfaces): """Run 2 instances at once on 2 subnets in all combinations.""" instance_api.instance_engine = ( instance_api.InstanceEngineNeutron()) self._build_multiple_data_model() self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1) get_vpc_default_security_group_id.return_value = None get_ec2_network_interfaces.return_value = dict( (ec2_instance_id, list(eni_pair)) for ec2_instance_id, eni_pair in zip( self.IDS_EC2_INSTANCE, zip(*[iter(self.EC2_ATTACHED_ENIS)] * 2))) ec2_instances = [ fakes.gen_ec2_instance(ec2_instance_id, launch_index=l_i, ec2_network_interfaces=eni_pair, reservation_id=fakes.ID_EC2_RESERVATION_1) for l_i, (ec2_instance_id, eni_pair) in enumerate(zip( self.IDS_EC2_INSTANCE, zip(*[iter(self.EC2_ATTACHED_ENIS)] * 2)))] ec2_reservation = fakes.gen_ec2_reservation(fakes.ID_EC2_RESERVATION_1, ec2_instances) fakes_db_items = dict((eni['id'], eni) for eni in self.DB_DETACHED_ENIS) fakes_db_items.update({ fakes.ID_EC2_IMAGE_1: fakes.DB_IMAGE_1, fakes.ID_EC2_SUBNET_1: fakes.DB_SUBNET_1, fakes.ID_EC2_SUBNET_2: fakes.DB_SUBNET_2}) self.db_api.get_item_by_id.side_effect = ( fakes.get_db_api_get_item_by_id(fakes_db_items)) self.network_interface_api.create_network_interface.side_effect = ( [{'networkInterface': eni} for eni in self.EC2_DETACHED_ENIS]) self.nova_servers.create.side_effect = [ fakes.OSInstance(os_instance_id, {'id': 'fakeFlavorId'}) for os_instance_id in self.IDS_OS_INSTANCE] self.novadb.instance_get_by_uuid.side_effect = self.NOVADB_INSTANCES self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1 self.db_api.add_item.side_effect = self.DB_INSTANCES resp = self.execute( 'RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'MinCount': '2', 'MaxCount': '2', 'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1, 'NetworkInterface.2.DeviceIndex': '1', 'NetworkInterface.2.SubnetId': fakes.ID_EC2_SUBNET_2, 'NetworkInterface.2.DeleteOnTermination': 'False'}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') self.assertThat(resp, matchers.DictMatches(ec2_reservation), verbose=True) self.network_interface_api.create_network_interface.assert_has_calls([ mock.call(mock.ANY, ec2_subnet_id) for ec2_subnet_id in self.IDS_EC2_SUBNET_BY_PORT]) self.nova_servers.create.assert_has_calls([ mock.call( '%s-%s' % (fakes.ID_EC2_RESERVATION_1, launch_index), fakes.ID_OS_IMAGE_1, self.fake_flavor, min_count=1, max_count=1, kernel_id=None, ramdisk_id=None, availability_zone=None, block_device_mapping={}, security_groups=None, nics=[{'port-id': port_id} for port_id in port_ids], key_name=None, userdata=None) for launch_index, port_ids in enumerate( zip(*[iter(self.IDS_OS_PORT)] * 2))]) (self.network_interface_api. _attach_network_interface_item.assert_has_calls([ mock.call(mock.ANY, eni, ec2_instance_id, dev_ind, delete_on_termination=dot) for eni, ec2_instance_id, dev_ind, dot in zip( self.DB_DETACHED_ENIS, itertools.chain(*map(lambda i: [i] * 2, self.IDS_EC2_INSTANCE)), [0, 1] * 2, [True, False, True, False])])) self.db_api.add_item.assert_has_calls([ mock.call(mock.ANY, 'i', tools.purge_dict(db_instance, ['id'])) 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.InstanceEngineNeutron.' 'get_ec2_classic_os_network') def test_run_instances_other_parameters(self, get_ec2_classic_os_network, format_reservation, parse_block_device_mapping): self.db_api.get_item_by_id.side_effect = ( fakes.get_db_api_get_item_by_id({ fakes.ID_EC2_IMAGE_1: fakes.DB_IMAGE_1, fakes.ID_EC2_IMAGE_AKI_1: fakes.DB_IMAGE_AKI_1, fakes.ID_EC2_IMAGE_ARI_1: fakes.DB_IMAGE_ARI_1})) self.glance.images.get.side_effect = ( fakes.get_by_1st_arg_getter({ fakes.ID_OS_IMAGE_1: fakes.OSImage(fakes.OS_IMAGE_1), fakes.ID_OS_IMAGE_AKI_1: fakes.OSImage(fakes.OS_IMAGE_AKI_1), fakes.ID_OS_IMAGE_ARI_1: fakes.OSImage(fakes.OS_IMAGE_ARI_1)})) get_ec2_classic_os_network.return_value = {'id': fakes.random_os_id()} format_reservation.return_value = {} parse_block_device_mapping.return_value = 'fake_bdm' def do_check(engine, extra_kwargs={}, extra_db_instance={}): instance_api.instance_engine = engine resp = self.execute( 'RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'MinCount': '1', 'MaxCount': '1', 'KernelId': fakes.ID_EC2_IMAGE_AKI_1, 'RamdiskId': fakes.ID_EC2_IMAGE_ARI_1, 'SecurityGroup.1': 'default', '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.nova_servers.create.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY, min_count=1, max_count=1, userdata=None, kernel_id=fakes.ID_OS_IMAGE_AKI_1, ramdisk_id=fakes.ID_OS_IMAGE_ARI_1, key_name=None, block_device_mapping='fake_bdm', availability_zone='fake_zone', security_groups=['default'], **extra_kwargs) self.nova_servers.reset_mock() db_instance = {'os_id': mock.ANY, 'reservation_id': mock.ANY, 'launch_index': 0, 'client_token': 'fake_client_token'} db_instance.update(extra_db_instance) self.db_api.add_item.assert_called_once_with( mock.ANY, 'i', db_instance) 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}}], fakes.OSImage(fakes.OS_IMAGE_1)) parse_block_device_mapping.reset_mock() do_check( instance_api.InstanceEngineNeutron(), extra_kwargs={ 'nics': [ {'net-id': get_ec2_classic_os_network.return_value['id']}], }, extra_db_instance={'vpc_id': None}) do_check(instance_api.InstanceEngineNova()) @mock.patch('ec2api.api.instance._format_reservation') @mock.patch('ec2api.api.instance._get_os_instances_by_instances') def test_idempotent_run(self, get_os_instances_by_instances, format_reservation): instance_engine = mock.MagicMock() instance_api.instance_engine = instance_engine get_ec2_network_interfaces = instance_engine.get_ec2_network_interfaces instances = [{'id': fakes.random_ec2_id('i'), 'os_id': fakes.random_os_id(), 'reservation_id': fakes.random_ec2_id('r'), 'client_token': 'client-token-%s' % ind} for ind in range(3)] os_instances = [fakes.OSInstance(inst['os_id']) for inst in instances] format_reservation.return_value = {'key': 'value'} # NOTE(ft): check select corresponding instance by client_token self.db_api.get_items.return_value = [instances[0], instances[1]] get_os_instances_by_instances.return_value = [os_instances[1]] self.novadb.instance_get_by_uuid.return_value = 'novadb_instance' get_ec2_network_interfaces.return_value = 'ec2_network_interfaces' resp = self.execute('RunInstances', {'MinCount': '1', 'MaxCount': '1', 'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'ClientToken': 'client-token-1'}) self.assertEqual({'http_status_code': 200, 'key': 'value'}, resp) format_reservation.assert_called_once_with( mock.ANY, instances[1]['reservation_id'], [(instances[1], os_instances[1], 'novadb_instance')], 'ec2_network_interfaces') get_os_instances_by_instances.assert_called_once_with( mock.ANY, {instances[1]['os_id']: instances[1]}) self.novadb.instance_get_by_uuid.assert_called_once_with( mock.ANY, os_instances[1].id) get_ec2_network_interfaces.assert_called_once_with( mock.ANY, [instances[1]['id']]) # NOTE(ft): check pass to general run_instances logic if no # corresponding client_token is found instance_engine.run_instances.return_value = {} resp = self.execute('RunInstances', {'MinCount': '1', 'MaxCount': '1', 'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'ClientToken': 'client-token-2'}) self.assertTrue(instance_engine.run_instances.called) # NOTE(ft): check pass to general run_instances logic if no more # corresponding OS instance exists instance_engine.reset_mock() get_os_instances_by_instances.return_value = [] resp = self.execute('RunInstances', {'MinCount': '1', 'MaxCount': '1', 'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'ClientToken': 'client-token-1'}) self.assertTrue(instance_engine.run_instances.called) # NOTE(ft): check case for several instances with same client_token, # but one no more exists in OS format_reservation.reset_mock() get_os_instances_by_instances.reset_mock() instance_engine.reset_mock() self.novadb.reset_mock() for inst in instances: inst['reservation_id'] = instances[0]['reservation_id'] inst['client_token'] = 'client-token' self.db_api.get_items.return_value = instances get_os_instances_by_instances.return_value = [os_instances[0], os_instances[2]] self.novadb.instance_get_by_uuid.side_effect = ['novadb-instance-0', 'novadb-instance-2'] get_ec2_network_interfaces.return_value = 'ec2_network_interfaces' resp = self.execute('RunInstances', {'MinCount': '1', 'MaxCount': '1', 'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'ClientToken': 'client-token'}) self.assertEqual({'http_status_code': 200, 'key': 'value'}, resp) format_reservation.assert_called_once_with( mock.ANY, instances[0]['reservation_id'], [(instances[0], os_instances[0], 'novadb-instance-0'), (instances[2], os_instances[2], 'novadb-instance-2')], 'ec2_network_interfaces') get_os_instances_by_instances.assert_called_once_with( mock.ANY, dict((inst['os_id'], inst) for inst in instances)) self.assertEqual([mock.call(mock.ANY, os_instances[0].id), mock.call(mock.ANY, os_instances[2].id)], self.novadb.instance_get_by_uuid.mock_calls) get_ec2_network_interfaces.assert_called_once_with( mock.ANY, [instances[0]['id'], instances[2]['id']]) def test_run_instances_rollback(self): instance_api.instance_engine = ( instance_api.InstanceEngineNeutron()) self.db_api.get_item_by_id.side_effect = ( fakes.get_db_api_get_item_by_id( {fakes.ID_EC2_IMAGE_1: fakes.DB_IMAGE_1, fakes.ID_EC2_SUBNET_1: fakes.DB_SUBNET_1, fakes.ID_EC2_NETWORK_INTERFACE_1: fakes.DB_NETWORK_INTERFACE_1})) self.db_api.get_item_ids.return_value = [ (fakes.ID_EC2_IMAGE_1, fakes.ID_OS_IMAGE_1)] self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1) self.network_interface_api.create_network_interface.return_value = ( {'networkInterface': fakes.EC2_NETWORK_INTERFACE_1}) self.db_api.add_item.return_value = fakes.DB_INSTANCE_1 self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1 self.nova_servers.create.return_value = ( fakes.OSInstance(fakes.ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'}, image={'id': fakes.ID_OS_IMAGE_1})) self.novadb.instance_get_by_uuid.side_effect = Exception() def do_check(params, new_port=True, delete_on_termination=None): mock_manager = mock.MagicMock() mock_manager.attach_mock(self.network_interface_api, 'network_interface_api') mock_manager.attach_mock(self.neutron, 'neutron') mock_manager.attach_mock(self.nova_servers, 'nova_servers') params.update({'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'MinCount': '1', 'MaxCount': '1'}) self.execute('RunInstances', params) calls = [] calls.append( mock.call.network_interface_api._detach_network_interface_item( mock.ANY, fakes.DB_NETWORK_INTERFACE_1)) if not new_port: calls.append( mock.call.neutron.update_port( fakes.ID_OS_PORT_1, {'port': {'device_id': '', 'device_owner': ''}})) calls.append( mock.call.nova_servers.delete(fakes.ID_OS_INSTANCE_1)) if new_port: calls.append( mock.call.network_interface_api.delete_network_interface( mock.ANY, network_interface_id=fakes.ID_EC2_NETWORK_INTERFACE_1)) mock_manager.assert_has_calls(calls) self.db_api.delete_item.assert_called_once_with( mock.ANY, fakes.ID_EC2_INSTANCE_1) self.network_interface_api.reset_mock() self.neutron.reset_mock() self.nova_servers.reset_mock() self.db_api.reset_mock() do_check({'SubnetId': fakes.ID_EC2_SUBNET_1}) do_check({'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1}) do_check({'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1, 'NetworkInterface.1.DeleteOnTermination': 'False'}, delete_on_termination=False) do_check({'NetworkInterface.1.DeviceIndex': '0', 'NetworkInterface.1.NetworkInterfaceId': fakes.ID_EC2_NETWORK_INTERFACE_1}, new_port=False) @mock.patch('ec2api.api.instance.InstanceEngineNeutron.' 'get_ec2_network_interfaces') @mock.patch('ec2api.api.instance._format_reservation') def test_run_instances_multiply_rollback(self, format_reservation, get_ec2_network_interfaces): instances = [{'id': fakes.random_ec2_id('i'), 'os_id': fakes.random_os_id()} for dummy in range(3)] os_instances = [fakes.OSInstance(inst['os_id']) for inst in instances] network_interfaces = [{'id': fakes.random_ec2_id('eni'), 'os_id': fakes.random_os_id()} for dummy in range(3)] self.db_api.get_item_by_id.side_effect = ( fakes.get_db_api_get_item_by_id( dict((item['id'], item) for item in itertools.chain([fakes.DB_IMAGE_1, fakes.DB_SUBNET_1], network_interfaces)))) self.db_api.get_item_ids.return_value = [ (fakes.ID_EC2_IMAGE_1, fakes.ID_OS_IMAGE_1)] self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1) self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1 get_ec2_network_interfaces.return_value = [] def do_check(engine): instance_api.instance_engine = engine self.network_interface_api.create_network_interface.side_effect = [ {'networkInterface': {'networkInterfaceId': eni['id']}} for eni in network_interfaces] self.db_api.add_item.side_effect = instances self.nova_servers.create.side_effect = os_instances self.novadb.instance_get_by_uuid.side_effect = [ {}, {}, Exception()] format_reservation.side_effect = ( lambda _context, r_id, instance_info, *args, **kwargs: ( {'reservationId': r_id, 'instancesSet': [ {'instanceId': inst['id']} for inst, _os_inst, _novadb_inst in instance_info]})) resp = self.execute('RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'InstanceType': 'fake_flavor', 'MinCount': '2', 'MaxCount': '3', 'SubnetId': fakes.ID_EC2_SUBNET_1}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') self.assertThat(resp, matchers.DictMatches( {'reservationId': fakes.ID_EC2_RESERVATION_1, 'instancesSet': [ {'instanceId': inst['id']} for inst in instances[:2]]})) self.nova_servers.delete.assert_called_once_with( instances[2]['os_id']) self.db_api.delete_item.assert_called_once_with( mock.ANY, instances[2]['id']) self.nova_servers.reset_mock() self.db_api.reset_mock() do_check(instance_api.InstanceEngineNeutron()) (self.network_interface_api._detach_network_interface_item. assert_called_once_with(mock.ANY, network_interfaces[2])) (self.network_interface_api.delete_network_interface. assert_called_once_with( mock.ANY, network_interface_id=network_interfaces[2]['id'])) do_check(instance_api.InstanceEngineNova()) def test_run_instances_invalid_parameters(self): resp = self.execute('RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '0', 'MaxCount': '0'}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('InvalidParameterValue', resp['Error']['Code']) resp = self.execute('RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '1', 'MaxCount': '0'}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('InvalidParameterValue', resp['Error']['Code']) resp = self.execute('RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '0', 'MaxCount': '1'}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('InvalidParameterValue', resp['Error']['Code']) resp = self.execute('RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '2', 'MaxCount': '1'}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('InvalidParameterValue', resp['Error']['Code']) @mock.patch.object(fakes.OSInstance, 'delete', autospec=True) @mock.patch.object(fakes.OSInstance, 'get', autospec=True) def test_terminate_instances(self, os_instance_get, os_instance_delete): """Terminate 2 instances in one request.""" instance_api.instance_engine = ( instance_api.InstanceEngineNeutron()) self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2] self.nova_servers.get.side_effect = [fakes.OS_INSTANCE_1, fakes.OS_INSTANCE_2] self.db_api.get_items.side_effect = fakes.get_db_api_get_items( {'eni': [fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2], 'eipalloc': [fakes.DB_ADDRESS_1, fakes.DB_ADDRESS_2]}) resp = self.execute('TerminateInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, 'InstanceId.2': fakes.ID_EC2_INSTANCE_2}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') fake_state_change = {'previousState': {'code': 0, 'name': 'pending'}, 'currentState': {'code': 0, 'name': 'pending'}} self.assertThat( resp, matchers.DictMatches( {'instancesSet': [ tools.update_dict({'instanceId': fakes.ID_EC2_INSTANCE_1}, fake_state_change), tools.update_dict({'instanceId': fakes.ID_EC2_INSTANCE_2}, fake_state_change)]})) self.db_api.get_items_by_ids.assert_called_once_with( mock.ANY, 'i', set([fakes.ID_EC2_INSTANCE_1, fakes.ID_EC2_INSTANCE_2])) (self.network_interface_api. detach_network_interface.assert_called_once_with( mock.ANY, fakes.ID_EC2_NETWORK_INTERFACE_2_ATTACH)) self.assertEqual(2, self.nova_servers.get.call_count) self.nova_servers.get.assert_any_call(fakes.ID_OS_INSTANCE_1) self.nova_servers.get.assert_any_call(fakes.ID_OS_INSTANCE_2) self.assertEqual( 0, self.address_api.dissassociate_address_item.call_count) self.assertFalse(self.db_api.delete_item.called) self.assertEqual(2, os_instance_delete.call_count) self.assertEqual(2, os_instance_get.call_count) for call_num, inst_id in enumerate([fakes.OS_INSTANCE_1, fakes.OS_INSTANCE_2]): self.assertEqual(mock.call(inst_id), os_instance_delete.call_args_list[call_num]) self.assertEqual(mock.call(inst_id), os_instance_get.call_args_list[call_num]) def test_terminate_instances_multiple_networks(self): """Terminate an instance with various combinations of ports.""" self._build_multiple_data_model() fake_state_change = {'previousState': {'code': 16, 'name': 'running'}, 'currentState': {'code': 16, 'name': 'running'}} ec2_terminate_instances_result = { 'instancesSet': [ tools.update_dict({'instanceId': fakes.ID_EC2_INSTANCE_1}, fake_state_change), tools.update_dict({'instanceId': fakes.ID_EC2_INSTANCE_2}, fake_state_change)]} self.db_api.get_items_by_ids.return_value = self.DB_INSTANCES self.nova_servers.get.side_effect = ( lambda ec2_id: fakes.OSInstance(ec2_id, vm_state='active')) def do_check(mock_eni_list=[], detached_enis=[], deleted_enis=[]): self.db_api.get_items.side_effect = fakes.get_db_api_get_items({ 'eni': copy.deepcopy(mock_eni_list) + [self.DB_FAKE_ENI], 'eipalloc': []}) resp = self.execute('TerminateInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, 'InstanceId.2': fakes.ID_EC2_INSTANCE_2}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') self.assertThat( resp, matchers.DictMatches(ec2_terminate_instances_result)) detach_network_interface = ( self.network_interface_api.detach_network_interface) self.assertEqual(len(detached_enis), detach_network_interface.call_count) for ec2_eni in detached_enis: detach_network_interface.assert_any_call( mock.ANY, ('eni-attach-%s' % ec2_eni['id'].split('-')[-1])) self.assertFalse(self.db_api.delete_item.called) detach_network_interface.reset_mock() self.db_api.delete_item.reset_mock() # NOTE(ft): 2 instances; the first has 2 correct ports; # the second has the first port attached by EC2 API but later detached # by OpenStack and the second port created through EC2 API but # attached by OpenStack only do_check( mock_eni_list=[ self.DB_ATTACHED_ENIS[0], self.DB_ATTACHED_ENIS[1], self.DB_ATTACHED_ENIS[2], self.DB_DETACHED_ENIS[3]], detached_enis=[self.DB_ATTACHED_ENIS[1]], deleted_enis=[self.DB_ATTACHED_ENIS[0], self.DB_ATTACHED_ENIS[2]]) # NOTE(ft): 2 instances: the first has the first port attached by # OpenStack only, the second port is attached correctly; # the second instance has one port created and attached by OpenStack # only do_check( mock_eni_list=[self.DB_ATTACHED_ENIS[1]], detached_enis=[self.DB_ATTACHED_ENIS[1]], deleted_enis=[]) def test_terminate_instances_invalid_parameters(self): resp = self.execute('TerminateInstances', {'InstanceId.1': fakes.random_ec2_id('i')}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('InvalidInstanceID.NotFound', resp['Error']['Code']) @mock.patch('ec2api.api.instance._get_os_instances_by_instances') def _test_instances_operation(self, operation, os_instance_operation, valid_state, invalid_state, get_os_instances_by_instances): os_instance_1 = copy.deepcopy(fakes.OS_INSTANCE_1) os_instance_2 = copy.deepcopy(fakes.OS_INSTANCE_2) for inst in (os_instance_1, os_instance_2): setattr(inst, 'OS-EXT-STS:vm_state', valid_state) self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2] get_os_instances_by_instances.return_value = [os_instance_1, os_instance_2] resp = self.execute(operation, {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, 'InstanceId.2': fakes.ID_EC2_INSTANCE_2}) self.assertEqual({'http_status_code': 200, 'return': True}, resp) self.assertEqual([mock.call(os_instance_1), mock.call(os_instance_2)], os_instance_operation.mock_calls) self.db_api.get_items_by_ids.assert_called_once_with( mock.ANY, 'i', set([fakes.ID_EC2_INSTANCE_1, fakes.ID_EC2_INSTANCE_2])) get_os_instances_by_instances.assert_called_once_with( mock.ANY, [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2], exactly=True) setattr(os_instance_2, 'OS-EXT-STS:vm_state', invalid_state) os_instance_operation.reset_mock() resp = self.execute('StartInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, 'InstanceId.2': fakes.ID_EC2_INSTANCE_2}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('IncorrectInstanceState', resp['Error']['Code']) self.assertEqual(0, os_instance_operation.call_count) @mock.patch.object(fakes.OSInstance, 'start', autospec=True) def test_start_instances(self, os_instance_start): self._test_instances_operation('StartInstances', os_instance_start, instance_api.vm_states_STOPPED, instance_api.vm_states_ACTIVE) @mock.patch.object(fakes.OSInstance, 'stop', autospec=True) def test_stop_instances(self, os_instance_stop): self._test_instances_operation('StopInstances', os_instance_stop, instance_api.vm_states_ACTIVE, instance_api.vm_states_STOPPED) @mock.patch.object(fakes.OSInstance, 'reboot', autospec=True) def test_reboot_instances(self, os_instance_reboot): self._test_instances_operation('RebootInstances', os_instance_reboot, instance_api.vm_states_ACTIVE, instance_api.vm_states_BUILDING) @mock.patch('ec2api.openstack.common.timeutils.utcnow') def _test_instance_get_operation(self, operation, getter, key, utcnow): self.db_api.get_item_by_id.return_value = fakes.DB_INSTANCE_2 self.nova_servers.get.return_value = fakes.OS_INSTANCE_2 getter.return_value = 'fake_data' utcnow.return_value = datetime.datetime(2015, 1, 19, 23, 34, 45, 123) resp = self.execute(operation, {'InstanceId': fakes.ID_EC2_INSTANCE_2}) self.assertEqual({'http_status_code': 200, 'instanceId': fakes.ID_EC2_INSTANCE_2, 'timestamp': '2015-01-19T23:34:45.000Z', key: 'fake_data'}, resp) self.db_api.get_item_by_id.assert_called_once_with( mock.ANY, 'i', fakes.ID_EC2_INSTANCE_2) self.nova_servers.get.assert_called_once_with(fakes.ID_OS_INSTANCE_2) getter.assert_called_once_with(fakes.OS_INSTANCE_2) @mock.patch.object(fakes.OSInstance, 'get_password', autospec=True) def test_get_password_data(self, get_password): self._test_instance_get_operation('GetPasswordData', get_password, 'passwordData') @mock.patch.object(fakes.OSInstance, 'get_console_output', autospec=True) def test_console_output(self, get_console_output): self._test_instance_get_operation('GetConsoleOutput', get_console_output, 'output') def test_describe_instances(self): """Describe 2 instances, one of which is vpc instance.""" instance_api.instance_engine = ( instance_api.InstanceEngineNeutron()) self.db_api.get_items.side_effect = ( fakes.get_db_api_get_items( {'eni': [fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2], 'i': [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2], 'ami': [fakes.DB_IMAGE_1, fakes.DB_IMAGE_2], 'vol': [fakes.DB_VOLUME_1, fakes.DB_VOLUME_2, fakes.DB_VOLUME_3]})) self.db_api.get_item_ids.side_effect = ( fakes.get_db_api_get_item_by_id({ (fakes.ID_OS_IMAGE_ARI_1,): [(fakes.ID_EC2_IMAGE_ARI_1, fakes.ID_OS_IMAGE_ARI_1)], (fakes.ID_OS_IMAGE_AKI_1,): [(fakes.ID_EC2_IMAGE_AKI_1, fakes.ID_OS_IMAGE_AKI_1)]})) self.nova_servers.list.return_value = [fakes.OS_INSTANCE_1, fakes.OS_INSTANCE_2] self.novadb.instance_get_by_uuid.side_effect = ( fakes.get_db_api_get_items({ fakes.ID_OS_INSTANCE_1: fakes.NOVADB_INSTANCE_1, fakes.ID_OS_INSTANCE_2: fakes.NOVADB_INSTANCE_2})) self.novadb.block_device_mapping_get_all_by_instance.side_effect = ( fakes.get_db_api_get_items({ fakes.ID_OS_INSTANCE_1: fakes.NOVADB_BDM_INSTANCE_1, fakes.ID_OS_INSTANCE_2: fakes.NOVADB_BDM_INSTANCE_2})) self.network_interface_api.describe_network_interfaces.side_effect = ( lambda *args, **kwargs: copy.deepcopy({ 'networkInterfaceSet': [fakes.EC2_NETWORK_INTERFACE_1, fakes.EC2_NETWORK_INTERFACE_2]})) resp = self.execute('DescribeInstances', {}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') self.assertThat(resp, matchers.DictMatches( {'reservationSet': [fakes.EC2_RESERVATION_1, fakes.EC2_RESERVATION_2]}, orderless_lists=True)) self.db_api.get_items_by_ids = tools.CopyingMock( return_value=[fakes.DB_INSTANCE_1]) resp = self.execute('DescribeInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') self.assertThat(resp, matchers.DictMatches( {'reservationSet': [fakes.EC2_RESERVATION_1]}, orderless_lists=True)) self.db_api.get_items_by_ids.assert_called_once_with( mock.ANY, 'i', set([fakes.ID_EC2_INSTANCE_1])) (self.network_interface_api.describe_network_interfaces. assert_called_with( mock.ANY, network_interface_id=[fakes.ID_EC2_NETWORK_INTERFACE_2])) self.check_filtering( 'DescribeInstances', 'reservationSet', [('block-device-mapping.device-name', fakes.ROOT_DEVICE_NAME_INSTANCE_2), ('client-token', fakes.CLIENT_TOKEN_INSTANCE_2), # TODO(ft): support filtering by none/empty value # ('dns-name', ''), ('image-id', fakes.ID_EC2_IMAGE_1), ('instance-id', fakes.ID_EC2_INSTANCE_2), ('instance-type', 'fake_flavor'), ('ip-address', fakes.IP_ADDRESS_2), ('kernel-id', fakes.ID_EC2_IMAGE_AKI_1), ('key-name', fakes.NAME_KEY_PAIR), # TODO(ft): support filtering by a none/empty value # ('launch-index', 0), # TODO(ft): fill the field in fakes with correct value # ('launch-time', ), ('private-dns-name', '%s-%s' % (fakes.ID_EC2_RESERVATION_1, 0)), ('private-ip-address', fakes.IP_NETWORK_INTERFACE_2), ('ramdisk-id', fakes.ID_EC2_IMAGE_ARI_1), ('root-device-name', fakes.ROOT_DEVICE_NAME_INSTANCE_1), ('root-device-type', 'ebs'), ('subnet-id', fakes.ID_EC2_SUBNET_2), ('vpc-id', fakes.ID_EC2_VPC_1), ('network-interface.description', fakes.DESCRIPTION_NETWORK_INTERFACE_2), ('network-interface.subnet-id', fakes.ID_EC2_SUBNET_2), ('network-interface.vpc-id', fakes.ID_EC2_VPC_1), ('network-interface.network-interface.id', fakes.ID_EC2_NETWORK_INTERFACE_2), ('network-interface.owner-id', fakes.ID_OS_PROJECT), # TODO(ft): support filtering by a boolean value # ('network-interface.requester-managed', False), ('network-interface.status', 'in-use'), # TODO(ft): declare a constant for the mac in fakes ('network-interface.mac-address', 'fb:10:2e:b2:ba:b7'), # TODO(ft): support filtering by a boolean value # ('network-interface.source-destination-check', True), ('reservation-id', fakes.ID_EC2_RESERVATION_1), ('owner-id', fakes.ID_OS_PROJECT)]) self.check_tag_support( 'DescribeInstances', ['reservationSet', 'instancesSet'], fakes.ID_EC2_INSTANCE_1, 'instanceId') def test_describe_instances_ec2_classic(self): instance_api.instance_engine = ( instance_api.InstanceEngineNova()) self.db_api.get_items.side_effect = ( fakes.get_db_api_get_items( {'i': [fakes.DB_INSTANCE_2], 'ami': [fakes.DB_IMAGE_1, fakes.DB_IMAGE_2], 'vol': [fakes.DB_VOLUME_1, fakes.DB_VOLUME_2, fakes.DB_VOLUME_3]})) self.nova_servers.list.return_value = [fakes.OS_INSTANCE_2] self.novadb.instance_get_by_uuid.return_value = ( fakes.NOVADB_INSTANCE_2) self.novadb.block_device_mapping_get_all_by_instance.return_value = ( fakes.NOVADB_BDM_INSTANCE_2) resp = self.execute('DescribeInstances', {}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') self.assertThat(resp, matchers.DictMatches( {'reservationSet': [fakes.EC2_RESERVATION_2]}, orderless_lists=True)) def test_describe_instances_mutliple_networks(self): """Describe 2 instances with various combinations of network.""" instance_api.instance_engine = ( instance_api.InstanceEngineNeutron()) self._build_multiple_data_model() self.db_api.get_items.side_effect = ( fakes.get_db_api_get_items( {'i': self.DB_INSTANCES, 'ami': [], 'vol': []})) self.novadb.instance_get_by_uuid.side_effect = ( fakes.get_db_api_get_items( dict((os_id, novadb_instance) for os_id, novadb_instance in zip( self.IDS_OS_INSTANCE, self.NOVADB_INSTANCES)))) describe_network_interfaces = ( self.network_interface_api.describe_network_interfaces) def do_check(ips_by_instance=[], ec2_enis_by_instance=[], ec2_instance_ips=[]): describe_network_interfaces.return_value = copy.deepcopy( {'networkInterfaceSet': list( itertools.chain(*ec2_enis_by_instance))}) self.nova_servers.list.return_value = [ fakes.OSInstance( os_id, {'id': 'fakeFlavorId'}, addresses=dict((subnet_name, [{'addr': addr, 'version': 4, 'OS-EXT-IPS:type': 'fixed'}]) for subnet_name, addr in ips)) for os_id, ips in zip( self.IDS_OS_INSTANCE, ips_by_instance)] resp = self.execute('DescribeInstances', {}) self.assertEqual(200, resp['http_status_code']) resp.pop('http_status_code') instances = [fakes.gen_ec2_instance( inst_id, launch_index=l_i, private_ip_address=ip, ec2_network_interfaces=enis, reservation_id=fakes.ID_EC2_RESERVATION_1) for l_i, (inst_id, ip, enis) in enumerate(zip( self.IDS_EC2_INSTANCE, ec2_instance_ips, ec2_enis_by_instance))] reservation_set = [fakes.gen_ec2_reservation( fakes.ID_EC2_RESERVATION_1, instances)] self.assertThat({'reservationSet': reservation_set}, matchers.DictMatches(resp, orderless_lists=True), verbose=True) def ip_info(ind): return (self.EC2_ATTACHED_ENIS[ind]['subnetId'], self.EC2_ATTACHED_ENIS[ind]['privateIpAddress']) # NOTE(ft): 2 instances; the first has 2 correct ports; # the second has the first port attached by EC2 API but later detached # by OpenStack and the second port created through EC2 API but # attached by OpenStack only do_check( ips_by_instance=[[ip_info(0), ip_info(1)], [ip_info(3)]], ec2_enis_by_instance=[ [self.EC2_ATTACHED_ENIS[0], self.EC2_ATTACHED_ENIS[1]], []], ec2_instance_ips=[fakes.IP_FIRST_SUBNET_1, fakes.IP_LAST_SUBNET_2]) # NOTE(ft): 2 instances: the first has the first port attached by # OpenStack only, the second port is attached correctly; # the second instance has one port created and attached by OpenStack # only do_check( ips_by_instance=[[ip_info(0), ip_info(1)], [ip_info(3)]], ec2_enis_by_instance=[[self.EC2_ATTACHED_ENIS[1]], []], ec2_instance_ips=[None, fakes.IP_LAST_SUBNET_2]) @mock.patch('ec2api.api.instance._remove_instances') def test_describe_instances_auto_remove(self, remove_instances): self.db_api.get_items.side_effect = ( fakes.get_db_api_get_items( {'i': [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2], 'ami': [], 'vol': [fakes.DB_VOLUME_2]})) self.nova_servers.list.return_value = [fakes.OS_INSTANCE_2] self.novadb.instance_get_by_uuid.return_value = ( fakes.NOVADB_INSTANCE_2) self.novadb.block_device_mapping_get_all_by_instance.return_value = ( fakes.NOVADB_BDM_INSTANCE_2) resp = self.execute('DescribeInstances', {}) self.assertThat(resp, matchers.DictMatches( {'http_status_code': 200, 'reservationSet': [fakes.EC2_RESERVATION_2]}, orderless_lists=True)) remove_instances.assert_called_once_with( mock.ANY, [fakes.DB_INSTANCE_1], purge_linked_items=False) @mock.patch('ec2api.api.instance._format_instance') def test_describe_instances_sorting(self, format_instance): db_instances = [ {'id': fakes.random_ec2_id('i'), 'os_id': fakes.random_os_id(), 'vpc_id': None, 'launch_index': i, 'reservation_id': fakes.ID_EC2_RESERVATION_1} for i in range(5)] random.shuffle(db_instances) self.db_api.get_items.side_effect = ( fakes.get_db_api_get_items( {'i': db_instances, 'ami': [], 'vol': []})) os_instances = [ fakes.OSInstance(inst['os_id']) for inst in db_instances] self.nova_servers.list.return_value = os_instances format_instance.side_effect = ( lambda context, instance, *args: ( {'instanceId': instance['id'], 'amiLaunchIndex': instance['launch_index']})) resp = self.execute('DescribeInstances', {}) self.assertEqual(200, resp['http_status_code']) self.assertEqual( [0, 1, 2, 3, 4], [inst['amiLaunchIndex'] for inst in resp['reservationSet'][0]['instancesSet']]) def test_describe_instances_invalid_parameters(self): resp = self.execute('DescribeInstances', {'InstanceId.1': fakes.random_ec2_id('i')}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('InvalidInstanceID.NotFound', resp['Error']['Code']) self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_2] resp = self.execute('DescribeInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_2, 'InstanceId.2': fakes.random_ec2_id('i')}) self.assertEqual(400, resp['http_status_code']) self.assertEqual('InvalidInstanceID.NotFound', resp['Error']['Code']) def test_describe_instance_attributes(self): self.db_api.get_item_by_id.side_effect = ( fakes.get_db_api_get_item_by_id({ fakes.ID_EC2_INSTANCE_1: fakes.DB_INSTANCE_1, fakes.ID_EC2_INSTANCE_2: fakes.DB_INSTANCE_2})) self.db_api.get_item_ids.side_effect = ( fakes.get_db_api_get_item_by_id({ (fakes.ID_OS_IMAGE_AKI_1,): [(fakes.ID_EC2_IMAGE_AKI_1, fakes.ID_OS_IMAGE_AKI_1)], (fakes.ID_OS_IMAGE_ARI_1,): [(fakes.ID_EC2_IMAGE_ARI_1, fakes.ID_OS_IMAGE_ARI_1)]})) self.nova_servers.get.side_effect = ( fakes.get_by_1st_arg_getter({ fakes.ID_OS_INSTANCE_1: fakes.OS_INSTANCE_1, fakes.ID_OS_INSTANCE_2: fakes.OS_INSTANCE_2})) self.novadb.instance_get_by_uuid.side_effect = ( fakes.get_db_api_get_items({ fakes.ID_OS_INSTANCE_1: fakes.NOVADB_INSTANCE_1, fakes.ID_OS_INSTANCE_2: fakes.NOVADB_INSTANCE_2})) self.novadb.block_device_mapping_get_all_by_instance.side_effect = ( fakes.get_db_api_get_items({ fakes.ID_OS_INSTANCE_1: fakes.NOVADB_BDM_INSTANCE_1, fakes.ID_OS_INSTANCE_2: fakes.NOVADB_BDM_INSTANCE_2})) self.cinder.volumes.get.return_value = fakes.CinderVolume( fakes.OS_VOLUME_2) self.db_api.get_items.return_value = [fakes.DB_VOLUME_2] def do_check(instance_id, attribute, expected): resp = self.execute('DescribeInstanceAttribute', {'InstanceId': instance_id, 'Attribute': attribute}) expected.update({'http_status_code': 200, 'instanceId': instance_id}) self.assertThat(resp, matchers.DictMatches(expected)) do_check(fakes.ID_EC2_INSTANCE_2, 'blockDeviceMapping', {'rootDeviceType': 'ebs', 'blockDeviceMapping': ( fakes.EC2_INSTANCE_2['blockDeviceMapping'])}) do_check(fakes.ID_EC2_INSTANCE_2, 'disableApiTermination', {'disableApiTermination': {'value': False}}) do_check(fakes.ID_EC2_INSTANCE_2, 'groupSet', {'groupSet': fakes.EC2_RESERVATION_2['groupSet']}) do_check(fakes.ID_EC2_INSTANCE_2, 'instanceInitiatedShutdownBehavior', {'instanceInitiatedShutdownBehavior': {'value': 'stop'}}) do_check(fakes.ID_EC2_INSTANCE_2, 'instanceType', {'instanceType': {'value': 'fake_flavor'}}) do_check(fakes.ID_EC2_INSTANCE_1, 'kernel', {'kernel': {'value': fakes.ID_EC2_IMAGE_AKI_1}}) do_check(fakes.ID_EC2_INSTANCE_1, 'ramdisk', {'ramdisk': {'value': fakes.ID_EC2_IMAGE_ARI_1}}) do_check(fakes.ID_EC2_INSTANCE_2, 'rootDeviceName', {'rootDeviceName': { 'value': fakes.ROOT_DEVICE_NAME_INSTANCE_2}}) do_check(fakes.ID_EC2_INSTANCE_2, 'userData', {'userData': {'value': fakes.USER_DATA_INSTANCE_2}}) def _build_multiple_data_model(self): # NOTE(ft): generate necessary fake data # We need 4 detached ports in 2 subnets. # Sequence of all ports list is s1i1, s2i1, s1i2, s2i2, # where sNiM - port info of instance iM on subnet sN. # We generate port ids but use subnet and instance ids since # fakes contain enough ids for subnets an instances, but not for ports. instances_count = 2 subnets_count = 2 ports_count = instances_count * subnets_count ids_ec2_eni = [fakes.random_ec2_id('eni') for _ in range(ports_count)] ids_os_port = [fakes.random_os_id() for _ in range(ports_count)] ids_ec2_subnet = (fakes.ID_EC2_SUBNET_1, fakes.ID_EC2_SUBNET_2) ids_ec2_subnet_by_port = ids_ec2_subnet * 2 ips = (fakes.IP_FIRST_SUBNET_1, fakes.IP_FIRST_SUBNET_2, fakes.IP_LAST_SUBNET_1, fakes.IP_LAST_SUBNET_2) ids_ec2_instance = [fakes.ID_EC2_INSTANCE_1, fakes.ID_EC2_INSTANCE_2] ids_ec2_instance_by_port = list( itertools.chain(*map(lambda i: [i] * subnets_count, ids_ec2_instance))) ids_os_instance = [fakes.ID_OS_INSTANCE_1, fakes.ID_OS_INSTANCE_2] dots_by_port = [True, False] * instances_count db_attached_enis = [ fakes.gen_db_network_interface( ec2_id, os_id, fakes.ID_EC2_VPC_1, subnet_ec2_id, ip, instance_id=instance_ec2_id, device_index=dev_ind, delete_on_termination=dot) for (ec2_id, os_id, subnet_ec2_id, ip, instance_ec2_id, dev_ind, dot) in zip( ids_ec2_eni, ids_os_port, ids_ec2_subnet_by_port, ips, ids_ec2_instance_by_port, range(subnets_count) * instances_count, dots_by_port)] db_detached_enis = [ fakes.gen_db_network_interface( ec2_id, os_id, fakes.ID_EC2_VPC_1, subnet_ec2_id, ip) for ec2_id, os_id, subnet_ec2_id, ip in zip( ids_ec2_eni, ids_os_port, ids_ec2_subnet_by_port, ips)] ec2_attached_enis = [ fakes.gen_ec2_network_interface( db_eni['id'], None, # ec2_subnet [db_eni['private_ip_address']], ec2_instance_id=ec2_instance_id, device_index=dev_ind, delete_on_termination=dot, ec2_subnet_id=ec2_subnet_id, ec2_vpc_id=fakes.ID_EC2_VPC_1) for db_eni, dot, ec2_subnet_id, ec2_instance_id, dev_ind in zip( db_attached_enis, dots_by_port, ids_ec2_subnet_by_port, ids_ec2_instance_by_port, range(subnets_count) * instances_count)] ec2_detached_enis = [ fakes.gen_ec2_network_interface( db_eni['id'], None, # ec2_subnet [db_eni['private_ip_address']], ec2_subnet_id=ec2_subnet_id, ec2_vpc_id=fakes.ID_EC2_VPC_1) for db_eni, ec2_subnet_id in zip( db_detached_enis, ids_ec2_subnet_by_port)] db_instances = [ {'id': db_id, 'os_id': os_id, 'vpc_id': fakes.ID_EC2_VPC_1, 'reservation_id': fakes.ID_EC2_RESERVATION_1, 'launch_index': l_i} for l_i, (db_id, os_id) in enumerate(zip( ids_ec2_instance, ids_os_instance))] novadb_instances = [ {'kernel_id': None, 'ramdisk_id': None, 'root_device_name': '/dev/vda', 'hostname': '%s-%s' % (fakes.ID_EC2_RESERVATION_1, l_i)} for l_i, ec2_id in enumerate(ids_ec2_instance)] self.IDS_EC2_SUBNET = ids_ec2_subnet self.IDS_OS_PORT = ids_os_port self.IDS_OS_INSTANCE = ids_os_instance self.IDS_EC2_INSTANCE = ids_ec2_instance self.IDS_EC2_SUBNET_BY_PORT = ids_ec2_subnet_by_port self.DB_ATTACHED_ENIS = db_attached_enis self.DB_DETACHED_ENIS = db_detached_enis self.EC2_ATTACHED_ENIS = ec2_attached_enis self.EC2_DETACHED_ENIS = ec2_detached_enis self.DB_INSTANCES = db_instances self.NOVADB_INSTANCES = novadb_instances # NOTE(ft): additional fake data to check filtering, etc self.DB_FAKE_ENI = fakes.gen_db_network_interface( fakes.random_ec2_id('eni'), fakes.random_os_id(), fakes.ID_EC2_VPC_1, fakes.ID_EC2_SUBNET_2, 'fake_ip') ec2_fake_eni = fakes.gen_ec2_network_interface( self.DB_FAKE_ENI['id'], fakes.EC2_SUBNET_2, ['fake_ip']) self.OS_FAKE_PORT = fakes.gen_os_port( fakes.random_os_id(), ec2_fake_eni, fakes.ID_OS_SUBNET_2, ['fake_ip']) # TODO(ft): add tests for get_vpc_default_security_group_id, class InstancePrivateTestCase(test_base.BaseTestCase): @mock.patch('glanceclient.client.Client') @mock.patch('ec2api.db.api.IMPL') def test_parse_image_parameters(self, db_api, glance): glance = glance.return_value fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) # NOTE(ft): check cases of not available image image = {'id': fakes.random_ec2_id('ami'), 'os_id': fakes.random_os_id()} db_api.get_public_items.return_value = [image] os_image = fakes.OSImage({ 'id': image['os_id'], 'status': None, 'properties': {}}) glance.images.get.return_value = os_image self.assertRaises(exception.ImageNotActive, instance_api._parse_image_parameters, fake_context, image['id'], None, None) os_image.status = 'active' os_image.properties['image_state'] = 'decrypting' self.assertRaises(exception.ImageNotActive, instance_api._parse_image_parameters, fake_context, image['id'], None, None) # NOTE(ft): check normal flow for user owned images db_api.get_public_items.return_value = [] db_api.get_item_by_id.side_effect = ( fakes.get_db_api_get_item_by_id({ fakes.ID_EC2_IMAGE_1: fakes.DB_IMAGE_1, fakes.ID_EC2_IMAGE_AKI_1: fakes.DB_IMAGE_AKI_1, fakes.ID_EC2_IMAGE_ARI_1: fakes.DB_IMAGE_ARI_1})) glance.images.get.side_effect = ( fakes.get_by_1st_arg_getter({ fakes.ID_OS_IMAGE_1: fakes.OSImage(fakes.OS_IMAGE_1), fakes.ID_OS_IMAGE_AKI_1: fakes.OSImage(fakes.OS_IMAGE_AKI_1), fakes.ID_OS_IMAGE_ARI_1: fakes.OSImage(fakes.OS_IMAGE_ARI_1)})) self.assertEqual( (fakes.OSImage(fakes.OS_IMAGE_1), fakes.ID_OS_IMAGE_AKI_1, fakes.ID_OS_IMAGE_ARI_1), instance_api._parse_image_parameters( fake_context, fakes.ID_EC2_IMAGE_1, fakes.ID_EC2_IMAGE_AKI_1, fakes.ID_EC2_IMAGE_ARI_1)) # NOTE(ft): check cases of unknown image id self.assertRaises(exception.InvalidAMIIDNotFound, instance_api._parse_image_parameters, fake_context, fakes.random_ec2_id('ami'), fakes.ID_EC2_IMAGE_AKI_1, fakes.ID_EC2_IMAGE_ARI_1) self.assertRaises(exception.InvalidAMIIDNotFound, instance_api._parse_image_parameters, fake_context, fakes.ID_EC2_IMAGE_1, fakes.random_ec2_id('aki'), fakes.ID_EC2_IMAGE_ARI_1) self.assertRaises(exception.InvalidAMIIDNotFound, instance_api._parse_image_parameters, fake_context, fakes.ID_EC2_IMAGE_1, fakes.ID_EC2_IMAGE_AKI_1, fakes.random_ec2_id('ari')) # NOTE(ft): check cases of absence of images in OS for n in range(3): glance.images.get.side_effect = ( [fakes.OSImage(fakes.OS_IMAGE_1)] * n + [glance_exception.HTTPNotFound()]) self.assertRaises( exception.InvalidAMIIDNotFound, instance_api._parse_image_parameters, fake_context, fakes.ID_EC2_IMAGE_1, fakes.ID_EC2_IMAGE_AKI_1, fakes.ID_EC2_IMAGE_ARI_1) # NOTE(ft): check normal flow for public images db_api.get_item_by_id.side_effect = None db_api.get_public_items.side_effect = ( fakes.get_db_api_get_item_by_id({ (fakes.ID_EC2_IMAGE_1,): [fakes.DB_IMAGE_1], (fakes.ID_EC2_IMAGE_AKI_1,): [fakes.DB_IMAGE_AKI_1], (fakes.ID_EC2_IMAGE_ARI_1,): [fakes.DB_IMAGE_ARI_1]})) glance.images.get.side_effect = ( fakes.get_by_1st_arg_getter({ fakes.ID_OS_IMAGE_1: fakes.OSImage(fakes.OS_IMAGE_1), fakes.ID_OS_IMAGE_AKI_1: fakes.OSImage(fakes.OS_IMAGE_AKI_1), fakes.ID_OS_IMAGE_ARI_1: fakes.OSImage(fakes.OS_IMAGE_ARI_1)})) self.assertEqual( (fakes.OSImage(fakes.OS_IMAGE_1), fakes.ID_OS_IMAGE_AKI_1, fakes.ID_OS_IMAGE_ARI_1), instance_api._parse_image_parameters( fake_context, fakes.ID_EC2_IMAGE_1, fakes.ID_EC2_IMAGE_AKI_1, fakes.ID_EC2_IMAGE_ARI_1)) @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.DictMatches( {'/dev/vdf': fakes.ID_OS_SNAPSHOT_1 + ':snap::True', '/dev/vdg': fakes.ID_OS_SNAPSHOT_2 + ':snap:111:False', '/dev/vdh': fakes.ID_OS_VOLUME_1 + ':vol::True', '/dev/vdi': fakes.ID_OS_VOLUME_2 + ':vol::True', '/dev/sdb1': '::55:'}, orderless_lists=True)) @mock.patch('ec2api.api.instance.novadb') @mock.patch('novaclient.v1_1.client.Client') @mock.patch('ec2api.db.api.IMPL') def test_format_instance(self, db_api, nova, novadb): nova = nova.return_value fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) fake_flavor = mock.Mock() fake_flavor.configure_mock(name='fake_flavor') nova.flavors.get.return_value = fake_flavor instance = {'id': fakes.random_ec2_id('i'), 'os_id': fakes.random_os_id(), 'launch_index': 0} os_instance = fakes.OSInstance(instance['os_id'], flavor={'id': 'fakeFlavorId'}) novadb_instance = {'kernel_id': None, 'ramdisk_id': None, 'hostname': instance['id']} # NOTE(ft): check instance state formatting setattr(os_instance, 'OS-EXT-STS:vm_state', 'active') formatted_instance = instance_api._format_instance( fake_context, instance, os_instance, novadb_instance, [], {}) self.assertEqual({'name': 'running', 'code': 16}, formatted_instance['instanceState']) setattr(os_instance, 'OS-EXT-STS:vm_state', 'stopped') formatted_instance = instance_api._format_instance( fake_context, instance, os_instance, novadb_instance, [], {}) self.assertEqual({'name': 'stopped', 'code': 80}, formatted_instance['instanceState']) # NOTE(ft): check auto creating of DB item for unknown OS images os_instance.image = {'id': fakes.random_os_id()} novadb_instance['kernel_id'] = fakes.random_os_id() novadb_instance['ramdisk_id'] = fakes.random_os_id() formatted_instance = instance_api._format_instance( fake_context, instance, os_instance, novadb_instance, [], {}) db_api.add_item_id.assert_has_calls( [mock.call(mock.ANY, 'ami', os_instance.image['id']), mock.call(mock.ANY, 'aki', novadb_instance['kernel_id']), mock.call(mock.ANY, 'ari', novadb_instance['ramdisk_id'])], any_order=True) @mock.patch('cinderclient.v1.client.Client') @mock.patch('ec2api.api.instance.novadb') def test_format_instance_bdm(self, novadb, cinder): cinder = cinder.return_value cinder.volumes.get.return_value = ( mock.Mock(status='attached', attachments={'device': 'fake'})) id_os_instance_1 = fakes.random_os_id() id_os_instance_2 = fakes.random_os_id() novadb.block_device_mapping_get_all_by_instance.side_effect = ( fakes.get_db_api_get_items({ id_os_instance_1: [{'device_name': '/dev/sdb1', 'delete_on_termination': False, 'snapshot_id': '1', 'volume_id': '2', 'no_device': False}, {'device_name': '/dev/sdb2', 'delete_on_termination': False, 'snapshot_id': None, 'volume_id': '3', 'volume_size': 1, 'no_device': False}, {'device_name': '/dev/sdb3', 'delete_on_termination': True, 'snapshot_id': '4', 'volume_id': '5', 'no_device': False}, {'device_name': '/dev/sdb4', 'delete_on_termination': False, 'snapshot_id': '6', 'volume_id': '7', 'no_device': False}, {'device_name': '/dev/sdb5', 'delete_on_termination': False, 'snapshot_id': '8', 'volume_id': '9', 'volume_size': 0, 'no_device': False}, {'device_name': '/dev/sdb6', 'delete_on_termination': False, 'snapshot_id': '10', 'volume_id': '11', 'volume_size': 1, 'no_device': False}, {'device_name': '/dev/sdb7', 'snapshot_id': None, 'volume_id': None, 'no_device': True}, {'device_name': '/dev/sdb8', 'snapshot_id': None, 'volume_id': None, 'virtual_name': 'swap', 'no_device': False}, {'device_name': '/dev/sdb9', 'snapshot_id': None, 'volume_id': None, 'virtual_name': 'ephemeral3', 'no_device': False}], id_os_instance_2: [{'device_name': 'vda', 'delete_on_termination': False, 'snapshot_id': '1', 'volume_id': '21', 'no_device': False}] })) db_volumes_1 = {'2': {'id': 'vol-00000002'}, '3': {'id': 'vol-00000003'}, '5': {'id': 'vol-00000005'}, '7': {'id': 'vol-00000007'}, '9': {'id': 'vol-00000009'}, '11': {'id': 'vol-0000000b'}} fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) result = {} instance_api._cloud_format_instance_bdm( fake_context, id_os_instance_1, '/dev/sdb1', result, db_volumes_1) self.assertThat( result, matchers.DictMatches({ 'rootDeviceType': 'ebs', 'blockDeviceMapping': [ {'deviceName': '/dev/sdb1', 'ebs': {'status': 'attached', 'deleteOnTermination': False, 'volumeId': 'vol-00000002', }}, {'deviceName': '/dev/sdb2', 'ebs': {'status': 'attached', 'deleteOnTermination': False, 'volumeId': 'vol-00000003', }}, {'deviceName': '/dev/sdb3', 'ebs': {'status': 'attached', 'deleteOnTermination': True, 'volumeId': 'vol-00000005', }}, {'deviceName': '/dev/sdb4', 'ebs': {'status': 'attached', 'deleteOnTermination': False, 'volumeId': 'vol-00000007', }}, {'deviceName': '/dev/sdb5', 'ebs': {'status': 'attached', 'deleteOnTermination': False, 'volumeId': 'vol-00000009', }}, {'deviceName': '/dev/sdb6', 'ebs': {'status': 'attached', 'deleteOnTermination': False, 'volumeId': 'vol-0000000b', }}]}, orderless_lists=True)) result = {} with mock.patch('ec2api.db.api.IMPL') as db_api: db_api.get_items.return_value = [{'id': 'vol-00000015', 'os_id': '21'}] instance_api._cloud_format_instance_bdm( fake_context, id_os_instance_2, '/dev/sdc1', result) self.assertThat( result, matchers.DictMatches({ 'rootDeviceType': 'instance-store', 'blockDeviceMapping': [ {'deviceName': 'vda', 'ebs': {'status': 'attached', 'deleteOnTermination': False, 'volumeId': 'vol-00000015', }}]})) @mock.patch('cinderclient.v1.client.Client') @mock.patch('ec2api.api.instance.novadb') def test_format_instance_bdm_while_attaching_volume(self, novadb, cinder): cinder = cinder.return_value cinder.volumes.get.return_value = ( mock.Mock(status='attaching')) id_os_instance = fakes.random_os_id() novadb.block_device_mapping_get_all_by_instance.return_value = ( [{'device_name': '/dev/sdb1', 'delete_on_termination': False, 'snapshot_id': '1', 'volume_id': '2', 'no_device': False}]) fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) result = {} instance_api._cloud_format_instance_bdm( fake_context, id_os_instance, '/dev/vda', result, {'2': {'id': 'vol-00000002'}}) self.assertThat( result, matchers.DictMatches({ 'rootDeviceType': 'instance-store', 'blockDeviceMapping': [ {'deviceName': '/dev/sdb1', 'ebs': {'status': 'attaching', 'deleteOnTermination': False, 'volumeId': 'vol-00000002', }}]})) @mock.patch('ec2api.api.instance._remove_instances') @mock.patch('novaclient.v1_1.client.Client') def test_get_os_instances_by_instances(self, nova, remove_instances): nova = nova.return_value fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) def do_check(exactly_flag): nova.servers.get.side_effect = [fakes.OS_INSTANCE_1, nova_exception.NotFound(404), fakes.OS_INSTANCE_2] absent_instance = {'id': fakes.random_ec2_id('i'), 'os_id': fakes.random_os_id()} params = (fake_context, [fakes.DB_INSTANCE_1, absent_instance, fakes.DB_INSTANCE_2], exactly_flag) if exactly_flag: self.assertRaises(exception.InvalidInstanceIDNotFound, instance_api._get_os_instances_by_instances, *params) else: res = instance_api._get_os_instances_by_instances(*params) self.assertEqual([fakes.OS_INSTANCE_1, fakes.OS_INSTANCE_2], res) remove_instances.assert_called_once_with(fake_context, [absent_instance]) remove_instances.reset_mock() do_check(True) do_check(False) @mock.patch('ec2api.api.network_interface._detach_network_interface_item') @mock.patch('ec2api.api.address._disassociate_address_item') @mock.patch('ec2api.db.api.IMPL') def test_remove_instances(self, db_api, disassociate_address_item, detach_network_interface_item): fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) instances = [{'id': fakes.random_ec2_id('i')} for dummy in range(4)] network_interfaces = [ {'id': fakes.random_ec2_id('eni'), 'instance_id': inst['id'], 'delete_on_termination': num in (0, 1, 4, 6)} for num, inst in enumerate(itertools.chain( *(zip(instances[:3], instances[:3]) + [[{'id': fakes.random_ec2_id('i')}] * 2])))] network_interfaces.extend({'id': fakes.random_ec2_id('eni')} for dummy in range(2)) addresses = [{'id': fakes.random_ec2_id('eipalloc'), 'network_interface_id': eni['id']} for eni in network_interfaces[::2]] addresses.extend({'id': fakes.random_ec2_id('eipalloc')} for dummy in range(2)) instances_to_remove = instances[:2] + [instances[3]] network_interfaces_of_removed_instances = { instances[0]['id']: network_interfaces[0:2], instances[1]['id']: network_interfaces[2:4], instances[3]['id']: []} network_interfaces_to_delete = [network_interfaces[0], network_interfaces[1]] network_interfaces_to_detach = [network_interfaces[2], network_interfaces[3]] addresses_to_dissassociate = [addresses[0]] db_api.get_items.side_effect = fakes.get_db_api_get_items({ 'eni': network_interfaces, 'eipalloc': addresses}) def check_calls(): for eni in network_interfaces_to_detach: detach_network_interface_item.assert_any_call(fake_context, eni) for eni in network_interfaces_to_delete: db_api.delete_item.assert_any_call(fake_context, eni['id']) for addr in addresses_to_dissassociate: disassociate_address_item.assert_any_call(fake_context, addr) detach_network_interface_item.reset_mock() db_api.reset_mock() disassociate_address_item.reset_mock() instance_api._remove_instances(fake_context, instances_to_remove) check_calls() instance_api._remove_instances(fake_context, instances_to_remove, network_interfaces_of_removed_instances) check_calls() @mock.patch('ec2api.api.instance.novadb') def test_is_ebs_instance(self, novadb): context = mock.Mock(service_catalog=[{'type': 'fake'}]) os_instance = fakes.OSInstance(fakes.random_os_id()) novadb.instance_get_by_uuid.return_value = {} novadb.block_device_mapping_get_all_by_instance.return_value = [] self.assertFalse(instance_api._is_ebs_instance(context, os_instance)) novadb.instance_get_by_uuid.return_value = { 'root_device_name': '/dev/vda'} self.assertFalse(instance_api._is_ebs_instance(context, os_instance)) novadb.block_device_mapping_get_all_by_instance.return_value = [ {'device_name': '/dev/vda', 'volume_id': None, 'snapshot_id': None, 'no_device': True}] self.assertFalse(instance_api._is_ebs_instance(context, os_instance)) novadb.block_device_mapping_get_all_by_instance.return_value = [ {'device_name': '/dev/vda', 'volume_id': fakes.random_ec2_id('vol'), 'snapshot_id': None, 'no_device': True}] self.assertFalse(instance_api._is_ebs_instance(context, os_instance)) novadb.block_device_mapping_get_all_by_instance.return_value = [ {'device_name': '/dev/vda', 'volume_id': '', 'snapshot_id': '', 'no_device': False}] self.assertFalse(instance_api._is_ebs_instance(context, os_instance)) novadb.block_device_mapping_get_all_by_instance.return_value = [ {'device_name': '/dev/vdb', 'volume_id': fakes.random_ec2_id('vol'), 'snapshot_id': '', 'no_device': False}] self.assertFalse(instance_api._is_ebs_instance(context, os_instance)) novadb.block_device_mapping_get_all_by_instance.return_value = [ {'device_name': '/dev/vda', 'volume_id': fakes.random_ec2_id('vol'), 'snapshot_id': '', 'no_device': False}] self.assertTrue(instance_api._is_ebs_instance(context, os_instance)) novadb.block_device_mapping_get_all_by_instance.return_value = [ {'device_name': 'vda', 'volume_id': fakes.random_ec2_id('vol'), 'snapshot_id': '', 'no_device': False}] self.assertTrue(instance_api._is_ebs_instance(context, os_instance)) def test_block_device_strip_dev(self): self.assertEqual( instance_api._block_device_strip_dev('/dev/sda'), 'sda') self.assertEqual(instance_api._block_device_strip_dev('sda'), 'sda') def test_block_device_prepend_dev(self): mapping = ['/dev/sda', 'sdb', 'sdc', 'sdd', 'sde'] expected = ['/dev/sda', '/dev/sdb', '/dev/sdc', '/dev/sdd', '/dev/sde'] for m, e in zip(mapping, expected): prepended = instance_api._block_device_prepend_dev(m) self.assertEqual(e, prepended)