Network Interfaces: Initial implementation.

This commit is contained in:
Shawn Falkner-Horine 2014-09-08 16:50:18 -07:00
parent 22d9141122
commit 42f8cea5e6
9 changed files with 826 additions and 43 deletions

View File

@ -72,6 +72,14 @@ class InvalidSubnetIdError(EC2ClientError):
.format(subnet_id)) .format(subnet_id))
class InvalidNetworkInterfaceIdError(EC2ClientError):
def __init__(self, eni_id):
super(InvalidNetworkInterfaceIdError, self).__init__(
"InvalidNetworkInterfaceID.NotFound",
"The network interface ID '{0}' does not exist"
.format(eni_id))
class InvalidSecurityGroupDuplicateError(EC2ClientError): class InvalidSecurityGroupDuplicateError(EC2ClientError):
def __init__(self, name): def __init__(self, name):
super(InvalidSecurityGroupDuplicateError, self).__init__( super(InvalidSecurityGroupDuplicateError, self).__init__(

View File

@ -26,6 +26,7 @@ from .exceptions import (
ResourceAlreadyAssociatedError, ResourceAlreadyAssociatedError,
InvalidVPCIdError, InvalidVPCIdError,
InvalidSubnetIdError, InvalidSubnetIdError,
InvalidNetworkInterfaceIdError,
InvalidSecurityGroupDuplicateError, InvalidSecurityGroupDuplicateError,
InvalidSecurityGroupNotFoundError, InvalidSecurityGroupNotFoundError,
InvalidPermissionNotFoundError, InvalidPermissionNotFoundError,
@ -48,11 +49,14 @@ from .utils import (
random_dhcp_option_id, random_dhcp_option_id,
random_eip_allocation_id, random_eip_allocation_id,
random_eip_association_id, random_eip_association_id,
random_eni_attach_id,
random_eni_id,
random_internet_gateway_id, random_internet_gateway_id,
random_instance_id, random_instance_id,
random_internet_gateway_id, random_internet_gateway_id,
random_ip, random_ip,
random_key_pair, random_key_pair,
random_public_ip,
random_reservation_id, random_reservation_id,
random_route_table_id, random_route_table_id,
random_security_group_id, random_security_group_id,
@ -77,6 +81,172 @@ class TaggedEC2Instance(object):
return tags return tags
class NetworkInterface(object):
def __init__(self, subnet, private_ip_address, device_index=0, public_ip_auto_assign=True, group_ids=None):
self.id = random_eni_id()
self.device_index = device_index
self.private_ip_address = private_ip_address
self.subnet = subnet
self.instance = None
self.public_ip = None
self.public_ip_auto_assign = public_ip_auto_assign
self.start()
self.attachments = []
self.group_set = []
group = None
if group_ids:
for group_id in group_ids:
group = ec2_backend.get_security_group_from_id(group_id)
if not group:
# Create with specific group ID.
group = SecurityGroup(group_id, group_id, group_id, vpc_id=subnet.vpc_id)
ec2_backend.groups[subnet.vpc_id][group_id] = group
if group:
self.group_set.append(group)
@classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
properties = cloudformation_json['Properties']
security_group_ids = properties.get('SecurityGroups', [])
subnet_id = properties['SubnetId']
subnet = ec2_backend.get_subnet(subnet_id)
private_ip_address = properties.get('PrivateIpAddress', None)
network_interface = ec2_backend.create_network_interface(
subnet,
private_ip_address,
group_ids=security_group_ids
)
return network_interface
def stop(self):
if self.public_ip_auto_assign:
self.public_ip = None
def start(self):
self.check_auto_public_ip()
def check_auto_public_ip(self):
if self.public_ip_auto_assign:
self.public_ip = random_public_ip()
def attach(self, instance_id, device_index):
attachment = {'attachmentId': random_eni_attach_id(),
'instanceId': instance_id,
'deviceIndex': device_index}
self.attachments.append(attachment)
class NetworkInterfaceBackend(object):
def __init__(self):
self.enis = {}
super(NetworkInterfaceBackend, self).__init__()
def create_network_interface(self, subnet, private_ip_address, group_ids=None, **kwargs):
eni = NetworkInterface(subnet, private_ip_address, group_ids=group_ids)
self.enis[eni.id] = eni
return eni
def get_network_interface(self, eni_id):
for eni in self.enis.values():
if eni_id == eni.id:
return eni
raise InvalidNetworkInterfaceIdError(eni_id)
def delete_network_interface(self, eni_id):
deleted = self.enis.pop(eni_id, None)
if not deleted:
raise InvalidNetworkInterfaceIdError(eni_id)
return deleted
def describe_network_interfaces(self, filters=None):
enis = self.enis.values()
if filters:
for (_filter, _filter_value) in filters.items():
if _filter == 'network-interface-id':
_filter = 'id'
enis = [ eni for eni in enis if getattr(eni, _filter) in _filter_value ]
elif _filter == 'group-id':
original_enis = enis
enis = []
for eni in original_enis:
group_ids = []
for group in eni.group_set:
if group.id in _filter_value:
enis.append(eni)
break
else:
ec2_backend.raise_not_implemented_error("The filter '{0}' for DescribeNetworkInterfaces".format(_filter))
return enis
def modify_network_interface_attribute(self, eni_id, group_id):
eni = self.get_network_interface(eni_id)
group = self.get_security_group_from_id(group_id)
eni.group_set = [group]
def prep_nics_for_instance(self, instance, nic_spec, subnet_id=None, private_ip=None, associate_public_ip=None):
nics = {}
# Primary NIC defaults
primary_nic = {'SubnetId': subnet_id,
'PrivateIpAddress': private_ip,
'AssociatePublicIpAddress': associate_public_ip}
primary_nic = dict((k,v) for k, v in primary_nic.items() if v)
# If empty NIC spec but primary NIC values provided, create NIC from them.
if primary_nic and not nic_spec:
nic_spec[0] = primary_nic
nic_spec[0]['DeviceIndex'] = 0
# Flesh out data structures and associations
for nic in nic_spec.values():
use_eni = None
security_group_ids = []
device_index = int(nic.get('DeviceIndex'))
nic_id = nic.get('NetworkInterfaceId', None)
if nic_id:
# If existing NIC found, use it.
use_nic = ec2_backend.get_network_interface(nic_id)
use_nic.device_index = device_index
use_nic.public_ip_auto_assign = False
else:
# If primary NIC values provided, use them for the primary NIC.
if device_index == 0 and primary_nic:
nic.update(primary_nic)
subnet = ec2_backend.get_subnet(nic['SubnetId'])
group_id = nic.get('SecurityGroupId',None)
group_ids = [group_id] if group_id else []
use_nic = ec2_backend.create_network_interface(subnet,
nic.get('PrivateIpAddress',None),
device_index=device_index,
public_ip_auto_assign=nic.get('AssociatePublicIpAddress',False),
group_ids=group_ids)
use_nic.instance = instance # This is used upon associate/disassociate public IP.
if use_nic.instance.security_groups:
use_nic.group_set.extend(use_nic.instance.security_groups)
use_nic.attach(instance.id, device_index)
nics[device_index] = use_nic
return nics
class Instance(BotoInstance, TaggedEC2Instance): class Instance(BotoInstance, TaggedEC2Instance):
def __init__(self, image_id, user_data, security_groups, **kwargs): def __init__(self, image_id, user_data, security_groups, **kwargs):
super(Instance, self).__init__() super(Instance, self).__init__()
@ -108,6 +278,12 @@ class Instance(BotoInstance, TaggedEC2Instance):
# string will have a "u" prefix -- need to get rid of it # string will have a "u" prefix -- need to get rid of it
self.user_data[0] = self.user_data[0].encode('utf-8') self.user_data[0] = self.user_data[0].encode('utf-8')
self.nics = ec2_backend.prep_nics_for_instance(self,
kwargs.get("nics", {}),
subnet_id=kwargs.get("subnet_id",None),
private_ip=kwargs.get("private_ip",None),
associate_public_ip=kwargs.get("associate_public_ip",None))
@classmethod @classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json): def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
properties = cloudformation_json['Properties'] properties = cloudformation_json['Properties']
@ -131,14 +307,23 @@ class Instance(BotoInstance, TaggedEC2Instance):
return self.id return self.id
def start(self, *args, **kwargs): def start(self, *args, **kwargs):
for nic in self.nics.values():
nic.start()
self._state.name = "running" self._state.name = "running"
self._state.code = 16 self._state.code = 16
def stop(self, *args, **kwargs): def stop(self, *args, **kwargs):
for nic in self.nics.values():
nic.stop()
self._state.name = "stopped" self._state.name = "stopped"
self._state.code = 80 self._state.code = 80
def terminate(self, *args, **kwargs): def terminate(self, *args, **kwargs):
for nic in self.nics.values():
nic.stop()
self._state.name = "terminated" self._state.name = "terminated"
self._state.code = 48 self._state.code = 48
@ -146,6 +331,21 @@ class Instance(BotoInstance, TaggedEC2Instance):
self._state.name = "running" self._state.name = "running"
self._state.code = 16 self._state.code = 16
def get_tags(self):
tags = ec2_backend.describe_tags(self.id)
return tags
@property
def dynamic_group_list(self):
if self.nics:
groups = []
for nic in self.nics.values():
for group in nic.group_set:
groups.append(group)
return groups
else:
return self.security_groups
class InstanceBackend(object): class InstanceBackend(object):
@ -511,6 +711,7 @@ class SecurityGroup(object):
self.description = description self.description = description
self.ingress_rules = [] self.ingress_rules = []
self.egress_rules = [] self.egress_rules = []
self.enis = {}
self.vpc_id = vpc_id self.vpc_id = vpc_id
@classmethod @classmethod
@ -569,18 +770,23 @@ class SecurityGroupBackend(object):
def describe_security_groups(self): def describe_security_groups(self):
return itertools.chain(*[x.values() for x in self.groups.values()]) return itertools.chain(*[x.values() for x in self.groups.values()])
def _delete_security_group(self, vpc_id, group_id):
if self.groups[vpc_id][group_id].enis:
raise DependencyViolationError("{0} is being utilized by {1}".format(group_id, 'ENIs'))
return self.groups[vpc_id].pop(group_id)
def delete_security_group(self, name=None, group_id=None): def delete_security_group(self, name=None, group_id=None):
if group_id: if group_id:
# loop over all the SGs, find the right one # loop over all the SGs, find the right one
for vpc in self.groups.values(): for vpc_id, groups in self.groups.items():
if group_id in vpc: if group_id in groups:
return vpc.pop(group_id) return self._delete_security_group(vpc_id, group_id)
raise InvalidSecurityGroupNotFoundError(group_id) raise InvalidSecurityGroupNotFoundError(group_id)
elif name: elif name:
# Group Name. Has to be in standard EC2, VPC needs to be identified by group_id # Group Name. Has to be in standard EC2, VPC needs to be identified by group_id
group = self.get_security_group_from_name(name) group = self.get_security_group_from_name(name)
if group: if group:
return self.groups[None].pop(group.id) return self._delete_security_group(None, group.id)
raise InvalidSecurityGroupNotFoundError(name) raise InvalidSecurityGroupNotFoundError(name)
def get_security_group_from_id(self, group_id): def get_security_group_from_id(self, group_id):
@ -1003,6 +1209,12 @@ class SubnetBackend(object):
self.subnets = {} self.subnets = {}
super(SubnetBackend, self).__init__() super(SubnetBackend, self).__init__()
def get_subnet(self, subnet_id):
subnet = self.subnets.get(subnet_id, None)
if not subnet:
raise InvalidSubnetIdError(subnet_id)
return subnet
def create_subnet(self, vpc_id, cidr_block): def create_subnet(self, vpc_id, cidr_block):
subnet_id = random_subnet_id() subnet_id = random_subnet_id()
subnet = Subnet(subnet_id, vpc_id, cidr_block) subnet = Subnet(subnet_id, vpc_id, cidr_block)
@ -1289,6 +1501,7 @@ class ElasticAddress(object):
self.allocation_id = random_eip_allocation_id() if domain == "vpc" else None self.allocation_id = random_eip_allocation_id() if domain == "vpc" else None
self.domain = domain self.domain = domain
self.instance = None self.instance = None
self.eni = None
self.association_id = None self.association_id = None
@classmethod @classmethod
@ -1355,7 +1568,7 @@ class ElasticAddressBackend(object):
return eips return eips
def associate_address(self, instance, address=None, allocation_id=None, reassociate=False): def associate_address(self, instance=None, eni=None, address=None, allocation_id=None, reassociate=False):
eips = [] eips = []
if address: if address:
eips = self.address_by_ip([address]) eips = self.address_by_ip([address])
@ -1363,13 +1576,20 @@ class ElasticAddressBackend(object):
eips = self.address_by_allocation([allocation_id]) eips = self.address_by_allocation([allocation_id])
eip = eips[0] eip = eips[0]
if eip.instance and not reassociate: new_instance_association = bool(instance and (not eip.instance or eip.instance.id == instance.id))
raise ResourceAlreadyAssociatedError(eip.public_ip) new_eni_association = bool(eni and (not eip.eni or eni.id == eip.eni.id))
eip.instance = instance if new_instance_association or new_eni_association or reassociate:
if eip.domain == "vpc": eip.instance = instance
eip.association_id = random_eip_association_id() eip.eni = eni
return eip if eip.eni:
eip.eni.public_ip = eip.public_ip
if eip.domain == "vpc":
eip.association_id = random_eip_association_id()
return eip
raise ResourceAlreadyAssociatedError(eip.public_ip)
def describe_addresses(self): def describe_addresses(self):
return self.addresses return self.addresses
@ -1382,6 +1602,13 @@ class ElasticAddressBackend(object):
eips = self.address_by_association([association_id]) eips = self.address_by_association([association_id])
eip = eips[0] eip = eips[0]
if eip.eni:
if eip.eni.instance and eip.eni.instance._state.name == "running":
eip.eni.check_auto_public_ip()
else:
eip.eni.public_ip = None
eip.eni = None
eip.instance = None eip.instance = None
eip.association_id = None eip.association_id = None
return True return True
@ -1474,6 +1701,7 @@ class DHCPOptionsSetBackend(object):
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend, RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend, VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
NetworkInterfaceBackend,
VPCPeeringConnectionBackend, VPCPeeringConnectionBackend,
RouteTableBackend, RouteBackend, InternetGatewayBackend, RouteTableBackend, RouteBackend, InternetGatewayBackend,
VPCGatewayAttachmentBackend, SpotRequestBackend, VPCGatewayAttachmentBackend, SpotRequestBackend,

View File

@ -17,10 +17,12 @@ class ElasticIPAddresses(BaseResponse):
return template.render(address=address) return template.render(address=address)
def associate_address(self): def associate_address(self):
instance = eni = None
if "InstanceId" in self.querystring: if "InstanceId" in self.querystring:
instance = ec2_backend.get_instance(self.querystring['InstanceId'][0]) instance = ec2_backend.get_instance(self.querystring['InstanceId'][0])
elif "NetworkInterfaceId" in self.querystring: elif "NetworkInterfaceId" in self.querystring:
raise NotImplementedError("Lookup by allocation id not implemented") eni = ec2_backend.get_network_interface(self.querystring['NetworkInterfaceId'][0])
else: else:
ec2_backend.raise_error("MissingParameter", "Invalid request, expect InstanceId/NetworkId parameter.") ec2_backend.raise_error("MissingParameter", "Invalid request, expect InstanceId/NetworkId parameter.")
@ -28,12 +30,15 @@ class ElasticIPAddresses(BaseResponse):
if "AllowReassociation" in self.querystring: if "AllowReassociation" in self.querystring:
reassociate = self.querystring['AllowReassociation'][0] == "true" reassociate = self.querystring['AllowReassociation'][0] == "true"
if "PublicIp" in self.querystring: if instance or eni:
eip = ec2_backend.associate_address(instance, address=self.querystring['PublicIp'][0], reassociate=reassociate) if "PublicIp" in self.querystring:
elif "AllocationId" in self.querystring: eip = ec2_backend.associate_address(instance=instance, eni=eni, address=self.querystring['PublicIp'][0], reassociate=reassociate)
eip = ec2_backend.associate_address(instance, allocation_id=self.querystring['AllocationId'][0], reassociate=reassociate) elif "AllocationId" in self.querystring:
eip = ec2_backend.associate_address(instance=instance, eni=eni, allocation_id=self.querystring['AllocationId'][0], reassociate=reassociate)
else:
ec2_backend.raise_error("MissingParameter", "Invalid request, expect PublicIp/AllocationId parameter.")
else: else:
ec2_backend.raise_error("MissingParameter", "Invalid request, expect PublicIp/AllocationId parameter.") ec2_backend.raise_error("MissingParameter", "Invalid request, expect either instance or ENI.")
template = Template(ASSOCIATE_ADDRESS_RESPONSE) template = Template(ASSOCIATE_ADDRESS_RESPONSE)
return template.render(address=eip) return template.render(address=eip)
@ -103,6 +108,11 @@ DESCRIBE_ADDRESS_RESPONSE = """<DescribeAddressesResponse xmlns="http://ec2.amaz
{% else %} {% else %}
<instanceId/> <instanceId/>
{% endif %} {% endif %}
{% if address.eni %}
<networkInterfaceId>{{ address.eni.id }}</networkInterfaceId>
{% else %}
<networkInterfaceId/>
{% endif %}
{% if address.allocation_id %} {% if address.allocation_id %}
<allocationId>{{ address.allocation_id }}</allocationId> <allocationId>{{ address.allocation_id }}</allocationId>
{% endif %} {% endif %}

View File

@ -1,5 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from jinja2 import Template
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from moto.ec2.models import ec2_backend
from moto.ec2.utils import sequence_from_querystring, resource_ids_from_querystring, filters_from_querystring
class ElasticNetworkInterfaces(BaseResponse): class ElasticNetworkInterfaces(BaseResponse):
@ -7,22 +11,157 @@ class ElasticNetworkInterfaces(BaseResponse):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).attach_network_interface is not yet implemented') raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).attach_network_interface is not yet implemented')
def create_network_interface(self): def create_network_interface(self):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).create_network_interface is not yet implemented') subnet_id = self.querystring.get('SubnetId')[0]
private_ip_address = self.querystring.get('PrivateIpAddress', [None])[0]
groups = sequence_from_querystring('SecurityGroupId', self.querystring)
subnet = ec2_backend.get_subnet(subnet_id)
eni = ec2_backend.create_network_interface(subnet, private_ip_address, groups)
template = Template(CREATE_NETWORK_INTERFACE_RESPONSE)
return template.render(eni=eni)
def delete_network_interface(self): def delete_network_interface(self):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).delete_network_interface is not yet implemented') eni_id = self.querystring.get('NetworkInterfaceId')[0]
eni = ec2_backend.delete_network_interface(eni_id)
template = Template(DELETE_NETWORK_INTERFACE_RESPONSE)
return template.render()
def describe_network_interface_attribute(self): def describe_network_interface_attribute(self):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).describe_network_interface_attribute is not yet implemented') raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).describe_network_interface_attribute is not yet implemented')
def describe_network_interfaces(self): def describe_network_interfaces(self):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).describe_network_interfaces is not yet implemented') #Partially implemented. Supports only network-interface-id and group-id filters
filters = filters_from_querystring(self.querystring)
enis = ec2_backend.describe_network_interfaces(filters)
template = Template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
return template.render(enis=enis)
def detach_network_interface(self): def detach_network_interface(self):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).detach_network_interface is not yet implemented') raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).detach_network_interface is not yet implemented')
def modify_network_interface_attribute(self): def modify_network_interface_attribute(self):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).modify_network_interface_attribute is not yet implemented') #Currently supports modifying one and only one security group
eni_id = self.querystring.get('NetworkInterfaceId')[0]
group_id = self.querystring.get('SecurityGroupId.1')[0]
ec2_backend.modify_network_interface_attribute(eni_id, group_id)
return MODIFY_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE
def reset_network_interface_attribute(self): def reset_network_interface_attribute(self):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).reset_network_interface_attribute is not yet implemented') raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).reset_network_interface_attribute is not yet implemented')
CREATE_NETWORK_INTERFACE_RESPONSE = """
<CreateNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>2c6021ec-d705-445a-9780-420d0c7ab793</requestId>
<networkInterface>
<networkInterfaceId>{{ eni.id }}</networkInterfaceId>
<subnetId>{{ eni.subnet.id }}</subnetId>
<vpcId>{{ eni.subnet.vpc_id }}</vpcId>
<availabilityZone>us-west-2a</availabilityZone>
<description/>
<ownerId>498654062920</ownerId>
<requesterManaged>false</requesterManaged>
<status>pending</status>
<macAddress>02:07:a9:b6:12:51</macAddress>
{% if eni.private_ip_address %}
<privateIpAddress>{{ eni.private_ip_address }}</privateIpAddress>
{% endif %}
<sourceDestCheck>true</sourceDestCheck>
<groupSet>
{% for group in eni.group_set %}
<item>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
</item>
{% endfor %}
</groupSet>
<tagSet/>
{% if eni.private_ip_address %}
<privateIpAddressesSet>
<item>
<privateIpAddress>{{ eni.private_ip_address }}</privateIpAddress>
<primary>true</primary>
</item>
</privateIpAddressesSet>
{% else %}
<privateIpAddressesSet/>
{% endif %}
</networkInterface>
</CreateNetworkInterfaceResponse>
"""
DESCRIBE_NETWORK_INTERFACES_RESPONSE = """<DescribeNetworkInterfacesResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>ddb0aaf1-8b65-4f0a-94fa-654b18b8a204</requestId>
<networkInterfaceSet>
{% for eni in enis %}
<item>
<networkInterfaceId>{{ eni.id }}</networkInterfaceId>
<subnetId>{{ eni.subnet.id }}</subnetId>
<vpcId>vpc-9367a6f8</vpcId>
<availabilityZone>us-west-2a</availabilityZone>
<description>Primary network interface</description>
<ownerId>190610284047</ownerId>
<requesterManaged>false</requesterManaged>
<status>in-use</status>
<macAddress>0e:a3:a7:7b:95:a7</macAddress>
{% if eni.private_ip_address %}
<privateIpAddress>{{ eni.private_ip_address }}</privateIpAddress>
{% endif %}
<privateDnsName>ip-10-0-0-134.us-west-2.compute.internal</privateDnsName>
<sourceDestCheck>true</sourceDestCheck>
<groupSet>
{% for group in eni.group_set %}
<item>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
</item>
{% endfor %}
</groupSet>
{% for attachment in eni.attachments %}
<attachment>
<attachmentId>{{ attachment['attachmentId'] }}</attachmentId>
<instanceId>{{ attachment['instanceId'] }}</instanceId>
<instanceOwnerId>190610284047</instanceOwnerId>
<deviceIndex>{{ attachment['deviceIndex'] }}</deviceIndex>
<status>attached</status>
<attachTime>2013-10-04T17:38:53.000Z</attachTime>
<deleteOnTermination>true</deleteOnTermination>
</attachment>
{% endfor %}
<association>
<publicIp>{{ eni.public_ip }}</publicIp>
<publicDnsName>ec2-54-200-86-47.us-west-2.compute.amazonaws.com</publicDnsName>
<ipOwnerId>amazon</ipOwnerId>
</association>
<tagSet/>
{% if eni.private_ip_address %}
<privateIpAddressesSet>
<item>
<privateIpAddress>{{ eni.private_ip_address }}</privateIpAddress>
<privateDnsName>ip-10-0-0-134.us-west-2.compute.internal</privateDnsName>
<primary>true</primary>
{% if eni.public_ip %}
<association>
<publicIp>{{ eni.public_ip }}</publicIp>
<publicDnsName>ec2-54-200-86-47.us-west-2.compute.amazonaws.com</publicDnsName>
<ipOwnerId>amazon</ipOwnerId>
</association>
{% endif %}
</item>
</privateIpAddressesSet>
{% else %}
<privateIpAddressesSet/>
{% endif %}
</item>
{% endfor %}
</networkInterfaceSet>
</DescribeNetworkInterfacesResponse>"""
MODIFY_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE = """<ModifyNetworkInterfaceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2012-12-01/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</ModifyNetworkInterfaceAttributeResponse>"""
DELETE_NETWORK_INTERFACE_RESPONSE = """
<DeleteNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2013-07-15/">
<requestId>34b5b3b4-d0c5-49b9-b5e2-a468ef6adcd8</requestId>
<return>true</return>
</DeleteNetworkInterfaceResponse>"""

View File

@ -3,7 +3,7 @@ from jinja2 import Template
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores from moto.core.utils import camelcase_to_underscores
from moto.ec2.utils import instance_ids_from_querystring, filters_from_querystring, filter_reservations from moto.ec2.utils import instance_ids_from_querystring, filters_from_querystring, filter_reservations, dict_from_querystring
class InstanceResponse(BaseResponse): class InstanceResponse(BaseResponse):
@ -26,13 +26,19 @@ class InstanceResponse(BaseResponse):
user_data = self.querystring.get('UserData') user_data = self.querystring.get('UserData')
security_group_names = self._get_multi_param('SecurityGroup') security_group_names = self._get_multi_param('SecurityGroup')
security_group_ids = self._get_multi_param('SecurityGroupId') security_group_ids = self._get_multi_param('SecurityGroupId')
nics = dict_from_querystring("NetworkInterface", self.querystring)
instance_type = self.querystring.get("InstanceType", ["m1.small"])[0] instance_type = self.querystring.get("InstanceType", ["m1.small"])[0]
subnet_id = self.querystring.get("SubnetId", [None])[0] subnet_id = self.querystring.get("SubnetId", [None])[0]
private_ip = self.querystring.get("PrivateIpAddress", [None])[0]
associate_public_ip = self.querystring.get("AssociatePublicIpAddress", [None])[0]
key_name = self.querystring.get("KeyName", [None])[0] key_name = self.querystring.get("KeyName", [None])[0]
new_reservation = self.ec2_backend.add_instances( new_reservation = self.ec2_backend.add_instances(
image_id, min_count, user_data, security_group_names, image_id, min_count, user_data, security_group_names,
instance_type=instance_type, subnet_id=subnet_id, instance_type=instance_type, subnet_id=subnet_id,
key_name=key_name, security_group_ids=security_group_ids) key_name=key_name, security_group_ids=security_group_ids,
nics=nics, private_ip=private_ip, associate_public_ip=associate_public_ip)
template = Template(EC2_RUN_INSTANCES) template = Template(EC2_RUN_INSTANCES)
return template.render(reservation=new_reservation) return template.render(reservation=new_reservation)
@ -189,10 +195,19 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
<monitoring> <monitoring>
<state>enabled</state> <state>enabled</state>
</monitoring> </monitoring>
<subnetId>{{ instance.subnet_id }}</subnetId> {% if instance.nics %}
<subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
<vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
<privateIpAddress>{{ instance.nics[0].private_ip_address }}</privateIpAddress>
{% if instance.nics[0].public_ip %}
<ipAddress>46.51.219.63</ipAddress>
{% endif %}
{% else %}
<subnetId>{{ instance.subnet_id }}</subnetId>
{% endif %}
<sourceDestCheck>true</sourceDestCheck> <sourceDestCheck>true</sourceDestCheck>
<groupSet> <groupSet>
{% for group in instance.security_groups %} {% for group in instance.dynamic_group_list %}
<item> <item>
<groupId>{{ group.id }}</groupId> <groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName> <groupName>{{ group.name }}</groupName>
@ -208,6 +223,54 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
<clientToken/> <clientToken/>
<hypervisor>xen</hypervisor> <hypervisor>xen</hypervisor>
<ebsOptimized>false</ebsOptimized> <ebsOptimized>false</ebsOptimized>
<networkInterfaceSet>
{% for nic in instance.nics.values() %}
<item>
<networkInterfaceId>{{ nic.id }}</networkInterfaceId>
<subnetId>{{ nic.subnet.id }}</subnetId>
<vpcId>{{ nic.subnet.vpc_id }}</vpcId>
<description>Primary network interface</description>
<ownerId>111122223333</ownerId>
<status>in-use</status>
<macAddress>1b:2b:3c:4d:5e:6f</macAddress>
<privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
<sourceDestCheck>true</sourceDestCheck>
<groupSet>
{% for group in nic.group_set %}
<item>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
</item>
{% endfor %}
</groupSet>
<attachment>
<attachmentId>eni-attach-1a2b3c4d</attachmentId>
<deviceIndex>{{ nic.device_index }}</deviceIndex>
<status>attached</status>
<attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
<deleteOnTermination>true</deleteOnTermination>
</attachment>
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>111122223333</ipOwnerId>
</association>
{% endif %}
<privateIpAddressesSet>
<item>
<privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
<primary>true</primary>
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>111122223333</ipOwnerId>
</association>
{% endif %}
</item>
</privateIpAddressesSet>
</item>
{% endfor %}
</networkInterfaceSet>
</item> </item>
{% endfor %} {% endfor %}
</instancesSet> </instancesSet>
@ -220,7 +283,14 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
<item> <item>
<reservationId>{{ reservation.id }}</reservationId> <reservationId>{{ reservation.id }}</reservationId>
<ownerId>111122223333</ownerId> <ownerId>111122223333</ownerId>
<groupSet></groupSet> <groupSet>
{% for group in reservation.dynamic_group_list %}
<item>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
</item>
{% endfor %}
</groupSet>
<instancesSet> <instancesSet>
{% for instance in reservation.instances %} {% for instance in reservation.instances %}
<item> <item>
@ -249,13 +319,17 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
<monitoring> <monitoring>
<state>disabled</state> <state>disabled</state>
</monitoring> </monitoring>
<subnetId>{{ instance.subnet_id }}</subnetId> {% if instance.nics %}
<vpcId>vpc-1a2b3c4d</vpcId> <subnetId>{{ instance.nics[0].subnet.id }}</subnetId>
<privateIpAddress>10.0.0.12</privateIpAddress> <vpcId>{{ instance.nics[0].subnet.vpc_id }}</vpcId>
<ipAddress>46.51.219.63</ipAddress> <privateIpAddress>{{ instance.nics[0].private_ip_address }}</privateIpAddress>
{% if instance.nics[0].public_ip %}
<ipAddress>46.51.219.63</ipAddress>
{% endif %}
{% endif %}
<sourceDestCheck>true</sourceDestCheck> <sourceDestCheck>true</sourceDestCheck>
<groupSet> <groupSet>
{% for group in instance.security_groups %} {% for group in instance.dynamic_group_list %}
<item> <item>
<groupId>{{ group.id }}</groupId> <groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName> <groupName>{{ group.name }}</groupName>
@ -280,7 +354,54 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
{% endfor %} {% endfor %}
</tagSet> </tagSet>
<hypervisor>xen</hypervisor> <hypervisor>xen</hypervisor>
<networkInterfaceSet /> <networkInterfaceSet>
{% for nic in instance.nics.values() %}
<item>
<networkInterfaceId>{{ nic.id }}</networkInterfaceId>
<subnetId>{{ nic.subnet.id }}</subnetId>
<vpcId>{{ nic.subnet.vpc_id }}</vpcId>
<description>Primary network interface</description>
<ownerId>111122223333</ownerId>
<status>in-use</status>
<macAddress>1b:2b:3c:4d:5e:6f</macAddress>
<privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
<sourceDestCheck>true</sourceDestCheck>
<groupSet>
{% for group in nic.group_set %}
<item>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
</item>
{% endfor %}
</groupSet>
<attachment>
<attachmentId>eni-attach-1a2b3c4d</attachmentId>
<deviceIndex>{{ nic.device_index }}</deviceIndex>
<status>attached</status>
<attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
<deleteOnTermination>true</deleteOnTermination>
</attachment>
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>111122223333</ipOwnerId>
</association>
{% endif %}
<privateIpAddressesSet>
<item>
<privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
<primary>true</primary>
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>111122223333</ipOwnerId>
</association>
{% endif %}
</item>
</privateIpAddressesSet>
</item>
{% endfor %}
</networkInterfaceSet>
</item> </item>
{% endfor %} {% endfor %}
</instancesSet> </instancesSet>

View File

@ -72,6 +72,18 @@ def random_dhcp_option_id():
return random_id(prefix='dopt') return random_id(prefix='dopt')
def random_eni_id():
return random_id(prefix='eni')
def random_eni_attach_id():
return random_id(prefix='eni-attach')
def random_public_ip():
return '54.214.{0}.{1}'.format(random.choice(range(255)),
random.choice(range(255)))
def random_ip(): def random_ip():
return "127.{0}.{1}.{2}".format( return "127.{0}.{1}.{2}".format(
random.randint(0, 255), random.randint(0, 255),
@ -172,6 +184,21 @@ def filters_from_querystring(querystring_dict):
return response_values return response_values
def dict_from_querystring(parameter, querystring_dict):
use_dict={}
for key, value in querystring_dict.items():
match = re.search(r"{0}.(\d).(\w+)".format(parameter), key)
if match:
use_dict_index = match.groups()[0]
use_dict_element_property = match.groups()[1]
if not use_dict.get(use_dict_index):
use_dict[use_dict_index] = {}
use_dict[use_dict_index][use_dict_element_property]=value[0]
return use_dict
def keypair_names_from_querystring(querystring_dict): def keypair_names_from_querystring(querystring_dict):
keypair_names = [] keypair_names = []
for key, value in querystring_dict.items(): for key, value in querystring_dict.items():

View File

@ -110,29 +110,89 @@ def test_eip_associate_vpc():
instance.terminate() instance.terminate()
@mock_ec2
def test_eip_associate_network_interface():
"""Associate/Disassociate EIP to NIC"""
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
eni = conn.create_network_interface(subnet.id)
eip = conn.allocate_address(domain='vpc')
eip.network_interface_id.should.be.none
with assert_raises(EC2ResponseError) as cm:
conn.associate_address(network_interface_id=eni.id)
cm.exception.code.should.equal('MissingParameter')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
conn.associate_address(network_interface_id=eni.id, allocation_id=eip.allocation_id)
eip = conn.get_all_addresses(addresses=[eip.public_ip])[0] # no .update() on address ):
eip.network_interface_id.should.be.equal(eni.id)
conn.disassociate_address(association_id=eip.association_id)
eip = conn.get_all_addresses(addresses=[eip.public_ip])[0] # no .update() on address ):
eip.network_interface_id.should.be.equal(u'')
eip.association_id.should.be.none
eip.release()
eip = None
@mock_ec2 @mock_ec2
def test_eip_reassociate(): def test_eip_reassociate():
"""reassociate EIP""" """reassociate EIP"""
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd', min_count=2)
instance = reservation.instances[0] instance1, instance2 = reservation.instances
eip = conn.allocate_address() eip = conn.allocate_address()
conn.associate_address(instance_id=instance.id, public_ip=eip.public_ip) conn.associate_address(instance_id=instance1.id, public_ip=eip.public_ip)
# Same ID is idempotent
conn.associate_address(instance_id=instance1.id, public_ip=eip.public_ip)
# Different ID detects resource association
with assert_raises(EC2ResponseError) as cm: with assert_raises(EC2ResponseError) as cm:
conn.associate_address(instance_id=instance.id, public_ip=eip.public_ip, allow_reassociation=False) conn.associate_address(instance_id=instance2.id, public_ip=eip.public_ip, allow_reassociation=False)
cm.exception.code.should.equal('Resource.AlreadyAssociated') cm.exception.code.should.equal('Resource.AlreadyAssociated')
cm.exception.status.should.equal(400) cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
conn.associate_address.when.called_with(instance_id=instance.id, public_ip=eip.public_ip, allow_reassociation=True).should_not.throw(EC2ResponseError) conn.associate_address.when.called_with(instance_id=instance2.id, public_ip=eip.public_ip, allow_reassociation=True).should_not.throw(EC2ResponseError)
eip.release() eip.release()
eip = None eip = None
instance.terminate() instance1.terminate()
instance2.terminate()
@mock_ec2
def test_eip_reassociate_nic():
"""reassociate EIP"""
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
eni1 = conn.create_network_interface(subnet.id)
eni2 = conn.create_network_interface(subnet.id)
eip = conn.allocate_address()
conn.associate_address(network_interface_id=eni1.id, public_ip=eip.public_ip)
# Same ID is idempotent
conn.associate_address(network_interface_id=eni1.id, public_ip=eip.public_ip)
# Different ID detects resource association
with assert_raises(EC2ResponseError) as cm:
conn.associate_address(network_interface_id=eni2.id, public_ip=eip.public_ip)
cm.exception.code.should.equal('Resource.AlreadyAssociated')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
conn.associate_address.when.called_with(network_interface_id=eni2.id, public_ip=eip.public_ip, allow_reassociation=True).should_not.throw(EC2ResponseError)
eip.release()
eip = None
@mock_ec2 @mock_ec2
def test_eip_associate_invalid_args(): def test_eip_associate_invalid_args():

View File

@ -1,5 +1,10 @@
from __future__ import unicode_literals from __future__ import unicode_literals
# Ensure 'assert_raises' context manager support for Python 2.6
import tests.backport_assert_raises
from nose.tools import assert_raises
import boto import boto
from boto.exception import EC2ResponseError
import sure # noqa import sure # noqa
from moto import mock_ec2 from moto import mock_ec2
@ -7,4 +12,115 @@ from moto import mock_ec2
@mock_ec2 @mock_ec2
def test_elastic_network_interfaces(): def test_elastic_network_interfaces():
pass conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
eni = conn.create_network_interface(subnet.id)
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(1)
eni = all_enis[0]
eni.groups.should.have.length_of(0)
eni.private_ip_addresses.should.have.length_of(0)
conn.delete_network_interface(eni.id)
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(0)
with assert_raises(EC2ResponseError) as cm:
conn.delete_network_interface(eni.id)
cm.exception.code.should.equal('InvalidNetworkInterfaceID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_elastic_network_interfaces_subnet_validation():
conn = boto.connect_vpc('the_key', 'the_secret')
with assert_raises(EC2ResponseError) as cm:
conn.create_network_interface("subnet-abcd1234")
cm.exception.code.should.equal('InvalidSubnetID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_elastic_network_interfaces_with_private_ip():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
private_ip = "54.0.0.1"
eni = conn.create_network_interface(subnet.id, private_ip)
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(1)
eni = all_enis[0]
eni.groups.should.have.length_of(0)
eni.private_ip_addresses.should.have.length_of(1)
eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip)
@mock_ec2
def test_elastic_network_interfaces_with_groups():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
security_group1 = conn.create_security_group('test security group #1', 'this is a test security group')
security_group2 = conn.create_security_group('test security group #2', 'this is a test security group')
conn.create_network_interface(subnet.id, groups=[security_group1.id,security_group2.id])
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(1)
eni = all_enis[0]
eni.groups.should.have.length_of(2)
set([group.id for group in eni.groups]).should.equal(set([security_group1.id,security_group2.id]))
conn.modify_network_interface_attribute(eni.id, 'groupset', [security_group1.id])
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(1)
eni = all_enis[0]
eni.groups.should.have.length_of(1)
eni.groups[0].id.should.equal(security_group1.id)
@mock_ec2
def test_elastic_network_interfaces_filtering():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
security_group1 = conn.create_security_group('test security group #1', 'this is a test security group')
security_group2 = conn.create_security_group('test security group #2', 'this is a test security group')
eni1 = conn.create_network_interface(subnet.id, groups=[security_group1.id,security_group2.id])
eni2 = conn.create_network_interface(subnet.id, groups=[security_group1.id])
eni3 = conn.create_network_interface(subnet.id)
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(3)
# Filter by ENI ID
enis_by_id = conn.get_all_network_interfaces(filters={'network-interface-id':eni1.id})
enis_by_id.should.have.length_of(1)
set([eni.id for eni in enis_by_id]).should.equal(set([eni1.id]))
# Filter by Security Group
enis_by_group = conn.get_all_network_interfaces(filters={'group-id':security_group1.id})
enis_by_group.should.have.length_of(2)
set([eni.id for eni in enis_by_group]).should.equal(set([eni1.id,eni2.id]))
# Filter by ENI ID and Security Group
enis_by_group = conn.get_all_network_interfaces(filters={'network-interface-id':eni1.id, 'group-id':security_group1.id})
enis_by_group.should.have.length_of(1)
set([eni.id for eni in enis_by_group]).should.equal(set([eni1.id]))
# Unsupported filter
conn.get_all_network_interfaces.when.called_with(filters={'not-implemented-filter': 'foobar'}).should.throw(NotImplementedError)

View File

@ -233,12 +233,86 @@ def test_run_instance_with_instance_type():
@mock_ec2 @mock_ec2
def test_run_instance_with_subnet(): def test_run_instance_with_subnet():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_vpc('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd', vpc = conn.create_vpc("10.0.0.0/16")
subnet_id="subnet-abcd1234") subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
reservation = conn.run_instances('ami-1234abcd', subnet_id=subnet.id)
instance = reservation.instances[0] instance = reservation.instances[0]
instance.subnet_id.should.equal("subnet-abcd1234") instance.subnet_id.should.equal(subnet.id)
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(1)
@mock_ec2
def test_run_instance_with_nic_autocreated():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
security_group1 = conn.create_security_group('test security group #1', 'this is a test security group')
security_group2 = conn.create_security_group('test security group #2', 'this is a test security group')
private_ip = "54.0.0.1"
reservation = conn.run_instances('ami-1234abcd', subnet_id=subnet.id,
security_groups=[security_group1.name],
security_group_ids=[security_group2.id],
private_ip_address=private_ip)
instance = reservation.instances[0]
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(1)
eni = all_enis[0]
instance.interfaces.should.have.length_of(1)
instance.interfaces[0].id.should.equal(eni.id)
instance.subnet_id.should.equal(subnet.id)
instance.groups.should.have.length_of(2)
set([group.id for group in instance.groups]).should.equal(set([security_group1.id,security_group2.id]))
eni.subnet_id.should.equal(subnet.id)
eni.groups.should.have.length_of(2)
set([group.id for group in eni.groups]).should.equal(set([security_group1.id,security_group2.id]))
eni.private_ip_addresses.should.have.length_of(1)
eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip)
@mock_ec2
def test_run_instance_with_nic_preexisting():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
security_group1 = conn.create_security_group('test security group #1', 'this is a test security group')
security_group2 = conn.create_security_group('test security group #2', 'this is a test security group')
private_ip = "54.0.0.1"
eni = conn.create_network_interface(subnet.id, private_ip, groups=[security_group1.id])
# Boto requires NetworkInterfaceCollection of NetworkInterfaceSpecifications...
# annoying, but generates the desired querystring.
from boto.ec2.networkinterface import NetworkInterfaceSpecification, NetworkInterfaceCollection
interface = NetworkInterfaceSpecification(network_interface_id=eni.id, device_index=0)
interfaces = NetworkInterfaceCollection(interface)
# end Boto objects
reservation = conn.run_instances('ami-1234abcd', network_interfaces=interfaces,
security_group_ids=[security_group2.id])
instance = reservation.instances[0]
instance.subnet_id.should.equal(subnet.id)
all_enis = conn.get_all_network_interfaces()
all_enis.should.have.length_of(1)
instance.interfaces.should.have.length_of(1)
instance_eni = instance.interfaces[0]
instance_eni.id.should.equal(eni.id)
instance_eni.subnet_id.should.equal(subnet.id)
instance_eni.groups.should.have.length_of(2)
set([group.id for group in instance_eni.groups]).should.equal(set([security_group1.id,security_group2.id]))
instance_eni.private_ip_addresses.should.have.length_of(1)
instance_eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip)
@mock_ec2 @mock_ec2