Check for VPC existence for hostedzone
diff --git a/localstack/services/route53/provider.py b/localstack/services/route53/provider.py
index bfe2a2d7be0b8..d858fe7914ae8 100644
--- a/localstack/services/route53/provider.py
+++ b/localstack/services/route53/provider.py
@@ -2,6 +2,7 @@
 from typing import Optional
 
 import moto.route53.models as route53_models
+from botocore.exceptions import ClientError
 from moto.route53.models import route53_backends
 
 from localstack.aws.api import RequestContext
@@ -17,11 +18,13 @@
     HealthCheck,
     HealthCheckId,
     HostedZoneConfig,
+    InvalidVPCId,
     Nonce,
     NoSuchHealthCheck,
     ResourceId,
     Route53Api,
 )
+from localstack.aws.connect import connect_to
 from localstack.services.moto import call_moto
 from localstack.services.plugins import ServiceLifecycleHook
 
@@ -37,6 +40,24 @@ def create_hosted_zone(
         delegation_set_id: ResourceId = None,
         **kwargs,
     ) -> CreateHostedZoneResponse:
+        # private hosted zones cannot be created in a VPC that does not exist
+        # check that the VPC exists
+        if vpc:
+            vpc_id = vpc.get("VPCId")
+            vpc_region = vpc.get("VPCRegion")
+            if not vpc_id or not vpc_region:
+                raise Exception(
+                    "VPCId and VPCRegion must be specified when creating a private hosted zone"
+                )
+            try:
+                connect_to(
+                    aws_access_key_id=context.account_id, region_name=vpc_region
+                ).ec2.describe_vpcs(VpcIds=[vpc_id])
+            except ClientError as e:
+                if e.response.get("Error", {}).get("Code") == "InvalidVpcID.NotFound":
+                    raise InvalidVPCId("The VPC ID is invalid.", sender_fault=True) from e
+                raise e
+
         response = call_moto(context)
 
         # moto does not populate the VPC struct of the response if creating a private hosted zone
diff --git a/tests/aws/services/route53/test_route53.py b/tests/aws/services/route53/test_route53.py
index 160a772c6f5f7..d7a62b4eaa07a 100644
--- a/tests/aws/services/route53/test_route53.py
+++ b/tests/aws/services/route53/test_route53.py
@@ -49,7 +49,13 @@ def test_crud_health_check(self, aws_client):
         assert "NoSuchHealthCheck" in str(ctx.value)
 
     @markers.aws.validated
-    @markers.snapshot.skip_snapshot_verify(paths=["$..HostedZone.CallerReference"])
+    @markers.snapshot.skip_snapshot_verify(
+        paths=[
+            "$..HostedZone.CallerReference",
+            # moto does not return MaxItems for list_hosted_zones_by_vpc
+            "$..MaxItems",
+        ]
+    )
     def test_create_private_hosted_zone(
         self, region_name, aws_client, cleanups, snapshot, hosted_zone
     ):
@@ -76,6 +82,13 @@ def test_create_private_hosted_zone(
         response = aws_client.route53.get_hosted_zone(Id=zone_id)
         snapshot.match("get_hosted_zone", response)
 
+        response = aws_client.route53.list_hosted_zones_by_vpc(VPCId=vpc_id, VPCRegion=region_name)
+        snapshot.match("list_hosted_zones_by_vpc", response)
+
+        response = aws_client.route53.list_hosted_zones()
+        zones = [zone for zone in response["HostedZones"] if name in zone["Name"]]
+        snapshot.match("list_hosted_zones", zones)
+
     @markers.aws.unknown
     def test_associate_vpc_with_hosted_zone(
         self, cleanups, hosted_zone, aws_client, account_id, region_name
@@ -150,6 +163,16 @@ def test_associate_vpc_with_hosted_zone(
                 VPC={"VPCRegion": vpc_region, "VPCId": vpc2_id},
             )
 
+    @markers.aws.validated
+    def test_create_hosted_zone_in_non_existent_vpc(
+        self, aws_client, hosted_zone, snapshot, region_name
+    ):
+        vpc = {"VPCId": "non-existent", "VPCRegion": region_name}
+        with pytest.raises(aws_client.route53.exceptions.InvalidVPCId) as exc_info:
+            hosted_zone(Name=f"zone-{short_uid()}.com", VPC=vpc)
+
+        snapshot.match("failure-response", exc_info.value.response)
+
     @markers.aws.unknown
     def test_reusable_delegation_sets(self, aws_client):
         client = aws_client.route53
diff --git a/tests/aws/services/route53/test_route53.snapshot.json b/tests/aws/services/route53/test_route53.snapshot.json
index 181a7ab22f6c5..e82bcf05108b1 100644
--- a/tests/aws/services/route53/test_route53.snapshot.json
+++ b/tests/aws/services/route53/test_route53.snapshot.json
@@ -47,7 +47,7 @@
     }
   },
   "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_private_hosted_zone": {
-    "recorded-date": "15-12-2023, 15:20:07",
+    "recorded-date": "11-04-2024, 14:03:14",
     "recorded-content": {
       "create-hosted-zone-response": {
         "ChangeInfo": {
@@ -96,6 +96,50 @@
           "HTTPHeaders": {},
           "HTTPStatusCode": 200
         }
+      },
+      "list_hosted_zones_by_vpc": {
+        "HostedZoneSummaries": [
+          {
+            "HostedZoneId": "<zone-id:1>",
+            "Name": "<zone_name:1>",
+            "Owner": {
+              "OwningAccount": "111111111111"
+            }
+          }
+        ],
+        "MaxItems": "100",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 200
+        }
+      },
+      "list_hosted_zones": [
+        {
+          "Id": "/hostedzone/<zone-id:1>",
+          "Name": "<zone_name:1>",
+          "CallerReference": "<caller-reference:1>",
+          "Config": {
+            "Comment": "test",
+            "PrivateZone": true
+          },
+          "ResourceRecordSetCount": 2
+        }
+      ]
+    }
+  },
+  "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone_in_non_existent_vpc": {
+    "recorded-date": "10-04-2024, 15:47:22",
+    "recorded-content": {
+      "failure-response": {
+        "Error": {
+          "Code": "InvalidVPCId",
+          "Message": "The VPC ID is invalid.",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
       }
     }
   }
diff --git a/tests/aws/services/route53/test_route53.validation.json b/tests/aws/services/route53/test_route53.validation.json
index 60db1366275ea..dedc896d977d7 100644
--- a/tests/aws/services/route53/test_route53.validation.json
+++ b/tests/aws/services/route53/test_route53.validation.json
@@ -2,7 +2,10 @@
   "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone": {
     "last_validated_date": "2023-11-02T11:59:59+00:00"
   },
+  "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone_in_non_existent_vpc": {
+    "last_validated_date": "2024-04-10T15:47:22+00:00"
+  },
   "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_private_hosted_zone": {
-    "last_validated_date": "2023-12-15T14:20:07+00:00"
+    "last_validated_date": "2024-04-11T14:03:14+00:00"
   }
 }

<!-- Please refer to the contribution guidelines before raising a PR: https://github.com/localstack/localstack/blob/master/CONTRIBUTING.md -->

<!-- Why am I raising this PR? Add context such as related issues, PRs, or documentation. -->
## Motivation

If a hosted zone is to be created in a VPC, `moto` does not check for existence for that VPC whereas AWS does.

<!-- What notable changes does this PR make? -->
## Changes

* If the VPC details are specified by the caller of `create_hosted_zone`, look up the VPC and raise an error if it does not exist.
* Update `test_create_private_hosted_zone` to call `list_hosted_zones_by_vpc` and snapshot the result

<!-- The following sections are optional, but can be useful! 

## Testing

Description of how to test the changes

## TODO

What's left to do:

- [ ] ...
- [ ] ...

-->

