# 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 import mock from novaclient import exceptions as nova_exception from oslotest import base as test_base import ec2api.api.clients 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): 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) 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')) # NOTE(ft): create a special mock for Nova calls with admin account. # Also make sure that an admin account is used only for this calls. # The special mock is needed to validate tested function to retrieve # appropriate data, as long as only calls with admin account return # some specific data. self.nova_admin = mock.create_autospec(self.NOVACLIENT_SPEC_OBJ) self.novaclient_getter.side_effect = ( lambda *args, **kwargs: ( self.nova_admin if (kwargs.get('auth_token') == 'admin_token') else self.nova if (kwargs.get('auth_token') != 'admin_token') else None)) 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.set_mock_db_items( fakes.DB_SUBNET_1, fakes.DB_NETWORK_INTERFACE_1, fakes.DB_IMAGE_1, fakes.DB_IMAGE_ARI_1, fakes.DB_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({ 'id': fakes.ID_OS_INSTANCE_1, 'flavor': {'id': 'fakeFlavorId'}, 'image': {'id': fakes.ID_OS_IMAGE_1}})) 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, [tools.patch_dict( 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, reservation_id=fakes.ID_EC2_RESERVATION_1), {'privateDnsName': None}, ['rootDeviceType', 'rootDeviceName'])]) 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.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( fakes.EC2_INSTANCE_1['privateDnsName'], 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)) get_ec2_network_interfaces.assert_called_once_with( mock.ANY, instance_ids=[fakes.ID_EC2_INSTANCE_1]) self.network_interface_api.reset_mock() self.nova.servers.reset_mock() self.db_api.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 = [ tools.patch_dict( fakes.gen_ec2_instance( ec2_instance_id, launch_index=l_i, ec2_network_interfaces=eni_pair, reservation_id=fakes.ID_EC2_RESERVATION_1), {'privateDnsName': None}, ['rootDeviceType', 'rootDeviceName']) 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) self.set_mock_db_items( fakes.DB_IMAGE_1, fakes.DB_SUBNET_1, fakes.DB_SUBNET_2, *self.DB_DETACHED_ENIS) 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({ 'id': os_instance_id, 'flavor': {'id': 'fakeFlavorId'}}) for os_instance_id in self.IDS_OS_INSTANCE] 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.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.set_mock_db_items( fakes.DB_IMAGE_1, fakes.DB_IMAGE_AKI_1, fakes.DB_IMAGE_ARI_1) self.glance.images.get.side_effect = ( tools.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 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.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_full({'id': inst['os_id']}) for inst in instances] format_reservation.return_value = {'key': 'value'} # NOTE(ft): check select corresponding instance by client_token self.set_mock_db_items(instances[0], instances[1]) get_os_instances_by_instances.return_value = [os_instances[1]] 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({'key': 'value'}, resp) format_reservation.assert_called_once_with( mock.ANY, instances[1]['reservation_id'], [(instances[1], os_instances[1])], 'ec2_network_interfaces') get_os_instances_by_instances.assert_called_once_with( mock.ANY, instances[1:2], nova=self.nova_admin) 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() for inst in instances: inst['reservation_id'] = instances[0]['reservation_id'] inst['client_token'] = 'client-token' self.set_mock_db_items(*instances) get_os_instances_by_instances.return_value = [os_instances[0], os_instances[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({'key': 'value'}, resp) format_reservation.assert_called_once_with( mock.ANY, instances[0]['reservation_id'], [(instances[0], os_instances[0]), (instances[2], os_instances[2])], 'ec2_network_interfaces') self.assert_any_call(get_os_instances_by_instances, mock.ANY, instances, nova=self.nova_admin) 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.set_mock_db_items(fakes.DB_IMAGE_1, fakes.DB_SUBNET_1, fakes.DB_NETWORK_INTERFACE_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({'id': fakes.ID_OS_INSTANCE_1, 'flavor': {'id': 'fakeFlavorId'}, 'image': {'id': fakes.ID_OS_IMAGE_1}})) (self.network_interface_api. _attach_network_interface_item.side_effect) = Exception() @tools.screen_unexpected_exception_logs 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.assert_execution_error( self.ANY_EXECUTE_ERROR, 'RunInstances', params) calls = [] 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({'id': inst['os_id']}) for inst in instances] self.nova_admin.servers.list.return_value = os_instances[:2] network_interfaces = [{'id': fakes.random_ec2_id('eni'), 'os_id': fakes.random_os_id()} for dummy in range(3)] self.set_mock_db_items(fakes.DB_IMAGE_1, fakes.DB_SUBNET_1, *network_interfaces) 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 format_reservation.side_effect = ( lambda _context, r_id, instance_info, *args, **kwargs: ( {'reservationId': r_id, 'instancesSet': [ {'instanceId': inst['id']} for inst, _os_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.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() (self.network_interface_api. _attach_network_interface_item.side_effect) = [ None, None, Exception()] with tools.ScreeningLogger(log_name='ec2api.api'): do_check(instance_api.InstanceEngineNeutron()) (self.network_interface_api.delete_network_interface. assert_called_once_with( mock.ANY, network_interface_id=network_interfaces[2]['id'])) self.nova.servers.update.side_effect = [None, None, Exception()] with tools.ScreeningLogger(log_name='ec2api.api'): do_check(instance_api.InstanceEngineNova()) def test_run_instances_invalid_parameters(self): self.assert_execution_error('InvalidParameterValue', 'RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '0', 'MaxCount': '0'}) self.assert_execution_error('InvalidParameterValue', 'RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '1', 'MaxCount': '0'}) self.assert_execution_error('InvalidParameterValue', 'RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '0', 'MaxCount': '1'}) self.assert_execution_error('InvalidParameterValue', 'RunInstances', {'ImageId': fakes.ID_EC2_IMAGE_1, 'MinCount': '2', 'MaxCount': '1'}) @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.set_mock_db_items( fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2, fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2, fakes.DB_ADDRESS_1, fakes.DB_ADDRESS_2) os_instances = [fakes.OSInstance(fakes.OS_INSTANCE_1), fakes.OSInstance(fakes.OS_INSTANCE_2)] self.nova.servers.get.side_effect = os_instances resp = self.execute('TerminateInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, 'InstanceId.2': fakes.ID_EC2_INSTANCE_2}) 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, 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(os_instances): 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.nova.servers.get.side_effect = ( lambda ec2_id: fakes.OSInstance({'id': ec2_id, 'vm_state': 'active'})) def do_check(mock_eni_list=[], detached_enis=[], deleted_enis=[]): self.set_mock_db_items(self.DB_FAKE_ENI, *(self.DB_INSTANCES + mock_eni_list)) resp = self.execute('TerminateInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, 'InstanceId.2': fakes.ID_EC2_INSTANCE_2}) 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): self.assert_execution_error( 'InvalidInstanceID.NotFound', 'TerminateInstances', {'InstanceId.1': fakes.random_ec2_id('i')}) @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 = fakes.OSInstance(fakes.OS_INSTANCE_1) os_instance_2 = fakes.OSInstance(fakes.OS_INSTANCE_2) for inst in (os_instance_1, os_instance_2): setattr(inst, 'OS-EXT-STS:vm_state', valid_state) self.set_mock_db_items(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({'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, 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() self.assert_execution_error('IncorrectInstanceState', 'StartInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, 'InstanceId.2': fakes.ID_EC2_INSTANCE_2}) 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('oslo_utils.timeutils.utcnow') def _test_instance_get_operation(self, operation, getter, key, utcnow): self.set_mock_db_items(fakes.DB_INSTANCE_2) os_instance_2 = fakes.OSInstance(fakes.OS_INSTANCE_2) self.nova.servers.get.return_value = 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({'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, fakes.ID_EC2_INSTANCE_2) self.nova.servers.get.assert_called_once_with(fakes.ID_OS_INSTANCE_2) getter.assert_called_once_with(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.set_mock_db_items( fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2, fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2, fakes.DB_IMAGE_1, fakes.DB_IMAGE_2, fakes.DB_IMAGE_ARI_1, fakes.DB_IMAGE_AKI_1, fakes.DB_VOLUME_1, fakes.DB_VOLUME_2, fakes.DB_VOLUME_3) self.nova_admin.servers.list.return_value = [ fakes.OSInstance_full(fakes.OS_INSTANCE_1), fakes.OSInstance_full(fakes.OS_INSTANCE_2)] 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.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.assertThat(resp, matchers.DictMatches( {'reservationSet': [fakes.EC2_RESERVATION_1, fakes.EC2_RESERVATION_2]}, orderless_lists=True)) self.nova_admin.servers.list.assert_called_once_with( search_opts={'all_tenants': True, 'project_id': fakes.ID_OS_PROJECT}) self.cinder.volumes.list.assert_called_once_with(search_opts=None) 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.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, set([fakes.ID_EC2_INSTANCE_1])) (self.network_interface_api.describe_network_interfaces. assert_called_with(mock.ANY)) 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.set_mock_db_items( fakes.DB_INSTANCE_2, fakes.DB_IMAGE_1, fakes.DB_IMAGE_2, fakes.DB_VOLUME_1, fakes.DB_VOLUME_2, fakes.DB_VOLUME_3) self.nova_admin.servers.list.return_value = [ fakes.OSInstance_full(fakes.OS_INSTANCE_2)] self.cinder.volumes.list.return_value = [ fakes.OSVolume(fakes.OS_VOLUME_1), fakes.OSVolume(fakes.OS_VOLUME_2), fakes.OSVolume(fakes.OS_VOLUME_3)] resp = self.execute('DescribeInstances', {}) 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.set_mock_db_items(*self.DB_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_admin.servers.list.return_value = [ fakes.OSInstance_full({ 'id': os_id, 'flavor': {'id': 'fakeFlavorId'}, 'addresses': dict((subnet_name, [{'addr': addr, 'version': 4, 'OS-EXT-IPS:type': 'fixed'}]) for subnet_name, addr in ips), 'root_device_name': '/dev/vda', 'hostname': '%s-%s' % (fakes.ID_EC2_RESERVATION_1, l_i)}) for l_i, (os_id, ips) in enumerate(zip( self.IDS_OS_INSTANCE, ips_by_instance))] resp = self.execute('DescribeInstances', {}) 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.set_mock_db_items(fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2, fakes.DB_VOLUME_2) self.nova_admin.servers.list.return_value = [ fakes.OSInstance_full(fakes.OS_INSTANCE_2)] self.cinder.volumes.list.return_value = [ fakes.OSVolume(fakes.OS_VOLUME_2)] resp = self.execute('DescribeInstances', {}) self.assertThat(resp, matchers.DictMatches( {'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.set_mock_db_items(*db_instances) os_instances = [ fakes.OSInstance_full({'id': inst['os_id']}) for inst in db_instances] self.nova_admin.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( [0, 1, 2, 3, 4], [inst['amiLaunchIndex'] for inst in resp['reservationSet'][0]['instancesSet']]) def test_describe_instances_invalid_parameters(self): self.assert_execution_error( 'InvalidInstanceID.NotFound', 'DescribeInstances', {'InstanceId.1': fakes.random_ec2_id('i')}) self.set_mock_db_items(fakes.DB_INSTANCE_2) self.assert_execution_error( 'InvalidInstanceID.NotFound', 'DescribeInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_2, 'InstanceId.2': fakes.random_ec2_id('i')}) def test_describe_instance_attributes(self): self.set_mock_db_items(fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2, fakes.DB_IMAGE_ARI_1, fakes.DB_IMAGE_AKI_1, fakes.DB_VOLUME_2) self.nova_admin.servers.get.side_effect = ( tools.get_by_1st_arg_getter({ fakes.ID_OS_INSTANCE_1: ( fakes.OSInstance_full(fakes.OS_INSTANCE_1)), fakes.ID_OS_INSTANCE_2: ( fakes.OSInstance_full(fakes.OS_INSTANCE_2))})) self.cinder.volumes.list.return_value = [ fakes.OSVolume(fakes.OS_VOLUME_2)] def do_check(instance_id, attribute, expected): resp = self.execute('DescribeInstanceAttribute', {'InstanceId': instance_id, 'Attribute': attribute}) expected.update({'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, 'groupSet', {'groupSet': fakes.EC2_RESERVATION_2['groupSet']}) 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))] 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 # 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): def test_merge_network_interface_parameters(self): engine = instance_api.InstanceEngineNeutron() self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, 'subnet-1', None, None, [{'device_index': 0, 'private_ip_address': '10.10.10.10'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, None, '10.10.10.10', None, [{'device_index': 0, 'subnet_id': 'subnet-1'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, ['default'], None, None, None, [{'device_index': 0, 'subnet_id': 'subnet-1'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, None, None, ['sg-1'], [{'device_index': 0, 'subnet_id': 'subnet-1'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, 'subnet-1', None, None, [{'device_index': 1, 'associate_public_ip_address': True}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, 'subnet-1', None, None, [{'device_index': 0, 'associate_public_ip_address': True}, {'device_index': 1, 'subnet_id': 'subnet-2'}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, 'subnet-1', None, None, [{'device_index': 0}]) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, ['default'], 'subnet-1', None, None, None) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, None, '10.10.10.10', None, None) self.assertRaises( exception.InvalidParameterCombination, engine.merge_network_interface_parameters, None, None, None, ['sg-1'], None) self.assertEqual( (None, [{'device_index': 0, 'subnet_id': 'subnet-1'}]), engine.merge_network_interface_parameters( None, 'subnet-1', None, None, None)) self.assertEqual( (None, [{'device_index': 0, 'subnet_id': 'subnet-1', 'private_ip_address': '10.10.10.10'}]), engine.merge_network_interface_parameters( None, 'subnet-1', '10.10.10.10', None, None)) self.assertEqual( (None, [{'device_index': 0, 'subnet_id': 'subnet-1', 'private_ip_address': '10.10.10.10', 'security_group_id': ['sg-1']}]), engine.merge_network_interface_parameters( None, 'subnet-1', '10.10.10.10', ['sg-1'], None)) self.assertEqual( (None, [{'device_index': 0, 'subnet_id': 'subnet-1', 'security_group_id': ['sg-1']}]), engine.merge_network_interface_parameters( None, 'subnet-1', None, ['sg-1'], None)) self.assertEqual( (None, [{'device_index': 0, 'subnet_id': 'subnet-1'}]), engine.merge_network_interface_parameters( None, None, None, None, [{'device_index': 0, 'subnet_id': 'subnet-1'}])) self.assertEqual( (['default'], []), engine.merge_network_interface_parameters( ['default'], None, None, None, None)) self.assertEqual((None, []), engine.merge_network_interface_parameters( None, None, None, None, None)) def test_check_network_interface_parameters(self): engine = instance_api.InstanceEngineNeutron() self.assertRaises( exception.InvalidParameterValue, engine.check_network_interface_parameters, [{'subnet_id': 'subnet-1'}], False) self.assertRaises( exception.InvalidParameterValue, engine.check_network_interface_parameters, [{'device_index': 0, 'subnet_id': 'subnet-1'}, {'device_index': 0, 'subnet_id': 'subnet-2'}], False) self.assertRaises( exception.InvalidParameterValue, engine.check_network_interface_parameters, [{'device_index': 0, 'private_ip_address': '10.10.10.10'}], False) self.assertRaises( exception.InvalidParameterCombination, engine.check_network_interface_parameters, [{'device_index': 0, 'network_interface_id': 'eni-1', 'subnet_id': 'subnet-1'}], False) self.assertRaises( exception.InvalidParameterCombination, engine.check_network_interface_parameters, [{'device_index': 0, 'network_interface_id': 'eni-1', 'private_ip_address': '10.10.10.10'}], False) self.assertRaises( exception.InvalidParameterCombination, engine.check_network_interface_parameters, [{'device_index': 0, 'network_interface_id': 'eni-1', 'security_group_id': ['sg-1']}], False) self.assertRaises( exception.InvalidParameterCombination, engine.check_network_interface_parameters, [{'device_index': 0, 'network_interface_id': 'eni-1', 'delete_on_termination': True}], False) self.assertRaises( exception.InvalidParameterCombination, engine.check_network_interface_parameters, [{'device_index': 0, 'network_interface_id': 'eni-1'}], True) self.assertRaises( exception.InvalidParameterCombination, engine.check_network_interface_parameters, [{'device_index': 0, 'subnet_id': 'subnet-1', 'private_ip_address': '10.10.10.10'}], True) self.assertRaises( exception.UnsupportedOperation, engine.check_network_interface_parameters, [{'device_index': 1, 'subnet_id': 'subnet-1'}], False) engine.check_network_interface_parameters( [{'device_index': 0, 'subnet_id': 'subnet-1'}], False) engine.check_network_interface_parameters( [{'device_index': 0, 'subnet_id': 'subnet-1', 'private_ip_address': '10.10.10.10', 'security_group_id': ['sg-1'], 'delete_on_termination': True}], False) engine.check_network_interface_parameters( [{'device_index': 0, 'network_interface_id': 'eni-1'}], False) engine.check_network_interface_parameters( [{'device_index': 0, 'subnet_id': 'subnet-1', 'security_group_id': ['sg-1'], 'delete_on_termination': True}, {'device_index': 1, 'subnet_id': 'subnet-2'}], True) engine.check_network_interface_parameters([], False) @mock.patch('ec2api.db.api.IMPL') def test_parse_network_interface_parameters(self, db_api): engine = instance_api.InstanceEngineNeutron() context = mock.Mock() db_api.get_item_by_id.side_effect = tools.get_db_api_get_item_by_id( fakes.DB_SUBNET_1, tools.update_dict(fakes.DB_SUBNET_2, {'vpc_id': fakes.ID_EC2_VPC_2}), fakes.DB_NETWORK_INTERFACE_1, fakes.DB_NETWORK_INTERFACE_2) resp = engine.parse_network_interface_parameters( context, [{'device_index': 1, 'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_1}, {'device_index': 0, 'subnet_id': fakes.ID_EC2_SUBNET_1, 'delete_on_termination': False, 'security_group_id': [fakes.ID_EC2_SECURITY_GROUP_1]}]) self.assertEqual( (fakes.ID_EC2_VPC_1, [{'device_index': 0, 'create_args': (fakes.ID_EC2_SUBNET_1, {'security_group_id': ( [fakes.ID_EC2_SECURITY_GROUP_1])}), 'delete_on_termination': False}, {'device_index': 1, 'network_interface': fakes.DB_NETWORK_INTERFACE_1, 'detach_on_crash': True, 'delete_on_termination': False}]), resp) resp = engine.parse_network_interface_parameters( context, [{'device_index': 0, 'subnet_id': fakes.ID_EC2_SUBNET_1, 'associate_public_ip_address': True}]) self.assertEqual( (fakes.ID_EC2_VPC_1, [{'device_index': 0, 'create_args': (fakes.ID_EC2_SUBNET_1, {}), 'delete_on_termination': True}]), resp) # NOTE(ft): a network interface has being attached twice self.assertRaises( exception.InvalidParameterValue, engine.parse_network_interface_parameters, context, [{'device_index': 0, 'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_1}, {'device_index': 1, 'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_1}]) # NOTE(ft): a network interface is in use self.assertRaises( exception.InvalidNetworkInterfaceInUse, engine.parse_network_interface_parameters, context, [{'device_index': 0, 'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_2}]) # NOTE(ft): specified objects are belonging to different VPCs self.assertRaises( exception.InvalidParameterValue, engine.parse_network_interface_parameters, context, [{'device_index': 0, 'subnet_id': fakes.ID_EC2_SUBNET_1}, {'device_index': 1, 'subnet_id': fakes.ID_EC2_SUBNET_2}]) self.assertRaises( exception.InvalidParameterValue, engine.parse_network_interface_parameters, context, [{'device_index': 0, 'network_interface_id': fakes.ID_EC2_NETWORK_INTERFACE_1}, {'device_index': 1, 'subnet_id': fakes.ID_EC2_SUBNET_2}]) @mock.patch('ec2api.api.ec2utils.get_os_image') def test_parse_image_parameters(self, get_os_image): fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) # NOTE(ft): check normal flow os_image = fakes.OSImage(fakes.OS_IMAGE_1) get_os_image.side_effect = [ fakes.OSImage(fakes.OS_IMAGE_AKI_1), fakes.OSImage(fakes.OS_IMAGE_ARI_1), os_image] self.assertEqual( (os_image, 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)) get_os_image.assert_has_calls( [mock.call(fake_context, fakes.ID_EC2_IMAGE_AKI_1), mock.call(fake_context, fakes.ID_EC2_IMAGE_ARI_1), mock.call(fake_context, fakes.ID_EC2_IMAGE_1)]) get_os_image.side_effect = None get_os_image.return_value = os_image get_os_image.reset_mock() self.assertEqual( (os_image, None, None), instance_api._parse_image_parameters( fake_context, fakes.ID_EC2_IMAGE_1, None, None)) get_os_image.assert_called_once_with( fake_context, fakes.ID_EC2_IMAGE_1) # NOTE(ft): check cases of not available image os_image = fakes.OSImage({ 'id': fakes.random_os_id(), 'status': None, 'properties': {}}) get_os_image.return_value = os_image self.assertRaises( exception.ImageNotActive, instance_api._parse_image_parameters, fake_context, fakes.random_ec2_id('ami'), None, None) os_image.status = 'active' os_image.properties['image_state'] = 'decrypting' self.assertRaises( exception.ImageNotActive, instance_api._parse_image_parameters, fake_context, fakes.random_ec2_id('ami'), 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 = tools.get_db_api_get_item_by_id( fakes.DB_VOLUME_1, fakes.DB_VOLUME_2, fakes.DB_VOLUME_3, fakes.DB_SNAPSHOT_1, 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('cinderclient.client.Client') @mock.patch('novaclient.client.Client') @mock.patch('ec2api.db.api.IMPL') def test_format_instance(self, db_api, nova, cinder): 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_full({'id': instance['os_id'], 'flavor': {'id': 'fakeFlavorId'}}) # 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, [], {}) 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, [], {}) 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()} kernel_id = fakes.random_os_id() ramdisk_id = fakes.random_os_id() setattr(os_instance, 'OS-EXT-SRV-ATTR:kernel_id', kernel_id) setattr(os_instance, 'OS-EXT-SRV-ATTR:ramdisk_id', ramdisk_id) formatted_instance = instance_api._format_instance( fake_context, instance, os_instance, [], {}) db_api.add_item_id.assert_has_calls( [mock.call(mock.ANY, 'ami', os_instance.image['id']), mock.call(mock.ANY, 'aki', kernel_id), mock.call(mock.ANY, 'ari', ramdisk_id)], any_order=True) @mock.patch('cinderclient.client.Client') def test_format_instance_bdm(self, cinder): id_os_instance_1 = fakes.random_os_id() id_os_instance_2 = fakes.random_os_id() cinder = cinder.return_value cinder.volumes.list.return_value = [ fakes.OSVolume({'id': '2', 'status': 'attached', 'attachments': [{'device': '/dev/sdb1', 'server_id': id_os_instance_1}]}), fakes.OSVolume({'id': '5', 'status': 'attached', 'attachments': [{'device': '/dev/sdb3', 'server_id': id_os_instance_1}]}), fakes.OSVolume({'id': '21', 'status': 'attached', 'attachments': [{'device': 'vda', 'server_id': id_os_instance_2}]}), ] os_instance_1 = fakes.OSInstance_full({ 'id': id_os_instance_1, 'volumes_attached': [{'id': '2', 'delete_on_termination': False}, {'id': '5', 'delete_on_termination': True}], 'root_device_name': '/dev/sdb1'}) os_instance_2 = fakes.OSInstance_full({ 'id': id_os_instance_2, 'volumes_attached': [{'id': '21', 'delete_on_termination': False}], 'root_device_name': '/dev/sdc1'}) db_volumes_1 = {'2': {'id': 'vol-00000002'}, '5': {'id': 'vol-00000005'}} fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) result = {} instance_api._cloud_format_instance_bdm( fake_context, os_instance_1, 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/sdb3', 'ebs': {'status': 'attached', 'deleteOnTermination': True, 'volumeId': 'vol-00000005', }}]}, orderless_lists=True), verbose=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, os_instance_2, result) self.assertThat( result, matchers.DictMatches({ 'rootDeviceType': 'instance-store', 'blockDeviceMapping': [ {'deviceName': 'vda', 'ebs': {'status': 'attached', 'deleteOnTermination': False, 'volumeId': 'vol-00000015', }}]})) @mock.patch('cinderclient.client.Client') def test_format_instance_bdm_while_attaching_volume(self, cinder): id_os_instance = fakes.random_os_id() cinder = cinder.return_value cinder.volumes.list.return_value = [ fakes.OSVolume({'id': '2', 'status': 'attaching', 'attachments': [{'device': '/dev/sdb1', 'server_id': id_os_instance}]})] os_instance = fakes.OSInstance_full({ 'id': id_os_instance, 'volumes_attached': [{'id': '2', 'delete_on_termination': False}], 'root_device_name': '/dev/vda'}) fake_context = mock.Mock(service_catalog=[{'type': 'fake'}]) result = {} instance_api._cloud_format_instance_bdm( fake_context, os_instance, 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', }}]})) def test_format_instance_bdm_no_bdm(self): context = mock.Mock() os_instance_id = fakes.random_os_id() os_instance = fakes.OSInstance_full({'id': os_instance_id}) res = {} setattr(os_instance, 'OS-EXT-SRV-ATTR:root_device_name', None) instance_api._cloud_format_instance_bdm( context, os_instance, res, {}, {os_instance_id: []}) self.assertEqual({}, res) res = {} setattr(os_instance, 'OS-EXT-SRV-ATTR:root_device_name', '') instance_api._cloud_format_instance_bdm( context, os_instance, res, {}, {os_instance_id: []}) self.assertEqual({}, res) res = {} setattr(os_instance, 'OS-EXT-SRV-ATTR:root_device_name', '/dev/vdd') instance_api._cloud_format_instance_bdm( context, os_instance, res, {}, {os_instance_id: []}) self.assertEqual({'rootDeviceType': 'instance-store'}, res) @mock.patch('ec2api.api.instance._remove_instances') @mock.patch('novaclient.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'}]) os_instance_1 = fakes.OSInstance(fakes.OS_INSTANCE_1) os_instance_2 = fakes.OSInstance(fakes.OS_INSTANCE_2) def do_check(exactly_flag=None, specify_nova_client=False): nova.servers.get.side_effect = [os_instance_1, nova_exception.NotFound(404), 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, nova if specify_nova_client else False) 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([os_instance_1, os_instance_2], res) remove_instances.assert_called_once_with(fake_context, [absent_instance]) remove_instances.reset_mock() do_check(exactly_flag=True) # NOTE(ft): stop to return fake data by the mocked client and create # a new one to pass it into the function nova.servers.side_effect = None nova = mock.Mock() do_check(specify_nova_client=True) @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 = tools.get_db_api_get_items( *(network_interfaces + 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('cinderclient.client.Client') def test_get_os_volumes(self, cinder): cinder = cinder.return_value context = mock.Mock(service_catalog=[{'type': 'fake'}], is_os_admin=False) os_volume_ids = [fakes.random_os_id() for _i in range(5)] os_instance_ids = [fakes.random_os_id() for _i in range(2)] os_volumes = [ fakes.OSVolume( {'id': os_volume_ids[0], 'status': 'attached', 'attachments': [{'server_id': os_instance_ids[0]}]}), fakes.OSVolume( {'id': os_volume_ids[1], 'status': 'attaching', 'attachments': []}), fakes.OSVolume( {'id': os_volume_ids[2], 'status': 'detaching', 'attachments': [{'server_id': os_instance_ids[0]}]}), fakes.OSVolume( {'id': os_volume_ids[3], 'status': 'attached', 'attachments': [{'server_id': os_instance_ids[1]}]}), fakes.OSVolume( {'id': os_volume_ids[4], 'status': 'available', 'attachments': []}), ] cinder.volumes.list.return_value = os_volumes res = instance_api._get_os_volumes(context) self.assertIn(os_instance_ids[0], res) self.assertIn(os_instance_ids[1], res) self.assertEqual([os_volumes[0], os_volumes[2]], res[os_instance_ids[0]]) self.assertEqual([os_volumes[3]], res[os_instance_ids[1]]) cinder.volumes.list.assert_called_once_with(search_opts=None) context.is_os_admin = True instance_api._get_os_volumes(context) cinder.volumes.list.assert_called_with( search_opts={'all_tenants': True, 'project_id': context.project_id}) @mock.patch('ec2api.api.clients.nova', wraps=ec2api.api.clients.nova) @mock.patch('ec2api.context.get_os_admin_context') @mock.patch('cinderclient.client.Client') @mock.patch('novaclient.client.Client') def test_is_ebs_instance(self, nova, cinder, get_os_admin_context, nova_client_getter): nova = nova.return_value cinder = cinder.return_value context = mock.Mock(service_catalog=[{'type': 'fake'}], is_os_admin=False) os_instance = fakes.OSInstance_full({'id': fakes.random_os_id()}) nova.servers.get.return_value = os_instance cinder.volumes.list.return_value = [] self.assertFalse(instance_api._is_ebs_instance(context, os_instance.id)) cinder.volumes.list.return_value = [ fakes.OSVolume( {'id': fakes.random_os_id(), 'status': 'attached', 'attachments': [{'device': '/dev/vda', 'server_id': os_instance.id}]})] setattr(os_instance, 'OS-EXT-SRV-ATTR:root_device_name', '') self.assertFalse(instance_api._is_ebs_instance(context, os_instance.id)) setattr(os_instance, 'OS-EXT-SRV-ATTR:root_device_name', '/dev/vda') cinder.volumes.list.return_value = [] self.assertFalse(instance_api._is_ebs_instance(context, os_instance.id)) cinder.volumes.list.return_value = [ fakes.OSVolume( {'id': fakes.random_os_id(), 'status': 'attached', 'attachments': [{'device': '/dev/vda', 'server_id': fakes.random_os_id()}]})] self.assertFalse(instance_api._is_ebs_instance(context, os_instance.id)) cinder.volumes.list.return_value = [ fakes.OSVolume( {'id': fakes.random_os_id(), 'status': 'attached', 'attachments': [{'device': '/dev/vdb', 'server_id': os_instance.id}]})] self.assertFalse(instance_api._is_ebs_instance(context, os_instance.id)) cinder.volumes.list.return_value = [ fakes.OSVolume( {'id': fakes.random_os_id(), 'status': 'attached', 'attachments': [{'device': '/dev/vda', 'server_id': os_instance.id}]})] self.assertTrue(instance_api._is_ebs_instance(context, os_instance.id)) nova_client_getter.assert_called_with( get_os_admin_context.return_value) cinder.volumes.list.assert_called_with(search_opts=None) cinder.volumes.list.return_value = [ fakes.OSVolume( {'id': fakes.random_os_id(), 'status': 'attached', 'attachments': [{'device': 'vda', 'server_id': os_instance.id}]})] self.assertTrue(instance_api._is_ebs_instance(context, os_instance.id)) 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)