From 39c3c5c8aa392b492a0f7f886a2770e2cdadb993 Mon Sep 17 00:00:00 2001 From: earthmant Date: Wed, 25 Nov 2015 16:38:02 +0200 Subject: [PATCH 1/3] Adding Support for VPN Connections support create, delete, describe --- moto/ec2/exceptions.py | 8 + moto/ec2/models.py | 72 +++++- moto/ec2/responses/vpn_connections.py | 314 +++++++++++++++++++++++++- moto/ec2/utils.py | 12 + 4 files changed, 400 insertions(+), 6 deletions(-) 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 446561d33..0d7163866 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, ) @@ -2512,6 +2514,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 = {} @@ -2707,7 +2773,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, @@ -2769,7 +2835,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..e01fd272e 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, vpn_connection_ids_from_query_string 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 = vpn_connection_ids_from_query_string(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..70adf2634 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']) @@ -193,6 +197,14 @@ def vpc_ids_from_querystring(querystring_dict): return vpc_ids +def vpn_connection_ids_from_query_string(querystring_dict): + vpn_connection_ids = [] + for key, value in querystring_dict.items(): + if 'VpnConnectionId' in key: + vpn_connection_ids.append(value[0]) + return vpn_connection_ids + + def sequence_from_querystring(parameter, querystring_dict): parameter_values = [] for key, value in querystring_dict.items(): From 76a939cee68abe47e301ce0aa8848a5c139c317f Mon Sep 17 00:00:00 2001 From: earthmant Date: Thu, 3 Dec 2015 13:19:10 +0200 Subject: [PATCH 2/3] making requested fix used sequence_from_querystring and removed added functon vpn_connection_ids_from_query_string added tests --- moto/ec2/responses/vpn_connections.py | 4 +-- moto/ec2/utils.py | 8 ------ tests/test_ec2/test_vpn_connections.py | 40 ++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/moto/ec2/responses/vpn_connections.py b/moto/ec2/responses/vpn_connections.py index e01fd272e..7825e7ebb 100644 --- a/moto/ec2/responses/vpn_connections.py +++ b/moto/ec2/responses/vpn_connections.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.ec2.utils import filters_from_querystring, vpn_connection_ids_from_query_string +from moto.ec2.utils import filters_from_querystring, sequence_from_querystring class VPNConnections(BaseResponse): @@ -20,7 +20,7 @@ class VPNConnections(BaseResponse): return template.render(vpn_connection=vpn_connection) def describe_vpn_connections(self): - vpn_connection_ids = vpn_connection_ids_from_query_string(self.querystring) + 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) diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 70adf2634..e0edde7d6 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -197,14 +197,6 @@ def vpc_ids_from_querystring(querystring_dict): return vpc_ids -def vpn_connection_ids_from_query_string(querystring_dict): - vpn_connection_ids = [] - for key, value in querystring_dict.items(): - if 'VpnConnectionId' in key: - vpn_connection_ids.append(value[0]) - return vpn_connection_ids - - def sequence_from_querystring(parameter, querystring_dict): parameter_values = [] for key, value in querystring_dict.items(): diff --git a/tests/test_ec2/test_vpn_connections.py b/tests/test_ec2/test_vpn_connections.py index 06b9fba5e..064ff0e88 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, assert_in 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) as message: + 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) From e98dfb1648f1fd445aa2a32090c4c90f8a5d61a5 Mon Sep 17 00:00:00 2001 From: earthmant Date: Thu, 3 Dec 2015 13:51:21 +0200 Subject: [PATCH 3/3] corrected unused import in test_vpn_connectins --- tests/test_ec2/test_vpn_connections.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_ec2/test_vpn_connections.py b/tests/test_ec2/test_vpn_connections.py index 064ff0e88..dd96e7b65 100644 --- a/tests/test_ec2/test_vpn_connections.py +++ b/tests/test_ec2/test_vpn_connections.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals import boto -from nose.tools import assert_raises, assert_in +from nose.tools import assert_raises import sure # noqa from boto.exception import EC2ResponseError @@ -28,7 +28,7 @@ def test_delete_vpn_connections(): @mock_ec2 def test_delete_vpn_connections_bad_id(): conn = boto.connect_vpc('the_key', 'the_secret') - with assert_raises(EC2ResponseError) as message: + with assert_raises(EC2ResponseError): conn.delete_vpn_connection('vpn-0123abcd') @mock_ec2