Repository: getmoto/moto

Issue:
PeerRegion not being respected when calling ec2.create_vpc_peering_connection
When creating peering connections, Moto doesn't seem to be respecting the `PeerRegion` argument.

For example, the following code, which is attempting to create a peering connection from London to Sydney…

```python
import moto
import boto3

with moto.mock_ec2():
    # Create VPC in Sydney
    ec2_au = boto3.resource('ec2', region_name='ap-southeast-2')
    vpc_au = ec2_au.create_vpc(CidrBlock='172.16.0.0/24')

    # Create VPC in London
    ec2_uk = boto3.resource('ec2', region_name='eu-west-2')
    vpc_uk = ec2_uk.create_vpc(CidrBlock='10.0.0.0/24')

    # Attempt to create a peering connection from London to Sydney.
    print(f'Creating connection from {vpc_uk.id} to {vpc_au.id}…')
    ec2_uk.create_vpc_peering_connection(
        VpcId=vpc_uk.id,
        PeerVpcId=vpc_au.id,
        PeerRegion='ap-southeast-2',
    )
    print('👍')
```

…results in a VPC not found error:

```
$ pipenv run python test.py 
Creating connection from vpc-c36bcc20 to vpc-7e1727c8…
Traceback (most recent call last):
  File "test.py", line 18, in <module>
    PeerRegion='ap-southeast-2',
  File "/Users/craiga/.ve/boto3-bug-mXS2lQPJ/lib/python3.7/site-packages/boto3/resources/factory.py", line 520, in do_action
    response = action(self, *args, **kwargs)
  File "/Users/craiga/.ve/boto3-bug-mXS2lQPJ/lib/python3.7/site-packages/boto3/resources/action.py", line 83, in __call__
    response = getattr(parent.meta.client, operation_name)(**params)
  File "/Users/craiga/.ve/boto3-bug-mXS2lQPJ/lib/python3.7/site-packages/botocore/client.py", line 314, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/craiga/.ve/boto3-bug-mXS2lQPJ/lib/python3.7/site-packages/botocore/client.py", line 612, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (InvalidVpcID.NotFound) when calling the CreateVpcPeeringConnection operation: VpcID vpc-7e1727c8 does not exist.
```

The same error occurs regardless of which regions are used. I originally found this issue setting up a connection from eu-west-2 to eu-west-1.

Creating a connection within Canada (or any other region) works just fine.

```python
import moto
import boto3

with moto.mock_ec2():
    # Create two VPCs in Canada
    ec2_ca = boto3.resource('ec2', region_name='ca-central-1')
    vpc_ca_1 = ec2_ca.create_vpc(CidrBlock='192.168.10.0/24')
    vpc_ca_2 = ec2_ca.create_vpc(CidrBlock='192.168.20.0/24')

    # Create a peering connection between the two.
    print(f'Creating connection from {vpc_ca_1.id} to {vpc_ca_2.id}…')
    ec2_ca.create_vpc_peering_connection(
        VpcId=vpc_ca_1.id,
        PeerVpcId=vpc_ca_2.id,
        PeerRegion='ca-central-1',
    )
    print('👍')
```

```
$ pipenv run python test.py 
Creating connection from vpc-112ca08c to vpc-cbd82b90…
👍
```

Using moto 1.3.5 and boto3 1.7.84 installed using pipenv.


Discussion:

Resolving patch:
diff --git a/moto/ec2/models.py b/moto/ec2/models.py
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -13,6 +13,7 @@
 import boto.ec2
 
 from collections import defaultdict
+import weakref
 from datetime import datetime
 from boto.ec2.instance import Instance as BotoInstance, Reservation
 from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
@@ -2115,10 +2116,20 @@ def get_cidr_block_association_set(self, ipv6=False):
 
 
 class VPCBackend(object):
+    __refs__ = defaultdict(list)
+
     def __init__(self):
         self.vpcs = {}
+        self.__refs__[self.__class__].append(weakref.ref(self))
         super(VPCBackend, self).__init__()
 
+    @classmethod
+    def get_instances(cls):
+        for inst_ref in cls.__refs__[cls]:
+            inst = inst_ref()
+            if inst is not None:
+                yield inst
+
     def create_vpc(self, cidr_block, instance_tenancy='default', amazon_provided_ipv6_cidr_block=False):
         vpc_id = random_vpc_id()
         vpc = VPC(self, vpc_id, cidr_block, len(self.vpcs) == 0, instance_tenancy, amazon_provided_ipv6_cidr_block)
@@ -2142,6 +2153,13 @@ def get_vpc(self, vpc_id):
             raise InvalidVPCIdError(vpc_id)
         return self.vpcs.get(vpc_id)
 
+    # get vpc by vpc id and aws region
+    def get_cross_vpc(self, vpc_id, peer_region):
+        for vpcs in self.get_instances():
+            if vpcs.region_name == peer_region:
+                match_vpc = vpcs.get_vpc(vpc_id)
+        return match_vpc
+
     def get_all_vpcs(self, vpc_ids=None, filters=None):
         matches = self.vpcs.values()
         if vpc_ids:
diff --git a/moto/ec2/responses/vpc_peering_connections.py b/moto/ec2/responses/vpc_peering_connections.py
--- a/moto/ec2/responses/vpc_peering_connections.py
+++ b/moto/ec2/responses/vpc_peering_connections.py
@@ -5,8 +5,12 @@
 class VPCPeeringConnections(BaseResponse):
 
     def create_vpc_peering_connection(self):
+        peer_region = self._get_param('PeerRegion')
+        if peer_region == self.region or peer_region is None:
+            peer_vpc = self.ec2_backend.get_vpc(self._get_param('PeerVpcId'))
+        else:
+            peer_vpc = self.ec2_backend.get_cross_vpc(self._get_param('PeerVpcId'), peer_region)
         vpc = self.ec2_backend.get_vpc(self._get_param('VpcId'))
-        peer_vpc = self.ec2_backend.get_vpc(self._get_param('PeerVpcId'))
         vpc_pcx = self.ec2_backend.create_vpc_peering_connection(vpc, peer_vpc)
         template = self.response_template(
             CREATE_VPC_PEERING_CONNECTION_RESPONSE)
@@ -41,26 +45,31 @@ def reject_vpc_peering_connection(self):
 
 
 CREATE_VPC_PEERING_CONNECTION_RESPONSE = """
-<CreateVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
-  <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
-  <vpcPeeringConnection>
-    <vpcPeeringConnectionId>{{ vpc_pcx.id }}</vpcPeeringConnectionId>
+<CreateVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-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>
+     <ownerId>777788889999</ownerId>
+     <vpcId>{{ vpc_pcx.vpc.id }}</vpcId>
+     <cidrBlock>{{ vpc_pcx.vpc.cidr_block }}</cidrBlock>
+     <peeringOptions>
+       <allowEgressFromLocalClassicLinkToRemoteVpc>false</allowEgressFromLocalClassicLinkToRemoteVpc>
+       <allowEgressFromLocalVpcToRemoteClassicLink>false</allowEgressFromLocalVpcToRemoteClassicLink>
+       <allowDnsResolutionFromRemoteVpc>false</allowDnsResolutionFromRemoteVpc>
+     </peeringOptions>
     </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>
+     <code>initiating-request</code>
+     <message>Initiating Request to {accepter ID}</message>
     </status>
     <expirationTime>2014-02-18T14:37:25.000Z</expirationTime>
     <tagSet/>
-  </vpcPeeringConnection>
+ </vpcPeeringConnection>
 </CreateVpcPeeringConnectionResponse>
 """
 


Test patch:
diff --git a/tests/test_ec2/test_vpc_peering.py b/tests/test_ec2/test_vpc_peering.py
--- a/tests/test_ec2/test_vpc_peering.py
+++ b/tests/test_ec2/test_vpc_peering.py
@@ -2,12 +2,15 @@
 # Ensure 'assert_raises' context manager support for Python 2.6
 import tests.backport_assert_raises
 from nose.tools import assert_raises
+from moto.ec2.exceptions import EC2ClientError
+from botocore.exceptions import ClientError
 
+import boto3
 import boto
 from boto.exception import EC2ResponseError
 import sure  # noqa
 
-from moto import mock_ec2_deprecated
+from moto import mock_ec2, mock_ec2_deprecated
 from tests.helpers import requires_boto_gte
 
 
@@ -93,3 +96,37 @@ def test_vpc_peering_connections_delete():
     cm.exception.code.should.equal('InvalidVpcPeeringConnectionId.NotFound')
     cm.exception.status.should.equal(400)
     cm.exception.request_id.should_not.be.none
+
+
+@mock_ec2
+def test_vpc_peering_connections_cross_region():
+    # create vpc in us-west-1 and ap-northeast-1
+    ec2_usw1 = boto3.resource('ec2', region_name='us-west-1')
+    vpc_usw1 = ec2_usw1.create_vpc(CidrBlock='10.90.0.0/16')
+    ec2_apn1 = boto3.resource('ec2', region_name='ap-northeast-1')
+    vpc_apn1 = ec2_apn1.create_vpc(CidrBlock='10.20.0.0/16')
+    # create peering
+    vpc_pcx = ec2_usw1.create_vpc_peering_connection(
+        VpcId=vpc_usw1.id,
+        PeerVpcId=vpc_apn1.id,
+        PeerRegion='ap-northeast-1',
+    )
+    vpc_pcx.status['Code'].should.equal('initiating-request')
+    vpc_pcx.requester_vpc.id.should.equal(vpc_usw1.id)
+    vpc_pcx.accepter_vpc.id.should.equal(vpc_apn1.id)
+
+
+@mock_ec2
+def test_vpc_peering_connections_cross_region_fail():
+    # create vpc in us-west-1 and ap-northeast-1
+    ec2_usw1 = boto3.resource('ec2', region_name='us-west-1')
+    vpc_usw1 = ec2_usw1.create_vpc(CidrBlock='10.90.0.0/16')
+    ec2_apn1 = boto3.resource('ec2', region_name='ap-northeast-1')
+    vpc_apn1 = ec2_apn1.create_vpc(CidrBlock='10.20.0.0/16')
+    # create peering wrong region with no vpc
+    with assert_raises(ClientError) as cm:
+        ec2_usw1.create_vpc_peering_connection(
+            VpcId=vpc_usw1.id,
+            PeerVpcId=vpc_apn1.id,
+            PeerRegion='ap-northeast-2')
+    cm.exception.response['Error']['Code'].should.equal('InvalidVpcID.NotFound')