Repository: getmoto/moto
Pull request #6994 (2023-11-06 13:25:59)

RDS: detect rds instance / cluster engine mismatch
Resolves https://github.com/getmoto/moto/issues/6993


Patch:
diff --git a/moto/rds/exceptions.py b/moto/rds/exceptions.py
index 1ee93b20b..f6af07161 100644
--- a/moto/rds/exceptions.py
+++ b/moto/rds/exceptions.py
@@ -201,3 +201,11 @@ class InvalidDBInstanceIdentifier(InvalidParameterValue):
             "Identifiers must begin with a letter; must contain only ASCII letters, digits, and hyphens; "
             "and must not end with a hyphen or contain two consecutive hyphens."
         )
+
+
+class InvalidDBInstanceEngine(InvalidParameterCombination):
+    def __init__(self, instance_engine: str, cluster_engine: str) -> None:
+        super().__init__(
+            f"The engine name requested for your DB instance ({instance_engine}) doesn't match "
+            f"the engine name of your DB cluster ({cluster_engine})."
+        )
diff --git a/moto/rds/models.py b/moto/rds/models.py
index c430ab369..5e39aaef4 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -27,6 +27,7 @@ from .exceptions import (
     DBClusterParameterGroupNotFoundError,
     OptionGroupNotFoundFaultError,
     InvalidDBClusterStateFaultError,
+    InvalidDBInstanceEngine,
     InvalidDBInstanceStateError,
     SnapshotQuotaExceededError,
     DBSnapshotAlreadyExistsError,
@@ -1593,6 +1594,10 @@ class RDSBackend(BaseBackend):
                     raise InvalidParameterValue(
                         "Instances cannot be added to Aurora Serverless clusters."
                     )
+                if database.engine != cluster.engine:
+                    raise InvalidDBInstanceEngine(
+                        str(database.engine), str(cluster.engine)
+                    )
                 cluster.cluster_members.append(database_id)
         self.databases[database_id] = database
         return database


Test patch:
diff --git a/tests/test_rds/test_rds_clusters.py b/tests/test_rds/test_rds_clusters.py
index 3190bc8ae..2c261d498 100644
--- a/tests/test_rds/test_rds_clusters.py
+++ b/tests/test_rds/test_rds_clusters.py
@@ -7,10 +7,12 @@ import pytest
 from moto import mock_rds
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 
+RDS_REGION = "eu-north-1"
+
 
 @mock_rds
 def test_describe_db_cluster_initial():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     resp = client.describe_db_clusters()
     assert len(resp["DBClusters"]) == 0
@@ -18,7 +20,7 @@ def test_describe_db_cluster_initial():
 
 @mock_rds
 def test_describe_db_cluster_fails_for_non_existent_cluster():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     resp = client.describe_db_clusters()
     assert len(resp["DBClusters"]) == 0
@@ -31,7 +33,7 @@ def test_describe_db_cluster_fails_for_non_existent_cluster():
 
 @mock_rds
 def test_create_db_cluster_needs_master_username():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     with pytest.raises(ClientError) as ex:
         client.create_db_cluster(DBClusterIdentifier="cluster-id", Engine="aurora")
@@ -44,7 +46,7 @@ def test_create_db_cluster_needs_master_username():
 
 @mock_rds
 def test_create_db_cluster_needs_master_user_password():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     with pytest.raises(ClientError) as ex:
         client.create_db_cluster(
@@ -59,7 +61,7 @@ def test_create_db_cluster_needs_master_user_password():
 
 @mock_rds
 def test_create_db_cluster_needs_long_master_user_password():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     with pytest.raises(ClientError) as ex:
         client.create_db_cluster(
@@ -78,7 +80,7 @@ def test_create_db_cluster_needs_long_master_user_password():
 
 @mock_rds
 def test_modify_db_cluster_needs_long_master_user_password():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -102,7 +104,7 @@ def test_modify_db_cluster_needs_long_master_user_password():
 
 @mock_rds
 def test_modify_db_cluster_new_cluster_identifier():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
     old_id = "cluster-id"
     new_id = "new-cluster-id"
 
@@ -131,7 +133,7 @@ def test_modify_db_cluster_new_cluster_identifier():
 
 @mock_rds
 def test_create_db_cluster__verify_default_properties():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     resp = client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -198,7 +200,7 @@ def test_create_db_cluster__verify_default_properties():
 
 @mock_rds
 def test_create_db_cluster_additional_parameters():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     resp = client.create_db_cluster(
         AvailabilityZones=["eu-north-1b"],
@@ -253,7 +255,7 @@ def test_create_db_cluster_additional_parameters():
 
 @mock_rds
 def test_describe_db_cluster_after_creation():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id1",
@@ -286,7 +288,7 @@ def test_describe_db_cluster_after_creation():
 
 @mock_rds
 def test_delete_db_cluster():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -302,7 +304,7 @@ def test_delete_db_cluster():
 
 @mock_rds
 def test_delete_db_cluster_do_snapshot():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -323,7 +325,7 @@ def test_delete_db_cluster_do_snapshot():
 
 @mock_rds
 def test_delete_db_cluster_that_is_protected():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -341,7 +343,7 @@ def test_delete_db_cluster_that_is_protected():
 
 @mock_rds
 def test_delete_db_cluster_unknown_cluster():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     with pytest.raises(ClientError) as ex:
         client.delete_db_cluster(DBClusterIdentifier="cluster-unknown")
@@ -352,7 +354,7 @@ def test_delete_db_cluster_unknown_cluster():
 
 @mock_rds
 def test_start_db_cluster_unknown_cluster():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     with pytest.raises(ClientError) as ex:
         client.start_db_cluster(DBClusterIdentifier="cluster-unknown")
@@ -363,7 +365,7 @@ def test_start_db_cluster_unknown_cluster():
 
 @mock_rds
 def test_start_db_cluster_after_stopping():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -380,7 +382,7 @@ def test_start_db_cluster_after_stopping():
 
 @mock_rds
 def test_start_db_cluster_without_stopping():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -398,7 +400,7 @@ def test_start_db_cluster_without_stopping():
 
 @mock_rds
 def test_stop_db_cluster():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -419,7 +421,7 @@ def test_stop_db_cluster():
 
 @mock_rds
 def test_stop_db_cluster_already_stopped():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -439,7 +441,7 @@ def test_stop_db_cluster_already_stopped():
 
 @mock_rds
 def test_stop_db_cluster_unknown_cluster():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     with pytest.raises(ClientError) as ex:
         client.stop_db_cluster(DBClusterIdentifier="cluster-unknown")
@@ -807,7 +809,7 @@ def test_add_tags_to_cluster_snapshot():
 
 @mock_rds
 def test_create_serverless_db_cluster():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     resp = client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -831,7 +833,7 @@ def test_create_serverless_db_cluster():
 
 @mock_rds
 def test_create_db_cluster_with_enable_http_endpoint_invalid():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     resp = client.create_db_cluster(
         DBClusterIdentifier="cluster-id",
@@ -850,7 +852,7 @@ def test_create_db_cluster_with_enable_http_endpoint_invalid():
 
 @mock_rds
 def test_describe_db_clusters_filter_by_engine():
-    client = boto3.client("rds", region_name="eu-north-1")
+    client = boto3.client("rds", region_name=RDS_REGION)
 
     client.create_db_cluster(
         DBClusterIdentifier="id1",
@@ -922,3 +924,37 @@ def test_replicate_cluster():
     replica = us_west.describe_db_clusters()["DBClusters"][0]
     assert "ReplicationSourceIdentifier" not in replica
     assert replica["MultiAZ"] is False
+
+
+@mock_rds
+def test_createdb_instance_engine_mismatch_fail():
+    # Setup
+    client = boto3.client("rds", "us-east-1")
+    cluster_name = "test-cluster"
+    client.create_db_cluster(
+        DBClusterIdentifier=cluster_name,
+        Engine="aurora-postgresql",
+        EngineVersion="12.14",
+        MasterUsername="testuser",
+        MasterUserPassword="password",
+    )
+
+    # Execute
+
+    with pytest.raises(ClientError) as exc:
+        client.create_db_instance(
+            DBClusterIdentifier=cluster_name,
+            Engine="mysql",
+            EngineVersion="12.14",
+            DBInstanceIdentifier="test-instance",
+            DBInstanceClass="db.t4g.medium",
+        )
+
+    # Verify
+    err = exc.value.response["Error"]
+    assert err["Code"] == "InvalidParameterCombination"
+    assert (
+        err["Message"]
+        == "The engine name requested for your DB instance (mysql) doesn't match "
+        "the engine name of your DB cluster (aurora-postgresql)."
+    )
diff --git a/tests/test_rds/test_rds_clusters_with_instances.py b/tests/test_rds/test_rds_clusters_with_instances.py
index 707999327..09dec8c56 100644
--- a/tests/test_rds/test_rds_clusters_with_instances.py
+++ b/tests/test_rds/test_rds_clusters_with_instances.py
@@ -21,7 +21,7 @@ def test_add_instance_as_cluster_member():
         DBInstanceIdentifier="dbi",
         DBClusterIdentifier="dbci",
         DBInstanceClass="db.r5.large",
-        Engine="aurora-postgresql",
+        Engine="mysql",
     )
 
     cluster = client.describe_db_clusters()["DBClusters"][0]
@@ -53,7 +53,7 @@ def test_remove_instance_from_cluster():
         DBInstanceIdentifier="dbi",
         DBClusterIdentifier="dbci",
         DBInstanceClass="db.r5.large",
-        Engine="aurora-postgresql",
+        Engine="mysql",
     )
 
     client.delete_db_instance(