Merge pull request #156 from DreadPirateShawn/VPCPeeringConnections

Add support for VPCPeeringConnections.
This commit is contained in:
Steve Pulec 2014-08-20 07:38:28 -04:00
commit 9d06ccf0cd
7 changed files with 330 additions and 1 deletions

View File

@ -4,6 +4,7 @@ python:
- 2.7 - 2.7
env: env:
matrix: matrix:
- BOTO_VERSION=2.32.1
- BOTO_VERSION=2.31.1 - BOTO_VERSION=2.31.1
- BOTO_VERSION=2.25.0 - BOTO_VERSION=2.25.0
- BOTO_VERSION=2.19.0 - BOTO_VERSION=2.19.0

View File

@ -37,6 +37,22 @@ class InvalidVPCIdError(EC2ClientError):
.format(vpc_id)) .format(vpc_id))
class InvalidVPCPeeringConnectionIdError(EC2ClientError):
def __init__(self, vpc_peering_connection_id):
super(InvalidVPCPeeringConnectionIdError, self).__init__(
"InvalidVpcPeeringConnectionId.NotFound",
"VpcPeeringConnectionID {0} does not exist."
.format(vpc_peering_connection_id))
class InvalidVPCPeeringConnectionStateTransitionError(EC2ClientError):
def __init__(self, vpc_peering_connection_id):
super(InvalidVPCPeeringConnectionStateTransitionError, self).__init__(
"InvalidStateTransition",
"VpcPeeringConnectionID {0} is not in the correct state for the request."
.format(vpc_peering_connection_id))
class InvalidParameterValueError(EC2ClientError): class InvalidParameterValueError(EC2ClientError):
def __init__(self, parameter_value): def __init__(self, parameter_value):
super(InvalidParameterValueError, self).__init__( super(InvalidParameterValueError, self).__init__(

View File

@ -15,7 +15,9 @@ from .exceptions import (
InvalidInternetGatewayIDError, InvalidInternetGatewayIDError,
GatewayNotAttachedError, GatewayNotAttachedError,
ResourceAlreadyAssociatedError, ResourceAlreadyAssociatedError,
InvalidVPCIdError InvalidVPCIdError,
InvalidVPCPeeringConnectionIdError,
InvalidVPCPeeringConnectionStateTransitionError
) )
from .utils import ( from .utils import (
random_ami_id, random_ami_id,
@ -35,6 +37,7 @@ from .utils import (
random_subnet_id, random_subnet_id,
random_volume_id, random_volume_id,
random_vpc_id, random_vpc_id,
random_vpc_peering_connection_id,
) )
@ -715,6 +718,89 @@ class VPCBackend(object):
return vpc return vpc
class VPCPeeringConnectionStatus(object):
def __init__(self, code='initiating-request', message=''):
self.code = code
self.message = message
def initiating(self):
self.code = 'initiating-request'
self.message = 'Initiating Request to {accepter ID}'
def pending(self):
self.code = 'pending-acceptance'
self.message = 'Pending Acceptance by {accepter ID}'
def accept(self):
self.code = 'active'
self.message = 'Active'
def reject(self):
self.code = 'rejected'
self.message = 'Inactive'
class VPCPeeringConnection(TaggedEC2Instance):
def __init__(self, vpc_pcx_id, vpc, peer_vpc):
self.id = vpc_pcx_id
self.vpc = vpc
self.peer_vpc = peer_vpc
self._status = VPCPeeringConnectionStatus()
@classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
properties = cloudformation_json['Properties']
vpc = self.get_vpc(properties['VpcId'])
peer_vpc = self.get_vpc(properties['PeerVpcId'])
vpc_pcx = ec2_backend.create_vpc_peering_connection(vpc, peer_vpc)
return vpc_pcx
@property
def physical_resource_id(self):
return self.id
class VPCPeeringConnectionBackend(object):
def __init__(self):
self.vpc_pcxs = {}
super(VPCPeeringConnectionBackend, self).__init__()
def create_vpc_peering_connection(self, vpc, peer_vpc):
vpc_pcx_id = random_vpc_peering_connection_id()
vpc_pcx = VPCPeeringConnection(vpc_pcx_id, vpc, peer_vpc)
vpc_pcx._status.pending()
self.vpc_pcxs[vpc_pcx_id] = vpc_pcx
return vpc_pcx
def get_all_vpc_peering_connections(self):
return self.vpc_pcxs.values()
def get_vpc_peering_connection(self, vpc_pcx_id):
if vpc_pcx_id not in self.vpc_pcxs:
raise InvalidVPCPeeringConnectionIdError(vpc_pcx_id)
return self.vpc_pcxs.get(vpc_pcx_id)
def delete_vpc_peering_connection(self, vpc_pcx_id):
return self.vpc_pcxs.pop(vpc_pcx_id, None)
def accept_vpc_peering_connection(self, vpc_pcx_id):
vpc_pcx = self.get_vpc_peering_connection(vpc_pcx_id)
if vpc_pcx._status.code != 'pending-acceptance':
raise InvalidVPCPeeringConnectionStateTransitionError(vpc_pcx.id)
vpc_pcx._status.accept()
return vpc_pcx
def reject_vpc_peering_connection(self, vpc_pcx_id):
vpc_pcx = self.get_vpc_peering_connection(vpc_pcx_id)
if vpc_pcx._status.code != 'pending-acceptance':
raise InvalidVPCPeeringConnectionStateTransitionError(vpc_pcx.id)
vpc_pcx._status.reject()
return vpc_pcx
class Subnet(TaggedEC2Instance): class Subnet(TaggedEC2Instance):
def __init__(self, subnet_id, vpc_id, cidr_block): def __init__(self, subnet_id, vpc_id, cidr_block):
self.id = subnet_id self.id = subnet_id
@ -1176,6 +1262,7 @@ class DHCPOptionsSetBackend(object):
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend, RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend, VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
VPCPeeringConnectionBackend,
RouteTableBackend, RouteBackend, InternetGatewayBackend, RouteTableBackend, RouteBackend, InternetGatewayBackend,
VPCGatewayAttachmentBackend, SpotRequestBackend, VPCGatewayAttachmentBackend, SpotRequestBackend,
ElasticAddressBackend, KeyPairBackend, DHCPOptionsSetBackend): ElasticAddressBackend, KeyPairBackend, DHCPOptionsSetBackend):

View File

@ -24,6 +24,7 @@ from .virtual_private_gateways import VirtualPrivateGateways
from .vm_export import VMExport from .vm_export import VMExport
from .vm_import import VMImport from .vm_import import VMImport
from .vpcs import VPCs from .vpcs import VPCs
from .vpc_peering_connections import VPCPeeringConnections
from .vpn_connections import VPNConnections from .vpn_connections import VPNConnections
from .windows import Windows from .windows import Windows
@ -55,6 +56,7 @@ class EC2Response(
VMExport, VMExport,
VMImport, VMImport,
VPCs, VPCs,
VPCPeeringConnections,
VPNConnections, VPNConnections,
Windows, Windows,
): ):

View File

@ -0,0 +1,137 @@
from jinja2 import Template
from moto.core.responses import BaseResponse
from moto.ec2.models import ec2_backend
class VPCPeeringConnections(BaseResponse):
def create_vpc_peering_connection(self):
vpc = ec2_backend.get_vpc(self.querystring.get('VpcId')[0])
peer_vpc = ec2_backend.get_vpc(self.querystring.get('PeerVpcId')[0])
vpc_pcx = ec2_backend.create_vpc_peering_connection(vpc, peer_vpc)
template = Template(CREATE_VPC_PEERING_CONNECTION_RESPONSE)
return template.render(vpc_pcx=vpc_pcx)
def delete_vpc_peering_connection(self):
vpc_pcx_id = self.querystring.get('VpcPeeringConnectionId')[0]
vpc_pcx = ec2_backend.delete_vpc_peering_connection(vpc_pcx_id)
if vpc_pcx:
template = Template(DELETE_VPC_PEERING_CONNECTION_RESPONSE)
return template.render(vpc_pcx=vpc_pcx)
else:
return "", dict(status=404)
def describe_vpc_peering_connections(self):
vpc_pcxs = ec2_backend.get_all_vpc_peering_connections()
template = Template(DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE)
return template.render(vpc_pcxs=vpc_pcxs)
def accept_vpc_peering_connection(self):
vpc_pcx_id = self.querystring.get('VpcPeeringConnectionId')[0]
vpc_pcx = ec2_backend.accept_vpc_peering_connection(vpc_pcx_id)
if vpc_pcx:
template = Template(ACCEPT_VPC_PEERING_CONNECTION_RESPONSE)
return template.render(vpc_pcx=vpc_pcx)
else:
return "", dict(status=404)
def reject_vpc_peering_connection(self):
vpc_pcx_id = self.querystring.get('VpcPeeringConnectionId')[0]
vpc_pcx = ec2_backend.reject_vpc_peering_connection(vpc_pcx_id)
if vpc_pcx:
template = Template(REJECT_VPC_PEERING_CONNECTION_RESPONSE)
return template.render()
else:
return "", dict(status=404)
CREATE_VPC_PEERING_CONNECTION_RESPONSE = """
<CreateVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<vpcPeeringConnection>
<vpcPeeringConnectionId>{{ vpc_pcx.id }}</vpcPeeringConnectionId>
<requesterVpcInfo>
<ownerId>777788889999</ownerId>
<vpcId>{{ vpc_pcx.vpc.id }}</vpcId>
<cidrBlock>{{ vpc_pcx.vpc.cidr_block }}</cidrBlock>
</requesterVpcInfo>
<accepterVpcInfo>
<ownerId>123456789012</ownerId>
<vpcId>{{ vpc_pcx.peer_vpc.id }}</vpcId>
</accepterVpcInfo>
<status>
<code>initiating-request</code>
<message>Initiating request to {accepter ID}.</message>
</status>
<expirationTime>2014-02-18T14:37:25.000Z</expirationTime>
<tagSet/>
</vpcPeeringConnection>
</CreateVpcPeeringConnectionResponse>
"""
DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = """
<DescribeVpcPeeringConnectionsResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<vpcPeeringConnectionSet>
{% for vpc_pcx in vpc_pcxs %}
<item>
<vpcPeeringConnectionId>{{ vpc_pcx.id }}</vpcPeeringConnectionId>
<requesterVpcInfo>
<ownerId>777788889999</ownerId>
<vpcId>{{ vpc_pcx.vpc.id }}</vpcId>
<cidrBlock>{{ vpc_pcx.vpc.cidr_block }}</cidrBlock>
</requesterVpcInfo>
<accepterVpcInfo>
<ownerId>111122223333</ownerId>
<vpcId>{{ vpc_pcx.peer_vpc.id }}</vpcId>
</accepterVpcInfo>
<status>
<code>{{ vpc_pcx._status.code }}</code>
<message>{{ vpc_pcx._status.message }}</message>
</status>
<expirationTime>2014-02-17T16:00:50.000Z</expirationTime>
<tagSet/>
</item>
{% endfor %}
</vpcPeeringConnectionSet>
</DescribeVpcPeeringConnectionsResponse>
"""
DELETE_VPC_PEERING_CONNECTION_RESPONSE = """
<DeleteVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<return>true</return>
</DeleteVpcPeeringConnectionResponse>
"""
ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = """
<AcceptVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<vpcPeeringConnection>
<vpcPeeringConnectionId>{{ vpc_pcx.id }}</vpcPeeringConnectionId>
<requesterVpcInfo>
<ownerId>123456789012</ownerId>
<vpcId>{{ vpc_pcx.vpc.id }}</vpcId>
<cidrBlock>{{ vpc_pcx.vpc.cidr_block }}</cidrBlock>
</requesterVpcInfo>
<accepterVpcInfo>
<ownerId>777788889999</ownerId>
<vpcId>{{ vpc_pcx.peer_vpc.id }}</vpcId>
<cidrBlock>{{ vpc_pcx.peer_vpc.cidr_block }}</cidrBlock>
</accepterVpcInfo>
<status>
<code>{{ vpc_pcx._status.code }}</code>
<message>{{ vpc_pcx._status.message }}</message>
</status>
<tagSet/>
</vpcPeeringConnection>
</AcceptVpcPeeringConnectionResponse>
"""
REJECT_VPC_PEERING_CONNECTION_RESPONSE = """
<RejectVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<return>true</return>
</RejectVpcPeeringConnectionResponse>
"""

View File

@ -46,6 +46,10 @@ def random_vpc_id():
return random_id(prefix='vpc') return random_id(prefix='vpc')
def random_vpc_peering_connection_id():
return random_id(prefix='pcx')
def random_eip_association_id(): def random_eip_association_id():
return random_id(prefix='eipassoc') return random_id(prefix='eipassoc')

View File

@ -0,0 +1,82 @@
import boto
from boto.exception import EC2ResponseError
import sure # noqa
from moto import mock_ec2
from tests.helpers import requires_boto_gte
@requires_boto_gte("2.32.0")
@mock_ec2
def test_vpc_peering_connections():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc = conn.create_vpc("10.0.0.0/16")
peer_vpc = conn.create_vpc("11.0.0.0/16")
vpc_pcx = conn.create_vpc_peering_connection(vpc.id, peer_vpc.id)
vpc_pcx._status.code.should.equal('initiating-request')
return vpc_pcx
@requires_boto_gte("2.32.0")
@mock_ec2
def test_vpc_peering_connections_get_all():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc_pcx = test_vpc_peering_connections()
vpc_pcx._status.code.should.equal('initiating-request')
all_vpc_pcxs = conn.get_all_vpc_peering_connections()
all_vpc_pcxs.should.have.length_of(1)
all_vpc_pcxs[0]._status.code.should.equal('pending-acceptance')
@requires_boto_gte("2.32.0")
@mock_ec2
def test_vpc_peering_connections_accept():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc_pcx = test_vpc_peering_connections()
vpc_pcx = conn.accept_vpc_peering_connection(vpc_pcx.id)
vpc_pcx._status.code.should.equal('active')
conn.reject_vpc_peering_connection.when.called_with(
vpc_pcx.id).should.throw(EC2ResponseError)
all_vpc_pcxs = conn.get_all_vpc_peering_connections()
all_vpc_pcxs.should.have.length_of(1)
all_vpc_pcxs[0]._status.code.should.equal('active')
@requires_boto_gte("2.32.0")
@mock_ec2
def test_vpc_peering_connections_reject():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc_pcx = test_vpc_peering_connections()
verdict = conn.reject_vpc_peering_connection(vpc_pcx.id)
verdict.should.equal(True)
conn.accept_vpc_peering_connection.when.called_with(
vpc_pcx.id).should.throw(EC2ResponseError)
all_vpc_pcxs = conn.get_all_vpc_peering_connections()
all_vpc_pcxs.should.have.length_of(1)
all_vpc_pcxs[0]._status.code.should.equal('rejected')
@requires_boto_gte("2.32.1")
@mock_ec2
def test_vpc_peering_connections_delete():
conn = boto.connect_vpc('the_key', 'the_secret')
vpc_pcx = test_vpc_peering_connections()
verdict = vpc_pcx.delete()
verdict.should.equal(True)
all_vpc_pcxs = conn.get_all_vpc_peering_connections()
all_vpc_pcxs.should.have.length_of(0)
conn.delete_vpc_peering_connection.when.called_with(
"pcx-1234abcd").should.throw(EC2ResponseError)