Repository: getmoto/moto
Pull request #7623 (2024-04-23 07:59:09)

Panorama: Add more features about nodes

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7623?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.20%. Comparing base [(`9aef694`)](https://app.codecov.io/gh/getmoto/moto/commit/9aef6944da6a6f1b84be99cefb34f1724e6ca58f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`383ce73`)](https://app.codecov.io/gh/getmoto/moto/pull/7623?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 198 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7623      +/-   ##
==========================================
- Coverage   95.88%   94.20%   -1.68%     
==========================================
  Files         843     1072     +229     
  Lines       82578    92277    +9699     
==========================================
+ Hits        79178    86929    +7751     
- Misses       3400     5348    +1948     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7623/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/7623/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `29.47% <24.48%> (-3.06%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7623/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.17% <100.00%> (-1.68%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/7623?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96ed897f0..fa08930b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,63 @@
 Moto Changelog
 ==============
 
+5.0.6
+-----
+Docker Digest for 5.0.6: _sha256:da919d3c1db07b378c413ed00a6c1c3e32ce1943a13671658c4db0f55dd49e42_
+
+    New Services:
+        * Bedrock:
+            * create_model_customization_job()
+            * delete_custom_model()
+            * delete_model_invocation_logging_configuration()
+            * get_custom_model()
+            * get_model_customization_job()
+            * get_model_invocation_logging_configuration()
+            * list_custom_models()
+            * list_model_customization_jobs()
+            * list_tags_for_resource()
+            * put_model_invocation_logging_configuration()
+            * stop_model_customization_job()
+            * tag_resource()
+            * untag_resource()
+
+        * BedrockAgent:
+            * create_agent()
+            * create_knowledge_base()
+            * delete_agent()
+            * delete_knowledge_base()
+            * get_agent()
+            * get_knowledge_base()
+            * list_agents()
+            * list_knowledge_bases()
+            * list_tags_for_resource()
+            * tag_resource()
+            * untag_resource()
+
+    New Methods:
+        * EC2:
+            * describe_addresses_attributes()
+
+        * Rekognition:
+            * detect_custom_labels()
+
+        * Sagemaker:
+            * update_trial_component()
+
+    Miscellaneous:
+        * CloudFront: update_distribution() now supports the CacheBehaviours-parameter
+        * DynamoDB: query() now acts correctly when paginating GSI tables without range keys
+        * EC2: RouteTables can now have multiple propagations
+        * EC2: describe_instances() now now filter by product-code and product-code.type
+        * EC2: describe_security_group_rules() now validates the format of the incoming security group id's
+        * ECS: update_service() now supports the loadBalancers-parameter
+        * Logs: describe_metric_filter() now has the correct validation for metricNamespaces
+        * IOT: search_index() now supports thingTypeName
+        * SFN: send_task_failure()/send_task_success() now work correctly when using the Parser
+        * SNS: subscribe() now throws an exception if the endpoint doesn't exist
+        * SQS: Improved queue name validation
+
+
 5.0.5
 -----
 Docker Digest for 5.0.5: _sha256:b95cf0d65557475f29e7256938028eef352e23acafe8e07c071cd58b67c44708_
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index ee6d58a46..0796f2bf0 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -704,6 +704,78 @@
 - [X] update_scheduling_policy
 </details>
 
+## bedrock
+<details>
+<summary>65% implemented</summary>
+
+- [X] create_model_customization_job
+- [ ] create_provisioned_model_throughput
+- [X] delete_custom_model
+- [X] delete_model_invocation_logging_configuration
+- [ ] delete_provisioned_model_throughput
+- [X] get_custom_model
+- [ ] get_foundation_model
+- [X] get_model_customization_job
+- [X] get_model_invocation_logging_configuration
+- [ ] get_provisioned_model_throughput
+- [X] list_custom_models
+- [ ] list_foundation_models
+- [X] list_model_customization_jobs
+- [ ] list_provisioned_model_throughputs
+- [X] list_tags_for_resource
+- [X] put_model_invocation_logging_configuration
+- [X] stop_model_customization_job
+- [X] tag_resource
+- [X] untag_resource
+- [ ] update_provisioned_model_throughput
+</details>
+
+## bedrock-agent
+<details>
+<summary>27% implemented</summary>
+
+- [ ] associate_agent_knowledge_base
+- [X] create_agent
+- [ ] create_agent_action_group
+- [ ] create_agent_alias
+- [ ] create_data_source
+- [X] create_knowledge_base
+- [X] delete_agent
+- [ ] delete_agent_action_group
+- [ ] delete_agent_alias
+- [ ] delete_agent_version
+- [ ] delete_data_source
+- [X] delete_knowledge_base
+- [ ] disassociate_agent_knowledge_base
+- [X] get_agent
+- [ ] get_agent_action_group
+- [ ] get_agent_alias
+- [ ] get_agent_knowledge_base
+- [ ] get_agent_version
+- [ ] get_data_source
+- [ ] get_ingestion_job
+- [X] get_knowledge_base
+- [ ] list_agent_action_groups
+- [ ] list_agent_aliases
+- [ ] list_agent_knowledge_bases
+- [ ] list_agent_versions
+- [X] list_agents
+- [ ] list_data_sources
+- [ ] list_ingestion_jobs
+- [X] list_knowledge_bases
+- [X] list_tags_for_resource
+- [ ] prepare_agent
+- [ ] start_ingestion_job
+- [X] tag_resource
+- [X] untag_resource
+- [ ] update_agent
+- [ ] update_agent_action_group
+- [ ] update_agent_alias
+- [ ] update_agent_knowledge_base
+- [ ] update_data_source
+- [ ] update_knowledge_base
+</details>
+
 ## budgets
 <details>
 <summary>30% implemented</summary>
@@ -735,7 +807,7 @@
 
 ## ce
 <details>
-<summary>20% implemented</summary>
+<summary>19% implemented</summary>
 
 - [ ] create_anomaly_monitor
 - [ ] create_anomaly_subscription
@@ -764,11 +836,13 @@
 - [ ] get_savings_plans_utilization_details
 - [ ] get_tags
 - [ ] get_usage_forecast
+- [ ] list_cost_allocation_tag_backfill_history
 - [ ] list_cost_allocation_tags
 - [ ] list_cost_category_definitions
 - [ ] list_savings_plans_purchase_recommendation_generation
 - [X] list_tags_for_resource
 - [ ] provide_anomaly_feedback
+- [ ] start_cost_allocation_tag_backfill
 - [ ] start_savings_plans_purchase_recommendation_generation
 - [X] tag_resource
 - [X] untag_resource
@@ -838,6 +912,7 @@
 - [ ] list_stack_instance_resource_drifts
 - [X] list_stack_instances
 - [X] list_stack_resources
+- [ ] list_stack_set_auto_deployment_targets
 - [X] list_stack_set_operation_results
 - [X] list_stack_set_operations
 - [X] list_stack_sets
@@ -1949,7 +2024,7 @@
 
 ## dynamodb
 <details>
-<summary>59% implemented</summary>
+<summary>56% implemented</summary>
 
 - [X] batch_execute_statement
 - [X] batch_get_item
@@ -1959,6 +2034,7 @@
 - [X] create_table
 - [X] delete_backup
 - [X] delete_item
+- [ ] delete_resource_policy
 - [X] delete_table
 - [X] describe_backup
 - [X] describe_continuous_backups
@@ -1979,6 +2055,7 @@
 - [X] execute_transaction
 - [ ] export_table_to_point_in_time
 - [X] get_item
+- [ ] get_resource_policy
 - [ ] import_table
 - [X] list_backups
 - [ ] list_contributor_insights
@@ -1988,6 +2065,7 @@
 - [X] list_tables
 - [X] list_tags_of_resource
 - [X] put_item
+- [ ] put_resource_policy
 - [X] query
 - [X] restore_table_from_backup
 - [X] restore_table_to_point_in_time
@@ -2255,7 +2333,7 @@
 - [ ] describe_account_attributes
 - [ ] describe_address_transfers
 - [X] describe_addresses
-- [ ] describe_addresses_attribute
+- [X] describe_addresses_attribute
 - [ ] describe_aggregate_id_format
 - [X] describe_availability_zones
 - [ ] describe_aws_network_performance_metric_subscriptions
@@ -2325,6 +2403,7 @@
 - [ ] describe_local_gateway_virtual_interfaces
 - [ ] describe_local_gateways
 - [ ] describe_locked_snapshots
+- [ ] describe_mac_hosts
 - [X] describe_managed_prefix_lists
 - [ ] describe_moving_addresses
 - [X] describe_nat_gateways
@@ -2415,6 +2494,7 @@
 - [ ] disable_image
 - [ ] disable_image_block_public_access
 - [ ] disable_image_deprecation
+- [ ] disable_image_deregistration_protection
 - [ ] disable_ipam_organization_admin_account
 - [ ] disable_serial_console_access
 - [ ] disable_snapshot_block_public_access
@@ -2445,6 +2525,7 @@
 - [ ] enable_image
 - [ ] enable_image_block_public_access
 - [ ] enable_image_deprecation
+- [ ] enable_image_deregistration_protection
 - [ ] enable_ipam_organization_admin_account
 - [ ] enable_reachability_analyzer_organization_sharing
 - [ ] enable_serial_console_access
@@ -2472,6 +2553,7 @@
 - [ ] get_groups_for_capacity_reservation
 - [ ] get_host_reservation_purchase_preview
 - [ ] get_image_block_public_access_state
+- [ ] get_instance_metadata_defaults
 - [ ] get_instance_types_from_instance_requirements
 - [ ] get_instance_uefi_data
 - [ ] get_ipam_address_history
@@ -2533,6 +2615,7 @@
 - [ ] modify_instance_event_start_time
 - [ ] modify_instance_event_window
 - [ ] modify_instance_maintenance_options
+- [ ] modify_instance_metadata_defaults
 - [ ] modify_instance_metadata_options
 - [ ] modify_instance_placement
 - [ ] modify_ipam
@@ -4514,7 +4597,7 @@
 
 ## kms
 <details>
-<summary>60% implemented</summary>
+<summary>57% implemented</summary>
 
 - [X] cancel_key_deletion
 - [ ] connect_custom_key_store
@@ -4548,6 +4631,7 @@
 - [ ] list_aliases
 - [X] list_grants
 - [ ] list_key_policies
+- [ ] list_key_rotations
 - [X] list_keys
 - [X] list_resource_tags
 - [X] list_retirable_grants
@@ -4556,6 +4640,7 @@
 - [X] replicate_key
 - [X] retire_grant
 - [X] revoke_grant
+- [ ] rotate_key_on_demand
 - [X] schedule_key_deletion
 - [X] sign
 - [X] tag_resource
@@ -4630,7 +4715,7 @@
 
 ## lambda
 <details>
-<summary>62% implemented</summary>
+<summary>65% implemented</summary>
 
 - [ ] add_layer_version_permission
 - [X] add_permission
@@ -4651,10 +4736,10 @@
 - [ ] delete_provisioned_concurrency_config
 - [ ] get_account_settings
 - [X] get_alias
-- [X] get_code_signing_config
+- [ ] get_code_signing_config
 - [X] get_event_source_mapping
 - [X] get_function
-- [ ] get_function_code_signing_config
+- [X] get_function_code_signing_config
 - [X] get_function_concurrency
 - [ ] get_function_configuration
 - [X] get_function_event_invoke_config
@@ -4675,13 +4760,13 @@
 - [ ] list_function_url_configs
 - [X] list_functions
 - [ ] list_functions_by_code_signing_config
-- [ ] list_layer_versions
+- [X] list_layer_versions
 - [X] list_layers
 - [ ] list_provisioned_concurrency_configs
 - [X] list_tags
 - [X] list_versions_by_function
 - [X] publish_layer_version
-- [ ] publish_version
+- [X] publish_version
 - [ ] put_function_code_signing_config
 - [X] put_function_concurrency
 - [X] put_function_event_invoke_config
@@ -4872,7 +4957,7 @@
 
 ## medialive
 <details>
-<summary>18% implemented</summary>
+<summary>13% implemented</summary>
 
 - [ ] accept_input_device_transfer
 - [ ] batch_delete
@@ -4882,19 +4967,29 @@
 - [ ] cancel_input_device_transfer
 - [ ] claim_device
 - [X] create_channel
+- [ ] create_cloud_watch_alarm_template
+- [ ] create_cloud_watch_alarm_template_group
+- [ ] create_event_bridge_rule_template
+- [ ] create_event_bridge_rule_template_group
 - [X] create_input
 - [ ] create_input_security_group
 - [ ] create_multiplex
 - [ ] create_multiplex_program
 - [ ] create_partner_input
+- [ ] create_signal_map
 - [ ] create_tags
 - [X] delete_channel
+- [ ] delete_cloud_watch_alarm_template
+- [ ] delete_cloud_watch_alarm_template_group
+- [ ] delete_event_bridge_rule_template
+- [ ] delete_event_bridge_rule_template_group
 - [X] delete_input
 - [ ] delete_input_security_group
 - [ ] delete_multiplex
 - [ ] delete_multiplex_program
 - [ ] delete_reservation
 - [ ] delete_schedule
+- [ ] delete_signal_map
 - [ ] delete_tags
 - [ ] describe_account_configuration
 - [X] describe_channel
@@ -4908,7 +5003,16 @@
 - [ ] describe_reservation
 - [ ] describe_schedule
 - [ ] describe_thumbnails
+- [ ] get_cloud_watch_alarm_template
+- [ ] get_cloud_watch_alarm_template_group
+- [ ] get_event_bridge_rule_template
+- [ ] get_event_bridge_rule_template_group
+- [ ] get_signal_map
 - [X] list_channels
+- [ ] list_cloud_watch_alarm_template_groups
+- [ ] list_cloud_watch_alarm_templates
+- [ ] list_event_bridge_rule_template_groups
+- [ ] list_event_bridge_rule_templates
 - [ ] list_input_device_transfers
 - [ ] list_input_devices
 - [ ] list_input_security_groups
@@ -4917,15 +5021,19 @@
 - [ ] list_multiplexes
 - [ ] list_offerings
 - [ ] list_reservations
+- [ ] list_signal_maps
 - [ ] list_tags_for_resource
 - [ ] purchase_offering
 - [ ] reboot_input_device
 - [ ] reject_input_device_transfer
 - [ ] restart_channel_pipelines
 - [X] start_channel
+- [ ] start_delete_monitor_deployment
 - [ ] start_input_device
 - [ ] start_input_device_maintenance_window
+- [ ] start_monitor_deployment
 - [ ] start_multiplex
+- [ ] start_update_signal_map
 - [X] stop_channel
 - [ ] stop_input_device
 - [ ] stop_multiplex
@@ -4933,6 +5041,10 @@
 - [ ] update_account_configuration
 - [X] update_channel
 - [ ] update_channel_class
+- [ ] update_cloud_watch_alarm_template
+- [ ] update_cloud_watch_alarm_template_group
+- [ ] update_event_bridge_rule_template
+- [ ] update_event_bridge_rule_template_group
 - [X] update_input
 - [ ] update_input_device
 - [ ] update_input_security_group
@@ -5330,11 +5442,11 @@
 
 ## panorama
 <details>
-<summary>14% implemented</summary>
+<summary>23% implemented</summary>
 
 - [ ] create_application_instance
 - [ ] create_job_for_devices
-- [ ] create_node_from_template_job
+- [X] create_node_from_template_job
 - [ ] create_package
 - [ ] create_package_import_job
 - [X] delete_device
@@ -5345,7 +5457,7 @@
 - [X] describe_device
 - [ ] describe_device_job
 - [ ] describe_node
-- [ ] describe_node_from_template_job
+- [X] describe_node_from_template_job
 - [ ] describe_package
 - [ ] describe_package_import_job
 - [ ] describe_package_version
@@ -5355,7 +5467,7 @@
 - [X] list_devices
 - [ ] list_devices_jobs
 - [ ] list_node_from_template_jobs
-- [ ] list_nodes
+- [X] list_nodes
 - [ ] list_package_import_jobs
 - [ ] list_packages
 - [ ] list_tags_for_resource
@@ -5803,7 +5915,7 @@
 
 ## rds
 <details>
-<summary>39% implemented</summary>
+<summary>38% implemented</summary>
 
 - [ ] add_role_to_db_cluster
 - [ ] add_role_to_db_instance
@@ -5931,6 +6043,7 @@
 - [X] modify_db_subnet_group
 - [ ] modify_event_subscription
 - [ ] modify_global_cluster
+- [ ] modify_integration
 - [X] modify_option_group
 - [ ] modify_tenant_database
 - [X] promote_read_replica
@@ -6137,7 +6250,7 @@
 
 ## rekognition
 <details>
-<summary>9% implemented</summary>
+<summary>10% implemented</summary>
 
 - [ ] associate_faces
 - [X] compare_faces
@@ -7092,7 +7205,7 @@
 - [ ] update_space
 - [ ] update_training_job
 - [ ] update_trial
-- [ ] update_trial_component
+- [X] update_trial_component
 - [ ] update_user_profile
 - [ ] update_workforce
 - [ ] update_workteam
@@ -8017,13 +8130,15 @@
 
 ## workspaces
 <details>
-<summary>20% implemented</summary>
+<summary>18% implemented</summary>
 
+- [ ] accept_account_link_invitation
 - [ ] associate_connection_alias
 - [ ] associate_ip_groups
 - [ ] associate_workspace_application
 - [ ] authorize_ip_rules
 - [ ] copy_workspace_image
+- [ ] create_account_link_invitation
 - [ ] create_connect_client_add_in
 - [ ] create_connection_alias
 - [ ] create_ip_group
@@ -8033,6 +8148,7 @@
 - [ ] create_workspace_bundle
 - [X] create_workspace_image
 - [X] create_workspaces
+- [ ] delete_account_link_invitation
 - [ ] delete_client_branding
 - [ ] delete_connect_client_add_in
 - [ ] delete_connection_alias
@@ -8066,8 +8182,10 @@
 - [ ] disassociate_connection_alias
 - [ ] disassociate_ip_groups
 - [ ] disassociate_workspace_application
+- [ ] get_account_link
 - [ ] import_client_branding
 - [ ] import_workspace_image
+- [ ] list_account_links
 - [ ] list_available_management_cidr_ranges
 - [ ] migrate_workspace
 - [ ] modify_account
@@ -8082,6 +8200,7 @@
 - [ ] reboot_workspaces
 - [ ] rebuild_workspaces
 - [X] register_workspace_directory
+- [ ] reject_account_link_invitation
 - [ ] restore_workspace
 - [ ] revoke_ip_rules
 - [ ] start_workspaces
@@ -8120,8 +8239,6 @@
 - backup-gateway
 - backupstorage
 - bcm-data-exports
-- bedrock
-- bedrock-agent
 - bedrock-agent-runtime
 - bedrock-runtime
 - billingconductor
@@ -8146,6 +8263,7 @@
 - cloudtrail-data
 - codeartifact
 - codecatalyst
+- codeconnections
 - codedeploy
 - codeguru-reviewer
 - codeguru-security
@@ -8161,12 +8279,14 @@
 - connectcampaigns
 - connectcases
 - connectparticipant
+- controlcatalog
 - controltower
 - cost-optimization-hub
 - cur
 - customer-profiles
 - dataexchange
 - datazone
+- deadline
 - detective
 - devicefarm
 - devops-guru
@@ -8203,7 +8323,6 @@
 - inspector-scan
 - internetmonitor
 - iot-jobs-data
-- iot-roborunner
 - iot1click-devices
 - iot1click-projects
 - iotanalytics
@@ -8303,6 +8422,7 @@
 - route53-recovery-cluster
 - route53-recovery-control-config
 - route53-recovery-readiness
+- route53profiles
 - rum
 - s3outposts
 - sagemaker-a2i-runtime
@@ -8332,6 +8452,7 @@
 - supplychain
 - support-app
 - synthetics
+- timestream-influxdb
 - timestream-query
 - tnb
 - transfer
diff --git a/docs/docs/services/bedrock-agent.rst b/docs/docs/services/bedrock-agent.rst
new file mode 100644
index 000000000..dbf3f6b31
--- /dev/null
+++ b/docs/docs/services/bedrock-agent.rst
@@ -0,0 +1,59 @@
+.. _implementedservice_bedrock-agent:
+
+.. |start-h3| raw:: html
+
+    <h3>
+
+.. |end-h3| raw:: html
+
+    </h3>
+
+=============
+bedrock-agent
+=============
+
+.. autoclass:: moto.bedrockagent.models.AgentsforBedrockBackend
+
+|start-h3| Implemented features for this service |end-h3|
+
+- [ ] associate_agent_knowledge_base
+- [X] create_agent
+- [ ] create_agent_action_group
+- [ ] create_agent_alias
+- [ ] create_data_source
+- [X] create_knowledge_base
+- [X] delete_agent
+- [ ] delete_agent_action_group
+- [ ] delete_agent_alias
+- [ ] delete_agent_version
+- [ ] delete_data_source
+- [X] delete_knowledge_base
+- [ ] disassociate_agent_knowledge_base
+- [X] get_agent
+- [ ] get_agent_action_group
+- [ ] get_agent_alias
+- [ ] get_agent_knowledge_base
+- [ ] get_agent_version
+- [ ] get_data_source
+- [ ] get_ingestion_job
+- [X] get_knowledge_base
+- [ ] list_agent_action_groups
+- [ ] list_agent_aliases
+- [ ] list_agent_knowledge_bases
+- [ ] list_agent_versions
+- [X] list_agents
+- [ ] list_data_sources
+- [ ] list_ingestion_jobs
+- [X] list_knowledge_bases
+- [X] list_tags_for_resource
+- [ ] prepare_agent
+- [ ] start_ingestion_job
+- [X] tag_resource
+- [X] untag_resource
+- [ ] update_agent
+- [ ] update_agent_action_group
+- [ ] update_agent_alias
+- [ ] update_agent_knowledge_base
+- [ ] update_data_source
+- [ ] update_knowledge_base
+
diff --git a/docs/docs/services/bedrock.rst b/docs/docs/services/bedrock.rst
new file mode 100644
index 000000000..e86d0faf0
--- /dev/null
+++ b/docs/docs/services/bedrock.rst
@@ -0,0 +1,43 @@
+.. _implementedservice_bedrock:
+
+.. |start-h3| raw:: html
+
+    <h3>
+
+.. |end-h3| raw:: html
+
+    </h3>
+
+=======
+bedrock
+=======
+
+.. autoclass:: moto.bedrock.models.BedrockBackend
+
+|start-h3| Implemented features for this service |end-h3|
+
+- [X] create_model_customization_job
+- [ ] create_provisioned_model_throughput
+- [X] delete_custom_model
+- [X] delete_model_invocation_logging_configuration
+- [ ] delete_provisioned_model_throughput
+- [X] get_custom_model
+- [ ] get_foundation_model
+- [X] get_model_customization_job
+- [X] get_model_invocation_logging_configuration
+- [ ] get_provisioned_model_throughput
+- [X] list_custom_models
+  
+        The foundation_model_arn_equals-argument is not yet supported
+        
+
+- [ ] list_foundation_models
+- [X] list_model_customization_jobs
+- [ ] list_provisioned_model_throughputs
+- [X] list_tags_for_resource
+- [X] put_model_invocation_logging_configuration
+- [X] stop_model_customization_job
+- [X] tag_resource
+- [X] untag_resource
+- [ ] update_provisioned_model_throughput
+
diff --git a/docs/docs/services/ce.rst b/docs/docs/services/ce.rst
index ee3b41d83..456fd8ac4 100644
--- a/docs/docs/services/ce.rst
+++ b/docs/docs/services/ce.rst
@@ -98,11 +98,13 @@ ce
 - [ ] get_savings_plans_utilization_details
 - [ ] get_tags
 - [ ] get_usage_forecast
+- [ ] list_cost_allocation_tag_backfill_history
 - [ ] list_cost_allocation_tags
 - [ ] list_cost_category_definitions
 - [ ] list_savings_plans_purchase_recommendation_generation
 - [X] list_tags_for_resource
 - [ ] provide_anomaly_feedback
+- [ ] start_cost_allocation_tag_backfill
 - [ ] start_savings_plans_purchase_recommendation_generation
 - [X] tag_resource
 - [X] untag_resource
diff --git a/docs/docs/services/cloudformation.rst b/docs/docs/services/cloudformation.rst
index c7a4e31e4..01e4298a1 100644
--- a/docs/docs/services/cloudformation.rst
+++ b/docs/docs/services/cloudformation.rst
@@ -93,6 +93,7 @@ cloudformation
         
 
 - [X] list_stack_resources
+- [ ] list_stack_set_auto_deployment_targets
 - [X] list_stack_set_operation_results
 - [X] list_stack_set_operations
 - [X] list_stack_sets
diff --git a/docs/docs/services/dynamodb.rst b/docs/docs/services/dynamodb.rst
index 8f7cede80..10c9c57da 100644
--- a/docs/docs/services/dynamodb.rst
+++ b/docs/docs/services/dynamodb.rst
@@ -26,6 +26,7 @@ dynamodb
 - [X] create_table
 - [X] delete_backup
 - [X] delete_item
+- [ ] delete_resource_policy
 - [X] delete_table
 - [X] describe_backup
 - [X] describe_continuous_backups
@@ -56,6 +57,7 @@ dynamodb
 
 - [ ] export_table_to_point_in_time
 - [X] get_item
+- [ ] get_resource_policy
 - [ ] import_table
 - [X] list_backups
 - [ ] list_contributor_insights
@@ -65,6 +67,7 @@ dynamodb
 - [X] list_tables
 - [X] list_tags_of_resource
 - [X] put_item
+- [ ] put_resource_policy
 - [X] query
 - [X] restore_table_from_backup
 - [X] restore_table_to_point_in_time
diff --git a/docs/docs/services/ec2.rst b/docs/docs/services/ec2.rst
index 2750ebd76..e6c917d32 100644
--- a/docs/docs/services/ec2.rst
+++ b/docs/docs/services/ec2.rst
@@ -242,7 +242,7 @@ ec2
 - [ ] describe_account_attributes
 - [ ] describe_address_transfers
 - [X] describe_addresses
-- [ ] describe_addresses_attribute
+- [X] describe_addresses_attribute
 - [ ] describe_aggregate_id_format
 - [X] describe_availability_zones
   
@@ -325,6 +325,7 @@ ec2
 - [ ] describe_local_gateway_virtual_interfaces
 - [ ] describe_local_gateways
 - [ ] describe_locked_snapshots
+- [ ] describe_mac_hosts
 - [X] describe_managed_prefix_lists
 - [ ] describe_moving_addresses
 - [X] describe_nat_gateways
@@ -434,6 +435,7 @@ ec2
 - [ ] disable_image
 - [ ] disable_image_block_public_access
 - [ ] disable_image_deprecation
+- [ ] disable_image_deregistration_protection
 - [ ] disable_ipam_organization_admin_account
 - [ ] disable_serial_console_access
 - [ ] disable_snapshot_block_public_access
@@ -464,6 +466,7 @@ ec2
 - [ ] enable_image
 - [ ] enable_image_block_public_access
 - [ ] enable_image_deprecation
+- [ ] enable_image_deregistration_protection
 - [ ] enable_ipam_organization_admin_account
 - [ ] enable_reachability_analyzer_organization_sharing
 - [ ] enable_serial_console_access
@@ -491,6 +494,7 @@ ec2
 - [ ] get_groups_for_capacity_reservation
 - [ ] get_host_reservation_purchase_preview
 - [ ] get_image_block_public_access_state
+- [ ] get_instance_metadata_defaults
 - [ ] get_instance_types_from_instance_requirements
 - [ ] get_instance_uefi_data
 - [ ] get_ipam_address_history
@@ -552,6 +556,7 @@ ec2
 - [ ] modify_instance_event_start_time
 - [ ] modify_instance_event_window
 - [ ] modify_instance_maintenance_options
+- [ ] modify_instance_metadata_defaults
 - [ ] modify_instance_metadata_options
 - [ ] modify_instance_placement
 - [ ] modify_ipam
diff --git a/docs/docs/services/kms.rst b/docs/docs/services/kms.rst
index 1970480cf..0fd48074e 100644
--- a/docs/docs/services/kms.rst
+++ b/docs/docs/services/kms.rst
@@ -57,6 +57,7 @@ kms
 - [ ] list_aliases
 - [X] list_grants
 - [ ] list_key_policies
+- [ ] list_key_rotations
 - [X] list_keys
 - [X] list_resource_tags
 - [X] list_retirable_grants
@@ -65,6 +66,7 @@ kms
 - [X] replicate_key
 - [X] retire_grant
 - [X] revoke_grant
+- [ ] rotate_key_on_demand
 - [X] schedule_key_deletion
 - [X] sign
   
diff --git a/docs/docs/services/lambda.rst b/docs/docs/services/lambda.rst
index 40045f6e4..f42b297ba 100644
--- a/docs/docs/services/lambda.rst
+++ b/docs/docs/services/lambda.rst
@@ -48,10 +48,10 @@ lambda
 - [ ] delete_provisioned_concurrency_config
 - [ ] get_account_settings
 - [X] get_alias
-- [X] get_code_signing_config
+- [ ] get_code_signing_config
 - [X] get_event_source_mapping
 - [X] get_function
-- [ ] get_function_code_signing_config
+- [X] get_function_code_signing_config
 - [X] get_function_concurrency
 - [ ] get_function_configuration
 - [X] get_function_event_invoke_config
@@ -100,13 +100,13 @@ lambda
 - [ ] list_function_url_configs
 - [X] list_functions
 - [ ] list_functions_by_code_signing_config
-- [ ] list_layer_versions
+- [X] list_layer_versions
 - [X] list_layers
 - [ ] list_provisioned_concurrency_configs
 - [X] list_tags
 - [X] list_versions_by_function
 - [X] publish_layer_version
-- [ ] publish_version
+- [X] publish_version
 - [ ] put_function_code_signing_config
 - [X] put_function_concurrency
   Establish concurrency limit/reservations for a function
diff --git a/docs/docs/services/medialive.rst b/docs/docs/services/medialive.rst
index 87381a0a5..061a152a9 100644
--- a/docs/docs/services/medialive.rst
+++ b/docs/docs/services/medialive.rst
@@ -26,6 +26,10 @@ medialive
         The RequestID and Reserved parameters are not yet implemented
         
 
+- [ ] create_cloud_watch_alarm_template
+- [ ] create_cloud_watch_alarm_template_group
+- [ ] create_event_bridge_rule_template
+- [ ] create_event_bridge_rule_template_group
 - [X] create_input
   
         The VPC and RequestId parameters are not yet implemented
@@ -35,14 +39,20 @@ medialive
 - [ ] create_multiplex
 - [ ] create_multiplex_program
 - [ ] create_partner_input
+- [ ] create_signal_map
 - [ ] create_tags
 - [X] delete_channel
+- [ ] delete_cloud_watch_alarm_template
+- [ ] delete_cloud_watch_alarm_template_group
+- [ ] delete_event_bridge_rule_template
+- [ ] delete_event_bridge_rule_template_group
 - [X] delete_input
 - [ ] delete_input_security_group
 - [ ] delete_multiplex
 - [ ] delete_multiplex_program
 - [ ] delete_reservation
 - [ ] delete_schedule
+- [ ] delete_signal_map
 - [ ] delete_tags
 - [ ] describe_account_configuration
 - [X] describe_channel
@@ -56,11 +66,20 @@ medialive
 - [ ] describe_reservation
 - [ ] describe_schedule
 - [ ] describe_thumbnails
+- [ ] get_cloud_watch_alarm_template
+- [ ] get_cloud_watch_alarm_template_group
+- [ ] get_event_bridge_rule_template
+- [ ] get_event_bridge_rule_template_group
+- [ ] get_signal_map
 - [X] list_channels
   
         Pagination is not yet implemented
         
 
+- [ ] list_cloud_watch_alarm_template_groups
+- [ ] list_cloud_watch_alarm_templates
+- [ ] list_event_bridge_rule_template_groups
+- [ ] list_event_bridge_rule_templates
 - [ ] list_input_device_transfers
 - [ ] list_input_devices
 - [ ] list_input_security_groups
@@ -73,15 +92,19 @@ medialive
 - [ ] list_multiplexes
 - [ ] list_offerings
 - [ ] list_reservations
+- [ ] list_signal_maps
 - [ ] list_tags_for_resource
 - [ ] purchase_offering
 - [ ] reboot_input_device
 - [ ] reject_input_device_transfer
 - [ ] restart_channel_pipelines
 - [X] start_channel
+- [ ] start_delete_monitor_deployment
 - [ ] start_input_device
 - [ ] start_input_device_maintenance_window
+- [ ] start_monitor_deployment
 - [ ] start_multiplex
+- [ ] start_update_signal_map
 - [X] stop_channel
 - [ ] stop_input_device
 - [ ] stop_multiplex
@@ -89,6 +112,10 @@ medialive
 - [ ] update_account_configuration
 - [X] update_channel
 - [ ] update_channel_class
+- [ ] update_cloud_watch_alarm_template
+- [ ] update_cloud_watch_alarm_template_group
+- [ ] update_event_bridge_rule_template
+- [ ] update_event_bridge_rule_template_group
 - [X] update_input
 - [ ] update_input_device
 - [ ] update_input_security_group
diff --git a/docs/docs/services/panorama.rst b/docs/docs/services/panorama.rst
index 116fd497f..807e05d10 100644
--- a/docs/docs/services/panorama.rst
+++ b/docs/docs/services/panorama.rst
@@ -16,7 +16,7 @@ panorama
 
 - [ ] create_application_instance
 - [ ] create_job_for_devices
-- [ ] create_node_from_template_job
+- [X] create_node_from_template_job
 - [ ] create_package
 - [ ] create_package_import_job
 - [X] delete_device
@@ -27,7 +27,7 @@ panorama
 - [X] describe_device
 - [ ] describe_device_job
 - [ ] describe_node
-- [ ] describe_node_from_template_job
+- [X] describe_node_from_template_job
 - [ ] describe_package
 - [ ] describe_package_import_job
 - [ ] describe_package_version
@@ -37,7 +37,7 @@ panorama
 - [X] list_devices
 - [ ] list_devices_jobs
 - [ ] list_node_from_template_jobs
-- [ ] list_nodes
+- [X] list_nodes
 - [ ] list_package_import_jobs
 - [ ] list_packages
 - [ ] list_tags_for_resource
diff --git a/docs/docs/services/rds.rst b/docs/docs/services/rds.rst
index 47f249f9d..bc0f6ecc4 100644
--- a/docs/docs/services/rds.rst
+++ b/docs/docs/services/rds.rst
@@ -148,6 +148,7 @@ rds
 - [X] modify_db_subnet_group
 - [ ] modify_event_subscription
 - [ ] modify_global_cluster
+- [ ] modify_integration
 - [X] modify_option_group
 - [ ] modify_tenant_database
 - [X] promote_read_replica
diff --git a/docs/docs/services/rekognition.rst b/docs/docs/services/rekognition.rst
index 9b6243fc8..1ab0b78e7 100644
--- a/docs/docs/services/rekognition.rst
+++ b/docs/docs/services/rekognition.rst
@@ -39,7 +39,7 @@ rekognition
 - [ ] describe_project_versions
 - [ ] describe_projects
 - [ ] describe_stream_processor
-- [ ] detect_custom_labels
+- [X] detect_custom_labels
 - [ ] detect_faces
 - [X] detect_labels
 - [ ] detect_moderation_labels
diff --git a/docs/docs/services/sagemaker.rst b/docs/docs/services/sagemaker.rst
index 8d3bf1404..6b370b25b 100644
--- a/docs/docs/services/sagemaker.rst
+++ b/docs/docs/services/sagemaker.rst
@@ -335,7 +335,7 @@ sagemaker
 - [ ] update_space
 - [ ] update_training_job
 - [ ] update_trial
-- [ ] update_trial_component
+- [X] update_trial_component
 - [ ] update_user_profile
 - [ ] update_workforce
 - [ ] update_workteam
diff --git a/docs/docs/services/workspaces.rst b/docs/docs/services/workspaces.rst
index 0b57da593..81132bd18 100644
--- a/docs/docs/services/workspaces.rst
+++ b/docs/docs/services/workspaces.rst
@@ -16,11 +16,13 @@ workspaces
 
 |start-h3| Implemented features for this service |end-h3|
 
+- [ ] accept_account_link_invitation
 - [ ] associate_connection_alias
 - [ ] associate_ip_groups
 - [ ] associate_workspace_application
 - [ ] authorize_ip_rules
 - [ ] copy_workspace_image
+- [ ] create_account_link_invitation
 - [ ] create_connect_client_add_in
 - [ ] create_connection_alias
 - [ ] create_ip_group
@@ -30,6 +32,7 @@ workspaces
 - [ ] create_workspace_bundle
 - [X] create_workspace_image
 - [X] create_workspaces
+- [ ] delete_account_link_invitation
 - [ ] delete_client_branding
 - [ ] delete_connect_client_add_in
 - [ ] delete_connection_alias
@@ -67,8 +70,10 @@ workspaces
 - [ ] disassociate_connection_alias
 - [ ] disassociate_ip_groups
 - [ ] disassociate_workspace_application
+- [ ] get_account_link
 - [ ] import_client_branding
 - [ ] import_workspace_image
+- [ ] list_account_links
 - [ ] list_available_management_cidr_ranges
 - [ ] migrate_workspace
 - [ ] modify_account
@@ -83,6 +88,7 @@ workspaces
 - [ ] reboot_workspaces
 - [ ] rebuild_workspaces
 - [X] register_workspace_directory
+- [ ] reject_account_link_invitation
 - [ ] restore_workspace
 - [ ] revoke_ip_rules
 - [ ] start_workspaces
diff --git a/moto/__init__.py b/moto/__init__.py
index 10bb8c7d3..599e22841 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -1,4 +1,4 @@
 from moto.core.decorator import mock_aws as mock_aws
 
 __title__ = "moto"
-__version__ = "5.0.6.dev"
+__version__ = "5.0.7.dev"
diff --git a/moto/amp/urls.py b/moto/amp/urls.py
index 520698ec7..317eba946 100644
--- a/moto/amp/urls.py
+++ b/moto/amp/urls.py
@@ -15,10 +15,6 @@ url_paths = {
     "{0}/workspaces/(?P<workspace_id>[^/]+)/rulegroupsnamespaces$": PrometheusServiceResponse.dispatch,
     "{0}/workspaces/(?P<workspace_id>[^/]+)/rulegroupsnamespaces/(?P<name>[^/]+)$": PrometheusServiceResponse.dispatch,
     "{0}/tags/(?P<resource_arn>[^/]+)$": PrometheusServiceResponse.dispatch,
-    "{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)$": PrometheusServiceResponse.method_dispatch(
-        PrometheusServiceResponse.tags  # type: ignore
-    ),
-    "{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)/(?P<ns_name>[^/]+)$": PrometheusServiceResponse.method_dispatch(
-        PrometheusServiceResponse.tags  # type: ignore
-    ),
+    "{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)$": PrometheusServiceResponse.dispatch,
+    "{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)/(?P<ns_name>[^/]+)$": PrometheusServiceResponse.dispatch,
 }
diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py
index a28eaf9b9..1030ac26b 100644
--- a/moto/apigateway/responses.py
+++ b/moto/apigateway/responses.py
@@ -1,5 +1,5 @@
 import json
-from typing import Any, Dict, List
+from typing import Dict, List
 from urllib.parse import unquote
 
 from moto.core.responses import TYPE_RESPONSE, BaseResponse
@@ -413,25 +413,25 @@ class APIGatewayResponse(BaseResponse):
         )
         return json.dumps(stage_response.to_json())
 
-    def restapis_stages_tags(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def tag_resource(self) -> str:
         url_path_parts = unquote(self.path.split("/tags/")[1]).split("/")
         function_id = url_path_parts[-3]
         stage_name = url_path_parts[-1]
-        if self.method == "PUT":
-            tags = self._get_param("tags")
-            if tags:
-                stage = self.backend.get_stage(function_id, stage_name)
-                stage.tags = merge_multiple_dicts(stage.tags or {}, tags)
-            return 200, {}, json.dumps({"item": tags})
-        if self.method == "DELETE":
+        tags = self._get_param("tags")
+        if tags:
             stage = self.backend.get_stage(function_id, stage_name)
-            for tag in (stage.tags or {}).copy():
-                if tag in (self.querystring.get("tagKeys") or {}):
-                    stage.tags.pop(tag, None)  # type: ignore[union-attr]
-            return 200, {}, json.dumps({"item": ""})
+            stage.tags = merge_multiple_dicts(stage.tags or {}, tags)
+        return json.dumps({"item": tags})
+
+    def untag_resource(self) -> str:
+        url_path_parts = unquote(self.path.split("/tags/")[1]).split("/")
+        function_id = url_path_parts[-3]
+        stage_name = url_path_parts[-1]
+        stage = self.backend.get_stage(function_id, stage_name)
+        for tag in (stage.tags or {}).copy():
+            if tag in (self.querystring.get("tagKeys") or {}):
+                stage.tags.pop(tag, None)  # type: ignore[union-attr]
+        return json.dumps({"item": ""})
 
     def get_export(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py
index 2d80fcf44..1b520f313 100644
--- a/moto/apigateway/urls.py
+++ b/moto/apigateway/urls.py
@@ -10,12 +10,8 @@ url_paths = {
     "{0}/restapis/(?P<function_id>[^/]+)/authorizers$": APIGatewayResponse.dispatch,
     "{0}/restapis/(?P<function_id>[^/]+)/authorizers/(?P<authorizer_id>[^/]+)/?$": APIGatewayResponse.dispatch,
     "{0}/restapis/(?P<function_id>[^/]+)/stages$": APIGatewayResponse.dispatch,
-    "{0}/tags/(?P<resourceArn>[^/]+)$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.restapis_stages_tags
-    ),
-    "{0}/tags/arn:aws:apigateway:(?P<region_name>[^/]+)::/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.restapis_stages_tags
-    ),
+    "{0}/tags/(?P<resourceArn>[^/]+)$": APIGatewayResponse.dispatch,
+    "{0}/tags/arn:aws:apigateway:(?P<region_name>[^/]+)::/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": APIGatewayResponse.dispatch,
     "{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/?$": APIGatewayResponse.dispatch,
     "{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/exports/(?P<export_type>[^/]+)/?$": APIGatewayResponse.dispatch,
     "{0}/restapis/(?P<function_id>[^/]+)/deployments$": APIGatewayResponse.dispatch,
diff --git a/moto/apigatewayv2/responses.py b/moto/apigatewayv2/responses.py
index a03833405..18d6b1b83 100644
--- a/moto/apigatewayv2/responses.py
+++ b/moto/apigatewayv2/responses.py
@@ -1,7 +1,6 @@
 """Handles incoming apigatewayv2 requests, invokes methods, returns responses."""
 
 import json
-from typing import Any
 from urllib.parse import unquote
 
 from moto.core.responses import TYPE_RESPONSE, BaseResponse
@@ -21,16 +20,6 @@ class ApiGatewayV2Response(BaseResponse):
         """Return backend instance specific for this region."""
         return apigatewayv2_backends[self.current_account][self.region]
 
-    def tags(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:  # type: ignore[return]
-        self.setup_class(request, full_url, headers)
-
-        if self.method == "POST":
-            return self.tag_resource()
-        if self.method == "GET":
-            return self.get_tags()
-        if self.method == "DELETE":
-            return self.untag_resource()
-
     def create_api(self) -> TYPE_RESPONSE:
         params = json.loads(self.body)
 
diff --git a/moto/apigatewayv2/urls.py b/moto/apigatewayv2/urls.py
index bbe914622..72f4922d6 100644
--- a/moto/apigatewayv2/urls.py
+++ b/moto/apigatewayv2/urls.py
@@ -26,15 +26,9 @@ url_paths = {
     "{0}/v2/apis/(?P<api_id>[^/]+)/routes/(?P<route_id>[^/]+)/requestparameters/(?P<request_parameter>[^/]+)$": ApiGatewayV2Response.dispatch,
     "{0}/v2/apis/(?P<api_id>[^/]+)/stages$": ApiGatewayV2Response.dispatch,
     "{0}/v2/apis/(?P<api_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": ApiGatewayV2Response.dispatch,
-    "{0}/v2/tags/(?P<resource_arn>[^/]+)$": ApiGatewayV2Response.method_dispatch(
-        ApiGatewayV2Response.tags
-    ),
-    "{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/apis/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.method_dispatch(
-        ApiGatewayV2Response.tags
-    ),
-    "{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/vpclinks/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.method_dispatch(
-        ApiGatewayV2Response.tags
-    ),
+    "{0}/v2/tags/(?P<resource_arn>[^/]+)$": ApiGatewayV2Response.dispatch,
+    "{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/apis/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.dispatch,
+    "{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/vpclinks/(?P<resource_arn_pt2>[^/]+)$": ApiGatewayV2Response.dispatch,
     "{0}/v2/vpclinks$": ApiGatewayV2Response.dispatch,
     "{0}/v2/vpclinks/(?P<vpc_link_id>[^/]+)$": ApiGatewayV2Response.dispatch,
     "{0}/v2/domainnames$": ApiGatewayV2Response.dispatch,
diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py
index 9d4b8504a..af87165a2 100644
--- a/moto/awslambda/responses.py
+++ b/moto/awslambda/responses.py
@@ -23,17 +23,6 @@ class LambdaResponse(BaseResponse):
     def backend(self) -> LambdaBackend:
         return get_backend(self.current_account, self.region)
 
-    def tag(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-        if request.method == "GET":
-            return self._list_tags()
-        elif request.method == "POST":
-            return self._tag_resource()
-        elif request.method == "DELETE":
-            return self._untag_resource()
-        else:
-            raise ValueError(f"Cannot handle {request.method} request")
-
     def add_permission(self) -> str:
         function_name = unquote(self.path.split("/")[-2])
         qualifier = self.querystring.get("Qualifier", [None])[0]
@@ -280,19 +269,19 @@ class LambdaResponse(BaseResponse):
         else:
             return self.default_region
 
-    def _list_tags(self) -> TYPE_RESPONSE:
+    def list_tags(self) -> str:
         function_arn = unquote(self.path.rsplit("/", 1)[-1])
 
         tags = self.backend.list_tags(function_arn)
-        return 200, {}, json.dumps({"Tags": tags})
+        return json.dumps({"Tags": tags})
 
-    def _tag_resource(self) -> TYPE_RESPONSE:
+    def tag_resource(self) -> str:
         function_arn = unquote(self.path.rsplit("/", 1)[-1])
 
         self.backend.tag_resource(function_arn, self.json_body["Tags"])
-        return 200, {}, "{}"
+        return "{}"
 
-    def _untag_resource(self) -> TYPE_RESPONSE:
+    def untag_resource(self) -> TYPE_RESPONSE:
         function_arn = unquote(self.path.rsplit("/", 1)[-1])
         tag_keys = self.querystring["tagKeys"]
 
diff --git a/moto/awslambda/urls.py b/moto/awslambda/urls.py
index 9fc0c3c5c..3ff56adac 100644
--- a/moto/awslambda/urls.py
+++ b/moto/awslambda/urls.py
@@ -16,9 +16,7 @@ url_paths = {
     r"{0}/(?P<api_version>[^/]+)/functions/(?P<resource_arn>.+)/invocations/?$": LambdaResponse.dispatch,
     r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/invoke-async$": LambdaResponse.dispatch,
     r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/invoke-async/$": LambdaResponse.dispatch,
-    r"{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)": LambdaResponse.method_dispatch(
-        LambdaResponse.tag
-    ),
+    r"{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)": LambdaResponse.dispatch,
     r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/policy/(?P<statement_id>[\w_-]+)$": LambdaResponse.dispatch,
     r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/policy/?$": LambdaResponse.dispatch,
     r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_:%-]+)/configuration/?$": LambdaResponse.dispatch,
diff --git a/moto/backend_index.py b/moto/backend_index.py
index 23bbae341..a1952b1f3 100644
--- a/moto/backend_index.py
+++ b/moto/backend_index.py
@@ -22,6 +22,8 @@ backend_url_patterns = [
     ("backup", re.compile("https?://backup\\.(.+)\\.amazonaws\\.com")),
     ("batch", re.compile("https?://batch\\.(.+)\\.amazonaws.com")),
     ("budgets", re.compile("https?://budgets\\.amazonaws\\.com")),
+    ("bedrock", re.compile("https?://bedrock\\.(.+)\\.amazonaws\\.com")),
+    ("bedrockagent", re.compile("https?://bedrock-agent\\.(.+)\\.amazonaws\\.com")),
     ("ce", re.compile("https?://ce\\.(.+)\\.amazonaws\\.com")),
     ("cloudformation", re.compile("https?://cloudformation\\.(.+)\\.amazonaws\\.com")),
     ("cloudfront", re.compile("https?://cloudfront\\.amazonaws\\.com")),
diff --git a/moto/backends.py b/moto/backends.py
index 7a5d381c0..3e5a5a10d 100644
--- a/moto/backends.py
+++ b/moto/backends.py
@@ -20,6 +20,8 @@ if TYPE_CHECKING:
     from moto.autoscaling.models import AutoScalingBackend
     from moto.awslambda.models import LambdaBackend
     from moto.batch.models import BatchBackend
+    from moto.bedrock.models import BedrockBackend
+    from moto.bedrockagent.models import AgentsforBedrockBackend
     from moto.budgets.models import BudgetsBackend
     from moto.ce.models import CostExplorerBackend
     from moto.cloudformation.models import CloudFormationBackend
@@ -182,6 +184,8 @@ SERVICE_NAMES = Union[
     "Literal['athena']",
     "Literal['autoscaling']",
     "Literal['batch']",
+    "Literal['bedrock']",
+    "Literal['bedrock-agent']",
     "Literal['budgets']",
     "Literal['ce']",
     "Literal['cloudformation']",
@@ -344,6 +348,12 @@ def get_backend(
 @overload
 def get_backend(name: "Literal['batch']") -> "BackendDict[BatchBackend]": ...
 @overload
+def get_backend(name: "Literal['bedrock']") -> "BackendDict[BedrockBackend]": ...
+@overload
+def get_backend(
+    name: "Literal['bedrock-agent']",
+) -> "BackendDict[AgentsforBedrockBackend]": ...
+@overload
 def get_backend(name: "Literal['budgets']") -> "BackendDict[BudgetsBackend]": ...
 @overload
 def get_backend(name: "Literal['ce']") -> "BackendDict[CostExplorerBackend]": ...
diff --git a/moto/bedrock/__init__.py b/moto/bedrock/__init__.py
new file mode 100644
index 000000000..f203809b1
--- /dev/null
+++ b/moto/bedrock/__init__.py
@@ -0,0 +1,1 @@
+from .models import bedrock_backends  # noqa: F401
diff --git a/moto/bedrock/exceptions.py b/moto/bedrock/exceptions.py
new file mode 100644
index 000000000..9745d59b0
--- /dev/null
+++ b/moto/bedrock/exceptions.py
@@ -0,0 +1,33 @@
+"""Exceptions raised by the bedrock service."""
+
+from moto.core.exceptions import JsonRESTError
+
+# Bedrock.Client.exceptions.ResourceNotFoundException
+
+
+class BedrockClientError(JsonRESTError):
+    code = 400
+
+
+class ResourceNotFoundException(BedrockClientError):
+    def __init__(self, msg: str):
+        super().__init__("ResourceNotFoundException", f"{msg}")
+
+
+class ResourceInUseException(BedrockClientError):
+    def __init__(self, msg: str):
+        super().__init__("ResourceInUseException", f"{msg}")
+
+
+class ValidationException(BedrockClientError):
+    def __init__(self, msg: str):
+        super().__init__(
+            "ValidationException",
+            "Input validation failed. Check your request parameters and retry the request.",
+            f"{msg}",
+        )
+
+
+class TooManyTagsException(BedrockClientError):
+    def __init__(self, msg: str):
+        super().__init__("TooManyTagsException", f"{msg}")
diff --git a/moto/bedrock/models.py b/moto/bedrock/models.py
new file mode 100644
index 000000000..6023b1b55
--- /dev/null
+++ b/moto/bedrock/models.py
@@ -0,0 +1,534 @@
+"""BedrockBackend class with methods for supported APIs."""
+
+import re
+from datetime import datetime
+from typing import Any, Dict, List, Optional
+
+from moto.bedrock.exceptions import (
+    ResourceInUseException,
+    ResourceNotFoundException,
+    TooManyTagsException,
+    ValidationException,
+)
+from moto.core.base_backend import BackendDict, BaseBackend
+from moto.core.common_models import BaseModel
+from moto.utilities.paginator import paginate
+from moto.utilities.tagging_service import TaggingService
+
+
+class ModelCustomizationJob(BaseModel):
+    def __init__(
+        self,
+        job_name: str,
+        custom_model_name: str,
+        role_arn: str,
+        base_model_identifier: str,
+        training_data_config: Dict[str, str],
+        output_data_config: Dict[str, str],
+        hyper_parameters: Dict[str, str],
+        region_name: str,
+        account_id: str,
+        client_request_token: Optional[str],
+        customization_type: Optional[str],
+        custom_model_kms_key_id: Optional[str],
+        job_tags: Optional[List[Dict[str, str]]],
+        custom_model_tags: Optional[List[Dict[str, str]]],
+        validation_data_config: Optional[Dict[str, Any]],
+        vpc_config: Optional[Dict[str, Any]],
+    ):
+        self.job_name = job_name
+        self.custom_model_name = custom_model_name
+        self.role_arn = role_arn
+        self.client_request_token = client_request_token
+        self.base_model_identifier = base_model_identifier
+        self.customization_type = customization_type
+        self.custom_model_kms_key_id = custom_model_kms_key_id
+        self.job_tags = job_tags
+        self.custom_model_tags = custom_model_tags
+        if "s3Uri" not in training_data_config or not re.match(
+            r"s3://.*", training_data_config["s3Uri"]
+        ):
+            raise ValidationException(
+                "Validation error detected: "
+                f"Value '{training_data_config}' at 'training_data_config' failed to satisfy constraint: "
+                "Member must satisfy regular expression pattern: "
+                "s3://.*"
+            )
+        self.training_data_config = training_data_config
+        if validation_data_config:
+            if "validators" in validation_data_config:
+                for validator in validation_data_config["validators"]:
+                    if not re.match(r"s3://.*", validator["s3Uri"]):
+                        raise ValidationException(
+                            "Validation error detected: "
+                            f"Value '{validator}' at 'validation_data_config' failed to satisfy constraint: "
+                            "Member must satisfy regular expression pattern: "
+                            "s3://.*"
+                        )
+        self.validation_data_config = validation_data_config
+        if "s3Uri" not in output_data_config or not re.match(
+            r"s3://.*", output_data_config["s3Uri"]
+        ):
+            raise ValidationException(
+                "Validation error detected: "
+                f"Value '{output_data_config}' at 'output_data_config' failed to satisfy constraint: "
+                "Member must satisfy regular expression pattern: "
+                "s3://.*"
+            )
+        self.output_data_config = output_data_config
+        self.hyper_parameters = hyper_parameters
+        self.vpc_config = vpc_config
+        self.region_name = region_name
+        self.account_id = account_id
+        self.job_arn = f"arn:aws:bedrock:{self.region_name}:{self.account_id}:model-customization-job/{self.job_name}"
+        self.output_model_name = f"{self.custom_model_name}-{self.job_name}"
+        self.output_model_arn = f"arn:aws:bedrock:{self.region_name}:{self.account_id}:custom-model/{self.output_model_name}"
+        self.status = "InProgress"
+        self.failure_message = "Failure Message"
+        self.creation_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        self.last_modified_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        self.end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        self.base_model_arn = f"arn:aws:bedrock:{self.region_name}::foundation-model/{self.base_model_identifier}"
+        self.output_model_kms_key_arn = f"arn:aws:kms:{self.region_name}:{self.account_id}:key/{self.output_model_name}-kms-key"
+        self.training_metrics = {"trainingLoss": 0.0}  # hard coded
+        self.validation_metrics = [{"validationLoss": 0.0}]  # hard coded
+
+    def to_dict(self) -> Dict[str, Any]:
+        dct = {
+            "baseModelArn": self.base_model_arn,
+            "clientRequestToken": self.client_request_token,
+            "creationTime": self.creation_time,
+            "customizationType": self.customization_type,
+            "endTime": self.end_time,
+            "failureMessage": self.failure_message,
+            "hyperParameters": self.hyper_parameters,
+            "jobArn": self.job_arn,
+            "jobName": self.job_name,
+            "lastModifiedTime": self.last_modified_time,
+            "outputDataConfig": self.output_data_config,
+            "outputModelArn": self.output_model_arn,
+            "outputModelKmsKeyArn": self.output_model_kms_key_arn,
+            "outputModelName": self.output_model_name,
+            "roleArn": self.role_arn,
+            "status": self.status,
+            "trainingDataConfig": self.training_data_config,
+            "trainingMetrics": self.training_metrics,
+            "validationDataConfig": self.validation_data_config,
+            "validationMetrics": self.validation_metrics,
+            "vpcConfig": self.vpc_config,
+        }
+        return {k: v for k, v in dct.items() if v}
+
+
+class CustomModel(BaseModel):
+    def __init__(
+        self,
+        model_name: str,
+        job_name: str,
+        job_arn: str,
+        base_model_arn: str,
+        hyper_parameters: Dict[str, str],
+        output_data_config: Dict[str, str],
+        training_data_config: Dict[str, str],
+        training_metrics: Dict[str, float],
+        base_model_name: str,
+        region_name: str,
+        account_id: str,
+        customization_type: Optional[str],
+        model_kms_key_arn: Optional[str],
+        validation_data_config: Optional[Dict[str, Any]],
+        validation_metrics: Optional[List[Dict[str, float]]],
+    ):
+        self.model_name = model_name
+        self.job_name = job_name
+        self.job_arn = job_arn
+        self.base_model_arn = base_model_arn
+        self.customization_type = customization_type
+        self.model_kms_key_arn = model_kms_key_arn
+        self.hyper_parameters = hyper_parameters
+        self.training_data_config = training_data_config
+        self.validation_data_config = validation_data_config
+        self.output_data_config = output_data_config
+        self.training_metrics = training_metrics
+        self.validation_metrics = validation_metrics
+        self.region_name = region_name
+        self.account_id = account_id
+        self.model_arn = f"arn:aws:bedrock:{self.region_name}:{self.account_id}:custom-model/{self.model_name}"
+        self.creation_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        self.base_model_name = base_model_name
+
+    def to_dict(self) -> Dict[str, Any]:
+        dct = {
+            "baseModelArn": self.base_model_arn,
+            "creationTime": self.creation_time,
+            "customizationType": self.customization_type,
+            "hyperParameters": self.hyper_parameters,
+            "jobArn": self.job_arn,
+            "jobName": self.job_name,
+            "modelArn": self.model_arn,
+            "modelKmsKeyArn": self.model_kms_key_arn,
+            "modelName": self.model_name,
+            "outputDataConfig": self.output_data_config,
+            "trainingDataConfig": self.training_data_config,
+            "trainingMetrics": self.training_metrics,
+            "validationDataConfig": self.validation_data_config,
+            "validationMetrics": self.validation_metrics,
+        }
+        return {k: v for k, v in dct.items() if v}
+
+
+class model_invocation_logging_configuration(BaseModel):
+    def __init__(self, logging_config: Dict[str, Any]) -> None:
+        self.logging_config = logging_config
+
+
+class BedrockBackend(BaseBackend):
+    """Implementation of Bedrock APIs."""
+
+    PAGINATION_MODEL = {
+        "list_model_customization_jobs": {
+            "input_token": "next_token",
+            "limit_key": "max_results",
+            "limit_default": 100,
+            "unique_attribute": "jobArn",
+        },
+        "list_custom_models": {
+            "input_token": "next_token",
+            "limit_key": "max_results",
+            "limit_default": 100,
+            "unique_attribute": "modelArn",
+        },
+    }
+
+    def __init__(self, region_name: str, account_id: str) -> None:
+        super().__init__(region_name, account_id)
+        self.model_customization_jobs: Dict[str, ModelCustomizationJob] = {}
+        self.custom_models: Dict[str, CustomModel] = {}
+        self.model_invocation_logging_configuration: Optional[
+            model_invocation_logging_configuration
+        ] = None
+        self.tagger = TaggingService()
+
+    def _list_arns(self) -> List[str]:
+        return [job.job_arn for job in self.model_customization_jobs.values()] + [
+            model.model_arn for model in self.custom_models.values()
+        ]
+
+    def create_model_customization_job(
+        self,
+        job_name: str,
+        custom_model_name: str,
+        role_arn: str,
+        base_model_identifier: str,
+        training_data_config: Dict[str, Any],
+        output_data_config: Dict[str, str],
+        hyper_parameters: Dict[str, str],
+        client_request_token: Optional[str],
+        customization_type: Optional[str],
+        custom_model_kms_key_id: Optional[str],
+        job_tags: Optional[List[Dict[str, str]]],
+        custom_model_tags: Optional[List[Dict[str, str]]],
+        validation_data_config: Optional[Dict[str, Any]],
+        vpc_config: Optional[Dict[str, Any]],
+    ) -> str:
+        if job_name in self.model_customization_jobs.keys():
+            raise ResourceInUseException(
+                f"Model customization job {job_name} already exists"
+            )
+        if custom_model_name in self.custom_models.keys():
+            raise ResourceInUseException(
+                f"Custom model {custom_model_name} already exists"
+            )
+        model_customization_job = ModelCustomizationJob(
+            job_name,
+            custom_model_name,
+            role_arn,
+            base_model_identifier,
+            training_data_config,
+            output_data_config,
+            hyper_parameters,
+            self.region_name,
+            self.account_id,
+            client_request_token,
+            customization_type,
+            custom_model_kms_key_id,
+            job_tags,
+            custom_model_tags,
+            validation_data_config,
+            vpc_config,
+        )
+        self.model_customization_jobs[job_name] = model_customization_job
+        if job_tags:
+            self.tag_resource(model_customization_job.job_arn, job_tags)
+        # Create associated custom model
+        custom_model = CustomModel(
+            custom_model_name,
+            job_name,
+            model_customization_job.job_arn,
+            model_customization_job.base_model_arn,
+            model_customization_job.hyper_parameters,
+            model_customization_job.output_data_config,
+            model_customization_job.training_data_config,
+            model_customization_job.training_metrics,
+            model_customization_job.base_model_identifier,
+            self.region_name,
+            self.account_id,
+            model_customization_job.customization_type,
+            model_customization_job.output_model_kms_key_arn,
+            model_customization_job.validation_data_config,
+            model_customization_job.validation_metrics,
+        )
+        self.custom_models[custom_model_name] = custom_model
+        if custom_model_tags:
+            self.tag_resource(custom_model.model_arn, custom_model_tags)
+        return model_customization_job.job_arn
+
+    def get_model_customization_job(self, job_identifier: str) -> ModelCustomizationJob:
+        if job_identifier not in self.model_customization_jobs:
+            raise ResourceNotFoundException(
+                f"Model customization job {job_identifier} not found"
+            )
+        else:
+            return self.model_customization_jobs[job_identifier]
+
+    def stop_model_customization_job(self, job_identifier: str) -> None:
+        if job_identifier in self.model_customization_jobs:
+            self.model_customization_jobs[job_identifier].status = "Stopped"
+        else:
+            raise ResourceNotFoundException(
+                f"Model customization job {job_identifier} not found"
+            )
+        return
+
+    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    def list_model_customization_jobs(
+        self,
+        creation_time_after: Optional[datetime],
+        creation_time_before: Optional[datetime],
+        status_equals: Optional[str],
+        name_contains: Optional[str],
+        max_results: Optional[int],
+        next_token: Optional[str],
+        sort_by: Optional[str],
+        sort_order: Optional[str],
+    ) -> List[Any]:
+        customization_jobs_fetched = list(self.model_customization_jobs.values())
+
+        if name_contains is not None:
+            customization_jobs_fetched = list(
+                filter(
+                    lambda x: name_contains in x.job_name,
+                    customization_jobs_fetched,
+                )
+            )
+
+        if creation_time_after is not None:
+            customization_jobs_fetched = list(
+                filter(
+                    lambda x: x.creation_time > str(creation_time_after),
+                    customization_jobs_fetched,
+                )
+            )
+
+        if creation_time_before is not None:
+            customization_jobs_fetched = list(
+                filter(
+                    lambda x: x.creation_time < str(creation_time_before),
+                    customization_jobs_fetched,
+                )
+            )
+        if status_equals is not None:
+            customization_jobs_fetched = list(
+                filter(
+                    lambda x: x.status == status_equals,
+                    customization_jobs_fetched,
+                )
+            )
+
+        if sort_by is not None:
+            if sort_by == "CreationTime":
+                if sort_order is not None and sort_order == "Ascending":
+                    customization_jobs_fetched = sorted(
+                        customization_jobs_fetched, key=lambda x: x.creation_time
+                    )
+                elif sort_order is not None and sort_order == "Descending":
+                    customization_jobs_fetched = sorted(
+                        customization_jobs_fetched,
+                        key=lambda x: x.creation_time,
+                        reverse=True,
+                    )
+                else:
+                    raise ValidationException(f"Invalid sort order: {sort_order}")
+            else:
+                raise ValidationException(f"Invalid sort by field: {sort_by}")
+
+        model_customization_job_summaries = []
+        for model in customization_jobs_fetched:
+            model_customization_job_summaries.append(
+                {
+                    "jobArn": model.job_arn,
+                    "baseModelArn": model.base_model_arn,
+                    "jobName": model.job_name,
+                    "status": model.status,
+                    "lastModifiedTime": model.last_modified_time,
+                    "creationTime": model.creation_time,
+                    "endTime": model.end_time,
+                    "customModelArn": model.output_model_arn,
+                    "customModelName": model.custom_model_name,
+                    "customizationType": model.customization_type,
+                }
+            )
+        return model_customization_job_summaries
+
+    def get_model_invocation_logging_configuration(self) -> Optional[Dict[str, Any]]:
+        if self.model_invocation_logging_configuration:
+            return self.model_invocation_logging_configuration.logging_config
+        else:
+            return {}
+
+    def put_model_invocation_logging_configuration(
+        self, logging_config: Dict[str, Any]
+    ) -> None:
+        invocation_logging = model_invocation_logging_configuration(logging_config)
+        self.model_invocation_logging_configuration = invocation_logging
+        return
+
+    def get_custom_model(self, model_identifier: str) -> CustomModel:
+        if model_identifier[:3] == "arn":
+            for model in self.custom_models.values():
+                if model.model_arn == model_identifier:
+                    return model
+            raise ResourceNotFoundException(
+                f"Custom model {model_identifier} not found"
+            )
+        elif model_identifier in self.custom_models:
+            return self.custom_models[model_identifier]
+        else:
+            raise ResourceNotFoundException(
+                f"Custom model {model_identifier} not found"
+            )
+
+    def delete_custom_model(self, model_identifier: str) -> None:
+        if model_identifier in self.custom_models:
+            del self.custom_models[model_identifier]
+        else:
+            raise ResourceNotFoundException(
+                f"Custom model {model_identifier} not found"
+            )
+        return
+
+    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    def list_custom_models(
+        self,
+        creation_time_before: Optional[datetime],
+        creation_time_after: Optional[datetime],
+        name_contains: Optional[str],
+        base_model_arn_equals: Optional[str],
+        foundation_model_arn_equals: Optional[str],
+        max_results: Optional[int],
+        next_token: Optional[str],
+        sort_by: Optional[str],
+        sort_order: Optional[str],
+    ) -> List[Any]:
+        """
+        The foundation_model_arn_equals-argument is not yet supported
+        """
+        custom_models_fetched = list(self.custom_models.values())
+
+        if name_contains is not None:
+            custom_models_fetched = list(
+                filter(
+                    lambda x: name_contains in x.job_name,
+                    custom_models_fetched,
+                )
+            )
+
+        if creation_time_after is not None:
+            custom_models_fetched = list(
+                filter(
+                    lambda x: x.creation_time > str(creation_time_after),
+                    custom_models_fetched,
+                )
+            )
+
+        if creation_time_before is not None:
+            custom_models_fetched = list(
+                filter(
+                    lambda x: x.creation_time < str(creation_time_before),
+                    custom_models_fetched,
+                )
+            )
+        if base_model_arn_equals is not None:
+            custom_models_fetched = list(
+                filter(
+                    lambda x: x.base_model_arn == base_model_arn_equals,
+                    custom_models_fetched,
+                )
+            )
+
+        if sort_by is not None:
+            if sort_by == "CreationTime":
+                if sort_order is not None and sort_order == "Ascending":
+                    custom_models_fetched = sorted(
+                        custom_models_fetched, key=lambda x: x.creation_time
+                    )
+                elif sort_order is not None and sort_order == "Descending":
+                    custom_models_fetched = sorted(
+                        custom_models_fetched,
+                        key=lambda x: x.creation_time,
+                        reverse=True,
+                    )
+                else:
+                    raise ValidationException(f"Invalid sort order: {sort_order}")
+            else:
+                raise ValidationException(f"Invalid sort by field: {sort_by}")
+        model_summaries = []
+        for model in custom_models_fetched:
+            model_summaries.append(
+                {
+                    "modelArn": model.model_arn,
+                    "modelName": model.model_name,
+                    "creationTime": model.creation_time,
+                    "baseModelArn": model.base_model_arn,
+                    "baseModelName": model.base_model_name,
+                    "jobArn": model.job_arn,
+                    "customizationType": model.customization_type,
+                }
+            )
+        return model_summaries
+
+    def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None:
+        if resource_arn not in self._list_arns():
+            raise ResourceNotFoundException(f"Resource {resource_arn} not found")
+        fixed_tags = []
+        if len(tags) + len(self.tagger.list_tags_for_resource(resource_arn)) > 50:
+            raise TooManyTagsException(
+                "Member must have length less than or equal to 50"
+            )
+        for tag_dict in tags:
+            fixed_tags.append({"Key": tag_dict["key"], "Value": tag_dict["value"]})
+        self.tagger.tag_resource(resource_arn, fixed_tags)
+        return
+
+    def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
+        if resource_arn not in self._list_arns():
+            raise ResourceNotFoundException(f"Resource {resource_arn} not found")
+        self.tagger.untag_resource_using_names(resource_arn, tag_keys)
+        return
+
+    def list_tags_for_resource(self, resource_arn: str) -> List[Dict[str, str]]:
+        if resource_arn not in self._list_arns():
+            raise ResourceNotFoundException(f"Resource {resource_arn} not found")
+        tags = self.tagger.list_tags_for_resource(resource_arn)
+        fixed_tags = []
+        for tag_dict in tags["Tags"]:
+            fixed_tags.append({"key": tag_dict["Key"], "value": tag_dict["Value"]})
+        return fixed_tags
+
+    def delete_model_invocation_logging_configuration(self) -> None:
+        if self.model_invocation_logging_configuration:
+            self.model_invocation_logging_configuration.logging_config = {}
+        return
+
+
+bedrock_backends = BackendDict(BedrockBackend, "bedrock")
diff --git a/moto/bedrock/responses.py b/moto/bedrock/responses.py
new file mode 100644
index 000000000..c6adda635
--- /dev/null
+++ b/moto/bedrock/responses.py
@@ -0,0 +1,186 @@
+"""Handles incoming bedrock requests, invokes methods, returns responses."""
+
+import json
+from urllib.parse import unquote
+
+from moto.core.responses import BaseResponse
+
+from .models import BedrockBackend, bedrock_backends
+
+
+class BedrockResponse(BaseResponse):
+    """Handler for Bedrock requests and responses."""
+
+    def __init__(self) -> None:
+        super().__init__(service_name="bedrock")
+
+    @property
+    def bedrock_backend(self) -> BedrockBackend:
+        """Return backend instance specific for this region."""
+        return bedrock_backends[self.current_account][self.region]
+
+    def create_model_customization_job(self) -> str:
+        params = json.loads(self.body)
+        job_name = params.get("jobName")
+        custom_model_name = params.get("customModelName")
+        role_arn = params.get("roleArn")
+        client_request_token = params.get("clientRequestToken")
+        base_model_identifier = params.get("baseModelIdentifier")
+        customization_type = params.get("customizationType")
+        custom_model_kms_key_id = params.get("customModelKmsKeyId")
+        job_tags = params.get("jobTags")
+        custom_model_tags = params.get("customModelTags")
+        training_data_config = params.get("trainingDataConfig")
+        validation_data_config = params.get("validationDataConfig")
+        output_data_config = params.get("outputDataConfig")
+        hyper_parameters = params.get("hyperParameters")
+        vpc_config = params.get("vpcConfig")
+        job_arn = self.bedrock_backend.create_model_customization_job(
+            job_name=job_name,
+            custom_model_name=custom_model_name,
+            role_arn=role_arn,
+            client_request_token=client_request_token,
+            base_model_identifier=base_model_identifier,
+            customization_type=customization_type,
+            custom_model_kms_key_id=custom_model_kms_key_id,
+            job_tags=job_tags,
+            custom_model_tags=custom_model_tags,
+            training_data_config=training_data_config,
+            validation_data_config=validation_data_config,
+            output_data_config=output_data_config,
+            hyper_parameters=hyper_parameters,
+            vpc_config=vpc_config,
+        )
+        return json.dumps(dict(jobArn=job_arn))
+
+    def get_model_customization_job(self) -> str:
+        job_identifier = self.path.split("/")[-1]
+        model_customization_job = self.bedrock_backend.get_model_customization_job(
+            job_identifier=job_identifier
+        )
+        return json.dumps(dict(model_customization_job.to_dict()))
+
+    def get_model_invocation_logging_configuration(self) -> str:
+        logging_config = (
+            self.bedrock_backend.get_model_invocation_logging_configuration()
+        )
+        return json.dumps(dict(loggingConfig=logging_config))
+
+    def put_model_invocation_logging_configuration(self) -> None:
+        params = json.loads(self.body)
+        logging_config = params.get("loggingConfig")
+        self.bedrock_backend.put_model_invocation_logging_configuration(
+            logging_config=logging_config
+        )
+        return
+
+    def tag_resource(self) -> None:
+        params = json.loads(self.body)
+        resource_arn = params.get("resourceARN")
+        tags = params.get("tags")
+        self.bedrock_backend.tag_resource(
+            resource_arn=resource_arn,
+            tags=tags,
+        )
+        return
+
+    def untag_resource(self) -> str:
+        params = json.loads(self.body)
+        resource_arn = params.get("resourceARN")
+        tag_keys = params.get("tagKeys")
+        self.bedrock_backend.untag_resource(
+            resource_arn=resource_arn,
+            tag_keys=tag_keys,
+        )
+        return json.dumps(dict())
+
+    def list_tags_for_resource(self) -> str:
+        params = json.loads(self.body)
+        resource_arn = params.get("resourceARN")
+        tags = self.bedrock_backend.list_tags_for_resource(
+            resource_arn=resource_arn,
+        )
+        return json.dumps(dict(tags=tags))
+
+    def get_custom_model(self) -> str:
+        model_identifier = unquote(self.path.split("/")[-1])
+        custom_model = self.bedrock_backend.get_custom_model(
+            model_identifier=model_identifier
+        )
+        return json.dumps(dict(custom_model.to_dict()))
+
+    def list_custom_models(self) -> str:
+        params = self._get_params()
+        creation_time_before = params.get("creationTimeBefore")
+        creation_time_after = params.get("creationTimeAfter")
+        name_contains = params.get("nameContains")
+        base_model_arn_equals = params.get("baseModelArnEquals")
+        foundation_model_arn_equals = params.get("foundationModelArnEquals")
+        max_results = params.get("maxResults")
+        next_token = params.get("nextToken")
+        sort_by = params.get("sortBy")
+        sort_order = params.get("sortOrder")
+
+        max_results = int(max_results) if max_results else None
+        model_summaries, next_token = self.bedrock_backend.list_custom_models(
+            creation_time_before=creation_time_before,
+            creation_time_after=creation_time_after,
+            name_contains=name_contains,
+            base_model_arn_equals=base_model_arn_equals,
+            foundation_model_arn_equals=foundation_model_arn_equals,
+            max_results=max_results,
+            next_token=next_token,
+            sort_by=sort_by,
+            sort_order=sort_order,
+        )
+        return json.dumps(dict(nextToken=next_token, modelSummaries=model_summaries))
+
+    def list_model_customization_jobs(self) -> str:
+        params = self._get_params()
+        creation_time_after = params.get("creationTimeAfter")
+        creation_time_before = params.get("creationTimeBefore")
+        status_equals = params.get("statusEquals")
+        name_contains = params.get("nameContains")
+        max_results = params.get("maxResults")
+        next_token = params.get("nextToken")
+        sort_by = params.get("sortBy")
+        sort_order = params.get("sortOrder")
+
+        max_results = int(max_results) if max_results else None
+        (
+            model_customization_job_summaries,
+            next_token,
+        ) = self.bedrock_backend.list_model_customization_jobs(
+            creation_time_after=creation_time_after,
+            creation_time_before=creation_time_before,
+            status_equals=status_equals,
+            name_contains=name_contains,
+            max_results=max_results,
+            next_token=next_token,
+            sort_by=sort_by,
+            sort_order=sort_order,
+        )
+        return json.dumps(
+            dict(
+                nextToken=next_token,
+                modelCustomizationJobSummaries=model_customization_job_summaries,
+            )
+        )
+
+    def delete_custom_model(self) -> str:
+        model_identifier = self.path.split("/")[-1]
+        self.bedrock_backend.delete_custom_model(
+            model_identifier=model_identifier,
+        )
+        return json.dumps(dict())
+
+    def stop_model_customization_job(self) -> str:
+        job_identifier = self.path.split("/")[-2]
+        self.bedrock_backend.stop_model_customization_job(
+            job_identifier=job_identifier,
+        )
+        return json.dumps(dict())
+
+    def delete_model_invocation_logging_configuration(self) -> str:
+        self.bedrock_backend.delete_model_invocation_logging_configuration()
+        return json.dumps(dict())
diff --git a/moto/bedrock/urls.py b/moto/bedrock/urls.py
new file mode 100644
index 000000000..079762694
--- /dev/null
+++ b/moto/bedrock/urls.py
@@ -0,0 +1,29 @@
+"""bedrock base URL and path."""
+
+from ..bedrockagent.responses import AgentsforBedrockResponse
+from .responses import BedrockResponse
+
+url_bases = [
+    r"https?://bedrock\.(.+)\.amazonaws\.com",
+]
+
+url_paths = {
+    "{0}/.*$": BedrockResponse.dispatch,
+    "{0}/agents/?$": AgentsforBedrockResponse.dispatch,
+    "{0}/agents/(?P<agent_name>[^/]+)/$": AgentsforBedrockResponse.dispatch,
+    "{0}/custom-models$": BedrockResponse.dispatch,
+    "{0}/custom-models/(?P<modelIdentifier>[^/]+)$": BedrockResponse.dispatch,
+    "{0}/custom-models/(?P<arn_prefix>[^/]+)/(?P<jobIdentifier>[^/]+)$": BedrockResponse.dispatch,
+    "{0}/knowledgebases$": AgentsforBedrockResponse.dispatch,
+    "{0}/knowledgebases/(?P<kb_name>[^/]+)$": AgentsforBedrockResponse.dispatch,
+    "{0}/knowledgebases/(?P<kb_name>[^/]+)/$": AgentsforBedrockResponse.dispatch,
+    "{0}/listTagsForResource$": BedrockResponse.dispatch,
+    "{0}/logging/modelinvocations$": BedrockResponse.dispatch,
+    "{0}/model-customization-jobs$": BedrockResponse.dispatch,
+    "{0}/model-customization-jobs/(?P<jobIdentifier>[^/]+)$": BedrockResponse.dispatch,
+    "{0}/model-customization-jobs/(?P<jobIdentifier>[^/]+)/stop$": BedrockResponse.dispatch,
+    "{0}/tags/(?P<resource_arn>[^/]+)$": AgentsforBedrockResponse.dispatch,
+    "{0}/tags/(?P<arn_prefix>[^/]+)/(?P<name>[^/]+)$": AgentsforBedrockResponse.dispatch,
+    "{0}/tagResource$": BedrockResponse.dispatch,
+    "{0}/untagResource$": BedrockResponse.dispatch,
+}
diff --git a/moto/bedrockagent/__init__.py b/moto/bedrockagent/__init__.py
new file mode 100644
index 000000000..5a019b11b
--- /dev/null
+++ b/moto/bedrockagent/__init__.py
@@ -0,0 +1,1 @@
+from .models import bedrockagent_backends  # noqa: F401
diff --git a/moto/bedrockagent/exceptions.py b/moto/bedrockagent/exceptions.py
new file mode 100644
index 000000000..97d94a14f
--- /dev/null
+++ b/moto/bedrockagent/exceptions.py
@@ -0,0 +1,26 @@
+"""Exceptions raised by the bedrockagent service."""
+
+from moto.core.exceptions import JsonRESTError
+
+
+class AgentsforBedrockClientError(JsonRESTError):
+    code = 400
+
+
+class ResourceNotFoundException(AgentsforBedrockClientError):
+    def __init__(self, msg: str):
+        super().__init__("ResourceNotFoundException", f"{msg}")
+
+
+class ConflictException(AgentsforBedrockClientError):
+    def __init__(self, msg: str):
+        super().__init__("ConflictException", f"{msg}")
+
+
+class ValidationException(AgentsforBedrockClientError):
+    def __init__(self, msg: str):
+        super().__init__(
+            "ValidationException",
+            "Input validation failed. Check your request parameters and retry the request.",
+            f"{msg}",
+        )
diff --git a/moto/bedrockagent/models.py b/moto/bedrockagent/models.py
new file mode 100644
index 000000000..245c23c73
--- /dev/null
+++ b/moto/bedrockagent/models.py
@@ -0,0 +1,322 @@
+"""AgentsforBedrockBackend class with methods for supported APIs."""
+
+from typing import Any, Dict, List, Optional, Tuple
+
+from moto.bedrockagent.exceptions import (
+    ConflictException,
+    ResourceNotFoundException,
+    ValidationException,
+)
+from moto.core.base_backend import BackendDict, BaseBackend
+from moto.core.common_models import BaseModel
+from moto.core.utils import unix_time
+from moto.moto_api._internal import mock_random
+from moto.utilities.paginator import paginate
+from moto.utilities.tagging_service import TaggingService
+
+
+class Agent(BaseModel):
+    def __init__(
+        self,
+        agent_name: str,
+        agent_resource_role_arn: str,
+        region_name: str,
+        account_id: str,
+        client_token: Optional[str],
+        instruction: Optional[str],
+        foundation_model: Optional[str],
+        description: Optional[str],
+        idle_session_ttl_in_seconds: Optional[int],
+        customer_encryption_key_arn: Optional[str],
+        prompt_override_configuration: Optional[Dict[str, Any]],
+    ):
+        self.agent_name = agent_name
+        self.client_token = client_token
+        self.instruction = instruction
+        self.foundation_model = foundation_model
+        self.description = description
+        self.idle_session_ttl_in_seconds = idle_session_ttl_in_seconds
+        self.agent_resource_role_arn = agent_resource_role_arn
+        self.customer_encryption_key_arn = customer_encryption_key_arn
+        self.prompt_override_configuration = prompt_override_configuration
+        self.region_name = region_name
+        self.account_id = account_id
+        self.created_at = unix_time()
+        self.updated_at = unix_time()
+        self.prepared_at = unix_time()
+        self.agent_status = "PREPARED"
+        self.agent_id = self.agent_name + str(mock_random.uuid4())[:8]
+        self.agent_arn = f"arn:aws:bedrock:{self.region_name}:{self.account_id}:agent/{self.agent_id}"
+        self.agent_version = "1.0"
+        self.failure_reasons: List[str] = []
+        self.recommended_actions = ["action"]
+
+    def to_dict(self) -> Dict[str, Any]:
+        dct = {
+            "agentId": self.agent_id,
+            "agentName": self.agent_name,
+            "agentArn": self.agent_arn,
+            "agentVersion": self.agent_version,
+            "clientToken": self.client_token,
+            "instruction": self.instruction,
+            "agentStatus": self.agent_status,
+            "foundationModel": self.foundation_model,
+            "description": self.description,
+            "idleSessionTTLInSeconds": self.idle_session_ttl_in_seconds,
+            "agentResourceRoleArn": self.agent_resource_role_arn,
+            "customerEncryptionKeyArn": self.customer_encryption_key_arn,
+            "createdAt": self.created_at,
+            "updatedAt": self.updated_at,
+            "preparedAt": self.prepared_at,
+            "failureReasons": self.failure_reasons,
+            "recommendedActions": self.recommended_actions,
+            "promptOverrideConfiguration": self.prompt_override_configuration,
+        }
+        return {k: v for k, v in dct.items() if v}
+
+    def dict_summary(self) -> Dict[str, Any]:
+        dct = {
+            "agentId": self.agent_id,
+            "agentName": self.agent_name,
+            "agentStatus": self.agent_status,
+            "description": self.description,
+            "updatedAt": self.updated_at,
+            "latestAgentVersion": self.agent_version,
+        }
+        return {k: v for k, v in dct.items() if v}
+
+
+class KnowledgeBase(BaseModel):
+    def __init__(
+        self,
+        name: str,
+        role_arn: str,
+        region_name: str,
+        account_id: str,
+        knowledge_base_configuration: Dict[str, Any],
+        storage_configuration: Dict[str, Any],
+        client_token: Optional[str],
+        description: Optional[str],
+    ):
+        self.client_token = client_token
+        self.name = name
+        self.description = description
+        self.role_arn = role_arn
+        if knowledge_base_configuration["type"] != "VECTOR":
+            raise ValidationException(
+                "Validation error detected: "
+                f"Value '{knowledge_base_configuration['type']}' at 'knowledgeBaseConfiguration' failed to satisfy constraint: "
+                "Member must contain 'type' as 'VECTOR'"
+            )
+        self.knowledge_base_configuration = knowledge_base_configuration
+        if storage_configuration["type"] not in [
+            "OPENSEARCH_SERVERLESS",
+            "PINECONE",
+            "REDIS_ENTERPRISE_CLOUD",
+            "RDS",
+        ]:
+            raise ValidationException(
+                "Validation error detected: "
+                f"Value '{storage_configuration['type']}' at 'storageConfiguration' failed to satisfy constraint: "
+                "Member 'type' must be one of: OPENSEARCH_SERVERLESS | PINECONE | REDIS_ENTERPRISE_CLOUD | RDS"
+            )
+        self.storage_configuration = storage_configuration
+        self.region_name = region_name
+        self.account_id = account_id
+        self.knowledge_base_id = self.name + str(mock_random.uuid4())[:8]
+        self.knowledge_base_arn = f"arn:aws:bedrock:{self.region_name}:{self.account_id}:knowledge-base/{self.knowledge_base_id}"
+        self.created_at = unix_time()
+        self.updated_at = unix_time()
+        self.status = "Active"
+        self.failure_reasons: List[str] = []
+
+    def to_dict(self) -> Dict[str, Any]:
+        dct = {
+            "knowledgeBaseId": self.knowledge_base_id,
+            "name": self.name,
+            "knowledgeBaseArn": self.knowledge_base_arn,
+            "description": self.description,
+            "roleArn": self.role_arn,
+            "knowledgeBaseConfiguration": self.knowledge_base_configuration,
+            "storageConfiguration": self.storage_configuration,
+            "status": self.status,
+            "createdAt": self.created_at,
+            "updatedAt": self.updated_at,
+            "failureReasons": self.failure_reasons,
+        }
+        return {k: v for k, v in dct.items() if v}
+
+    def dict_summary(self) -> Dict[str, Any]:
+        dct = {
+            "knowledgeBaseId": self.knowledge_base_id,
+            "name": self.name,
+            "description": self.description,
+            "status": self.status,
+            "updatedAt": self.updated_at,
+        }
+        return {k: v for k, v in dct.items() if v}
+
+
+class AgentsforBedrockBackend(BaseBackend):
+    """Implementation of AgentsforBedrock APIs."""
+
+    PAGINATION_MODEL = {
+        "list_agents": {
+            "input_token": "next_token",
+            "limit_key": "max_results",
+            "limit_default": 100,
+            "unique_attribute": "agentId",
+        },
+        "list_knowledge_bases": {
+            "input_token": "next_token",
+            "limit_key": "max_results",
+            "limit_default": 100,
+            "unique_attribute": "knowledgeBaseId",
+        },
+    }
+
+    def __init__(self, region_name: str, account_id: str):
+        super().__init__(region_name, account_id)
+        self.agents: Dict[str, Agent] = {}
+        self.knowledge_bases: Dict[str, KnowledgeBase] = {}
+        self.tagger = TaggingService()
+
+    def _list_arns(self) -> List[str]:
+        return [agent.agent_arn for agent in self.agents.values()] + [
+            knowledge_base.knowledge_base_arn
+            for knowledge_base in self.knowledge_bases.values()
+        ]
+
+    def create_agent(
+        self,
+        agent_name: str,
+        agent_resource_role_arn: str,
+        client_token: Optional[str],
+        instruction: Optional[str],
+        foundation_model: Optional[str],
+        description: Optional[str],
+        idle_session_ttl_in_seconds: Optional[int],
+        customer_encryption_key_arn: Optional[str],
+        tags: Optional[Dict[str, str]],
+        prompt_override_configuration: Optional[Dict[str, Any]],
+    ) -> Agent:
+        agent = Agent(
+            agent_name,
+            agent_resource_role_arn,
+            self.region_name,
+            self.account_id,
+            client_token,
+            instruction,
+            foundation_model,
+            description,
+            idle_session_ttl_in_seconds,
+            customer_encryption_key_arn,
+            prompt_override_configuration,
+        )
+        self.agents[agent.agent_id] = agent
+        if tags:
+            self.tag_resource(agent.agent_arn, tags)
+        return agent
+
+    def get_agent(self, agent_id: str) -> Agent:
+        if agent_id not in self.agents:
+            raise ResourceNotFoundException(f"Agent {agent_id} not found")
+        return self.agents[agent_id]
+
+    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    def list_agents(
+        self, max_results: Optional[int], next_token: Optional[str]
+    ) -> List[Any]:
+        agent_summaries = [agent.dict_summary() for agent in self.agents.values()]
+        return agent_summaries
+
+    def delete_agent(
+        self, agent_id: str, skip_resource_in_use_check: Optional[bool]
+    ) -> Tuple[str, str]:
+        if agent_id in self.agents:
+            if (
+                skip_resource_in_use_check
+                or self.agents[agent_id].agent_status == "PREPARED"
+            ):
+                self.agents[agent_id].agent_status = "DELETING"
+                agent_status = self.agents[agent_id].agent_status
+                del self.agents[agent_id]
+            else:
+                raise ConflictException(f"Agent {agent_id} is in use")
+        else:
+            raise ResourceNotFoundException(f"Agent {agent_id} not found")
+        return agent_id, agent_status
+
+    def create_knowledge_base(
+        self,
+        name: str,
+        role_arn: str,
+        knowledge_base_configuration: Dict[str, Any],
+        storage_configuration: Dict[str, Any],
+        client_token: Optional[str],
+        description: Optional[str],
+        tags: Optional[Dict[str, str]],
+    ) -> KnowledgeBase:
+        knowledge_base = KnowledgeBase(
+            name,
+            role_arn,
+            self.region_name,
+            self.account_id,
+            knowledge_base_configuration,
+            storage_configuration,
+            client_token,
+            description,
+        )
+        self.knowledge_bases[knowledge_base.knowledge_base_id] = knowledge_base
+        if tags:
+            self.tag_resource(knowledge_base.knowledge_base_arn, tags)
+        return knowledge_base
+
+    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    def list_knowledge_bases(
+        self, max_results: Optional[int], next_token: Optional[str]
+    ) -> List[Any]:
+        knowledge_base_summaries = [
+            knowledge_base.dict_summary()
+            for knowledge_base in self.knowledge_bases.values()
+        ]
+        return knowledge_base_summaries
+
+    def delete_knowledge_base(self, knowledge_base_id: str) -> Tuple[str, str]:
+        if knowledge_base_id in self.knowledge_bases:
+            self.knowledge_bases[knowledge_base_id].status = "DELETING"
+            knowledge_base_status = self.knowledge_bases[knowledge_base_id].status
+            del self.knowledge_bases[knowledge_base_id]
+        else:
+            raise ResourceNotFoundException(
+                f"Knowledge base {knowledge_base_id} not found"
+            )
+        return knowledge_base_id, knowledge_base_status
+
+    def get_knowledge_base(self, knowledge_base_id: str) -> KnowledgeBase:
+        if knowledge_base_id not in self.knowledge_bases:
+            raise ResourceNotFoundException(
+                f"Knowledge base {knowledge_base_id} not found"
+            )
+        return self.knowledge_bases[knowledge_base_id]
+
+    def tag_resource(self, resource_arn: str, tags: Dict[str, str]) -> None:
+        if resource_arn not in self._list_arns():
+            raise ResourceNotFoundException(f"Resource {resource_arn} not found")
+        tags_input = TaggingService.convert_dict_to_tags_input(tags or {})
+        self.tagger.tag_resource(resource_arn, tags_input)
+        return
+
+    def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
+        if resource_arn not in self._list_arns():
+            raise ResourceNotFoundException(f"Resource {resource_arn} not found")
+        self.tagger.untag_resource_using_names(resource_arn, tag_keys)
+        return
+
+    def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]:
+        if resource_arn not in self._list_arns():
+            raise ResourceNotFoundException(f"Resource {resource_arn} not found")
+        return self.tagger.get_tag_dict_for_resource(resource_arn)
+
+
+bedrockagent_backends = BackendDict(AgentsforBedrockBackend, "bedrock")
diff --git a/moto/bedrockagent/responses.py b/moto/bedrockagent/responses.py
new file mode 100644
index 000000000..7d3bada0e
--- /dev/null
+++ b/moto/bedrockagent/responses.py
@@ -0,0 +1,153 @@
+"""Handles incoming bedrockagent requests, invokes methods, returns responses."""
+
+import json
+from urllib.parse import unquote
+
+from moto.core.responses import BaseResponse
+
+from .models import AgentsforBedrockBackend, bedrockagent_backends
+
+
+class AgentsforBedrockResponse(BaseResponse):
+    """Handler for AgentsforBedrock requests and responses."""
+
+    def __init__(self) -> None:
+        super().__init__(service_name="bedrock-agent")
+
+    @property
+    def bedrockagent_backend(self) -> AgentsforBedrockBackend:
+        """Return backend instance specific for this region."""
+        return bedrockagent_backends[self.current_account][self.region]
+
+    def create_agent(self) -> str:
+        params = json.loads(self.body)
+        agent_name = params.get("agentName")
+        client_token = params.get("clientToken")
+        instruction = params.get("instruction")
+        foundation_model = params.get("foundationModel")
+        description = params.get("description")
+        idle_session_ttl_in_seconds = params.get("idleSessionTTLInSeconds")
+        agent_resource_role_arn = params.get("agentResourceRoleArn")
+        customer_encryption_key_arn = params.get("customerEncryptionKeyArn")
+        tags = params.get("tags")
+        prompt_override_configuration = params.get("promptOverrideConfiguration")
+        agent = self.bedrockagent_backend.create_agent(
+            agent_name=agent_name,
+            client_token=client_token,
+            instruction=instruction,
+            foundation_model=foundation_model,
+            description=description,
+            idle_session_ttl_in_seconds=idle_session_ttl_in_seconds,
+            agent_resource_role_arn=agent_resource_role_arn,
+            customer_encryption_key_arn=customer_encryption_key_arn,
+            tags=tags,
+            prompt_override_configuration=prompt_override_configuration,
+        )
+        return json.dumps({"agent": dict(agent.to_dict())})
+
+    def get_agent(self) -> str:
+        agent_id = self.path.split("/")[-2]
+        agent = self.bedrockagent_backend.get_agent(agent_id=agent_id)
+        return json.dumps({"agent": dict(agent.to_dict())})
+
+    def list_agents(self) -> str:
+        params = json.loads(self.body)
+        max_results = params.get("maxResults")
+        next_token = params.get("nextToken")
+        max_results = int(max_results) if max_results else None
+        agents, next_token = self.bedrockagent_backend.list_agents(
+            max_results=max_results,
+            next_token=next_token,
+        )
+        return json.dumps(
+            {
+                "agentSummaries": agents,
+                "nextToken": next_token,
+            }
+        )
+
+    def delete_agent(self) -> str:
+        params = self._get_params()
+        skip_resource_in_use_check = params.get("skipResourceInUseCheck")
+        agent_id = self.path.split("/")[-2]
+        agent_id, agent_status = self.bedrockagent_backend.delete_agent(
+            agent_id=agent_id, skip_resource_in_use_check=skip_resource_in_use_check
+        )
+        return json.dumps({"agentId": agent_id, "agentStatus": agent_status})
+
+    def create_knowledge_base(self) -> str:
+        params = json.loads(self.body)
+        client_token = params.get("clientToken")
+        name = params.get("name")
+        description = params.get("description")
+        role_arn = params.get("roleArn")
+        knowledge_base_configuration = params.get("knowledgeBaseConfiguration")
+        storage_configuration = params.get("storageConfiguration")
+        tags = params.get("tags")
+        knowledge_base = self.bedrockagent_backend.create_knowledge_base(
+            client_token=client_token,
+            name=name,
+            description=description,
+            role_arn=role_arn,
+            knowledge_base_configuration=knowledge_base_configuration,
+            storage_configuration=storage_configuration,
+            tags=tags,
+        )
+        return json.dumps({"knowledgeBase": dict(knowledge_base.to_dict())})
+
+    def list_knowledge_bases(self) -> str:
+        params = json.loads(self.body)
+        max_results = params.get("maxResults")
+        next_token = params.get("nextToken")
+        max_results = int(max_results) if max_results else None
+        knowledge_bases, next_token = self.bedrockagent_backend.list_knowledge_bases(
+            max_results=max_results,
+            next_token=next_token,
+        )
+        return json.dumps(
+            {
+                "knowledgeBaseSummaries": knowledge_bases,
+                "nextToken": next_token,
+            }
+        )
+
+    def delete_knowledge_base(self) -> str:
+        knowledge_base_id = self.path.split("/")[-1]
+        (
+            knowledge_base_id,
+            knowledge_base_status,
+        ) = self.bedrockagent_backend.delete_knowledge_base(
+            knowledge_base_id=knowledge_base_id
+        )
+        return json.dumps(
+            {"knowledgeBaseId": knowledge_base_id, "status": knowledge_base_status}
+        )
+
+    def get_knowledge_base(self) -> str:
+        knowledge_base_id = self.path.split("/")[-1]
+        knowledge_base = self.bedrockagent_backend.get_knowledge_base(
+            knowledge_base_id=knowledge_base_id
+        )
+        return json.dumps({"knowledgeBase": knowledge_base.to_dict()})
+
+    def tag_resource(self) -> str:
+        params = json.loads(self.body)
+        resource_arn = unquote(self.path.split("/tags/")[-1])
+        tags = params.get("tags")
+        self.bedrockagent_backend.tag_resource(resource_arn=resource_arn, tags=tags)
+        return json.dumps(dict())
+
+    def untag_resource(self) -> str:
+        resource_arn = unquote(self.path.split("/tags/")[-1])
+        tag_keys = self.querystring.get("tagKeys", [])
+        self.bedrockagent_backend.untag_resource(
+            resource_arn=resource_arn, tag_keys=tag_keys
+        )
+        return json.dumps(dict())
+
+    def list_tags_for_resource(self) -> str:
+        resource_arn = unquote(self.path.split("/tags/")[-1])
+        tags = self.bedrockagent_backend.list_tags_for_resource(
+            resource_arn=resource_arn
+        )
+        return json.dumps(dict(tags=tags))
diff --git a/moto/bedrockagent/urls.py b/moto/bedrockagent/urls.py
new file mode 100644
index 000000000..bb7ce1624
--- /dev/null
+++ b/moto/bedrockagent/urls.py
@@ -0,0 +1,11 @@
+"""bedrockagent base URL and path."""
+
+from .responses import AgentsforBedrockResponse
+
+url_bases = [
+    r"https?://bedrock-agent\.(.+)\.amazonaws\.com",
+]
+
+url_paths = {
+    "{0}/.*$": AgentsforBedrockResponse.dispatch,
+}
diff --git a/moto/core/responses.py b/moto/core/responses.py
index 83770e028..9ed7410f7 100644
--- a/moto/core/responses.py
+++ b/moto/core/responses.py
@@ -399,6 +399,8 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
         self.path = self.parsed_url.path
         if self.is_werkzeug_request and "RAW_URI" in request.environ:
             self.raw_path = urlparse(request.environ.get("RAW_URI")).path
+            if self.raw_path and not self.raw_path.startswith("/"):
+                self.raw_path = f"/{self.raw_path}"
         else:
             self.raw_path = self.path
 
@@ -491,6 +493,9 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
 
         def _convert(elem: str, is_last: bool) -> str:
             if not re.match("^{.*}$", elem):
+                # URL-parts sometimes contain a $
+                # Like Greengrass: /../deployments/$reset
+                # We don't want to our regex to think this marks an end-of-line, so let's escape it
                 return elem.replace("$", r"\$")
             name = (
                 elem.replace("{", "")
@@ -549,7 +554,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
         if match:
             return match.split(".")[-1]
         # get action from method and uri
-        return self._get_action_from_method_and_request_uri(self.method, self.path)
+        return self._get_action_from_method_and_request_uri(self.method, self.raw_path)
 
     def call_action(self) -> TYPE_RESPONSE:
         headers = self.response_headers
diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py
index 869e22a49..bca8131fb 100644
--- a/moto/dynamodb/exceptions.py
+++ b/moto/dynamodb/exceptions.py
@@ -381,3 +381,9 @@ class UnknownKeyType(MockValidationException):
     def __init__(self, key_type: str, position: str):
         msg = f"1 validation error detected: Value '{key_type}' at '{position}' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
         super().__init__(msg)
+
+
+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)
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
index c47b97797..c6634e195 100644
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -10,6 +10,7 @@ from moto.dynamodb.comparisons import get_expected, get_filter_expression
 from moto.dynamodb.exceptions import (
     BackupNotFoundException,
     ConditionalCheckFailed,
+    DeletionProtectedException,
     ItemSizeTooLarge,
     ItemSizeToUpdateTooLarge,
     MockValidationException,
@@ -71,6 +72,10 @@ class DynamoDBBackend(BaseBackend):
     def delete_table(self, name: str) -> Table:
         if name not in self.tables:
             raise ResourceNotFoundException
+        table_for_deletion = self.tables.get(name)
+        if isinstance(table_for_deletion, Table):
+            if table_for_deletion.deletion_protection_enabled:
+                raise DeletionProtectedException(name)
         return self.tables.pop(name)
 
     def describe_endpoints(self) -> List[Dict[str, Union[int, str]]]:
@@ -137,6 +142,7 @@ class DynamoDBBackend(BaseBackend):
         throughput: Dict[str, Any],
         billing_mode: str,
         stream_spec: Dict[str, Any],
+        deletion_protection_enabled: bool,
     ) -> Table:
         table = self.get_table(name)
         if attr_definitions:
@@ -149,6 +155,10 @@ class DynamoDBBackend(BaseBackend):
             table = self.update_table_billing_mode(name, billing_mode)
         if stream_spec:
             table = self.update_table_streams(name, stream_spec)
+        if deletion_protection_enabled:
+            table = self.update_table_deletion_protection_enabled(
+                name, deletion_protection_enabled
+            )
         return table
 
     def update_table_throughput(self, name: str, throughput: Dict[str, int]) -> Table:
@@ -161,6 +171,13 @@ class DynamoDBBackend(BaseBackend):
         table.billing_mode = billing_mode
         return table
 
+    def update_table_deletion_protection_enabled(
+        self, name: str, deletion_protection_enabled: bool
+    ) -> Table:
+        table = self.tables[name]
+        table.deletion_protection_enabled = deletion_protection_enabled
+        return table
+
     def update_table_streams(
         self, name: str, stream_specification: Dict[str, Any]
     ) -> Table:
diff --git a/moto/dynamodb/models/table.py b/moto/dynamodb/models/table.py
index 371f482e1..43c55c666 100644
--- a/moto/dynamodb/models/table.py
+++ b/moto/dynamodb/models/table.py
@@ -243,6 +243,7 @@ class Table(CloudFormationModel):
         streams: Optional[Dict[str, Any]] = None,
         sse_specification: Optional[Dict[str, Any]] = None,
         tags: Optional[List[Dict[str, str]]] = None,
+        deletion_protection_enabled: Optional[bool] = False,
     ):
         self.name = table_name
         self.account_id = account_id
@@ -306,6 +307,7 @@ class Table(CloudFormationModel):
             self.sse_specification["KMSMasterKeyId"] = self._get_default_encryption_key(
                 account_id, region
             )
+        self.deletion_protection_enabled = deletion_protection_enabled
 
     def _get_default_encryption_key(self, account_id: str, region: str) -> str:
         from moto.kms import kms_backends
@@ -443,6 +445,7 @@ class Table(CloudFormationModel):
                     index.describe() for index in self.global_indexes
                 ],
                 "LocalSecondaryIndexes": [index.describe() for index in self.indexes],
+                "DeletionProtectionEnabled": self.deletion_protection_enabled,
             }
         }
         if self.latest_stream_label:
diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py
index 24019ebe1..49131ae72 100644
--- a/moto/dynamodb/responses.py
+++ b/moto/dynamodb/responses.py
@@ -302,6 +302,7 @@ class DynamoHandler(BaseResponse):
         streams = body.get("StreamSpecification")
         # Get any tags
         tags = body.get("Tags", [])
+        deletion_protection_enabled = body.get("DeletionProtectionEnabled", False)
 
         table = self.dynamodb_backend.create_table(
             table_name,
@@ -314,6 +315,7 @@ class DynamoHandler(BaseResponse):
             billing_mode=billing_mode,
             sse_specification=sse_spec,
             tags=tags,
+            deletion_protection_enabled=deletion_protection_enabled,
         )
         return dynamo_json_dump(table.describe())
 
@@ -431,6 +433,7 @@ class DynamoHandler(BaseResponse):
         throughput = self.body.get("ProvisionedThroughput", None)
         billing_mode = self.body.get("BillingMode", None)
         stream_spec = self.body.get("StreamSpecification", None)
+        deletion_protection_enabled = self.body.get("DeletionProtectionEnabled")
         table = self.dynamodb_backend.update_table(
             name=name,
             attr_definitions=attr_definitions,
@@ -438,6 +441,7 @@ class DynamoHandler(BaseResponse):
             throughput=throughput,
             billing_mode=billing_mode,
             stream_spec=stream_spec,
+            deletion_protection_enabled=deletion_protection_enabled,
         )
         return dynamo_json_dump(table.describe())
 
diff --git a/moto/ec2/models/security_groups.py b/moto/ec2/models/security_groups.py
index 532313652..81760baf5 100644
--- a/moto/ec2/models/security_groups.py
+++ b/moto/ec2/models/security_groups.py
@@ -812,6 +812,9 @@ class SecurityGroupBackend:
             group_name_or_id, vpc_id
         )  # type: ignore[assignment]
 
+        if group is None:
+            raise InvalidSecurityGroupNotFoundError(group_name_or_id)
+
         if security_rule_ids:
             group.ingress_rules = [
                 rule for rule in group.ingress_rules if rule.id not in security_rule_ids
@@ -983,6 +986,9 @@ class SecurityGroupBackend:
             group_name_or_id, vpc_id
         )  # type: ignore[assignment]
 
+        if group is None:
+            raise InvalidSecurityGroupNotFoundError(group_name_or_id)
+
         if security_rule_ids:
             group.egress_rules = [
                 rule for rule in group.egress_rules if rule.id not in security_rule_ids
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 fa699a34c..4576e96d1 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
@@ -4091,6 +4091,50 @@
   "InstanceType": "r6i.xlarge",
   "Location": "euw3-az3"
  },
+ {
+  "InstanceType": "r7i.12xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.16xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.24xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.2xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.48xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.4xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.8xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.large",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.metal-24xl",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.metal-48xl",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "r7i.xlarge",
+  "Location": "euw3-az3"
+ },
  {
   "InstanceType": "t2.2xlarge",
   "Location": "euw3-az3"
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 7e3a1787f..a95733be8 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
@@ -4091,6 +4091,50 @@
   "InstanceType": "r6i.xlarge",
   "Location": "eu-west-3c"
  },
+ {
+  "InstanceType": "r7i.12xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.16xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.24xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.2xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.48xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.4xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.8xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.large",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.metal-24xl",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.metal-48xl",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "r7i.xlarge",
+  "Location": "eu-west-3c"
+ },
  {
   "InstanceType": "t2.2xlarge",
   "Location": "eu-west-3c"
diff --git a/moto/panorama/models.py b/moto/panorama/models.py
index db4980875..c32589ebc 100644
--- a/moto/panorama/models.py
+++ b/moto/panorama/models.py
@@ -1,5 +1,6 @@
 import base64
 import json
+import uuid
 from datetime import datetime, timedelta
 from typing import Any, Dict, List, Optional, Union
 
@@ -8,7 +9,12 @@ from dateutil.tz import tzutc
 from moto.core.base_backend import BackendDict, BaseBackend
 from moto.core.common_models import BaseModel
 from moto.moto_api._internal.managed_state_model import ManagedState
-from moto.panorama.utils import deep_convert_datetime_to_isoformat, hash_device_name
+from moto.panorama.utils import (
+    arn_formatter,
+    deep_convert_datetime_to_isoformat,
+    generate_package_id,
+    hash_device_name,
+)
 from moto.utilities.paginator import paginate
 
 from .exceptions import (
@@ -22,6 +28,12 @@ PAGINATION_MODEL = {
         "limit_default": 123,
         "unique_attribute": "device_id",
     },
+    "list_nodes": {
+        "input_token": "next_token",
+        "limit_key": "max_results",
+        "limit_default": 123,
+        "unique_attribute": "package_id",
+    },
 }
 
 
@@ -91,9 +103,7 @@ class Device(BaseObject):
         self.certificates = base64.b64encode("certificate".encode("utf-8")).decode(
             "utf-8"
         )
-        self.arn = (
-            f"arn:aws:panorama:{self.region_name}:{self.account_id}:device/{self.name}"
-        )
+        self.arn = arn_formatter("device", self.name, self.account_id, self.region_name)
         self.device_id = f"device-{hash_device_name(name)}"
         self.iot_thing_name = ""
 
@@ -221,10 +231,95 @@ class Device(BaseObject):
         return {"DeviceId": self.device_id}
 
 
+class Package(BaseObject):
+    def __init__(
+        self,
+        category: str,
+        description: str,
+        name: str,
+        account_id: str,
+        region_name: str,
+        package_name: str,
+        package_version: str,
+    ):
+        self.category = category
+        self.description = description
+        self.name = name
+        now = datetime.now(tzutc())
+        self.created_time = now
+        self.last_updated_time = now
+        self.package_name = package_name
+        self.patch_version = generate_package_id(self.package_name)
+        self.package_version = package_version
+        self.output_package_name = f"{self.package_name}-{self.package_version}-{self.patch_version[:8]}-{self.name}"
+        self.owner_account = account_id
+        self.package_id = f"package-{hash_device_name(package_name)}"
+        self.package_arn = arn_formatter(
+            "package", self.package_id, account_id, region_name
+        )
+
+    def response_object(self) -> Dict[str, Any]:
+        response_object = super().gen_response_object()
+        response_object = deep_convert_datetime_to_isoformat(response_object)
+        return response_object
+
+    def response_listed(self) -> Dict[str, Any]:
+        package_response = self.response_object()
+        return package_response
+
+
+class Node(BaseObject):
+    def __init__(
+        self,
+        job_id: str,
+        job_tags: List[Dict[str, Union[str, Dict[str, str]]]],
+        node_description: str,
+        node_name: str,
+        output_package_name: str,
+        output_package_version: str,
+        template_parameters: Dict[str, str],
+        template_type: str,
+    ) -> None:
+        self.job_id = job_id
+        now = datetime.now(tzutc())
+        self.created_time = now
+        self.last_updated_time = now
+        self.job_tags = job_tags
+        self.node_description = node_name
+        self.status = "PENDING"
+        self.node_name = node_name
+        self.output_package_name = output_package_name
+        self.output_package_version = output_package_version
+        self.template_parameters = self.protect_secrets(template_parameters)
+        self.template_type = template_type
+
+    def response_object(self) -> Dict[str, Any]:
+        response_object = super().gen_response_object()
+        response_object = deep_convert_datetime_to_isoformat(response_object)
+        return response_object
+
+    def response_created(self) -> Dict[str, str]:
+        return {"JobId": self.job_id}
+
+    def response_described(self) -> Dict[str, Any]:
+        return self.response_object()
+
+    @staticmethod
+    def protect_secrets(template_parameters: Dict[str, str]) -> Dict[str, str]:
+        for key in template_parameters.keys():
+            if key.lower() == "password":
+                template_parameters[key] = "SAVED_AS_SECRET"
+            if key.lower() == "username":
+                template_parameters[key] = "SAVED_AS_SECRET"
+        return template_parameters
+
+
 class PanoramaBackend(BaseBackend):
     def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
         self.devices_memory: Dict[str, Device] = {}
+        self.node_from_template_memory: Dict[str, Node] = {}
+        self.nodes_memory: Dict[str, Package] = {}
 
     def provision_device(
         self,
@@ -291,6 +386,54 @@ class PanoramaBackend(BaseBackend):
     def delete_device(self, device_id: str) -> Device:
         return self.devices_memory.pop(device_id)
 
+    def create_node_from_template_job(
+        self,
+        job_tags: List[Dict[str, Union[str, Dict[str, str]]]],
+        node_description: str,
+        node_name: str,
+        output_package_name: str,
+        output_package_version: str,
+        template_parameters: Dict[str, str],
+        template_type: str,
+    ) -> Node:
+        job_id = str(uuid.uuid4()).lower()
+        self.node_from_template_memory[job_id] = Node(
+            job_id=job_id,
+            job_tags=job_tags,
+            node_description=node_description,
+            node_name=node_name,
+            output_package_name=output_package_name,
+            output_package_version=output_package_version,
+            template_parameters=template_parameters,
+            template_type=template_type,
+        )
+        if template_type == "RTSP_CAMERA_STREAM":
+            package = Package(
+                category="MEDIA_SOURCE",
+                description=node_description,
+                name=node_name,
+                account_id=self.account_id,
+                region_name=self.region_name,
+                package_name=output_package_name,
+                package_version=output_package_version,
+            )
+            self.nodes_memory[package.package_id] = package
+
+        return self.node_from_template_memory[job_id]
+
+    def describe_node_from_template_job(self, job_id: str) -> Node:
+        return self.node_from_template_memory[job_id]
+
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_nodes(self, category: str) -> List[Package]:
+        category_nodes = list(
+            filter(
+                lambda x: x.category == category,
+                self.nodes_memory.values(),
+            )
+        )
+        return category_nodes
+
 
 panorama_backends = BackendDict(
     PanoramaBackend,
diff --git a/moto/panorama/responses.py b/moto/panorama/responses.py
index 3e8b98f59..a7cd19d88 100644
--- a/moto/panorama/responses.py
+++ b/moto/panorama/responses.py
@@ -70,3 +70,41 @@ class PanoramaResponse(BaseResponse):
         device_id = urllib.parse.unquote(self._get_param("DeviceId"))
         device = self.panorama_backend.delete_device(device_id=device_id)
         return json.dumps(device.response_deleted)
+
+    def create_node_from_template_job(self) -> str:
+        job_tags = self._get_param("JobTags")
+        node_description = self._get_param("NodeDescription")
+        node_name = self._get_param("NodeName")
+        output_package_name = self._get_param("OutputPackageName")
+        output_package_version = self._get_param("OutputPackageVersion")
+        template_parameters = self._get_param("TemplateParameters")
+        template_type = self._get_param("TemplateType")
+        node = self.panorama_backend.create_node_from_template_job(
+            job_tags=job_tags,
+            node_description=node_description,
+            node_name=node_name,
+            output_package_name=output_package_name,
+            output_package_version=output_package_version,
+            template_parameters=template_parameters,
+            template_type=template_type,
+        )
+        return json.dumps(node.response_created())
+
+    def describe_node_from_template_job(self) -> str:
+        job_id = self._get_param("JobId")
+        node = self.panorama_backend.describe_node_from_template_job(job_id=job_id)
+        return json.dumps(node.response_described())
+
+    def list_nodes(self) -> str:
+        category = self._get_param("category")
+        max_results = self._get_int_param("maxResults")
+        next_token = self._get_param("nextToken")
+        list_nodes, next_token = self.panorama_backend.list_nodes(
+            category=category, max_results=max_results, next_token=next_token
+        )
+        return json.dumps(
+            {
+                "Nodes": [node.response_listed() for node in list_nodes],
+                "NextToken": next_token,
+            }
+        )
diff --git a/moto/panorama/urls.py b/moto/panorama/urls.py
index 13f54d0ff..cddfa4f42 100644
--- a/moto/panorama/urls.py
+++ b/moto/panorama/urls.py
@@ -8,4 +8,7 @@ url_paths = {
     "{0}/$": PanoramaResponse.dispatch,
     "{0}/devices$": PanoramaResponse.dispatch,
     "{0}/devices/(?P<DeviceId>[^/]+)$": PanoramaResponse.dispatch,
+    "{0}/packages/template-job$": PanoramaResponse.dispatch,
+    "{0}/packages/template-job/(?P<JobId>[^/]+)$": PanoramaResponse.dispatch,
+    "{0}/nodes$": PanoramaResponse.dispatch,
 }
diff --git a/moto/panorama/utils.py b/moto/panorama/utils.py
index ad52c39df..d5c676d63 100644
--- a/moto/panorama/utils.py
+++ b/moto/panorama/utils.py
@@ -19,3 +19,16 @@ def hash_device_name(name: str) -> str:
     digest = hashlib.md5(name.encode("utf-8")).digest()
     token = base64.b64encode(digest)
     return token.decode("utf-8")
+
+
+def generate_package_id(name: str) -> str:
+    digest = hashlib.md5(name.encode("utf-8")).digest()
+    token = base64.b64encode(digest).decode("utf-8")
+    # make token only containing alphanumeric characters
+    token = "".join(char for char in token if char.isalnum())
+    token = token * (64 // len(token) + 1)
+    return token[:64].lower()
+
+
+def arn_formatter(_type: str, _id: str, account_id: str, region_name: str) -> str:
+    return f"arn:aws:panorama:{region_name}:{account_id}:{_type}/{_id}"
diff --git a/moto/sns/models.py b/moto/sns/models.py
index 98c380fb6..a3459de58 100644
--- a/moto/sns/models.py
+++ b/moto/sns/models.py
@@ -679,7 +679,7 @@ class SNSBackend(BaseBackend):
             if token == endpoint.token:
                 same_user_data = custom_user_data == endpoint.custom_user_data
                 same_attrs = (
-                    attributes.get("Enabled", "").lower()
+                    attributes.get("Enabled", "true").lower()
                     == endpoint.attributes["Enabled"]
                 )
 
diff --git a/moto/ssm/resources/regions.json b/moto/ssm/resources/regions.json
index 95db69e13..dda6a1fb1 100644
--- a/moto/ssm/resources/regions.json
+++ b/moto/ssm/resources/regions.json
@@ -13443,6 +13443,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "rum": {
+                "Value": "rum",
+                "endpoint": {
+                  "Value": "rum.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "s3": {
                 "Value": "s3",
                 "endpoint": {
@@ -14052,6 +14061,15 @@
               "bedrock": {
                 "Value": "bedrock"
               },
+              "budgets": {
+                "Value": "budgets",
+                "endpoint": {
+                  "Value": "budgets.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "chatbot": {
                 "Value": "chatbot"
               },
@@ -21533,6 +21551,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "rum": {
+                "Value": "rum",
+                "endpoint": {
+                  "Value": "rum.ap-southeast-4.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "s3": {
                 "Value": "s3",
                 "endpoint": {
@@ -24603,6 +24630,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "globalaccelerator": {
+                "Value": "globalaccelerator",
+                "endpoint": {
+                  "Value": "globalaccelerator.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "glue": {
                 "Value": "glue",
                 "endpoint": {
@@ -27380,6 +27416,15 @@
               "marketplace": {
                 "Value": "marketplace"
               },
+              "marketplace-entitlement": {
+                "Value": "marketplace-entitlement",
+                "endpoint": {
+                  "Value": "entitlement-marketplace.cn-northwest-1.amazonaws.com.cn"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "mcs": {
                 "Value": "mcs",
                 "endpoint": {
@@ -27392,7 +27437,7 @@
               "mediaconvert": {
                 "Value": "mediaconvert",
                 "endpoint": {
-                  "Value": "subscribe.mediaconvert.cn-northwest-1.amazonaws.com.cn"
+                  "Value": "mediaconvert.cn-northwest-1.amazonaws.com.cn"
                 },
                 "protocols": {
                   "Value": "HTTPS"
@@ -31646,6 +31691,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "rum": {
+                "Value": "rum",
+                "endpoint": {
+                  "Value": "rum.eu-central-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "s3": {
                 "Value": "s3",
                 "endpoint": {
@@ -36131,6 +36185,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "fis": {
+                "Value": "fis",
+                "endpoint": {
+                  "Value": "fis.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "fms": {
                 "Value": "fms",
                 "endpoint": {
@@ -36569,6 +36632,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "rum": {
+                "Value": "rum",
+                "endpoint": {
+                  "Value": "rum.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "s3": {
                 "Value": "s3",
                 "endpoint": {
@@ -45065,6 +45137,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "networkmanager": {
+                "Value": "networkmanager",
+                "endpoint": {
+                  "Value": "networkmanager.us-west-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "notifications": {
                 "Value": "notifications"
               },
@@ -46653,6 +46734,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "rum": {
+                "Value": "rum",
+                "endpoint": {
+                  "Value": "rum.me-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "s3": {
                 "Value": "s3",
                 "endpoint": {
@@ -57342,6 +57432,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "license-manager-user-subscriptions": {
+                "Value": "license-manager-user-subscriptions",
+                "endpoint": {
+                  "Value": "license-manager-user-subscriptions.us-gov-east-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "logs": {
                 "Value": "logs",
                 "endpoint": {
@@ -58957,6 +59056,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "license-manager-user-subscriptions": {
+                "Value": "license-manager-user-subscriptions",
+                "endpoint": {
+                  "Value": "license-manager-user-subscriptions.us-gov-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "logs": {
                 "Value": "logs",
                 "endpoint": {
diff --git a/moto/ssm/resources/services.json b/moto/ssm/resources/services.json
index 47ec67a4f..86565ab15 100644
--- a/moto/ssm/resources/services.json
+++ b/moto/ssm/resources/services.json
@@ -7965,6 +7965,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ap-southeast-1": {
+                "Value": "ap-southeast-1",
+                "endpoint": {
+                  "Value": "budgets.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ap-southeast-2": {
                 "Value": "ap-southeast-2",
                 "endpoint": {
@@ -25769,6 +25778,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "eu-south-2": {
+                "Value": "eu-south-2",
+                "endpoint": {
+                  "Value": "fis.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-west-1": {
                 "Value": "eu-west-1",
                 "endpoint": {
@@ -28645,6 +28663,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ca-west-1": {
+                "Value": "ca-west-1",
+                "endpoint": {
+                  "Value": "globalaccelerator.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-central-1": {
                 "Value": "eu-central-1",
                 "endpoint": {
@@ -38319,6 +38346,24 @@
                   "Value": "HTTPS"
                 }
               },
+              "us-gov-east-1": {
+                "Value": "us-gov-east-1",
+                "endpoint": {
+                  "Value": "license-manager-user-subscriptions.us-gov-east-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
+              "us-gov-west-1": {
+                "Value": "us-gov-west-1",
+                "endpoint": {
+                  "Value": "license-manager-user-subscriptions.us-gov-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "us-west-1": {
                 "Value": "us-west-1",
                 "endpoint": {
@@ -39948,6 +39993,15 @@
               "Value": "AWS Marketplace Entitlement Service"
             },
             "regions": {
+              "cn-northwest-1": {
+                "Value": "cn-northwest-1",
+                "endpoint": {
+                  "Value": "entitlement-marketplace.cn-northwest-1.amazonaws.com.cn"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "us-east-1": {
                 "Value": "us-east-1",
                 "endpoint": {
@@ -40487,7 +40541,7 @@
               "cn-northwest-1": {
                 "Value": "cn-northwest-1",
                 "endpoint": {
-                  "Value": "subscribe.mediaconvert.cn-northwest-1.amazonaws.com.cn"
+                  "Value": "mediaconvert.cn-northwest-1.amazonaws.com.cn"
                 },
                 "protocols": {
                   "Value": "HTTPS"
@@ -43997,6 +44051,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "il-central-1": {
+                "Value": "il-central-1",
+                "endpoint": {
+                  "Value": "networkmanager.us-west-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "me-south-1": {
                 "Value": "me-south-1",
                 "endpoint": {
@@ -52845,6 +52908,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ap-south-2": {
+                "Value": "ap-south-2",
+                "endpoint": {
+                  "Value": "rum.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ap-southeast-1": {
                 "Value": "ap-southeast-1",
                 "endpoint": {
@@ -52872,6 +52944,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ap-southeast-4": {
+                "Value": "ap-southeast-4",
+                "endpoint": {
+                  "Value": "rum.ap-southeast-4.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ca-central-1": {
                 "Value": "ca-central-1",
                 "endpoint": {
@@ -52890,6 +52971,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "eu-central-2": {
+                "Value": "eu-central-2",
+                "endpoint": {
+                  "Value": "rum.eu-central-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-north-1": {
                 "Value": "eu-north-1",
                 "endpoint": {
@@ -52908,6 +52998,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "eu-south-2": {
+                "Value": "eu-south-2",
+                "endpoint": {
+                  "Value": "rum.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-west-1": {
                 "Value": "eu-west-1",
                 "endpoint": {
@@ -52935,6 +53034,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "me-central-1": {
+                "Value": "me-central-1",
+                "endpoint": {
+                  "Value": "rum.me-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "me-south-1": {
                 "Value": "me-south-1",
                 "endpoint": {
diff --git a/moto/stepfunctions/parser/api.py b/moto/stepfunctions/parser/api.py
index 96fe9859e..173867900 100644
--- a/moto/stepfunctions/parser/api.py
+++ b/moto/stepfunctions/parser/api.py
@@ -268,8 +268,13 @@ class InvalidOutput(ServiceException):
 
 class InvalidToken(ServiceException):
     code: str = "InvalidToken"
+    exception_type: str = "UnrecognizedClientException"
     sender_fault: bool = False
     status_code: int = 400
+    message: str = "The security token included in the request is invalid."
+
+    def __init__(self):
+        super().__init__(self.message, self.exception_type, self.status_code)
 
 
 class InvalidTracingConfiguration(ServiceException):


Test patch:
diff --git a/tests/test_bedrock/__init__.py b/tests/test_bedrock/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_bedrock/test_bedrock.py b/tests/test_bedrock/test_bedrock.py
new file mode 100644
index 000000000..4950053ce
--- /dev/null
+++ b/tests/test_bedrock/test_bedrock.py
@@ -0,0 +1,1361 @@
+"""Unit tests for bedrock-supported APIs."""
+
+from datetime import datetime
+from unittest import SkipTest
+
+import boto3
+import pytest
+from botocore.exceptions import ClientError
+from freezegun import freeze_time
+
+from moto import mock_aws, settings
+
+DEFAULT_REGION = "us-east-1"
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_aws
+def test_create_model_customization_job():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    # create a test s3 client and bucket
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+
+    resp = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    assert (
+        resp["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+
+
+@mock_aws
+def test_get_model_customization_job():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    resp = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.get_model_customization_job(jobIdentifier="testjob")
+
+    assert (
+        resp["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+    assert resp["roleArn"] == "testrole"
+
+
+@mock_aws
+def test_get_model_invocation_logging_configuration():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    logging_config = {
+        "cloudWatchConfig": {
+            "logGroupName": "Test",
+            "roleArn": "testrole",
+            "largeDataDeliveryS3Config": {
+                "bucketName": "testbucket",
+            },
+        },
+        "s3Config": {
+            "bucketName": "testconfigbucket",
+        },
+    }
+    client.put_model_invocation_logging_configuration(loggingConfig=logging_config)
+    response = client.get_model_invocation_logging_configuration()
+    assert response["loggingConfig"]["cloudWatchConfig"]["logGroupName"] == "Test"
+    assert response["loggingConfig"]["s3Config"]["bucketName"] == "testconfigbucket"
+
+
+@mock_aws
+def test_put_model_invocation_logging_configuration():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    logging_config = {
+        "cloudWatchConfig": {
+            "logGroupName": "Test",
+            "roleArn": "testrole",
+            "largeDataDeliveryS3Config": {
+                "bucketName": "testbucket",
+            },
+        },
+        "s3Config": {
+            "bucketName": "testconfigbucket",
+        },
+    }
+    client.put_model_invocation_logging_configuration(loggingConfig=logging_config)
+    response = client.get_model_invocation_logging_configuration()
+    assert response["loggingConfig"]["cloudWatchConfig"]["logGroupName"] == "Test"
+
+
+@mock_aws
+def test_tag_resource_model_customization_job():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.tag_resource(
+        resourceARN=job_arn["jobArn"],
+        tags=[
+            {"key": "testkey", "value": "testvalue"},
+            {"key": "testkey2", "value": "testvalue2"},
+        ],
+    )
+    resp = client.list_tags_for_resource(resourceARN=job_arn["jobArn"])
+    assert resp["tags"][0]["key"] == "testkey"
+    assert resp["tags"][1]["value"] == "testvalue2"
+
+
+@mock_aws
+def test_untag_resource():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.tag_resource(
+        resourceARN=job_arn["jobArn"],
+        tags=[
+            {"key": "testkey", "value": "testvalue"},
+            {"key": "testkey2", "value": "testvalue2"},
+        ],
+    )
+    client.untag_resource(resourceARN=job_arn["jobArn"], tagKeys=["testkey"])
+    resp = client.list_tags_for_resource(resourceARN=job_arn["jobArn"])
+
+    assert resp["tags"][0]["key"] == "testkey2"
+    assert resp["tags"][0]["value"] == "testvalue2"
+
+
+@mock_aws
+def test_untag_resource_custom_model():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+
+    client.tag_resource(
+        resourceARN=job_arn["jobArn"],
+        tags=[
+            {"key": "testkey", "value": "testvalue"},
+            {"key": "testkey2", "value": "testvalue2"},
+        ],
+    )
+    resp = client.untag_resource(resourceARN=job_arn["jobArn"], tagKeys=["testkey"])
+    resp = client.list_tags_for_resource(resourceARN=job_arn["jobArn"])
+
+    assert resp["tags"][0]["key"] == "testkey2"
+    assert resp["tags"][0]["value"] == "testvalue2"
+
+
+@mock_aws
+def test_list_tags_for_resource():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.tag_resource(
+        resourceARN=job_arn["jobArn"],
+        tags=[
+            {"key": "testkey", "value": "testvalue"},
+            {"key": "testkey2", "value": "testvalue2"},
+        ],
+    )
+    resp = client.list_tags_for_resource(resourceARN=job_arn["jobArn"])
+
+    assert resp["tags"][0]["key"] == "testkey"
+    assert resp["tags"][1]["value"] == "testvalue2"
+
+
+@mock_aws
+def test_get_custom_model():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.get_custom_model(modelIdentifier="testmodel")
+    assert (
+        resp["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel"
+    )
+    assert resp["jobArn"] == job_arn["jobArn"]
+
+
+@mock_aws
+def test_get_custom_model_arn():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+
+    resp = client.get_custom_model(modelIdentifier="testmodel")
+    model = client.get_custom_model(modelIdentifier=resp["modelArn"])
+    assert model["modelName"] == "testmodel"
+    assert resp["jobArn"] == job_arn["jobArn"]
+
+
+@mock_aws
+def test_get_custom_model_arn_not_found():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+
+    resp = client.get_custom_model(modelIdentifier="testmodel")
+    with pytest.raises(ClientError) as ex:
+        client.get_custom_model(modelIdentifier=(resp["modelArn"] + "no"))
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_list_custom_models():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel1",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_custom_models()
+    assert len(resp["modelSummaries"]) == 2
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel1"
+    )
+    assert (
+        resp["modelSummaries"][1]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel2"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel1",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_model_customization_jobs()
+    assert len(resp["modelCustomizationJobSummaries"]) == 2
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+    assert (
+        resp["modelCustomizationJobSummaries"][1]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob2"
+    )
+
+
+@mock_aws
+def test_delete_custom_model():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.delete_custom_model(modelIdentifier="testmodel")
+
+    with pytest.raises(ClientError) as ex:
+        client.get_custom_model(modelIdentifier="testmodel")
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_delete_custom_model_not_found():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+
+    with pytest.raises(ClientError) as ex:
+        client.delete_custom_model(modelIdentifier="testmodel1")
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_stop_model_customization_job():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.stop_model_customization_job(jobIdentifier="testjob")
+    resp = client.get_model_customization_job(jobIdentifier="testjob")
+    assert resp["status"] == "Stopped"
+
+
+@mock_aws
+def test_delete_model_invocation_logging_configuration():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    logging_config = {
+        "cloudWatchConfig": {
+            "logGroupName": "Test",
+            "roleArn": "testrole",
+            "largeDataDeliveryS3Config": {
+                "bucketName": "testbucket",
+            },
+        },
+        "s3Config": {
+            "bucketName": "testconfigbucket",
+        },
+    }
+    client.put_model_invocation_logging_configuration(loggingConfig=logging_config)
+    client.delete_model_invocation_logging_configuration()
+    assert client.get_model_invocation_logging_configuration()["loggingConfig"] == {}
+
+
+@mock_aws
+def test_create_model_customization_job_bad_training_data_config():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    # create a test s3 client and bucket
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    with pytest.raises(ClientError) as ex:
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "aws:s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_create_model_customization_job_bad_validation_data_config():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    # create a test s3 client and bucket
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    with pytest.raises(ClientError) as ex:
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            validationDataConfig={
+                "validators": [{"s3Uri": "aws:s3://validation_bucket"}]
+            },
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_create_model_customization_job_bad_output_data_config():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    # create a test s3 client and bucket
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    with pytest.raises(ClientError) as ex:
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            validationDataConfig={"validators": [{"s3Uri": "s3://validation_bucket"}]},
+            outputDataConfig={"s3Uri": "aws:s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_create_model_customization_job_duplicate_job_name():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    # create a test s3 client and bucket
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        validationDataConfig={"validators": [{"s3Uri": "s3://validation_bucket"}]},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with pytest.raises(ClientError) as ex:
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            validationDataConfig={"validators": [{"s3Uri": "s3://validation_bucket"}]},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    assert ex.value.response["Error"]["Code"] == "ResourceInUseException"
+
+
+@mock_aws
+def test_create_model_customization_job_duplicate_model_name():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    # create a test s3 client and bucket
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    client.create_model_customization_job(
+        jobName="testjob1",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        validationDataConfig={"validators": [{"s3Uri": "s3://validation_bucket"}]},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with pytest.raises(ClientError) as ex:
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            validationDataConfig={"validators": [{"s3Uri": "s3://validation_bucket"}]},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    assert ex.value.response["Error"]["Code"] == "ResourceInUseException"
+
+
+@mock_aws
+def test_create_model_customization_job_tags():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    # create a test s3 client and bucket
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    resp = client.create_model_customization_job(
+        jobName="testjob1",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        validationDataConfig={"validators": [{"s3Uri": "s3://validation_bucket"}]},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+        jobTags=[{"key": "test", "value": "testvalue"}],
+        customModelTags=[{"key": "modeltest", "value": "modeltestvalue"}],
+    )
+    job_tags = client.list_tags_for_resource(resourceARN=resp["jobArn"])
+    model_arn = client.list_custom_models()["modelSummaries"][0]["modelArn"]
+    model_tags = client.list_tags_for_resource(resourceARN=model_arn)
+    assert job_tags["tags"] == [{"key": "test", "value": "testvalue"}]
+    assert model_tags["tags"] == [{"key": "modeltest", "value": "modeltestvalue"}]
+
+
+@mock_aws
+def test_get_model_customization_job_not_found():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with pytest.raises(ClientError) as ex:
+        client.get_model_customization_job(jobIdentifier="testjob1")
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_stop_model_customization_job_not_found():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with pytest.raises(ClientError) as ex:
+        client.stop_model_customization_job(jobIdentifier="testjob1")
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_list_model_customization_jobs_max_results():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel1",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_model_customization_jobs(maxResults=1)
+    assert len(resp["modelCustomizationJobSummaries"]) == 1
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs_name_contains():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel1",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_model_customization_jobs(nameContains="testjob2")
+    assert len(resp["modelCustomizationJobSummaries"]) == 1
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob2"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs_creation_time_before():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_model_customization_jobs(
+        creationTimeBefore=datetime(2022, 2, 1, 12, 0, 0)
+    )
+    assert len(resp["modelCustomizationJobSummaries"]) == 1
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs_creation_time_after():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_model_customization_jobs(
+        creationTimeAfter=datetime(2022, 2, 1, 12, 0, 0)
+    )
+    assert len(resp["modelCustomizationJobSummaries"]) == 1
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob2"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs_status():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel1",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_model_customization_jobs(statusEquals="InProgress")
+    assert len(resp["modelCustomizationJobSummaries"]) == 2
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+    assert (
+        resp["modelCustomizationJobSummaries"][1]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob2"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs_ascending_sort():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob3",
+        customModelName="testmodel3",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with freeze_time("2023-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob2",
+            customModelName="testmodel2",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    resp = client.list_model_customization_jobs(
+        sortBy="CreationTime", sortOrder="Ascending"
+    )
+    assert len(resp["modelCustomizationJobSummaries"]) == 3
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+    assert (
+        resp["modelCustomizationJobSummaries"][1]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob2"
+    )
+    assert (
+        resp["modelCustomizationJobSummaries"][2]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob3"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs_descending_sort():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob3",
+        customModelName="testmodel3",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with freeze_time("2023-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob2",
+            customModelName="testmodel2",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    resp = client.list_model_customization_jobs(
+        sortBy="CreationTime", sortOrder="Descending"
+    )
+    assert len(resp["modelCustomizationJobSummaries"]) == 3
+    assert (
+        resp["modelCustomizationJobSummaries"][0]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob3"
+    )
+    assert (
+        resp["modelCustomizationJobSummaries"][1]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob2"
+    )
+    assert (
+        resp["modelCustomizationJobSummaries"][2]["jobArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:model-customization-job/testjob"
+    )
+
+
+@mock_aws
+def test_list_model_customization_jobs_bad_sort_order():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with pytest.raises(ClientError) as ex:
+        client.list_model_customization_jobs(
+            sortBy="CreationTime", sortOrder="decending"
+        )
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_list_model_customization_jobs_bad_sort_by():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob3",
+        customModelName="testmodel3",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with freeze_time("2023-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob2",
+            customModelName="testmodel2",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    with pytest.raises(ClientError) as ex:
+        client.list_model_customization_jobs(
+            sortBy="Creationime", sortOrder="Descending"
+        )
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_get_model_invocation_logging_configuration_empty():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    response = client.get_model_invocation_logging_configuration()
+    assert response["loggingConfig"] == {}
+
+
+@mock_aws
+def test_list_custom_models_max_results():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_custom_models(maxResults=1)
+    assert len(resp["modelSummaries"]) == 1
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel"
+    )
+
+
+@mock_aws
+def test_list_custom_models_name_contains():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel1",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_custom_models(nameContains="testjob2")
+    assert len(resp["modelSummaries"]) == 1
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel2"
+    )
+
+
+@mock_aws
+def test_list_custom_models_creation_time_before():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_custom_models(creationTimeBefore=datetime(2022, 2, 1, 12, 0, 0))
+    assert len(resp["modelSummaries"]) == 1
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel"
+    )
+
+
+@mock_aws
+def test_list_custom_models_creation_time_after():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_custom_models(creationTimeAfter=datetime(2022, 2, 1, 12, 0, 0))
+    assert len(resp["modelSummaries"]) == 1
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel2"
+    )
+
+
+@mock_aws
+def test_list_custom_models_ascending_sort():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob3",
+        customModelName="testmodel3",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with freeze_time("2023-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob2",
+            customModelName="testmodel2",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    resp = client.list_custom_models(sortBy="CreationTime", sortOrder="Ascending")
+    assert len(resp["modelSummaries"]) == 3
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel"
+    )
+    assert (
+        resp["modelSummaries"][1]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel2"
+    )
+    assert (
+        resp["modelSummaries"][2]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel3"
+    )
+
+
+@mock_aws
+def test_list_custom_models_descending_sort():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob3",
+        customModelName="testmodel3",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with freeze_time("2023-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob2",
+            customModelName="testmodel2",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    resp = client.list_custom_models(sortBy="CreationTime", sortOrder="Descending")
+    assert len(resp["modelSummaries"]) == 3
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel3"
+    )
+    assert (
+        resp["modelSummaries"][1]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel2"
+    )
+    assert (
+        resp["modelSummaries"][2]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel"
+    )
+
+
+@mock_aws
+def test_list_custom_models_bad_sort_order():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob3",
+        customModelName="testmodel3",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with freeze_time("2023-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob2",
+            customModelName="testmodel2",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    with pytest.raises(ClientError) as ex:
+        client.list_custom_models(sortBy="CreationTime", sortOrder="decending")
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_list_custom_models_bad_sort_by():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't freeze time in ServerMode")
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    with freeze_time("2022-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob",
+            customModelName="testmodel1",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    client.create_model_customization_job(
+        jobName="testjob3",
+        customModelName="testmodel3",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with freeze_time("2023-01-01 12:00:00"):
+        client.create_model_customization_job(
+            jobName="testjob2",
+            customModelName="testmodel2",
+            roleArn="testrole",
+            baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+            trainingDataConfig={"s3Uri": "s3://training_bucket"},
+            outputDataConfig={"s3Uri": "s3://output_bucket"},
+            hyperParameters={"learning_rate": "0.01"},
+        )
+    with pytest.raises(ClientError) as ex:
+        client.list_custom_models(sortBy="Creationime", sortOrder="Descending")
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_list_custom_models_base_model_arn_equals():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.create_model_customization_job(
+        jobName="testjob2",
+        customModelName="testmodel2",
+        roleArn="testrole",
+        baseModelIdentifier="amazon.titan-text-lite-v1",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    resp = client.list_custom_models(
+        baseModelArnEquals="arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0"
+    )
+    assert len(resp["modelSummaries"]) == 1
+    assert (
+        resp["modelSummaries"][0]["modelArn"]
+        == "arn:aws:bedrock:us-east-1:123456789012:custom-model/testmodel"
+    )
+
+
+@mock_aws
+def test_tag_resource_not_found():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with pytest.raises(ClientError) as ex:
+        client.tag_resource(
+            resourceARN=job_arn["jobArn"] + "no",
+            tags=[
+                {"key": "testkey", "value": "testvalue"},
+                {"key": "testkey2", "value": "testvalue2"},
+            ],
+        )
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_tag_resource_too_many():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    with pytest.raises(ClientError) as ex:
+        client.tag_resource(
+            resourceARN=job_arn["jobArn"],
+            tags=[{"key": f"testkey{i}", "value": f"testvalue{i}"} for i in range(51)],
+        )
+    assert ex.value.response["Error"]["Code"] == "TooManyTagsException"
+
+
+@mock_aws
+def test_untag_resource_not_found():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+
+    client.tag_resource(
+        resourceARN=job_arn["jobArn"],
+        tags=[
+            {"key": "testkey", "value": "testvalue"},
+            {"key": "testkey2", "value": "testvalue2"},
+        ],
+    )
+    with pytest.raises(ClientError) as ex:
+        client.untag_resource(resourceARN=job_arn["jobArn"] + "no", tagKeys=["testkey"])
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_list_tags_for_resource_not_found():
+    client = boto3.client("bedrock", region_name=DEFAULT_REGION)
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION)
+    s3_client.create_bucket(Bucket="training_bucket")
+    s3_client.create_bucket(Bucket="output_bucket")
+    job_arn = client.create_model_customization_job(
+        jobName="testjob",
+        customModelName="testmodel",
+        roleArn="testrole",
+        baseModelIdentifier="anthropic.claude-3-sonnet-20240229-v1:0",
+        trainingDataConfig={"s3Uri": "s3://training_bucket"},
+        outputDataConfig={"s3Uri": "s3://output_bucket"},
+        hyperParameters={"learning_rate": "0.01"},
+    )
+    client.tag_resource(
+        resourceARN=job_arn["jobArn"],
+        tags=[
+            {"key": "testkey", "value": "testvalue"},
+            {"key": "testkey2", "value": "testvalue2"},
+        ],
+    )
+    with pytest.raises(ClientError) as ex:
+        client.list_tags_for_resource(resourceARN=job_arn["jobArn"] + "no")
+    assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
diff --git a/tests/test_bedrockagent/__init__.py b/tests/test_bedrockagent/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_bedrockagent/test_bedrockagent.py b/tests/test_bedrockagent/test_bedrockagent.py
new file mode 100644
index 000000000..094c312a1
--- /dev/null
+++ b/tests/test_bedrockagent/test_bedrockagent.py
@@ -0,0 +1,725 @@
+"""Unit tests for bedrockagent-supported APIs."""
+
+import boto3
+import pytest
+from botocore.exceptions import ClientError
+
+from moto import mock_aws
+
+DEFAULT_REGION = "us-east-1"
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_aws
+def test_create_agent():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+
+    resp = client.create_agent(
+        agentName="agent_name",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+    # resp = client.create_agent(
+    #     agentName="agent_name",
+    #     clientToken="client_tokenclient_tokenclient_token",
+    #     instruction="instructioninstructioninstructioninstruction",
+    #     foundationModel="foundation_model",
+    #     description="description",
+    #     idleSessionTTLInSeconds=60,
+    #     agentResourceRoleArn="agent_resource_role_arn",
+    #     customerEncryptionKeyArn="customer_encryption_key_arn",
+    #     tags={
+    #             "Key": "test-tag-key",
+    #         },
+    #     promptOverrideConfiguration={
+    #     'promptConfigurations': [
+    #         {
+    #             'promptType': 'PRE_PROCESSING',
+    #             'promptCreationMode': 'DEFAULT',
+    #             'promptState': 'ENABLED',
+    #             'basePromptTemplate': 'string',
+    #             'inferenceConfiguration': {
+    #                 'temperature': 1.0,
+    #                 'topP': 1.0,
+    #                 'topK': 123,
+    #                 'maximumLength': 123,
+    #                 'stopSequences': [
+    #                     'string',
+    #                 ]
+    #             },
+    #             'parserMode': 'DEFAULT'
+    #         },
+    #     ],
+    #     'overrideLambda': 'overrideLambdaoverrideLambdaoverrideLambdaoverrideLambda'
+    # }
+    # )
+    assert resp["agent"]["agentName"] == "agent_name"
+
+
+@mock_aws
+def test_get_agent():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    agent = client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+    resp = client.get_agent(agentId=agent["agent"]["agentId"])
+    assert resp["agent"]["agentName"] == "testname"
+
+
+@mock_aws
+def test_get_agent_not_found():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+    with pytest.raises(ClientError) as e:
+        client.get_agent(agentId="non-existent-agent-id")
+    assert e.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_list_agents():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_agent(
+        agentName="testname1",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+    client.create_agent(
+        agentName="testname2",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+    resp = client.list_agents()
+    # assert resp['agent']['agentName'] == "testname"
+
+    assert len(resp["agentSummaries"]) == 2
+    assert resp["agentSummaries"][0]["agentName"] == "testname1"
+    assert resp["agentSummaries"][1]["agentName"] == "testname2"
+
+
+@mock_aws
+def test_delete_agent():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    agent = client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+        tags={"Key": "test-tag-key"},
+    )
+    agent_id = agent["agent"]["agentId"]
+    resp = client.delete_agent(agentId=agent_id, skipResourceInUseCheck=True)
+
+    assert resp["agentId"] == agent_id
+    assert resp["agentStatus"] == "DELETING"
+
+
+@mock_aws
+def test_create_knowledge_base():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    resp = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+
+    assert resp["knowledgeBase"]["name"] == "testkb"
+
+
+@mock_aws
+def test_list_knowledge_bases():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_knowledge_base(
+        name="testkb1",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    client.create_knowledge_base(
+        name="testkb2",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    resp = client.list_knowledge_bases()
+
+    assert len(resp["knowledgeBaseSummaries"]) == 2
+    assert resp["knowledgeBaseSummaries"][0]["name"] == "testkb1"
+    assert resp["knowledgeBaseSummaries"][1]["name"] == "testkb2"
+
+
+@mock_aws
+def test_delete_knowledge_base():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    kb = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    kb_id = kb["knowledgeBase"]["knowledgeBaseId"]
+    resp = client.delete_knowledge_base(knowledgeBaseId=kb_id)
+    assert resp["knowledgeBaseId"] == kb_id
+    assert resp["status"] == "DELETING"
+
+
+@mock_aws
+def test_delete_knowledge_base_not_found():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    with pytest.raises(ClientError) as e:
+        client.delete_knowledge_base(knowledgeBaseId="non-existent-kb-id")
+    assert e.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_get_knowledge_base():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    kb = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    kb_id = kb["knowledgeBase"]["knowledgeBaseId"]
+    resp = client.get_knowledge_base(knowledgeBaseId=kb_id)
+    assert resp["knowledgeBase"]["name"] == "testkb"
+
+
+@mock_aws
+def test_get_knowledge_base_not_found():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    with pytest.raises(ClientError) as e:
+        client.get_knowledge_base(knowledgeBaseId="non-existent-kb-id")
+    assert e.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_tag_resource_agent():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    agent = client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+    )
+    agent_arn = agent["agent"]["agentArn"]
+    resp = client.tag_resource(resourceArn=agent_arn, tags={"Key": "test-tag"})
+    resp = client.list_tags_for_resource(resourceArn=agent_arn)
+    assert resp["tags"]["Key"] == "test-tag"
+
+
+@mock_aws
+def test_tag_resource_knowledge_base():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    kb = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+    )
+    kb_arn = kb["knowledgeBase"]["knowledgeBaseArn"]
+    resp = client.tag_resource(resourceArn=kb_arn, tags={"Key": "test-tag"})
+    resp = client.list_tags_for_resource(resourceArn=kb_arn)
+    assert resp["tags"]["Key"] == "test-tag"
+
+
+@mock_aws
+def test_untag_resource_agent():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    agent = client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+    )
+    agent_arn = agent["agent"]["agentArn"]
+    resp = client.tag_resource(
+        resourceArn=agent_arn, tags={"Key1": "test-tag", "Key2": "test-tag2"}
+    )
+    resp = client.untag_resource(resourceArn=agent_arn, tagKeys=["Key1"])
+    resp = client.list_tags_for_resource(resourceArn=agent_arn)
+    assert len(resp["tags"]) == 1
+    assert resp["tags"]["Key2"] == "test-tag2"
+
+
+@mock_aws
+def test_untag_resource_knowledge_base():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    kb = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+    )
+    kb_arn = kb["knowledgeBase"]["knowledgeBaseArn"]
+    resp = client.tag_resource(
+        resourceArn=kb_arn, tags={"Key1": "test-tag", "Key2": "test-tag2"}
+    )
+    resp = client.untag_resource(resourceArn=kb_arn, tagKeys=["Key1", "Key2"])
+    resp = client.list_tags_for_resource(resourceArn=kb_arn)
+    assert len(resp["tags"]) == 0
+
+
+@mock_aws
+def test_list_tags_for_resource_agent():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    agent = client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+    )
+    agent_arn = agent["agent"]["agentArn"]
+    resp = client.tag_resource(
+        resourceArn=agent_arn, tags={"Key1": "test-tag", "Key2": "test-tag2"}
+    )
+    resp = client.list_tags_for_resource(resourceArn=agent_arn)
+    assert resp["tags"]["Key1"] == "test-tag"
+    assert resp["tags"]["Key2"] == "test-tag2"
+
+
+@mock_aws
+def test_list_tags_for_resource_knowledge_base():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    kb = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+    )
+    kb_arn = kb["knowledgeBase"]["knowledgeBaseArn"]
+    resp = client.tag_resource(
+        resourceArn=kb_arn, tags={"Key1": "test-tag", "Key2": "test-tag2"}
+    )
+    resp = client.list_tags_for_resource(resourceArn=kb_arn)
+    assert resp["tags"]["Key1"] == "test-tag"
+    assert resp["tags"]["Key2"] == "test-tag2"
+
+
+@mock_aws
+def test_create_knowledge_base_bad_knowledge_base_config():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    with pytest.raises(ClientError) as e:
+        client.create_knowledge_base(
+            name="testkb",
+            description="description",
+            roleArn="test_role_arn",
+            knowledgeBaseConfiguration={
+                "type": "vECTOR",
+            },
+            storageConfiguration={
+                "type": "OPENSEARCH_SERVERLESS",
+            },
+            tags={"Key": "test-tag"},
+        )
+    assert e.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_create_knowledge_base_bad_storage_config():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    with pytest.raises(ClientError) as e:
+        client.create_knowledge_base(
+            name="testkb",
+            description="description",
+            roleArn="test_role_arn",
+            knowledgeBaseConfiguration={
+                "type": "VECTOR",
+            },
+            storageConfiguration={
+                "type": "oPENSEARCH_SERVERLES",
+            },
+            tags={"Key": "test-tag"},
+        )
+    assert e.value.response["Error"]["Code"] == "ValidationException"
+
+
+# @mock_aws
+# def test_list_agents_token():
+#     client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+#     client.create_agent(
+#         agentName="testname1",
+#         agentResourceRoleArn="test-agent-arn",
+#         tags={
+#             "Key": "test-tag-key",
+#         },
+#     )
+#     client.create_agent(
+#         agentName="testname2",
+#         agentResourceRoleArn="test-agent-arn",
+#         tags={
+#             "Key": "test-tag-key",
+#         },
+#     )
+#     resp = client.list_agents(nextToken="1")
+
+#     assert len(resp["agentSummaries"]) == 1
+#     assert resp["agentSummaries"][0]["agentName"] == "testname2"
+
+
+# @mock_aws
+# def test_list_agents_bad_token():
+#     client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+#     client.create_agent(
+#         agentName="testname1",
+#         agentResourceRoleArn="test-agent-arn",
+#         tags={
+#             "Key": "test-tag-key",
+#         },
+#     )
+#     client.create_agent(
+#         agentName="testname2",
+#         agentResourceRoleArn="test-agent-arn",
+#         tags={
+#             "Key": "test-tag-key",
+#         },
+#     )
+#     with pytest.raises(ClientError) as e:
+#         client.list_agents(nextToken="3")
+#     assert e.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_list_agents_max_results():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_agent(
+        agentName="testname1",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+    client.create_agent(
+        agentName="testname2",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+
+    resp = client.list_agents(maxResults=1)
+
+    assert len(resp["agentSummaries"]) == 1
+    assert resp["agentSummaries"][0]["agentName"] == "testname1"
+
+
+@mock_aws
+def test_list_agents_big_max_results():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_agent(
+        agentName="testname1",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+    client.create_agent(
+        agentName="testname2",
+        agentResourceRoleArn="test-agent-arn",
+        tags={
+            "Key": "test-tag-key",
+        },
+    )
+
+    resp = client.list_agents(maxResults=4)
+
+    assert len(resp["agentSummaries"]) == 2
+    assert resp["agentSummaries"][0]["agentName"] == "testname1"
+    assert resp["agentSummaries"][1]["agentName"] == "testname2"
+
+
+@mock_aws
+def test_delete_agent_not_found():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+        tags={"Key": "test-tag-key"},
+    )
+    with pytest.raises(ClientError) as e:
+        client.delete_agent(
+            agentId="non-existent-agent-id", skipResourceInUseCheck=True
+        )
+    assert e.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+# @mock_aws
+# def test_delete_agent_in_use():
+#     client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+#     agent = client.create_agent(
+#         agentName="testname",
+#         agentResourceRoleArn="test-agent-arn",
+#         tags={"Key": "test-tag-key"},
+#     )
+#     agent_id = agent["agent"]["agentId"]
+#     with patch("moto.bedrockagent.models.Agent.agent_status", return_value="IN_USE"):
+#         # mock = Mock(spec = Agent)
+#         # mock.agent_status.return_value = "IN_USE"
+#         with pytest.raises(ClientError) as e:
+#             resp = client.delete_agent(agentId=agent_id, skipResourceInUseCheck=False)
+#         assert e.value.response["Error"]["Code"] == "ConflictException"
+
+
+# @mock_aws
+# def test_list_knowledge_bases_token():
+#     client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+#     resp = client.create_knowledge_base(
+#         name="testkb",
+#         description="description",
+#         roleArn="test_role_arn",
+#         knowledgeBaseConfiguration={
+#             "type": "VECTOR",
+#         },
+#         storageConfiguration={
+#             "type": "OPENSEARCH_SERVERLESS",
+#         },
+#         tags={"Key": "test-tag"},
+#     )
+#     resp = client.create_knowledge_base(
+#         name="testkb2",
+#         description="description",
+#         roleArn="test_role_arn",
+#         knowledgeBaseConfiguration={
+#             "type": "VECTOR",
+#         },
+#         storageConfiguration={
+#             "type": "OPENSEARCH_SERVERLESS",
+#         },
+#         tags={"Key": "test-tag"},
+#     )
+#     resp = client.list_knowledge_bases(nextToken="1")
+
+#     assert len(resp["knowledgeBaseSummaries"]) == 1
+#     assert resp["knowledgeBaseSummaries"][0]["name"] == "testkb2"
+
+
+# @mock_aws
+# def test_list_knowledge_bases_bad_token():
+#     client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+#     client.create_knowledge_base(
+#         name="testkb",
+#         description="description",
+#         roleArn="test_role_arn",
+#         knowledgeBaseConfiguration={
+#             "type": "VECTOR",
+#         },
+#         storageConfiguration={
+#             "type": "OPENSEARCH_SERVERLESS",
+#         },
+#         tags={"Key": "test-tag"},
+#     )
+#     client.create_knowledge_base(
+#         name="testkb2",
+#         description="description",
+#         roleArn="test_role_arn",
+#         knowledgeBaseConfiguration={
+#             "type": "VECTOR",
+#         },
+#         storageConfiguration={
+#             "type": "OPENSEARCH_SERVERLESS",
+#         },
+#         tags={"Key": "test-tag"},
+#     )
+#     with pytest.raises(ClientError) as e:
+#         client.list_knowledge_bases(nextToken="3")
+#     assert e.value.response["Error"]["Code"] == "ValidationException"
+
+
+@mock_aws
+def test_list_knowledge_bases_max_results():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    resp = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    resp = client.create_knowledge_base(
+        name="testkb2",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+
+    resp = client.list_knowledge_bases(maxResults=1)
+
+    assert len(resp["knowledgeBaseSummaries"]) == 1
+    assert resp["knowledgeBaseSummaries"][0]["name"] == "testkb"
+
+
+@mock_aws
+def test_list_knowledge_bases_big_max_results():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    resp = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+    resp = client.create_knowledge_base(
+        name="testkb2",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+        tags={"Key": "test-tag"},
+    )
+
+    resp = client.list_knowledge_bases(maxResults=4)
+
+    assert len(resp["knowledgeBaseSummaries"]) == 2
+    assert resp["knowledgeBaseSummaries"][0]["name"] == "testkb"
+    assert resp["knowledgeBaseSummaries"][1]["name"] == "testkb2"
+
+
+@mock_aws
+def test_tag_resource_knowledge_base_not_found():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    kb = client.create_knowledge_base(
+        name="testkb",
+        description="description",
+        roleArn="test_role_arn",
+        knowledgeBaseConfiguration={
+            "type": "VECTOR",
+        },
+        storageConfiguration={
+            "type": "OPENSEARCH_SERVERLESS",
+        },
+    )
+    kb_arn = kb["knowledgeBase"]["knowledgeBaseArn"]
+    with pytest.raises(ClientError) as e:
+        client.tag_resource(resourceArn=kb_arn + "no", tags={"Key": "test-tag"})
+    assert e.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_untag_resource_agent_not_found():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    agent = client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+    )
+    agent_arn = agent["agent"]["agentArn"]
+    client.tag_resource(
+        resourceArn=agent_arn, tags={"Key1": "test-tag", "Key2": "test-tag2"}
+    )
+    with pytest.raises(ClientError) as e:
+        client.untag_resource(resourceArn=agent_arn + "no", tagKeys=["Key1"])
+    assert e.value.response["Error"]["Code"] == "ResourceNotFoundException"
+
+
+@mock_aws
+def test_list_tags_for_resource_agent_not_found():
+    client = boto3.client("bedrock-agent", region_name=DEFAULT_REGION)
+    agent = client.create_agent(
+        agentName="testname",
+        agentResourceRoleArn="test-agent-arn",
+    )
+    agent_arn = agent["agent"]["agentArn"]
+    client.tag_resource(
+        resourceArn=agent_arn, tags={"Key1": "test-tag", "Key2": "test-tag2"}
+    )
+    with pytest.raises(ClientError) as e:
+        client.list_tags_for_resource(resourceArn=agent_arn + "no")
+    assert e.value.response["Error"]["Code"] == "ResourceNotFoundException"
diff --git a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
index a8ecd2224..f39f1ca55 100644
--- a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
+++ b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
@@ -1389,3 +1389,31 @@ def test_cannot_scan_gsi_with_consistent_read():
         "Code": "ValidationException",
         "Message": "Consistent reads are not supported on global secondary indexes",
     }
+
+
+@mock_aws
+def test_delete_table():
+    client = boto3.client("dynamodb", region_name="us-east-1")
+
+    # Create the DynamoDB table.
+    client.create_table(
+        TableName="test1",
+        AttributeDefinitions=[
+            {"AttributeName": "client", "AttributeType": "S"},
+            {"AttributeName": "app", "AttributeType": "S"},
+        ],
+        KeySchema=[
+            {"AttributeName": "client", "KeyType": "HASH"},
+            {"AttributeName": "app", "KeyType": "RANGE"},
+        ],
+        ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
+        DeletionProtectionEnabled=True,
+    )
+
+    with pytest.raises(ClientError) as err:
+        client.delete_table(TableName="test1")
+    assert err.value.response["Error"]["Code"] == "ValidationException"
+    assert (
+        err.value.response["Error"]["Message"]
+        == "1 validation error detected: Table 'test1' can't be deleted while DeletionProtectionEnabled is set to True"
+    )
diff --git a/tests/test_dynamodb/test_dynamodb_create_table.py b/tests/test_dynamodb/test_dynamodb_create_table.py
index ed8b1a91c..07f9e7c9b 100644
--- a/tests/test_dynamodb/test_dynamodb_create_table.py
+++ b/tests/test_dynamodb/test_dynamodb_create_table.py
@@ -50,6 +50,7 @@ def test_create_table_standard():
         {"AttributeName": "subject", "KeyType": "RANGE"},
     ]
     assert actual["ItemCount"] == 0
+    assert not actual["DeletionProtectionEnabled"]
 
 
 @mock_aws
@@ -233,6 +234,22 @@ def test_create_table_with_tags():
     assert resp["Tags"] == [{"Key": "tk", "Value": "tv"}]
 
 
+@mock_aws
+def test_create_table_with_deletion_protection_enabled():
+    client = boto3.client("dynamodb", region_name="us-east-1")
+
+    client.create_table(
+        TableName="test-deletion_protection",
+        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
+        DeletionProtectionEnabled=True,
+    )
+
+    actual = client.describe_table(TableName="test-deletion_protection")["Table"]
+    assert actual["DeletionProtectionEnabled"]
+
+
 @mock_aws
 def test_create_table_pay_per_request():
     client = boto3.client("dynamodb", region_name="us-east-1")
diff --git a/tests/test_dynamodb/test_dynamodb_update_table.py b/tests/test_dynamodb/test_dynamodb_update_table.py
index f73adcbda..a1f2ad819 100644
--- a/tests/test_dynamodb/test_dynamodb_update_table.py
+++ b/tests/test_dynamodb/test_dynamodb_update_table.py
@@ -53,6 +53,23 @@ def test_update_table_throughput():
     assert table.provisioned_throughput["WriteCapacityUnits"] == 6
 
 
+@mock_aws
+def test_update_table_deletion_protection_enabled():
+    conn = boto3.resource("dynamodb", region_name="us-west-2")
+    table = conn.create_table(
+        TableName="messages",
+        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
+        BillingMode="PAY_PER_REQUEST",
+        DeletionProtectionEnabled=False,
+    )
+    assert not table.deletion_protection_enabled
+
+    table.update(DeletionProtectionEnabled=True)
+
+    assert table.deletion_protection_enabled
+
+
 @mock_aws
 def test_update_table__enable_stream():
     conn = boto3.client("dynamodb", region_name="us-east-1")
diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py
index cbb9cb5ad..ed6127d42 100644
--- a/tests/test_ec2/test_security_groups.py
+++ b/tests/test_ec2/test_security_groups.py
@@ -304,6 +304,13 @@ def test_authorize_ip_range_and_revoke():
         {"CidrIp": "123.123.123.123/32"}
     ]
 
+    # Bad group must produce error
+    with pytest.raises(ClientError) as ex:
+        client.revoke_security_group_egress(GroupId="badcompany")
+    assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
+    assert "RequestId" in ex.value.response["ResponseMetadata"]
+    assert ex.value.response["Error"]["Code"] == "InvalidGroup.NotFound"
+
     # Wrong Cidr should throw error
     with pytest.raises(ClientError) as ex:
         wrong_permissions = copy.deepcopy(egress_permissions)
@@ -380,6 +387,13 @@ def test_authorize_other_group_and_revoke():
     assert "RequestId" in ex.value.response["ResponseMetadata"]
     assert ex.value.response["Error"]["Code"] == "InvalidGroup.NotFound"
 
+    # Bad group must produce error
+    with pytest.raises(ClientError) as ex:
+        client.revoke_security_group_ingress(GroupId="badcompany")
+    assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
+    assert "RequestId" in ex.value.response["ResponseMetadata"]
+    assert ex.value.response["Error"]["Code"] == "InvalidGroup.NotFound"
+
     # Actually revoke
     security_group.revoke_ingress(IpPermissions=permissions)
 
diff --git a/tests/test_panorama/test_nodes.py b/tests/test_panorama/test_nodes.py
new file mode 100644
index 000000000..d1408e5a2
--- /dev/null
+++ b/tests/test_panorama/test_nodes.py
@@ -0,0 +1,162 @@
+import datetime
+
+import boto3
+
+from moto import mock_aws
+
+
+@mock_aws
+def test_create_node_from_template_job() -> None:
+    # Given
+    panorama_client = boto3.client("panorama")
+
+    # When
+    response = panorama_client.create_node_from_template_job(
+        JobTags=[
+            {"ResourceType": "PACKAGE", "Tags": {"key": "value"}},
+        ],
+        NodeDescription="a description",
+        NodeName="a-camera-node",
+        OutputPackageName="an-output-package-name",
+        OutputPackageVersion="1.0",
+        TemplateParameters={
+            "Username": "a-user-name",
+            "Password": "a-password",
+            "StreamUrl": "rtsp://",
+        },
+        TemplateType="RTSP_CAMERA_STREAM",
+    )
+
+    # Then
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert "JobId" in response
+    assert isinstance(response["JobId"], str)
+
+
+@mock_aws
+def test_describe_node_from_template_job() -> None:
+    # Given
+    panorama_client = boto3.client("panorama")
+
+    # When
+    response = panorama_client.create_node_from_template_job(
+        JobTags=[
+            {"ResourceType": "PACKAGE", "Tags": {"key": "value"}},
+        ],
+        NodeDescription="a description",
+        NodeName="not-a-camera-node",
+        OutputPackageName="not-an-output-package-name",
+        OutputPackageVersion="1.0",
+        TemplateParameters={
+            "Username": "not-a-user-name",
+            "Password": "not-a-password",
+            "StreamUrl": "rtsp://192.168.0.201/1",
+        },
+        TemplateType="RTSP_CAMERA_STREAM",
+    )
+    describe_response = panorama_client.describe_node_from_template_job(
+        JobId=response["JobId"]
+    )
+
+    # Then
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert "JobId" in response
+    assert isinstance(response["JobId"], str)
+    assert isinstance(describe_response["CreatedTime"], datetime.datetime)
+    assert describe_response["JobId"] == response["JobId"]
+    assert describe_response["JobTags"] == [
+        {"ResourceType": "PACKAGE", "Tags": {"key": "value"}}
+    ]
+    assert isinstance(describe_response["LastUpdatedTime"], datetime.datetime)
+    # Weird, seems to be a bug in panorama
+    assert describe_response["NodeDescription"] == "not-a-camera-node"
+    assert describe_response["NodeName"] == "not-a-camera-node"
+    assert describe_response["OutputPackageName"] == "not-an-output-package-name"
+    assert describe_response["OutputPackageVersion"] == "1.0"
+    assert describe_response["Status"] == "PENDING"
+    assert describe_response["TemplateParameters"] == {
+        "Username": "SAVED_AS_SECRET",
+        "Password": "SAVED_AS_SECRET",
+        "StreamUrl": "rtsp://192.168.0.201/1",
+    }
+    assert describe_response["TemplateType"] == "RTSP_CAMERA_STREAM"
+
+
+@mock_aws
+def test_list_nodes() -> None:
+    # Given
+    panorama_client = boto3.client("panorama")
+    panorama_client.create_node_from_template_job(
+        JobTags=[
+            {"ResourceType": "PACKAGE", "Tags": {"key": "value"}},
+        ],
+        NodeDescription="a description",
+        NodeName="not-a-camera-node",
+        OutputPackageName="not-an-output-package-name",
+        OutputPackageVersion="1.0",
+        TemplateParameters={
+            "Username": "not-a-user-name",
+            "Password": "not-a-password",
+            "StreamUrl": "rtsp://",
+        },
+        TemplateType="RTSP_CAMERA_STREAM",
+    )
+    panorama_client.create_node_from_template_job(
+        JobTags=[
+            {"ResourceType": "PACKAGE", "Tags": {"key": "value"}},
+        ],
+        NodeDescription="a description",
+        NodeName="not-another-camera-node",
+        OutputPackageName="not-another-output-package-name",
+        OutputPackageVersion="1.0",
+        TemplateParameters={
+            "Username": "not-another-user-name",
+            "Password": "not-another-password",
+            "StreamUrl": "rtsp://",
+        },
+        TemplateType="RTSP_CAMERA_STREAM",
+    )
+    given_category = "MEDIA_SOURCE"
+
+    # When
+    response_page_1 = panorama_client.list_nodes(Category=given_category, MaxResults=1)
+    response_page_2 = panorama_client.list_nodes(
+        Category=given_category, MaxResults=1, NextToken=response_page_1["NextToken"]
+    )
+
+    # Then
+    assert response_page_1["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert "Nodes" in response_page_1
+    assert isinstance(response_page_1["Nodes"], list)
+    assert len(response_page_1["Nodes"]) == 1
+    assert "NextToken" in response_page_1
+    assert isinstance(response_page_1["NextToken"], str)
+    node = response_page_1["Nodes"][0]
+    assert node["Category"] == given_category
+    assert isinstance(node["CreatedTime"], datetime.datetime)
+    assert node["Description"] == "a description"
+    assert node["Name"] == "not-a-camera-node"
+    assert node["OwnerAccount"] == "123456789012"
+    assert (
+        node["PackageArn"]
+        == "arn:aws:panorama:us-east-1:123456789012:package/package-+Y6+ycqU+ogoBTHuibMzGg=="
+    )
+    assert node["PackageId"] == "package-+Y6+ycqU+ogoBTHuibMzGg=="
+    assert node["PackageName"] == "not-an-output-package-name"
+    assert node["PackageVersion"] == "1.0"
+    assert (
+        node["PatchVersion"]
+        == "y6ycquogobthuibmzggy6ycquogobthuibmzggy6ycquogobthuibmzggy6ycquo"
+    )
+
+    assert response_page_2["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert "Nodes" in response_page_2
+    assert isinstance(response_page_2["Nodes"], list)
+    assert len(response_page_2["Nodes"]) == 1
+    assert "NextToken" not in response_page_2
+
+
+"""
+botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the CreateNodeFromTemplateJob operation: {"reason":"FIELD_VALIDATION_FAILED","message":"The input fails to satisfy the constraints specified by an AWS service.","fields":[{"name":"TemplateParameters","message":"Unsupported template parameters: [param]"}]}
+botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the CreateNodeFromTemplateJob operation: {"Message":[ECMA 262 regex \"^([0-9]+)\\.([0-9]+)$\" does not match input string \"not-an-output-package-version\"]}
+"""
diff --git a/tests/test_sns/test_application_boto3.py b/tests/test_sns/test_application_boto3.py
index d13872d27..8411e4dec 100644
--- a/tests/test_sns/test_application_boto3.py
+++ b/tests/test_sns/test_application_boto3.py
@@ -139,7 +139,43 @@ def test_create_platform_endpoint():
 
 @pytest.mark.aws_verified
 @sns_aws_verified
-def test_create_duplicate_platform_endpoint(api_key=None):
+def test_create_duplicate_default_platform_endpoint(api_key=None):
+    conn = boto3.client("sns", region_name="us-east-1")
+    platform_name = str(uuid4())[0:6]
+    app_arn = None
+    try:
+        platform_application = conn.create_platform_application(
+            Name=platform_name,
+            Platform="GCM",
+            Attributes={"PlatformCredential": api_key},
+        )
+        app_arn = platform_application["PlatformApplicationArn"]
+
+        # Create duplicate endpoints
+        arn1 = conn.create_platform_endpoint(
+            PlatformApplicationArn=app_arn, Token="token"
+        )["EndpointArn"]
+        arn2 = conn.create_platform_endpoint(
+            PlatformApplicationArn=app_arn, Token="token"
+        )["EndpointArn"]
+
+        # Create another duplicate endpoint, just specify the default value
+        arn3 = conn.create_platform_endpoint(
+            PlatformApplicationArn=app_arn,
+            Token="token",
+            Attributes={"Enabled": "true"},
+        )["EndpointArn"]
+
+        assert arn1 == arn2
+        assert arn2 == arn3
+    finally:
+        if app_arn is not None:
+            conn.delete_platform_application(PlatformApplicationArn=app_arn)
+
+
+@pytest.mark.aws_verified
+@sns_aws_verified
+def test_create_duplicate_platform_endpoint_with_attrs(api_key=None):
     identity = boto3.client("sts", region_name="us-east-1").get_caller_identity()
     account_id = identity["Account"]
 
diff --git a/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py b/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py
index 1fbd708d0..33eedf528 100644
--- a/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py
+++ b/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py
@@ -6,6 +6,7 @@ from uuid import uuid4
 import boto3
 import pytest
 import requests
+from botocore.exceptions import ClientError
 
 from moto import mock_aws
 
@@ -240,3 +241,54 @@ def test_state_machine_calling_dynamodb_put_and_delete(table_name=None, sleep_ti
         exec_input=json.dumps(exec_input),
         sleep_time=sleep_time,
     )
+
+
+@aws_verified
+@pytest.mark.aws_verified
+def test_send_task_failure_invalid_token(table_name=None, sleep_time=0):
+    dynamodb = boto3.client("dynamodb", "us-east-1")
+    exec_input = {"TableName": table_name, "Item": {"id": {"S": "id1"}}}
+
+    tmpl_name = "services/dynamodb_task_token"
+
+    def _verify_result(client, execution, execution_arn):
+        if execution["status"] == "RUNNING":
+            items = dynamodb.scan(TableName=table_name)["Items"]
+            if len(items) > 0:
+                # Execute
+                with pytest.raises(ClientError) as exc:
+                    client.send_task_failure(taskToken="bad_token", error="test error")
+
+                # Verify
+                assert (
+                    exc.value.response["Error"]["Code"] == "UnrecognizedClientException"
+                )
+                assert (
+                    exc.value.response["Error"]["Message"]
+                    == "The security token included "
+                    "in the request is invalid."
+                )
+
+                # Execute
+                with pytest.raises(ClientError) as exc:
+                    client.send_task_success(
+                        taskToken="bad_token", output="test output"
+                    )
+
+                # Verify
+                assert (
+                    exc.value.response["Error"]["Code"] == "UnrecognizedClientException"
+                )
+                assert (
+                    exc.value.response["Error"]["Message"]
+                    == "The security token included "
+                    "in the request is invalid."
+                )
+
+    verify_execution_result(
+        _verify_result,
+        expected_status=None,
+        tmpl_name=tmpl_name,
+        exec_input=json.dumps(exec_input),
+        sleep_time=sleep_time,
+    )