diff --git a/moto/ec2/models.py b/moto/ec2/models.py
index 1bcdbe5d7..0a391c5d7 100644
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -15,6 +15,9 @@ from .utils import (
random_subnet_id,
random_volume_id,
random_vpc_id,
+ random_eip_association_id,
+ random_eip_allocation_id,
+ random_ip,
)
@@ -29,24 +32,24 @@ class Instance(BotoInstance):
super(Instance, self).__init__()
self.id = random_instance_id()
self.image_id = image_id
- self._state = InstanceState()
+ self._state = InstanceState("running", 16)
self.user_data = user_data
def start(self):
- self._state.name = "pending"
- self._state.code = 0
+ self._state.name = "running"
+ self._state.code = 16
def stop(self):
- self._state.name = "stopping"
- self._state.code = 64
+ self._state.name = "stopped"
+ self._state.code = 80
def terminate(self):
- self._state.name = "shutting-down"
- self._state.code = 32
+ self._state.name = "terminated"
+ self._state.code = 48
def reboot(self):
- self._state.name = "pending"
- self._state.code = 0
+ self._state.name = "running"
+ self._state.code = 16
def get_tags(self):
tags = ec2_backend.describe_tags(self.id)
@@ -215,8 +218,12 @@ class AmiBackend(object):
self.amis[ami_id] = ami
return ami
- def describe_images(self):
- return self.amis.values()
+ def describe_images(self, ami_ids=None):
+ if ami_ids:
+ images = [image for image in self.amis.values() if image.id in ami_ids]
+ else:
+ images = self.amis.values()
+ return images
def deregister_image(self, ami_id):
if ami_id in self.amis:
@@ -571,9 +578,92 @@ class SpotRequestBackend(object):
return requests
+class ElasticAddress():
+ def __init__(self, domain):
+ self.public_ip = random_ip()
+ self.allocation_id = random_eip_allocation_id() if domain == "vpc" else None
+ self.domain = domain
+ self.instance = None
+ self.association_id = None
+
+
+class ElasticAddressBackend(object):
+
+ def __init__(self):
+ self.addresses = []
+ super(ElasticAddressBackend, self).__init__()
+
+ def allocate_address(self, domain):
+ address = ElasticAddress(domain)
+ self.addresses.append(address)
+ return address
+
+ def address_by_ip(self, ips):
+ return [address for address in self.addresses
+ if address.public_ip in ips]
+
+ def address_by_allocation(self, allocation_ids):
+ return [address for address in self.addresses
+ if address.allocation_id in allocation_ids]
+
+ def address_by_association(self, association_ids):
+ return [address for address in self.addresses
+ if address.association_id in association_ids]
+
+ def associate_address(self, instance, address=None, allocation_id=None, reassociate=False):
+ eips = []
+ if address:
+ eips = self.address_by_ip([address])
+ elif allocation_id:
+ eips = self.address_by_allocation([allocation_id])
+ eip = eips[0] if len(eips) > 0 else None
+
+ if eip and eip.instance is None or reassociate:
+ eip.instance = instance
+ if eip.domain == "vpc":
+ eip.association_id = random_eip_association_id()
+ return eip
+ else:
+ return None
+
+ def describe_addresses(self):
+ return self.addresses
+
+ def disassociate_address(self, address=None, association_id=None):
+ eips = []
+ if address:
+ eips = self.address_by_ip([address])
+ elif association_id:
+ eips = self.address_by_association([association_id])
+
+ if eips:
+ eip = eips[0]
+ eip.instance = None
+ eip.association_id = None
+ return True
+ else:
+ return False
+
+ def release_address(self, address=None, allocation_id=None):
+ eips = []
+ if address:
+ eips = self.address_by_ip([address])
+ elif allocation_id:
+ eips = self.address_by_allocation([allocation_id])
+
+ if eips:
+ eip = eips[0]
+ self.disassociate_address(address=eip.public_ip)
+ eip.allocation_id = None
+ self.addresses.remove(eip)
+ return True
+ else:
+ return False
+
+
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
- VPCBackend, SubnetBackend, SpotRequestBackend):
+ VPCBackend, SubnetBackend, SpotRequestBackend, ElasticAddressBackend):
pass
diff --git a/moto/ec2/responses/amis.py b/moto/ec2/responses/amis.py
index b95fbfab6..10936e635 100644
--- a/moto/ec2/responses/amis.py
+++ b/moto/ec2/responses/amis.py
@@ -1,13 +1,16 @@
from jinja2 import Template
from moto.ec2.models import ec2_backend
-from moto.ec2.utils import instance_ids_from_querystring
+from moto.ec2.utils import instance_ids_from_querystring, image_ids_from_querystring
class AmisResponse(object):
def create_image(self):
name = self.querystring.get('Name')[0]
- description = self.querystring.get('Description')[0]
+ if "Description" in self.querystring:
+ description = self.querystring.get('Description')[0]
+ else:
+ description = ""
instance_ids = instance_ids_from_querystring(self.querystring)
instance_id = instance_ids[0]
image = ec2_backend.create_image(instance_id, name, description)
@@ -30,7 +33,8 @@ class AmisResponse(object):
raise NotImplementedError('AMIs.describe_image_attribute is not yet implemented')
def describe_images(self):
- images = ec2_backend.describe_images()
+ ami_ids = image_ids_from_querystring(self.querystring)
+ images = ec2_backend.describe_images(ami_ids=ami_ids)
template = Template(DESCRIBE_IMAGES_RESPONSE)
return template.render(images=images)
diff --git a/moto/ec2/responses/elastic_ip_addresses.py b/moto/ec2/responses/elastic_ip_addresses.py
index 368517d7d..60cdbcf5e 100644
--- a/moto/ec2/responses/elastic_ip_addresses.py
+++ b/moto/ec2/responses/elastic_ip_addresses.py
@@ -1,21 +1,132 @@
from jinja2 import Template
from moto.ec2.models import ec2_backend
-from moto.ec2.utils import resource_ids_from_querystring
+from moto.ec2.utils import sequence_from_querystring
+
class ElasticIPAddresses(object):
def allocate_address(self):
- raise NotImplementedError('ElasticIPAddresses.allocate_address is not yet implemented')
+ if "Domain" in self.querystring:
+ domain = self.querystring.get('Domain')[0]
+ if domain != "vpc":
+ return "Invalid domain:{0}.".format(domain), dict(status=400)
+ else:
+ domain = "standard"
+ address = ec2_backend.allocate_address(domain)
+ template = Template(ALLOCATE_ADDRESS_RESPONSE)
+ return template.render(address=address)
def associate_address(self):
- raise NotImplementedError('ElasticIPAddresses.associate_address is not yet implemented')
+ 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")
+ else:
+ return "Invalid request, expect InstanceId/NetworkId parameter.", dict(status=400)
+
+ reassociate = False
+ 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)
+ else:
+ return "Invalid request, expect PublicIp/AllocationId parameter.", dict(status=400)
+
+ if eip:
+ template = Template(ASSOCIATE_ADDRESS_RESPONSE)
+ return template.render(address=eip)
+ else:
+ return "Failed to associate address.", dict(status=400)
def describe_addresses(self):
- raise NotImplementedError('ElasticIPAddresses.describe_addresses is not yet implemented')
+ template = Template(DESCRIBE_ADDRESS_RESPONSE)
+
+ if "Filter.1.Name" in self.querystring:
+ raise NotImplementedError("Filtering not supported in describe_address.")
+ elif "PublicIp.1" in self.querystring:
+ public_ips = sequence_from_querystring("PublicIp", self.querystring)
+ addresses = ec2_backend.address_by_ip(public_ips)
+ elif "AllocationId.1" in self.querystring:
+ allocation_ids = sequence_from_querystring("AllocationId", self.querystring)
+ addresses = ec2_backend.address_by_allocation(allocation_ids)
+ else:
+ addresses = ec2_backend.describe_addresses()
+ return template.render(addresses=addresses)
def disassociate_address(self):
- raise NotImplementedError('ElasticIPAddresses.disassociate_address is not yet implemented')
+ if "PublicIp" in self.querystring:
+ disassociated = ec2_backend.disassociate_address(address=self.querystring['PublicIp'][0])
+ elif "AssociationId" in self.querystring:
+ disassociated = ec2_backend.disassociate_address(association_id=self.querystring['AssociationId'][0])
+ else:
+ return "Invalid request, expect PublicIp/AssociationId parameter.", dict(status=400)
+
+ if disassociated:
+ return Template(DISASSOCIATE_ADDRESS_RESPONSE).render()
+ else:
+ return "Address conresponding to PublicIp/AssociationIP not found.", dict(status=400)
def release_address(self):
- raise NotImplementedError('ElasticIPAddresses.release_address is not yet implemented')
+ if "PublicIp" in self.querystring:
+ released = ec2_backend.release_address(address=self.querystring['PublicIp'][0])
+ elif "AllocationId" in self.querystring:
+ released = ec2_backend.release_address(allocation_id=self.querystring['AllocationId'][0])
+ else:
+ return "Invalid request, expect PublicIp/AllocationId parameter.", dict(status=400)
+
+ if released:
+ return Template(RELEASE_ADDRESS_RESPONSE).render()
+ else:
+ return "Address conresponding to PublicIp/AssociationIP not found.", dict(status=400)
+
+
+ALLOCATE_ADDRESS_RESPONSE = """
+ 59dbff89-35bd-4eac-99ed-be587EXAMPLE
+ {{ address.public_ip }}
+ {{ address.domain }}
+ {% if address.allocation_id %}
+ {{ address.allocation_id }}
+ {% endif %}
+"""
+
+ASSOCIATE_ADDRESS_RESPONSE = """
+ 59dbff89-35bd-4eac-99ed-be587EXAMPLE
+ true
+ {% if address.association_id %}
+ {{ address.association_id }}
+ {% endif %}
+"""
+
+DESCRIBE_ADDRESS_RESPONSE = """
+ 59dbff89-35bd-4eac-99ed-be587EXAMPLE
+
+ {% for address in addresses %}
+ -
+ {{ address.public_ip }}
+ {{ address.domain }}
+ {% if address.instance %}
+ {{ address.instance.id }}
+ {% else %}
+
+ {% endif %}
+ {% if address.association_id %}
+ {{ address.association_id }}
+ {% endif %}
+
+ {% endfor %}
+
+"""
+
+DISASSOCIATE_ADDRESS_RESPONSE = """
+ 59dbff89-35bd-4eac-99ed-be587EXAMPLE
+ true
+"""
+
+RELEASE_ADDRESS_RESPONSE = """
+ 59dbff89-35bd-4eac-99ed-be587EXAMPLE
+ true
+"""
diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py
index 68be9dafd..f230dcf49 100644
--- a/moto/ec2/responses/instances.py
+++ b/moto/ec2/responses/instances.py
@@ -95,8 +95,8 @@ EC2_RUN_INSTANCES = """