Repository: getmoto/moto
Pull request #8082 (2024-09-04 07:02:38)

AppMesh: Support tagging for Virtual Nodes/Routers

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8082?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.44%. Comparing base [(`321c08d`)](https://app.codecov.io/gh/getmoto/moto/commit/321c08d0701a29316c7d576516af497412d443bc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`7b3eb2a`)](https://app.codecov.io/gh/getmoto/moto/commit/7b3eb2a40f8018d9a3e250f2d2560b546ddf3f94?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 1 commits behind head on master.

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


```diff
@@           Coverage Diff           @@
##           master    #8082   +/-   ##
=======================================
  Coverage   94.44%   94.44%           
=======================================
  Files        1141     1141           
  Lines       98071    98078    +7     
=======================================
+ Hits        92620    92628    +8     
+ Misses       5451     5450    -1     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8082/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/8082/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.98% <0.00%> (-0.01%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8082/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.41% <100.00%> (+<0.01%)` | :arrow_up: |

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/8082?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/moto/appmesh/dataclasses/mesh.py b/moto/appmesh/dataclasses/mesh.py
index f2d9a1684..1e88e95d1 100644
--- a/moto/appmesh/dataclasses/mesh.py
+++ b/moto/appmesh/dataclasses/mesh.py
@@ -18,9 +18,9 @@ class Mesh:
     metadata: Metadata
     spec: MeshSpec
     status: Status
-    tags: List[Dict[str, str]]
     virtual_nodes: Dict[str, VirtualNode] = field(default_factory=dict)
     virtual_routers: Dict[str, VirtualRouter] = field(default_factory=dict)
+    tags: List[Dict[str, str]] = field(default_factory=list)
 
     def to_dict(self) -> Dict[str, Any]:  # type ignore[misc]
         return {
diff --git a/moto/appmesh/dataclasses/route.py b/moto/appmesh/dataclasses/route.py
index daedefa65..636f4e384 100644
--- a/moto/appmesh/dataclasses/route.py
+++ b/moto/appmesh/dataclasses/route.py
@@ -311,9 +311,9 @@ class Route:
     metadata: RouteMetadata
     route_name: str
     spec: RouteSpec
-    tags: Optional[List[Dict[str, str]]]
     virtual_router_name: str
     status: Status = field(default_factory=lambda: {"status": "ACTIVE"})
+    tags: List[Dict[str, str]] = field(default_factory=list)
 
     def to_dict(self) -> Dict[str, Any]:  # type: ignore[misc]
         return clean_dict(
diff --git a/moto/appmesh/dataclasses/virtual_node.py b/moto/appmesh/dataclasses/virtual_node.py
index 96c3f2b95..a356ce760 100644
--- a/moto/appmesh/dataclasses/virtual_node.py
+++ b/moto/appmesh/dataclasses/virtual_node.py
@@ -550,9 +550,9 @@ class VirtualNode:
     mesh_owner: str
     metadata: VirtualNodeMetadata
     spec: VirtualNodeSpec
-    tags: Optional[List[Dict[str, str]]]
     virtual_node_name: str
     status: Status = field(default_factory=lambda: {"status": "ACTIVE"})
+    tags: List[Dict[str, str]] = field(default_factory=list)
 
     def to_dict(self) -> Dict[str, Any]:  # type: ignore[misc]
         return clean_dict(
diff --git a/moto/appmesh/dataclasses/virtual_router.py b/moto/appmesh/dataclasses/virtual_router.py
index 956627ab3..61118ded2 100644
--- a/moto/appmesh/dataclasses/virtual_router.py
+++ b/moto/appmesh/dataclasses/virtual_router.py
@@ -23,8 +23,8 @@ class VirtualRouter:
     spec: VirtualRouterSpec
     status: Status
     virtual_router_name: str
-    tags: List[Dict[str, str]]
     routes: Dict[str, Route] = field(default_factory=dict)
+    tags: List[Dict[str, str]] = field(default_factory=list)
 
     def to_dict(self) -> Dict[str, Any]:  # type ignore[misc]
         return {
diff --git a/moto/appmesh/models.py b/moto/appmesh/models.py
index f27b96943..800720129 100644
--- a/moto/appmesh/models.py
+++ b/moto/appmesh/models.py
@@ -1,6 +1,6 @@
 """AppMeshBackend class with methods for supported APIs."""
 
-from typing import Any, Dict, List, Literal, Optional
+from typing import Any, Dict, List, Literal, Optional, Union
 
 from moto.appmesh.dataclasses.mesh import (
     Mesh,
@@ -184,7 +184,7 @@ class AppMeshBackend(BaseBackend):
         mesh_name: str,
         egress_filter_type: Optional[str],
         ip_preference: Optional[str],
-        tags: List[Dict[str, str]],
+        tags: Optional[List[Dict[str, str]]],
     ) -> Mesh:
         from moto.sts import sts_backends
 
@@ -207,7 +207,7 @@ class AppMeshBackend(BaseBackend):
             spec=spec,
             status={"status": "ACTIVE"},
             metadata=metadata,
-            tags=tags,
+            tags=tags or list(),
         )
         self.meshes[mesh_name] = mesh
         return mesh
@@ -266,11 +266,21 @@ class AppMeshBackend(BaseBackend):
             for mesh in self.meshes.values()
         ]
 
-    def _get_resource_with_arn(self, resource_arn: str) -> Mesh:
+    def _get_resource_with_arn(
+        self, resource_arn: str
+    ) -> Union[Mesh, VirtualRouter, Route, VirtualNode]:
         for mesh in self.meshes.values():
             if mesh.metadata.arn == resource_arn:
                 return mesh
-        # TODO implement for routers, routes, and virtual nodes
+            for virtual_router in mesh.virtual_routers.values():
+                if virtual_router.metadata.arn == resource_arn:
+                    return virtual_router
+                for route in virtual_router.routes.values():
+                    if route.metadata.arn == resource_arn:
+                        return route
+            for virtual_node in mesh.virtual_nodes.values():
+                if virtual_node.metadata.arn == resource_arn:
+                    return virtual_node
         raise ResourceNotFoundError(resource_arn)
 
     @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
@@ -279,8 +289,8 @@ class AppMeshBackend(BaseBackend):
 
     def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None:
         if len(tags) > 0:
-            mesh = self._get_resource_with_arn(resource_arn=resource_arn)
-            mesh.tags.extend(tags)
+            resource = self._get_resource_with_arn(resource_arn=resource_arn)
+            resource.tags.extend(tags)
         return
 
     def describe_virtual_router(
@@ -299,7 +309,7 @@ class AppMeshBackend(BaseBackend):
         mesh_name: str,
         mesh_owner: Optional[str],
         port_mappings: List[PortMapping],
-        tags: List[Dict[str, str]],
+        tags: Optional[List[Dict[str, str]]],
         virtual_router_name: str,
     ) -> VirtualRouter:
         self._check_router_availability(
@@ -323,7 +333,7 @@ class AppMeshBackend(BaseBackend):
             metadata=metadata,
             status={"status": "ACTIVE"},
             spec=spec,
-            tags=tags,
+            tags=tags or list(),
         )
         self.meshes[mesh_name].virtual_routers[virtual_router_name] = virtual_router
         return virtual_router
@@ -419,7 +429,7 @@ class AppMeshBackend(BaseBackend):
             metadata=metadata,
             route_name=route_name,
             spec=spec,
-            tags=tags,
+            tags=tags or list(),
             virtual_router_name=virtual_router_name,
         )
         self.meshes[mesh_name].virtual_routers[virtual_router_name].routes[
@@ -554,7 +564,7 @@ class AppMeshBackend(BaseBackend):
             mesh_owner=owner,
             metadata=metadata,
             spec=spec,
-            tags=tags,
+            tags=tags or list(),
             virtual_node_name=virtual_node_name,
         )
         self.meshes[mesh_name].virtual_nodes[virtual_node_name] = virtual_node
diff --git a/moto/appmesh/utils/spec_parsing.py b/moto/appmesh/utils/spec_parsing.py
index f5a7b8715..05361a7a1 100644
--- a/moto/appmesh/utils/spec_parsing.py
+++ b/moto/appmesh/utils/spec_parsing.py
@@ -68,12 +68,6 @@ from moto.appmesh.exceptions import (
 )
 
 
-def clean_dict(obj: Dict[str, Any]) -> Dict[str, Any]:  # type: ignore[misc]
-    return {
-        key: value for key, value in obj.items() if value is not None and value != []
-    }
-
-
 def port_mappings_from_router_spec(spec: Any) -> List[RouterPortMapping]:  # type: ignore[misc]
     return [
         RouterPortMapping(
diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py
index c56d91081..5c4432e4f 100644
--- a/moto/resourcegroupstaggingapi/models.py
+++ b/moto/resourcegroupstaggingapi/models.py
@@ -690,8 +690,25 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
             "sagemaker:model-explainability-job-definition": self.sagemaker_backend.model_explainability_job_definitions,
             "sagemaker:model-quality-job-definition": self.sagemaker_backend.model_quality_job_definitions,
             "sagemaker:hyper-parameter-tuning-job": self.sagemaker_backend.hyper_parameter_tuning_jobs,
-            "sagemaker:model-bias-defintion": self.sagemaker_backend.model_bias_job_definitions,
+            "sagemaker:model-bias-job-definition": self.sagemaker_backend.model_bias_job_definitions,
             "sagemaker:data-quality-job-definition": self.sagemaker_backend.data_quality_job_definitions,
+            "sagemaker:model": self.sagemaker_backend._models,
+            "sagemaker:notebook-instance": self.sagemaker_backend.notebook_instances,
+            "sagemaker:endpoint-config": self.sagemaker_backend.endpoint_configs,
+            "sagemaker:endpoint": self.sagemaker_backend.endpoints,
+            "sagemaker:experiment": self.sagemaker_backend.experiments,
+            "sagemaker:pipeline": self.sagemaker_backend.pipelines,
+            "sagemaker:pipeline-execution": self.sagemaker_backend.pipeline_executions,
+            "sagemaker:processing-job": self.sagemaker_backend.processing_jobs,
+            "sagemaker:trial": self.sagemaker_backend.trials,
+            "sagemaker:trial-component": self.sagemaker_backend.trial_components,
+            "sagemaker:training-job": self.sagemaker_backend.training_jobs,
+            "sagemaker:transform-job": self.sagemaker_backend.transform_jobs,
+            "sagemaker:notebook-instance-lifecycle-config": self.sagemaker_backend.notebook_instance_lifecycle_configurations,
+            "sagemaker:model-card": self.sagemaker_backend.model_cards,
+            "sagemaker:model-package-group": self.sagemaker_backend.model_package_groups,
+            "sagemaker:model-package": self.sagemaker_backend.model_packages,
+            "sagemaker:feature-group": self.sagemaker_backend.feature_groups,
         }
         for resource_type, resource_source in sagemaker_resource_map.items():
             if (
diff --git a/moto/sagemaker/models.py b/moto/sagemaker/models.py
index c354010d3..9d2fc7ec4 100644
--- a/moto/sagemaker/models.py
+++ b/moto/sagemaker/models.py
@@ -39,19 +39,19 @@ PAGINATION_MODEL = {
         "input_token": "NextToken",
         "limit_key": "MaxResults",
         "limit_default": 100,
-        "unique_attribute": "experiment_arn",
+        "unique_attribute": "arn",
     },
     "list_trials": {
         "input_token": "NextToken",
         "limit_key": "MaxResults",
         "limit_default": 100,
-        "unique_attribute": "trial_arn",
+        "unique_attribute": "arn",
     },
     "list_trial_components": {
         "input_token": "NextToken",
         "limit_key": "MaxResults",
         "limit_default": 100,
-        "unique_attribute": "trial_component_arn",
+        "unique_attribute": "arn",
     },
     "list_tags": {
         "input_token": "NextToken",
@@ -63,13 +63,13 @@ PAGINATION_MODEL = {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 100,
-        "unique_attribute": "model_package_group_arn",
+        "unique_attribute": "arn",
     },
     "list_model_packages": {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 100,
-        "unique_attribute": "model_package_arn",
+        "unique_attribute": "arn",
     },
     "list_notebook_instances": {
         "input_token": "next_token",
@@ -87,7 +87,7 @@ PAGINATION_MODEL = {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 100,
-        "unique_attribute": "cluster_node_arn",
+        "unique_attribute": "arn",
     },
     "list_auto_ml_jobs": {
         "input_token": "next_token",
@@ -99,13 +99,13 @@ PAGINATION_MODEL = {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 100,
-        "unique_attribute": "endpoint_arn",
+        "unique_attribute": "arn",
     },
     "list_endpoint_configs": {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 100,
-        "unique_attribute": "endpoint_config_arn",
+        "unique_attribute": "arn",
     },
     "list_compilation_jobs": {
         "input_token": "next_token",
@@ -141,7 +141,7 @@ PAGINATION_MODEL = {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 100,
-        "unique_attribute": "model_card_arn",
+        "unique_attribute": "arn",
     },
     "list_model_card_versions": {
         "input_token": "next_token",
@@ -204,7 +204,7 @@ class FakePipelineExecution(BaseObject):
         pipeline_definition: str,
         client_request_token: str,
     ):
-        self.pipeline_execution_arn = pipeline_execution_arn
+        self.arn = pipeline_execution_arn
         self.pipeline_execution_display_name = pipeline_execution_display_name
         self.pipeline_parameters = pipeline_parameters
         self.pipeline_execution_description = pipeline_execution_description
@@ -253,9 +253,7 @@ class FakePipeline(BaseObject):
         parallelism_configuration: Dict[str, int],
     ):
         self.pipeline_name = pipeline_name
-        self.pipeline_arn = arn_formatter(
-            "pipeline", pipeline_name, account_id, region_name
-        )
+        self.arn = arn_formatter("pipeline", pipeline_name, account_id, region_name)
         self.pipeline_display_name = pipeline_display_name or pipeline_name
         self.pipeline_definition = pipeline_definition
         self.pipeline_description = pipeline_description
@@ -306,10 +304,9 @@ class FakeProcessingJob(BaseObject):
         stopping_condition: Dict[str, int],
     ):
         self.processing_job_name = processing_job_name
-        self.processing_job_arn = FakeProcessingJob.arn_formatter(
+        self.arn = FakeProcessingJob.arn_formatter(
             processing_job_name, account_id, region_name
         )
-
         now_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
         self.creation_time = now_string
         self.last_modified_time = now_string
@@ -327,13 +324,16 @@ class FakeProcessingJob(BaseObject):
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
-        return {
+        response = {
             k: v for k, v in response_object.items() if v is not None and v != [None]
         }
+        response["ProcessingJobArn"] = response.pop("Arn")
+
+        return response
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"ProcessingJobArn": self.processing_job_arn}
+        return {"ProcessingJobArn": self.arn}
 
     @staticmethod
     def arn_formatter(name: str, account_id: str, region: str) -> str:
@@ -384,7 +384,7 @@ class FakeTrainingJob(BaseObject):
         self.debug_rule_configurations = debug_rule_configurations
         self.tensor_board_output_config = tensor_board_output_config
         self.experiment_config = experiment_config
-        self.training_job_arn = FakeTrainingJob.arn_formatter(
+        self.arn = FakeTrainingJob.arn_formatter(
             training_job_name, account_id, region_name
         )
         self.creation_time = self.last_modified_time = datetime.now().strftime(
@@ -430,13 +430,16 @@ class FakeTrainingJob(BaseObject):
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
-        return {
+        response = {
             k: v for k, v in response_object.items() if v is not None and v != [None]
         }
+        response["TrainingJobArn"] = response.pop("Arn")
+
+        return response
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"TrainingJobArn": self.training_job_arn}
+        return {"TrainingJobArn": self.arn}
 
     @staticmethod
     def arn_formatter(name: str, account_id: str, region_name: str) -> str:
@@ -455,9 +458,7 @@ class FakeEndpoint(BaseObject, CloudFormationModel):
         tags: List[Dict[str, str]],
     ):
         self.endpoint_name = endpoint_name
-        self.endpoint_arn = FakeEndpoint.arn_formatter(
-            endpoint_name, account_id, region_name
-        )
+        self.arn = FakeEndpoint.arn_formatter(endpoint_name, account_id, region_name)
         self.endpoint_config_name = endpoint_config_name
         self.production_variants = self._process_production_variants(
             production_variants
@@ -511,7 +512,7 @@ class FakeEndpoint(BaseObject, CloudFormationModel):
     def summary(self) -> Dict[str, Any]:
         return {
             "EndpointName": self.endpoint_name,
-            "EndpointArn": self.endpoint_arn,
+            "EndpointArn": self.arn,
             "CreationTime": self.creation_time,
             "LastModifiedTime": self.last_modified_time,
             "EndpointStatus": self.endpoint_status,
@@ -520,13 +521,16 @@ class FakeEndpoint(BaseObject, CloudFormationModel):
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
-        return {
+        response = {
             k: v for k, v in response_object.items() if v is not None and v != [None]
         }
+        response["EndpointArn"] = response.pop("Arn")
+
+        return response
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"EndpointArn": self.endpoint_arn}
+        return {"EndpointArn": self.arn}
 
     @staticmethod
     def arn_formatter(endpoint_name: str, account_id: str, region_name: str) -> str:
@@ -534,7 +538,7 @@ class FakeEndpoint(BaseObject, CloudFormationModel):
 
     @property
     def physical_resource_id(self) -> str:
-        return self.endpoint_arn
+        return self.arn
 
     @classmethod
     def has_cfn_attr(cls, attr: str) -> bool:
@@ -590,7 +594,7 @@ class FakeEndpoint(BaseObject, CloudFormationModel):
     ) -> "FakeEndpoint":
         # Changes to the Endpoint will not change resource name
         cls.delete_from_cloudformation_json(
-            original_resource.endpoint_arn, cloudformation_json, account_id, region_name
+            original_resource.arn, cloudformation_json, account_id, region_name
         )
         new_resource = cls.create_from_cloudformation_json(
             original_resource.endpoint_name,
@@ -633,6 +637,7 @@ class FakeEndpointConfig(BaseObject, CloudFormationModel):
         self.endpoint_config_arn = FakeEndpointConfig.arn_formatter(
             endpoint_config_name, account_id, region_name
         )
+        self.arn = (self.endpoint_config_arn,)
         self.production_variants = production_variants or []
         self.data_capture_config = data_capture_config or {}
         self.tags = tags or []
@@ -880,7 +885,7 @@ class FakeTransformJob(BaseObject):
         self.data_processing = data_processing
         self.tags = tags
         self.experiment_config = experiment_config
-        self.transform_job_arn = FakeTransformJob.arn_formatter(
+        self.arn = FakeTransformJob.arn_formatter(
             transform_job_name, account_id, region_name
         )
         self.transform_job_status = "Completed"
@@ -913,7 +918,7 @@ class FakeTransformJob(BaseObject):
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"TransformJobArn": self.transform_job_arn}
+        return {"TransformJobArn": self.arn}
 
     @staticmethod
     def arn_formatter(name: str, account_id: str, region_name: str) -> str:
@@ -940,24 +945,25 @@ class Model(BaseObject, CloudFormationModel):
         self.vpc_config = vpc_config
         self.primary_container = primary_container
         self.execution_role_arn = execution_role_arn or "arn:test"
-        self.model_arn = arn_formatter(
-            "model", self.model_name, account_id, region_name
-        )
+        self.arn = arn_formatter("model", self.model_name, account_id, region_name)
 
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
-        return {
+        response = {
             k: v for k, v in response_object.items() if v is not None and v != [None]
         }
+        response["ModelArn"] = response.pop("Arn")
+
+        return response
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"ModelArn": self.model_arn}
+        return {"ModelArn": self.arn}
 
     @property
     def physical_resource_id(self) -> str:
-        return self.model_arn
+        return self.arn
 
     @classmethod
     def has_cfn_attr(cls, attr: str) -> bool:
@@ -1017,7 +1023,7 @@ class Model(BaseObject, CloudFormationModel):
     ) -> "Model":
         # Most changes to the model will change resource name for Models
         cls.delete_from_cloudformation_json(
-            original_resource.model_arn, cloudformation_json, account_id, region_name
+            original_resource.arn, cloudformation_json, account_id, region_name
         )
         new_resource = cls.create_from_cloudformation_json(
             new_resource_name, cloudformation_json, account_id, region_name
@@ -1065,7 +1071,7 @@ class ModelPackageGroup(BaseObject):
         )
         datetime_now = datetime.now(tzutc())
         self.model_package_group_name = model_package_group_name
-        self.model_package_group_arn = model_package_group_arn
+        self.arn = model_package_group_arn
         self.model_package_group_description = model_package_group_description
         self.creation_time = datetime_now
         self.created_by = {
@@ -1083,12 +1089,15 @@ class ModelPackageGroup(BaseObject):
                 response_object[k] = v.isoformat()
         response_values = [
             "ModelPackageGroupName",
-            "ModelPackageGroupArn",
+            "Arn",
             "ModelPackageGroupDescription",
             "CreationTime",
             "ModelPackageGroupStatus",
         ]
-        return {k: v for k, v in response_object.items() if k in response_values}
+        response = {k: v for k, v in response_object.items() if k in response_values}
+        response["ModelPackageGroupArn"] = response.pop("Arn")
+
+        return response
 
 
 class FakeModelCard(BaseObject):
@@ -1106,9 +1115,7 @@ class FakeModelCard(BaseObject):
         last_modified_time: Optional[str] = None,
     ) -> None:
         datetime_now = str(datetime.now(tzutc()))
-        self.model_card_arn = arn_formatter(
-            "model-card", model_card_name, account_id, region_name
-        )
+        self.arn = arn_formatter("model-card", model_card_name, account_id, region_name)
         self.model_card_name = model_card_name
         self.model_card_version = model_card_version
         self.content = content
@@ -1122,7 +1129,7 @@ class FakeModelCard(BaseObject):
 
     def describe(self) -> Dict[str, Any]:
         return {
-            "ModelCardArn": self.model_card_arn,
+            "ModelCardArn": self.arn,
             "ModelCardName": self.model_card_name,
             "ModelCardVersion": self.model_card_version,
             "Content": self.content,
@@ -1137,7 +1144,7 @@ class FakeModelCard(BaseObject):
     def summary(self) -> Dict[str, Any]:
         return {
             "ModelCardName": self.model_card_name,
-            "ModelCardArn": self.model_card_arn,
+            "ModelCardArn": self.arn,
             "ModelCardStatus": self.model_card_status,
             "CreationTime": self.creation_time,
             "LastModifiedTime": self.last_modified_time,
@@ -1146,7 +1153,7 @@ class FakeModelCard(BaseObject):
     def version_summary(self) -> Dict[str, Any]:
         return {
             "ModelCardName": self.model_card_name,
-            "ModelCardArn": self.model_card_arn,
+            "ModelCardArn": self.arn,
             "ModelCardStatus": self.model_card_status,
             "ModelCardVersion": self.model_card_version,
             "CreationTime": self.creation_time,
@@ -1188,7 +1195,7 @@ class FeatureGroup(BaseObject):
         self.role_arn = role_arn
 
         self.creation_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
-        self.feature_group_arn = arn_formatter(
+        self.arn = arn_formatter(
             region_name=region_name,
             account_id=account_id,
             _type="feature-group",
@@ -1198,7 +1205,7 @@ class FeatureGroup(BaseObject):
 
     def describe(self) -> Dict[str, Any]:
         return {
-            "FeatureGroupArn": self.feature_group_arn,
+            "FeatureGroupArn": self.arn,
             "FeatureGroupName": self.feature_group_name,
             "RecordIdentifierFeatureName": self.record_identifier_feature_name,
             "EventTimeFeatureName": self.event_time_feature_name,
@@ -1258,7 +1265,7 @@ class ModelPackage(BaseObject):
         self.model_package_name = model_package_name
         self.model_package_group_name = model_package_group_name
         self.model_package_version = model_package_version
-        self.model_package_arn = model_package_arn
+        self.arn = model_package_arn
         self.model_package_description = model_package_description
         self.creation_time = datetime_now
         self.inference_specification = inference_specification
@@ -1314,7 +1321,7 @@ class ModelPackage(BaseObject):
             "ModelPackageName",
             "ModelPackageGroupName",
             "ModelPackageVersion",
-            "ModelPackageArn",
+            "Arn",
             "ModelPackageDescription",
             "CreationTime",
             "InferenceSpecification",
@@ -1342,12 +1349,15 @@ class ModelPackage(BaseObject):
             del response_object["ModelPackageName"]
         elif self.model_package_type == "Unversioned":
             del response_object["ModelPackageGroupName"]
-        return {
+        response = {
             k: v
             for k, v in response_object.items()
             if k in response_values
             if v is not None
         }
+        response["ModelPackageArn"] = response.pop("Arn")
+
+        return response
 
     def modifications_done(self) -> None:
         self.last_modified_time = datetime.now(tzutc())
@@ -2748,28 +2758,29 @@ class FakeSageMakerNotebookInstanceLifecycleConfig(BaseObject, CloudFormationMod
         self.creation_time = self.last_modified_time = datetime.now().strftime(
             "%Y-%m-%d %H:%M:%S"
         )
-        self.notebook_instance_lifecycle_config_arn = (
-            FakeSageMakerNotebookInstanceLifecycleConfig.arn_formatter(
-                self.notebook_instance_lifecycle_config_name, account_id, region_name
-            )
+        self.arn = FakeSageMakerNotebookInstanceLifecycleConfig.arn_formatter(
+            self.notebook_instance_lifecycle_config_name, account_id, region_name
         )
 
     @staticmethod
     def arn_formatter(name: str, account_id: str, region_name: str) -> str:
         return arn_formatter(
-            "notebook-instance-lifecycle-configuration", name, account_id, region_name
+            "notebook-instance-lifecycle-config", name, account_id, region_name
         )
 
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
-        return {
+        response = {
             k: v for k, v in response_object.items() if v is not None and v != [None]
         }
+        response["NotebookInstanceLifecycleConfigArn"] = response.pop("Arn")
+
+        return response
 
     @property
     def physical_resource_id(self) -> str:
-        return self.notebook_instance_lifecycle_config_arn
+        return self.arn
 
     @classmethod
     def has_cfn_attr(cls, attr: str) -> bool:
@@ -2823,7 +2834,7 @@ class FakeSageMakerNotebookInstanceLifecycleConfig(BaseObject, CloudFormationMod
     ) -> "FakeSageMakerNotebookInstanceLifecycleConfig":
         # Operations keep same resource name so delete old and create new to mimic update
         cls.delete_from_cloudformation_json(
-            original_resource.notebook_instance_lifecycle_config_arn,
+            original_resource.arn,
             cloudformation_json,
             account_id,
             region_name,
@@ -2997,7 +3008,7 @@ class SageMakerModelBackend(BaseBackend):
         experiment_data = self.experiments[experiment_name]
         return {
             "ExperimentName": experiment_data.experiment_name,
-            "ExperimentArn": experiment_data.experiment_arn,
+            "ExperimentArn": experiment_data.arn,
             "CreationTime": experiment_data.creation_time,
             "LastModifiedTime": experiment_data.last_modified_time,
         }
@@ -3143,7 +3154,7 @@ class SageMakerModelBackend(BaseBackend):
             experiment_summaries = [
                 {
                     "ExperimentName": experiment_data.experiment_name,
-                    "ExperimentArn": experiment_data.experiment_arn,
+                    "ExperimentArn": experiment_data.arn,
                     "CreationTime": experiment_data.creation_time,
                     "LastModifiedTime": experiment_data.last_modified_time,
                 }
@@ -3160,7 +3171,7 @@ class SageMakerModelBackend(BaseBackend):
             trial_summaries = [
                 {
                     "TrialName": trial_data.trial_name,
-                    "TrialArn": trial_data.trial_arn,
+                    "TrialArn": trial_data.arn,
                     "CreationTime": trial_data.creation_time,
                     "LastModifiedTime": trial_data.last_modified_time,
                 }
@@ -3177,7 +3188,7 @@ class SageMakerModelBackend(BaseBackend):
             trial_component_summaries = [
                 {
                     "TrialComponentName": trial_component_data.trial_component_name,
-                    "TrialComponentArn": trial_component_data.trial_component_arn,
+                    "TrialComponentArn": trial_component_data.arn,
                     "CreationTime": trial_component_data.creation_time,
                     "LastModifiedTime": trial_component_data.last_modified_time,
                 }
@@ -3191,7 +3202,7 @@ class SageMakerModelBackend(BaseBackend):
         if resource == "ModelPackageGroup":
             package_groups = [
                 {
-                    "ModelPackageGroupArn": group.model_package_group_arn,
+                    "ModelPackageGroupArn": group.arn,
                     "ModelPackageGroupDescription": group.model_package_group_description,
                     "CreationTime": group.creation_time.strftime("%Y-%m-%d %H:%M:%S"),
                     "ModelPackageGroupName": group.model_package_group_name,
@@ -3352,10 +3363,8 @@ class SageMakerModelBackend(BaseBackend):
             self.trial_components[trial_component_name].trial_name = trial_name
 
         return {
-            "TrialComponentArn": self.trial_components[
-                trial_component_name
-            ].trial_component_arn,
-            "TrialArn": self.trials[trial_name].trial_arn,
+            "TrialComponentArn": self.trial_components[trial_component_name].arn,
+            "TrialArn": self.trials[trial_name].arn,
         }
 
     def disassociate_trial_component(
@@ -3788,7 +3797,7 @@ class SageMakerModelBackend(BaseBackend):
     def delete_pipeline(self, pipeline_name: str) -> str:
         pipeline = get_pipeline_from_name(self.pipelines, pipeline_name)
         del self.pipelines[pipeline.pipeline_name]
-        return pipeline.pipeline_arn
+        return pipeline.arn
 
     def update_pipeline(self, pipeline_name: str, **kwargs: Any) -> str:
         pipeline = get_pipeline_from_name(self.pipelines, pipeline_name)
@@ -3816,7 +3825,7 @@ class SageMakerModelBackend(BaseBackend):
                     continue
                 setattr(self.pipelines[pipeline_name], attr_key, attr_value)
 
-        return pipeline.pipeline_arn
+        return pipeline.arn
 
     def start_pipeline_execution(
         self,
@@ -3863,7 +3872,7 @@ class SageMakerModelBackend(BaseBackend):
         return {
             "PipelineExecutionSummaries": [
                 {
-                    "PipelineExecutionArn": pipeline_execution_arn,
+                    "PipelineExecutionArn": arn,
                     "StartTime": pipeline_execution.start_time,
                     "PipelineExecutionStatus": pipeline_execution.pipeline_execution_status,
                     "PipelineExecutionDescription": pipeline_execution.pipeline_execution_description,
@@ -3872,7 +3881,7 @@ class SageMakerModelBackend(BaseBackend):
                         pipeline_execution.pipeline_execution_failure_reason
                     ),
                 }
-                for pipeline_execution_arn, pipeline_execution in pipeline.pipeline_executions.items()
+                for arn, pipeline_execution in pipeline.pipeline_executions.items()
             ]
         }
 
@@ -3909,8 +3918,8 @@ class SageMakerModelBackend(BaseBackend):
         pipeline = get_pipeline_from_name(self.pipelines, pipeline_name)
 
         return {
-            "PipelineArn": pipeline.pipeline_arn,
-            "PipelineExecutionArn": pipeline_execution.pipeline_execution_arn,
+            "PipelineArn": pipeline.arn,
+            "PipelineExecutionArn": pipeline_execution.arn,
             "PipelineExecutionDisplayName": pipeline_execution.pipeline_execution_display_name,
             "PipelineExecutionStatus": pipeline_execution.pipeline_execution_status,
             "PipelineExecutionDescription": pipeline_execution.pipeline_execution_description,
@@ -3926,7 +3935,7 @@ class SageMakerModelBackend(BaseBackend):
     def describe_pipeline(self, pipeline_name: str) -> Dict[str, Any]:
         pipeline = get_pipeline_from_name(self.pipelines, pipeline_name)
         return {
-            "PipelineArn": pipeline.pipeline_arn,
+            "PipelineArn": pipeline.arn,
             "PipelineName": pipeline.pipeline_name,
             "PipelineDisplayName": pipeline.pipeline_display_name,
             "PipelineDescription": pipeline.pipeline_description,
@@ -4008,7 +4017,7 @@ class SageMakerModelBackend(BaseBackend):
 
         pipeline_summaries = [
             {
-                "PipelineArn": pipeline_data.pipeline_arn,
+                "PipelineArn": pipeline_data.arn,
                 "PipelineName": pipeline_data.pipeline_name,
                 "PipelineDisplayName": pipeline_data.pipeline_display_name,
                 "PipelineDescription": pipeline_data.pipeline_description,
@@ -4096,7 +4105,7 @@ class SageMakerModelBackend(BaseBackend):
         processing_job_summaries = [
             {
                 "ProcessingJobName": processing_job_data.processing_job_name,
-                "ProcessingJobArn": processing_job_data.processing_job_arn,
+                "ProcessingJobArn": processing_job_data.arn,
                 "CreationTime": processing_job_data.creation_time,
                 "ProcessingEndTime": processing_job_data.processing_end_time,
                 "LastModifiedTime": processing_job_data.last_modified_time,
@@ -4217,7 +4226,7 @@ class SageMakerModelBackend(BaseBackend):
         transform_job_summaries = [
             {
                 "TransformJobName": transform_job_data.transform_job_name,
-                "TransformJobArn": transform_job_data.transform_job_arn,
+                "TransformJobArn": transform_job_data.arn,
                 "CreationTime": transform_job_data.creation_time,
                 "TransformEndTime": transform_job_data.transform_end_time,
                 "LastModifiedTime": transform_job_data.last_modified_time,
@@ -4365,7 +4374,7 @@ class SageMakerModelBackend(BaseBackend):
         training_job_summaries = [
             {
                 "TrainingJobName": training_job_data.training_job_name,
-                "TrainingJobArn": training_job_data.training_job_arn,
+                "TrainingJobArn": training_job_data.arn,
                 "CreationTime": training_job_data.creation_time,
                 "TrainingEndTime": training_job_data.training_end_time,
                 "LastModifiedTime": training_job_data.last_modified_time,
@@ -4426,7 +4435,7 @@ class SageMakerModelBackend(BaseBackend):
                     break
 
         endpoint.endpoint_status = "InService"
-        return endpoint.endpoint_arn
+        return endpoint.arn
 
     def create_model_package_group(
         self,
@@ -4441,9 +4450,7 @@ class SageMakerModelBackend(BaseBackend):
             region_name=self.region_name,
             tags=tags or [],
         )
-        return self.model_package_groups[
-            model_package_group_name
-        ].model_package_group_arn
+        return self.model_package_groups[model_package_group_name].arn
 
     def _get_versioned_or_not(
         self, model_package_type: Optional[str], model_package_version: Optional[int]
@@ -4621,7 +4628,7 @@ class SageMakerModelBackend(BaseBackend):
         )
         model_package.modifications_done()
 
-        return model_package.model_package_arn
+        return model_package.arn
 
     def create_model_package(
         self,
@@ -4695,13 +4702,11 @@ class SageMakerModelBackend(BaseBackend):
             model_package_type=model_package_type,
         )
         self.model_package_name_mapping[model_package.model_package_name] = (
-            model_package.model_package_arn
+            model_package.arn
         )
-        self.model_package_name_mapping[model_package.model_package_arn] = (
-            model_package.model_package_arn
-        )
-        self.model_packages[model_package.model_package_arn] = model_package
-        return model_package.model_package_arn
+        self.model_package_name_mapping[model_package.arn] = model_package.arn
+        self.model_packages[model_package.arn] = model_package
+        return model_package.arn
 
     def create_feature_group(
         self,
@@ -4735,8 +4740,8 @@ class SageMakerModelBackend(BaseBackend):
             account_id=self.account_id,
             tags=tags,
         )
-        self.feature_groups[feature_group.feature_group_arn] = feature_group
-        return feature_group.feature_group_arn
+        self.feature_groups[feature_group.arn] = feature_group
+        return feature_group.arn
 
     def describe_feature_group(
         self,
@@ -5636,7 +5641,7 @@ class SageMakerModelBackend(BaseBackend):
         )
 
         self.model_cards[model_card_name].append(model_card)
-        return model_card.model_card_arn
+        return model_card.arn
 
     def update_model_card(
         self, model_card_name: str, content: str, model_card_status: str
@@ -5668,7 +5673,7 @@ class SageMakerModelBackend(BaseBackend):
         )
 
         self.model_cards[model_card_name].append(model_card)
-        return model_card.model_card_arn
+        return model_card.arn
 
     @paginate(pagination_model=PAGINATION_MODEL)
     def list_model_cards(
@@ -5872,9 +5877,7 @@ class FakeExperiment(BaseObject):
         tags: List[Dict[str, str]],
     ):
         self.experiment_name = experiment_name
-        self.experiment_arn = arn_formatter(
-            "experiment", experiment_name, account_id, region_name
-        )
+        self.arn = arn_formatter("experiment", experiment_name, account_id, region_name)
         self.tags = tags
         self.creation_time = self.last_modified_time = datetime.now().strftime(
             "%Y-%m-%d %H:%M:%S"
@@ -5889,7 +5892,7 @@ class FakeExperiment(BaseObject):
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"ExperimentArn": self.experiment_arn}
+        return {"ExperimentArn": self.arn}
 
 
 class FakeTrial(BaseObject):
@@ -5903,7 +5906,7 @@ class FakeTrial(BaseObject):
         trial_components: List[str],
     ):
         self.trial_name = trial_name
-        self.trial_arn = FakeTrial.arn_formatter(trial_name, account_id, region_name)
+        self.arn = FakeTrial.arn_formatter(trial_name, account_id, region_name)
         self.tags = tags
         self.trial_components = trial_components
         self.experiment_name = experiment_name
@@ -5914,13 +5917,16 @@ class FakeTrial(BaseObject):
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
-        return {
+        response = {
             k: v for k, v in response_object.items() if v is not None and v != [None]
         }
+        response["TrialArn"] = response.pop("Arn")
+
+        return response
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"TrialArn": self.trial_arn}
+        return {"TrialArn": self.arn}
 
     @staticmethod
     def arn_formatter(name: str, account_id: str, region: str) -> str:
@@ -5948,7 +5954,7 @@ class FakeTrialComponent(BaseObject):
         self.display_name = (
             display_name if display_name is not None else trial_component_name
         )
-        self.trial_component_arn = FakeTrialComponent.arn_formatter(
+        self.arn = FakeTrialComponent.arn_formatter(
             trial_component_name, account_id, region_name
         )
         self.status = status
@@ -5971,9 +5977,12 @@ class FakeTrialComponent(BaseObject):
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
         response_object = self.gen_response_object()
         response_object["Metrics"] = self.gen_metrics_response_object()
-        return {
+        response = {
             k: v for k, v in response_object.items() if v is not None and v != [None]
         }
+        response["TrialComponentArn"] = response.pop("Arn")
+
+        return response
 
     def gen_metrics_response_object(
         self,
@@ -6000,7 +6009,7 @@ class FakeTrialComponent(BaseObject):
             timestamp_int: int = cast(int, self.metrics[metrics_name]["Timestamp"])
             metrics_response_object = {
                 "MetricName": metrics_name,
-                "SourceArn": self.trial_component_arn,
+                "SourceArn": self.arn,
                 "TimeStamp": datetime.fromtimestamp(timestamp_int, tz=tzutc()).strftime(
                     "%Y-%m-%d %H:%M:%S"
                 ),
@@ -6016,7 +6025,7 @@ class FakeTrialComponent(BaseObject):
 
     @property
     def response_create(self) -> Dict[str, str]:
-        return {"TrialComponentArn": self.trial_component_arn}
+        return {"TrialComponentArn": self.arn}
 
     @staticmethod
     def arn_formatter(
diff --git a/moto/sagemaker/responses.py b/moto/sagemaker/responses.py
index 5283b796f..2c5ae9f0d 100644
--- a/moto/sagemaker/responses.py
+++ b/moto/sagemaker/responses.py
@@ -168,7 +168,7 @@ class SageMakerResponse(BaseResponse):
             endpoint_config_name=self._get_param("EndpointConfigName"),
             tags=self._get_param("Tags"),
         )
-        return 200, {}, json.dumps({"EndpointArn": endpoint.endpoint_arn})
+        return 200, {}, json.dumps({"EndpointArn": endpoint.arn})
 
     def describe_endpoint(self) -> str:
         endpoint_name = self._get_param("EndpointName")
@@ -192,7 +192,7 @@ class SageMakerResponse(BaseResponse):
             stopping_condition=self._get_param("StoppingCondition"),
             tags=self._get_param("Tags"),
         )
-        response = {"ProcessingJobArn": processing_job.processing_job_arn}
+        response = {"ProcessingJobArn": processing_job.arn}
         return 200, {}, json.dumps(response)
 
     def describe_processing_job(self) -> str:
@@ -218,7 +218,7 @@ class SageMakerResponse(BaseResponse):
             experiment_config=self._get_param("ExperimentConfig"),
         )
         response = {
-            "TransformJobArn": transform_job.transform_job_arn,
+            "TransformJobArn": transform_job.arn,
         }
         return 200, {}, json.dumps(response)
 
@@ -255,7 +255,7 @@ class SageMakerResponse(BaseResponse):
             experiment_config=self._get_param("ExperimentConfig"),
         )
         response = {
-            "TrainingJobArn": training_job.training_job_arn,
+            "TrainingJobArn": training_job.arn,
         }
         return 200, {}, json.dumps(response)
 
@@ -275,7 +275,7 @@ class SageMakerResponse(BaseResponse):
             )
         )
         response = {
-            "NotebookInstanceLifecycleConfigArn": lifecycle_configuration.notebook_instance_lifecycle_config_arn,
+            "NotebookInstanceLifecycleConfigArn": lifecycle_configuration.arn,
         }
         return 200, {}, json.dumps(response)
 
@@ -313,7 +313,7 @@ class SageMakerResponse(BaseResponse):
         experiment_summaries = [
             {
                 "ExperimentName": experiment_data.experiment_name,
-                "ExperimentArn": experiment_data.experiment_arn,
+                "ExperimentArn": experiment_data.arn,
                 "CreationTime": experiment_data.creation_time,
                 "LastModifiedTime": experiment_data.last_modified_time,
             }
@@ -359,7 +359,7 @@ class SageMakerResponse(BaseResponse):
         trial_summaries = [
             {
                 "TrialName": trial_data.trial_name,
-                "TrialArn": trial_data.trial_arn,
+                "TrialArn": trial_data.arn,
                 "CreationTime": trial_data.creation_time,
                 "LastModifiedTime": trial_data.last_modified_time,
             }
@@ -390,7 +390,7 @@ class SageMakerResponse(BaseResponse):
         trial_component_summaries = [
             {
                 "TrialComponentName": trial_component_data.trial_component_name,
-                "TrialComponentArn": trial_component_data.trial_component_arn,
+                "TrialComponentArn": trial_component_data.arn,
                 "CreationTime": trial_component_data.creation_time,
                 "LastModifiedTime": trial_component_data.last_modified_time,
             }
@@ -526,20 +526,20 @@ class SageMakerResponse(BaseResponse):
             parallelism_configuration=self._get_param("ParallelismConfiguration"),
         )
         response = {
-            "PipelineArn": pipeline.pipeline_arn,
+            "PipelineArn": pipeline.arn,
         }
 
         return 200, {}, json.dumps(response)
 
     def delete_pipeline(self) -> TYPE_RESPONSE:
-        pipeline_arn = self.sagemaker_backend.delete_pipeline(
+        arn = self.sagemaker_backend.delete_pipeline(
             pipeline_name=self._get_param("PipelineName"),
         )
-        response = {"PipelineArn": pipeline_arn}
+        response = {"PipelineArn": arn}
         return 200, {}, json.dumps(response)
 
     def update_pipeline(self) -> TYPE_RESPONSE:
-        pipeline_arn = self.sagemaker_backend.update_pipeline(
+        arn = self.sagemaker_backend.update_pipeline(
             pipeline_name=self._get_param("PipelineName"),
             pipeline_display_name=self._get_param("PipelineDisplayName"),
             pipeline_definition=self._get_param("PipelineDefinition"),
@@ -551,7 +551,7 @@ class SageMakerResponse(BaseResponse):
             parallelism_configuration=self._get_param("ParallelismConfiguration"),
         )
 
-        response = {"PipelineArn": pipeline_arn}
+        response = {"PipelineArn": arn}
         return 200, {}, json.dumps(response)
 
     def list_pipelines(self) -> TYPE_RESPONSE:
diff --git a/moto/shield/exceptions.py b/moto/shield/exceptions.py
index 1ae2efb63..0c9976c48 100644
--- a/moto/shield/exceptions.py
+++ b/moto/shield/exceptions.py
@@ -36,3 +36,13 @@ class ValidationException(JsonRESTError):
 
     def __init__(self, message: str):
         super().__init__("ValidationException", message)
+
+
+class SubscriptionNotFoundError(JsonRESTError):
+    code = 400
+
+    def __init__(self) -> None:
+        super().__init__(
+            "SubscriptionNotFoundException",
+            "No Shield subscription has been created for this account",
+        )
diff --git a/moto/shield/models.py b/moto/shield/models.py
index 252f0900c..fc217e3ba 100644
--- a/moto/shield/models.py
+++ b/moto/shield/models.py
@@ -1,6 +1,10 @@
 """ShieldBackend class with methods for supported APIs."""
 
-from typing import Any, Dict, List
+import random
+import string
+from dataclasses import dataclass, field
+from datetime import datetime, timedelta
+from typing import Any, Dict, List, Optional
 
 from moto.core.base_backend import BackendDict, BaseBackend
 from moto.core.common_models import BaseModel
@@ -10,11 +14,132 @@ from moto.shield.exceptions import (
     InvalidResourceException,
     ResourceAlreadyExistsException,
     ResourceNotFoundException,
+    SubscriptionNotFoundError,
     ValidationException,
 )
 from moto.utilities.tagging_service import TaggingService
 
 
+@dataclass
+class Limit:
+    type: str
+    max: int
+
+    def to_dict(self) -> Dict[str, Any]:  # type: ignore
+        return {"Type": self.type, "Max": self.max}
+
+
+@dataclass
+class ArbitraryPatternLimits:
+    max_members: int
+
+    def to_dict(self) -> Dict[str, Any]:  # type: ignore
+        return {"MaxMembers": self.max_members}
+
+
+@dataclass
+class PatternTypeLimits:
+    arbitrary_pattern_limits: ArbitraryPatternLimits
+
+    def to_dict(self) -> Dict[str, Any]:  # type: ignore
+        return {"ArbitraryPatternLimits": self.arbitrary_pattern_limits.to_dict()}
+
+
+@dataclass
+class ProtectionGroupLimits:
+    max_protection_groups: int
+    pattern_type_limits: PatternTypeLimits
+
+    def to_dict(self) -> Dict[str, Any]:  # type: ignore
+        return {
+            "MaxProtectionGroups": self.max_protection_groups,
+            "PatternTypeLimits": self.pattern_type_limits.to_dict(),
+        }
+
+
+@dataclass
+class ProtectionLimits:
+    protected_resource_type_limits: List[Limit]
+
+    def to_dict(self) -> Dict[str, Any]:  # type: ignore
+        return {
+            "ProtectedResourceTypeLimits": [
+                limit.to_dict() for limit in self.protected_resource_type_limits
+            ]
+        }
+
+
+@dataclass
+class SubscriptionLimits:
+    protection_limits: ProtectionLimits
+    protection_group_limits: ProtectionGroupLimits
+
+    def to_dict(self) -> Dict[str, Any]:  # type: ignore
+        return {
+            "ProtectionLimits": self.protection_limits.to_dict(),
+            "ProtectionGroupLimits": self.protection_group_limits.to_dict(),
+        }
+
+
+def default_subscription_limits() -> SubscriptionLimits:
+    protection_limits = ProtectionLimits(
+        protected_resource_type_limits=[
+            Limit(type="ELASTIC_IP_ADDRESS", max=100),
+            Limit(type="APPLICATION_LOAD_BALANCER", max=50),
+        ]
+    )
+    protection_group_limits = ProtectionGroupLimits(
+        max_protection_groups=20,
+        pattern_type_limits=PatternTypeLimits(
+            arbitrary_pattern_limits=ArbitraryPatternLimits(max_members=100)
+        ),
+    )
+    return SubscriptionLimits(
+        protection_limits=protection_limits,
+        protection_group_limits=protection_group_limits,
+    )
+
+
+@dataclass
+class Subscription:
+    account_id: str
+    start_time: datetime = field(default_factory=datetime.now)
+    end_time: datetime = field(
+        default_factory=lambda: datetime.now() + timedelta(days=365)
+    )
+    auto_renew: str = field(default="ENABLED")
+    limits: List[Limit] = field(
+        default_factory=lambda: [Limit(type="MitigationCapacityUnits", max=10000)]
+    )
+    proactive_engagement_status: str = field(default="ENABLED")
+    subscription_limits: SubscriptionLimits = field(
+        default_factory=default_subscription_limits
+    )
+    subscription_arn: str = field(default="")
+    time_commitment_in_seconds: int = field(default=31536000)
+
+    def __post_init__(self) -> None:
+        if self.subscription_arn == "":
+            subscription_id = "".join(random.choices(string.hexdigits[:16], k=12))
+            subscription_id_formatted = "-".join(
+                [subscription_id[i : i + 4] for i in range(0, 12, 4)]
+            )
+            self.subscription_arn = f"arn:aws:shield::{self.account_id}:subscription/{subscription_id_formatted}"
+        return
+
+    def to_dict(self) -> Dict[str, Any]:  # type: ignore
+        return {
+            "StartTime": self.start_time.strftime("%d/%m/%Y, %H:%M:%S"),
+            "EndTime": self.end_time.strftime("%d/%m/%Y, %H:%M:%S"),
+            "TimeCommitmentInSeconds": self.time_commitment_in_seconds,
+            "AutoRenew": self.auto_renew,
+            "Limits": [limit.to_dict() for limit in self.limits],
+            "ProactiveEngagementStatus": self.proactive_engagement_status,
+            "SubscriptionLimits": self.subscription_limits.to_dict(),
+            "SubscriptionArn": self.subscription_arn,
+        }
+
+
 class Protection(BaseModel):
     def __init__(
         self, account_id: str, name: str, resource_arn: str, tags: List[Dict[str, str]]
@@ -64,6 +189,7 @@ class ShieldBackend(BaseBackend):
     def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
         self.protections: Dict[str, Protection] = dict()
+        self.subscription: Optional[Subscription] = None
         self.tagger = TaggingService()
 
     def validate_resource_arn(self, resource_arn: str) -> None:
@@ -197,5 +323,14 @@ class ShieldBackend(BaseBackend):
     def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
         self.tagger.untag_resource_using_names(resource_arn, tag_keys)
 
+    def create_subscription(self) -> None:
+        self.subscription = Subscription(account_id=self.account_id)
+        return
+
+    def describe_subscription(self) -> Subscription:
+        if self.subscription is None:
+            raise SubscriptionNotFoundError()
+        return self.subscription
+
 
 shield_backends = BackendDict(ShieldBackend, "ec2")
diff --git a/moto/shield/responses.py b/moto/shield/responses.py
index 0cf33edc3..8dfb7172f 100644
--- a/moto/shield/responses.py
+++ b/moto/shield/responses.py
@@ -85,3 +85,11 @@ class ShieldResponse(BaseResponse):
             tag_keys=tag_keys,
         )
         return "{}"
+
+    def create_subscription(self) -> str:
+        self.shield_backend.create_subscription()
+        return "{}"
+
+    def describe_subscription(self) -> str:
+        subscription = self.shield_backend.describe_subscription()
+        return json.dumps(dict(Subscription=subscription.to_dict()))


Test patch:
diff --git a/tests/test_appmesh/test_appmesh.py b/tests/test_appmesh/test_appmesh.py
index f0b73f043..ec7f37587 100644
--- a/tests/test_appmesh/test_appmesh.py
+++ b/tests/test_appmesh/test_appmesh.py
@@ -127,28 +127,6 @@ def test_create_list_update_describe_delete_mesh(client):
     assert err["Message"] == "There are no meshes with the name mesh2."
 
 
-@mock_aws
-def test_tag_and_list_tags_for_resource(client):
-    connection = client.create_mesh(
-        meshName="mesh1",
-        spec={
-            "egressFilter": {"type": "DROP_ALL"},
-            "serviceDiscovery": {"ipPreference": "IPv4_ONLY"},
-        },
-        tags=[{"key": "owner", "value": "moto"}],
-    )
-    mesh = connection.get("mesh")
-    arn = mesh["metadata"]["arn"]
-
-    client.tag_resource(
-        resourceArn=arn, tags=[{"key": "organization", "value": "moto"}]
-    )
-    connection = client.list_tags_for_resource(resourceArn=arn)
-    tags = connection["tags"]
-    assert tags[0] == {"key": "owner", "value": "moto"}
-    assert tags[1] == {"key": "organization", "value": "moto"}
-
-
 @mock_aws
 def test_create_describe_list_update_delete_virtual_router(client):
     connection = client.create_mesh(
@@ -1228,3 +1206,89 @@ def test_create_describe_list_update_delete_virtual_node(client):
         err["Message"]
         == f"{GRPC_NODE} is not a virtual node associated with mesh {MESH_NAME}"
     )
+
+
+@mock_aws
+def test_tag_and_list_tags_for_resource(client):
+    # create resources
+    connection = client.create_mesh(
+        meshName=MESH_NAME,
+        spec={
+            "egressFilter": {"type": "DROP_ALL"},
+            "serviceDiscovery": {"ipPreference": "IPv4_ONLY"},
+        },
+        tags=[{"key": "owner", "value": "moto"}],
+    )
+    mesh = connection["mesh"]
+    mesh_owner = mesh["metadata"]["meshOwner"]
+    mesh_arn = mesh["metadata"]["arn"]
+
+    ROUTER_NAME = "mock_router"
+
+    connection = client.create_virtual_router(
+        meshName=MESH_NAME,
+        meshOwner=mesh_owner,
+        virtualRouterName=ROUTER_NAME,
+        spec={"listeners": [{"portMapping": {"port": 80, "protocol": "http"}}]},
+        tags=[{"key": "router_traffic", "value": "http"}],
+    )
+
+    virtual_router = connection.get("virtualRouter")
+    virtual_router_arn = virtual_router["metadata"]["arn"]
+
+    connection = client.create_route(
+        meshOwner=mesh_owner,
+        meshName=MESH_NAME,
+        virtualRouterName=ROUTER_NAME,
+        routeName="mock_http_route",
+        tags=[{"key": "license", "value": "mit"}],
+        spec=http_route_spec,
+    )
+
+    route = connection["route"]
+    route_arn = route["metadata"]["arn"]
+
+    connection = client.create_virtual_node(
+        meshName=MESH_NAME,
+        meshOwner=mesh_owner,
+        spec=http_virtual_node_spec,
+        tags=[{"key": "type", "value": "http"}],
+        virtualNodeName="mock_http_node",
+    )
+    virtual_node = connection["virtualNode"]
+    virtual_node_arn = virtual_node["metadata"]["arn"]
+
+    # add tags and validate
+    client.tag_resource(
+        resourceArn=mesh_arn, tags=[{"key": "organization", "value": "moto"}]
+    )
+    connection = client.list_tags_for_resource(resourceArn=mesh_arn)
+    tags = connection["tags"]
+    assert tags[0] == {"key": "owner", "value": "moto"}
+    assert tags[1] == {"key": "organization", "value": "moto"}
+
+    client.tag_resource(
+        resourceArn=virtual_router_arn,
+        tags=[{"key": "organization", "value": "2moto2furious"}],
+    )
+    connection = client.list_tags_for_resource(resourceArn=virtual_router_arn)
+    tags = connection["tags"]
+    assert tags[0] == {"key": "router_traffic", "value": "http"}
+    assert tags[1] == {"key": "organization", "value": "2moto2furious"}
+
+    client.tag_resource(
+        resourceArn=route_arn, tags=[{"key": "organization", "value": "motyo_drift"}]
+    )
+    connection = client.list_tags_for_resource(resourceArn=route_arn)
+    tags = connection["tags"]
+    assert tags[0] == {"key": "license", "value": "mit"}
+    assert tags[1] == {"key": "organization", "value": "motyo_drift"}
+
+    client.tag_resource(
+        resourceArn=virtual_node_arn,
+        tags=[{"key": "organization", "value": "how_moto_got_its_mojo_back"}],
+    )
+    connection = client.list_tags_for_resource(resourceArn=virtual_node_arn)
+    tags = connection["tags"]
+    assert tags[0] == {"key": "type", "value": "http"}
+    assert tags[1] == {"key": "organization", "value": "how_moto_got_its_mojo_back"}
diff --git a/tests/test_sagemaker/test_sagemaker_notebooks.py b/tests/test_sagemaker/test_sagemaker_notebooks.py
index d24f866cd..779bef0b4 100644
--- a/tests/test_sagemaker/test_sagemaker_notebooks.py
+++ b/tests/test_sagemaker/test_sagemaker_notebooks.py
@@ -39,7 +39,7 @@ def _get_notebook_instance_arn(notebook_name):
 def _get_notebook_instance_lifecycle_arn(lifecycle_name):
     return (
         f"arn:aws:sagemaker:{TEST_REGION_NAME}:{ACCOUNT_ID}"
-        f":notebook-instance-lifecycle-configuration/{lifecycle_name}"
+        f":notebook-instance-lifecycle-config/{lifecycle_name}"
     )
 
 
diff --git a/tests/test_shield/test_shield.py b/tests/test_shield/test_shield.py
index 4838e4546..482e6f900 100644
--- a/tests/test_shield/test_shield.py
+++ b/tests/test_shield/test_shield.py
@@ -370,3 +370,52 @@ def test_untag_resource():
     resp = client.list_tags_for_resource(ResourceARN=protection_arn)
     assert len(resp["Tags"]) == 1
     assert "key2" == resp["Tags"][0]["Key"]
+
+
+@mock_aws
+def test_create_and_describe_subscription():
+    client = boto3.client("shield", region_name="eu-west-1")
+    client.create_subscription()
+    connection = client.describe_subscription()
+    subscription = connection["Subscription"]
+    assert subscription["AutoRenew"] == "ENABLED"
+    assert subscription["Limits"][0]["Type"] == "MitigationCapacityUnits"
+    assert subscription["Limits"][0]["Max"] == 10000
+    assert subscription["ProactiveEngagementStatus"] == "ENABLED"
+    assert (
+        subscription["SubscriptionLimits"]["ProtectionLimits"][
+            "ProtectedResourceTypeLimits"
+        ][0]["Type"]
+        == "ELASTIC_IP_ADDRESS"
+    )
+    assert (
+        subscription["SubscriptionLimits"]["ProtectionLimits"][
+            "ProtectedResourceTypeLimits"
+        ][0]["Max"]
+        == 100
+    )
+    assert (
+        subscription["SubscriptionLimits"]["ProtectionLimits"][
+            "ProtectedResourceTypeLimits"
+        ][1]["Type"]
+        == "APPLICATION_LOAD_BALANCER"
+    )
+    assert (
+        subscription["SubscriptionLimits"]["ProtectionLimits"][
+            "ProtectedResourceTypeLimits"
+        ][1]["Max"]
+        == 50
+    )
+    assert (
+        subscription["SubscriptionLimits"]["ProtectionGroupLimits"][
+            "MaxProtectionGroups"
+        ]
+        == 20
+    )
+    assert (
+        subscription["SubscriptionLimits"]["ProtectionGroupLimits"][
+            "PatternTypeLimits"
+        ]["ArbitraryPatternLimits"]["MaxMembers"]
+        == 100
+    )
+    assert subscription["TimeCommitmentInSeconds"] == 31536000