Skip to content
This repository was archived by the owner on Oct 24, 2020. It is now read-only.

Commit 1daa519

Browse files
authored
Merge pull request #63 from loads/fix/issue-62
fix: check service drained vs container draining
2 parents 0a34ae0 + fd4907e commit 1daa519

File tree

4 files changed

+62
-85
lines changed

4 files changed

+62
-85
lines changed

ardere/aws.py

+21
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,27 @@ def all_services_ready(self, steps):
636636
results = executer.map(self.service_ready, steps)
637637
return all(results)
638638

639+
def service_done(self, step):
640+
# type: (Dict[str, Any]) -> bool
641+
"""Query a service to return whether its fully drained and back to
642+
INACTIVE"""
643+
service_name = step["name"]
644+
response = self._ecs_client.describe_services(
645+
cluster=self._ecs_name,
646+
services=[service_name]
647+
)
648+
649+
service = response["services"][0]
650+
return service["status"] == "INACTIVE"
651+
652+
def all_services_done(self, steps):
653+
# type: (List[Dict[str, Any]]) -> bool
654+
"""Queries all service ARN's in the plan to see if they're fully
655+
DRAINED and now INACTIVE"""
656+
with ThreadPoolExecutor(max_workers=8) as executer:
657+
results = executer.map(self.service_done, steps)
658+
return all(results)
659+
639660
def stop_finished_service(self, start_time, step):
640661
# type: (start_time, Dict[str, Any]) -> None
641662
"""Stops a service if it needs to shutdown"""

ardere/step_functions.py

+6-34
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,7 @@ def check_for_cluster_done(self):
345345
return self.event
346346

347347
def cleanup_cluster(self):
348-
"""Shutdown all ECS services and deregister all task definitions
349-
350-
"""
348+
"""Shutdown all ECS services and deregister all task definitions"""
351349
self.ecs.shutdown_plan(self.event["steps"])
352350

353351
# Attempt to remove the S3 object
@@ -363,34 +361,8 @@ def cleanup_cluster(self):
363361
return self.event
364362

365363
def check_drained(self):
366-
"""Ensure that all services are shut down before allowing restart
367-
368-
"""
369-
client = self.boto.client('ecs')
370-
actives = client.list_container_instances(
371-
cluster=self.event["ecs_name"],
372-
maxResults=1,
373-
status="ACTIVE",
374-
).get('containerInstanceArns', [])
375-
# filter out metric servers
376-
if self.event["metrics_options"]["enabled"]:
377-
metrics = self.ecs.locate_metrics_service()
378-
if metrics:
379-
metrics_arn = metrics.get("serviceArn")
380-
try:
381-
actives.remove(metrics_arn)
382-
except ValueError:
383-
pass
384-
if len(actives):
385-
raise UndrainedInstancesException(
386-
"Still active: {}.".format(actives))
387-
draining = len(
388-
client.list_container_instances(
389-
cluster=self.event["ecs_name"],
390-
maxResults=1,
391-
status="DRAINING",
392-
).get('containerInstanceArns', []))
393-
if draining:
394-
raise UndrainedInstancesException(
395-
"Still draining: {}.".format(draining))
396-
return self.event
364+
"""Ensure that all services are shut down before allowing restart"""
365+
if self.ecs.all_services_done(self.event["steps"]):
366+
return self.event
367+
else:
368+
raise UndrainedInstancesException("Services still draining")

tests/test_aws.py

+32
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,38 @@ def test_all_services_ready(self):
244244
ecs.all_services_ready(ecs._plan["steps"])
245245
ecs.service_ready.assert_called()
246246

247+
def test_service_done_true(self):
248+
ecs = self._make_FUT()
249+
step = ecs._plan["steps"][0]
250+
251+
ecs._ecs_client.describe_services.return_value = {
252+
"services": [{
253+
"status": "INACTIVE"
254+
}]
255+
}
256+
257+
result = ecs.service_done(step)
258+
eq_(result, True)
259+
260+
def test_service_not_known(self):
261+
ecs = self._make_FUT()
262+
step = ecs._plan["steps"][0]
263+
264+
ecs._ecs_client.describe_services.return_value = {
265+
"services": [{
266+
"status": "DRAINING"
267+
}]
268+
}
269+
270+
result = ecs.service_done(step)
271+
eq_(result, False)
272+
273+
def test_all_services_done(self):
274+
ecs = self._make_FUT()
275+
ecs.service_done = mock.Mock()
276+
ecs.all_services_done(ecs._plan["steps"])
277+
ecs.service_done.assert_called()
278+
247279
def test_stop_finished_service_stopped(self):
248280
ecs = self._make_FUT()
249281
ecs._ecs_client.update_service = mock.Mock()

tests/test_step_functions.py

+3-51
Original file line numberDiff line numberDiff line change
@@ -293,62 +293,14 @@ def test_cleanup_cluster_error(self):
293293
self.runner.cleanup_cluster()
294294
mock_s3.Object.assert_called()
295295

296-
def test_drain_check_active(self):
297-
from ardere.exceptions import UndrainedInstancesException
298-
299-
mock_client = mock.Mock()
300-
mock_client.list_container_instances.return_value = {
301-
'containerInstanceArns': [
302-
'Some-Arn-01234567890',
303-
'Metric-Arn-01234567890',
304-
],
305-
"nextToken": "token-8675309"
306-
}
307-
self.mock_boto.client.return_value = mock_client
308-
assert_raises(UndrainedInstancesException,
309-
self.runner.check_drained)
310-
311296
def test_drain_check_draining(self):
312297
from ardere.exceptions import UndrainedInstancesException
313-
314-
mock_client = mock.Mock()
315-
mock_client.list_container_instances.side_effect = [
316-
{},
317-
{
318-
'containerInstanceArns': [
319-
'Some-Arn-01234567890',
320-
],
321-
"nextToken": "token-8675309"
322-
}
323-
]
324-
self.mock_boto.client.return_value = mock_client
298+
self.mock_ecs.all_services_done.return_value = True
299+
self.runner.check_drained()
300+
self.mock_ecs.all_services_done.return_value = False
325301
assert_raises(UndrainedInstancesException,
326302
self.runner.check_drained)
327303

328-
def test_drain_check(self):
329-
# Include a "metrics" instance to show that we ignore it.
330-
self.plan["metrics_options"] = dict(enabled=True)
331-
self.mock_ecs.locate_metrics_service.return_value = {
332-
"deployments": [{
333-
"desiredCount": 1,
334-
"runningCount": 1
335-
}],
336-
"serviceArn": "Metric-Arn-01234567890"
337-
}
338-
339-
mock_client = mock.Mock()
340-
mock_client.list_container_instances.side_effect = [
341-
{ # Actives
342-
'containerInstanceArns': [
343-
'Metric-Arn-01234567890',
344-
],
345-
"nextToken": "token-8675309"
346-
},
347-
{} # Draining
348-
]
349-
self.mock_boto.client.return_value = mock_client
350-
self.runner.check_drained()
351-
352304

353305
class TestValidation(unittest.TestCase):
354306
def _make_FUT(self):

0 commit comments

Comments
 (0)