Updates to create_subnet and describe_subnets responses (#2053)

* Removed Tags field from create_subnet response.

* Added DefaultForAz to create_subnet response.

* Added MapPublicIpOnLaunch to create_subnet response.

* Added OwnerId to create_subnet response.

* Added AssignIpv6AddressOnCreation field for create_subnet and describe_subnet and implemented setting it in modify_subnet_attribute.

* Added SubnetArn to create_subnet response.

* Added AvailabilityZoneId to create_subnet and describe_subnet responses, and error for invalid availability zone.

* Added Ipv6CidrBlockAssociationSet to create_subnet response.

* Added missing fields to describe_subnets response.

* Added myself to list of contributors and marked describe_subnet as implemented.

* Fixed linting errors.

* Fixed blank line containing a tab.

* Fixed accidentally deleted ).

* Fixed broken tests.
This commit is contained in:
Bendegúz Ács 2019-05-28 17:33:25 +02:00 committed by Terry Cain
parent 2386d47fe3
commit 8f53b16b9a
7 changed files with 272 additions and 42 deletions

View File

@ -54,5 +54,6 @@ Moto is written by Steve Pulec with contributions from:
* [William Richard](https://github.com/william-richard) * [William Richard](https://github.com/william-richard)
* [Alex Casalboni](https://github.com/alexcasalboni) * [Alex Casalboni](https://github.com/alexcasalboni)
* [Jon Beilke](https://github.com/jrbeilke) * [Jon Beilke](https://github.com/jrbeilke)
* [Bendeguz Acs](https://github.com/acsbendi)
* [Craig Anderson](https://github.com/craiga) * [Craig Anderson](https://github.com/craiga)
* [Robert Lewis](https://github.com/ralewis85) * [Robert Lewis](https://github.com/ralewis85)

View File

@ -1473,7 +1473,7 @@
- [X] describe_spot_instance_requests - [X] describe_spot_instance_requests
- [ ] describe_spot_price_history - [ ] describe_spot_price_history
- [ ] describe_stale_security_groups - [ ] describe_stale_security_groups
- [ ] describe_subnets - [X] describe_subnets
- [X] describe_tags - [X] describe_tags
- [ ] describe_volume_attribute - [ ] describe_volume_attribute
- [ ] describe_volume_status - [ ] describe_volume_status

View File

@ -430,6 +430,16 @@ class OperationNotPermitted(EC2ClientError):
) )
class InvalidAvailabilityZoneError(EC2ClientError):
def __init__(self, availability_zone_value, valid_availability_zones):
super(InvalidAvailabilityZoneError, self).__init__(
"InvalidParameterValue",
"Value ({0}) for parameter availabilityZone is invalid. "
"Subnets can currently only be created in the following availability zones: {1}.".format(availability_zone_value, valid_availability_zones)
)
class NetworkAclEntryAlreadyExistsError(EC2ClientError): class NetworkAclEntryAlreadyExistsError(EC2ClientError):
def __init__(self, rule_number): def __init__(self, rule_number):

View File

@ -36,6 +36,7 @@ from .exceptions import (
InvalidAMIIdError, InvalidAMIIdError,
InvalidAMIAttributeItemValueError, InvalidAMIAttributeItemValueError,
InvalidAssociationIdError, InvalidAssociationIdError,
InvalidAvailabilityZoneError,
InvalidCIDRBlockParameterError, InvalidCIDRBlockParameterError,
InvalidCIDRSubnetError, InvalidCIDRSubnetError,
InvalidCustomerGatewayIdError, InvalidCustomerGatewayIdError,
@ -1288,17 +1289,107 @@ class Region(object):
class Zone(object): class Zone(object):
def __init__(self, name, region_name): def __init__(self, name, region_name, zone_id):
self.name = name self.name = name
self.region_name = region_name self.region_name = region_name
self.zone_id = zone_id
class RegionsAndZonesBackend(object): class RegionsAndZonesBackend(object):
regions = [Region(ri.name, ri.endpoint) for ri in boto.ec2.regions()] regions = [Region(ri.name, ri.endpoint) for ri in boto.ec2.regions()]
zones = dict( zones = {
(region, [Zone(region + c, region) for c in 'abc']) 'ap-south-1': [
for region in [r.name for r in regions]) Zone(region_name="ap-south-1", name="ap-south-1a", zone_id="aps1-az1"),
Zone(region_name="ap-south-1", name="ap-south-1b", zone_id="aps1-az3")
],
'eu-west-3': [
Zone(region_name="eu-west-3", name="eu-west-3a", zone_id="euw3-az1"),
Zone(region_name="eu-west-3", name="eu-west-3b", zone_id="euw3-az2"),
Zone(region_name="eu-west-3", name="eu-west-3c", zone_id="euw3-az3")
],
'eu-north-1': [
Zone(region_name="eu-north-1", name="eu-north-1a", zone_id="eun1-az1"),
Zone(region_name="eu-north-1", name="eu-north-1b", zone_id="eun1-az2"),
Zone(region_name="eu-north-1", name="eu-north-1c", zone_id="eun1-az3")
],
'eu-west-2': [
Zone(region_name="eu-west-2", name="eu-west-2a", zone_id="euw2-az2"),
Zone(region_name="eu-west-2", name="eu-west-2b", zone_id="euw2-az3"),
Zone(region_name="eu-west-2", name="eu-west-2c", zone_id="euw2-az1")
],
'eu-west-1': [
Zone(region_name="eu-west-1", name="eu-west-1a", zone_id="euw1-az3"),
Zone(region_name="eu-west-1", name="eu-west-1b", zone_id="euw1-az1"),
Zone(region_name="eu-west-1", name="eu-west-1c", zone_id="euw1-az2")
],
'ap-northeast-3': [
Zone(region_name="ap-northeast-3", name="ap-northeast-2a", zone_id="apne3-az1")
],
'ap-northeast-2': [
Zone(region_name="ap-northeast-2", name="ap-northeast-2a", zone_id="apne2-az1"),
Zone(region_name="ap-northeast-2", name="ap-northeast-2c", zone_id="apne2-az3")
],
'ap-northeast-1': [
Zone(region_name="ap-northeast-1", name="ap-northeast-1a", zone_id="apne1-az4"),
Zone(region_name="ap-northeast-1", name="ap-northeast-1c", zone_id="apne1-az1"),
Zone(region_name="ap-northeast-1", name="ap-northeast-1d", zone_id="apne1-az2")
],
'sa-east-1': [
Zone(region_name="sa-east-1", name="sa-east-1a", zone_id="sae1-az1"),
Zone(region_name="sa-east-1", name="sa-east-1c", zone_id="sae1-az3")
],
'ca-central-1': [
Zone(region_name="ca-central-1", name="ca-central-1a", zone_id="cac1-az1"),
Zone(region_name="ca-central-1", name="ca-central-1b", zone_id="cac1-az2")
],
'ap-southeast-1': [
Zone(region_name="ap-southeast-1", name="ap-southeast-1a", zone_id="apse1-az1"),
Zone(region_name="ap-southeast-1", name="ap-southeast-1b", zone_id="apse1-az2"),
Zone(region_name="ap-southeast-1", name="ap-southeast-1c", zone_id="apse1-az3")
],
'ap-southeast-2': [
Zone(region_name="ap-southeast-2", name="ap-southeast-2a", zone_id="apse2-az1"),
Zone(region_name="ap-southeast-2", name="ap-southeast-2b", zone_id="apse2-az3"),
Zone(region_name="ap-southeast-2", name="ap-southeast-2c", zone_id="apse2-az2")
],
'eu-central-1': [
Zone(region_name="eu-central-1", name="eu-central-1a", zone_id="euc1-az2"),
Zone(region_name="eu-central-1", name="eu-central-1b", zone_id="euc1-az3"),
Zone(region_name="eu-central-1", name="eu-central-1c", zone_id="euc1-az1")
],
'us-east-1': [
Zone(region_name="us-east-1", name="us-east-1a", zone_id="use1-az6"),
Zone(region_name="us-east-1", name="us-east-1b", zone_id="use1-az1"),
Zone(region_name="us-east-1", name="us-east-1c", zone_id="use1-az2"),
Zone(region_name="us-east-1", name="us-east-1d", zone_id="use1-az4"),
Zone(region_name="us-east-1", name="us-east-1e", zone_id="use1-az3"),
Zone(region_name="us-east-1", name="us-east-1f", zone_id="use1-az5")
],
'us-east-2': [
Zone(region_name="us-east-2", name="us-east-2a", zone_id="use2-az1"),
Zone(region_name="us-east-2", name="us-east-2b", zone_id="use2-az2"),
Zone(region_name="us-east-2", name="us-east-2c", zone_id="use2-az3")
],
'us-west-1': [
Zone(region_name="us-west-1", name="us-west-1a", zone_id="usw1-az3"),
Zone(region_name="us-west-1", name="us-west-1b", zone_id="usw1-az1")
],
'us-west-2': [
Zone(region_name="us-west-2", name="us-west-2a", zone_id="usw2-az2"),
Zone(region_name="us-west-2", name="us-west-2b", zone_id="usw2-az1"),
Zone(region_name="us-west-2", name="us-west-2c", zone_id="usw2-az3")
],
'cn-north-1': [
Zone(region_name="cn-north-1", name="cn-north-1a", zone_id="cnn1-az1"),
Zone(region_name="cn-north-1", name="cn-north-1b", zone_id="cnn1-az2")
],
'us-gov-west-1': [
Zone(region_name="us-gov-west-1", name="us-gov-west-1a", zone_id="usgw1-az1"),
Zone(region_name="us-gov-west-1", name="us-gov-west-1b", zone_id="usgw1-az2"),
Zone(region_name="us-gov-west-1", name="us-gov-west-1c", zone_id="usgw1-az3")
]
}
def describe_regions(self, region_names=[]): def describe_regions(self, region_names=[]):
if len(region_names) == 0: if len(region_names) == 0:
@ -2374,7 +2465,7 @@ class VPCPeeringConnectionBackend(object):
class Subnet(TaggedEC2Resource): class Subnet(TaggedEC2Resource):
def __init__(self, ec2_backend, subnet_id, vpc_id, cidr_block, availability_zone, default_for_az, def __init__(self, ec2_backend, subnet_id, vpc_id, cidr_block, availability_zone, default_for_az,
map_public_ip_on_launch): map_public_ip_on_launch, owner_id=111122223333, assign_ipv6_address_on_creation=False):
self.ec2_backend = ec2_backend self.ec2_backend = ec2_backend
self.id = subnet_id self.id = subnet_id
self.vpc_id = vpc_id self.vpc_id = vpc_id
@ -2383,6 +2474,9 @@ class Subnet(TaggedEC2Resource):
self._availability_zone = availability_zone self._availability_zone = availability_zone
self.default_for_az = default_for_az self.default_for_az = default_for_az
self.map_public_ip_on_launch = map_public_ip_on_launch self.map_public_ip_on_launch = map_public_ip_on_launch
self.owner_id = owner_id
self.assign_ipv6_address_on_creation = assign_ipv6_address_on_creation
self.ipv6_cidr_block_associations = []
# Theory is we assign ip's as we go (as 16,777,214 usable IPs in a /8) # Theory is we assign ip's as we go (as 16,777,214 usable IPs in a /8)
self._subnet_ip_generator = self.cidr.hosts() self._subnet_ip_generator = self.cidr.hosts()
@ -2412,7 +2506,7 @@ class Subnet(TaggedEC2Resource):
@property @property
def availability_zone(self): def availability_zone(self):
return self._availability_zone return self._availability_zone.name
@property @property
def physical_resource_id(self): def physical_resource_id(self):
@ -2509,7 +2603,7 @@ class SubnetBackend(object):
return subnets[subnet_id] return subnets[subnet_id]
raise InvalidSubnetIdError(subnet_id) raise InvalidSubnetIdError(subnet_id)
def create_subnet(self, vpc_id, cidr_block, availability_zone): def create_subnet(self, vpc_id, cidr_block, availability_zone, context=None):
subnet_id = random_subnet_id() subnet_id = random_subnet_id()
vpc = self.get_vpc(vpc_id) # Validate VPC exists and the supplied CIDR block is a subnet of the VPC's vpc = self.get_vpc(vpc_id) # Validate VPC exists and the supplied CIDR block is a subnet of the VPC's
vpc_cidr_block = ipaddress.IPv4Network(six.text_type(vpc.cidr_block), strict=False) vpc_cidr_block = ipaddress.IPv4Network(six.text_type(vpc.cidr_block), strict=False)
@ -2529,8 +2623,15 @@ class SubnetBackend(object):
# consider it the default # consider it the default
default_for_az = str(availability_zone not in self.subnets).lower() default_for_az = str(availability_zone not in self.subnets).lower()
map_public_ip_on_launch = default_for_az map_public_ip_on_launch = default_for_az
subnet = Subnet(self, subnet_id, vpc_id, cidr_block, availability_zone, if availability_zone is None:
default_for_az, map_public_ip_on_launch) availability_zone = 'us-east-1a'
try:
availability_zone_data = next(zone for zones in RegionsAndZonesBackend.zones.values() for zone in zones if zone.name == availability_zone)
except StopIteration:
raise InvalidAvailabilityZoneError(availability_zone, ", ".join([zone.name for zones in RegionsAndZonesBackend.zones.values() for zone in zones]))
subnet = Subnet(self, subnet_id, vpc_id, cidr_block, availability_zone_data,
default_for_az, map_public_ip_on_launch,
owner_id=context.get_current_user() if context else '111122223333', assign_ipv6_address_on_creation=False)
# AWS associates a new subnet with the default Network ACL # AWS associates a new subnet with the default Network ACL
self.associate_default_network_acl_with_subnet(subnet_id, vpc_id) self.associate_default_network_acl_with_subnet(subnet_id, vpc_id)
@ -2558,11 +2659,12 @@ class SubnetBackend(object):
return subnets.pop(subnet_id, None) return subnets.pop(subnet_id, None)
raise InvalidSubnetIdError(subnet_id) raise InvalidSubnetIdError(subnet_id)
def modify_subnet_attribute(self, subnet_id, map_public_ip): def modify_subnet_attribute(self, subnet_id, attr_name, attr_value):
subnet = self.get_subnet(subnet_id) subnet = self.get_subnet(subnet_id)
if map_public_ip not in ('true', 'false'): if attr_name in ('map_public_ip_on_launch', 'assign_ipv6_address_on_creation'):
raise InvalidParameterValueError(map_public_ip) setattr(subnet, attr_name, attr_value)
subnet.map_public_ip_on_launch = map_public_ip else:
raise InvalidParameterValueError(attr_name)
class SubnetRouteTableAssociation(object): class SubnetRouteTableAssociation(object):

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import random import random
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores
from moto.ec2.utils import filters_from_querystring from moto.ec2.utils import filters_from_querystring
@ -16,6 +17,7 @@ class Subnets(BaseResponse):
vpc_id, vpc_id,
cidr_block, cidr_block,
availability_zone, availability_zone,
context=self,
) )
template = self.response_template(CREATE_SUBNET_RESPONSE) template = self.response_template(CREATE_SUBNET_RESPONSE)
return template.render(subnet=subnet) return template.render(subnet=subnet)
@ -35,8 +37,13 @@ class Subnets(BaseResponse):
def modify_subnet_attribute(self): def modify_subnet_attribute(self):
subnet_id = self._get_param('SubnetId') subnet_id = self._get_param('SubnetId')
map_public_ip = self._get_param('MapPublicIpOnLaunch.Value')
self.ec2_backend.modify_subnet_attribute(subnet_id, map_public_ip) for attribute in ('MapPublicIpOnLaunch', 'AssignIpv6AddressOnCreation'):
if self.querystring.get('%s.Value' % attribute):
attr_name = camelcase_to_underscores(attribute)
attr_value = self.querystring.get('%s.Value' % attribute)[0]
self.ec2_backend.modify_subnet_attribute(
subnet_id, attr_name, attr_value)
return MODIFY_SUBNET_ATTRIBUTE_RESPONSE return MODIFY_SUBNET_ATTRIBUTE_RESPONSE
@ -49,17 +56,14 @@ CREATE_SUBNET_RESPONSE = """
<vpcId>{{ subnet.vpc_id }}</vpcId> <vpcId>{{ subnet.vpc_id }}</vpcId>
<cidrBlock>{{ subnet.cidr_block }}</cidrBlock> <cidrBlock>{{ subnet.cidr_block }}</cidrBlock>
<availableIpAddressCount>251</availableIpAddressCount> <availableIpAddressCount>251</availableIpAddressCount>
<availabilityZone>{{ subnet.availability_zone }}</availabilityZone> <availabilityZone>{{ subnet._availability_zone.name }}</availabilityZone>
<tagSet> <availabilityZoneId>{{ subnet._availability_zone.zone_id }}</availabilityZoneId>
{% for tag in subnet.get_tags() %} <defaultForAz>{{ subnet.default_for_az }}</defaultForAz>
<item> <mapPublicIpOnLaunch>{{ subnet.map_public_ip_on_launch }}</mapPublicIpOnLaunch>
<resourceId>{{ tag.resource_id }}</resourceId> <ownerId>{{ subnet.owner_id }}</ownerId>
<resourceType>{{ tag.resource_type }}</resourceType> <assignIpv6AddressOnCreation>{{ subnet.assign_ipv6_address_on_creation }}</assignIpv6AddressOnCreation>
<key>{{ tag.key }}</key> <ipv6CidrBlockAssociationSet>{{ subnet.ipv6_cidr_block_associations }}</ipv6CidrBlockAssociationSet>
<value>{{ tag.value }}</value> <subnetArn>arn:aws:ec2:{{ subnet._availability_zone.name[0:-1] }}:{{ subnet.owner_id }}:subnet/{{ subnet.id }}</subnetArn>
</item>
{% endfor %}
</tagSet>
</subnet> </subnet>
</CreateSubnetResponse>""" </CreateSubnetResponse>"""
@ -80,9 +84,15 @@ DESCRIBE_SUBNETS_RESPONSE = """
<vpcId>{{ subnet.vpc_id }}</vpcId> <vpcId>{{ subnet.vpc_id }}</vpcId>
<cidrBlock>{{ subnet.cidr_block }}</cidrBlock> <cidrBlock>{{ subnet.cidr_block }}</cidrBlock>
<availableIpAddressCount>251</availableIpAddressCount> <availableIpAddressCount>251</availableIpAddressCount>
<availabilityZone>{{ subnet.availability_zone }}</availabilityZone> <availabilityZone>{{ subnet._availability_zone.name }}</availabilityZone>
<availabilityZoneId>{{ subnet._availability_zone.zone_id }}</availabilityZoneId>
<defaultForAz>{{ subnet.default_for_az }}</defaultForAz> <defaultForAz>{{ subnet.default_for_az }}</defaultForAz>
<mapPublicIpOnLaunch>{{ subnet.map_public_ip_on_launch }}</mapPublicIpOnLaunch> <mapPublicIpOnLaunch>{{ subnet.map_public_ip_on_launch }}</mapPublicIpOnLaunch>
<ownerId>{{ subnet.owner_id }}</ownerId>
<assignIpv6AddressOnCreation>{{ subnet.assign_ipv6_address_on_creation }}</assignIpv6AddressOnCreation>
<ipv6CidrBlockAssociationSet>{{ subnet.ipv6_cidr_block_associations }}</ipv6CidrBlockAssociationSet>
<subnetArn>arn:aws:ec2:{{ subnet._availability_zone.name[0:-1] }}:{{ subnet.owner_id }}:subnet/{{ subnet.id }}</subnetArn>
{% if subnet.get_tags() %}
<tagSet> <tagSet>
{% for tag in subnet.get_tags() %} {% for tag in subnet.get_tags() %}
<item> <item>
@ -93,6 +103,7 @@ DESCRIBE_SUBNETS_RESPONSE = """
</item> </item>
{% endfor %} {% endfor %}
</tagSet> </tagSet>
{% endif %}
</item> </item>
{% endfor %} {% endfor %}
</subnetSet> </subnetSet>

View File

@ -30,12 +30,12 @@ def test_new_subnet_associates_with_default_network_acl():
conn = boto.connect_vpc('the_key', 'the secret') conn = boto.connect_vpc('the_key', 'the secret')
vpc = conn.get_all_vpcs()[0] vpc = conn.get_all_vpcs()[0]
subnet = conn.create_subnet(vpc.id, "172.31.48.0/20") subnet = conn.create_subnet(vpc.id, "172.31.112.0/20")
all_network_acls = conn.get_all_network_acls() all_network_acls = conn.get_all_network_acls()
all_network_acls.should.have.length_of(1) all_network_acls.should.have.length_of(1)
acl = all_network_acls[0] acl = all_network_acls[0]
acl.associations.should.have.length_of(4) acl.associations.should.have.length_of(7)
[a.subnet_id for a in acl.associations].should.contain(subnet.id) [a.subnet_id for a in acl.associations].should.contain(subnet.id)

View File

@ -118,7 +118,7 @@ def test_boto3_non_default_subnet():
@mock_ec2 @mock_ec2
def test_modify_subnet_attribute(): def test_modify_subnet_attribute_public_ip_on_launch():
ec2 = boto3.resource('ec2', region_name='us-west-1') ec2 = boto3.resource('ec2', region_name='us-west-1')
client = boto3.client('ec2', region_name='us-west-1') client = boto3.client('ec2', region_name='us-west-1')
@ -145,6 +145,34 @@ def test_modify_subnet_attribute():
subnet.map_public_ip_on_launch.should.be.ok subnet.map_public_ip_on_launch.should.be.ok
@mock_ec2
def test_modify_subnet_attribute_assign_ipv6_address_on_creation():
ec2 = boto3.resource('ec2', region_name='us-west-1')
client = boto3.client('ec2', region_name='us-west-1')
# Get the default VPC
vpc = list(ec2.vpcs.all())[0]
subnet = ec2.create_subnet(
VpcId=vpc.id, CidrBlock='172.31.112.0/20', AvailabilityZone='us-west-1a')
# 'map_public_ip_on_launch' is set when calling 'DescribeSubnets' action
subnet.reload()
# For non default subnet, attribute value should be 'False'
subnet.assign_ipv6_address_on_creation.shouldnt.be.ok
client.modify_subnet_attribute(
SubnetId=subnet.id, AssignIpv6AddressOnCreation={'Value': False})
subnet.reload()
subnet.assign_ipv6_address_on_creation.shouldnt.be.ok
client.modify_subnet_attribute(
SubnetId=subnet.id, AssignIpv6AddressOnCreation={'Value': True})
subnet.reload()
subnet.assign_ipv6_address_on_creation.should.be.ok
@mock_ec2 @mock_ec2
def test_modify_subnet_attribute_validation(): def test_modify_subnet_attribute_validation():
ec2 = boto3.resource('ec2', region_name='us-west-1') ec2 = boto3.resource('ec2', region_name='us-west-1')
@ -291,6 +319,84 @@ def test_subnet_tags_through_cloudformation():
subnet.tags["blah"].should.equal("baz") subnet.tags["blah"].should.equal("baz")
@mock_ec2
def test_create_subnet_response_fields():
ec2 = boto3.resource('ec2', region_name='us-west-1')
client = boto3.client('ec2', region_name='us-west-1')
vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16')
subnet = client.create_subnet(
VpcId=vpc.id, CidrBlock='10.0.0.0/24', AvailabilityZone='us-west-1a')['Subnet']
subnet.should.have.key('AvailabilityZone')
subnet.should.have.key('AvailabilityZoneId')
subnet.should.have.key('AvailableIpAddressCount')
subnet.should.have.key('CidrBlock')
subnet.should.have.key('State')
subnet.should.have.key('SubnetId')
subnet.should.have.key('VpcId')
subnet.shouldnt.have.key('Tags')
subnet.should.have.key('DefaultForAz').which.should.equal(False)
subnet.should.have.key('MapPublicIpOnLaunch').which.should.equal(False)
subnet.should.have.key('OwnerId')
subnet.should.have.key('AssignIpv6AddressOnCreation').which.should.equal(False)
subnet_arn = "arn:aws:ec2:{region}:{owner_id}:subnet/{subnet_id}".format(region=subnet['AvailabilityZone'][0:-1],
owner_id=subnet['OwnerId'],
subnet_id=subnet['SubnetId'])
subnet.should.have.key('SubnetArn').which.should.equal(subnet_arn)
subnet.should.have.key('Ipv6CidrBlockAssociationSet').which.should.equal([])
@mock_ec2
def test_describe_subnet_response_fields():
ec2 = boto3.resource('ec2', region_name='us-west-1')
client = boto3.client('ec2', region_name='us-west-1')
vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16')
subnet_object = ec2.create_subnet(
VpcId=vpc.id, CidrBlock='10.0.0.0/24', AvailabilityZone='us-west-1a')
subnets = client.describe_subnets(SubnetIds=[subnet_object.id])['Subnets']
subnets.should.have.length_of(1)
subnet = subnets[0]
subnet.should.have.key('AvailabilityZone')
subnet.should.have.key('AvailabilityZoneId')
subnet.should.have.key('AvailableIpAddressCount')
subnet.should.have.key('CidrBlock')
subnet.should.have.key('State')
subnet.should.have.key('SubnetId')
subnet.should.have.key('VpcId')
subnet.shouldnt.have.key('Tags')
subnet.should.have.key('DefaultForAz').which.should.equal(False)
subnet.should.have.key('MapPublicIpOnLaunch').which.should.equal(False)
subnet.should.have.key('OwnerId')
subnet.should.have.key('AssignIpv6AddressOnCreation').which.should.equal(False)
subnet_arn = "arn:aws:ec2:{region}:{owner_id}:subnet/{subnet_id}".format(region=subnet['AvailabilityZone'][0:-1],
owner_id=subnet['OwnerId'],
subnet_id=subnet['SubnetId'])
subnet.should.have.key('SubnetArn').which.should.equal(subnet_arn)
subnet.should.have.key('Ipv6CidrBlockAssociationSet').which.should.equal([])
@mock_ec2
def test_create_subnet_with_invalid_availability_zone():
ec2 = boto3.resource('ec2', region_name='us-west-1')
client = boto3.client('ec2', region_name='us-west-1')
vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16')
subnet_availability_zone = 'asfasfas'
with assert_raises(ClientError) as ex:
subnet = client.create_subnet(
VpcId=vpc.id, CidrBlock='10.0.0.0/24', AvailabilityZone=subnet_availability_zone)
assert str(ex.exception).startswith(
"An error occurred (InvalidParameterValue) when calling the CreateSubnet "
"operation: Value ({}) for parameter availabilityZone is invalid. Subnets can currently only be created in the following availability zones: ".format(subnet_availability_zone))
@mock_ec2 @mock_ec2
def test_create_subnet_with_invalid_cidr_range(): def test_create_subnet_with_invalid_cidr_range():
ec2 = boto3.resource('ec2', region_name='us-west-1') ec2 = boto3.resource('ec2', region_name='us-west-1')