"Fossies" - the Fresh Open Source Software Archive

Member "ec2-api-13.0.0/ec2api/api/network_interface.py" (6 Oct 2021, 28156 Bytes) of package /linux/misc/openstack/ec2-api-13.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 # Copyright 2014
    2 # The Cloudscaling Group, Inc.
    3 #
    4 # Licensed under the Apache License, Version 2.0 (the "License");
    5 # you may not use this file except in compliance with the License.
    6 # You may obtain a copy of the License at
    7 # http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS,
   11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   12 # See the License for the specific language governing permissions and
   13 # limitations under the License.
   14 
   15 
   16 import collections
   17 
   18 import netaddr
   19 from neutronclient.common import exceptions as neutron_exception
   20 from oslo_config import cfg
   21 from oslo_log import log as logging
   22 
   23 from ec2api.api import address as address_api
   24 from ec2api.api import common
   25 from ec2api.api import dhcp_options
   26 from ec2api.api import ec2utils
   27 from ec2api.api import security_group as security_group_api
   28 from ec2api import clients
   29 from ec2api.db import api as db_api
   30 from ec2api import exception
   31 from ec2api.i18n import _
   32 
   33 
   34 CONF = cfg.CONF
   35 LOG = logging.getLogger(__name__)
   36 
   37 
   38 """Network interface related API implementation
   39 """
   40 
   41 
   42 Validator = common.Validator
   43 
   44 
   45 def create_network_interface(context, subnet_id,
   46                              private_ip_address=None,
   47                              private_ip_addresses=None,
   48                              secondary_private_ip_address_count=None,
   49                              description=None,
   50                              security_group_id=None,
   51                              client_token=None):
   52 
   53     if client_token:
   54         result = describe_network_interfaces(context,
   55                                    filter=[{'name': 'client-token',
   56                                             'value': [client_token]}])
   57         if result['networkInterfaceSet']:
   58             if len(result['networkInterfaceSet']) > 1:
   59                 LOG.error('describe_network_interfaces returns %s '
   60                           'network_interfaces, but 1 is expected.',
   61                           len(result['networkInterfaceSet']))
   62                 LOG.error('Requested client token: %s', client_token)
   63                 LOG.error('Result: %s', result)
   64             return result['networkInterfaceSet'][0]
   65 
   66     subnet = ec2utils.get_db_item(context, subnet_id)
   67     if subnet is None:
   68         raise exception.InvalidSubnetIDNotFound(id=subnet_id)
   69     neutron = clients.neutron(context)
   70     os_subnet = neutron.show_subnet(subnet['os_id'])['subnet']
   71     # NOTE(Alex): Combine and check ip addresses. Neutron will accept
   72     # ip_address as a parameter for specified address and subnet_id for
   73     # address to auto-allocate.
   74     # TODO(Alex): Implement better diagnostics.
   75     subnet_ipnet = netaddr.IPNetwork(os_subnet['cidr'])
   76     if not private_ip_addresses:
   77         private_ip_addresses = []
   78     if private_ip_address is not None:
   79         private_ip_addresses.insert(0,
   80                                     {'private_ip_address': private_ip_address,
   81                                      'primary': True})
   82     primary_ip = None
   83     fixed_ips = []
   84     for ip in private_ip_addresses:
   85         ip_address = netaddr.IPAddress(ip['private_ip_address'])
   86         if ip_address not in subnet_ipnet:
   87             raise exception.InvalidParameterValue(
   88                 value=str(ip_address),
   89                 parameter='PrivateIpAddresses',
   90                 reason='IP address is out of the subnet range')
   91         if ip.get('primary', False):
   92             if primary_ip is not None:
   93                 raise exception.InvalidParameterValue(
   94                     value=str(ip_address),
   95                     parameter='PrivateIpAddresses',
   96                     reason='More than one primary ip is supplied')
   97             else:
   98                 primary_ip = str(ip_address)
   99                 fixed_ips.insert(0, {'ip_address': primary_ip})
  100         else:
  101             fixed_ips.append({'ip_address': str(ip_address)})
  102     if not fixed_ips and not secondary_private_ip_address_count:
  103         secondary_private_ip_address_count = 1
  104     if secondary_private_ip_address_count is None:
  105         secondary_private_ip_address_count = 0
  106     if secondary_private_ip_address_count > 0:
  107         for _i in range(secondary_private_ip_address_count):
  108             fixed_ips.append({'subnet_id': os_subnet['id']})
  109     vpc = db_api.get_item_by_id(context, subnet['vpc_id'])
  110     vpc_id = vpc['id']
  111     dhcp_options_id = vpc.get('dhcp_options_id', None)
  112     if not security_group_id:
  113         default_groups = security_group_api.describe_security_groups(
  114             context,
  115             filter=[{'name': 'vpc-id', 'value': [vpc_id]},
  116                     {'name': 'group-name', 'value': ['default']}]
  117         )['securityGroupInfo']
  118         security_group_id = [default_group['groupId']
  119                              for default_group in default_groups]
  120     security_groups = db_api.get_items_by_ids(context, security_group_id)
  121     if any(security_group['vpc_id'] != vpc['id']
  122            for security_group in security_groups):
  123         msg = _('You have specified two resources that belong to '
  124                 'different networks.')
  125         raise exception.InvalidGroupNotFound(msg)
  126     os_groups = [security_group['os_id'] for security_group in security_groups]
  127     with common.OnCrashCleaner() as cleaner:
  128         os_port_body = {'port': {'network_id': os_subnet['network_id'],
  129                                  'security_groups': os_groups}}
  130         os_port_body['port']['fixed_ips'] = fixed_ips
  131         try:
  132             os_port = neutron.create_port(os_port_body)['port']
  133         except (neutron_exception.IpAddressGenerationFailureClient,
  134                 neutron_exception.OverQuotaClient):
  135             raise exception.InsufficientFreeAddressesInSubnet()
  136         except (neutron_exception.IpAddressInUseClient,
  137                 neutron_exception.BadRequest) as ex:
  138             # NOTE(ft): AWS returns InvalidIPAddress.InUse for a primary IP
  139             # address, but InvalidParameterValue for secondary one.
  140             # AWS returns PrivateIpAddressLimitExceeded, but Neutron does
  141             # general InvalidInput (converted to BadRequest) in the same case.
  142             msg = _('Specified network interface parameters are invalid. '
  143                     'Reason: %(reason)s') % {'reason': ex.message}
  144             raise exception.InvalidParameterValue(msg)
  145         cleaner.addCleanup(neutron.delete_port, os_port['id'])
  146         if primary_ip is None:
  147             primary_ip = os_port['fixed_ips'][0]['ip_address']
  148         network_interface = db_api.add_item(context, 'eni',
  149                                             {'os_id': os_port['id'],
  150                                              'vpc_id': subnet['vpc_id'],
  151                                              'subnet_id': subnet['id'],
  152                                              'description': description,
  153                                              'private_ip_address': primary_ip})
  154         cleaner.addCleanup(db_api.delete_item,
  155                            context, network_interface['id'])
  156 
  157         network_interface_id = network_interface['id']
  158         neutron.update_port(os_port['id'],
  159                             {'port': {'name': network_interface_id}})
  160         if dhcp_options_id:
  161             dhcp_options._add_dhcp_opts_to_port(
  162                 context,
  163                 db_api.get_item_by_id(context, dhcp_options_id),
  164                 network_interface,
  165                 os_port)
  166     security_groups = security_group_api._format_security_groups_ids_names(
  167         context)
  168     return {'networkInterface':
  169             _format_network_interface(context,
  170                                       network_interface,
  171                                       os_port,
  172                                       security_groups=security_groups)}
  173 
  174 
  175 def delete_network_interface(context, network_interface_id):
  176     network_interface = ec2utils.get_db_item(context, network_interface_id)
  177     if 'instance_id' in network_interface:
  178         msg = _("Network interface '%(eni_id)s' is currently in use.")
  179         msg = msg % {'eni_id': network_interface_id}
  180         raise exception.InvalidParameterValue(msg)
  181 
  182     for address in db_api.get_items(context, 'eipalloc'):
  183         if address.get('network_interface_id') == network_interface['id']:
  184             address_api._disassociate_address_item(context, address)
  185 
  186     neutron = clients.neutron(context)
  187     with common.OnCrashCleaner() as cleaner:
  188         db_api.delete_item(context, network_interface['id'])
  189         cleaner.addCleanup(db_api.restore_item, context, 'eni',
  190                            network_interface)
  191         try:
  192             neutron.delete_port(network_interface['os_id'])
  193         except neutron_exception.PortNotFoundClient:
  194             pass
  195     return True
  196 
  197 
  198 class NetworkInterfaceDescriber(common.TaggableItemsDescriber):
  199 
  200     KIND = 'eni'
  201     FILTER_MAP = {'addresses.private-ip-address': ['privateIpAddressesSet',
  202                                                    'privateIpAddress'],
  203                   'addresses.primary': ['privateIpAddressesSet', 'primary'],
  204                   'addresses.association.public-ip': ['privateIpAddressesSet',
  205                                                       ('association',
  206                                                        'publicIp')],
  207                   'addresses.association.owner-id': ['privateIpAddressesSet',
  208                                                      ('association',
  209                                                       'ipOwnerId')],
  210                   'association.association-id': ('association',
  211                                                  'associationId'),
  212                   'association.allocation-id': ('association', 'allocationId'),
  213                   'association.ip-owner-id': ('association', 'ipOwnerId'),
  214                   'association.public-ip': ('association', 'publicIp'),
  215                   'attachment.attachment-id': ('attachment', 'attachmentId'),
  216                   'attachment.instance-id': ('attachment', 'instanceId'),
  217                   'attachment.instance-owner-id': ('attachment',
  218                                                    'instanceOwnerId'),
  219                   'attachment.device-index': ('attachment', 'deviceIndex'),
  220                   'attachment.status': ('attachment', 'status'),
  221                   'attachment.attach.time': ('attachment', 'attachTime'),
  222                   'attachment.delete-on-termination': ('attachment',
  223                                                        'deleteOnTermination'),
  224                   'client-token': 'clientToken',
  225                   'description': 'description',
  226                   'group-id': ['groupSet', 'groupId'],
  227                   'group-name': ['groupSet', 'groupName'],
  228                   'mac-address': 'macAddress',
  229                   'network-interface-id': 'networkInterfaceId',
  230                   'owner-id': 'ownerId',
  231                   'private-ip-address': 'privateIpAddress',
  232                   'requester-managed': 'requesterManaged',
  233                   'source-dest-check': 'sourceDestCheck',
  234                   'status': 'status',
  235                   'vpc-id': 'vpcId',
  236                   'subnet-id': 'subnetId'}
  237 
  238     def format(self, network_interface, os_port):
  239         if not network_interface:
  240             return None
  241         return _format_network_interface(
  242                 self.context, network_interface, os_port,
  243                 self.ec2_addresses[network_interface['id']],
  244                 self.security_groups)
  245 
  246     def get_os_items(self):
  247         addresses = address_api.describe_addresses(self.context)
  248         self.ec2_addresses = collections.defaultdict(list)
  249         for address in addresses['addressesSet']:
  250             if 'networkInterfaceId' in address:
  251                 self.ec2_addresses[
  252                         address['networkInterfaceId']].append(address)
  253         self.security_groups = (
  254             security_group_api._format_security_groups_ids_names(self.context))
  255         neutron = clients.neutron(self.context)
  256         return neutron.list_ports(tenant_id=self.context.project_id)['ports']
  257 
  258     def get_name(self, os_item):
  259         return ''
  260 
  261 
  262 def describe_network_interfaces(context, network_interface_id=None,
  263                                 filter=None):
  264     formatted_network_interfaces = NetworkInterfaceDescriber().describe(
  265             context, ids=network_interface_id, filter=filter)
  266     return {'networkInterfaceSet': formatted_network_interfaces}
  267 
  268 
  269 def assign_private_ip_addresses(context, network_interface_id,
  270                                 private_ip_address=None,
  271                                 secondary_private_ip_address_count=None,
  272                                 allow_reassignment=False):
  273     # TODO(Alex): allow_reassignment is not supported at the moment
  274     network_interface = ec2utils.get_db_item(context, network_interface_id)
  275     subnet = db_api.get_item_by_id(context, network_interface['subnet_id'])
  276     neutron = clients.neutron(context)
  277     os_subnet = neutron.show_subnet(subnet['os_id'])['subnet']
  278     os_port = neutron.show_port(network_interface['os_id'])['port']
  279     subnet_ipnet = netaddr.IPNetwork(os_subnet['cidr'])
  280     fixed_ips = os_port['fixed_ips'] or []
  281     if private_ip_address is not None:
  282         for ip_address in private_ip_address:
  283             if netaddr.IPAddress(ip_address) not in subnet_ipnet:
  284                 raise exception.InvalidParameterValue(
  285                     value=str(ip_address),
  286                     parameter='PrivateIpAddress',
  287                     reason='IP address is out of the subnet range')
  288             fixed_ips.append({'ip_address': str(ip_address)})
  289     elif secondary_private_ip_address_count > 0:
  290         for _i in range(secondary_private_ip_address_count):
  291             fixed_ips.append({'subnet_id': os_subnet['id']})
  292     try:
  293         neutron.update_port(os_port['id'],
  294                             {'port': {'fixed_ips': fixed_ips}})
  295     except neutron_exception.IpAddressGenerationFailureClient:
  296         raise exception.InsufficientFreeAddressesInSubnet()
  297     except neutron_exception.IpAddressInUseClient:
  298         msg = _('Some of %(addresses)s is assigned, but move is not '
  299                 'allowed.') % {'addresses': private_ip_address}
  300         raise exception.InvalidParameterValue(msg)
  301     except neutron_exception.BadRequest as ex:
  302         # NOTE(ft):AWS returns PrivateIpAddressLimitExceeded, but Neutron does
  303         # general InvalidInput (converted to BadRequest) in the same case.
  304         msg = _('Specified network interface parameters are invalid. '
  305                 'Reason: %(reason)s') % {'reason': ex.message}
  306         raise exception.InvalidParameterValue(msg)
  307     return True
  308 
  309 
  310 def unassign_private_ip_addresses(context, network_interface_id,
  311                                   private_ip_address):
  312     network_interface = ec2utils.get_db_item(context, network_interface_id)
  313     if network_interface['private_ip_address'] in private_ip_address:
  314         raise exception.InvalidParameterValue(
  315                 value=str(network_interface['private_ip_address']),
  316                 parameter='PrivateIpAddresses',
  317                 reason='Primary IP address cannot be unassigned')
  318     neutron = clients.neutron(context)
  319     os_port = neutron.show_port(network_interface['os_id'])['port']
  320     fixed_ips = os_port['fixed_ips'] or []
  321     new_fixed_ips = [ip for ip in fixed_ips
  322                      if ip['ip_address'] not in private_ip_address]
  323     if len(new_fixed_ips) + len(private_ip_address) != len(fixed_ips):
  324         msg = _('Some of the specified addresses are not assigned to '
  325                 'interface %(id)s') % {'id': network_interface_id}
  326         raise exception.InvalidParameterValue(msg)
  327     os_port = neutron.update_port(os_port['id'],
  328                                   {'port': {'fixed_ips': new_fixed_ips}})
  329     return True
  330 
  331 
  332 def describe_network_interface_attribute(context, network_interface_id,
  333                                          attribute=None):
  334     if attribute is None:
  335         raise exception.InvalidParameterCombination(
  336             _('No attributes specified.'))
  337     network_interface = ec2utils.get_db_item(context, network_interface_id)
  338 
  339     def _format_attr_description(result):
  340         result['description'] = {
  341             'value': network_interface.get('description', '')}
  342 
  343     def _format_attr_source_dest_check(result):
  344         result['sourceDestCheck'] = {
  345             'value': network_interface.get('source_dest_check', True)}
  346 
  347     def _format_attr_group_set(result):
  348         ec2_network_interface = describe_network_interfaces(context,
  349             network_interface_id=[network_interface_id]
  350         )['networkInterfaceSet'][0]
  351         result['groupSet'] = ec2_network_interface['groupSet']
  352 
  353     def _format_attr_attachment(result):
  354         ec2_network_interface = describe_network_interfaces(context,
  355             network_interface_id=[network_interface_id]
  356         )['networkInterfaceSet'][0]
  357         if 'attachment' in ec2_network_interface:
  358             result['attachment'] = ec2_network_interface['attachment']
  359 
  360     attribute_formatter = {
  361         'description': _format_attr_description,
  362         'sourceDestCheck': _format_attr_source_dest_check,
  363         'groupSet': _format_attr_group_set,
  364         'attachment': _format_attr_attachment,
  365     }
  366 
  367     fn = attribute_formatter.get(attribute)
  368     if fn is None:
  369         raise exception.InvalidParameterValue(value=attribute,
  370                                               parameter='attribute',
  371                                               reason='Unknown attribute.')
  372 
  373     result = {'networkInterfaceId': network_interface['id']}
  374     fn(result)
  375     return result
  376 
  377 
  378 def modify_network_interface_attribute(context, network_interface_id,
  379                                        description=None,
  380                                        source_dest_check=None,
  381                                        security_group_id=None,
  382                                        attachment=None):
  383     params_count = (
  384         int(description is not None) +
  385         int(source_dest_check is not None) +
  386         int(security_group_id is not None) +
  387         int(attachment is not None))
  388     if params_count != 1:
  389         raise exception.InvalidParameterCombination(
  390             'Multiple attributes specified')
  391     network_interface = ec2utils.get_db_item(context, network_interface_id)
  392     if description is not None:
  393         network_interface['description'] = description
  394         db_api.update_item(context, network_interface)
  395     neutron = clients.neutron(context)
  396     if security_group_id is not None:
  397         os_groups = [sg['os_id']
  398                      for sg in ec2utils.get_db_items(context, 'sg',
  399                                                      security_group_id)]
  400         neutron.update_port(network_interface['os_id'],
  401                             {'port': {'security_groups': os_groups}})
  402     if source_dest_check is not None:
  403         allowed = [] if source_dest_check else [{'ip_address': '0.0.0.0/0'}]
  404         neutron.update_port(network_interface['os_id'],
  405                             {'port': {'allowed_address_pairs': allowed}})
  406         network_interface['source_dest_check'] = source_dest_check
  407         db_api.update_item(context, network_interface)
  408     if attachment:
  409         attachment_id = attachment.get('attachment_id')
  410         delete_on_termination = attachment.get('delete_on_termination')
  411         if attachment_id is None or delete_on_termination is None:
  412             raise exception.MissingParameter(
  413                 _('The request must contain the parameter attachment '
  414                   'deleteOnTermination'))
  415         attachment_id_own = ec2utils.change_ec2_id_kind(
  416                 network_interface['id'], 'eni-attach')
  417         if ('instance_id' not in network_interface
  418                 or attachment_id_own != attachment_id):
  419             raise exception.InvalidAttachmentIDNotFound(id=attachment_id)
  420         network_interface['delete_on_termination'] = delete_on_termination
  421         db_api.update_item(context, network_interface)
  422     return True
  423 
  424 
  425 def reset_network_interface_attribute(context, network_interface_id,
  426                                       attribute):
  427     # TODO(Alex) This is only a stub because it's not supported by
  428     # Openstack. True will be returned for now in any case.
  429     # NOTE(Alex) There is a bug in the AWS doc about this method -
  430     # "sourceDestCheck" should be used instead of "SourceDestCheck".
  431     # Also aws cli doesn't work with it because it doesn't comply with
  432     # the API.
  433     if attribute == 'sourceDestCheck':
  434         return modify_network_interface_attribute(context,
  435                                                   network_interface_id,
  436                                                   source_dest_check=True)
  437     return True
  438 
  439 
  440 def attach_network_interface(context, network_interface_id,
  441                              instance_id, device_index):
  442     network_interface = ec2utils.get_db_item(context, network_interface_id)
  443     if 'instance_id' in network_interface:
  444         raise exception.InvalidParameterValue(
  445             _("Network interface '%(id)s' is currently in use.") %
  446             {'id': network_interface_id})
  447     os_instance_id = ec2utils.get_db_item(context, instance_id)['os_id']
  448     # TODO(Alex) Check that the instance is not yet attached to another VPC
  449     # TODO(Alex) Check that the instance is "our", not created via nova
  450     # (which means that it doesn't belong to any VPC and can't be attached)
  451     if any(eni['device_index'] == device_index
  452            for eni in db_api.get_items(context, 'eni')
  453            if eni.get('instance_id') == instance_id):
  454         raise exception.InvalidParameterValue(
  455             _("Instance '%(id)s' already has an interface attached at "
  456               "device index '%(index)s'.") % {'id': instance_id,
  457                                               'index': device_index})
  458     neutron = clients.neutron(context)
  459     os_port = neutron.show_port(network_interface['os_id'])['port']
  460     nova = clients.nova(context)
  461     with common.OnCrashCleaner() as cleaner:
  462         # TODO(Alex) nova inserts compute:%availability_zone into device_owner
  463         #                              'device_owner': 'compute:None'}})
  464         _attach_network_interface_item(context, network_interface,
  465                                        instance_id, device_index)
  466         cleaner.addCleanup(_detach_network_interface_item, context,
  467                            network_interface)
  468         nova.servers.interface_attach(os_instance_id, os_port['id'],
  469                                       None, None)
  470     return {'attachmentId': ec2utils.change_ec2_id_kind(
  471                     network_interface['id'], 'eni-attach')}
  472 
  473 
  474 def detach_network_interface(context, attachment_id, force=None):
  475     network_interface = db_api.get_item_by_id(
  476             context, ec2utils.change_ec2_id_kind(attachment_id, 'eni'))
  477     if not network_interface or 'instance_id' not in network_interface:
  478         raise exception.InvalidAttachmentIDNotFound(id=attachment_id)
  479     if network_interface['device_index'] == 0:
  480         raise exception.OperationNotPermitted(
  481             _('The network interface at device index 0 cannot be detached.'))
  482     neutron = clients.neutron(context)
  483     os_port = neutron.show_port(network_interface['os_id'])['port']
  484     with common.OnCrashCleaner() as cleaner:
  485         instance_id = network_interface['instance_id']
  486         device_index = network_interface['device_index']
  487         attach_time = network_interface['attach_time']
  488         delete_on_termination = network_interface['delete_on_termination']
  489         _detach_network_interface_item(context, network_interface)
  490         cleaner.addCleanup(_attach_network_interface_item,
  491                            context, network_interface, instance_id,
  492                            device_index, attach_time, delete_on_termination)
  493         neutron.update_port(os_port['id'],
  494                             {'port': {'device_id': '',
  495                                       'device_owner': ''}})
  496     return True
  497 
  498 
  499 def _format_network_interface(context, network_interface, os_port,
  500                               associated_ec2_addresses=[], security_groups={}):
  501     ec2_network_interface = {}
  502     ec2_network_interface['networkInterfaceId'] = network_interface['id']
  503     ec2_network_interface['subnetId'] = network_interface['subnet_id']
  504     ec2_network_interface['vpcId'] = network_interface['vpc_id']
  505     ec2_network_interface['description'] = network_interface['description']
  506     ec2_network_interface['sourceDestCheck'] = (
  507         network_interface.get('source_dest_check', True))
  508     ec2_network_interface['requesterManaged'] = (
  509         os_port.get('device_owner', '').startswith('network:'))
  510     ec2_network_interface['ownerId'] = context.project_id
  511     security_group_set = []
  512     for sg_id in os_port['security_groups']:
  513         if security_groups.get(sg_id):
  514             security_group_set.append(security_groups[sg_id])
  515     ec2_network_interface['groupSet'] = security_group_set
  516     if 'instance_id' in network_interface:
  517         ec2_network_interface['status'] = 'in-use'
  518         ec2_network_interface['attachment'] = {
  519             'attachmentId': ec2utils.change_ec2_id_kind(
  520                     network_interface['id'], 'eni-attach'),
  521             'instanceId': network_interface['instance_id'],
  522             'deviceIndex': network_interface['device_index'],
  523             'status': 'attached',
  524             'deleteOnTermination': network_interface['delete_on_termination'],
  525             'attachTime': network_interface['attach_time'],
  526             'instanceOwnerId': context.project_id
  527         }
  528     else:
  529         ec2_network_interface['status'] = 'available'
  530     ec2_network_interface['macAddress'] = os_port['mac_address']
  531     if os_port['fixed_ips']:
  532         ipsSet = []
  533         for ip in os_port['fixed_ips']:
  534             primary = (
  535                 network_interface.get('private_ip_address', '') ==
  536                 ip['ip_address'])
  537             item = {'privateIpAddress': ip['ip_address'],
  538                     'primary': primary}
  539             ec2_address = next(
  540                 (addr for addr in associated_ec2_addresses
  541                  if addr['privateIpAddress'] == ip['ip_address']),
  542                 None)
  543             if ec2_address:
  544                 item['association'] = {
  545                     'associationId': ec2utils.change_ec2_id_kind(
  546                                     ec2_address['allocationId'], 'eipassoc'),
  547                     'allocationId': ec2_address['allocationId'],
  548                     'ipOwnerId': context.project_id,
  549                     'publicDnsName': None,
  550                     'publicIp': ec2_address['publicIp'],
  551                 }
  552             if primary:
  553                 ipsSet.insert(0, item)
  554             else:
  555                 ipsSet.append(item)
  556         ec2_network_interface['privateIpAddressesSet'] = ipsSet
  557         primary_ip = ipsSet[0]
  558         ec2_network_interface['privateIpAddress'] = (
  559             primary_ip['privateIpAddress'])
  560         if 'association' in primary_ip:
  561             ec2_network_interface['association'] = primary_ip['association']
  562     # NOTE(ft): AWS returns empty tag set for a network interface
  563     # if no tag exists
  564     ec2_network_interface['tagSet'] = []
  565     return ec2_network_interface
  566 
  567 
  568 def _attach_network_interface_item(context, network_interface, instance_id,
  569                                    device_index, attach_time=None,
  570                                    delete_on_termination=False):
  571     if not attach_time:
  572         attach_time = ec2utils.isotime(None, True)
  573     network_interface.update({
  574         'instance_id': instance_id,
  575         'device_index': device_index,
  576         'attach_time': attach_time,
  577         'delete_on_termination': delete_on_termination})
  578     db_api.update_item(context, network_interface)
  579 
  580 
  581 def _detach_network_interface_item(context, network_interface):
  582     network_interface.pop('instance_id', None)
  583     network_interface.pop('device_index', None)
  584     network_interface.pop('attach_time', None)
  585     network_interface.pop('delete_on_termination', None)
  586     db_api.update_item(context, network_interface)