diff --git a/moto/greengrass/exceptions.py b/moto/greengrass/exceptions.py index 8c639f8c8..271fa2130 100644 --- a/moto/greengrass/exceptions.py +++ b/moto/greengrass/exceptions.py @@ -27,3 +27,15 @@ class InvalidInputException(GreengrassClientError): def __init__(self, msg): self.code = 400 super().__init__("InvalidInputException", msg) + + +class MissingCoreException(GreengrassClientError): + def __init__(self, msg): + self.code = 400 + super().__init__("MissingCoreException", msg) + + +class ResourceNotFoundException(GreengrassClientError): + def __init__(self, msg): + self.code = 404 + super().__init__("ResourceNotFoundException", msg) diff --git a/moto/greengrass/models.py b/moto/greengrass/models.py index 9a395c375..788cc850d 100644 --- a/moto/greengrass/models.py +++ b/moto/greengrass/models.py @@ -1,3 +1,4 @@ +import json import uuid from collections import OrderedDict from datetime import datetime @@ -10,6 +11,8 @@ from .exceptions import ( IdNotFoundException, InvalidInputException, InvalidContainerDefinitionException, + MissingCoreException, + ResourceNotFoundException, VersionNotFoundException, ) @@ -361,6 +364,31 @@ class FakeGroupVersion(BaseModel): return obj +class FakeDeployment(BaseModel): + def __init__(self, region_name, group_id, group_arn, deployment_type): + self.region_name = region_name + self.id = str(uuid.uuid4()) + self.group_id = group_id + self.group_arn = group_arn + self.created_at_datetime = datetime.utcnow() + self.update_at_datetime = datetime.utcnow() + self.deployment_status = "InProgress" + self.deployment_type = deployment_type + self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:/greengrass/groups/{self.group_id}/deployments/{self.id}" + + def to_dict(self, include_detail=False): + obj = {"DeploymentId": self.id, "DeploymentArn": self.arn} + + if include_detail: + obj["CreatedAt"] = iso_8601_datetime_with_milliseconds( + self.created_at_datetime + ) + obj["DeploymentType"] = self.deployment_type + obj["GroupArn"] = self.group_arn + + return obj + + class FakeAssociatedRole(BaseModel): def __init__(self, role_arn): self.role_arn = role_arn @@ -375,6 +403,20 @@ class FakeAssociatedRole(BaseModel): return obj +class FakeDeploymentStatus(BaseModel): + def __init__(self, deployment_type, updated_at, deployment_status="InProgress"): + self.deployment_type = deployment_type + self.update_at_datetime = updated_at + self.deployment_status = deployment_status + + def to_dict(self): + return { + "DeploymentStatus": self.deployment_status, + "DeploymentType": self.deployment_type, + "UpdatedAt": iso_8601_datetime_with_milliseconds(self.update_at_datetime), + } + + class GreengrassBackend(BaseBackend): def __init__(self, region_name, account_id): super().__init__(region_name, account_id) @@ -1081,6 +1123,109 @@ class GreengrassBackend(BaseBackend): return self.group_versions[group_id][group_version_id] + def create_deployment( + self, group_id, group_version_id, deployment_type, deployment_id=None + ): + + deployment_types = ( + "NewDeployment", + "Redeployment", + "ResetDeployment", + "ForceResetDeployment", + ) + if deployment_type not in deployment_types: + raise InvalidInputException( + f"That deployment type is not valid. Please specify one of the following types: {{{','.join(deployment_types)}}}." + ) + if deployment_type == "Redeployment": + if deployment_id is None: + raise InvalidInputException( + "Your request is missing the following required parameter(s): {DeploymentId}." + ) + if deployment_id not in self.deployments: + raise InvalidInputException( + f"Deployment ID '{deployment_id}' is invalid." + ) + + if group_id not in self.groups: + raise ResourceNotFoundException("That group definition does not exist.") + + if group_version_id not in self.group_versions[group_id]: + raise ResourceNotFoundException( + f"Version {group_version_id} of Group Definition {group_id} does not exist." + ) + + if ( + self.group_versions[group_id][group_version_id].core_definition_version_arn + is None + ): + + err = { + "ErrorDetails": [ + { + "DetailedErrorCode": "GG-303", + "DetailedErrorMessage": "You need a Greengrass Core in this Group before you can deploy.", + } + ] + } + + raise MissingCoreException(json.dumps(err)) + group_version_arn = self.group_versions[group_id][group_version_id].arn + deployment = FakeDeployment( + self.region_name, group_id, group_version_arn, deployment_type + ) + self.deployments[deployment.id] = deployment + return deployment + + def list_deployments(self, group_id): + + # ListDeployments API does not check specified group is exists + return [ + deployment + for deployment in self.deployments.values() + if deployment.group_id == group_id + ] + + def get_deployment_status(self, group_id, deployment_id): + + if deployment_id not in self.deployments: + raise InvalidInputException(f"Deployment '{deployment_id}' does not exist.") + + deployment = self.deployments[deployment_id] + + if deployment.group_id != group_id: + raise InvalidInputException(f"Deployment '{deployment_id}' does not exist.") + + return FakeDeploymentStatus( + deployment.deployment_type, + deployment.update_at_datetime, + deployment.deployment_status, + ) + + def reset_deployments(self, group_id, force=False): + + if group_id not in self.groups: + raise ResourceNotFoundException("That Group Definition does not exist.") + + deployment_type = "ForceResetDeployment" + if not force: + deployments = list(self.deployments.values()) + reset_error_msg = ( + f"Group id: {group_id} has not been deployed or has already been reset." + ) + if not deployments: + raise ResourceNotFoundException(reset_error_msg) + if deployments[-1].deployment_type not in ["NewDeployment", "Redeployment"]: + raise ResourceNotFoundException(reset_error_msg) + deployment_type = "ResetDeployment" + + group = self.groups[group_id] + deployment = FakeDeployment( + self.region_name, group_id, group.arn, deployment_type + ) + self.deployments[deployment.id] = deployment + return deployment + def associate_role_to_group(self, group_id, role_arn): # I don't know why, AssociateRoleToGroup does not check specified group is exists diff --git a/moto/greengrass/responses.py b/moto/greengrass/responses.py index 1601d9be4..6c2c1d808 100644 --- a/moto/greengrass/responses.py +++ b/moto/greengrass/responses.py @@ -680,6 +680,76 @@ class GreengrassResponse(BaseResponse): ) return 200, {"status": 200}, json.dumps(res.to_dict(include_detail=True)) + def deployments(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == "POST": + return self.create_deployment() + + if self.method == "GET": + return self.list_deployments() + + def create_deployment(self): + + group_id = self.path.split("/")[-2] + group_version_id = self._get_param("GroupVersionId") + deployment_type = self._get_param("DeploymentType") + deployment_id = self._get_param("DeploymentId") + + res = self.greengrass_backend.create_deployment( + group_id=group_id, + group_version_id=group_version_id, + deployment_type=deployment_type, + deployment_id=deployment_id, + ) + return 200, {"status": 200}, json.dumps(res.to_dict()) + + def list_deployments(self): + group_id = self.path.split("/")[-2] + res = self.greengrass_backend.list_deployments(group_id=group_id) + + deployments = ( + [] + if len(res) == 0 + else [deployment.to_dict(include_detail=True) for deployment in res] + ) + + return ( + 200, + {"status": 200}, + json.dumps({"Deployments": deployments}), + ) + + def deployment_satus(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == "GET": + return self.get_deployment_status() + + def get_deployment_status(self): + group_id = self.path.split("/")[-4] + deployment_id = self.path.split("/")[-2] + + res = self.greengrass_backend.get_deployment_status( + group_id=group_id, + deployment_id=deployment_id, + ) + return 200, {"status": 200}, json.dumps(res.to_dict()) + + def deployments_reset(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == "POST": + return self.reset_deployments() + + def reset_deployments(self): + group_id = self.path.split("/")[-3] + + res = self.greengrass_backend.reset_deployments( + group_id=group_id, + ) + return 200, {"status": 200}, json.dumps(res.to_dict()) + def role(self, request, full_url, headers): self.setup_class(request, full_url, headers) diff --git a/moto/greengrass/urls.py b/moto/greengrass/urls.py index 653fc752e..4ca910838 100644 --- a/moto/greengrass/urls.py +++ b/moto/greengrass/urls.py @@ -4,10 +4,8 @@ url_bases = [ r"https?://greengrass\.(.+)\.amazonaws.com", ] - response = GreengrassResponse() - url_paths = { "{0}/greengrass/definition/cores$": response.core_definitions, "{0}/greengrass/definition/cores/(?P[^/]+)/?$": response.core_definition, @@ -33,5 +31,8 @@ url_paths = { "{0}/greengrass/groups/(?P[^/]+)/?$": response.group, "{0}/greengrass/groups/(?P[^/]+)/role$": response.role, "{0}/greengrass/groups/(?P[^/]+)/versions$": response.group_versions, + "{0}/greengrass/groups/(?P[^/]+)/deployments$": response.deployments, + "{0}/greengrass/groups/(?P[^/]+)/deployments/\\$reset$": response.deployments_reset, + "{0}/greengrass/groups/(?P[^/]+)/deployments/(?P[^/]+)/status$": response.deployment_satus, "{0}/greengrass/groups/(?P[^/]+)/versions/(?P[^/]+)/?$": response.group_version, } diff --git a/tests/test_greengrass/test_greengrass_deployment.py b/tests/test_greengrass/test_greengrass_deployment.py new file mode 100644 index 000000000..95dbeb264 --- /dev/null +++ b/tests/test_greengrass/test_greengrass_deployment.py @@ -0,0 +1,496 @@ +import json + +import boto3 +from botocore.client import ClientError +import freezegun +import pytest + +from moto import mock_greengrass +from moto.core import get_account_id +from moto.settings import TEST_SERVER_MODE + +ACCOUNT_ID = get_account_id() + + +@mock_greengrass +def test_create_deployment(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + + res = client.create_deployment( + GroupId=group_id, GroupVersionId=latest_grp_ver, DeploymentType="NewDeployment" + ) + res.should.have.key("DeploymentArn") + res.should.have.key("DeploymentId") + res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + + +@mock_greengrass +def test_re_deployment_with_no_deployment_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + + with pytest.raises(ClientError) as ex: + client.create_deployment( + GroupId=group_id, + GroupVersionId=latest_grp_ver, + DeploymentType="Redeployment", + ) + ex.value.response["Error"]["Message"].should.equal( + "Your request is missing the following required parameter(s): {DeploymentId}." + ) + ex.value.response["Error"]["Code"].should.equal("InvalidInputException") + + +@mock_greengrass +def test_re_deployment_with_invalid_deployment_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + + deployment_id = "7b0bdeae-54c7-47cf-9f93-561e672efd9c" + with pytest.raises(ClientError) as ex: + client.create_deployment( + GroupId=group_id, + GroupVersionId=latest_grp_ver, + DeploymentType="Redeployment", + DeploymentId=deployment_id, + ) + ex.value.response["Error"]["Message"].should.equal( + f"Deployment ID '{deployment_id}' is invalid." + ) + ex.value.response["Error"]["Code"].should.equal("InvalidInputException") + + +@mock_greengrass +def test_create_deployment_with_no_core_group(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_group_res = client.create_group(Name="TestGroup") + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + + with pytest.raises(ClientError) as ex: + client.create_deployment( + GroupId=group_id, + GroupVersionId=latest_grp_ver, + DeploymentType="NewDeployment", + ) + + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Code"].should.equal("MissingCoreException") + err = json.loads(ex.value.response["Error"]["Message"]) + err_details = err["ErrorDetails"] + + err_details[0].should.have.key("DetailedErrorCode").equals("GG-303") + err_details[0].should.have.key("DetailedErrorMessage").equals( + "You need a Greengrass Core in this Group before you can deploy." + ) + + +@mock_greengrass +def test_create_deployment_with_invalid_group_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + with pytest.raises(ClientError) as ex: + client.create_deployment( + GroupId="7b0bdeae-54c7-47cf-9f93-561e672efd9c", + GroupVersionId="dfd06e54-6531-4a9b-9505-3c1036b6906a", + DeploymentType="NewDeployment", + ) + + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404) + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.value.response["Error"]["Message"].should.equal( + "That group definition does not exist." + ) + + +@mock_greengrass +def test_create_deployment_with_invalid_group_version_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + group_version_id = "dfd06e54-6531-4a9b-9505-3c1036b6906a" + + with pytest.raises(ClientError) as ex: + client.create_deployment( + GroupId=group_id, + GroupVersionId=group_version_id, + DeploymentType="NewDeployment", + ) + + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404) + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.value.response["Error"]["Message"].should.equal( + f"Version {group_version_id} of Group Definition {group_id} does not exist." + ) + + +@mock_greengrass +def test_create_deployment_with_invalid_deployment_type(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_group_res = client.create_group(Name="TestGroup") + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + + with pytest.raises(ClientError) as ex: + client.create_deployment( + GroupId=group_id, + GroupVersionId=latest_grp_ver, + DeploymentType="InvalidDeploymentType", + ) + + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Code"].should.equal("InvalidInputException") + ex.value.response["Error"]["Message"].should.equal( + "That deployment type is not valid. Please specify one of the following types: {NewDeployment,Redeployment,ResetDeployment,ForceResetDeployment}." + ) + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_list_deployments(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + latest_grp_ver_arn = create_group_res["LatestVersionArn"] + deployment_type = "NewDeployment" + client.create_deployment( + GroupId=group_id, GroupVersionId=latest_grp_ver, DeploymentType=deployment_type + ) + + res = client.list_deployments(GroupId=group_id) + res.should.have.key("Deployments") + deployments = res["Deployments"][0] + + deployments.should.have.key("CreatedAt") + deployments.should.have.key("DeploymentArn") + deployments.should.have.key("DeploymentId") + deployments.should.have.key("DeploymentType").equals(deployment_type) + deployments.should.have.key("GroupArn").equals(latest_grp_ver_arn) + + if not TEST_SERVER_MODE: + deployments.should.have.key("CreatedAt").equal("2022-06-01T12:00:00.000Z") + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_get_deployment_status(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + deployment_type = "NewDeployment" + create_deployment_res = client.create_deployment( + GroupId=group_id, GroupVersionId=latest_grp_ver, DeploymentType=deployment_type + ) + + deployment_id = create_deployment_res["DeploymentId"] + res = client.get_deployment_status(GroupId=group_id, DeploymentId=deployment_id) + + res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + res.should.have.key("DeploymentStatus").equal("InProgress") + res.should.have.key("DeploymentType").equal(deployment_type) + res.should.have.key("UpdatedAt") + + if not TEST_SERVER_MODE: + res.should.have.key("UpdatedAt").equal("2022-06-01T12:00:00.000Z") + + +@mock_greengrass +def test_get_deployment_status_with_invalid_deployment_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_group_res = client.create_group(Name="TestGroup") + group_id = create_group_res["Id"] + + deployment_id = "7b0bdeae-54c7-47cf-9f93-561e672efd9c" + + with pytest.raises(ClientError) as ex: + client.get_deployment_status(GroupId=group_id, DeploymentId=deployment_id) + ex.value.response["Error"]["Message"].should.equal( + f"Deployment '{deployment_id}' does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("InvalidInputException") + + +@mock_greengrass +def test_get_deployment_status_with_invalid_group_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + deployment_type = "NewDeployment" + create_deployment_res = client.create_deployment( + GroupId=group_id, GroupVersionId=latest_grp_ver, DeploymentType=deployment_type + ) + + deployment_id = create_deployment_res["DeploymentId"] + + with pytest.raises(ClientError) as ex: + client.get_deployment_status( + GroupId="7b0bdeae-54c7-47cf-9f93-561e672efd9c", DeploymentId=deployment_id + ) + ex.value.response["Error"]["Message"].should.equal( + f"Deployment '{deployment_id}' does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("InvalidInputException") + + +@pytest.mark.skipif( + TEST_SERVER_MODE, + reason="Can't handle path that contains $ in server mode.So ResetGroup api can't use in server mode", +) +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_reset_deployments(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + group_arn = create_group_res["Arn"] + latest_grp_ver = create_group_res["LatestVersion"] + client.create_deployment( + GroupId=group_id, GroupVersionId=latest_grp_ver, DeploymentType="NewDeployment" + ) + + reset_res = client.reset_deployments(GroupId=group_id) + + reset_res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + reset_res.should.have.key("DeploymentId") + reset_res.should.have.key("DeploymentArn") + + list_res = client.list_deployments(GroupId=group_id) + reset_deployment = list_res["Deployments"][1] + reset_deployment.should.have.key("CreatedAt") + reset_deployment.should.have.key("DeploymentArn") + reset_deployment.should.have.key("DeploymentId") + reset_deployment.should.have.key("DeploymentType").should.equal("ResetDeployment") + reset_deployment.should.have.key("GroupArn").should.equal(group_arn) + + if not TEST_SERVER_MODE: + reset_deployment["CreatedAt"].should.equal("2022-06-01T12:00:00.000Z") + + +@pytest.mark.skipif( + TEST_SERVER_MODE, + reason="Can't handle path that contains $ in server mode.So ResetGroup api can't use in server mode", +) +@mock_greengrass +def test_reset_deployments_with_invalid_group_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + + with pytest.raises(ClientError) as ex: + client.reset_deployments(GroupId="7b0bdeae-54c7-47cf-9f93-561e672efd9c") + ex.value.response["Error"]["Message"].should.equal( + "That Group Definition does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + + +@pytest.mark.skipif( + TEST_SERVER_MODE, + reason="Can't handle path that contains $ in server mode.So ResetGroup api can't use in server mode", +) +@mock_greengrass +def test_reset_deployments_with_never_deployed_group(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + + with pytest.raises(ClientError) as ex: + client.reset_deployments(GroupId=group_id) + ex.value.response["Error"]["Message"].should.equal( + f"Group id: {group_id} has not been deployed or has already been reset." + ) + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + + +@pytest.mark.skipif( + TEST_SERVER_MODE, + reason="Can't handle path that contains $ in server mode.So ResetGroup api can't use in server mode", +) +@mock_greengrass +def test_reset_deployments_with_already_reset_group(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + cores = [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + + create_core_res = client.create_core_definition( + InitialVersion={"Cores": cores}, Name="TestCore" + ) + core_def_ver_arn = create_core_res["LatestVersionArn"] + create_group_res = client.create_group( + Name="TestGroup", InitialVersion={"CoreDefinitionVersionArn": core_def_ver_arn} + ) + group_id = create_group_res["Id"] + latest_grp_ver = create_group_res["LatestVersion"] + + client.create_deployment( + GroupId=group_id, GroupVersionId=latest_grp_ver, DeploymentType="NewDeployment" + ) + client.reset_deployments(GroupId=group_id) + + with pytest.raises(ClientError) as ex: + client.reset_deployments(GroupId=group_id) + ex.value.response["Error"]["Message"].should.equal( + f"Group id: {group_id} has not been deployed or has already been reset." + ) + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")