From 3c400fb115c90d086dca159d5e4090414fc7586b Mon Sep 17 00:00:00 2001 From: Feodor Tersin Date: Thu, 27 Nov 2014 19:07:14 +0400 Subject: [PATCH] Filters in describe instances Change-Id: If5f2e7c37f49df07bfa973dba3ac87a087477a89 --- ec2api/api/instance.py | 411 ++++++++++++++++++-------------- ec2api/api/network_interface.py | 9 +- ec2api/api/utils.py | 5 +- ec2api/exception.py | 4 +- ec2api/tests/fakes.py | 3 + ec2api/tests/test_instance.py | 62 +++-- 6 files changed, 281 insertions(+), 213 deletions(-) diff --git a/ec2api/api/instance.py b/ec2api/api/instance.py index b15c96a1..647deb56 100644 --- a/ec2api/api/instance.py +++ b/ec2api/api/instance.py @@ -22,6 +22,7 @@ import re from novaclient import exceptions as nova_exception from oslo.config import cfg +from ec2api.api import address as address_api from ec2api.api import clients from ec2api.api import ec2utils from ec2api.api import network_interface as network_interface_api @@ -48,6 +49,45 @@ CONF.register_opts(ec2_opts) # TODO(ft): implement DeviceIndex +INSTANCE_FILTER_MAP = { + 'block-device-mapping.device-name': ['blockDeviceMapping', + 'deviceName'], + 'client-token': 'clientToken', + 'dns-name': 'dnsName', + 'image-id': 'imageId', + 'instance-id': 'instanceId', + 'instance-type': 'instanceType', + 'ip-address': 'ipAddress', + 'kernel-id': 'kernelId', + 'key-name': 'keyName', + 'launch-index': 'amiLaunchIndex', + 'launch-time': 'launchTime', + 'private-dns-name': 'privateDnsName', + 'private-ip-address': 'privateIpAddress', + 'ramdisk-id': 'ramdiskId', + 'root-device-name': 'rootDeviceName', + 'root-device-type': 'rootDeviceType', + 'subnet-id': ['networkInterfaceSet', 'subnetId'], + 'vpc-id': ['networkInterfaceSet', 'vpcId'], + 'network-interface.description': ['networkInterfaceSet', + 'description'], + 'network-interface.subnet-id': ['networkInterfaceSet', 'subnetId'], + 'network-interface.vpc-id': ['networkInterfaceSet', 'vpcId'], + 'network-interface.network-interface.id': ['networkInterfaceSet', + 'networkInterfaceId'], + 'network-interface.owner-id': ['networkInterfaceSet', 'ownerId'], + 'network-interface.requester-managed': ['networkInterfaceSet', + 'requesterManaged'], + 'network-interface.status': ['networkInterfaceSet', 'status'], + 'network-interface.mac-address': ['networkInterfaceSet', 'macAddress'], + 'network-interface.source-destination-check': ['networkInterfaceSet', + 'sourceDestCheck'], +} +RESERVATION_FILTER_MAP = { + 'reservation-id': 'reservationId', + 'owner-id': 'ownerId', +} + def run_instances(context, image_id, min_count, max_count, key_name=None, security_group_id=None, @@ -103,7 +143,7 @@ def run_instances(context, image_id, min_count, max_count, security_groups_names = _get_vpc_default_security_group_id( context, vpc_id) - instances_infos = [] + instances_info = [] ec2_reservation_id = _generate_reservation_id() # TODO(ft): Process min and max counts on running errors accordingly to @@ -183,112 +223,114 @@ def run_instances(context, image_id, min_count, max_count, novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id) - instances_infos.append((instance, os_instance, novadb_instance, - network_interfaces,)) + instances_info.append((instance, os_instance, novadb_instance,)) - common_nw_info = _get_common_nw_info(context) - return _format_reservation(context, ec2_reservation_id, instances_infos, - common_nw_info) + # NOTE(ft): we cann't use describe_network_interfaces at this stage + # because network interfaces are not attached yet + ec2_network_interfaces = _format_network_interfaces( + context, instance_network_interfaces) + return _format_reservation(context, ec2_reservation_id, instances_info, + ec2_network_interfaces) def terminate_instances(context, instance_id): - instance_ids = set(instance_id or []) + instance_ids = set(instance_id) instances = ec2utils.get_db_items(context, 'i', instance_ids) + # TODO(ft): implement search db items in DB layer + network_interfaces = collections.defaultdict(list) + for eni in db_api.get_items(context, 'eni'): + if eni.get('instance_id') in instance_ids: + if eni['delete_on_termination']: + network_interfaces[eni['instance_id']].append(eni) + else: + network_interface_api.detach_network_interface( + context, + ec2utils.change_ec2_id_kind(eni['id'], 'eni-attach')) + + _remove_instances(context, instances, network_interfaces) nova = clients.nova(context) - os_instances_ids = [instance['os_id'] for instance in instances] - os_instances = nova.servers.list(search_opts={'id': os_instances_ids}) - - # TODO(ft): implement search db items by os_id in DB layer - network_interfaces = db_api.get_items(context, 'eni') - - # NOTE(ft): detach port before terminating instance to prevent - # Nova deletes it - neutron = clients.neutron(context) - os_ports = neutron.list_ports(device_id=os_instances_ids)['ports'] - for network_interface in network_interfaces: - if (network_interface.get('instance_id') not in instance_ids or - network_interface['delete_on_termination']): - continue - # NOTE(ft): detach ports only really attached to the instance - instance = next(i for i in instances - if i['id'] == network_interface['instance_id']) - os_port = next((p for p in os_ports - if p['id'] == network_interface['os_id']), None) - if (os_port and os_port['device_id'] == instance['os_id']): - neutron.update_port(os_port['id'], - {'port': {'device_id': '', - 'device_owner': ''}}) - network_interface_api._detach_network_interface_item( - context, network_interface) - - for os_instance in os_instances: - nova.servers.delete(os_instance.id) - for instance in instances: - db_api.delete_item(context, instance['id']) - - for network_interface in network_interfaces: - if (network_interface.get('instance_id') not in instance_ids or - not network_interface['delete_on_termination']): - continue - db_api.delete_item(context, network_interface['id']) - state_changes = [] - for inst in instances: - state_change = _format_state_change( - inst, - next((i for i in os_instances if i.id == inst['os_id']), None)) + for instance in instances: + try: + os_instance = nova.servers.get(instance['os_id']) + except nova_exception.NotFound: + os_instance = None + else: + os_instance.delete() + state_change = _format_state_change(instance, os_instance) state_changes.append(state_change) + return {'instancesSet': state_changes} -def describe_instances(context, instance_id=None, filter=None, **kwargs): +def describe_instances(context, instance_id=None, filter=None, + max_results=None, next_token=None): + instances = ec2utils.get_db_items(context, 'i', instance_id) + + if instance_id: + os_instances = _get_os_instances_by_instances(context, instances) + else: + os_instances = clients.nova(context).servers.list() - instances = db_api.get_items(context, 'i') instances_by_os_id = dict((i['os_id'], i) for i in instances) - nova = clients.nova(context) - os_instances = nova.servers.list() - # TODO(ft): implement search db items by os_id in DB layer - network_interfaces = collections.defaultdict(list) - for eni in db_api.get_items(context, 'eni'): - if 'instance_id' in eni: - network_interfaces[eni['instance_id']].append(eni) - reservations = _prepare_reservations( - context, os_instances, instances_by_os_id, network_interfaces) - common_nw_info = _get_common_nw_info(context) + reservations = collections.defaultdict(list) + for os_instance in os_instances: + novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id) + instance = instances_by_os_id.pop(os_instance.id, None) + if not instance: + instance = db_api.add_item( + context, 'i', + {'os_id': os_instance.id, + 'vpc_id': None, + 'reservation_id': novadb_instance['reservation_id'], + 'launch_index': novadb_instance['launch_index']}) + reservations[instance['reservation_id']].append( + (instance, os_instance, novadb_instance,)) + _remove_instances(context, instances_by_os_id.itervalues()) + + ec2_network_interfaces = _get_ec2_network_interfaces(context, instance_id) + reservation_filters = [] + instance_filters = [] + for f in filter or []: + if f.get('name') in RESERVATION_FILTER_MAP: + reservation_filters.append(f) + else: + instance_filters.append(f) ec2_reservations = [] - for reservation_id, instances_infos in reservations.iteritems(): - ec2_reservations.append(_format_reservation( - context, reservation_id, instances_infos, common_nw_info)) + for reservation_id, instances_info in reservations.iteritems(): + ec2_reservation = _format_reservation( + context, reservation_id, instances_info, + ec2_network_interfaces, instance_filters) + if (ec2_reservation['instancesSet'] and + not utils.filtered_out(ec2_reservation, reservation_filters, + RESERVATION_FILTER_MAP)): + ec2_reservations.append(ec2_reservation) return {'reservationSet': ec2_reservations} def reboot_instances(context, instance_id): - os_instances = _get_os_instances(context, instance_id) - for os_instance in os_instances: - os_instance.reboot() - return True + return _foreach_instance(context, instance_id, + lambda instance: instance.reboot()) def stop_instances(context, instance_id, force=False): - os_instances = _get_os_instances(context, instance_id) - for os_instance in os_instances: - os_instance.stop() - return True + return _foreach_instance(context, instance_id, + lambda instance: instance.stop()) def start_instances(context, instance_id): - os_instances = _get_os_instances(context, instance_id) - for os_instance in os_instances: - os_instance.start() - return True + return _foreach_instance(context, instance_id, + lambda instance: instance.start()) def describe_instance_attribute(context, instance_id, attribute): - os_instance = None - novadb_instance = None + instance = db_api.get_item_by_id(context, 'i', instance_id) + nova = clients.nova(context) + os_instance = nova.servers.get(instance['os_id']) + novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id) def _format_attr_block_device_mapping(result): root_device_name = _cloud_format_instance_root_device_name( @@ -351,82 +393,43 @@ def describe_instance_attribute(context, instance_id, attribute): # TODO(ft): clarify an exact AWS error raise exception.InvalidAttribute(attr=attribute) - instance = db_api.get_item_by_id(context, 'i', instance_id) - nova = clients.nova(context) - os_instance = nova.servers.get(instance['os_id']) - novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id) - result = {'instance_id': instance_id} fn(result) return result def _get_idempotent_run(context, client_token): - instances = db_api.get_items(context, 'i') - instances = [i for i in instances - if i.get('client_token') == client_token] + # TODO(ft): implement search in DB layer + instances = dict((i['os_id'], i) for i in db_api.get_items(context, 'i') + if i.get('client_token') == client_token) if not instances: return - os_instances = clients.nova(context).servers.list( - search_opts={'id': [i['os_id'] for i in instances]}) - os_instances = dict((i.id, i) for i in os_instances) - # TODO(ft): implement search db items by os_id in DB layer - instance_ids = set(i['id'] for i in instances) - network_interfaces = collections.defaultdict(list) - for eni in db_api.get_items(context, 'eni'): - if 'instance_id' in eni and eni['instance_id'] in instance_ids: - network_interfaces[eni['instance_id']].append(eni) - instances_infos = [] - for instance in instances: - os_instance = os_instances.get(instance['os_id']) - if not os_instance: - continue - novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id) - instances_infos.append((instance, os_instance, novadb_instance, - network_interfaces[instance['id']],)) - common_nw_info = _get_common_nw_info(context) - return _format_reservation(context, instance['reservation_id'], - instances_infos, common_nw_info) - - -def _prepare_reservations(context, os_instances, instances_by_os_id, - network_interfaces): - reservations = {} + os_instances = _get_os_instances_by_instances(context, instances) + instances_info = [] + instance_ids = [] for os_instance in os_instances: + instance = instances.pop(os_instance['id']) novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id) - if os_instance.id in instances_by_os_id: - instance = instances_by_os_id.get(os_instance.id) - reservation_id = instance['reservation_id'] - else: - reservation_id = novadb_instance['reservation_id'] - instance = db_api.add_item( - context, 'i', - {'os_id': os_instance.id, - 'vpc_id': None, - 'reservation_id': reservation_id, - 'launch_index': novadb_instance['launch_index']}) - if reservation_id not in reservations: - reservations[reservation_id] = [] - reservations[reservation_id].append( - (instance, os_instance, novadb_instance, - network_interfaces[instance['id']],)) - return reservations + instances_info.append((instance, os_instance, novadb_instance,)) + instance_ids.append(instance['id']) + if instances: + _remove_instances(context, instances.itervalues()) + if not instances_info: + return + ec2_network_interfaces = _get_ec2_network_interfaces(context, instance_ids) + return _format_reservation(context, instance['reservation_id'], + instances_info, ec2_network_interfaces) -def _format_reservation(context, reservation_id, instances_infos, - common_nw_info): - os_ports, addresses, security_groups = common_nw_info +def _format_reservation(context, reservation_id, instances_info, + ec2_network_interfaces, filters=None): ec2_instances = [] - for (instance, os_instance, novadb_instance, - network_interfaces) in instances_infos: - ports_info = [(eni, - os_ports[eni['os_id']], - addresses.get(eni['id'], [])) - for eni in network_interfaces - if eni['os_id'] in os_ports] - ec2_instances.append( - _format_instance(context, instance, os_instance, - novadb_instance, ports_info, security_groups)) + for (instance, os_instance, novadb_instance) in instances_info: + ec2_instance = _format_instance( + context, instance, os_instance, novadb_instance, + ec2_network_interfaces.get(instance['id'])) + if not utils.filtered_out(ec2_instance, filters, INSTANCE_FILTER_MAP): + ec2_instances.append(ec2_instance) ec2_reservation = {'reservationId': reservation_id, 'ownerId': os_instance.tenant_id, 'instancesSet': ec2_instances} @@ -437,7 +440,7 @@ def _format_reservation(context, reservation_id, instances_infos, def _format_instance(context, instance, os_instance, novadb_instance, - ports_info, security_groups): + ec2_network_interfaces): ec2_instance = {} ec2_instance['instanceId'] = instance['id'] image_uuid = os_instance.image['id'] if os_instance.image else '' @@ -490,40 +493,32 @@ def _format_instance(context, instance, os_instance, novadb_instance, 'availabilityZone': getattr(os_instance, 'OS-EXT-AZ:availability_zone') } - if not ports_info: + if not ec2_network_interfaces: # TODO(ft): boto uses 2010-08-31 version of AWS protocol # which doesn't contain groupSet element in an instance # We should support different versions of output data # ec2_instance['groupSet'] = _format_group_set( # context, os_instance.security_groups) return ec2_instance - ec2_network_interfaces = [] - for network_interface, os_port, addresses in ports_info: - ec2_network_interface = ( - network_interface_api._format_network_interface( - context, network_interface, os_port, addresses, - security_groups=security_groups)) - attachment = ec2_network_interface.get('attachment') - if attachment: - attachment.pop('instanceId', None) - attachment.pop('instanceOwnerId', None) - ec2_network_interfaces.append(ec2_network_interface) - ec2_instance['networkInterfaceSet'] = ec2_network_interfaces # NOTE(ft): get instance's subnet by instance's privateIpAddress instance_ip = ec2_instance['privateIpAddress'] - network_interface = None - for ((network_interface, os_port, _addresses), - ec2_network_interface) in zip(ports_info, ec2_network_interfaces): - if instance_ip in (ip['ip_address'] - for ip in os_port['fixed_ips']): - ec2_instance['subnetId'] = network_interface['subnet_id'] - # TODO(ft): boto uses 2010-08-31 version of AWS protocol - # which doesn't contain groupSet element in an instance - # We should support different versions of output data - # ec2_instance['groupSet'] = ec2_network_interface['groupSet'] - break - if network_interface: - ec2_instance['vpcId'] = network_interface['vpc_id'] + main_ec2_network_interface = None + for ec2_network_interface in ec2_network_interfaces: + ec2_network_interface['attachment'].pop('instanceId') + ec2_network_interface['attachment'].pop('instanceOwnerId') + if (not main_ec2_network_interface and + any(address['privateIpAddress'] == instance_ip + for address in + ec2_network_interface['privateIpAddressesSet'])): + main_ec2_network_interface = ec2_network_interface + ec2_instance['networkInterfaceSet'] = ec2_network_interfaces + if main_ec2_network_interface: + ec2_instance['subnetId'] = main_ec2_network_interface['subnetId'] + # TODO(ft): boto uses 2010-08-31 version of AWS protocol + # which doesn't contain groupSet element in an instance + # We should support different versions of output data + # ec2_instance['groupSet'] = main_ec2_network_interface['groupSet'] + ec2_instance['vpcId'] = ec2_network_interface['vpcId'] return ec2_instance @@ -544,13 +539,32 @@ def _format_state_change(instance, os_instance): } -def _get_common_nw_info(context): +def _get_ec2_network_interfaces(context, instance_ids=None): + ec2_network_interfaces = collections.defaultdict(list) + if not instance_ids: + network_interface_ids = None + else: + # TODO(ft): implement search db items in DB layer + network_interface_ids = [ + eni['id'] for eni in db_api.get_items(context, 'eni') + if eni.get('instance_id') in instance_ids] + enis = network_interface_api.describe_network_interfaces( + context, + network_interface_id=network_interface_ids)['networkInterfaceSet'] + for eni in enis: + if eni['status'] == 'in-use': + ec2_network_interfaces[eni['attachment']['instanceId']].append(eni) + return ec2_network_interfaces + + +def _format_network_interfaces(context, instances_network_interfaces): neutron = clients.neutron(context) os_ports = neutron.list_ports()['ports'] os_ports = dict((p['id'], p) for p in os_ports) - os_floating_ips = neutron.list_floatingips()['floatingips'] + # TODO(ft): reuse following code from network_interface_api + os_floating_ips = neutron.list_floatingips(fields=['id'])['floatingips'] os_floating_ip_ids = set(ip['id'] for ip in os_floating_ips) addresses = collections.defaultdict(list) for address in db_api.get_items(context, 'eipalloc'): @@ -561,7 +575,40 @@ def _get_common_nw_info(context): security_groups = security_group_api._format_security_groups_ids_names( context) - return os_ports, addresses, security_groups + ec2_network_interfaces = collections.defaultdict(list) + for network_interfaces in instances_network_interfaces: + for network_interface in network_interfaces: + ec2_eni = network_interface_api._format_network_interface( + context, network_interface, + os_ports[network_interface['os_id']], + addresses[network_interface['id']], security_groups) + ec2_network_interfaces[ + network_interface['instance_id']].append(ec2_eni) + return ec2_network_interfaces + + +def _remove_instances(context, instances, network_interfaces=None): + if network_interfaces is None: + # TODO(ft): implement search db items by os_id in DB layer + network_interfaces = collections.defaultdict(list) + for eni in db_api.get_items(context, 'eni'): + if 'instance_id' in eni: + network_interfaces[eni['instance_id']].append(eni) + + addresses = db_api.get_items(context, 'eipalloc') + addresses = dict((a['network_interface_id'], a) for a in addresses + if 'network_interface_id' in a) + for instance in instances: + for eni in network_interfaces[instance['id']]: + if eni['delete_on_termination']: + address = addresses.get(eni['id']) + if address: + address_api._disassociate_address_item(context, address) + db_api.delete_item(context, eni['id']) + else: + network_interface_api._detach_network_interface_item(context, + eni) + db_api.delete_item(context, instance['id']) def _check_min_max_count(min_count, max_count): @@ -832,21 +879,27 @@ def _get_ip_info_for_instance(os_instance): return fixed_ip, fixed_ip6, floating_ip -def _get_os_instances(context, instance_ids): +def _foreach_instance(context, instance_ids, func): instances = ec2utils.get_db_items(context, 'i', instance_ids) + os_instances = _get_os_instances_by_instances(context, instances, + exactly=True) + for os_instance in os_instances: + func(os_instance) + return True + +def _get_os_instances_by_instances(context, instances, exactly=False): nova = clients.nova(context) - os_instances_ids = [instance['os_id'] for instance in instances] - os_instances = nova.servers.list(search_opts={'id': os_instances_ids}) + os_instances = [] + for instance in instances: + try: + os_instances.append(nova.servers.get(instance['os_id'])) + except nova_exception.NotFound: + if exactly: + raise exception.InvalidInstanceIDNotFound(i_id=instance['id']) - if len(instances) > len(os_instances): - raise exception.InvalidInstanceIDNotFound( - instance_id=any(i['id'] for i in instances - if all(i['os_id'] != os_i.id - for os_i in os_instances))) return os_instances - # NOTE(ft): following functions are copied from various parts of Nova _dev = re.compile('^/dev/') diff --git a/ec2api/api/network_interface.py b/ec2api/api/network_interface.py index ac89b529..c622a1ef 100644 --- a/ec2api/api/network_interface.py +++ b/ec2api/api/network_interface.py @@ -354,12 +354,11 @@ def attach_network_interface(context, network_interface_id, network_interface['id'], 'eni-attach')} -def detach_network_interface(context, attachment_id, - force=None): - network_interface = db_api.get_item_by_id(context, 'eni', attachment_id) +def detach_network_interface(context, attachment_id, force=None): + network_interface = db_api.get_item_by_id( + context, 'eni', ec2utils.change_ec2_id_kind(attachment_id, 'eni')) if 'instance_id' not in network_interface: - raise exception.InvalidAttachmentIDNotFound( - attachment_id=attachment_id) + raise exception.InvalidAttachmentIDNotFound(attach_id=attachment_id) # TODO(Alex) Check that device index is not 0 (when we support it) and # forbid detaching. neutron = clients.neutron(context) diff --git a/ec2api/api/utils.py b/ec2api/api/utils.py index ddba88a3..ea6474d1 100644 --- a/ec2api/api/utils.py +++ b/ec2api/api/utils.py @@ -56,9 +56,8 @@ def filtered_out(item, filters, filter_map): value=filter['name'], parameter='filter', reason='invalid filter') if type(filter_name) is list: - value_set = item.get(filter_name[0]) - if value_set is not None: - values = [value[filter_name[1]] for value in value_set] + value_set = item.get(filter_name[0], []) + values = [value[filter_name[1]] for value in value_set] else: values = [item.get(filter_name)] if not values: diff --git a/ec2api/exception.py b/ec2api/exception.py index 8330097c..c0d88145 100644 --- a/ec2api/exception.py +++ b/ec2api/exception.py @@ -168,7 +168,7 @@ class NovaDbInstanceNotFound(EC2NotFound): class InvalidInstanceIDNotFound(EC2NotFound): ec2_code = 'InvalidInstanceID.NotFound' - msg_fmt = _("The instance ID '%(instance_id)s' does not exist") + msg_fmt = _("The instance ID '%(i_id)s' does not exist") class InvalidVpcIDNotFound(EC2NotFound): @@ -194,7 +194,7 @@ class InvalidNetworkInterfaceIDNotFound(EC2NotFound): class InvalidAttachmentIDNotFound(EC2NotFound): ec2_code = 'InvalidAttachmentID.NotFound' - msg_fmt = _("Attachment %(eni-attach_id)s could not " + msg_fmt = _("Attachment %(attach_id)s could not " "be found.") diff --git a/ec2api/tests/fakes.py b/ec2api/tests/fakes.py index 1792f5b6..714c625a 100644 --- a/ec2api/tests/fakes.py +++ b/ec2api/tests/fakes.py @@ -540,6 +540,9 @@ class OSInstance(object): def get(self): None + def delete(self): + None + OS_INSTANCE_1 = OSInstance( ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'}, diff --git a/ec2api/tests/test_instance.py b/ec2api/tests/test_instance.py index 7757cf2a..b6918ac7 100644 --- a/ec2api/tests/test_instance.py +++ b/ec2api/tests/test_instance.py @@ -339,16 +339,35 @@ class InstanceTestCase(base.ApiTestCase): fakes.EC2_NETWORK_INTERFACE_1['networkInterfaceId']}, new_port=False) - def test_terminate_instances(self): + @mock.patch('ec2api.api.address._disassociate_address_item') + @mock.patch('ec2api.api.network_interface.detach_network_interface') + def test_terminate_instances(self, detach_network_interface, + dissassociate_address_item): """Terminate 2 instances in one request.""" self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2] - self.nova_servers.list.return_value = [fakes.OS_INSTANCE_1, + self.nova_servers.get.side_effect = [fakes.OS_INSTANCE_1, fakes.OS_INSTANCE_2] - self.db_api.get_items.return_value = ( - [copy.deepcopy(fakes.DB_NETWORK_INTERFACE_1), - copy.deepcopy(fakes.DB_NETWORK_INTERFACE_2)]) - self.neutron.list_ports.return_value = {'ports': [fakes.OS_PORT_2]} + self.db_api.get_items.side_effect = fakes.get_db_api_get_items( + {'eni': [copy.deepcopy(fakes.DB_NETWORK_INTERFACE_1), + copy.deepcopy(fakes.DB_NETWORK_INTERFACE_2)], + 'eipalloc': [copy.deepcopy(fakes.DB_ADDRESS_1), + copy.deepcopy(fakes.DB_ADDRESS_2)]}) + + instance1_delete_patcher = mock.patch.object(fakes.OS_INSTANCE_1, + 'delete') + instance1_delete = instance1_delete_patcher.start() + self.addCleanup(instance1_delete.stop) + instance2_delete_patcher = mock.patch.object(fakes.OS_INSTANCE_2, + 'delete') + instance2_delete = instance2_delete_patcher.start() + self.addCleanup(instance2_delete.stop) + instance1_get_patcher = mock.patch.object(fakes.OS_INSTANCE_1, 'get') + instance1_get = instance1_get_patcher.start() + self.addCleanup(instance1_get.stop) + instance2_get_patcher = mock.patch.object(fakes.OS_INSTANCE_2, 'get') + instance2_get = instance2_get_patcher.start() + self.addCleanup(instance2_get.stop) resp = self.execute('TerminateInstances', {'InstanceId.1': fakes.ID_EC2_INSTANCE_1, @@ -370,26 +389,21 @@ class InstanceTestCase(base.ApiTestCase): 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.nova_servers.list.assert_called_once_with( - search_opts={'id': [fakes.ID_OS_INSTANCE_1, - fakes.ID_OS_INSTANCE_2]}) - self._assert_list_ports_is_called_with_filter( - [fakes.ID_OS_INSTANCE_1, fakes.ID_OS_INSTANCE_2]) - self.db_api.get_items.assert_called_once_with(mock.ANY, 'eni') - self.neutron.update_port.assert_called_once_with( - fakes.ID_OS_PORT_2, - {'port': {'device_id': '', - 'device_owner': ''}}) - self.db_api.update_item.assert_called_once_with( - mock.ANY, tools.purge_dict(fakes.DB_NETWORK_INTERFACE_2, - ('instance_id', 'attach_time', - 'delete_on_termination'))) - self.nova_servers.delete.assert_any_call(fakes.ID_OS_INSTANCE_1) - self.nova_servers.delete.assert_any_call(fakes.ID_OS_INSTANCE_2) - self.assertEqual(2, self.nova_servers.delete.call_count) + self.assertEqual(2, self.db_api.get_items.call_count) + self.db_api.get_items.assert_any_call(mock.ANY, 'eni') + self.db_api.get_items.assert_any_call(mock.ANY, 'eipalloc') + detach_network_interface.assert_called_once_with( + mock.ANY, fakes.ID_EC2_NETWORK_INTERFACE_2_ATTACH) + 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, dissassociate_address_item.call_count) + self.assertEqual(2, self.db_api.delete_item.call_count) for inst_id in (fakes.ID_EC2_INSTANCE_1, fakes.ID_EC2_INSTANCE_2): self.db_api.delete_item.assert_any_call(mock.ANY, inst_id) - self.assertEqual(2, self.db_api.delete_item.call_count) + instance1_delete.assert_called_once_with() + instance2_delete.assert_called_once_with() + instance1_get.assert_called_once_with() + instance2_get.assert_called_once_with() # TODO(ft): restore test after finish extraction of Nova EC2 API def _test_terminate_instances_multiple_networks(self):