Repository: getmoto/moto

Issue:
Cannot search instances by vpc-id
Hello,

Not sure if this is expected behaviour or not but currently searching for mock EC2 instances by VPC ID does not seem to work. Please see the test case here:

https://gist.github.com/jedops/6925ac2c3aefb2d9e4e13adc33156b05

Expected behaviour:
After running string is present: "found instance with id id-xxxxxxx"

Actual behaviour:
String is not present.




Discussion:
I'm facing a similar problem. Queries which filter by `vpc-id' work against AWS, but not against boto.
The example code passes for me against the latest version of moto. Can you confirm this is still an issue?
Still no instances found.
Can you give me some more details about your environment? Python version, OS, moto version? So that I can try to replicate.
Python version: 2.7.12
OS: Ubuntu 16.04
Linux kernel: 4.13.0-38-generic
moto version: 1.3.2
Ah, I see the issue now. With this particular test case, we aren't specifying the subnet/vpc explicitly, but relying on Amazon to pick it up based on the `NetworkInterfaces`. If someone can confirm that works with the real Amazon APIs, it should be an easy fix for us.
In the case that I encountered, I didn't specify the `NetworkInterfaces`. The case I encountered involved creating an auto-scaling group in a particular VPC (specifying subnet id), but then a filter by VPC not picking up the auto-scaling instances.
Any chance you can add some code to reproduce?
:zombie: revival post:

OS: Ubuntu 20.10
Python: 3.8.6
Go: 1.15.6
Moto: pulled c72670d536562ddfa9ac97cb064f08743e5f6139

Seeing some weird cross-contamination or something with the `vpc-id` filtering (or maybe it's just the way AWS works)

If this is too old/unrelated let me know and I'll open a new issue.

If I query instances against the CLI using the `vpc-id` as a filter:
```shell
aws --endpoint-url=http://localhost:5000 --region eu-west-1 ec2 describe-instances --filters Values=vpc-3afb5410 --query "Reservations[*].Instances[*].[InstanceId, InstanceType]"
```

I get a normal return with results and the filter works as expected:
```
i-161c2215840ed6928     c4.large
i-7b30b7f366b4366c3     c4.large
i-65ea23998a14fa95a     c4.large
```

When testing against the server from a separate application, Go in this case, using the following as my input to `ec2.DescribeInstances`:
```go
input := &ec2.DescribeInstancesInput{
    Filters: []ec2types.Filter{
        {
            Name:   aws.String("vpc-id"),
            Values: []string{*vpc.VpcId},
        },
    },
}
res, err := a.ec2Client.DescribeInstances(context.TODO(), input)
if err != nil {
    return errors.Wrap(err, "load cluster")
}
for _, reservation := range res.Reservations { //I then iterate over Reservations to get instance data
...    
```

moto spits out an error and I get an empty `Reservation` back:
```text
NotImplementedError: Filter dicts have not been implemented in Moto for 'subnet-id' yet. Feel free to open an issue at https://github.com/issues
```

So I even tried manually filtering by `subnet-id`:
```
aws --endpoint-url=http://localhost:5000 --region eu-west-1 ec2 describe-instances --filters Values=subnet-0b96875c --query "Reservations[*].Instances[*].[InstanceId, InstanceType]"
```

This returned fine:
```
i-161c2215840ed6928     c4.large
i-7b30b7f366b4366c3     c4.large
i-65ea23998a14fa95a     c4.large
```

I tried debugging the server at moto/ec2/models.py:1151 and watching the filters come in I see `{'vpc-id': ['vpc-3afb5410']}` come in as the first filter as expected, the reservations returned look like `[Reservation:r-346f8fc8, Reservation:r-00b0fc79, Reservation:r-112bc022]`, so far so good.

I move past the breakpoint and another return comes back with `{'subnet-id': ['subnet-0b96875c']}` as the filter value passed to `all_reservations`, then the reservations returned look like `[Reservation:r-346f8fc8, Reservation:r-00b0fc79, Reservation:r-112bc022]`, okay, same result.

Once the `Reservation` return comes back to the application though it's empty

Trying to pin down if this is my envrionment or why the filters are going down to `subnet-id`.

All the above code works fine against AWS in Go using `github.com/aws/aws-sdk-go-v2 v1.1.0`.

----

Full error output:
```python
Error on request:
Traceback (most recent call last):
  File "venv/lib/python3.8/site-packages/werkzeug/serving.py", line 323, in run_wsgi
    execute(self.server.app)
  File "venv/lib/python3.8/site-packages/werkzeug/serving.py", line 312, in execute
    application_iter = app(environ, start_response)
  File "moto/server.py", line 177, in __call__
    self.killReceived = True
  File "venv/lib/python3.8/site-packages/flask/app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "venv/lib/python3.8/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "venv/lib/python3.8/site-packages/flask_cors/extension.py", line 165, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "venv/lib/python3.8/site-packages/flask/app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "venv/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "venv/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "venv/lib/python3.8/site-packages/flask_cors/extension.py", line 165, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "venv/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "moto/core/utils.py", line 151, in __call__
    result = self.callback(request, request.url, {})
  File "moto/core/responses.py", line 202, in dispatch
    return cls()._dispatch(*args, **kwargs)
  File "moto/core/responses.py", line 312, in _dispatch
    return self.call_action()
  File "moto/core/responses.py", line 402, in call_action
    response = method()
  File "moto/ec2/responses/instances.py", line 28, in describe_instances
    reservations = self.ec2_backend.all_reservations(filters=filter_dict)
  File "moto/ec2/models.py", line 1152, in all_reservations
    reservations = filter_reservations(reservations, filters)
  File "moto/ec2/utils.py", line 418, in filter_reservations
    if passes_filter_dict(instance, filter_dict):
  File "moto/ec2/utils.py", line 397, in passes_filter_dict
    raise NotImplementedError(
NotImplementedError: Filter dicts have not been implemented in Moto for 'subnet-id' yet. Feel free to open an issue at https://github.com/issues
```
Hi @tony-dot-sh, as far as I can tell, you're not using the filters-argument correctly in the CLI command.
It should specify both Name and Values, and you're only specifying the values.
` --filters Values=vpc-3afb5410` --> ` --filters Name=vpc-id,Values=vpc-3afb5410`

Without the Name, the command will ignore the filters, which is why the result might look OK.

Running this example in Go returns an empty list for me, i.e. it correctly tries to filter on the unimplemented vpc-id attribute. No warnings whatsoever. Not sure where the subnet-id from your example comes from, either.

The full code that I used to reproduce:
https://gist.github.com/bblommers/7a80001183a659b5bd52b0a900a64939


Good :eyes:  @bblommers! Yeah, going deeper into this I see a few things on my end I was passing incorrectly.

This seems to be more about the `subnet-id` dict, and not the `vpc-id` in this issue.

Below should return results, but as specified in the error message, that dict hasn't been implemented.

```go
input := &ec2.DescribeInstancesInput{
    Filters: []ec2types.Filter{
        {
            Name:   aws.String("subnet-id"),
            Values: []string{*subnet.SubnetId},
        },
    },
}
out, err := client.DescribeInstances(context.TODO(), input)
...
```


Thanks!

Resolving patch:
diff --git a/moto/ec2/models.py b/moto/ec2/models.py
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -602,7 +602,6 @@ def __init__(self, ec2_backend, image_id, user_data, security_groups, **kwargs):
         self.instance_type = kwargs.get("instance_type", "m1.small")
         self.region_name = kwargs.get("region_name", "us-east-1")
         placement = kwargs.get("placement", None)
-        self.vpc_id = None
         self.subnet_id = kwargs.get("subnet_id")
         in_ec2_classic = not bool(self.subnet_id)
         self.key_name = kwargs.get("key_name")
@@ -646,7 +645,6 @@ def __init__(self, ec2_backend, image_id, user_data, security_groups, **kwargs):
 
         if self.subnet_id:
             subnet = ec2_backend.get_subnet(self.subnet_id)
-            self.vpc_id = subnet.vpc_id
             self._placement.zone = subnet.availability_zone
 
             if self.associate_public_ip is None:
@@ -666,6 +664,15 @@ def __init__(self, ec2_backend, image_id, user_data, security_groups, **kwargs):
             associate_public_ip=self.associate_public_ip,
         )
 
+    @property
+    def vpc_id(self):
+        if self.subnet_id:
+            subnet = self.ec2_backend.get_subnet(self.subnet_id)
+            return subnet.vpc_id
+        if self.nics and 0 in self.nics:
+            return self.nics[0].subnet.vpc_id
+        return None
+
     def __del__(self):
         try:
             subnet = self.ec2_backend.get_subnet(self.subnet_id)
diff --git a/moto/packages/boto/ec2/instance.py b/moto/packages/boto/ec2/instance.py
--- a/moto/packages/boto/ec2/instance.py
+++ b/moto/packages/boto/ec2/instance.py
@@ -178,7 +178,6 @@ def __init__(self, connection=None):
         self.spot_instance_request_id = None
         self.subnet_id = None
         self.lifecycle = None
-        self.vpc_id = None
         self.private_ip_address = None
         self.ip_address = None
         self.requester_id = None


Test patch:
diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py
--- a/tests/test_ec2/test_instances.py
+++ b/tests/test_ec2/test_instances.py
@@ -3107,6 +3107,30 @@ def test_describe_instances_dryrun():
     )
 
 
+@mock_ec2
+def test_describe_instances_filter_vpcid_via_networkinterface():
+    vpc_cidr_block = "10.26.0.0/16"
+    subnet_cidr_block = "10.26.1.0/24"
+    ec2 = boto3.resource("ec2", region_name="eu-west-1")
+    vpc = ec2.create_vpc(CidrBlock=vpc_cidr_block)
+    subnet = ec2.create_subnet(
+        VpcId=vpc.id, CidrBlock=subnet_cidr_block, AvailabilityZone="eu-west-1a"
+    )
+    my_interface = {
+        "SubnetId": subnet.id,
+        "DeviceIndex": 0,
+        "PrivateIpAddresses": [{"Primary": True, "PrivateIpAddress": "10.26.1.3"},],
+    }
+    instance = ec2.create_instances(
+        ImageId="myami", NetworkInterfaces=[my_interface], MinCount=1, MaxCount=1
+    )[0]
+
+    _filter = [{"Name": "vpc-id", "Values": [vpc.id,]}]
+    found = list(ec2.instances.filter(Filters=_filter))
+    found.should.have.length_of(1)
+    found.should.equal([instance])
+
+
 def retrieve_all_reservations(client, filters=[]):  # pylint: disable=W0102
     resp = client.describe_instances(Filters=filters)
     all_reservations = resp["Reservations"]