diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py
index 76c90e706..599f0d00d 100644
--- a/moto/ec2/exceptions.py
+++ b/moto/ec2/exceptions.py
@@ -80,6 +80,14 @@ class InvalidNetworkAclIdError(EC2ClientError):
.format(network_acl_id))
+class InvalidVpnGatewayIdError(EC2ClientError):
+ def __init__(self, network_acl_id):
+ super(InvalidVpnGatewayIdError, self).__init__(
+ "InvalidVpnGatewayID.NotFound",
+ "The virtual private gateway ID '{0}' does not exist"
+ .format(network_acl_id))
+
+
class InvalidNetworkInterfaceIdError(EC2ClientError):
def __init__(self, eni_id):
super(InvalidNetworkInterfaceIdError, self).__init__(
diff --git a/moto/ec2/models.py b/moto/ec2/models.py
index f5b68da28..9c30cb119 100644
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -53,7 +53,8 @@ from .exceptions import (
TagLimitExceeded,
InvalidID,
InvalidCIDRSubnetError,
- InvalidNetworkAclIdError
+ InvalidNetworkAclIdError,
+ InvalidVpnGatewayIdError
)
from .utils import (
EC2_RESOURCE_TO_PREFIX,
@@ -89,7 +90,8 @@ from .utils import (
filter_internet_gateways,
filter_reservations,
random_network_acl_id,
- random_network_acl_subnet_association_id)
+ random_network_acl_subnet_association_id,
+ random_vpn_gateway_id)
def validate_resource_ids(resource_ids):
@@ -2442,6 +2444,65 @@ class NetworkAclEntry(TaggedEC2Resource):
self.port_range_to = port_range_to
+class VpnGateway(TaggedEC2Resource):
+ def __init__(self, ec2_backend, id, type):
+ self.ec2_backend = ec2_backend
+ self.id = id
+ self.type = type
+ self.attachments = {}
+ super(VpnGateway, self).__init__()
+
+
+class VpnGatewayAttachment(object):
+ def __init__(self, vpc_id, state):
+ self.vpc_id = vpc_id
+ self.state = state
+ super(VpnGatewayAttachment, self).__init__()
+
+
+class VpnGatewayBackend(object):
+ def __init__(self):
+ self.vpn_gateways = {}
+ super(VpnGatewayBackend, self).__init__()
+
+ def create_vpn_gateway(self, type='ipsec.1'):
+ vpn_gateway_id = random_vpn_gateway_id()
+ vpn_gateway = VpnGateway(self, vpn_gateway_id, type)
+ self.vpn_gateways[vpn_gateway_id] = vpn_gateway
+ return vpn_gateway
+
+ def get_all_vpn_gateways(self, filters=None):
+ vpn_gateways = self.vpn_gateways.values()
+ return generic_filter(filters, vpn_gateways)
+
+ def get_vpn_gateway(self, vpn_gateway_id):
+ vpn_gateway = self.vpn_gateways.get(vpn_gateway_id, None)
+ if not vpn_gateway:
+ raise InvalidVpnGatewayIdError(vpn_gateway_id)
+ return vpn_gateway
+
+ def attach_vpn_gateway(self, vpn_gateway_id, vpc_id):
+ vpn_gateway = self.get_vpn_gateway(vpn_gateway_id)
+ self.get_vpc(vpc_id)
+ attachment = VpnGatewayAttachment(vpc_id, state='attached')
+ vpn_gateway.attachments[vpc_id] = attachment
+ return attachment
+
+ def delete_vpn_gateway(self, vpn_gateway_id):
+ deleted = self.vpn_gateways.pop(vpn_gateway_id, None)
+ if not deleted:
+ raise InvalidVpnGatewayIdError(vpn_gateway_id)
+ return deleted
+
+ def detach_vpn_gateway(self, vpn_gateway_id, vpc_id):
+ vpn_gateway = self.get_vpn_gateway(vpn_gateway_id)
+ self.get_vpc(vpc_id)
+ detached = vpn_gateway.attachments.pop(vpc_id, None)
+ if not detached:
+ raise InvalidVPCIdError(vpc_id)
+ return detached
+
+
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
@@ -2450,7 +2511,7 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RouteTableBackend, RouteBackend, InternetGatewayBackend,
VPCGatewayAttachmentBackend, SpotRequestBackend,
ElasticAddressBackend, KeyPairBackend, DHCPOptionsSetBackend,
- NetworkAclBackend):
+ NetworkAclBackend, VpnGatewayBackend):
def __init__(self, region_name):
super(EC2Backend, self).__init__()
@@ -2509,7 +2570,7 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['vpn-connection']:
self.raise_not_implemented_error('DescribeVpnConnections')
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['vpn-gateway']:
- self.raise_not_implemented_error('DescribeVpnGateways')
+ self.get_vpn_gateway(vpn_gateway_id=resource_id)
return True
ec2_backends = {}
diff --git a/moto/ec2/responses/virtual_private_gateways.py b/moto/ec2/responses/virtual_private_gateways.py
index 18e1e99f1..50b5704ea 100644
--- a/moto/ec2/responses/virtual_private_gateways.py
+++ b/moto/ec2/responses/virtual_private_gateways.py
@@ -1,19 +1,123 @@
from __future__ import unicode_literals
+from jinja2 import Template
from moto.core.responses import BaseResponse
+from moto.ec2.utils import filters_from_querystring
class VirtualPrivateGateways(BaseResponse):
def attach_vpn_gateway(self):
- raise NotImplementedError('VirtualPrivateGateways(AmazonVPC).attach_vpn_gateway is not yet implemented')
+ vpn_gateway_id = self.querystring.get('VpnGatewayId')[0]
+ vpc_id = self.querystring.get('VpcId')[0]
+ attachment = self.ec2_backend.attach_vpn_gateway(
+ vpn_gateway_id,
+ vpc_id
+ )
+ template = Template(ATTACH_VPN_GATEWAY_RESPONSE)
+ return template.render(attachment=attachment)
def create_vpn_gateway(self):
- raise NotImplementedError('VirtualPrivateGateways(AmazonVPC).create_vpn_gateway is not yet implemented')
+ type = self.querystring.get('Type', None)[0]
+ vpn_gateway = self.ec2_backend.create_vpn_gateway(type)
+ template = Template(CREATE_VPN_GATEWAY_RESPONSE)
+ return template.render(vpn_gateway=vpn_gateway)
def delete_vpn_gateway(self):
- raise NotImplementedError('VirtualPrivateGateways(AmazonVPC).delete_vpn_gateway is not yet implemented')
+ vpn_gateway_id = self.querystring.get('VpnGatewayId')[0]
+ vpn_gateway = self.ec2_backend.delete_vpn_gateway(vpn_gateway_id)
+ template = Template(DELETE_VPN_GATEWAY_RESPONSE)
+ return template.render(vpn_gateway=vpn_gateway)
def describe_vpn_gateways(self):
- raise NotImplementedError('VirtualPrivateGateways(AmazonVPC).describe_vpn_gateways is not yet implemented')
+ filters = filters_from_querystring(self.querystring)
+ vpn_gateways = self.ec2_backend.get_all_vpn_gateways(filters)
+ template = Template(DESCRIBE_VPN_GATEWAYS_RESPONSE)
+ return template.render(vpn_gateways=vpn_gateways)
def detach_vpn_gateway(self):
- raise NotImplementedError('VirtualPrivateGateways(AmazonVPC).detach_vpn_gateway is not yet implemented')
+ vpn_gateway_id = self.querystring.get('VpnGatewayId')[0]
+ vpc_id = self.querystring.get('VpcId')[0]
+ attachment = self.ec2_backend.detach_vpn_gateway(
+ vpn_gateway_id,
+ vpc_id
+ )
+ template = Template(DETACH_VPN_GATEWAY_RESPONSE)
+ return template.render(attachment=attachment)
+
+CREATE_VPN_GATEWAY_RESPONSE = """
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+ {{ vpn_gateway.id }}
+ available
+ {{ vpn_gateway.type }}
+ us-east-1a
+
+
+ {% for tag in vpn_gateway.get_tags() %}
+ -
+ {{ tag.resource_id }}
+ {{ tag.resource_type }}
+ {{ tag.key }}
+ {{ tag.value }}
+
+ {% endfor %}
+
+
+"""
+
+DESCRIBE_VPN_GATEWAYS_RESPONSE = """
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+ {% for vpn_gateway in vpn_gateways %}
+ -
+ {{ vpn_gateway.id }}
+ available
+ {{ vpn_gateway.id }}
+ us-east-1a
+
+ {% for attachment in vpn_gateway.attachments.values() %}
+
-
+ {{ attachment.vpc_id }}
+ {{ attachment.state }}
+
+ {% endfor %}
+
+
+
+ {% for tag in vpn_gateway.get_tags() %}
+ -
+ {{ tag.resource_id }}
+ {{ tag.resource_type }}
+ {{ tag.key }}
+ {{ tag.value }}
+
+ {% endfor %}
+
+
+ {% endfor %}
+
+"""
+
+ATTACH_VPN_GATEWAY_RESPONSE = """
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+ {{ attachment.vpc_id }}
+ {{ attachment.state }}
+
+"""
+
+DELETE_VPN_GATEWAY_RESPONSE = """
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+ true
+
+"""
+
+DETACH_VPN_GATEWAY_RESPONSE = """
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+ true
+
+"""
\ No newline at end of file
diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py
index c2608b9c8..6ff04821c 100644
--- a/moto/ec2/utils.py
+++ b/moto/ec2/utils.py
@@ -81,6 +81,10 @@ def random_network_acl_subnet_association_id():
return random_id(prefix=EC2_RESOURCE_TO_PREFIX['network-acl-subnet-assoc'])
+def random_vpn_gateway_id():
+ return random_id(prefix=EC2_RESOURCE_TO_PREFIX['vpn-gateway'])
+
+
def random_volume_id():
return random_id(prefix=EC2_RESOURCE_TO_PREFIX['volume'])
diff --git a/tests/test_ec2/test_virtual_private_gateways.py b/tests/test_ec2/test_virtual_private_gateways.py
index eea1e2222..8050559f1 100644
--- a/tests/test_ec2/test_virtual_private_gateways.py
+++ b/tests/test_ec2/test_virtual_private_gateways.py
@@ -7,4 +7,98 @@ from moto import mock_ec2
@mock_ec2
def test_virtual_private_gateways():
- pass
+ conn = boto.connect_vpc('the_key', 'the_secret')
+
+ vpn_gateway = conn.create_vpn_gateway('ipsec.1', 'us-east-1a')
+ vpn_gateway.should_not.be.none
+ vpn_gateway.id.should.match(r'vgw-\w+')
+ vpn_gateway.type.should.equal('ipsec.1')
+ vpn_gateway.state.should.equal('available')
+ vpn_gateway.availability_zone.should.equal('us-east-1a')
+
+@mock_ec2
+def test_describe_vpn_gateway():
+ conn = boto.connect_vpc('the_key', 'the_secret')
+ vpn_gateway = conn.create_vpn_gateway('ipsec.1', 'us-east-1a')
+
+ vgws = conn.get_all_vpn_gateways()
+ vgws.should.have.length_of(1)
+
+ gateway = vgws[0]
+ gateway.id.should.match(r'vgw-\w+')
+ gateway.id.should.equal(vpn_gateway.id)
+ vpn_gateway.type.should.equal('ipsec.1')
+ vpn_gateway.state.should.equal('available')
+ vpn_gateway.availability_zone.should.equal('us-east-1a')
+
+
+@mock_ec2
+def test_vpn_gateway_vpc_attachment():
+ conn = boto.connect_vpc('the_key', 'the_secret')
+ vpc = conn.create_vpc("10.0.0.0/16")
+ vpn_gateway = conn.create_vpn_gateway('ipsec.1', 'us-east-1a')
+
+ conn.attach_vpn_gateway(
+ vpn_gateway_id=vpn_gateway.id,
+ vpc_id=vpc.id
+ )
+
+ gateway = conn.get_all_vpn_gateways()[0]
+ attachments = gateway.attachments
+ attachments.should.have.length_of(1)
+ attachments[0].vpc_id.should.equal(vpc.id)
+ attachments[0].state.should.equal('attached')
+
+
+@mock_ec2
+def test_delete_vpn_gateway():
+ conn = boto.connect_vpc('the_key', 'the_secret')
+ vpn_gateway = conn.create_vpn_gateway('ipsec.1', 'us-east-1a')
+
+ conn.delete_vpn_gateway(vpn_gateway.id)
+ vgws = conn.get_all_vpn_gateways()
+ vgws.should.have.length_of(0)
+
+
+@mock_ec2
+def test_vpn_gateway_tagging():
+ conn = boto.connect_vpc('the_key', 'the_secret')
+ vpn_gateway = conn.create_vpn_gateway('ipsec.1', 'us-east-1a')
+ vpn_gateway.add_tag("a key", "some value")
+
+ tag = conn.get_all_tags()[0]
+ tag.name.should.equal("a key")
+ tag.value.should.equal("some value")
+
+ # Refresh the subnet
+ vpn_gateway = conn.get_all_vpn_gateways()[0]
+ vpn_gateway.tags.should.have.length_of(1)
+ vpn_gateway.tags["a key"].should.equal("some value")
+
+
+@mock_ec2
+def test_detach_vpn_gateway():
+
+ conn = boto.connect_vpc('the_key', 'the_secret')
+ vpc = conn.create_vpc("10.0.0.0/16")
+ vpn_gateway = conn.create_vpn_gateway('ipsec.1', 'us-east-1a')
+
+ conn.attach_vpn_gateway(
+ vpn_gateway_id=vpn_gateway.id,
+ vpc_id=vpc.id
+ )
+
+ gateway = conn.get_all_vpn_gateways()[0]
+ attachments = gateway.attachments
+ attachments.should.have.length_of(1)
+ attachments[0].vpc_id.should.equal(vpc.id)
+ attachments[0].state.should.equal('attached')
+
+ conn.detach_vpn_gateway(
+ vpn_gateway_id=vpn_gateway.id,
+ vpc_id=vpc.id
+ )
+
+ gateway = conn.get_all_vpn_gateways()[0]
+ attachments = gateway.attachments
+ attachments.should.have.length_of(0)