Skip to content

Commit

Permalink
Use python docker-client for inspect command
Browse files Browse the repository at this point in the history
  • Loading branch information
Ravid Brown committed Jan 23, 2018
1 parent 4f4c755 commit 77f050c
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 50 deletions.
47 changes: 22 additions & 25 deletions docker_test_tools/environment.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import docker
import logging
import subprocess

Expand Down Expand Up @@ -41,6 +42,7 @@ def __init__(self, project_name, compose_path, log_path, collect_stats=False, re

self.plugins = []
self.plugins.append(self.logs_collector)
self.docker_client = docker.client.APIClient()
if collect_stats:
self.plugins.append(stats.StatsCollector(encoding=self.encoding,
project=self.project_name,
Expand Down Expand Up @@ -254,15 +256,19 @@ def get_container_id(self, name):
:param str name: container name as it appears in the docker compose file.
"""
self.validate_service_name(name)
try:
output = subprocess.check_output(
['docker-compose', '-f', self.compose_path, '-p', self.project_name, 'ps', '-q', name],
stderr=subprocess.STDOUT, env=self.environment_variables
)
except subprocess.CalledProcessError as error:
raise RuntimeError("Failed getting container %s id, reason: %s" % (name, error.output))

return utils.to_str(output)
# Filter the required container by container name docker-compose project.
# Since the python docker client support filtering only by one label, the second filter is done manually
filters = {
"label": "com.docker.compose.service={service}".format(service=name)
}
containers = self.docker_client.containers(filters=filters)
containers = [container for container in containers
if 'com.docker.compose.project' in container['Labels'] and
container['Labels']['com.docker.compose.project'] == self.project_name]
if len(containers) != 1:
raise RuntimeError("Unexpected containers number (%d) were found for name %s and project %s" % (len(containers), name, self.project_name))
return containers[0]['Id']

def is_container_ready(self, name):
"""Return True if the container is in ready state.
Expand All @@ -272,12 +278,12 @@ def is_container_ready(self, name):
:param str name: container name as it appears in the docker compose file.
"""
status_output = self._inspect(name, result_format='{{json .State}}')
status_output = self._inspect(name)['State']

if '"Health":' in status_output:
is_ready = '"Status":"healthy"' in status_output
if 'Health' in status_output:
is_ready = status_output['Health']['Status'] == "healthy"
else:
is_ready = '"Status":"running"' in status_output
is_ready = status_output['Status'] == "running"

log.debug("Container %s ready: %s", name, is_ready)
return is_ready
Expand All @@ -287,7 +293,7 @@ def container_status(self, name):
:param str name: container name as it appears in the docker compose file.
"""
return self._inspect(name, '{{json .State.Status}}')
return self._inspect(name)['State']['Status']

def wait_for_services(self, services=None, interval=1, timeout=60):
"""Wait for the services checks to pass.
Expand Down Expand Up @@ -410,23 +416,14 @@ def update_plugins(self, message):
for plugin in self.plugins:
plugin.update(message=message)

def _inspect(self, name, result_format='{{json}}'):
def _inspect(self, name):
"""
Returns the inspect content of a container
:param name: name of container
:param result_format: format of inspect output
"""
self.validate_service_name(name)
log.debug("Getting %s container state", name)
container_id = self.get_container_id(name)
try:
inspect_output = subprocess.check_output(
r"docker inspect --format='{}' {}".format(result_format, container_id),
shell=True, stderr=subprocess.STDOUT, env=self.environment_variables
)

except subprocess.CalledProcessError as error:
logging.warning("Failed getting container %s state, reason: %s", name, error.output)
return ''
inspect_output = self.docker_client.inspect_container(container_id)

return utils.to_str(inspect_output).strip('"\n')
return inspect_output
67 changes: 42 additions & 25 deletions tests/ut/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,14 @@ def test_container_methods_happy_flow(self, mocked_check_output):
)

mocked_check_output.return_value = "DOCKER_SERVICE"
self.controller.docker_client.containers = mock.MagicMock(return_value=[
{
'Labels': {'com.docker.compose.project': self.project_name},
'Id': 'container-id'
}
])
self.controller.get_container_id(service_name)
mocked_check_output.assert_called_with(
['docker-compose', '-f', self.compose_path, '-p', self.project_name, 'ps', '-q', service_name],
stderr=subprocess.STDOUT, env=self.ENVIRONMENT_VARIABLES
)
self.controller.docker_client.containers.assert_called_with(filters={'label': 'com.docker.compose.service=test'})

self.controller.pause_container(service_name)
mocked_check_output.assert_called_with(
Expand Down Expand Up @@ -107,33 +110,42 @@ def test_container_methods_happy_flow(self, mocked_check_output):

mock_get_id = mock.MagicMock(return_value='test-id')
with mock.patch("docker_test_tools.environment.EnvironmentController.get_container_id", mock_get_id):
mocked_check_output.return_value = '{"Health": {"Status":"healthy"}}'
self.controller.docker_client.inspect_container = mock.MagicMock(return_value={
"State": {
"Health": {
"Status": "healthy"
}
}
})
self.assertTrue(self.controller.is_container_ready('test'))
mocked_check_output.assert_called_with(
r"docker inspect --format='{{json .State}}' test-id",
shell=True, stderr=subprocess.STDOUT, env=self.ENVIRONMENT_VARIABLES
)

mocked_check_output.return_value = '{"Health": {"Status":"unhealthy"}}'
self.controller.docker_client.inspect_container.assert_called_with('test-id')

self.controller.docker_client.inspect_container = mock.MagicMock(return_value={
"State": {
"Health": {
"Status": "unhealthy"
}
}
})
self.assertFalse(self.controller.is_container_ready('test'))
mocked_check_output.assert_called_with(
r"docker inspect --format='{{json .State}}' test-id",
shell=True, stderr=subprocess.STDOUT, env=self.ENVIRONMENT_VARIABLES
)
self.controller.docker_client.inspect_container.assert_called_with('test-id')

mocked_check_output.return_value = '{"Status":"running"}'
self.controller.docker_client.inspect_container = mock.MagicMock(return_value={
"State": {
"Status": "running"
}
})
self.assertTrue(self.controller.is_container_ready('test'))
mocked_check_output.assert_called_with(
r"docker inspect --format='{{json .State}}' test-id",
shell=True, stderr=subprocess.STDOUT, env=self.ENVIRONMENT_VARIABLES
)
self.controller.docker_client.inspect_container.assert_called_with('test-id')

self.controller.docker_client.inspect_container = mock.MagicMock(return_value={
"State": {
"Status": "not-running"
}
})

mocked_check_output.return_value = '{"Status":"not-running"}'
self.assertFalse(self.controller.is_container_ready('test'))
mocked_check_output.assert_called_with(
r"docker inspect --format='{{json .State}}' test-id",
shell=True, stderr=subprocess.STDOUT, env=self.ENVIRONMENT_VARIABLES
)
self.controller.docker_client.inspect_container.assert_called_with('test-id')

@mock.patch('subprocess.check_output', mock.MagicMock(side_effect=subprocess.CalledProcessError(1, '', '')))
@mock.patch('docker_test_tools.environment.EnvironmentController.validate_service_name', mock.MagicMock())
Expand Down Expand Up @@ -175,6 +187,11 @@ def test_environment_compose_command_error(self):

mock_get_id = mock.MagicMock(return_value='test-id')
with mock.patch("docker_test_tools.environment.EnvironmentController.get_container_id", mock_get_id):
self.controller.docker_client.inspect_container = mock.MagicMock(return_value={
"State": {
"Status": "not-running"
}
})
self.assertFalse(self.controller.is_container_ready('test'))

def test_container_methods_bad_service_name(self):
Expand Down

0 comments on commit 77f050c

Please sign in to comment.