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

Commit

Permalink
Merge pull request #56 from loads/bug/45
Browse files Browse the repository at this point in the history
bug: Ensure all containers drained before exiting
  • Loading branch information
jrconlin committed Apr 11, 2017
2 parents 7a74a12 + 4cbea2f commit 067bedc
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 0 deletions.
4 changes: 4 additions & 0 deletions ardere/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ class ShutdownPlanException(Exception):

class ValidationException(Exception):
"""Exception to indicate validation error parsing input"""


class UndrainedInstancesException(Exception):
"""There are still ACTIVE or DRAINING instances in the cluster"""
28 changes: 28 additions & 0 deletions ardere/step_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
ServicesStartingException,
ShutdownPlanException,
ValidationException,
UndrainedInstancesException,
)

logger = logging.getLogger()
Expand Down Expand Up @@ -418,3 +419,30 @@ def cleanup_cluster(self):
except botocore.exceptions.ClientError:
pass
return self.event

def check_drained(self):
"""Ensure that all services are shut down before allowing restart
Step 8
"""
client = self.boto.client('ecs')
actives = len(
client.list_container_instances(
cluster=self.event["ecs_name"],
maxResults=1,
status="ACTIVE",
).get('containerInstanceArns', []))
if actives:
raise UndrainedInstancesException(
"Still {} active.".format(actives))
draining = len(
client.list_container_instances(
cluster=self.event["ecs_name"],
maxResults=1,
status="DRAINING",
).get('containerInstanceArns', []))
if draining:
raise UndrainedInstancesException(
"Still {} draining.".format(draining))
return self.event
4 changes: 4 additions & 0 deletions handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ def check_for_cluster_done(event, context):
def cleanup_cluster(event, context):
runner = AsynchronousPlanRunner(event, context)
return runner.cleanup_cluster()


def check_drain(event, context):
return AsynchronousPlanRunner(event, context).check_drained()
14 changes: 14 additions & 0 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ functions:
handler: handler.check_for_cluster_done
cleanup_cluster:
handler: handler.cleanup_cluster
check_drain:
handler: handler.check_drain

stepFunctions:
stateMachines:
Expand All @@ -134,6 +136,7 @@ stepFunctions:
IntervalSeconds: 10
MaxAttempts: 60
BackoffRate: 1
Catch:
-
ErrorEquals:
- States.ALL
Expand Down Expand Up @@ -201,6 +204,17 @@ stepFunctions:
"Clean-up Cluster":
Type: Task
Resource: cleanup_cluster
Next: "Checking Drain"
"Checking Drain":
Type: Task
Resource: check_drain
Retry:
-
ErrorEquals:
- UndrainedInstancesException
IntervalSeconds: 10
MaxAttempts: 10
BackoffRate: 1
End: true

resources:
Expand Down
39 changes: 39 additions & 0 deletions tests/test_step_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,45 @@ def test_cleanup_cluster_error(self):
self.runner.cleanup_cluster()
mock_s3.Object.assert_called()

def test_drain_check_active(self):
from ardere.exceptions import UndrainedInstancesException

mock_client = mock.Mock()
mock_client.list_container_instances.return_value = {
'containerInstanceArns': [
'Some-Arn-01234567890',
],
"nextToken": "token-8675309"
}
self.mock_boto.client.return_value = mock_client
assert_raises(UndrainedInstancesException,
self.runner.check_drained)

def test_drain_check_draining(self):
from ardere.exceptions import UndrainedInstancesException

mock_client = mock.Mock()
mock_client.list_container_instances.side_effect = [
{},
{
'containerInstanceArns': [
'Some-Arn-01234567890',
],
"nextToken": "token-8675309"
}
]
self.mock_boto.client.return_value = mock_client
assert_raises(UndrainedInstancesException,
self.runner.check_drained)

def test_drain_check(self):
mock_client = mock.Mock()
mock_client.list_container_instances.side_effect = [
{},
{}
]
self.mock_boto.client.return_value = mock_client
self.runner.check_drained()

class TestValidation(unittest.TestCase):
def _make_FUT(self):
Expand Down

0 comments on commit 067bedc

Please sign in to comment.