From bcb0e7a3d49a7fdd39d0b19dfa084aa6f98ba180 Mon Sep 17 00:00:00 2001 From: Ilya Sukhanov Date: Thu, 5 Jun 2014 00:12:22 -0400 Subject: [PATCH] Implement internet gateway mocking --- moto/ec2/exceptions.py | 31 ++++++- moto/ec2/models.py | 78 ++++++++++++---- moto/ec2/responses/dhcp_options.py | 2 - moto/ec2/responses/internet_gateways.py | 106 ++++++++++++++++++++-- moto/ec2/utils.py | 4 +- tests/test_ec2/test_internet_gateways.py | 110 ++++++++++++++++++++++- 6 files changed, 298 insertions(+), 33 deletions(-) diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 44612b824..682253897 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -1,6 +1,7 @@ from werkzeug.exceptions import BadRequest from jinja2 import Template + class InvalidIdError(RuntimeError): def __init__(self, id_value): super(InvalidIdError, self).__init__() @@ -38,12 +39,34 @@ class InvalidVPCIdError(EC2ClientError): class InvalidParameterValueError(EC2ClientError): def __init__(self, parameter_value): - super(InvalidParameterValueError, self).__init__( - "InvalidParameterValue", - "Value ({0}) for parameter value is invalid. Invalid DHCP option value.".format( - parameter_value)) + super(InvalidParameterValueError, self).__init__( + "InvalidParameterValue", + "Value {0} is invalid for parameter." + .format(parameter_value)) +class InvalidInternetGatewayIDError(EC2ClientError): + def __init__(self, internet_gateway_id): + super(InvalidInternetGatewayIDError, self).__init__( + "InvalidInternetGatewayID.NotFound", + "InternetGatewayID {0} does not exist." + .format(internet_gateway_id)) + + +class GatewayNotAttachedError(EC2ClientError): + def __init__(self, internet_gateway_id, vpc_id): + super(GatewayNotAttachedError, self).__init__( + "Gateway.NotAttached", + "InternetGatewayID {0} is not attached to a VPC {1}." + .format(internet_gateway_id, vpc_id)) + + +class ResourceAlreadyAssociatedError(EC2ClientError): + def __init__(self, resource): + super(ResourceAlreadyAssociatedError, self).__init__( + "Resource.AlreadyAssociated", + "Resource {0} is already associated." + .format(str(resource))) ERROR_RESPONSE = u""" diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 440c1c5a2..c25f49e28 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -8,15 +8,20 @@ from moto.core import BaseBackend from .exceptions import ( InvalidIdError, DependencyViolationError, - InvalidDHCPOptionsIdError + InvalidDHCPOptionsIdError, + InvalidInternetGatewayIDError, + GatewayNotAttachedError, + ResourceAlreadyAssociatedError, + InvalidVPCIdError ) from .utils import ( random_ami_id, random_dhcp_option_id, random_eip_allocation_id, random_eip_association_id, - random_gateway_id, + random_internet_gateway_id, random_instance_id, + random_internet_gateway_id, random_ip, random_key_pair, random_reservation_id, @@ -368,11 +373,11 @@ class SecurityRule(object): @property def unique_representation(self): return "{0}-{1}-{2}-{3}-{4}".format( - self.ip_protocol, - self.from_port, - self.to_port, - self.ip_ranges, - self.source_groups + self.ip_protocol, + self.from_port, + self.to_port, + self.ip_ranges, + self.source_groups ) def __eq__(self, other): @@ -691,6 +696,8 @@ class VPCBackend(object): return vpc def get_vpc(self, vpc_id): + if vpc_id not in self.vpcs: + raise InvalidVPCIdError(vpc_id) return self.vpcs.get(vpc_id) def get_all_vpcs(self): @@ -838,13 +845,14 @@ class RouteBackend(object): return route -class InternetGateway(object): - def __init__(self, gateway_id): - self.id = gateway_id +class InternetGateway(TaggedEC2Instance): + def __init__(self): + self.id = random_internet_gateway_id() + self.vpc = None @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): - return ec2_backend.create_gateway() + return ec2_backend.create_internet_gateway() @property def physical_resource_id(self): @@ -853,14 +861,50 @@ class InternetGateway(object): class InternetGatewayBackend(object): def __init__(self): - self.gateways = {} + self.internet_gateways = {} super(InternetGatewayBackend, self).__init__() - def create_gateway(self): - gateway_id = random_gateway_id() - gateway = InternetGateway(gateway_id) - self.gateways[gateway_id] = gateway - return gateway + def create_internet_gateway(self): + igw = InternetGateway() + self.internet_gateways[igw.id] = igw + return igw + + def describe_internet_gateways(self, internet_gateway_ids=None): + igws = [] + for igw_id in internet_gateway_ids or []: + if igw_id in self.internet_gateways: + igws.append(self.internet_gateways[igw_id]) + else: + raise InvalidInternetGatewayIDError(igw_id) + return igws or self.internet_gateways.values() + + def delete_internet_gateway(self, internet_gateway_id): + igw_ids = [internet_gateway_id] + igw = self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] + if igw.vpc: + raise DependencyViolationError( + "{0} is being utilized by {1}" + .format(internet_gateway_id, igw.vpc) + ) + self.internet_gateways.pop(internet_gateway_id) + return True + + def detach_internet_gateway(self, internet_gateway_id, vpc_id): + igw_ids = [internet_gateway_id] + igw = self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] + if not igw.vpc or igw.vpc.id != vpc_id: + raise GatewayNotAttachedError(internet_gateway_id, vpc_id) + igw.vpc = None + return True + + def attach_internet_gateway(self, internet_gateway_id, vpc_id): + igw_ids = [internet_gateway_id] + igw = self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] + if igw.vpc: + raise ResourceAlreadyAssociatedError(igw) + vpc = self.get_vpc(vpc_id) + igw.vpc = vpc + return True class VPCGatewayAttachment(object): diff --git a/moto/ec2/responses/dhcp_options.py b/moto/ec2/responses/dhcp_options.py index 482024667..6ac71d863 100644 --- a/moto/ec2/responses/dhcp_options.py +++ b/moto/ec2/responses/dhcp_options.py @@ -20,8 +20,6 @@ class DHCPOptions(BaseResponse): dhcp_opt = ec2_backend.describe_dhcp_options([dhcp_opt_id])[0] vpc = ec2_backend.get_vpc(vpc_id) - if not vpc: - raise InvalidVPCIdError(vpc_id) ec2_backend.associate_dhcp_options(dhcp_opt, vpc) diff --git a/moto/ec2/responses/internet_gateways.py b/moto/ec2/responses/internet_gateways.py index c9b24922f..9309baaeb 100644 --- a/moto/ec2/responses/internet_gateways.py +++ b/moto/ec2/responses/internet_gateways.py @@ -1,18 +1,112 @@ from moto.core.responses import BaseResponse - +from moto.ec2.models import ec2_backend +from moto.ec2.utils import sequence_from_querystring +from jinja2 import Template class InternetGateways(BaseResponse): def attach_internet_gateway(self): - raise NotImplementedError('InternetGateways(AmazonVPC).attach_internet_gateway is not yet implemented') + igw_id = self.querystring.get("InternetGatewayId", [None])[0] + vpc_id = self.querystring.get("VpcId", [None])[0] + ec2_backend.attach_internet_gateway(igw_id, vpc_id) + template = Template(ATTACH_INTERNET_GATEWAY_RESPONSE) + return template.render() def create_internet_gateway(self): - raise NotImplementedError('InternetGateways(AmazonVPC).create_internet_gateway is not yet implemented') + igw = ec2_backend.create_internet_gateway() + template = Template(CREATE_INTERNET_GATEWAY_RESPONSE) + return template.render(internet_gateway=igw) def delete_internet_gateway(self): - raise NotImplementedError('InternetGateways(AmazonVPC).delete_internet_gateway is not yet implemented') + igw_id = self.querystring.get("InternetGatewayId", [None])[0] + ec2_backend.delete_internet_gateway(igw_id) + template = Template(DELETE_INTERNET_GATEWAY_RESPONSE) + return template.render() def describe_internet_gateways(self): - raise NotImplementedError('InternetGateways(AmazonVPC).describe_internet_gateways is not yet implemented') + if "Filter.1.Name" in self.querystring: + raise NotImplementedError( + "Filtering not supported in describe_internet_gateways.") + elif "InternetGatewayId.1" in self.querystring: + igw_ids = sequence_from_querystring( + "InternetGatewayId", self.querystring) + igws = ec2_backend.describe_internet_gateways(igw_ids) + else: + igws = ec2_backend.describe_internet_gateways() + template = Template(DESCRIBE_INTERNET_GATEWAYS_RESPONSE) + return template.render(internet_gateways=igws) def detach_internet_gateway(self): - raise NotImplementedError('InternetGateways(AmazonVPC).detach_internet_gateway is not yet implemented') + # TODO validate no instances with EIPs in VPC before detaching + # raise else DependencyViolationError() + igw_id = self.querystring.get("InternetGatewayId", [None])[0] + vpc_id = self.querystring.get("VpcId", [None])[0] + ec2_backend.detach_internet_gateway(igw_id, vpc_id) + template = Template(DETACH_INTERNET_GATEWAY_RESPONSE) + return template.render() + + +ATTACH_INTERNET_GATEWAY_RESPONSE = u""" + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true +""" + +CREATE_INTERNET_GATEWAY_RESPONSE = u""" + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + + {{ internet_gateway.id }} + + + {% for tag in internet_gateway.get_tags() %} + + {{ tag.resource_id }} + {{ tag.resource_type }} + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + +""" + +DELETE_INTERNET_GATEWAY_RESPONSE = u""" + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true +""" + +DESCRIBE_INTERNET_GATEWAYS_RESPONSE = u""" + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + + {% for igw in internet_gateways %} + + {{ igw.id }} + {% if igw.vpc %} + + + {{ igw.vpc.id }} + available + + + {% else %} + + {% endif %} + + {% for tag in igw.get_tags() %} + + {{ tag.resource_id }} + {{ tag.resource_type }} + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + + {% endfor %} + +""" + +DETACH_INTERNET_GATEWAY_RESPONSE = u""" + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true +""" + diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index b41d172f5..e6291d10b 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -50,7 +50,7 @@ def random_eip_association_id(): return random_id(prefix='eipassoc') -def random_gateway_id(): +def random_internet_gateway_id(): return random_id(prefix='igw') @@ -157,7 +157,7 @@ def dhcp_configuration_from_querystring(querystring, option=u'DhcpConfiguration' def filters_from_querystring(querystring_dict): response_values = {} for key, value in querystring_dict.iteritems(): - match = re.search("Filter.(\d).Name", key) + match = re.search(r"Filter.(\d).Name", key) if match: filter_index = match.groups()[0] value_prefix = "Filter.{0}.Value".format(filter_index) diff --git a/tests/test_ec2/test_internet_gateways.py b/tests/test_ec2/test_internet_gateways.py index 67f0067ea..e3eca6bca 100644 --- a/tests/test_ec2/test_internet_gateways.py +++ b/tests/test_ec2/test_internet_gateways.py @@ -1,9 +1,115 @@ +import re + import boto +from boto.exception import EC2ResponseError + import sure # noqa from moto import mock_ec2 +VPC_CIDR="10.0.0.0/16" +BAD_VPC="vpc-deadbeef" +BAD_IGW="igw-deadbeef" + @mock_ec2 -def test_internet_gateways(): - pass +def test_igw_create(): + """ internet gateway create """ + conn = boto.connect_vpc('the_key', 'the_secret') + + conn.get_all_internet_gateways().should.have.length_of(0) + igw = conn.create_internet_gateway() + conn.get_all_internet_gateways().should.have.length_of(1) + igw.id.should.match(r'igw-[0-9a-f]+') + + igw = conn.get_all_internet_gateways()[0] + igw.attachments.should.have.length_of(0) + +@mock_ec2 +def test_igw_attach(): + """ internet gateway attach """ + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + vpc = conn.create_vpc(VPC_CIDR) + conn.attach_internet_gateway(igw.id, vpc.id) + + igw = conn.get_all_internet_gateways()[0] + igw.attachments[0].vpc_id.should.be.equal(vpc.id) + +@mock_ec2 +def test_igw_attach_bad_vpc(): + """ internet gateway fail to attach w/ bad vpc """ + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + conn.attach_internet_gateway.when.called_with(igw.id, BAD_VPC).should.throw(EC2ResponseError) + +@mock_ec2 +def test_igw_attach_twice(): + """ internet gateway fail to attach twice """ + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + vpc1 = conn.create_vpc(VPC_CIDR) + vpc2 = conn.create_vpc(VPC_CIDR) + conn.attach_internet_gateway(igw.id, vpc1.id) + conn.attach_internet_gateway.when.called_with(igw.id, vpc2.id).should.throw(EC2ResponseError) + +@mock_ec2 +def test_igw_detach(): + """ internet gateway detach""" + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + vpc = conn.create_vpc(VPC_CIDR) + conn.attach_internet_gateway(igw.id, vpc.id) + conn.detach_internet_gateway(igw.id, vpc.id) + igw = conn.get_all_internet_gateways()[0] + igw.attachments.should.have.length_of(0) + +@mock_ec2 +def test_igw_detach_bad_vpc(): + """ internet gateway fail to detach w/ bad vpc """ + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + vpc = conn.create_vpc(VPC_CIDR) + conn.attach_internet_gateway(igw.id, vpc.id) + conn.detach_internet_gateway.when.called_with(igw.id, BAD_VPC).should.throw(EC2ResponseError) + +@mock_ec2 +def test_igw_detach_unattached(): + """ internet gateway fail to detach unattached """ + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + conn.detach_internet_gateway.when.called_with(igw.id, BAD_VPC).should.throw(EC2ResponseError) + +@mock_ec2 +def test_igw_delete(): + """ internet gateway delete""" + conn = boto.connect_vpc('the_key', 'the_secret') + vpc = conn.create_vpc(VPC_CIDR) + conn.get_all_internet_gateways().should.have.length_of(0) + igw = conn.create_internet_gateway() + conn.get_all_internet_gateways().should.have.length_of(1) + conn.delete_internet_gateway(igw.id) + conn.get_all_internet_gateways().should.have.length_of(0) + +@mock_ec2 +def test_igw_delete_attached(): + """ internet gateway fail to delete attached """ + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + vpc = conn.create_vpc(VPC_CIDR) + conn.attach_internet_gateway(igw.id, vpc.id) + conn.delete_internet_gateway.when.called_with(igw.id).should.throw(EC2ResponseError) + +@mock_ec2 +def test_igw_desribe(): + """ internet gateway fetch by id """ + conn = boto.connect_vpc('the_key', 'the_secret') + igw = conn.create_internet_gateway() + igw_by_search = conn.get_all_internet_gateways([igw.id])[0] + igw.id.should.equal(igw_by_search.id) + +@mock_ec2 +def test_igw_desribe_bad_id(): + """ internet gateway fail to fetch by bad id """ + conn = boto.connect_vpc('the_key', 'the_secret') + conn.get_all_internet_gateways.when.called_with([BAD_IGW]).should.throw(EC2ResponseError)