EMR and SWF - add arn to response (#3873)

* emr: add ClusterArn to describe_cluster response

* emr: add ClusterArn to list_clusters response

* emr: add ClusterArn to put_auto_scaling_policy response

* emr: add ClusterArn to run_job_flow response

* emr: rename property "cluster_arn" to simply "arn"

* emr: generalize arn for account_id and region

* swf: add arn to list_domains response

* black reformat source code

* fix double import

* swf: require region on Domain object

Co-authored-by: Kevin Neal <Kevin_Neal@intuit.com>
This commit is contained in:
khneal 2021-04-23 07:20:36 -07:00 committed by GitHub
parent c31dffcc92
commit 8b523c3fe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 21 deletions

View File

@ -7,7 +7,7 @@ import warnings
import pytz import pytz
from boto3 import Session from boto3 import Session
from dateutil.parser import parse as dtparse from dateutil.parser import parse as dtparse
from moto.core import BaseBackend, BaseModel from moto.core import ACCOUNT_ID, BaseBackend, BaseModel
from moto.emr.exceptions import EmrError, InvalidRequestException from moto.emr.exceptions import EmrError, InvalidRequestException
from .utils import ( from .utils import (
random_instance_group_id, random_instance_group_id,
@ -272,6 +272,12 @@ class FakeCluster(BaseModel):
) )
self.kerberos_attributes = kerberos_attributes self.kerberos_attributes = kerberos_attributes
@property
def arn(self):
return "arn:aws:elasticmapreduce:{0}:{1}:cluster/{2}".format(
self.emr_backend.region_name, ACCOUNT_ID, self.id
)
@property @property
def instance_groups(self): def instance_groups(self):
return self.emr_backend.get_instance_groups(self.instance_group_ids) return self.emr_backend.get_instance_groups(self.instance_group_ids)

View File

@ -529,13 +529,16 @@ class ElasticMapReduceResponse(BaseResponse):
@generate_boto3_response("PutAutoScalingPolicy") @generate_boto3_response("PutAutoScalingPolicy")
def put_auto_scaling_policy(self): def put_auto_scaling_policy(self):
cluster_id = self._get_param("ClusterId") cluster_id = self._get_param("ClusterId")
cluster = self.backend.get_cluster(cluster_id)
instance_group_id = self._get_param("InstanceGroupId") instance_group_id = self._get_param("InstanceGroupId")
auto_scaling_policy = self._get_param("AutoScalingPolicy") auto_scaling_policy = self._get_param("AutoScalingPolicy")
instance_group = self.backend.put_auto_scaling_policy( instance_group = self.backend.put_auto_scaling_policy(
instance_group_id, auto_scaling_policy instance_group_id, auto_scaling_policy
) )
template = self.response_template(PUT_AUTO_SCALING_POLICY) template = self.response_template(PUT_AUTO_SCALING_POLICY)
return template.render(cluster_id=cluster_id, instance_group=instance_group) return template.render(
cluster_id=cluster_id, cluster=cluster, instance_group=instance_group
)
@generate_boto3_response("RemoveAutoScalingPolicy") @generate_boto3_response("RemoveAutoScalingPolicy")
def remove_auto_scaling_policy(self): def remove_auto_scaling_policy(self):
@ -691,6 +694,7 @@ DESCRIBE_CLUSTER_TEMPLATE = """<DescribeClusterResponse xmlns="http://elasticmap
<TerminationProtected>{{ cluster.termination_protected|lower }}</TerminationProtected> <TerminationProtected>{{ cluster.termination_protected|lower }}</TerminationProtected>
<VisibleToAllUsers>{{ cluster.visible_to_all_users|lower }}</VisibleToAllUsers> <VisibleToAllUsers>{{ cluster.visible_to_all_users|lower }}</VisibleToAllUsers>
<StepConcurrencyLevel>{{ cluster.step_concurrency_level }}</StepConcurrencyLevel> <StepConcurrencyLevel>{{ cluster.step_concurrency_level }}</StepConcurrencyLevel>
<ClusterArn>{{ cluster.arn }}</ClusterArn>
</Cluster> </Cluster>
</DescribeClusterResult> </DescribeClusterResult>
<ResponseMetadata> <ResponseMetadata>
@ -940,6 +944,7 @@ LIST_CLUSTERS_TEMPLATE = """<ListClustersResponse xmlns="http://elasticmapreduce
{% endif %} {% endif %}
</Timeline> </Timeline>
</Status> </Status>
<ClusterArn>{{ cluster.arn }}</ClusterArn>
</member> </member>
{% endfor %} {% endfor %}
</Clusters> </Clusters>
@ -1249,6 +1254,7 @@ REMOVE_TAGS_TEMPLATE = """<RemoveTagsResponse xmlns="http://elasticmapreduce.ama
RUN_JOB_FLOW_TEMPLATE = """<RunJobFlowResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31"> RUN_JOB_FLOW_TEMPLATE = """<RunJobFlowResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
<RunJobFlowResult> <RunJobFlowResult>
<JobFlowId>{{ cluster.id }}</JobFlowId> <JobFlowId>{{ cluster.id }}</JobFlowId>
<ClusterArn>{{ cluster.arn }}</ClusterArn>
</RunJobFlowResult> </RunJobFlowResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>8296d8b8-ed85-11dd-9877-6fad448a8419</RequestId> <RequestId>8296d8b8-ed85-11dd-9877-6fad448a8419</RequestId>
@ -1378,6 +1384,7 @@ PUT_AUTO_SCALING_POLICY = """<PutAutoScalingPolicyResponse xmlns="http://elastic
{% endif %} {% endif %}
</AutoScalingPolicy> </AutoScalingPolicy>
{% endif %} {% endif %}
<ClusterArn>{{ cluster.arn }}</ClusterArn>
</PutAutoScalingPolicyResult> </PutAutoScalingPolicyResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>d47379d9-b505-49af-9335-a68950d82535</RequestId> <RequestId>d47379d9-b505-49af-9335-a68950d82535</RequestId>

View File

@ -112,7 +112,12 @@ class SWFBackend(BaseBackend):
): ):
if self._get_domain(name, ignore_empty=True): if self._get_domain(name, ignore_empty=True):
raise SWFDomainAlreadyExistsFault(name) raise SWFDomainAlreadyExistsFault(name)
domain = Domain(name, workflow_execution_retention_period_in_days, description) domain = Domain(
name,
workflow_execution_retention_period_in_days,
self.region_name,
description,
)
self.domains.append(domain) self.domains.append(domain)
def deprecate_domain(self, name): def deprecate_domain(self, name):

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import defaultdict from collections import defaultdict
from moto.core import BaseModel from moto.core import ACCOUNT_ID, BaseModel
from ..exceptions import ( from ..exceptions import (
SWFUnknownResourceFault, SWFUnknownResourceFault,
SWFWorkflowExecutionAlreadyStartedFault, SWFWorkflowExecutionAlreadyStartedFault,
@ -9,9 +9,10 @@ from ..exceptions import (
class Domain(BaseModel): class Domain(BaseModel):
def __init__(self, name, retention, description=None): def __init__(self, name, retention, region_name, description=None):
self.name = name self.name = name
self.retention = retention self.retention = retention
self.region_name = region_name
self.description = description self.description = description
self.status = "REGISTERED" self.status = "REGISTERED"
self.types = {"activity": defaultdict(dict), "workflow": defaultdict(dict)} self.types = {"activity": defaultdict(dict), "workflow": defaultdict(dict)}
@ -31,6 +32,9 @@ class Domain(BaseModel):
hsh = {"name": self.name, "status": self.status} hsh = {"name": self.name, "status": self.status}
if self.description: if self.description:
hsh["description"] = self.description hsh["description"] = self.description
hsh["arn"] = "arn:aws:swf:{0}:{1}:/domain/{2}".format(
self.region_name, ACCOUNT_ID, self.name
)
return hsh return hsh
def to_full_dict(self): def to_full_dict(self):

View File

@ -13,6 +13,7 @@ from botocore.exceptions import ClientError
import pytest import pytest
from moto import mock_emr from moto import mock_emr
from moto.core import ACCOUNT_ID
run_job_flow_args = dict( run_job_flow_args = dict(
@ -76,7 +77,8 @@ input_instance_groups = [
@mock_emr @mock_emr
def test_describe_cluster(): def test_describe_cluster():
client = boto3.client("emr", region_name="us-east-1") region_name = "us-east-1"
client = boto3.client("emr", region_name=region_name)
args = deepcopy(run_job_flow_args) args = deepcopy(run_job_flow_args)
args["Applications"] = [{"Name": "Spark", "Version": "2.4.2"}] args["Applications"] = [{"Name": "Spark", "Version": "2.4.2"}]
@ -178,6 +180,11 @@ def test_describe_cluster():
cl["TerminationProtected"].should.equal(False) cl["TerminationProtected"].should.equal(False)
cl["VisibleToAllUsers"].should.equal(True) cl["VisibleToAllUsers"].should.equal(True)
cl["ClusterArn"].should.equal(
"arn:aws:elasticmapreduce:{0}:{1}:cluster/{2}".format(
region_name, ACCOUNT_ID, cluster_id
)
)
@mock_emr @mock_emr
@ -384,12 +391,17 @@ def test_list_clusters():
@mock_emr @mock_emr
def test_run_job_flow(): def test_run_job_flow():
client = boto3.client("emr", region_name="us-east-1") region_name = "us-east-1"
client = boto3.client("emr", region_name=region_name)
args = deepcopy(run_job_flow_args) args = deepcopy(run_job_flow_args)
cluster_id = client.run_job_flow(**args)["JobFlowId"] resp = client.run_job_flow(**args)
resp = client.describe_job_flows(JobFlowIds=[cluster_id])["JobFlows"][0] resp["ClusterArn"].startswith(
"arn:aws:elasticmapreduce:{0}:{1}:cluster/".format(region_name, ACCOUNT_ID)
)
job_flow_id = resp["JobFlowId"]
resp = client.describe_job_flows(JobFlowIds=[job_flow_id])["JobFlows"][0]
resp["ExecutionStatusDetail"]["State"].should.equal("WAITING") resp["ExecutionStatusDetail"]["State"].should.equal("WAITING")
resp["JobFlowId"].should.equal(cluster_id) resp["JobFlowId"].should.equal(job_flow_id)
resp["Name"].should.equal(args["Name"]) resp["Name"].should.equal(args["Name"])
resp["Instances"]["MasterInstanceType"].should.equal( resp["Instances"]["MasterInstanceType"].should.equal(
args["Instances"]["MasterInstanceType"] args["Instances"]["MasterInstanceType"]
@ -544,8 +556,9 @@ def test_run_job_flow_with_instance_groups_with_autoscaling():
@mock_emr @mock_emr
def test_put_remove_auto_scaling_policy(): def test_put_remove_auto_scaling_policy():
region_name = "us-east-1"
input_groups = dict((g["Name"], g) for g in input_instance_groups) input_groups = dict((g["Name"], g) for g in input_instance_groups)
client = boto3.client("emr", region_name="us-east-1") client = boto3.client("emr", region_name=region_name)
args = deepcopy(run_job_flow_args) args = deepcopy(run_job_flow_args)
args["Instances"] = {"InstanceGroups": input_instance_groups} args["Instances"] = {"InstanceGroups": input_instance_groups}
cluster_id = client.run_job_flow(**args)["JobFlowId"] cluster_id = client.run_job_flow(**args)["JobFlowId"]
@ -567,6 +580,11 @@ def test_put_remove_auto_scaling_policy():
) )
del resp["AutoScalingPolicy"]["Status"] del resp["AutoScalingPolicy"]["Status"]
resp["AutoScalingPolicy"].should.equal(auto_scaling_policy_with_cluster_id) resp["AutoScalingPolicy"].should.equal(auto_scaling_policy_with_cluster_id)
resp["ClusterArn"].should.equal(
"arn:aws:elasticmapreduce:{0}:{1}:cluster/{2}".format(
region_name, ACCOUNT_ID, cluster_id
)
)
core_instance_group = [ core_instance_group = [
ig ig

View File

@ -1,9 +1,11 @@
from collections import namedtuple from collections import namedtuple
import sure # noqa import sure # noqa
from moto.core import ACCOUNT_ID
from moto.swf.exceptions import SWFUnknownResourceFault from moto.swf.exceptions import SWFUnknownResourceFault
from moto.swf.models import Domain from moto.swf.models import Domain
TEST_REGION = "us-east-1"
# Fake WorkflowExecution for tests purposes # Fake WorkflowExecution for tests purposes
WorkflowExecution = namedtuple( WorkflowExecution = namedtuple(
"WorkflowExecution", ["workflow_id", "run_id", "execution_status", "open"] "WorkflowExecution", ["workflow_id", "run_id", "execution_status", "open"]
@ -11,15 +13,21 @@ WorkflowExecution = namedtuple(
def test_domain_short_dict_representation(): def test_domain_short_dict_representation():
domain = Domain("foo", "52") domain = Domain("foo", "52", TEST_REGION)
domain.to_short_dict().should.equal({"name": "foo", "status": "REGISTERED"}) domain.to_short_dict().should.equal(
{
"name": "foo",
"status": "REGISTERED",
"arn": "arn:aws:swf:{0}:{1}:/domain/foo".format(TEST_REGION, ACCOUNT_ID),
}
)
domain.description = "foo bar" domain.description = "foo bar"
domain.to_short_dict()["description"].should.equal("foo bar") domain.to_short_dict()["description"].should.equal("foo bar")
def test_domain_full_dict_representation(): def test_domain_full_dict_representation():
domain = Domain("foo", "52") domain = Domain("foo", "52", TEST_REGION)
domain.to_full_dict()["domainInfo"].should.equal(domain.to_short_dict()) domain.to_full_dict()["domainInfo"].should.equal(domain.to_short_dict())
_config = domain.to_full_dict()["configuration"] _config = domain.to_full_dict()["configuration"]
@ -27,38 +35,38 @@ def test_domain_full_dict_representation():
def test_domain_string_representation(): def test_domain_string_representation():
domain = Domain("my-domain", "60") domain = Domain("my-domain", "60", TEST_REGION)
str(domain).should.equal("Domain(name: my-domain, status: REGISTERED)") str(domain).should.equal("Domain(name: my-domain, status: REGISTERED)")
def test_domain_add_to_activity_task_list(): def test_domain_add_to_activity_task_list():
domain = Domain("my-domain", "60") domain = Domain("my-domain", "60", TEST_REGION)
domain.add_to_activity_task_list("foo", "bar") domain.add_to_activity_task_list("foo", "bar")
domain.activity_task_lists.should.equal({"foo": ["bar"]}) domain.activity_task_lists.should.equal({"foo": ["bar"]})
def test_domain_activity_tasks(): def test_domain_activity_tasks():
domain = Domain("my-domain", "60") domain = Domain("my-domain", "60", TEST_REGION)
domain.add_to_activity_task_list("foo", "bar") domain.add_to_activity_task_list("foo", "bar")
domain.add_to_activity_task_list("other", "baz") domain.add_to_activity_task_list("other", "baz")
sorted(domain.activity_tasks).should.equal(["bar", "baz"]) sorted(domain.activity_tasks).should.equal(["bar", "baz"])
def test_domain_add_to_decision_task_list(): def test_domain_add_to_decision_task_list():
domain = Domain("my-domain", "60") domain = Domain("my-domain", "60", TEST_REGION)
domain.add_to_decision_task_list("foo", "bar") domain.add_to_decision_task_list("foo", "bar")
domain.decision_task_lists.should.equal({"foo": ["bar"]}) domain.decision_task_lists.should.equal({"foo": ["bar"]})
def test_domain_decision_tasks(): def test_domain_decision_tasks():
domain = Domain("my-domain", "60") domain = Domain("my-domain", "60", TEST_REGION)
domain.add_to_decision_task_list("foo", "bar") domain.add_to_decision_task_list("foo", "bar")
domain.add_to_decision_task_list("other", "baz") domain.add_to_decision_task_list("other", "baz")
sorted(domain.decision_tasks).should.equal(["bar", "baz"]) sorted(domain.decision_tasks).should.equal(["bar", "baz"])
def test_domain_get_workflow_execution(): def test_domain_get_workflow_execution():
domain = Domain("my-domain", "60") domain = Domain("my-domain", "60", TEST_REGION)
wfe1 = WorkflowExecution( wfe1 = WorkflowExecution(
workflow_id="wf-id-1", run_id="run-id-1", execution_status="OPEN", open=True workflow_id="wf-id-1", run_id="run-id-1", execution_status="OPEN", open=True

View File

@ -6,6 +6,7 @@ import sure # noqa
from moto import mock_swf_deprecated from moto import mock_swf_deprecated
from moto import mock_swf from moto import mock_swf
from moto.core import ACCOUNT_ID
# RegisterDomain endpoint # RegisterDomain endpoint
@ -20,6 +21,9 @@ def test_register_domain():
domain["name"].should.equal("test-domain") domain["name"].should.equal("test-domain")
domain["status"].should.equal("REGISTERED") domain["status"].should.equal("REGISTERED")
domain["description"].should.equal("A test domain") domain["description"].should.equal("A test domain")
domain["arn"].should.equal(
"arn:aws:swf:us-east-1:{0}:/domain/test-domain".format(ACCOUNT_ID)
)
@mock_swf_deprecated @mock_swf_deprecated

View File

@ -31,7 +31,7 @@ for key, value in ACTIVITY_TASK_TIMEOUTS.items():
# A test Domain # A test Domain
def get_basic_domain(): def get_basic_domain():
return Domain("test-domain", "90") return Domain("test-domain", "90", "us-east-1")
# A test WorkflowType # A test WorkflowType