From 56cb06041cd0114491f3323421c26de21ce0a058 Mon Sep 17 00:00:00 2001 From: Hugo Lopes Tavares Date: Wed, 8 Apr 2015 15:05:20 -0400 Subject: [PATCH 1/3] Add filter "availabilityZone" to DescribeSubnets and add availability zone support too --- moto/ec2/models.py | 34 ++++++++++++++++++++++++++++------ moto/ec2/responses/subnets.py | 10 +++++++++- tests/test_ec2/test_subnets.py | 14 ++++++++++---- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 25f32bc76..3549c29fe 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1695,11 +1695,12 @@ class VPCPeeringConnectionBackend(object): class Subnet(TaggedEC2Resource): - def __init__(self, ec2_backend, subnet_id, vpc_id, cidr_block): + def __init__(self, ec2_backend, subnet_id, vpc_id, cidr_block, availability_zone): self.ec2_backend = ec2_backend self.id = subnet_id self.vpc_id = vpc_id self.cidr_block = cidr_block + self._availability_zone = availability_zone @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): @@ -1715,21 +1716,42 @@ class Subnet(TaggedEC2Resource): @property def availability_zone(self): - # This could probably be smarter, but there doesn't appear to be a - # way to pull AZs for a region in boto - return self.ec2_backend.region_name + "a" + if self._availability_zone is None: + # This could probably be smarter, but there doesn't appear to be a + # way to pull AZs for a region in boto + return self.ec2_backend.region_name + "a" + else: + return self._availability_zone @property def physical_resource_id(self): return self.id def get_filter_value(self, filter_name): + """ + API Version 2014-10-01 defines the following filters for DescribeSubnets: + + * availabilityZone + * available-ip-address-count + * cidrBlock + * defaultForAz + * state + * subnet-id + * tag:key=value + * tag-key + * tag-value + * vpc-id + + Taken from: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html + """ if filter_name in ['cidr', 'cidrBlock', 'cidr-block']: return self.cidr_block elif filter_name == 'vpc-id': return self.vpc_id elif filter_name == 'subnet-id': return self.id + elif filter_name == 'availabilityZone': + return self.availability_zone filter_value = super(Subnet, self).get_filter_value(filter_name) @@ -1756,9 +1778,9 @@ class SubnetBackend(object): raise InvalidSubnetIdError(subnet_id) return subnet - def create_subnet(self, vpc_id, cidr_block): + def create_subnet(self, vpc_id, cidr_block, availability_zone=None): subnet_id = random_subnet_id() - subnet = Subnet(self, subnet_id, vpc_id, cidr_block) + subnet = Subnet(self, subnet_id, vpc_id, cidr_block, availability_zone) self.get_vpc(vpc_id) # Validate VPC exists # AWS associates a new subnet with the default Network ACL diff --git a/moto/ec2/responses/subnets.py b/moto/ec2/responses/subnets.py index 9f0808648..f0cd6db33 100644 --- a/moto/ec2/responses/subnets.py +++ b/moto/ec2/responses/subnets.py @@ -7,7 +7,15 @@ class Subnets(BaseResponse): def create_subnet(self): vpc_id = self.querystring.get('VpcId')[0] cidr_block = self.querystring.get('CidrBlock')[0] - subnet = self.ec2_backend.create_subnet(vpc_id, cidr_block) + if 'AvailabilityZone' in self.querystring: + availability_zone = self.querystring['AvailabilityZone'][0] + else: + availability_zone = None + subnet = self.ec2_backend.create_subnet( + vpc_id, + cidr_block, + availability_zone, + ) template = self.response_template(CREATE_SUBNET_RESPONSE) return template.render(subnet=subnet) diff --git a/tests/test_ec2/test_subnets.py b/tests/test_ec2/test_subnets.py index c26267405..e148eac61 100644 --- a/tests/test_ec2/test_subnets.py +++ b/tests/test_ec2/test_subnets.py @@ -4,6 +4,7 @@ import tests.backport_assert_raises from nose.tools import assert_raises import boto +import boto.vpc from boto.exception import EC2ResponseError import sure # noqa @@ -62,12 +63,12 @@ def test_subnet_tagging(): @mock_ec2 def test_get_subnets_filtering(): - conn = boto.connect_vpc('the_key', 'the_secret') + conn = boto.vpc.connect_to_region('us-west-1') vpcA = conn.create_vpc("10.0.0.0/16") - subnetA = conn.create_subnet(vpcA.id, "10.0.0.0/24") + subnetA = conn.create_subnet(vpcA.id, "10.0.0.0/24", availability_zone='us-west-1a') vpcB = conn.create_vpc("10.0.0.0/16") - subnetB1 = conn.create_subnet(vpcB.id, "10.0.0.0/24") - subnetB2 = conn.create_subnet(vpcB.id, "10.0.1.0/24") + subnetB1 = conn.create_subnet(vpcB.id, "10.0.0.0/24", availability_zone='us-west-1a') + subnetB2 = conn.create_subnet(vpcB.id, "10.0.1.0/24", availability_zone='us-west-1b') all_subnets = conn.get_all_subnets() all_subnets.should.have.length_of(3) @@ -100,5 +101,10 @@ def test_get_subnets_filtering(): subnets_by_id.should.have.length_of(1) set([subnet.id for subnet in subnets_by_id]).should.equal(set([subnetA.id])) + # Filter by availabilityZone + subnets_by_az = conn.get_all_subnets(filters={'availabilityZone': 'us-west-1a', 'vpc-id': vpcB.id}) + subnets_by_az.should.have.length_of(1) + set([subnet.id for subnet in subnets_by_az]).should.equal(set([subnetB1.id])) + # Unsupported filter conn.get_all_subnets.when.called_with(filters={'not-implemented-filter': 'foobar'}).should.throw(NotImplementedError) From 65d51a58441140949c107f72c883cffb66964489 Mon Sep 17 00:00:00 2001 From: Hugo Lopes Tavares Date: Wed, 8 Apr 2015 16:47:43 -0400 Subject: [PATCH 2/3] Make availability zone dynamic in Subnet Response templates --- moto/ec2/responses/subnets.py | 4 ++-- tests/test_ec2/test_subnets.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/moto/ec2/responses/subnets.py b/moto/ec2/responses/subnets.py index f0cd6db33..a0798a615 100644 --- a/moto/ec2/responses/subnets.py +++ b/moto/ec2/responses/subnets.py @@ -41,7 +41,7 @@ CREATE_SUBNET_RESPONSE = """ {{ subnet.vpc_id }} {{ subnet.cidr_block }} 251 - us-east-1a + {{ subnet.availability_zone }} {% for tag in subnet.get_tags() %} @@ -72,7 +72,7 @@ DESCRIBE_SUBNETS_RESPONSE = """ {{ subnet.vpc_id }} {{ subnet.cidr_block }} 251 - us-east-1a + {{ subnet.availability_zone }} {% for tag in subnet.get_tags() %} diff --git a/tests/test_ec2/test_subnets.py b/tests/test_ec2/test_subnets.py index e148eac61..6205a50f2 100644 --- a/tests/test_ec2/test_subnets.py +++ b/tests/test_ec2/test_subnets.py @@ -61,6 +61,14 @@ def test_subnet_tagging(): subnet.tags["a key"].should.equal("some value") +@mock_ec2 +def test_subnet_should_have_proper_availability_zone_set(): + conn = boto.vpc.connect_to_region('us-west-1') + vpcA = conn.create_vpc("10.0.0.0/16") + subnetA = conn.create_subnet(vpcA.id, "10.0.0.0/24", availability_zone='us-west-1b') + subnetA.availability_zone.should.equal('us-west-1b') + + @mock_ec2 def test_get_subnets_filtering(): conn = boto.vpc.connect_to_region('us-west-1') From 5160fac9b77a92ffddee3ef1ddb97466c39343f6 Mon Sep 17 00:00:00 2001 From: Hugo Lopes Tavares Date: Wed, 8 Apr 2015 16:48:02 -0400 Subject: [PATCH 3/3] Add availability zone support to Subnets created via CloudFormation --- moto/ec2/models.py | 5 +++- .../test_cloudformation_stack_integration.py | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 3549c29fe..cdb891487 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1707,10 +1707,13 @@ class Subnet(TaggedEC2Resource): properties = cloudformation_json['Properties'] vpc_id = properties['VpcId'] + cidr_block = properties['CidrBlock'] + availability_zone = properties.get('AvailabilityZone') ec2_backend = ec2_backends[region_name] subnet = ec2_backend.create_subnet( vpc_id=vpc_id, - cidr_block=properties['CidrBlock'] + cidr_block=cidr_block, + availability_zone=availability_zone, ) return subnet diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 170a5226c..7db6c22e4 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -1189,3 +1189,32 @@ def test_security_group_ingress_separate_from_security_group_by_id_using_vpc(): security_group1.rules[0].ip_protocol.should.equal('tcp') security_group1.rules[0].from_port.should.equal('80') security_group1.rules[0].to_port.should.equal('8080') + + +@mock_cloudformation +@mock_ec2 +def test_subnets_should_be_created_with_availability_zone(): + vpc_conn = boto.vpc.connect_to_region('us-west-1') + vpc = vpc_conn.create_vpc("10.0.0.0/16") + + subnet_template = { + "AWSTemplateFormatVersion" : "2010-09-09", + "Resources" : { + "testSubnet" : { + "Type" : "AWS::EC2::Subnet", + "Properties" : { + "VpcId" : vpc.id, + "CidrBlock" : "10.0.0.0/24", + "AvailabilityZone" : "us-west-1b", + } + } + } + } + cf_conn = boto.cloudformation.connect_to_region("us-west-1") + template_json = json.dumps(subnet_template) + cf_conn.create_stack( + "test_stack", + template_body=template_json, + ) + subnet = vpc_conn.get_all_subnets(filters={'cidrBlock': '10.0.0.0/24'})[0] + subnet.availability_zone.should.equal('us-west-1b')