From 283df4e62bcb9266fb5e00237107a682d00e94b6 Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 3 Feb 2018 13:14:27 -0800 Subject: [PATCH 1/8] Add health string generator. Works like DockerCLI Signed-off-by: Sorawis Nilparuk --- compose/container.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/compose/container.py b/compose/container.py index 9323b119249..83a551d3445 100644 --- a/compose/container.py +++ b/compose/container.py @@ -129,7 +129,7 @@ def human_readable_state(self): if self.is_restarting: return 'Restarting' if self.is_running: - return 'Ghost' if self.get('State.Ghost') else 'Up' + return 'Ghost' if self.get('State.Ghost') else self.create_health_status_string else: return 'Exit %s' % self.get('State.ExitCode') @@ -172,6 +172,19 @@ def has_api_logs(self): log_type = self.log_driver return not log_type or log_type in ('json-file', 'journald') + @property + def create_health_status_string(self): + """ Generate UP status string with up time and health + """ + status_string = 'Up' + if 'Health' in self.get('State'): + container_status = self.get('State.Health.Status') + if container_status == 'starting': + status_string += ' (health: starting)' + else: + status_string += ' (%s)' % container_status + return status_string + def attach_log_stream(self): """A log stream can only be attached if the container uses a json-file log driver. @@ -243,7 +256,7 @@ def inspect_if_not_inspected(self): self.inspect() def wait(self): - return self.client.wait(self.id).get('StatusCode', 127) + return self.client.wait(self.id) def logs(self, *args, **kwargs): return self.client.logs(self.id, *args, **kwargs) From e3437c55da923b43c6584e920b5848e1ceb01509 Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 3 Feb 2018 13:17:20 -0800 Subject: [PATCH 2/8] Add unittests for healthy, starting, none case Signed-off-by: Sorawis Nilparuk --- tests/unit/container_test.py | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/unit/container_test.py b/tests/unit/container_test.py index 0fcf23fa6cf..d64263c1fd6 100644 --- a/tests/unit/container_test.py +++ b/tests/unit/container_test.py @@ -129,6 +129,73 @@ def test_get_local_port(self): assert container.get_local_port(45454, protocol='tcp') == '0.0.0.0:49197' + def test_human_readable_states_no_health(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 7623, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-01-29T00:34:25.2052414Z", + "FinishedAt": "0001-01-01T00:00:00Z" + }, + }, has_been_inspected=True) + expected = "Up" + assert container.human_readable_state == expected + + def test_human_readable_states_starting(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 11744, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-02-03T07:56:20.3591233Z", + "FinishedAt": "2018-01-31T08:56:11.0505228Z", + "Health": { + "Status": "starting", + "FailingStreak": 0, + "Log": [] + } + } + }, has_been_inspected=True) + expected = "Up (health: starting)" + assert container.human_readable_state == expected + + def test_human_readable_states_healthy(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 5674, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-02-03T08:32:05.3281831Z", + "FinishedAt": "2018-02-03T08:11:35.7872706Z", + "Health": { + "Status": "healthy", + "FailingStreak": 0, + "Log": [] + } + } + }, has_been_inspected=True) + expected = "Up (healthy)" + assert container.human_readable_state == expected + def test_get(self): container = Container(None, { "Status": "Up 8 seconds", From 91b98a53372dd09ee04cb4ec84c0a312873a51db Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 3 Feb 2018 14:54:41 -0800 Subject: [PATCH 3/8] Update test to use JSON status Container running in attached mode returns a JSON string for its exit code rather than just 0. Updated acceptance test to match this behavior Signed-off-by: Sorawis Nilparuk --- tests/acceptance/cli_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index ade7d10a9d3..47df7e83875 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -875,8 +875,8 @@ def test_up_attached(self): assert 'simple_1 | simple' in result.stdout assert 'another_1 | another' in result.stdout - assert 'simple_1 exited with code 0' in result.stdout - assert 'another_1 exited with code 0' in result.stdout + assert 'simple_1 exited with code {\'Error\': None, \'StatusCode\': 0}' in result.stdout + assert 'another_1 exited with code {\'Error\': None, \'StatusCode\': 0}' in result.stdout @v2_only() def test_up(self): From 5017c057c60f7c12552cafca467b24103e60060e Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 3 Feb 2018 15:01:49 -0800 Subject: [PATCH 4/8] Rollback previous changes. May fix the changes in another PR Signed-off-by: Sorawis Nilparuk --- tests/acceptance/cli_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 47df7e83875..ade7d10a9d3 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -875,8 +875,8 @@ def test_up_attached(self): assert 'simple_1 | simple' in result.stdout assert 'another_1 | another' in result.stdout - assert 'simple_1 exited with code {\'Error\': None, \'StatusCode\': 0}' in result.stdout - assert 'another_1 exited with code {\'Error\': None, \'StatusCode\': 0}' in result.stdout + assert 'simple_1 exited with code 0' in result.stdout + assert 'another_1 exited with code 0' in result.stdout @v2_only() def test_up(self): From 9fcf91962c48f160946ee0018e6f81440c81c296 Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 3 Feb 2018 16:56:00 -0800 Subject: [PATCH 5/8] Fixed accidental changes causing fail tests I fat fingered a line in my first commit, this commit changes that to match current master. Signed-off-by: Sorawis Nilparuk --- compose/container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/container.py b/compose/container.py index 83a551d3445..7373042cb1d 100644 --- a/compose/container.py +++ b/compose/container.py @@ -256,7 +256,7 @@ def inspect_if_not_inspected(self): self.inspect() def wait(self): - return self.client.wait(self.id) + return self.client.wait(self.id).get('StatusCode', 127) def logs(self, *args, **kwargs): return self.client.logs(self.id, *args, **kwargs) From 8bb33c318e688c4392034b68f86c2ae4f583c934 Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 10 Feb 2018 14:37:03 -0800 Subject: [PATCH 6/8] Rename health string property and made changes as requested Signed-off-by: Sorawis Nilparuk --- compose/container.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/compose/container.py b/compose/container.py index 7373042cb1d..0c2ca990218 100644 --- a/compose/container.py +++ b/compose/container.py @@ -129,7 +129,7 @@ def human_readable_state(self): if self.is_restarting: return 'Restarting' if self.is_running: - return 'Ghost' if self.get('State.Ghost') else self.create_health_status_string + return 'Ghost' if self.get('State.Ghost') else self.human_readable_health_status else: return 'Exit %s' % self.get('State.ExitCode') @@ -173,16 +173,15 @@ def has_api_logs(self): return not log_type or log_type in ('json-file', 'journald') @property - def create_health_status_string(self): + def human_readable_health_status(self): """ Generate UP status string with up time and health """ status_string = 'Up' - if 'Health' in self.get('State'): - container_status = self.get('State.Health.Status') - if container_status == 'starting': - status_string += ' (health: starting)' - else: - status_string += ' (%s)' % container_status + container_status = self.get('State.Health.Status') + if container_status == 'starting': + status_string += ' (health: starting)' + elif container_status is not None: + status_string += ' (%s)' % container_status return status_string def attach_log_stream(self): From a257190e9a806b4e121e1040cf379b078f60a2f5 Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 10 Feb 2018 15:34:32 -0800 Subject: [PATCH 7/8] Squash commits Signed-off-by: Sorawis Nilparuk --- compose/container.py | 14 +++++++- tests/unit/container_test.py | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/compose/container.py b/compose/container.py index 9323b119249..0c2ca990218 100644 --- a/compose/container.py +++ b/compose/container.py @@ -129,7 +129,7 @@ def human_readable_state(self): if self.is_restarting: return 'Restarting' if self.is_running: - return 'Ghost' if self.get('State.Ghost') else 'Up' + return 'Ghost' if self.get('State.Ghost') else self.human_readable_health_status else: return 'Exit %s' % self.get('State.ExitCode') @@ -172,6 +172,18 @@ def has_api_logs(self): log_type = self.log_driver return not log_type or log_type in ('json-file', 'journald') + @property + def human_readable_health_status(self): + """ Generate UP status string with up time and health + """ + status_string = 'Up' + container_status = self.get('State.Health.Status') + if container_status == 'starting': + status_string += ' (health: starting)' + elif container_status is not None: + status_string += ' (%s)' % container_status + return status_string + def attach_log_stream(self): """A log stream can only be attached if the container uses a json-file log driver. diff --git a/tests/unit/container_test.py b/tests/unit/container_test.py index 0fcf23fa6cf..d64263c1fd6 100644 --- a/tests/unit/container_test.py +++ b/tests/unit/container_test.py @@ -129,6 +129,73 @@ def test_get_local_port(self): assert container.get_local_port(45454, protocol='tcp') == '0.0.0.0:49197' + def test_human_readable_states_no_health(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 7623, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-01-29T00:34:25.2052414Z", + "FinishedAt": "0001-01-01T00:00:00Z" + }, + }, has_been_inspected=True) + expected = "Up" + assert container.human_readable_state == expected + + def test_human_readable_states_starting(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 11744, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-02-03T07:56:20.3591233Z", + "FinishedAt": "2018-01-31T08:56:11.0505228Z", + "Health": { + "Status": "starting", + "FailingStreak": 0, + "Log": [] + } + } + }, has_been_inspected=True) + expected = "Up (health: starting)" + assert container.human_readable_state == expected + + def test_human_readable_states_healthy(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 5674, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-02-03T08:32:05.3281831Z", + "FinishedAt": "2018-02-03T08:11:35.7872706Z", + "Health": { + "Status": "healthy", + "FailingStreak": 0, + "Log": [] + } + } + }, has_been_inspected=True) + expected = "Up (healthy)" + assert container.human_readable_state == expected + def test_get(self): container = Container(None, { "Status": "Up 8 seconds", From 665323fd7af4625eaa4995f17e8d1e2fde3d39af Mon Sep 17 00:00:00 2001 From: Sorawis Nilparuk Date: Sat, 10 Feb 2018 15:34:32 -0800 Subject: [PATCH 8/8] Squash commits Signed-off-by: Sorawis Nilparuk Add health string generator. Works like DockerCLI Signed-off-by: Sorawis Nilparuk Add unittests for healthy, starting, none case Signed-off-by: Sorawis Nilparuk Update test to use JSON status Container running in attached mode returns a JSON string for its exit code rather than just 0. Updated acceptance test to match this behavior Signed-off-by: Sorawis Nilparuk Rollback previous changes. May fix the changes in another PR Signed-off-by: Sorawis Nilparuk Fixed accidental changes causing fail tests I fat fingered a line in my first commit, this commit changes that to match current master. Signed-off-by: Sorawis Nilparuk Rename health string property and made changes as requested Signed-off-by: Sorawis Nilparuk --- compose/container.py | 14 +++++++- tests/unit/container_test.py | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/compose/container.py b/compose/container.py index 9323b119249..0c2ca990218 100644 --- a/compose/container.py +++ b/compose/container.py @@ -129,7 +129,7 @@ def human_readable_state(self): if self.is_restarting: return 'Restarting' if self.is_running: - return 'Ghost' if self.get('State.Ghost') else 'Up' + return 'Ghost' if self.get('State.Ghost') else self.human_readable_health_status else: return 'Exit %s' % self.get('State.ExitCode') @@ -172,6 +172,18 @@ def has_api_logs(self): log_type = self.log_driver return not log_type or log_type in ('json-file', 'journald') + @property + def human_readable_health_status(self): + """ Generate UP status string with up time and health + """ + status_string = 'Up' + container_status = self.get('State.Health.Status') + if container_status == 'starting': + status_string += ' (health: starting)' + elif container_status is not None: + status_string += ' (%s)' % container_status + return status_string + def attach_log_stream(self): """A log stream can only be attached if the container uses a json-file log driver. diff --git a/tests/unit/container_test.py b/tests/unit/container_test.py index 0fcf23fa6cf..d64263c1fd6 100644 --- a/tests/unit/container_test.py +++ b/tests/unit/container_test.py @@ -129,6 +129,73 @@ def test_get_local_port(self): assert container.get_local_port(45454, protocol='tcp') == '0.0.0.0:49197' + def test_human_readable_states_no_health(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 7623, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-01-29T00:34:25.2052414Z", + "FinishedAt": "0001-01-01T00:00:00Z" + }, + }, has_been_inspected=True) + expected = "Up" + assert container.human_readable_state == expected + + def test_human_readable_states_starting(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 11744, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-02-03T07:56:20.3591233Z", + "FinishedAt": "2018-01-31T08:56:11.0505228Z", + "Health": { + "Status": "starting", + "FailingStreak": 0, + "Log": [] + } + } + }, has_been_inspected=True) + expected = "Up (health: starting)" + assert container.human_readable_state == expected + + def test_human_readable_states_healthy(self): + container = Container(None, { + "State": { + "Status": "running", + "Running": True, + "Paused": False, + "Restarting": False, + "OOMKilled": False, + "Dead": False, + "Pid": 5674, + "ExitCode": 0, + "Error": "", + "StartedAt": "2018-02-03T08:32:05.3281831Z", + "FinishedAt": "2018-02-03T08:11:35.7872706Z", + "Health": { + "Status": "healthy", + "FailingStreak": 0, + "Log": [] + } + } + }, has_been_inspected=True) + expected = "Up (healthy)" + assert container.human_readable_state == expected + def test_get(self): container = Container(None, { "Status": "Up 8 seconds",