Implement NAT Gateway resource

This commit is contained in:
Hugo Lopes Tavares 2016-02-29 18:22:15 -05:00
parent 9b0be24b28
commit 21fb961c6a
5 changed files with 240 additions and 3 deletions

View File

@ -15,6 +15,7 @@ from boto.ec2.launchspecification import LaunchSpecification
from moto.core import BaseBackend
from moto.core.models import Model
from moto.core.utils import iso_8601_datetime_with_milliseconds
from .exceptions import (
EC2ClientError,
DependencyViolationError,
@ -70,6 +71,7 @@ from .utils import (
random_instance_id,
random_internet_gateway_id,
random_ip,
random_nat_gateway_id,
random_key_pair,
random_private_ip,
random_public_ip,
@ -2955,6 +2957,61 @@ class CustomerGatewayBackend(object):
return deleted
class NatGateway(object):
def __init__(self, backend, subnet_id, allocation_id):
# public properties
self.id = random_nat_gateway_id()
self.subnet_id = subnet_id
self.allocation_id = allocation_id
self.state = 'available'
self.private_ip = random_private_ip()
# protected properties
self._created_at = datetime.utcnow()
self._backend = backend
# NOTE: this is the core of NAT Gateways creation
self._eni = self._backend.create_network_interface(backend.get_subnet(self.subnet_id), self.private_ip)
# associate allocation with ENI
self._backend.associate_address(eni=self._eni, allocation_id=self.allocation_id)
@property
def vpc_id(self):
subnet = self._backend.get_subnet(self.subnet_id)
return subnet.vpc_id
@property
def create_time(self):
return iso_8601_datetime_with_milliseconds(self._created_at)
@property
def network_interface_id(self):
return self._eni.id
@property
def public_ip(self):
eips = self._backend.address_by_allocation([self.allocation_id])
return eips[0].public_ip
class NatGatewayBackend(object):
def __init__(self):
self.nat_gateways = {}
def get_all_nat_gateways(self, filters):
return self.nat_gateways.values()
def create_nat_gateway(self, subnet_id, allocation_id):
nat_gateway = NatGateway(self, subnet_id, allocation_id)
self.nat_gateways[nat_gateway.id] = nat_gateway
return nat_gateway
def delete_nat_gateway(self, nat_gateway_id):
return self.nat_gateways.pop(nat_gateway_id)
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
@ -2963,7 +3020,8 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RouteTableBackend, RouteBackend, InternetGatewayBackend,
VPCGatewayAttachmentBackend, SpotRequestBackend,
ElasticAddressBackend, KeyPairBackend, DHCPOptionsSetBackend,
NetworkAclBackend, VpnGatewayBackend, CustomerGatewayBackend):
NetworkAclBackend, VpnGatewayBackend, CustomerGatewayBackend,
NatGatewayBackend):
def __init__(self, region_name):
super(EC2Backend, self).__init__()

View File

@ -29,6 +29,7 @@ from .vpcs import VPCs
from .vpc_peering_connections import VPCPeeringConnections
from .vpn_connections import VPNConnections
from .windows import Windows
from .nat_gateways import NatGateways
class EC2Response(
@ -61,6 +62,7 @@ class EC2Response(
VPCPeeringConnections,
VPNConnections,
Windows,
NatGateways,
):
@property
def ec2_backend(self):

View File

@ -0,0 +1,73 @@
from __future__ import unicode_literals
from moto.core.responses import BaseResponse
from moto.ec2.utils import filters_from_querystring
class NatGateways(BaseResponse):
def create_nat_gateway(self):
subnet_id = self.querystring.get('SubnetId')[0]
allocation_id = self.querystring.get('AllocationId')[0]
nat_gateway = self.ec2_backend.create_nat_gateway(subnet_id=subnet_id, allocation_id=allocation_id)
template = self.response_template(CREATE_NAT_GATEWAY)
return template.render(nat_gateway=nat_gateway)
def delete_nat_gateway(self):
nat_gateway_id = self.querystring.get('NatGatewayId')[0]
nat_gateway = self.ec2_backend.delete_nat_gateway(nat_gateway_id)
template = self.response_template(DELETE_NAT_GATEWAY_RESPONSE)
return template.render(nat_gateway=nat_gateway)
def describe_nat_gateways(self):
filters = filters_from_querystring(self.querystring)
nat_gateways = self.ec2_backend.get_all_nat_gateways(filters)
template = self.response_template(DESCRIBE_NAT_GATEWAYS_RESPONSE)
return template.render(nat_gateways=nat_gateways)
DESCRIBE_NAT_GATEWAYS_RESPONSE = """<DescribeNatGatewaysResponse xmlns="http://ec2.amazonaws.com/doc/2015-10-01/">
<requestId>bfed02c6-dae9-47c0-86a2-example</requestId>
<natGatewaySet>
{% for nat_gateway in nat_gateways %}
<item>
<subnetId>{{ nat_gateway.subnet_id }}</subnetId>
<natGatewayAddressSet>
<item>
<networkInterfaceId>{{ nat_gateway.network_interface_id }}</networkInterfaceId>
<publicIp>{{ nat_gateway.public_ip }}</publicIp>
<allocationId>{{ nat_gateway.allocation_id }}</allocationId>
<privateIp>{{ nat_gateway.private_ip }}</privateIp>
</item>
</natGatewayAddressSet>
<createTime>{{ nat_gateway.create_time }}</createTime>
<vpcId>{{ nat_gateway.vpc_id }}</vpcId>
<natGatewayId>{{ nat_gateway.id }}</natGatewayId>
<state>{{ nat_gateway.state }}</state>
</item>
{% endfor %}
</natGatewaySet>
</DescribeNatGatewaysResponse>
"""
CREATE_NAT_GATEWAY = """<CreateNatGatewayResponse xmlns="http://ec2.amazonaws.com/doc/2015-10-01/">
<requestId>1b74dc5c-bcda-403f-867d-example</requestId>
<natGateway>
<subnetId>{{ nat_gateway.subnet_id }}</subnetId>
<natGatewayAddressSet>
<item>
<allocationId>{{ nat_gateway.allocation_id }}</allocationId>
</item>
</natGatewayAddressSet>
<createTime>{{ nat_gateway.create_time }}</createTime>
<vpcId>{{ nat_gateway.vpc_id }}</vpcId>
<natGatewayId>{{ nat_gateway.id }}</natGatewayId>
<state>{{ nat_gateway.state }}</state>
</natGateway>
</CreateNatGatewayResponse>
"""
DELETE_NAT_GATEWAY_RESPONSE = """<DeleteNatGatewayResponse xmlns="http://ec2.amazonaws.com/doc/2015-10-01/">
<requestId>741fc8ab-6ebe-452b-b92b-example</requestId>
<natGatewayId>{{ nat_gateway.id }}</natGatewayId>
</DeleteNatGatewayResponse>"""

View File

@ -9,6 +9,7 @@ EC2_RESOURCE_TO_PREFIX = {
'image': 'ami',
'instance': 'i',
'internet-gateway': 'igw',
'nat-gateway': 'nat',
'network-acl': 'acl',
'network-acl-subnet-assoc': 'aclassoc',
'network-interface': 'eni',
@ -33,8 +34,7 @@ EC2_RESOURCE_TO_PREFIX = {
EC2_PREFIX_TO_RESOURCE = dict((v, k) for (k, v) in EC2_RESOURCE_TO_PREFIX.items())
def random_id(prefix=''):
size = 8
def random_id(prefix='', size=8):
chars = list(range(10)) + ['a', 'b', 'c', 'd', 'e', 'f']
resource_id = ''.join(six.text_type(random.choice(chars)) for x in range(size))
@ -133,6 +133,10 @@ def random_eni_attach_id():
return random_id(prefix=EC2_RESOURCE_TO_PREFIX['network-interface-attachment'])
def random_nat_gateway_id():
return random_id(prefix=EC2_RESOURCE_TO_PREFIX['nat-gateway'], size=17)
def random_public_ip():
return '54.214.{0}.{1}'.format(random.choice(range(255)),
random.choice(range(255)))

View File

@ -0,0 +1,100 @@
from __future__ import unicode_literals
import boto3
import sure # noqa
from moto import mock_ec2
@mock_ec2
def test_describe_nat_gateways():
conn = boto3.client('ec2', 'us-east-1')
response = conn.describe_nat_gateways()
response['NatGateways'].should.have.length_of(0)
@mock_ec2
def test_create_nat_gateway():
conn = boto3.client('ec2', 'us-east-1')
vpc = conn.create_vpc(CidrBlock='10.0.0.0/16')
vpc_id = vpc['Vpc']['VpcId']
subnet = conn.create_subnet(
VpcId=vpc_id,
CidrBlock='10.0.1.0/27',
AvailabilityZone='us-east-1a',
)
allocation_id = conn.allocate_address(Domain='vpc')['AllocationId']
subnet_id = subnet['Subnet']['SubnetId']
response = conn.create_nat_gateway(
SubnetId=subnet_id,
AllocationId=allocation_id,
)
response['NatGateway']['VpcId'].should.equal(vpc_id)
response['NatGateway']['SubnetId'].should.equal(subnet_id)
response['NatGateway']['State'].should.equal('available')
@mock_ec2
def test_delete_nat_gateway():
conn = boto3.client('ec2', 'us-east-1')
vpc = conn.create_vpc(CidrBlock='10.0.0.0/16')
vpc_id = vpc['Vpc']['VpcId']
subnet = conn.create_subnet(
VpcId=vpc_id,
CidrBlock='10.0.1.0/27',
AvailabilityZone='us-east-1a',
)
allocation_id = conn.allocate_address(Domain='vpc')['AllocationId']
subnet_id = subnet['Subnet']['SubnetId']
nat_gateway = conn.create_nat_gateway(
SubnetId=subnet_id,
AllocationId=allocation_id,
)
nat_gateway_id = nat_gateway['NatGateway']['NatGatewayId']
response = conn.delete_nat_gateway(NatGatewayId=nat_gateway_id)
response.should.equal({
'NatGatewayId': nat_gateway_id,
'ResponseMetadata': {
'HTTPStatusCode': 200,
'RequestId': '741fc8ab-6ebe-452b-b92b-example'
}
})
@mock_ec2
def test_create_and_describe_nat_gateway():
conn = boto3.client('ec2', 'us-east-1')
vpc = conn.create_vpc(CidrBlock='10.0.0.0/16')
vpc_id = vpc['Vpc']['VpcId']
subnet = conn.create_subnet(
VpcId=vpc_id,
CidrBlock='10.0.1.0/27',
AvailabilityZone='us-east-1a',
)
allocation_id = conn.allocate_address(Domain='vpc')['AllocationId']
subnet_id = subnet['Subnet']['SubnetId']
create_response = conn.create_nat_gateway(
SubnetId=subnet_id,
AllocationId=allocation_id,
)
nat_gateway_id = create_response['NatGateway']['NatGatewayId']
describe_response = conn.describe_nat_gateways()
enis = conn.describe_network_interfaces()['NetworkInterfaces']
eni_id = enis[0]['NetworkInterfaceId']
public_ip = conn.describe_addresses(AllocationIds=[allocation_id])['Addresses'][0]['PublicIp']
describe_response['NatGateways'].should.have.length_of(1)
describe_response['NatGateways'][0]['NatGatewayId'].should.equal(nat_gateway_id)
describe_response['NatGateways'][0]['State'].should.equal('available')
describe_response['NatGateways'][0]['SubnetId'].should.equal(subnet_id)
describe_response['NatGateways'][0]['VpcId'].should.equal(vpc_id)
describe_response['NatGateways'][0]['NatGatewayAddresses'][0]['AllocationId'].should.equal(allocation_id)
describe_response['NatGateways'][0]['NatGatewayAddresses'][0]['NetworkInterfaceId'].should.equal(eni_id)
assert describe_response['NatGateways'][0]['NatGatewayAddresses'][0]['PrivateIp'].startswith('10.')
describe_response['NatGateways'][0]['NatGatewayAddresses'][0]['PublicIp'].should.equal(public_ip)