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 = """