diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 3c181e045..5c3d64727 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -84,6 +84,14 @@ class InvalidVpnGatewayIdError(EC2ClientError): .format(network_acl_id)) +class InvalidVpnConnectionIdError(EC2ClientError): + def __init__(self, network_acl_id): + super(InvalidVpnConnectionIdError, self).__init__( + "InvalidVpnConnectionID.NotFound", + "The vpnConnection 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 f1ce042b8..92b26697c 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -54,7 +54,8 @@ from .exceptions import ( InvalidID, InvalidCIDRSubnetError, InvalidNetworkAclIdError, - InvalidVpnGatewayIdError + InvalidVpnGatewayIdError, + InvalidVpnConnectionIdError ) from .utils import ( EC2_RESOURCE_TO_PREFIX, @@ -93,6 +94,7 @@ from .utils import ( random_network_acl_id, random_network_acl_subnet_association_id, random_vpn_gateway_id, + random_vpn_connection_id, is_tag_filter, ) @@ -2526,6 +2528,70 @@ class DHCPOptionsSetBackend(object): return True +class VPNConnection(TaggedEC2Resource): + def __init__(self, ec2_backend, id, type, + customer_gateway_id, vpn_gateway_id): + self.ec2_backend = ec2_backend + self.id = id + self.state = 'available' + self.customer_gateway_configuration = {} + self.type = type + self.customer_gateway_id = customer_gateway_id + self.vpn_gateway_id = vpn_gateway_id + self.tunnels = None + self.options = None + self.static_routes = None + + +class VPNConnectionBackend(object): + def __init__(self): + self.vpn_connections = {} + super(VPNConnectionBackend, self).__init__() + + def create_vpn_connection(self, type, customer_gateway_id, + vpn_gateway_id, + static_routes_only=None): + vpn_connection_id = random_vpn_connection_id() + if static_routes_only: + pass + vpn_connection = VPNConnection( + self, id=vpn_connection_id, type=type, + customer_gateway_id=customer_gateway_id, + vpn_gateway_id=vpn_gateway_id + ) + self.vpn_connections[vpn_connection.id] = vpn_connection + return vpn_connection + + def delete_vpn_connection(self, vpn_connection_id): + + if vpn_connection_id in self.vpn_connections: + self.vpn_connections.pop(vpn_connection_id) + else: + raise InvalidVpnConnectionIdError(vpn_connection_id) + return True + + def describe_vpn_connections(self, vpn_connection_ids=None): + vpn_connections = [] + for vpn_connection_id in vpn_connection_ids or []: + if vpn_connection_id in self.vpn_connections: + vpn_connections.append(self.vpn_connections[vpn_connection_id]) + else: + raise InvalidVpnConnectionIdError(vpn_connection_id) + return vpn_connections or self.vpn_connections.values() + + def get_all_vpn_connections(self, vpn_connection_ids=None, filters=None): + vpn_connections = self.vpn_connections.values() + + if vpn_connection_ids: + vpn_connections = [vpn_connection for vpn_connection in vpn_connections + if vpn_connection.id in vpn_connection_ids] + if len(vpn_connections) != len(vpn_connection_ids): + invalid_id = list(set(vpn_connection_ids).difference(set([vpn_connection.id for vpn_connection in vpn_connections])))[0] + raise InvalidVpnConnectionIdError(invalid_id) + + return generic_filter(filters, vpn_connections) + + class NetworkAclBackend(object): def __init__(self): self.network_acls = {} @@ -2724,7 +2790,7 @@ class VpnGatewayBackend(object): class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend, VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend, - NetworkInterfaceBackend, + NetworkInterfaceBackend, VPNConnectionBackend, VPCPeeringConnectionBackend, RouteTableBackend, RouteBackend, InternetGatewayBackend, VPCGatewayAttachmentBackend, SpotRequestBackend, @@ -2786,7 +2852,7 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, elif resource_prefix == EC2_RESOURCE_TO_PREFIX['vpc-peering-connection']: self.get_vpc_peering_connection(vpc_pcx_id=resource_id) elif resource_prefix == EC2_RESOURCE_TO_PREFIX['vpn-connection']: - self.raise_not_implemented_error('DescribeVpnConnections') + self.describe_vpn_connections(vpn_connection_ids=[resource_id]) elif resource_prefix == EC2_RESOURCE_TO_PREFIX['vpn-gateway']: self.get_vpn_gateway(vpn_gateway_id=resource_id) return True diff --git a/moto/ec2/responses/vpn_connections.py b/moto/ec2/responses/vpn_connections.py index ad947efec..7825e7ebb 100644 --- a/moto/ec2/responses/vpn_connections.py +++ b/moto/ec2/responses/vpn_connections.py @@ -1,13 +1,321 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse +from moto.ec2.utils import filters_from_querystring, sequence_from_querystring class VPNConnections(BaseResponse): def create_vpn_connection(self): - raise NotImplementedError('VPNConnections(AmazonVPC).create_vpn_connection is not yet implemented') + type = self.querystring.get("Type", [None])[0] + cgw_id = self.querystring.get("CustomerGatewayId", [None])[0] + vgw_id = self.querystring.get("VPNGatewayId", [None])[0] + static_routes = self.querystring.get("StaticRoutesOnly", [None])[0] + vpn_connection = self.ec2_backend.create_vpn_connection(type, cgw_id, vgw_id, static_routes_only=static_routes) + template = self.response_template(CREATE_VPN_CONNECTION_RESPONSE) + return template.render(vpn_connection=vpn_connection) def delete_vpn_connection(self): - raise NotImplementedError('VPNConnections(AmazonVPC).delete_vpn_connection is not yet implemented') + vpn_connection_id = self.querystring.get('VpnConnectionId')[0] + vpn_connection = self.ec2_backend.delete_vpn_connection(vpn_connection_id) + template = self.response_template(DELETE_VPN_CONNECTION_RESPONSE) + return template.render(vpn_connection=vpn_connection) def describe_vpn_connections(self): - raise NotImplementedError('VPNConnections(AmazonVPC).describe_vpn_connections is not yet implemented') + vpn_connection_ids = sequence_from_querystring('VpnConnectionId', self.querystring) + filters = filters_from_querystring(self.querystring) + vpn_connections = self.ec2_backend.get_all_vpn_connections( + vpn_connection_ids=vpn_connection_ids, filters=filters) + template = self.response_template(DESCRIBE_VPN_CONNECTION_RESPONSE) + return template.render(vpn_connections=vpn_connections) + + +CREATE_VPN_CONNECTION_RESPONSE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + + {{ vpn_connection.id }} + pending + + + {{ vpn_connection.customer_gateway_id }} + {{ vpn_connection.vpn_gateway_id }} + ipsec.1 + + + + 12.1.2.3 + + + 169.254.44.42 + 255.255.255.252 + 30 + + + 65000 + 30 + + + + + 52.2.144.13 + + + 169.254.44.41 + 255.255.255.252 + 30 + + + 7224 + 30 + + + + sha1 + aes-128-cbc + 28800 + group2 + main + Iw2IAN9XUsQeYUrkMGP3kP59ugFDkfHg + + + esp + hmac-sha1-96 + aes-128-cbc + 3600 + group2 + tunnel + true + true + 1387 + + 10 + 3 + + + + + + + 12.1.2.3 + + + 169.254.44.42 + 255.255.255.252 + 30 + + + 65000 + 30 + + + + + 52.2.144.13 + + + 169.254.44.41 + 255.255.255.252 + 30 + + + 7224 + 30 + + + + sha1 + aes-128-cbc + 28800 + group2 + main + Iw2IAN9XUsQeYUrkMGP3kP59ugFDkfHg + + + esp + hmac-sha1-96 + aes-128-cbc + 3600 + group2 + tunnel + true + true + 1387 + + 10 + 3 + + + + + + ipsec.1 + {{ vpn_connection.customer_gateway_id }} + {{ vpn_connection.vpn_gateway_id }} + + {% for tag in vpn_connection.get_tags() %} + + {{ tag.resource_id }} + {{ tag.resource_type }} + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + +""" + +CREATE_VPN_CONNECTION_ROUTE_RESPONSE = """ + + 4f35a1b2-c2c3-4093-b51f-abb9d7311990 + true +""" + +DELETE_VPN_CONNECTION_RESPONSE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + true +""" + +DELETE_VPN_CONNECTION_ROUTE_RESPONSE = """ + + 4f35a1b2-c2c3-4093-b51f-abb9d7311990 + true +""" + +DESCRIBE_VPN_CONNECTION_RESPONSE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + + {% for vpn_connection in vpn_connections %} + + {{ vpn_connection.id }} + available + + + {{ vpn_connection.customer_gateway_id }} + {{ vpn_connection.vpn_gateway_id }} + ipsec.1 + + + + 12.1.2.3 + + + 169.254.44.42 + 255.255.255.252 + 30 + + + 65000 + 30 + + + + + 52.2.144.13 + + + 169.254.44.41 + 255.255.255.252 + 30 + + + 7224 + 30 + + + + sha1 + aes-128-cbc + 28800 + group2 + main + Iw2IAN9XUsQeYUrkMGP3kP59ugFDkfHg + + + esp + hmac-sha1-96 + aes-128-cbc + 3600 + group2 + tunnel + true + true + 1387 + + 10 + 3 + + + + + + + 12.1.2.3 + + + 169.254.44.42 + 255.255.255.252 + 30 + + + 65000 + 30 + + + + + 52.2.144.13 + + + 169.254.44.41 + 255.255.255.252 + 30 + + + 7224 + 30 + + + + sha1 + aes-128-cbc + 28800 + group2 + main + Iw2IAN9XUsQeYUrkMGP3kP59ugFDkfHg + + + esp + hmac-sha1-96 + aes-128-cbc + 3600 + group2 + tunnel + true + true + 1387 + + 10 + 3 + + + + + + ipsec.1 + {{ vpn_connection.customer_gateway_id }} + {{ vpn_connection.vpn_gateway_id }} + + {% for tag in vpn_connection.get_tags() %} + + {{ tag.resource_id }} + {{ tag.resource_type }} + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + + {% endfor %} + +""" diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index b16c363ea..e0edde7d6 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -85,6 +85,10 @@ def random_vpn_gateway_id(): return random_id(prefix=EC2_RESOURCE_TO_PREFIX['vpn-gateway']) +def random_vpn_connection_id(): + return random_id(prefix=EC2_RESOURCE_TO_PREFIX['vpn-connection']) + + def random_volume_id(): return random_id(prefix=EC2_RESOURCE_TO_PREFIX['volume']) diff --git a/tests/test_ec2/test_vpn_connections.py b/tests/test_ec2/test_vpn_connections.py index 06b9fba5e..dd96e7b65 100644 --- a/tests/test_ec2/test_vpn_connections.py +++ b/tests/test_ec2/test_vpn_connections.py @@ -1,10 +1,46 @@ from __future__ import unicode_literals import boto +from nose.tools import assert_raises import sure # noqa +from boto.exception import EC2ResponseError from moto import mock_ec2 @mock_ec2 -def test_vpn_connections(): - pass +def test_create_vpn_connections(): + conn = boto.connect_vpc('the_key', 'the_secret') + vpn_connection = conn.create_vpn_connection('ipsec.1', 'vgw-0123abcd', 'cgw-0123abcd') + vpn_connection.should_not.be.none + vpn_connection.id.should.match(r'vpn-\w+') + vpn_connection.type.should.equal('ipsec.1') + +@mock_ec2 +def test_delete_vpn_connections(): + conn = boto.connect_vpc('the_key', 'the_secret') + vpn_connection = conn.create_vpn_connection('ipsec.1', 'vgw-0123abcd', 'cgw-0123abcd') + list_of_vpn_connections = conn.get_all_vpn_connections() + list_of_vpn_connections.should.have.length_of(1) + conn.delete_vpn_connection(vpn_connection.id) + list_of_vpn_connections = conn.get_all_vpn_connections() + list_of_vpn_connections.should.have.length_of(0) + +@mock_ec2 +def test_delete_vpn_connections_bad_id(): + conn = boto.connect_vpc('the_key', 'the_secret') + with assert_raises(EC2ResponseError): + conn.delete_vpn_connection('vpn-0123abcd') + +@mock_ec2 +def test_describe_vpn_connections(): + conn = boto.connect_vpc('the_key', 'the_secret') + list_of_vpn_connections = conn.get_all_vpn_connections() + list_of_vpn_connections.should.have.length_of(0) + conn.create_vpn_connection('ipsec.1', 'vgw-0123abcd', 'cgw-0123abcd') + list_of_vpn_connections = conn.get_all_vpn_connections() + list_of_vpn_connections.should.have.length_of(1) + vpn = conn.create_vpn_connection('ipsec.1', 'vgw-1234abcd', 'cgw-1234abcd') + list_of_vpn_connections = conn.get_all_vpn_connections() + list_of_vpn_connections.should.have.length_of(2) + list_of_vpn_connections = conn.get_all_vpn_connections(vpn.id) + list_of_vpn_connections.should.have.length_of(1)