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))
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):
def __init__(self, name):
super(InvalidSecurityGroupDuplicateError, self).__init__(

View File

@ -26,6 +26,7 @@ from .exceptions import (
ResourceAlreadyAssociatedError,
InvalidVPCIdError,
InvalidSubnetIdError,
InvalidNetworkInterfaceIdError,
InvalidSecurityGroupDuplicateError,
InvalidSecurityGroupNotFoundError,
InvalidPermissionNotFoundError,
@ -48,11 +49,14 @@ from .utils import (
random_dhcp_option_id,
random_eip_allocation_id,
random_eip_association_id,
random_eni_attach_id,
random_eni_id,
random_internet_gateway_id,
random_instance_id,
random_internet_gateway_id,
random_ip,
random_key_pair,
random_public_ip,
random_reservation_id,
random_route_table_id,
random_security_group_id,
@ -77,6 +81,172 @@ class TaggedEC2Instance(object):
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):
def __init__(self, image_id, user_data, security_groups, **kwargs):
super(Instance, self).__init__()
@ -108,6 +278,12 @@ class Instance(BotoInstance, TaggedEC2Instance):
# string will have a "u" prefix -- need to get rid of it
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
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
properties = cloudformation_json['Properties']
@ -131,14 +307,23 @@ class Instance(BotoInstance, TaggedEC2Instance):
return self.id
def start(self, *args, **kwargs):
for nic in self.nics.values():
nic.start()
self._state.name = "running"
self._state.code = 16
def stop(self, *args, **kwargs):
for nic in self.nics.values():
nic.stop()
self._state.name = "stopped"
self._state.code = 80
def terminate(self, *args, **kwargs):
for nic in self.nics.values():
nic.stop()
self._state.name = "terminated"
self._state.code = 48
@ -146,6 +331,21 @@ class Instance(BotoInstance, TaggedEC2Instance):
self._state.name = "running"
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):
@ -511,6 +711,7 @@ class SecurityGroup(object):
self.description = description
self.ingress_rules = []
self.egress_rules = []
self.enis = {}
self.vpc_id = vpc_id
@classmethod
@ -569,18 +770,23 @@ class SecurityGroupBackend(object):
def describe_security_groups(self):
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):
if group_id:
# loop over all the SGs, find the right one
for vpc in self.groups.values():
if group_id in vpc:
return vpc.pop(group_id)
for vpc_id, groups in self.groups.items():
if group_id in groups:
return self._delete_security_group(vpc_id, group_id)
raise InvalidSecurityGroupNotFoundError(group_id)
elif name:
# Group Name. Has to be in standard EC2, VPC needs to be identified by group_id
group = self.get_security_group_from_name(name)
if group:
return self.groups[None].pop(group.id)
return self._delete_security_group(None, group.id)
raise InvalidSecurityGroupNotFoundError(name)
def get_security_group_from_id(self, group_id):
@ -1003,6 +1209,12 @@ class SubnetBackend(object):
self.subnets = {}
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):
subnet_id = random_subnet_id()
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.domain = domain
self.instance = None
self.eni = None
self.association_id = None
@classmethod
@ -1355,7 +1568,7 @@ class ElasticAddressBackend(object):
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 = []
if address:
eips = self.address_by_ip([address])
@ -1363,13 +1576,20 @@ class ElasticAddressBackend(object):
eips = self.address_by_allocation([allocation_id])
eip = eips[0]
if eip.instance and not reassociate:
raise ResourceAlreadyAssociatedError(eip.public_ip)
new_instance_association = bool(instance and (not eip.instance or eip.instance.id == instance.id))
new_eni_association = bool(eni and (not eip.eni or eni.id == eip.eni.id))
eip.instance = instance
if eip.domain == "vpc":
eip.association_id = random_eip_association_id()
return eip
if new_instance_association or new_eni_association or reassociate:
eip.instance = instance
eip.eni = eni
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):
return self.addresses
@ -1382,6 +1602,13 @@ class ElasticAddressBackend(object):
eips = self.address_by_association([association_id])
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.association_id = None
return True
@ -1474,6 +1701,7 @@ class DHCPOptionsSetBackend(object):
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
NetworkInterfaceBackend,
VPCPeeringConnectionBackend,
RouteTableBackend, RouteBackend, InternetGatewayBackend,
VPCGatewayAttachmentBackend, SpotRequestBackend,

View File

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

View File

@ -1,5 +1,9 @@
from __future__ import unicode_literals
from jinja2 import Template
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):
@ -7,22 +11,157 @@ class ElasticNetworkInterfaces(BaseResponse):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).attach_network_interface is not yet implemented')
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):
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):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).describe_network_interface_attribute is not yet implemented')
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):
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).detach_network_interface is not yet implemented')
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):
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.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):
@ -26,13 +26,19 @@ class InstanceResponse(BaseResponse):
user_data = self.querystring.get('UserData')
security_group_names = self._get_multi_param('SecurityGroup')
security_group_ids = self._get_multi_param('SecurityGroupId')
nics = dict_from_querystring("NetworkInterface", self.querystring)
instance_type = self.querystring.get("InstanceType", ["m1.small"])[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]
new_reservation = self.ec2_backend.add_instances(
image_id, min_count, user_data, security_group_names,
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)
return template.render(reservation=new_reservation)
@ -189,10 +195,19 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
<monitoring>
<state>enabled</state>
</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>
<groupSet>
{% for group in instance.security_groups %}
{% for group in instance.dynamic_group_list %}
<item>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
@ -208,6 +223,54 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
<clientToken/>
<hypervisor>xen</hypervisor>
<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>
{% endfor %}
</instancesSet>
@ -220,7 +283,14 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
<item>
<reservationId>{{ reservation.id }}</reservationId>
<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>
{% for instance in reservation.instances %}
<item>
@ -249,13 +319,17 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
<monitoring>
<state>disabled</state>
</monitoring>
<subnetId>{{ instance.subnet_id }}</subnetId>
<vpcId>vpc-1a2b3c4d</vpcId>
<privateIpAddress>10.0.0.12</privateIpAddress>
<ipAddress>46.51.219.63</ipAddress>
{% 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 %}
{% endif %}
<sourceDestCheck>true</sourceDestCheck>
<groupSet>
{% for group in instance.security_groups %}
{% for group in instance.dynamic_group_list %}
<item>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
@ -280,7 +354,54 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
{% endfor %}
</tagSet>
<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>
{% endfor %}
</instancesSet>

View File

@ -72,6 +72,18 @@ def random_dhcp_option_id():
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():
return "127.{0}.{1}.{2}".format(
random.randint(0, 255),
@ -172,6 +184,21 @@ def filters_from_querystring(querystring_dict):
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):
keypair_names = []
for key, value in querystring_dict.items():

View File

@ -110,29 +110,89 @@ def test_eip_associate_vpc():
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
def test_eip_reassociate():
"""reassociate EIP"""
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
reservation = conn.run_instances('ami-1234abcd', min_count=2)
instance1, instance2 = reservation.instances
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:
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.status.should.equal(400)
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 = 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
def test_eip_associate_invalid_args():

View File

@ -1,5 +1,10 @@
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
from boto.exception import EC2ResponseError
import sure # noqa
from moto import mock_ec2
@ -7,4 +12,115 @@ from moto import mock_ec2
@mock_ec2
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
def test_run_instance_with_subnet():
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd',
subnet_id="subnet-abcd1234")
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")
reservation = conn.run_instances('ami-1234abcd', subnet_id=subnet.id)
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