Service: ResilienceHub (#7456)

This commit is contained in:
Bert Blommers 2024-03-11 08:58:24 +00:00 committed by GitHub
parent bfe1c12823
commit 3ef0f94fd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 870 additions and 5 deletions

View File

@ -6216,6 +6216,66 @@
- [ ] update_stream_processor - [ ] update_stream_processor
</details> </details>
## resiliencehub
<details>
<summary>18% implemented</summary>
- [ ] add_draft_app_version_resource_mappings
- [ ] batch_update_recommendation_status
- [X] create_app
- [ ] create_app_version_app_component
- [ ] create_app_version_resource
- [ ] create_recommendation_template
- [X] create_resiliency_policy
- [ ] delete_app
- [ ] delete_app_assessment
- [ ] delete_app_input_source
- [ ] delete_app_version_app_component
- [ ] delete_app_version_resource
- [ ] delete_recommendation_template
- [ ] delete_resiliency_policy
- [X] describe_app
- [ ] describe_app_assessment
- [ ] describe_app_version
- [ ] describe_app_version_app_component
- [ ] describe_app_version_resource
- [ ] describe_app_version_resources_resolution_status
- [ ] describe_app_version_template
- [ ] describe_draft_app_version_resources_import_status
- [X] describe_resiliency_policy
- [ ] import_resources_to_draft_app_version
- [ ] list_alarm_recommendations
- [ ] list_app_assessment_compliance_drifts
- [X] list_app_assessments
- [ ] list_app_component_compliances
- [ ] list_app_component_recommendations
- [ ] list_app_input_sources
- [ ] list_app_version_app_components
- [ ] list_app_version_resource_mappings
- [ ] list_app_version_resources
- [ ] list_app_versions
- [X] list_apps
- [ ] list_recommendation_templates
- [X] list_resiliency_policies
- [ ] list_sop_recommendations
- [ ] list_suggested_resiliency_policies
- [X] list_tags_for_resource
- [ ] list_test_recommendations
- [ ] list_unsupported_app_version_resources
- [ ] publish_app_version
- [ ] put_draft_app_version_template
- [ ] remove_draft_app_version_resource_mappings
- [ ] resolve_app_version_resources
- [ ] start_app_assessment
- [X] tag_resource
- [X] untag_resource
- [ ] update_app
- [ ] update_app_version
- [ ] update_app_version_app_component
- [ ] update_app_version_resource
- [ ] update_resiliency_policy
</details>
## resource-groups ## resource-groups
<details> <details>
<summary>61% implemented</summary> <summary>61% implemented</summary>
@ -8159,7 +8219,6 @@
- rbin - rbin
- redshift-serverless - redshift-serverless
- repostspace - repostspace
- resiliencehub
- resource-explorer-2 - resource-explorer-2
- rolesanywhere - rolesanywhere
- route53-recovery-cluster - route53-recovery-cluster

View File

@ -0,0 +1,83 @@
.. _implementedservice_resiliencehub:
.. |start-h3| raw:: html
<h3>
.. |end-h3| raw:: html
</h3>
=============
resiliencehub
=============
|start-h3| Implemented features for this service |end-h3|
- [ ] add_draft_app_version_resource_mappings
- [ ] batch_update_recommendation_status
- [X] create_app
The ClientToken-parameter is not yet implemented
- [ ] create_app_version_app_component
- [ ] create_app_version_resource
- [ ] create_recommendation_template
- [X] create_resiliency_policy
The ClientToken-parameter is not yet implemented
- [ ] delete_app
- [ ] delete_app_assessment
- [ ] delete_app_input_source
- [ ] delete_app_version_app_component
- [ ] delete_app_version_resource
- [ ] delete_recommendation_template
- [ ] delete_resiliency_policy
- [X] describe_app
- [ ] describe_app_assessment
- [ ] describe_app_version
- [ ] describe_app_version_app_component
- [ ] describe_app_version_resource
- [ ] describe_app_version_resources_resolution_status
- [ ] describe_app_version_template
- [ ] describe_draft_app_version_resources_import_status
- [X] describe_resiliency_policy
- [ ] import_resources_to_draft_app_version
- [ ] list_alarm_recommendations
- [ ] list_app_assessment_compliance_drifts
- [X] list_app_assessments
- [ ] list_app_component_compliances
- [ ] list_app_component_recommendations
- [ ] list_app_input_sources
- [ ] list_app_version_app_components
- [ ] list_app_version_resource_mappings
- [ ] list_app_version_resources
- [ ] list_app_versions
- [X] list_apps
The FromAssessmentTime/ToAssessmentTime-parameters are not yet implemented
- [ ] list_recommendation_templates
- [X] list_resiliency_policies
- [ ] list_sop_recommendations
- [ ] list_suggested_resiliency_policies
- [X] list_tags_for_resource
- [ ] list_test_recommendations
- [ ] list_unsupported_app_version_resources
- [ ] publish_app_version
- [ ] put_draft_app_version_template
- [ ] remove_draft_app_version_resource_mappings
- [ ] resolve_app_version_resources
- [ ] start_app_assessment
- [X] tag_resource
- [X] untag_resource
- [ ] update_app
- [ ] update_app_version
- [ ] update_app_version_app_component
- [ ] update_app_version_resource
- [ ] update_resiliency_policy

View File

@ -131,6 +131,7 @@ backend_url_patterns = [
("redshift", re.compile("https?://redshift\\.(.+)\\.amazonaws\\.com")), ("redshift", re.compile("https?://redshift\\.(.+)\\.amazonaws\\.com")),
("redshiftdata", re.compile("https?://redshift-data\\.(.+)\\.amazonaws\\.com")), ("redshiftdata", re.compile("https?://redshift-data\\.(.+)\\.amazonaws\\.com")),
("rekognition", re.compile("https?://rekognition\\.(.+)\\.amazonaws\\.com")), ("rekognition", re.compile("https?://rekognition\\.(.+)\\.amazonaws\\.com")),
("resiliencehub", re.compile("https?://resiliencehub\\.(.+)\\.amazonaws\\.com")),
( (
"resourcegroups", "resourcegroups",
re.compile("https?://resource-groups(-fips)?\\.(.+)\\.amazonaws.com"), re.compile("https?://resource-groups(-fips)?\\.(.+)\\.amazonaws.com"),
@ -138,14 +139,11 @@ backend_url_patterns = [
("resourcegroupstaggingapi", re.compile("https?://tagging\\.(.+)\\.amazonaws.com")), ("resourcegroupstaggingapi", re.compile("https?://tagging\\.(.+)\\.amazonaws.com")),
("robomaker", re.compile("https?://robomaker\\.(.+)\\.amazonaws\\.com")), ("robomaker", re.compile("https?://robomaker\\.(.+)\\.amazonaws\\.com")),
("route53", re.compile("https?://route53(\\..+)?\\.amazonaws.com")), ("route53", re.compile("https?://route53(\\..+)?\\.amazonaws.com")),
("route53domains", re.compile("https?://route53domains\\.(.+)\\.amazonaws\\.com")),
( (
"route53resolver", "route53resolver",
re.compile("https?://route53resolver\\.(.+)\\.amazonaws\\.com"), re.compile("https?://route53resolver\\.(.+)\\.amazonaws\\.com"),
), ),
(
"route53domains",
re.compile("https?://route53domains\\.(.+)\\.amazonaws\\.com"),
),
("s3", re.compile("https?://s3(?!-control)(.*)\\.amazonaws.com")), ("s3", re.compile("https?://s3(?!-control)(.*)\\.amazonaws.com")),
( (
"s3", "s3",

View File

@ -104,6 +104,7 @@ if TYPE_CHECKING:
from moto.redshift.models import RedshiftBackend from moto.redshift.models import RedshiftBackend
from moto.redshiftdata.models import RedshiftDataAPIServiceBackend from moto.redshiftdata.models import RedshiftDataAPIServiceBackend
from moto.rekognition.models import RekognitionBackend from moto.rekognition.models import RekognitionBackend
from moto.resiliencehub.models import ResilienceHubBackend
from moto.resourcegroups.models import ResourceGroupsBackend from moto.resourcegroups.models import ResourceGroupsBackend
from moto.resourcegroupstaggingapi.models import ResourceGroupsTaggingAPIBackend from moto.resourcegroupstaggingapi.models import ResourceGroupsTaggingAPIBackend
from moto.robomaker.models import RoboMakerBackend from moto.robomaker.models import RoboMakerBackend
@ -262,6 +263,7 @@ SERVICE_NAMES = Union[
"Literal['redshift']", "Literal['redshift']",
"Literal['redshift-data']", "Literal['redshift-data']",
"Literal['rekognition']", "Literal['rekognition']",
"Literal['resiliencehub']",
"Literal['resource-groups']", "Literal['resource-groups']",
"Literal['resourcegroupstaggingapi']", "Literal['resourcegroupstaggingapi']",
"Literal['robomaker']", "Literal['robomaker']",
@ -498,6 +500,8 @@ def get_backend(name: "Literal['redshift-data']") -> "BackendDict[RedshiftDataAP
@overload @overload
def get_backend(name: "Literal['rekognition']") -> "BackendDict[RekognitionBackend]": ... def get_backend(name: "Literal['rekognition']") -> "BackendDict[RekognitionBackend]": ...
@overload @overload
def get_backend(name: "Literal['resiliencehub']") -> "BackendDict[ResilienceHubBackend]": ...
@overload
def get_backend(name: "Literal['resource-groups']") -> "BackendDict[ResourceGroupsBackend]": ... def get_backend(name: "Literal['resource-groups']") -> "BackendDict[ResourceGroupsBackend]": ...
@overload @overload
def get_backend(name: "Literal['resourcegroupstaggingapi']") -> "BackendDict[ResourceGroupsTaggingAPIBackend]": ... def get_backend(name: "Literal['resourcegroupstaggingapi']") -> "BackendDict[ResourceGroupsTaggingAPIBackend]": ...

View File

@ -0,0 +1 @@
from .models import resiliencehub_backends # noqa: F401

View File

@ -0,0 +1,22 @@
"""Exceptions raised by the resiliencehub service."""
from moto.core.exceptions import JsonRESTError
class ResourceNotFound(JsonRESTError):
def __init__(self, msg: str):
super().__init__("ResourceNotFoundException", msg)
class AppNotFound(ResourceNotFound):
def __init__(self, arn: str):
super().__init__(f"App not found for appArn {arn}")
class ResiliencyPolicyNotFound(ResourceNotFound):
def __init__(self, arn: str):
super().__init__(f"ResiliencyPolicy {arn} not found")
class ValidationException(JsonRESTError):
def __init__(self, msg: str):
super().__init__("ValidationException", msg)

View File

@ -0,0 +1,212 @@
from typing import Any, Dict, List
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
from .exceptions import AppNotFound, ResiliencyPolicyNotFound
PAGINATION_MODEL = {
"list_apps": {
"input_token": "next_token",
"limit_key": "max_results",
"limit_default": 100,
"unique_attribute": "arn",
},
"list_resiliency_policies": {
"input_token": "next_token",
"limit_key": "max_results",
"limit_default": 100,
"unique_attribute": "arn",
},
}
class App(BaseModel):
def __init__(
self,
backend: "ResilienceHubBackend",
assessment_schedule: str,
description: str,
event_subscriptions: List[Dict[str, Any]],
name: str,
permission_model: Dict[str, Any],
policy_arn: str,
):
self.backend = backend
self.arn = f"arn:aws:resiliencehub:{backend.region_name}:{backend.account_id}:app/{mock_random.uuid4()}"
self.assessment_schedule = assessment_schedule or "Disabled"
self.compliance_status = "NotAssessed"
self.description = description
self.creation_time = unix_time()
self.event_subscriptions = event_subscriptions
self.name = name
self.permission_model = permission_model
self.policy_arn = policy_arn
self.resilience_score = 0.0
self.status = "Active"
def to_json(self) -> Dict[str, Any]:
resp = {
"appArn": self.arn,
"assessmentSchedule": self.assessment_schedule,
"complianceStatus": self.compliance_status,
"creationTime": self.creation_time,
"name": self.name,
"resilienceScore": self.resilience_score,
"status": self.status,
"tags": self.backend.list_tags_for_resource(self.arn),
}
if self.description is not None:
resp["description"] = self.description
if self.event_subscriptions:
resp["eventSubscriptions"] = self.event_subscriptions
if self.permission_model:
resp["permissionModel"] = self.permission_model
if self.policy_arn:
resp["policyArn"] = self.policy_arn
return resp
class Policy(BaseModel):
def __init__(
self,
backend: "ResilienceHubBackend",
policy: Dict[str, Dict[str, int]],
policy_name: str,
data_location_constraint: str,
policy_description: str,
tier: str,
):
self.arn = f"arn:aws:resiliencehub:{backend.region_name}:{backend.account_id}:resiliency-policy/{mock_random.uuid4()}"
self.backend = backend
self.data_location_constraint = data_location_constraint
self.creation_time = unix_time()
self.policy = policy
self.policy_description = policy_description
self.policy_name = policy_name
self.tier = tier
def to_json(self) -> Dict[str, Any]:
resp = {
"creationTime": self.creation_time,
"policy": self.policy,
"policyArn": self.arn,
"policyName": self.policy_name,
"tags": self.backend.list_tags_for_resource(self.arn),
"tier": self.tier,
}
if self.data_location_constraint:
resp["dataLocationConstraint"] = self.data_location_constraint
if self.policy_description:
resp["policyDescription"] = self.policy_description
return resp
class ResilienceHubBackend(BaseBackend):
def __init__(self, region_name: str, account_id: str):
super().__init__(region_name, account_id)
self.apps: Dict[str, App] = dict()
self.policies: Dict[str, Policy] = dict()
self.tagger = TaggingService()
def create_app(
self,
assessment_schedule: str,
description: str,
event_subscriptions: List[Dict[str, Any]],
name: str,
permission_model: Dict[str, Any],
policy_arn: str,
tags: Dict[str, str],
) -> App:
"""
The ClientToken-parameter is not yet implemented
"""
app = App(
backend=self,
assessment_schedule=assessment_schedule,
description=description,
event_subscriptions=event_subscriptions,
name=name,
permission_model=permission_model,
policy_arn=policy_arn,
)
self.apps[app.arn] = app
self.tag_resource(app.arn, tags)
return app
def create_resiliency_policy(
self,
data_location_constraint: str,
policy: Dict[str, Any],
policy_description: str,
policy_name: str,
tags: Dict[str, str],
tier: str,
) -> Policy:
"""
The ClientToken-parameter is not yet implemented
"""
pol = Policy(
backend=self,
data_location_constraint=data_location_constraint,
policy=policy,
policy_description=policy_description,
policy_name=policy_name,
tier=tier,
)
self.policies[pol.arn] = pol
self.tag_resource(pol.arn, tags)
return pol
@paginate(PAGINATION_MODEL)
def list_apps(self, app_arn: str, name: str, reverse_order: bool) -> List[App]:
"""
The FromAssessmentTime/ToAssessmentTime-parameters are not yet implemented
"""
if name:
app_summaries = [a for a in self.apps.values() if a.name == name]
elif app_arn:
app_summaries = [self.apps[app_arn]]
else:
app_summaries = list(self.apps.values())
if reverse_order:
app_summaries.reverse()
return app_summaries
def list_app_assessments(self) -> List[str]:
return []
def describe_app(self, app_arn: str) -> App:
if app_arn not in self.apps:
raise AppNotFound(app_arn)
return self.apps[app_arn]
@paginate(pagination_model=PAGINATION_MODEL)
def list_resiliency_policies(self, policy_name: str) -> List[Policy]:
if policy_name:
return [p for p in self.policies.values() if p.policy_name == policy_name]
return list(self.policies.values())
def describe_resiliency_policy(self, policy_arn: str) -> Policy:
if policy_arn not in self.policies:
raise ResiliencyPolicyNotFound(policy_arn)
return self.policies[policy_arn]
def tag_resource(self, resource_arn: str, tags: Dict[str, str]) -> None:
self.tagger.tag_resource(
resource_arn, TaggingService.convert_dict_to_tags_input(tags)
)
def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
self.tagger.untag_resource_using_names(resource_arn, tag_keys)
def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]:
return self.tagger.get_tag_dict_for_resource(resource_arn)
resiliencehub_backends = BackendDict(ResilienceHubBackend, "resiliencehub")

View File

@ -0,0 +1,159 @@
import json
from typing import Any
from urllib.parse import unquote
from moto.core.responses import BaseResponse
from .exceptions import ValidationException
from .models import ResilienceHubBackend, resiliencehub_backends
class ResilienceHubResponse(BaseResponse):
def tags(self, request: Any, full_url: str, headers: Any) -> str: # type: ignore[return]
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self.list_tags_for_resource()
if request.method == "POST":
return self.tag_resource()
if request.method == "DELETE":
return self.untag_resource()
def __init__(self) -> None:
super().__init__(service_name="resiliencehub")
@property
def resiliencehub_backend(self) -> ResilienceHubBackend:
return resiliencehub_backends[self.current_account][self.region]
def create_app(self) -> str:
params = json.loads(self.body)
assessment_schedule = params.get("assessmentSchedule")
description = params.get("description")
event_subscriptions = params.get("eventSubscriptions")
name = params.get("name")
permission_model = params.get("permissionModel")
policy_arn = params.get("policyArn")
tags = params.get("tags")
app = self.resiliencehub_backend.create_app(
assessment_schedule=assessment_schedule,
description=description,
event_subscriptions=event_subscriptions,
name=name,
permission_model=permission_model,
policy_arn=policy_arn,
tags=tags,
)
return json.dumps(dict(app=app.to_json()))
def create_resiliency_policy(self) -> str:
params = json.loads(self.body)
data_location_constraint = params.get("dataLocationConstraint")
policy = params.get("policy")
policy_description = params.get("policyDescription")
policy_name = params.get("policyName")
tags = params.get("tags")
tier = params.get("tier")
required_policy_types = ["Software", "Hardware", "AZ"]
all_policy_types = required_policy_types + ["Region"]
if any((p_type not in all_policy_types for p_type in policy.keys())):
raise ValidationException(
"1 validation error detected: Value at 'policy' failed to satisfy constraint: Map keys must satisfy constraint: [Member must satisfy enum value set: [Software, Hardware, Region, AZ]]"
)
for required_key in required_policy_types:
if required_key not in policy.keys():
raise ValidationException(
f"FailureType {required_key.upper()} does not exist"
)
policy = self.resiliencehub_backend.create_resiliency_policy(
data_location_constraint=data_location_constraint,
policy=policy,
policy_description=policy_description,
policy_name=policy_name,
tags=tags,
tier=tier,
)
return json.dumps(dict(policy=policy.to_json()))
def list_apps(self) -> str:
params = self._get_params()
app_arn = params.get("appArn")
max_results = int(params.get("maxResults", 100))
name = params.get("name")
next_token = params.get("nextToken")
reverse_order = params.get("reverseOrder") == "true"
app_summaries, next_token = self.resiliencehub_backend.list_apps(
app_arn=app_arn,
max_results=max_results,
name=name,
next_token=next_token,
reverse_order=reverse_order,
)
return json.dumps(
dict(
appSummaries=[a.to_json() for a in app_summaries], nextToken=next_token
)
)
def list_app_assessments(self) -> str:
summaries = self.resiliencehub_backend.list_app_assessments()
return json.dumps(dict(assessmentSummaries=summaries))
def describe_app(self) -> str:
params = json.loads(self.body)
app_arn = params.get("appArn")
app = self.resiliencehub_backend.describe_app(
app_arn=app_arn,
)
return json.dumps(dict(app=app.to_json()))
def list_resiliency_policies(self) -> str:
params = self._get_params()
max_results = int(params.get("maxResults", 100))
next_token = params.get("nextToken")
policy_name = params.get("policyName")
(
resiliency_policies,
next_token,
) = self.resiliencehub_backend.list_resiliency_policies(
max_results=max_results,
next_token=next_token,
policy_name=policy_name,
)
policies = [p.to_json() for p in resiliency_policies]
return json.dumps(dict(nextToken=next_token, resiliencyPolicies=policies))
def describe_resiliency_policy(self) -> str:
params = json.loads(self.body)
policy_arn = params.get("policyArn")
policy = self.resiliencehub_backend.describe_resiliency_policy(
policy_arn=policy_arn,
)
return json.dumps(dict(policy=policy.to_json()))
def tag_resource(self) -> str:
params = json.loads(self.body)
resource_arn = unquote(self.parsed_url.path.split("/tags/")[-1])
tags = params.get("tags")
self.resiliencehub_backend.tag_resource(
resource_arn=resource_arn,
tags=tags,
)
return "{}"
def untag_resource(self) -> str:
resource_arn = unquote(self.parsed_url.path.split("/tags/")[-1])
tag_keys = self.querystring.get("tagKeys", [])
self.resiliencehub_backend.untag_resource(
resource_arn=resource_arn,
tag_keys=tag_keys,
)
return "{}"
def list_tags_for_resource(self) -> str:
resource_arn = unquote(self.uri.split("/tags/")[-1])
tags = self.resiliencehub_backend.list_tags_for_resource(
resource_arn=resource_arn,
)
return json.dumps(dict(tags=tags))

View File

@ -0,0 +1,21 @@
"""resiliencehub base URL and path."""
from .responses import ResilienceHubResponse
url_bases = [
r"https?://resiliencehub\.(.+)\.amazonaws\.com",
]
url_paths = {
"{0}/create-app$": ResilienceHubResponse.dispatch,
"{0}/create-resiliency-policy$": ResilienceHubResponse.dispatch,
"{0}/describe-app$": ResilienceHubResponse.dispatch,
"{0}/describe-resiliency-policy$": ResilienceHubResponse.dispatch,
"{0}/list-apps$": ResilienceHubResponse.dispatch,
"{0}/list-app-assessments$": ResilienceHubResponse.dispatch,
"{0}/list-resiliency-policies$": ResilienceHubResponse.dispatch,
"{0}/tags/.+$": ResilienceHubResponse.dispatch,
"{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)$": ResilienceHubResponse.method_dispatch(
ResilienceHubResponse.tags # type: ignore
),
"{0}/.*$": ResilienceHubResponse.dispatch,
}

View File

View File

@ -0,0 +1,250 @@
from uuid import uuid4
import boto3
import pytest
from botocore.exceptions import ClientError
from moto import mock_aws
# 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
valid_resiliency_policy = {
"Software": {"rpoInSecs": 1, "rtoInSecs": 1},
"Hardware": {"rpoInSecs": 2, "rtoInSecs": 2},
"AZ": {"rpoInSecs": 3, "rtoInSecs": 3},
}
@mock_aws
def test_create_app():
client = boto3.client("resiliencehub", region_name="us-east-2")
app = client.create_app(name="myapp")["app"]
assert app["assessmentSchedule"] == "Disabled"
assert app["complianceStatus"] == "NotAssessed"
assert app["creationTime"]
assert app["name"] == "myapp"
assert app["status"] == "Active"
assert app["tags"] == {}
app2 = client.describe_app(appArn=app["appArn"])["app"]
assert app == app2
@mock_aws
def test_create_app_advanced():
event_subs = [
{
"eventType": "ScheduledAssessmentFailure",
"name": "some event",
"snsTopicArn": "some sns arn",
}
]
perm_model = {
"crossAccountRoleArns": ["arn1", "arn2"],
"invokerRoleName": "irn",
"type": "Rolebased",
}
client = boto3.client("resiliencehub", region_name="us-east-2")
app = client.create_app(
name="myapp",
assessmentSchedule="Daily",
description="some desc",
eventSubscriptions=event_subs,
permissionModel=perm_model,
policyArn="some policy arn",
)["app"]
assert app["assessmentSchedule"] == "Daily"
assert app["description"] == "some desc"
assert app["eventSubscriptions"] == event_subs
assert app["permissionModel"] == perm_model
assert app["policyArn"] == "some policy arn"
@mock_aws
def test_describe_unknown_app():
client = boto3.client("resiliencehub", region_name="us-east-1")
app_arn = f"arn:aws:resiliencehub:us-east-1:486285699788:app/{str(uuid4())}"
with pytest.raises(ClientError) as exc:
client.describe_app(appArn=app_arn)
err = exc.value.response["Error"]
assert err["Code"] == "ResourceNotFoundException"
assert err["Message"] == f"App not found for appArn {app_arn}"
@mock_aws
def test_create_resilience_policy():
client = boto3.client("resiliencehub", region_name="us-east-1")
policy = client.create_resiliency_policy(
policy=valid_resiliency_policy, policyName="polname", tier="NonCritical"
)["policy"]
assert policy["creationTime"]
assert policy["policy"] == valid_resiliency_policy
assert policy["policyName"] == "polname"
assert policy["tags"] == {}
assert policy["tier"] == "NonCritical"
policy2 = client.describe_resiliency_policy(policyArn=policy["policyArn"])["policy"]
assert policy == policy2
@mock_aws
def test_create_resilience_policy_advanced():
client = boto3.client("resiliencehub", region_name="us-east-1")
policy = client.create_resiliency_policy(
dataLocationConstraint="AnyLocation",
policy=valid_resiliency_policy,
policyName="polname",
policyDescription="test policy",
tier="NonCritical",
)["policy"]
assert policy["dataLocationConstraint"] == "AnyLocation"
assert policy["policyDescription"] == "test policy"
@mock_aws
def test_create_resilience_policy_missing_types():
client = boto3.client("resiliencehub", region_name="us-east-1")
with pytest.raises(ClientError) as exc:
client.create_resiliency_policy(
policy={"Software": {"rpoInSecs": 1, "rtoInSecs": 1}},
policyName="polname",
tier="NonCritical",
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "FailureType HARDWARE does not exist"
with pytest.raises(ClientError) as exc:
client.create_resiliency_policy(
policy={
"Software": {"rpoInSecs": 1, "rtoInSecs": 1},
"Hardware": {"rpoInSecs": 2, "rtoInSecs": 2},
},
policyName="polname",
tier="NonCritical",
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "FailureType AZ does not exist"
with pytest.raises(ClientError) as exc:
client.create_resiliency_policy(
policy={"Hardware": {"rpoInSecs": 1, "rtoInSecs": 1}},
policyName="polname",
tier="NonCritical",
)
err = exc.value.response["Error"]
err["Message"] == "FailureType SOFTWARE does not exist"
@mock_aws
def test_create_resilience_policy_with_unknown_policy_type():
client = boto3.client("resiliencehub", region_name="us-east-1")
with pytest.raises(ClientError) as exc:
client.create_resiliency_policy(
policy={
"st": {"rpoInSecs": 1, "rtoInSecs": 1},
},
policyName="polname",
tier="NonCritical",
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
"Member must satisfy enum value set: [Software, Hardware, Region, AZ]"
in err["Message"]
)
@mock_aws
def test_list_apps():
client = boto3.client("resiliencehub", region_name="ap-southeast-1")
assert client.list_apps()["appSummaries"] == []
for i in range(5):
arn = client.create_app(name=f"app_{i}")["app"]["appArn"]
app_2 = client.list_apps(name="app_2")["appSummaries"][0]
assert app_2["name"] == "app_2"
app_4 = client.list_apps(appArn=arn)["appSummaries"][0]
assert app_4["name"] == "app_4"
all_apps = client.list_apps()["appSummaries"]
assert len(all_apps) == 5
assert [a["name"] for a in all_apps] == [
"app_0",
"app_1",
"app_2",
"app_3",
"app_4",
]
all_apps = client.list_apps(reverseOrder=True)["appSummaries"]
assert len(all_apps) == 5
assert [a["name"] for a in all_apps] == [
"app_4",
"app_3",
"app_2",
"app_1",
"app_0",
]
page1 = client.list_apps(maxResults=2)
assert len(page1["appSummaries"]) == 2
page2 = client.list_apps(maxResults=2, nextToken=page1["nextToken"])
assert len(page2["appSummaries"]) == 2
full_page = client.list_apps(nextToken=page1["nextToken"])
assert len(full_page["appSummaries"]) == 3
@mock_aws
def test_list_app_assessments():
client = boto3.client("resiliencehub", region_name="ap-southeast-1")
assert client.list_app_assessments()["assessmentSummaries"] == []
@mock_aws
def test_list_resiliency_policies():
client = boto3.client("resiliencehub", region_name="ap-southeast-1")
assert client.list_resiliency_policies()["resiliencyPolicies"] == []
for i in range(5):
client.create_resiliency_policy(
policy=valid_resiliency_policy, policyName=f"policy_{i}", tier="NonCritical"
)["policy"]
assert len(client.list_resiliency_policies()["resiliencyPolicies"]) == 5
policy2 = client.list_resiliency_policies(policyName="policy_2")[
"resiliencyPolicies"
][0]
policy2["policyName"] == "policy_2"
page1 = client.list_resiliency_policies(maxResults=2)
assert len(page1["resiliencyPolicies"]) == 2
page2 = client.list_resiliency_policies(maxResults=2, nextToken=page1["nextToken"])
assert len(page2["resiliencyPolicies"]) == 2
assert page2["nextToken"]
page_full = client.list_resiliency_policies(nextToken=page1["nextToken"])
assert len(page_full["resiliencyPolicies"]) == 3
assert "nextToken" not in page_full
@mock_aws
def test_describe_unknown_resiliency_policy():
client = boto3.client("resiliencehub", region_name="eu-west-1")
with pytest.raises(ClientError) as exc:
client.describe_resiliency_policy(policyArn="unknownarn")
err = exc.value.response["Error"]
assert err["Code"] == "ResourceNotFoundException"
assert err["Message"] == "ResiliencyPolicy unknownarn not found"

View File

@ -0,0 +1,56 @@
import boto3
from moto import mock_aws
from .test_resiliencehub import valid_resiliency_policy
# 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_app_tagging():
client = boto3.client("resiliencehub", region_name="us-east-2")
app = client.create_app(name="myapp", tags={"k": "v"})["app"]
arn = app["appArn"]
assert app["tags"] == {"k": "v"}
assert app == client.describe_app(appArn=arn)["app"]
client.tag_resource(resourceArn=arn, tags={"k2": "v2"})
assert client.list_tags_for_resource(resourceArn=arn)["tags"] == {
"k": "v",
"k2": "v2",
}
client.untag_resource(resourceArn=arn, tagKeys=["k"])
assert client.list_tags_for_resource(resourceArn=arn)["tags"] == {"k2": "v2"}
@mock_aws
def test_policy_tagging():
client = boto3.client("resiliencehub", region_name="us-east-2")
policy = client.create_resiliency_policy(
policy=valid_resiliency_policy,
policyName="polname",
tier="NonCritical",
tags={"k": "v"},
)["policy"]
arn = policy["policyArn"]
assert policy["tags"] == {"k": "v"}
assert policy == client.describe_resiliency_policy(policyArn=arn)["policy"]
client.tag_resource(resourceArn=arn, tags={"k2": "v2"})
assert client.list_tags_for_resource(resourceArn=arn)["tags"] == {
"k": "v",
"k2": "v2",
}
client.untag_resource(resourceArn=arn, tagKeys=["k"])
assert client.list_tags_for_resource(resourceArn=arn)["tags"] == {"k2": "v2"}