Merge pull request #739 from okomestudio/ts/emr_list_clusters
Implement filters and pagers for some EMR end points
This commit is contained in:
commit
eaf70ac349
@ -3,6 +3,7 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import pytz
|
||||||
from boto.exception import JSONResponseError
|
from boto.exception import JSONResponseError
|
||||||
|
|
||||||
from jinja2 import Environment, DictLoader, TemplateNotFound
|
from jinja2 import Environment, DictLoader, TemplateNotFound
|
||||||
@ -477,6 +478,11 @@ def to_str(value, spec):
|
|||||||
return 'true' if value else 'false'
|
return 'true' if value else 'false'
|
||||||
elif vtype == 'integer':
|
elif vtype == 'integer':
|
||||||
return str(value)
|
return str(value)
|
||||||
|
elif vtype == 'float':
|
||||||
|
return str(value)
|
||||||
|
elif vtype == 'timestamp':
|
||||||
|
return datetime.datetime.utcfromtimestamp(
|
||||||
|
value).replace(tzinfo=pytz.utc).isoformat()
|
||||||
elif vtype == 'string':
|
elif vtype == 'string':
|
||||||
return str(value)
|
return str(value)
|
||||||
elif value is None:
|
elif value is None:
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import boto.emr
|
import boto.emr
|
||||||
import pytz
|
import pytz
|
||||||
|
from dateutil.parser import parse as dtparse
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
|
|
||||||
from .utils import random_instance_group_id, random_cluster_id, random_step_id
|
from .utils import random_instance_group_id, random_cluster_id, random_step_id
|
||||||
@ -273,12 +275,24 @@ class ElasticMapReduceBackend(BaseBackend):
|
|||||||
cluster = self.get_cluster(cluster_id)
|
cluster = self.get_cluster(cluster_id)
|
||||||
cluster.add_tags(tags)
|
cluster.add_tags(tags)
|
||||||
|
|
||||||
def describe_job_flows(self, job_flow_ids=None):
|
def describe_job_flows(self, job_flow_ids=None, job_flow_states=None, created_after=None, created_before=None):
|
||||||
clusters = self.clusters.values()
|
clusters = self.clusters.values()
|
||||||
|
|
||||||
|
within_two_month = datetime.now(pytz.utc) - timedelta(days=60)
|
||||||
|
clusters = [c for c in clusters if c.creation_datetime >= within_two_month]
|
||||||
|
|
||||||
if job_flow_ids:
|
if job_flow_ids:
|
||||||
return [cluster for cluster in clusters if cluster.id in job_flow_ids]
|
clusters = [c for c in clusters if c.id in job_flow_ids]
|
||||||
else:
|
if job_flow_states:
|
||||||
return clusters
|
clusters = [c for c in clusters if c.state in job_flow_states]
|
||||||
|
if created_after:
|
||||||
|
created_after = dtparse(created_after)
|
||||||
|
clusters = [c for c in clusters if c.creation_datetime > created_after]
|
||||||
|
if created_before:
|
||||||
|
created_before = dtparse(created_before)
|
||||||
|
clusters = [c for c in clusters if c.creation_datetime < created_before]
|
||||||
|
|
||||||
|
return sorted(clusters, key=lambda x: x.id)[:512]
|
||||||
|
|
||||||
def describe_step(self, cluster_id, step_id):
|
def describe_step(self, cluster_id, step_id):
|
||||||
cluster = self.clusters[cluster_id]
|
cluster = self.clusters[cluster_id]
|
||||||
@ -296,17 +310,48 @@ class ElasticMapReduceBackend(BaseBackend):
|
|||||||
if group_id in instance_group_ids
|
if group_id in instance_group_ids
|
||||||
]
|
]
|
||||||
|
|
||||||
def list_bootstrap_actions(self, cluster_id):
|
def list_bootstrap_actions(self, cluster_id, marker=None):
|
||||||
return self.clusters[cluster_id].bootstrap_actions
|
max_items = 50
|
||||||
|
actions = self.clusters[cluster_id].bootstrap_actions
|
||||||
|
start_idx = 0 if marker is None else int(marker)
|
||||||
|
marker = None if len(actions) <= start_idx + max_items else str(start_idx + max_items)
|
||||||
|
return actions[start_idx:start_idx + max_items], marker
|
||||||
|
|
||||||
def list_clusters(self):
|
def list_clusters(self, cluster_states=None, created_after=None,
|
||||||
return self.clusters.values()
|
created_before=None, marker=None):
|
||||||
|
max_items = 50
|
||||||
|
clusters = self.clusters.values()
|
||||||
|
if cluster_states:
|
||||||
|
clusters = [c for c in clusters if c.state in cluster_states]
|
||||||
|
if created_after:
|
||||||
|
created_after = dtparse(created_after)
|
||||||
|
clusters = [c for c in clusters if c.creation_datetime > created_after]
|
||||||
|
if created_before:
|
||||||
|
created_before = dtparse(created_before)
|
||||||
|
clusters = [c for c in clusters if c.creation_datetime < created_before]
|
||||||
|
clusters = sorted(clusters, key=lambda x: x.id)
|
||||||
|
start_idx = 0 if marker is None else int(marker)
|
||||||
|
marker = None if len(clusters) <= start_idx + max_items else str(start_idx + max_items)
|
||||||
|
return clusters[start_idx:start_idx + max_items], marker
|
||||||
|
|
||||||
def list_instance_groups(self, cluster_id):
|
def list_instance_groups(self, cluster_id, marker=None):
|
||||||
return self.clusters[cluster_id].instance_groups
|
max_items = 50
|
||||||
|
groups = sorted(self.clusters[cluster_id].instance_groups,
|
||||||
|
key=lambda x: x.id)
|
||||||
|
start_idx = 0 if marker is None else int(marker)
|
||||||
|
marker = None if len(groups) <= start_idx + max_items else str(start_idx + max_items)
|
||||||
|
return groups[start_idx:start_idx + max_items], marker
|
||||||
|
|
||||||
def list_steps(self, cluster_id, step_states=None):
|
def list_steps(self, cluster_id, marker=None, step_ids=None, step_states=None):
|
||||||
return self.clusters[cluster_id].steps
|
max_items = 50
|
||||||
|
steps = self.clusters[cluster_id].steps
|
||||||
|
if step_ids:
|
||||||
|
steps = [s for s in steps if s.id in step_ids]
|
||||||
|
if step_states:
|
||||||
|
steps = [s for s in steps if s.state in step_states]
|
||||||
|
start_idx = 0 if marker is None else int(marker)
|
||||||
|
marker = None if len(steps) <= start_idx + max_items else str(start_idx + max_items)
|
||||||
|
return steps[start_idx:start_idx + max_items], marker
|
||||||
|
|
||||||
def modify_instance_groups(self, instance_groups):
|
def modify_instance_groups(self, instance_groups):
|
||||||
result_groups = []
|
result_groups = []
|
||||||
@ -333,10 +378,11 @@ class ElasticMapReduceBackend(BaseBackend):
|
|||||||
cluster.set_termination_protection(value)
|
cluster.set_termination_protection(value)
|
||||||
|
|
||||||
def terminate_job_flows(self, job_flow_ids):
|
def terminate_job_flows(self, job_flow_ids):
|
||||||
clusters = [cluster for cluster in self.describe_job_flows()
|
clusters = []
|
||||||
if cluster.id in job_flow_ids]
|
for job_flow_id in job_flow_ids:
|
||||||
for cluster in clusters:
|
cluster = self.clusters[job_flow_id]
|
||||||
cluster.terminate()
|
cluster.terminate()
|
||||||
|
clusters.append(cluster)
|
||||||
return clusters
|
return clusters
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,8 +101,11 @@ class ElasticMapReduceResponse(BaseResponse):
|
|||||||
|
|
||||||
@generate_boto3_response('DescribeJobFlows')
|
@generate_boto3_response('DescribeJobFlows')
|
||||||
def describe_job_flows(self):
|
def describe_job_flows(self):
|
||||||
|
created_after = self._get_param('CreatedAfter')
|
||||||
|
created_before = self._get_param('CreatedBefore')
|
||||||
job_flow_ids = self._get_multi_param("JobFlowIds.member")
|
job_flow_ids = self._get_multi_param("JobFlowIds.member")
|
||||||
clusters = self.backend.describe_job_flows(job_flow_ids)
|
job_flow_states = self._get_multi_param('JobFlowStates.member')
|
||||||
|
clusters = self.backend.describe_job_flows(job_flow_ids, job_flow_states, created_after, created_before)
|
||||||
template = self.response_template(DESCRIBE_JOB_FLOWS_TEMPLATE)
|
template = self.response_template(DESCRIBE_JOB_FLOWS_TEMPLATE)
|
||||||
return template.render(clusters=clusters)
|
return template.render(clusters=clusters)
|
||||||
|
|
||||||
@ -120,22 +123,28 @@ class ElasticMapReduceResponse(BaseResponse):
|
|||||||
@generate_boto3_response('ListBootstrapActions')
|
@generate_boto3_response('ListBootstrapActions')
|
||||||
def list_bootstrap_actions(self):
|
def list_bootstrap_actions(self):
|
||||||
cluster_id = self._get_param('ClusterId')
|
cluster_id = self._get_param('ClusterId')
|
||||||
bootstrap_actions = self.backend.list_bootstrap_actions(cluster_id)
|
marker = self._get_param('Marker')
|
||||||
|
bootstrap_actions, marker = self.backend.list_bootstrap_actions(cluster_id, marker)
|
||||||
template = self.response_template(LIST_BOOTSTRAP_ACTIONS_TEMPLATE)
|
template = self.response_template(LIST_BOOTSTRAP_ACTIONS_TEMPLATE)
|
||||||
return template.render(bootstrap_actions=bootstrap_actions)
|
return template.render(bootstrap_actions=bootstrap_actions, marker=marker)
|
||||||
|
|
||||||
@generate_boto3_response('ListClusters')
|
@generate_boto3_response('ListClusters')
|
||||||
def list_clusters(self):
|
def list_clusters(self):
|
||||||
clusters = self.backend.list_clusters()
|
cluster_states = self._get_multi_param('ClusterStates.member')
|
||||||
|
created_after = self._get_param('CreatedAfter')
|
||||||
|
created_before = self._get_param('CreatedBefore')
|
||||||
|
marker = self._get_param('Marker')
|
||||||
|
clusters, marker = self.backend.list_clusters(cluster_states, created_after, created_before, marker)
|
||||||
template = self.response_template(LIST_CLUSTERS_TEMPLATE)
|
template = self.response_template(LIST_CLUSTERS_TEMPLATE)
|
||||||
return template.render(clusters=clusters)
|
return template.render(clusters=clusters, marker=marker)
|
||||||
|
|
||||||
@generate_boto3_response('ListInstanceGroups')
|
@generate_boto3_response('ListInstanceGroups')
|
||||||
def list_instance_groups(self):
|
def list_instance_groups(self):
|
||||||
cluster_id = self._get_param('ClusterId')
|
cluster_id = self._get_param('ClusterId')
|
||||||
instance_groups = self.backend.list_instance_groups(cluster_id)
|
marker = self._get_param('Marker')
|
||||||
|
instance_groups, marker = self.backend.list_instance_groups(cluster_id, marker=marker)
|
||||||
template = self.response_template(LIST_INSTANCE_GROUPS_TEMPLATE)
|
template = self.response_template(LIST_INSTANCE_GROUPS_TEMPLATE)
|
||||||
return template.render(instance_groups=instance_groups)
|
return template.render(instance_groups=instance_groups, marker=marker)
|
||||||
|
|
||||||
def list_instances(self):
|
def list_instances(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -143,9 +152,12 @@ class ElasticMapReduceResponse(BaseResponse):
|
|||||||
@generate_boto3_response('ListSteps')
|
@generate_boto3_response('ListSteps')
|
||||||
def list_steps(self):
|
def list_steps(self):
|
||||||
cluster_id = self._get_param('ClusterId')
|
cluster_id = self._get_param('ClusterId')
|
||||||
steps = self.backend.list_steps(cluster_id)
|
marker = self._get_param('Marker')
|
||||||
|
step_ids = self._get_multi_param('StepIds.member')
|
||||||
|
step_states = self._get_multi_param('StepStates.member')
|
||||||
|
steps, marker = self.backend.list_steps(cluster_id, marker=marker, step_ids=step_ids, step_states=step_states)
|
||||||
template = self.response_template(LIST_STEPS_TEMPLATE)
|
template = self.response_template(LIST_STEPS_TEMPLATE)
|
||||||
return template.render(steps=steps)
|
return template.render(steps=steps, marker=marker)
|
||||||
|
|
||||||
@generate_boto3_response('ModifyInstanceGroups')
|
@generate_boto3_response('ModifyInstanceGroups')
|
||||||
def modify_instance_groups(self):
|
def modify_instance_groups(self):
|
||||||
@ -623,6 +635,9 @@ LIST_BOOTSTRAP_ACTIONS_TEMPLATE = """<ListBootstrapActionsResponse xmlns="http:/
|
|||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</BootstrapActions>
|
</BootstrapActions>
|
||||||
|
{% if marker is not none %}
|
||||||
|
<Marker>{{ marker }}</Marker>
|
||||||
|
{% endif %}
|
||||||
</ListBootstrapActionsResult>
|
</ListBootstrapActionsResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId>df6f4f4a-ed85-11dd-9877-6fad448a8419</RequestId>
|
<RequestId>df6f4f4a-ed85-11dd-9877-6fad448a8419</RequestId>
|
||||||
@ -658,7 +673,9 @@ LIST_CLUSTERS_TEMPLATE = """<ListClustersResponse xmlns="http://elasticmapreduce
|
|||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Clusters>
|
</Clusters>
|
||||||
<Marker></Marker>
|
{% if marker is not none %}
|
||||||
|
<Marker>{{ marker }}</Marker>
|
||||||
|
{% endif %}
|
||||||
</ListClustersResult>
|
</ListClustersResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId>2690d7eb-ed86-11dd-9877-6fad448a8418</RequestId>
|
<RequestId>2690d7eb-ed86-11dd-9877-6fad448a8418</RequestId>
|
||||||
@ -706,6 +723,9 @@ LIST_INSTANCE_GROUPS_TEMPLATE = """<ListInstanceGroupsResponse xmlns="http://ela
|
|||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</InstanceGroups>
|
</InstanceGroups>
|
||||||
|
{% if marker is not none %}
|
||||||
|
<Marker>{{ marker }}</Marker>
|
||||||
|
{% endif %}
|
||||||
</ListInstanceGroupsResult>
|
</ListInstanceGroupsResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId>8296d8b8-ed85-11dd-9877-6fad448a8419</RequestId>
|
<RequestId>8296d8b8-ed85-11dd-9877-6fad448a8419</RequestId>
|
||||||
@ -760,6 +780,9 @@ LIST_STEPS_TEMPLATE = """<ListStepsResponse xmlns="http://elasticmapreduce.amazo
|
|||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Steps>
|
</Steps>
|
||||||
|
{% if marker is not none %}
|
||||||
|
<Marker>{{ marker }}</Marker>
|
||||||
|
{% endif %}
|
||||||
</ListStepsResult>
|
</ListStepsResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId>df6f4f4a-ed85-11dd-9877-6fad448a8419</RequestId>
|
<RequestId>df6f4f4a-ed85-11dd-9877-6fad448a8419</RequestId>
|
||||||
|
3
setup.py
3
setup.py
@ -10,7 +10,8 @@ install_requires = [
|
|||||||
"xmltodict",
|
"xmltodict",
|
||||||
"six",
|
"six",
|
||||||
"werkzeug",
|
"werkzeug",
|
||||||
"pytz"
|
"pytz",
|
||||||
|
"python-dateutil",
|
||||||
]
|
]
|
||||||
|
|
||||||
extras_require = {
|
extras_require = {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import boto
|
import boto
|
||||||
|
import pytz
|
||||||
from boto.emr.bootstrap_action import BootstrapAction
|
from boto.emr.bootstrap_action import BootstrapAction
|
||||||
from boto.emr.instance_group import InstanceGroup
|
from boto.emr.instance_group import InstanceGroup
|
||||||
from boto.emr.step import StreamingStep
|
from boto.emr.step import StreamingStep
|
||||||
@ -104,18 +107,53 @@ def test_describe_cluster():
|
|||||||
@mock_emr
|
@mock_emr
|
||||||
def test_describe_jobflows():
|
def test_describe_jobflows():
|
||||||
conn = boto.connect_emr()
|
conn = boto.connect_emr()
|
||||||
job1_id = conn.run_jobflow(**run_jobflow_args)
|
args = run_jobflow_args.copy()
|
||||||
job2_id = conn.run_jobflow(**run_jobflow_args)
|
expected = {}
|
||||||
|
|
||||||
|
for idx in range(400):
|
||||||
|
cluster_name = 'cluster' + str(idx)
|
||||||
|
args['name'] = cluster_name
|
||||||
|
cluster_id = conn.run_jobflow(**args)
|
||||||
|
expected[cluster_id] = {
|
||||||
|
'id': cluster_id,
|
||||||
|
'name': cluster_name,
|
||||||
|
'state': 'WAITING'
|
||||||
|
}
|
||||||
|
|
||||||
|
# need sleep since it appears the timestamp is always rounded to
|
||||||
|
# the nearest second internally
|
||||||
|
time.sleep(1)
|
||||||
|
timestamp = datetime.now(pytz.utc)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
for idx in range(400, 600):
|
||||||
|
cluster_name = 'cluster' + str(idx)
|
||||||
|
args['name'] = cluster_name
|
||||||
|
cluster_id = conn.run_jobflow(**args)
|
||||||
|
conn.terminate_jobflow(cluster_id)
|
||||||
|
expected[cluster_id] = {
|
||||||
|
'id': cluster_id,
|
||||||
|
'name': cluster_name,
|
||||||
|
'state': 'TERMINATED'
|
||||||
|
}
|
||||||
jobs = conn.describe_jobflows()
|
jobs = conn.describe_jobflows()
|
||||||
jobs.should.have.length_of(2)
|
jobs.should.have.length_of(512)
|
||||||
|
|
||||||
jobs = conn.describe_jobflows(jobflow_ids=[job2_id])
|
for cluster_id, y in expected.items():
|
||||||
jobs.should.have.length_of(1)
|
resp = conn.describe_jobflows(jobflow_ids=[cluster_id])
|
||||||
jobs[0].jobflowid.should.equal(job2_id)
|
resp.should.have.length_of(1)
|
||||||
|
resp[0].jobflowid.should.equal(cluster_id)
|
||||||
|
|
||||||
first_job = conn.describe_jobflow(job1_id)
|
resp = conn.describe_jobflows(states=['WAITING'])
|
||||||
first_job.jobflowid.should.equal(job1_id)
|
resp.should.have.length_of(400)
|
||||||
|
for x in resp:
|
||||||
|
x.state.should.equal('WAITING')
|
||||||
|
|
||||||
|
resp = conn.describe_jobflows(created_before=timestamp)
|
||||||
|
resp.should.have.length_of(400)
|
||||||
|
|
||||||
|
resp = conn.describe_jobflows(created_after=timestamp)
|
||||||
|
resp.should.have.length_of(200)
|
||||||
|
|
||||||
|
|
||||||
@mock_emr
|
@mock_emr
|
||||||
@ -204,43 +242,69 @@ def test_describe_jobflow():
|
|||||||
@mock_emr
|
@mock_emr
|
||||||
def test_list_clusters():
|
def test_list_clusters():
|
||||||
conn = boto.connect_emr()
|
conn = boto.connect_emr()
|
||||||
|
|
||||||
args = run_jobflow_args.copy()
|
args = run_jobflow_args.copy()
|
||||||
args['name'] = 'jobflow1'
|
expected = {}
|
||||||
cluster1_id = conn.run_jobflow(**args)
|
|
||||||
args['name'] = 'jobflow2'
|
|
||||||
cluster2_id = conn.run_jobflow(**args)
|
|
||||||
conn.terminate_jobflow(cluster2_id)
|
|
||||||
|
|
||||||
summary = conn.list_clusters()
|
for idx in range(40):
|
||||||
clusters = summary.clusters
|
cluster_name = 'jobflow' + str(idx)
|
||||||
clusters.should.have.length_of(2)
|
args['name'] = cluster_name
|
||||||
|
cluster_id = conn.run_jobflow(**args)
|
||||||
|
expected[cluster_id] = {
|
||||||
|
'id': cluster_id,
|
||||||
|
'name': cluster_name,
|
||||||
|
'normalizedinstancehours': '0',
|
||||||
|
'state': 'WAITING'
|
||||||
|
}
|
||||||
|
|
||||||
expected = {
|
# need sleep since it appears the timestamp is always rounded to
|
||||||
cluster1_id: {
|
# the nearest second internally
|
||||||
'id': cluster1_id,
|
time.sleep(1)
|
||||||
'name': 'jobflow1',
|
timestamp = datetime.now(pytz.utc)
|
||||||
'normalizedinstancehours': 0,
|
time.sleep(1)
|
||||||
'state': 'WAITING'},
|
|
||||||
cluster2_id: {
|
|
||||||
'id': cluster2_id,
|
|
||||||
'name': 'jobflow2',
|
|
||||||
'normalizedinstancehours': 0,
|
|
||||||
'state': 'TERMINATED'},
|
|
||||||
}
|
|
||||||
|
|
||||||
for x in clusters:
|
for idx in range(40, 70):
|
||||||
y = expected[x.id]
|
cluster_name = 'jobflow' + str(idx)
|
||||||
x.id.should.equal(y['id'])
|
args['name'] = cluster_name
|
||||||
x.name.should.equal(y['name'])
|
cluster_id = conn.run_jobflow(**args)
|
||||||
int(x.normalizedinstancehours).should.equal(y['normalizedinstancehours'])
|
conn.terminate_jobflow(cluster_id)
|
||||||
x.status.state.should.equal(y['state'])
|
expected[cluster_id] = {
|
||||||
x.status.timeline.creationdatetime.should.be.a(six.string_types)
|
'id': cluster_id,
|
||||||
if y['state'] == 'TERMINATED':
|
'name': cluster_name,
|
||||||
x.status.timeline.enddatetime.should.be.a(six.string_types)
|
'normalizedinstancehours': '0',
|
||||||
else:
|
'state': 'TERMINATED'
|
||||||
x.status.timeline.shouldnt.have.property('enddatetime')
|
}
|
||||||
x.status.timeline.readydatetime.should.be.a(six.string_types)
|
|
||||||
|
args = {}
|
||||||
|
while 1:
|
||||||
|
resp = conn.list_clusters(**args)
|
||||||
|
clusters = resp.clusters
|
||||||
|
len(clusters).should.be.lower_than_or_equal_to(50)
|
||||||
|
for x in clusters:
|
||||||
|
y = expected[x.id]
|
||||||
|
x.id.should.equal(y['id'])
|
||||||
|
x.name.should.equal(y['name'])
|
||||||
|
x.normalizedinstancehours.should.equal(y['normalizedinstancehours'])
|
||||||
|
x.status.state.should.equal(y['state'])
|
||||||
|
x.status.timeline.creationdatetime.should.be.a(six.string_types)
|
||||||
|
if y['state'] == 'TERMINATED':
|
||||||
|
x.status.timeline.enddatetime.should.be.a(six.string_types)
|
||||||
|
else:
|
||||||
|
x.status.timeline.shouldnt.have.property('enddatetime')
|
||||||
|
x.status.timeline.readydatetime.should.be.a(six.string_types)
|
||||||
|
if not hasattr(resp, 'marker'):
|
||||||
|
break
|
||||||
|
args = {'marker': resp.marker}
|
||||||
|
|
||||||
|
resp = conn.list_clusters(cluster_states=['TERMINATED'])
|
||||||
|
resp.clusters.should.have.length_of(30)
|
||||||
|
for x in resp.clusters:
|
||||||
|
x.status.state.should.equal('TERMINATED')
|
||||||
|
|
||||||
|
resp = conn.list_clusters(created_before=timestamp)
|
||||||
|
resp.clusters.should.have.length_of(40)
|
||||||
|
|
||||||
|
resp = conn.list_clusters(created_after=timestamp)
|
||||||
|
resp.clusters.should.have.length_of(30)
|
||||||
|
|
||||||
|
|
||||||
@mock_emr
|
@mock_emr
|
||||||
@ -516,7 +580,8 @@ def test_steps():
|
|||||||
|
|
||||||
expected = dict((s.name, s) for s in input_steps)
|
expected = dict((s.name, s) for s in input_steps)
|
||||||
|
|
||||||
for x in conn.list_steps(cluster_id).steps:
|
steps = conn.list_steps(cluster_id).steps
|
||||||
|
for x in steps:
|
||||||
y = expected[x.name]
|
y = expected[x.name]
|
||||||
# actiononfailure
|
# actiononfailure
|
||||||
list(arg.value for arg in x.config.args).should.equal([
|
list(arg.value for arg in x.config.args).should.equal([
|
||||||
@ -554,6 +619,17 @@ def test_steps():
|
|||||||
# x.status.timeline.enddatetime.should.be.a(six.string_types)
|
# x.status.timeline.enddatetime.should.be.a(six.string_types)
|
||||||
# x.status.timeline.startdatetime.should.be.a(six.string_types)
|
# x.status.timeline.startdatetime.should.be.a(six.string_types)
|
||||||
|
|
||||||
|
@requires_boto_gte('2.39')
|
||||||
|
def test_list_steps_with_states():
|
||||||
|
# boto's list_steps prior to 2.39 has a bug that ignores
|
||||||
|
# step_states argument.
|
||||||
|
steps = conn.list_steps(cluster_id).steps
|
||||||
|
step_id = steps[0].id
|
||||||
|
steps = conn.list_steps(cluster_id, step_states=['STARTING']).steps
|
||||||
|
steps.should.have.length_of(1)
|
||||||
|
steps[0].id.should.equal(step_id)
|
||||||
|
test_list_steps_with_states()
|
||||||
|
|
||||||
|
|
||||||
@mock_emr
|
@mock_emr
|
||||||
def test_tags():
|
def test_tags():
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import time
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
import pytz
|
||||||
import six
|
import six
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
@ -121,19 +124,54 @@ def test_describe_cluster():
|
|||||||
@mock_emr
|
@mock_emr
|
||||||
def test_describe_job_flows():
|
def test_describe_job_flows():
|
||||||
client = boto3.client('emr', region_name='us-east-1')
|
client = boto3.client('emr', region_name='us-east-1')
|
||||||
cluster1_id = client.run_job_flow(**run_job_flow_args)['JobFlowId']
|
args = deepcopy(run_job_flow_args)
|
||||||
cluster2_id = client.run_job_flow(**run_job_flow_args)['JobFlowId']
|
expected = {}
|
||||||
|
|
||||||
|
for idx in range(400):
|
||||||
|
cluster_name = 'cluster' + str(idx)
|
||||||
|
args['Name'] = cluster_name
|
||||||
|
cluster_id = client.run_job_flow(**args)['JobFlowId']
|
||||||
|
expected[cluster_id] = {
|
||||||
|
'Id': cluster_id,
|
||||||
|
'Name': cluster_name,
|
||||||
|
'State': 'WAITING'
|
||||||
|
}
|
||||||
|
|
||||||
|
# need sleep since it appears the timestamp is always rounded to
|
||||||
|
# the nearest second internally
|
||||||
|
time.sleep(1)
|
||||||
|
timestamp = datetime.now(pytz.utc)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
for idx in range(400, 600):
|
||||||
|
cluster_name = 'cluster' + str(idx)
|
||||||
|
args['Name'] = cluster_name
|
||||||
|
cluster_id = client.run_job_flow(**args)['JobFlowId']
|
||||||
|
client.terminate_job_flows(JobFlowIds=[cluster_id])
|
||||||
|
expected[cluster_id] = {
|
||||||
|
'Id': cluster_id,
|
||||||
|
'Name': cluster_name,
|
||||||
|
'State': 'TERMINATED'
|
||||||
|
}
|
||||||
|
|
||||||
resp = client.describe_job_flows()
|
resp = client.describe_job_flows()
|
||||||
resp['JobFlows'].should.have.length_of(2)
|
resp['JobFlows'].should.have.length_of(512)
|
||||||
|
|
||||||
resp = client.describe_job_flows(JobFlowIds=[cluster2_id])
|
for cluster_id, y in expected.items():
|
||||||
resp['JobFlows'].should.have.length_of(1)
|
resp = client.describe_job_flows(JobFlowIds=[cluster_id])
|
||||||
resp['JobFlows'][0]['JobFlowId'].should.equal(cluster2_id)
|
resp['JobFlows'].should.have.length_of(1)
|
||||||
|
resp['JobFlows'][0]['JobFlowId'].should.equal(cluster_id)
|
||||||
|
|
||||||
resp = client.describe_job_flows(JobFlowIds=[cluster1_id])
|
resp = client.describe_job_flows(JobFlowStates=['WAITING'])
|
||||||
resp['JobFlows'].should.have.length_of(1)
|
resp['JobFlows'].should.have.length_of(400)
|
||||||
resp['JobFlows'][0]['JobFlowId'].should.equal(cluster1_id)
|
for x in resp['JobFlows']:
|
||||||
|
x['ExecutionStatusDetail']['State'].should.equal('WAITING')
|
||||||
|
|
||||||
|
resp = client.describe_job_flows(CreatedBefore=timestamp)
|
||||||
|
resp['JobFlows'].should.have.length_of(400)
|
||||||
|
|
||||||
|
resp = client.describe_job_flows(CreatedAfter=timestamp)
|
||||||
|
resp['JobFlows'].should.have.length_of(200)
|
||||||
|
|
||||||
|
|
||||||
@mock_emr
|
@mock_emr
|
||||||
@ -203,41 +241,69 @@ def test_describe_job_flow():
|
|||||||
def test_list_clusters():
|
def test_list_clusters():
|
||||||
client = boto3.client('emr', region_name='us-east-1')
|
client = boto3.client('emr', region_name='us-east-1')
|
||||||
args = deepcopy(run_job_flow_args)
|
args = deepcopy(run_job_flow_args)
|
||||||
args['Name'] = 'jobflow1'
|
expected = {}
|
||||||
cluster1_id = client.run_job_flow(**args)['JobFlowId']
|
|
||||||
args['Name'] = 'jobflow2'
|
|
||||||
cluster2_id = client.run_job_flow(**args)['JobFlowId']
|
|
||||||
client.terminate_job_flows(JobFlowIds=[cluster2_id])
|
|
||||||
|
|
||||||
summary = client.list_clusters()
|
for idx in range(40):
|
||||||
clusters = summary['Clusters']
|
cluster_name = 'jobflow' + str(idx)
|
||||||
clusters.should.have.length_of(2)
|
args['Name'] = cluster_name
|
||||||
|
cluster_id = client.run_job_flow(**args)['JobFlowId']
|
||||||
expected = {
|
expected[cluster_id] = {
|
||||||
cluster1_id: {
|
'Id': cluster_id,
|
||||||
'Id': cluster1_id,
|
'Name': cluster_name,
|
||||||
'Name': 'jobflow1',
|
|
||||||
'NormalizedInstanceHours': 0,
|
'NormalizedInstanceHours': 0,
|
||||||
'State': 'WAITING'},
|
'State': 'WAITING'
|
||||||
cluster2_id: {
|
}
|
||||||
'Id': cluster2_id,
|
|
||||||
'Name': 'jobflow2',
|
|
||||||
'NormalizedInstanceHours': 0,
|
|
||||||
'State': 'TERMINATED'},
|
|
||||||
}
|
|
||||||
|
|
||||||
for x in clusters:
|
# need sleep since it appears the timestamp is always rounded to
|
||||||
y = expected[x['Id']]
|
# the nearest second internally
|
||||||
x['Id'].should.equal(y['Id'])
|
time.sleep(1)
|
||||||
x['Name'].should.equal(y['Name'])
|
timestamp = datetime.now(pytz.utc)
|
||||||
x['NormalizedInstanceHours'].should.equal(y['NormalizedInstanceHours'])
|
time.sleep(1)
|
||||||
x['Status']['State'].should.equal(y['State'])
|
|
||||||
x['Status']['Timeline']['CreationDateTime'].should.be.a('datetime.datetime')
|
for idx in range(40, 70):
|
||||||
if y['State'] == 'TERMINATED':
|
cluster_name = 'jobflow' + str(idx)
|
||||||
x['Status']['Timeline']['EndDateTime'].should.be.a('datetime.datetime')
|
args['Name'] = cluster_name
|
||||||
else:
|
cluster_id = client.run_job_flow(**args)['JobFlowId']
|
||||||
x['Status']['Timeline'].shouldnt.have.key('EndDateTime')
|
client.terminate_job_flows(JobFlowIds=[cluster_id])
|
||||||
x['Status']['Timeline']['ReadyDateTime'].should.be.a('datetime.datetime')
|
expected[cluster_id] = {
|
||||||
|
'Id': cluster_id,
|
||||||
|
'Name': cluster_name,
|
||||||
|
'NormalizedInstanceHours': 0,
|
||||||
|
'State': 'TERMINATED'
|
||||||
|
}
|
||||||
|
|
||||||
|
args = {}
|
||||||
|
while 1:
|
||||||
|
resp = client.list_clusters(**args)
|
||||||
|
clusters = resp['Clusters']
|
||||||
|
len(clusters).should.be.lower_than_or_equal_to(50)
|
||||||
|
for x in clusters:
|
||||||
|
y = expected[x['Id']]
|
||||||
|
x['Id'].should.equal(y['Id'])
|
||||||
|
x['Name'].should.equal(y['Name'])
|
||||||
|
x['NormalizedInstanceHours'].should.equal(y['NormalizedInstanceHours'])
|
||||||
|
x['Status']['State'].should.equal(y['State'])
|
||||||
|
x['Status']['Timeline']['CreationDateTime'].should.be.a('datetime.datetime')
|
||||||
|
if y['State'] == 'TERMINATED':
|
||||||
|
x['Status']['Timeline']['EndDateTime'].should.be.a('datetime.datetime')
|
||||||
|
else:
|
||||||
|
x['Status']['Timeline'].shouldnt.have.key('EndDateTime')
|
||||||
|
x['Status']['Timeline']['ReadyDateTime'].should.be.a('datetime.datetime')
|
||||||
|
marker = resp.get('Marker')
|
||||||
|
if marker is None:
|
||||||
|
break
|
||||||
|
args = {'Marker': marker}
|
||||||
|
|
||||||
|
resp = client.list_clusters(ClusterStates=['TERMINATED'])
|
||||||
|
resp['Clusters'].should.have.length_of(30)
|
||||||
|
for x in resp['Clusters']:
|
||||||
|
x['Status']['State'].should.equal('TERMINATED')
|
||||||
|
|
||||||
|
resp = client.list_clusters(CreatedBefore=timestamp)
|
||||||
|
resp['Clusters'].should.have.length_of(40)
|
||||||
|
|
||||||
|
resp = client.list_clusters(CreatedAfter=timestamp)
|
||||||
|
resp['Clusters'].should.have.length_of(30)
|
||||||
|
|
||||||
|
|
||||||
@mock_emr
|
@mock_emr
|
||||||
@ -567,6 +633,15 @@ def test_steps():
|
|||||||
# x['Status']['Timeline']['EndDateTime'].should.be.a('datetime.datetime')
|
# x['Status']['Timeline']['EndDateTime'].should.be.a('datetime.datetime')
|
||||||
# x['Status']['Timeline']['StartDateTime'].should.be.a('datetime.datetime')
|
# x['Status']['Timeline']['StartDateTime'].should.be.a('datetime.datetime')
|
||||||
|
|
||||||
|
step_id = steps[0]['Id']
|
||||||
|
steps = client.list_steps(ClusterId=cluster_id, StepIds=[step_id])['Steps']
|
||||||
|
steps.should.have.length_of(1)
|
||||||
|
steps[0]['Id'].should.equal(step_id)
|
||||||
|
|
||||||
|
steps = client.list_steps(ClusterId=cluster_id, StepStates=['STARTING'])['Steps']
|
||||||
|
steps.should.have.length_of(1)
|
||||||
|
steps[0]['Id'].should.equal(step_id)
|
||||||
|
|
||||||
|
|
||||||
@mock_emr
|
@mock_emr
|
||||||
def test_tags():
|
def test_tags():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user