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
env:
matrix:
- BOTO_VERSION=2.32.1
- BOTO_VERSION=2.31.1
- BOTO_VERSION=2.25.0
- BOTO_VERSION=2.19.0

View File

@ -37,6 +37,22 @@ class InvalidVPCIdError(EC2ClientError):
.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):
def __init__(self, parameter_value):
super(InvalidParameterValueError, self).__init__(

View File

@ -15,7 +15,9 @@ from .exceptions import (
InvalidInternetGatewayIDError,
GatewayNotAttachedError,
ResourceAlreadyAssociatedError,
InvalidVPCIdError
InvalidVPCIdError,
InvalidVPCPeeringConnectionIdError,
InvalidVPCPeeringConnectionStateTransitionError
)
from .utils import (
random_ami_id,
@ -35,6 +37,7 @@ from .utils import (
random_subnet_id,
random_volume_id,
random_vpc_id,
random_vpc_peering_connection_id,
)
@ -715,6 +718,89 @@ class VPCBackend(object):
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):
def __init__(self, subnet_id, vpc_id, cidr_block):
self.id = subnet_id
@ -1176,6 +1262,7 @@ class DHCPOptionsSetBackend(object):
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
VPCPeeringConnectionBackend,
RouteTableBackend, RouteBackend, InternetGatewayBackend,
VPCGatewayAttachmentBackend, SpotRequestBackend,
ElasticAddressBackend, KeyPairBackend, DHCPOptionsSetBackend):

View File

@ -24,6 +24,7 @@ from .virtual_private_gateways import VirtualPrivateGateways
from .vm_export import VMExport
from .vm_import import VMImport
from .vpcs import VPCs
from .vpc_peering_connections import VPCPeeringConnections
from .vpn_connections import VPNConnections
from .windows import Windows
@ -55,6 +56,7 @@ class EC2Response(
VMExport,
VMImport,
VPCs,
VPCPeeringConnections,
VPNConnections,
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')
def random_vpc_peering_connection_id():
return random_id(prefix='pcx')
def random_eip_association_id():
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)