Repository: getmoto/moto (python)

Issue:
DynamoDB: Need methods to put/get/delete policy on table
Hello,

I need to do testing on policies associated with DynamoDB Tables (recent AWS [feature](https://aws.amazon.com/about-aws/whats-new/2024/03/amazon-dynamodb-resource-based-policies/?nc1=h_ls)).

So I create this issue to plan the implementation of:
- dynamodb:put_resource_policy
- dynamodb:get_resource_policy
- dynamodb:delete_resource_policy

Thanks.


Pull request description:
DynamoDB: Resource Policies
Fixes #7694 

Patch:
diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py
index 6d5c6bee5..08ce7c7d7 100644
--- a/moto/cloudformation/models.py
+++ b/moto/cloudformation/models.py
@@ -28,6 +28,7 @@ from .utils import (
     generate_stackset_arn,
     generate_stackset_id,
     get_stack_from_s3_url,
+    validate_create_change_set,
     validate_template_cfn_lint,
     yaml_tag_constructor,
 )
@@ -1007,6 +1008,7 @@ class CloudFormationBackend(BaseBackend):
         tags: Optional[Dict[str, str]] = None,
         role_arn: Optional[str] = None,
     ) -> Tuple[str, str]:
+        validate_create_change_set(change_set_name)
         if change_set_type == "UPDATE":
             for stack in self.stacks.values():
                 if stack.name == stack_name:
diff --git a/moto/cloudformation/utils.py b/moto/cloudformation/utils.py
index af47a4068..598f8dc7c 100644
--- a/moto/cloudformation/utils.py
+++ b/moto/cloudformation/utils.py
@@ -8,6 +8,8 @@ import yaml
 from moto.moto_api._internal import mock_random as random
 from moto.utilities.utils import get_partition
 
+from .exceptions import ValidationError
+
 
 def generate_stack_id(stack_name: str, region: str, account: str) -> str:
     random_id = random.uuid4()
@@ -134,3 +136,19 @@ def get_stack_from_s3_url(template_url: str, account_id: str, partition: str) ->
 
     key = s3_backends[account_id][partition].get_object(bucket_name, key_name)
     return key.value.decode("utf-8")  # type: ignore[union-attr]
+
+
+def validate_create_change_set(change_set_name: str) -> None:
+    if not (change_set_name and change_set_name[0].isalpha()):
+        raise ValidationError(f"Invalid change set name: {change_set_name}")
+
+    if not all(c.isalnum() or c == "-" for c in change_set_name):
+        raise ValidationError(f"Invalid change set name: {change_set_name}")
+
+    if len(change_set_name) > 128:
+        raise ValidationError(
+            f"Change set name exceeds 128 characters: {change_set_name}"
+        )
+
+    # Additional validations can be added here later
+    return
diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py
index 21ffc8125..ae7c94f62 100644
--- a/moto/dynamodb/exceptions.py
+++ b/moto/dynamodb/exceptions.py
@@ -395,3 +395,11 @@ class DeletionProtectedException(MockValidationException):
     def __init__(self, table_name: str):
         msg = f"1 validation error detected: Table '{table_name}' can't be deleted while DeletionProtectionEnabled is set to True"
         super().__init__(msg)
+
+
+class PolicyNotFoundException(DynamodbException):
+    error_type = ERROR_TYPE_PREFIX + "PolicyNotFoundException"
+
+    def __init__(self, message: str):
+        super().__init__(PolicyNotFoundException.error_type, message=message)
+        self.exception_msg = message
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
index e8ab2ce99..9a37d709f 100644
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -20,6 +20,7 @@ from moto.dynamodb.exceptions import (
     MockValidationException,
     MultipleTransactionsException,
     PointInTimeRecoveryUnavailable,
+    PolicyNotFoundException,
     ResourceInUseException,
     ResourceNotFoundException,
     SourceTableNotFoundException,
@@ -34,6 +35,7 @@ from moto.dynamodb.models.dynamo_type import DynamoType, Item
 from moto.dynamodb.models.table import (
     Backup,
     GlobalSecondaryIndex,
+    ResourcePolicy,
     RestoredPITTable,
     RestoredTable,
     Table,
@@ -56,6 +58,7 @@ class DynamoDBBackend(BaseBackend):
         self.backups: Dict[str, Backup] = OrderedDict()
         self.table_imports: Dict[str, TableImport] = {}
         self.table_exports: Dict[str, TableExport] = {}
+        self.resource_policies: Dict[str, ResourcePolicy] = {}
 
     @staticmethod
     def default_vpc_endpoint_service(
@@ -1042,5 +1045,56 @@ class DynamoDBBackend(BaseBackend):
                 exports.append(self.table_exports[export_arn])
         return exports
 
+    def put_resource_policy(
+        self, resource_arn: str, policy_doc: str, expected_revision_id: Optional[str]
+    ) -> ResourcePolicy:
+        table_name = resource_arn.split("/")[-1]
+        if table_name not in self.tables:
+            raise ResourceNotFoundException(table_name=table_name)
+        if current_policy := self.resource_policies.get(resource_arn):
+            if expected_revision_id == "NO_POLICY":
+                raise PolicyNotFoundException(
+                    f"Resource-based policy not found for the provided ResourceArn: Requested policy update expecting none to be present for table {table_name}, but a policy exists with revision id {current_policy.revision_id}."
+                )
+            if (
+                expected_revision_id
+                and current_policy.revision_id != expected_revision_id
+            ):
+                raise PolicyNotFoundException(
+                    f"Resource-based policy not found for the provided ResourceArn: Requested update for policy with revision id {expected_revision_id}, but the policy associated to target table {table_name} has revision id {current_policy.revision_id}."
+                )
+            if current_policy.policy_doc == policy_doc:
+                return current_policy
+        policy = ResourcePolicy(
+            resource_arn=resource_arn,
+            policy_doc=policy_doc,
+        )
+        self.resource_policies[resource_arn] = policy
+        return policy
+
+    def get_resource_policy(self, resource_arn: str) -> ResourcePolicy:
+        table_name = resource_arn.split("/")[-1]
+        if table_name not in self.tables:
+            raise ResourceNotFoundException(table_name=table_name)
+        if resource_arn not in self.resource_policies:
+            raise PolicyNotFoundException(
+                f"Resource-based policy not found for the provided ResourceArn: {resource_arn}"
+            )
+        return self.resource_policies[resource_arn]
+
+    def delete_resource_policy(
+        self, resource_arn: str, expected_revision_id: Optional[str]
+    ) -> None:
+        table_name = resource_arn.split("/")[-1]
+        if table_name not in self.tables or not (
+            policy := self.resource_policies.get(resource_arn)
+        ):
+            raise ResourceNotFoundException(table_name=table_name)
+        if expected_revision_id and policy.revision_id != expected_revision_id:
+            raise PolicyNotFoundException(
+                f"Resource-based policy not found for the provided ResourceArn: Requested update for policy with revision id {expected_revision_id}, but the policy associated to target table {table_name} has revision id {policy.revision_id}."
+            )
+        self.resource_policies.pop(resource_arn)
+
 
 dynamodb_backends = BackendDict(DynamoDBBackend, "dynamodb")
diff --git a/moto/dynamodb/models/table.py b/moto/dynamodb/models/table.py
index 8e8d9f217..bd074fe05 100644
--- a/moto/dynamodb/models/table.py
+++ b/moto/dynamodb/models/table.py
@@ -1198,3 +1198,10 @@ class RestoredPITTable(Table):
             "RestoreInProgress": False,
         }
         return result
+
+
+class ResourcePolicy:
+    def __init__(self, resource_arn: str, policy_doc: str):
+        self.resource_arn = resource_arn
+        self.policy_doc = policy_doc
+        self.revision_id = str(int(unix_time_millis()))
diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py
index 9d000ffbf..b2e97d822 100644
--- a/moto/dynamodb/responses.py
+++ b/moto/dynamodb/responses.py
@@ -1269,3 +1269,26 @@ class DynamoHandler(BaseResponse):
                 }
             )
         return json.dumps({"ExportSummaries": response})
+
+    def put_resource_policy(self) -> str:
+        policy = self.dynamodb_backend.put_resource_policy(
+            resource_arn=self.body.get("ResourceArn"),
+            policy_doc=self.body.get("Policy"),
+            expected_revision_id=self.body.get("ExpectedRevisionId"),
+        )
+        return json.dumps({"RevisionId": policy.revision_id})
+
+    def get_resource_policy(self) -> str:
+        policy = self.dynamodb_backend.get_resource_policy(
+            resource_arn=self.body.get("ResourceArn")
+        )
+        return json.dumps(
+            {"Policy": policy.policy_doc, "RevisionId": policy.revision_id}
+        )
+
+    def delete_resource_policy(self) -> str:
+        self.dynamodb_backend.delete_resource_policy(
+            resource_arn=self.body.get("ResourceArn"),
+            expected_revision_id=self.body.get("ExpectedRevisionId"),
+        )
+        return "{}"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-1.json
index db480dcb5..cd498e3a3 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-1.json
@@ -5131,6 +5131,46 @@
   "InstanceType": "r7i.xlarge",
   "Location": "apne1-az2"
  },
+ {
+  "InstanceType": "r7iz.12xlarge",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.16xlarge",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.2xlarge",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.32xlarge",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.4xlarge",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.8xlarge",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.large",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.metal-16xl",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.metal-32xl",
+  "Location": "apne1-az2"
+ },
+ {
+  "InstanceType": "r7iz.xlarge",
+  "Location": "apne1-az2"
+ },
  {
   "InstanceType": "t2.2xlarge",
   "Location": "apne1-az2"
@@ -5963,6 +6003,26 @@
   "InstanceType": "c7gn.xlarge",
   "Location": "apne1-az4"
  },
+ {
+  "InstanceType": "c7i-flex.2xlarge",
+  "Location": "apne1-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.4xlarge",
+  "Location": "apne1-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.8xlarge",
+  "Location": "apne1-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.large",
+  "Location": "apne1-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.xlarge",
+  "Location": "apne1-az4"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "apne1-az4"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-2.json
index 5a6f116e0..71dedfdda 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ap-northeast-2.json
@@ -1807,6 +1807,10 @@
   "InstanceType": "u-12tb1.112xlarge",
   "Location": "apne2-az1"
  },
+ {
+  "InstanceType": "u-24tb1.112xlarge",
+  "Location": "apne2-az1"
+ },
  {
   "InstanceType": "u-6tb1.112xlarge",
   "Location": "apne2-az1"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-1.json
index f26218770..f0910b081 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-1.json
@@ -5007,6 +5007,10 @@
   "InstanceType": "u-12tb1.112xlarge",
   "Location": "euc1-az2"
  },
+ {
+  "InstanceType": "u-18tb1.112xlarge",
+  "Location": "euc1-az2"
+ },
  {
   "InstanceType": "u-3tb1.56xlarge",
   "Location": "euc1-az2"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json
index a630f5361..42da48e25 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json
@@ -1447,6 +1447,10 @@
   "InstanceType": "u-6tb1.56xlarge",
   "Location": "euw3-az1"
  },
+ {
+  "InstanceType": "u-9tb1.112xlarge",
+  "Location": "euw3-az1"
+ },
  {
   "InstanceType": "x1.16xlarge",
   "Location": "euw3-az1"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-east-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-east-1.json
index 2fae83566..7a7ed40cf 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-east-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-east-1.json
@@ -571,6 +571,42 @@
   "InstanceType": "c7gd.xlarge",
   "Location": "use1-az1"
  },
+ {
+  "InstanceType": "c7gn.12xlarge",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.16xlarge",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.2xlarge",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.4xlarge",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.8xlarge",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.large",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.medium",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.metal",
+  "Location": "use1-az1"
+ },
+ {
+  "InstanceType": "c7gn.xlarge",
+  "Location": "use1-az1"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "use1-az1"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-west-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-west-2.json
index bfed43c4a..3b0847951 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-west-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/us-west-2.json
@@ -635,6 +635,26 @@
   "InstanceType": "c7gn.xlarge",
   "Location": "usw2-az1"
  },
+ {
+  "InstanceType": "c7i-flex.2xlarge",
+  "Location": "usw2-az1"
+ },
+ {
+  "InstanceType": "c7i-flex.4xlarge",
+  "Location": "usw2-az1"
+ },
+ {
+  "InstanceType": "c7i-flex.8xlarge",
+  "Location": "usw2-az1"
+ },
+ {
+  "InstanceType": "c7i-flex.large",
+  "Location": "usw2-az1"
+ },
+ {
+  "InstanceType": "c7i-flex.xlarge",
+  "Location": "usw2-az1"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "usw2-az1"
@@ -9731,6 +9751,26 @@
   "InstanceType": "c7gd.xlarge",
   "Location": "usw2-az4"
  },
+ {
+  "InstanceType": "c7i-flex.2xlarge",
+  "Location": "usw2-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.4xlarge",
+  "Location": "usw2-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.8xlarge",
+  "Location": "usw2-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.large",
+  "Location": "usw2-az4"
+ },
+ {
+  "InstanceType": "c7i-flex.xlarge",
+  "Location": "usw2-az4"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "usw2-az4"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-1.json
index 83c391cce..0f7c0129e 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-1.json
@@ -627,6 +627,26 @@
   "InstanceType": "c7gn.xlarge",
   "Location": "ap-northeast-1a"
  },
+ {
+  "InstanceType": "c7i-flex.2xlarge",
+  "Location": "ap-northeast-1a"
+ },
+ {
+  "InstanceType": "c7i-flex.4xlarge",
+  "Location": "ap-northeast-1a"
+ },
+ {
+  "InstanceType": "c7i-flex.8xlarge",
+  "Location": "ap-northeast-1a"
+ },
+ {
+  "InstanceType": "c7i-flex.large",
+  "Location": "ap-northeast-1a"
+ },
+ {
+  "InstanceType": "c7i-flex.xlarge",
+  "Location": "ap-northeast-1a"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "ap-northeast-1a"
@@ -8011,6 +8031,46 @@
   "InstanceType": "r7i.xlarge",
   "Location": "ap-northeast-1d"
  },
+ {
+  "InstanceType": "r7iz.12xlarge",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.16xlarge",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.2xlarge",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.32xlarge",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.4xlarge",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.8xlarge",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.large",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.metal-16xl",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.metal-32xl",
+  "Location": "ap-northeast-1d"
+ },
+ {
+  "InstanceType": "r7iz.xlarge",
+  "Location": "ap-northeast-1d"
+ },
  {
   "InstanceType": "t2.2xlarge",
   "Location": "ap-northeast-1d"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-2.json
index 44c0c3892..2ad63ec14 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/ap-northeast-2.json
@@ -1807,6 +1807,10 @@
   "InstanceType": "u-12tb1.112xlarge",
   "Location": "ap-northeast-2a"
  },
+ {
+  "InstanceType": "u-24tb1.112xlarge",
+  "Location": "ap-northeast-2a"
+ },
  {
   "InstanceType": "u-6tb1.112xlarge",
   "Location": "ap-northeast-2a"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-1.json
index 6f48996ff..984c96841 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-1.json
@@ -2371,6 +2371,10 @@
   "InstanceType": "u-12tb1.112xlarge",
   "Location": "eu-central-1a"
  },
+ {
+  "InstanceType": "u-18tb1.112xlarge",
+  "Location": "eu-central-1a"
+ },
  {
   "InstanceType": "u-3tb1.56xlarge",
   "Location": "eu-central-1a"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json
index 2466a2415..24fb02c4a 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json
@@ -1447,6 +1447,10 @@
   "InstanceType": "u-6tb1.56xlarge",
   "Location": "eu-west-3a"
  },
+ {
+  "InstanceType": "u-9tb1.112xlarge",
+  "Location": "eu-west-3a"
+ },
  {
   "InstanceType": "x1.16xlarge",
   "Location": "eu-west-3a"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/us-east-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone/us-east-1.json
index 88c7eb22c..e6533f0e6 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/us-east-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/us-east-1.json
@@ -9915,6 +9915,42 @@
   "InstanceType": "c7gd.xlarge",
   "Location": "us-east-1d"
  },
+ {
+  "InstanceType": "c7gn.12xlarge",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.16xlarge",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.2xlarge",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.4xlarge",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.8xlarge",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.large",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.medium",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.metal",
+  "Location": "us-east-1d"
+ },
+ {
+  "InstanceType": "c7gn.xlarge",
+  "Location": "us-east-1d"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "us-east-1d"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/us-west-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone/us-west-2.json
index bc3d312b7..ef905cd2e 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/us-west-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/us-west-2.json
@@ -3631,6 +3631,26 @@
   "InstanceType": "c7gn.xlarge",
   "Location": "us-west-2b"
  },
+ {
+  "InstanceType": "c7i-flex.2xlarge",
+  "Location": "us-west-2b"
+ },
+ {
+  "InstanceType": "c7i-flex.4xlarge",
+  "Location": "us-west-2b"
+ },
+ {
+  "InstanceType": "c7i-flex.8xlarge",
+  "Location": "us-west-2b"
+ },
+ {
+  "InstanceType": "c7i-flex.large",
+  "Location": "us-west-2b"
+ },
+ {
+  "InstanceType": "c7i-flex.xlarge",
+  "Location": "us-west-2b"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "us-west-2b"
@@ -9731,6 +9751,26 @@
   "InstanceType": "c7gd.xlarge",
   "Location": "us-west-2d"
  },
+ {
+  "InstanceType": "c7i-flex.2xlarge",
+  "Location": "us-west-2d"
+ },
+ {
+  "InstanceType": "c7i-flex.4xlarge",
+  "Location": "us-west-2d"
+ },
+ {
+  "InstanceType": "c7i-flex.8xlarge",
+  "Location": "us-west-2d"
+ },
+ {
+  "InstanceType": "c7i-flex.large",
+  "Location": "us-west-2d"
+ },
+ {
+  "InstanceType": "c7i-flex.xlarge",
+  "Location": "us-west-2d"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "us-west-2d"
diff --git a/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json b/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json
index baa50a5a4..410390e3c 100644
--- a/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json
+++ b/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json
@@ -1499,6 +1499,10 @@
   "InstanceType": "u-6tb1.56xlarge",
   "Location": "eu-west-3"
  },
+ {
+  "InstanceType": "u-9tb1.112xlarge",
+  "Location": "eu-west-3"
+ },
  {
   "InstanceType": "x1.16xlarge",
   "Location": "eu-west-3"
diff --git a/moto/ec2/resources/instance_type_offerings/region/us-west-2.json b/moto/ec2/resources/instance_type_offerings/region/us-west-2.json
index 59adabaf8..0a7e73dad 100644
--- a/moto/ec2/resources/instance_type_offerings/region/us-west-2.json
+++ b/moto/ec2/resources/instance_type_offerings/region/us-west-2.json
@@ -659,6 +659,26 @@
   "InstanceType": "c7gn.xlarge",
   "Location": "us-west-2"
  },
+ {
+  "InstanceType": "c7i-flex.2xlarge",
+  "Location": "us-west-2"
+ },
+ {
+  "InstanceType": "c7i-flex.4xlarge",
+  "Location": "us-west-2"
+ },
+ {
+  "InstanceType": "c7i-flex.8xlarge",
+  "Location": "us-west-2"
+ },
+ {
+  "InstanceType": "c7i-flex.large",
+  "Location": "us-west-2"
+ },
+ {
+  "InstanceType": "c7i-flex.xlarge",
+  "Location": "us-west-2"
+ },
  {
   "InstanceType": "c7i.12xlarge",
   "Location": "us-west-2"
diff --git a/moto/ec2/resources/instance_types.json b/moto/ec2/resources/instance_types.json
index 24599c7a2..ace9327ed 100644
--- a/moto/ec2/resources/instance_types.json
+++ b/moto/ec2/resources/instance_types.json
@@ -15900,7 +15900,12 @@
    "NetworkPerformance": "22.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -16053,7 +16058,12 @@
    "NetworkPerformance": "30 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -16219,7 +16229,12 @@
    "NetworkPerformance": "Up to 15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -16329,7 +16344,12 @@
    "NetworkPerformance": "Up to 15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -16447,7 +16467,12 @@
    "NetworkPerformance": "15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -16581,7 +16606,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -16685,7 +16715,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -16881,7 +16916,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -52861,7 +52901,12 @@
    "NetworkPerformance": "22.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -53014,7 +53059,12 @@
    "NetworkPerformance": "30 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -53180,7 +53230,12 @@
    "NetworkPerformance": "Up to 15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -53290,7 +53345,12 @@
    "NetworkPerformance": "Up to 15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -53408,7 +53468,12 @@
    "NetworkPerformance": "15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -53542,7 +53607,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -53646,7 +53716,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -53842,7 +53917,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -75348,7 +75428,12 @@
    "NetworkPerformance": "22.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -75501,7 +75586,12 @@
    "NetworkPerformance": "30 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -75667,7 +75757,12 @@
    "NetworkPerformance": "Up to 15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -75777,7 +75872,12 @@
    "NetworkPerformance": "Up to 15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -75895,7 +75995,12 @@
    "NetworkPerformance": "15 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -76029,7 +76134,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -76133,7 +76243,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [
@@ -76329,7 +76444,12 @@
    "NetworkPerformance": "Up to 12.5 Gigabit"
   },
   "NitroEnclavesSupport": "unsupported",
-  "NitroTpmSupport": "unsupported",
+  "NitroTpmInfo": {
+   "SupportedVersions": [
+    "2.0"
+   ]
+  },
+  "NitroTpmSupport": "supported",
   "PhcSupport": "unsupported",
   "PlacementGroupInfo": {
    "SupportedStrategies": [


Test patch:
diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
index d34e6e4d5..c6c5815d1 100644
--- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
@@ -1078,7 +1078,7 @@ def test_get_template_summary_for_stack_created_by_changeset_execution():
     conn.create_change_set(
         StackName="stack_from_changeset",
         TemplateBody=json.dumps(dummy_template3),
-        ChangeSetName="test_changeset",
+        ChangeSetName="test-changeset",
         ChangeSetType="CREATE",
     )
     with pytest.raises(
@@ -1086,7 +1086,7 @@ def test_get_template_summary_for_stack_created_by_changeset_execution():
         match="GetTemplateSummary cannot be called on REVIEW_IN_PROGRESS stacks",
     ):
         conn.get_template_summary(StackName="stack_from_changeset")
-    conn.execute_change_set(ChangeSetName="test_changeset")
+    conn.execute_change_set(ChangeSetName="test-changeset")
     result = conn.get_template_summary(StackName="stack_from_changeset")
     assert result["ResourceTypes"] == ["AWS::EC2::VPC"]
     assert result["Version"] == "2010-09-09"
@@ -2555,6 +2555,46 @@ def test_create_and_update_stack_with_unknown_resource():
             )
 
 
+@mock_aws
+def test_invalid_change_set_name_starting_char():
+    cf = boto3.client("cloudformation", region_name=REGION_NAME)
+    with pytest.raises(ClientError):
+        cf.create_change_set(
+            StackName=TEST_STACK_NAME,
+            ChangeSetName="1invalid-change-set-name",
+            TemplateBody=json.dumps(dummy_template),
+            Description="Test Change Set",
+            ChangeSetType="CREATE",
+        )
+
+
+@mock_aws
+def test_invalid_change_set_name_length():
+    cf = boto3.client("cloudformation", region_name=REGION_NAME)
+    long_name = "a" * 129  # Exceeds the 128 character limit
+    with pytest.raises(ClientError):
+        cf.create_change_set(
+            StackName=TEST_STACK_NAME,
+            ChangeSetName=long_name,
+            TemplateBody=json.dumps(dummy_template),
+            Description="Test Change Set",
+            ChangeSetType="CREATE",
+        )
+
+
+@mock_aws
+def test_invalid_change_set_name_special_chars():
+    cf = boto3.client("cloudformation", region_name=REGION_NAME)
+    with pytest.raises(ClientError):
+        cf.create_change_set(
+            StackName=TEST_STACK_NAME,
+            ChangeSetName="invalid@name",
+            TemplateBody=json.dumps(dummy_template),
+            Description="Test Change Set",
+            ChangeSetType="CREATE",
+        )
+
+
 def get_role_name():
     with mock_aws():
         iam = boto3.client("iam", region_name=REGION_NAME)
diff --git a/tests/test_dynamodb/test_dynamodb_resource_policy.py b/tests/test_dynamodb/test_dynamodb_resource_policy.py
new file mode 100644
index 000000000..3be60fe57
--- /dev/null
+++ b/tests/test_dynamodb/test_dynamodb_resource_policy.py
@@ -0,0 +1,300 @@
+import json
+from time import sleep
+
+import boto3
+import pytest
+from botocore.exceptions import ClientError
+
+from tests import allow_aws_request
+from tests.test_dynamodb import dynamodb_aws_verified
+
+
+def get_policy_doc(action):
+    sts = boto3.client("sts", "us-east-1")
+    account_id = sts.get_caller_identity()["Account"]
+
+    policy_doc = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Principal": {"AWS": account_id},
+                "Effect": "Allow",
+                "Action": [action],
+                "Resource": "*",
+            }
+        ],
+    }
+    return json.dumps(policy_doc)
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_put_resource_policy(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    revision_id = put_resource_policy(ddb, table_arn)
+
+    policy = ddb.get_resource_policy(ResourceArn=table_arn)
+
+    assert policy["RevisionId"] == revision_id
+    assert "dynamodb:*" in json.loads(policy["Policy"])["Statement"][0]["Action"]
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_put_resource_policy_multiple_times(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    revision_id1 = put_resource_policy(ddb, table_arn)
+
+    # Putting the same resource policy is a NO-OP
+    revision_id2 = put_resource_policy(ddb, table_arn)
+    assert revision_id1 == revision_id2
+
+    # Only a different policy resorts in a different revision
+    revision_id3 = put_resource_policy(ddb, table_arn, action="dynamodb:PutItem")
+    assert revision_id3 != revision_id2
+    # Because revision ID's are timestamps, they are comparable
+    assert revision_id3 > revision_id2
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_put_resource_policy_against_current_revision(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    revision_id1 = put_resource_policy(ddb, table_arn)
+
+    revision_id2 = put_resource_policy(
+        ddb, table_arn, action="dynamodb:GetItem", revision_id=revision_id1
+    )
+
+    policy = ddb.get_resource_policy(ResourceArn=table_arn)
+
+    assert policy["RevisionId"] == revision_id2
+    assert "dynamodb:GetItem" in json.loads(policy["Policy"])["Statement"][0]["Action"]
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_put_resource_policy_against_previous_revision(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    revision_id1 = put_resource_policy(ddb, table_arn)
+
+    revision_id2 = put_resource_policy(
+        ddb, table_arn, action="dynamodb:PutItem", revision_id=revision_id1
+    )
+
+    with pytest.raises(ClientError) as exc:
+        put_resource_policy(
+            ddb, table_arn, action="dynamodb:GetItem", revision_id=revision_id1
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "PolicyNotFoundException"
+    assert (
+        err["Message"]
+        == f"Resource-based policy not found for the provided ResourceArn: Requested update for policy with revision id {revision_id1}, but the policy associated to target table {table_name} has revision id {revision_id2}."
+    )
+
+    assert ddb.get_resource_policy(ResourceArn=table_arn)["RevisionId"] == revision_id2
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_put_resource_policy_against_norevision(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    revision_id = put_resource_policy(ddb, table_arn, revision_id="NO_POLICY")
+
+    policy = ddb.get_resource_policy(ResourceArn=table_arn)
+
+    assert policy["RevisionId"] == revision_id
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_put_resource_policy_against_norevision_when_revision_exists(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    revision_id = put_resource_policy(ddb, table_arn)
+
+    with pytest.raises(ClientError) as exc:
+        put_resource_policy(ddb, table_arn, revision_id="NO_POLICY")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "PolicyNotFoundException"
+    assert (
+        err["Message"]
+        == f"Resource-based policy not found for the provided ResourceArn: Requested policy update expecting none to be present for table {table_name}, but a policy exists with revision id {revision_id}."
+    )
+
+    assert ddb.get_resource_policy(ResourceArn=table_arn)["RevisionId"] == revision_id
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_get_resource_policy_from_resource_without(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    with pytest.raises(ClientError) as exc:
+        ddb.get_resource_policy(ResourceArn=table_arn)
+    err = exc.value.response["Error"]
+    assert err["Code"] == "PolicyNotFoundException"
+    assert (
+        err["Message"]
+        == f"Resource-based policy not found for the provided ResourceArn: {table_arn}"
+    )
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_put_resource_policy_unknown_resource(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    with pytest.raises(ClientError) as exc:
+        put_resource_policy(ddb, table_arn + "_")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ResourceNotFoundException"
+    assert (
+        err["Message"]
+        == f"Requested resource not found: Table: {table_name}_ not found"
+    )
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_get_resource_policy_from_unknown_resource(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    with pytest.raises(ClientError) as exc:
+        ddb.get_resource_policy(ResourceArn=table_arn + "unknown")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ResourceNotFoundException"
+    assert (
+        err["Message"]
+        == f"Requested resource not found: Table: {table_name}unknown not found"
+    )
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_delete_resource_policy_from_unknown_resource(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    with pytest.raises(ClientError) as exc:
+        ddb.delete_resource_policy(ResourceArn=table_arn + "unknown")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ResourceNotFoundException"
+    assert (
+        err["Message"]
+        == f"Requested resource not found: Table: {table_name}unknown not found"
+    )
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_delete_resource_policy_from_unknown_revision(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    revision = put_resource_policy(ddb, table_arn)
+
+    with pytest.raises(ClientError) as exc:
+        ddb.delete_resource_policy(ResourceArn=table_arn, ExpectedRevisionId="unknown")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "PolicyNotFoundException"
+    assert (
+        err["Message"]
+        == f"Resource-based policy not found for the provided ResourceArn: Requested update for policy with revision id unknown, but the policy associated to target table {table_name} has revision id {revision}."
+    )
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_delete_resource_policy_from_former_revision(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    old_revision_id = put_resource_policy(ddb, table_arn)
+
+    new_revision_id = put_resource_policy(ddb, table_arn, action="dynamodb:PutItem")
+
+    with pytest.raises(ClientError) as exc:
+        ddb.delete_resource_policy(
+            ResourceArn=table_arn, ExpectedRevisionId=old_revision_id
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "PolicyNotFoundException"
+    assert (
+        err["Message"]
+        == f"Resource-based policy not found for the provided ResourceArn: Requested update for policy with revision id {old_revision_id}, but the policy associated to target table {table_name} has revision id {new_revision_id}."
+    )
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_delete_resource_policy(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    put_resource_policy(ddb, table_arn)
+
+    ddb.delete_resource_policy(ResourceArn=table_arn)
+    if allow_aws_request():
+        # Wait for deletion to finish
+        sleep(15)
+
+    with pytest.raises(ClientError) as exc:
+        ddb.get_resource_policy(ResourceArn=table_arn)
+    assert exc.value.response["Error"]["Code"] == "PolicyNotFoundException"
+
+
+@dynamodb_aws_verified()
+@pytest.mark.aws_verified
+def test_delete_resource_policy_with_revision(table_name=None):
+    ddb = boto3.client("dynamodb", "us-east-1")
+
+    table_arn = ddb.describe_table(TableName=table_name)["Table"]["TableArn"]
+    old_revision_id = put_resource_policy(ddb, table_arn)
+
+    ddb.delete_resource_policy(
+        ResourceArn=table_arn, ExpectedRevisionId=old_revision_id
+    )
+    if allow_aws_request():
+        # Wait for deletion to finish
+        sleep(15)
+
+    with pytest.raises(ClientError) as exc:
+        ddb.get_resource_policy(ResourceArn=table_arn)
+    assert exc.value.response["Error"]["Code"] == "PolicyNotFoundException"
+
+
+def put_resource_policy(ddb, table_arn, action="dynamodb:*", revision_id=None):
+    kwargs = {}
+    if revision_id:
+        kwargs["ExpectedRevisionId"] = revision_id
+    policy_doc = get_policy_doc(action)
+    revision_id = ddb.put_resource_policy(
+        ResourceArn=table_arn, Policy=policy_doc, **kwargs
+    )["RevisionId"]
+    # Put Resource Policy is an async action
+    # Periodically poll to see when the policy is ready
+    for x in range(20):
+        try:
+            policy = ddb.get_resource_policy(ResourceArn=table_arn)
+            assert policy["RevisionId"] == revision_id
+            return revision_id
+        except (ClientError, AssertionError):
+            # Policy is not ready yet
+            sleep(1)
+    assert False, f"Revision {revision_id} wasn't ready in 20 seconds"