diff --git a/moto/ec2/models.py b/moto/ec2/models.py
index 1c9e34989..719c5e94f 100644
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -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__()
diff --git a/moto/ec2/responses/__init__.py b/moto/ec2/responses/__init__.py
index e51992e41..d939178fb 100644
--- a/moto/ec2/responses/__init__.py
+++ b/moto/ec2/responses/__init__.py
@@ -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):
diff --git a/moto/ec2/responses/nat_gateways.py b/moto/ec2/responses/nat_gateways.py
new file mode 100644
index 000000000..e960b990c
--- /dev/null
+++ b/moto/ec2/responses/nat_gateways.py
@@ -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 = """
+ bfed02c6-dae9-47c0-86a2-example
+
+ {% for nat_gateway in nat_gateways %}
+ -
+ {{ nat_gateway.subnet_id }}
+
+
-
+ {{ nat_gateway.network_interface_id }}
+ {{ nat_gateway.public_ip }}
+ {{ nat_gateway.allocation_id }}
+ {{ nat_gateway.private_ip }}
+
+
+ {{ nat_gateway.create_time }}
+ {{ nat_gateway.vpc_id }}
+ {{ nat_gateway.id }}
+ {{ nat_gateway.state }}
+
+ {% endfor %}
+
+
+"""
+
+CREATE_NAT_GATEWAY = """
+ 1b74dc5c-bcda-403f-867d-example
+
+ {{ nat_gateway.subnet_id }}
+
+ -
+ {{ nat_gateway.allocation_id }}
+
+
+ {{ nat_gateway.create_time }}
+ {{ nat_gateway.vpc_id }}
+ {{ nat_gateway.id }}
+ {{ nat_gateway.state }}
+
+
+"""
+
+
+DELETE_NAT_GATEWAY_RESPONSE = """
+ 741fc8ab-6ebe-452b-b92b-example
+ {{ nat_gateway.id }}
+"""
diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py
index 5b7743bf4..5b05cafa0 100644
--- a/moto/ec2/utils.py
+++ b/moto/ec2/utils.py
@@ -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)))
diff --git a/tests/test_ec2/test_nat_gateway.py b/tests/test_ec2/test_nat_gateway.py
new file mode 100644
index 000000000..d073fc5e3
--- /dev/null
+++ b/tests/test_ec2/test_nat_gateway.py
@@ -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)