Merge pull request #1446 from shin-/service-logs

Add service_logs command to APIClient and logs command to models.Service
This commit is contained in:
Joffrey F 2017-03-21 13:57:25 -07:00 committed by GitHub
commit 3d8b20a906
5 changed files with 112 additions and 5 deletions

View File

@ -45,7 +45,7 @@ integration-test-py3: build-py3
integration-dind: build build-py3
docker rm -vf dpy-dind || :
docker run -d --name dpy-dind --privileged dockerswarm/dind:1.13.1 docker daemon\
-H tcp://0.0.0.0:2375
-H tcp://0.0.0.0:2375 --experimental
docker run --rm --env="DOCKER_HOST=tcp://docker:2375" --env="DOCKER_TEST_API_VERSION=1.26"\
--link=dpy-dind:docker docker-sdk-python py.test tests/integration
docker run --rm --env="DOCKER_HOST=tcp://docker:2375" --env="DOCKER_TEST_API_VERSION=1.26"\
@ -59,7 +59,7 @@ integration-dind-ssl: build-dind-certs build build-py3
--env="DOCKER_CERT_PATH=/certs" --volumes-from dpy-dind-certs --name dpy-dind-ssl\
-v /tmp --privileged dockerswarm/dind:1.13.1 docker daemon --tlsverify\
--tlscacert=/certs/ca.pem --tlscert=/certs/server-cert.pem\
--tlskey=/certs/server-key.pem -H tcp://0.0.0.0:2375
--tlskey=/certs/server-key.pem -H tcp://0.0.0.0:2375 --experimental
docker run --rm --volumes-from dpy-dind-ssl --env="DOCKER_HOST=tcp://docker:2375"\
--env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --env="DOCKER_TEST_API_VERSION=1.26"\
--link=dpy-dind-ssl:docker docker-sdk-python py.test tests/integration

View File

@ -166,6 +166,56 @@ class ServiceApiMixin(object):
url = self._url('/services')
return self._result(self._get(url, params=params), True)
@utils.minimum_version('1.25')
@utils.check_resource
def service_logs(self, service, details=False, follow=False, stdout=False,
stderr=False, since=0, timestamps=False, tail='all',
is_tty=None):
"""
Get log stream for a service.
Note: This endpoint works only for services with the ``json-file``
or ``journald`` logging drivers.
Args:
service (str): ID or name of the service
details (bool): Show extra details provided to logs.
Default: ``False``
follow (bool): Keep connection open to read logs as they are
sent by the Engine. Default: ``False``
stdout (bool): Return logs from ``stdout``. Default: ``False``
stderr (bool): Return logs from ``stderr``. Default: ``False``
since (int): UNIX timestamp for the logs staring point.
Default: 0
timestamps (bool): Add timestamps to every log line.
tail (string or int): Number of log lines to be returned,
counting from the current end of the logs. Specify an
integer or ``'all'`` to output all log lines.
Default: ``all``
is_tty (bool): Whether the service's :py:class:`ContainerSpec`
enables the TTY option. If omitted, the method will query
the Engine for the information, causing an additional
roundtrip.
Returns (generator): Logs for the service.
"""
params = {
'details': details,
'follow': follow,
'stdout': stdout,
'stderr': stderr,
'since': since,
'timestamps': timestamps,
'tail': tail
}
url = self._url('/services/{0}/logs', service)
res = self._get(url, params=params, stream=True)
if is_tty is None:
is_tty = self.inspect_service(
service
)['Spec']['TaskTemplate']['ContainerSpec'].get('TTY', False)
return self._get_result_tty(True, res, is_tty)
@utils.minimum_version('1.24')
def tasks(self, filters=None):
"""

View File

@ -77,6 +77,34 @@ class Service(Model):
**create_kwargs
)
def logs(self, **kwargs):
"""
Get log stream for the service.
Note: This method works only for services with the ``json-file``
or ``journald`` logging drivers.
Args:
details (bool): Show extra details provided to logs.
Default: ``False``
follow (bool): Keep connection open to read logs as they are
sent by the Engine. Default: ``False``
stdout (bool): Return logs from ``stdout``. Default: ``False``
stderr (bool): Return logs from ``stderr``. Default: ``False``
since (int): UNIX timestamp for the logs staring point.
Default: 0
timestamps (bool): Add timestamps to every log line.
tail (string or int): Number of log lines to be returned,
counting from the current end of the logs. Specify an
integer or ``'all'`` to output all log lines.
Default: ``all``
Returns (generator): Logs for the service.
"""
is_tty = self.attrs['Spec']['TaskTemplate']['ContainerSpec'].get(
'TTY', False
)
return self.client.api.service_logs(self.id, is_tty=is_tty, **kwargs)
class ServiceCollection(Collection):
"""Services on the Docker server."""

View File

@ -1,3 +1,4 @@
import functools
import os
import os.path
import random
@ -53,6 +54,15 @@ def requires_api_version(version):
)
def requires_experimental(f):
@functools.wraps(f)
def wrapped(self, *args, **kwargs):
if not self.client.info()['ExperimentalBuild']:
pytest.skip('Feature requires Docker Engine experimental mode')
return f(self, *args, **kwargs)
return wrapped
def wait_on_condition(condition, delay=0.1, timeout=40):
start_time = time.time()
while not condition():

View File

@ -4,8 +4,11 @@ import random
import time
import docker
import six
from ..helpers import force_leave_swarm, requires_api_version
from ..helpers import (
force_leave_swarm, requires_api_version, requires_experimental
)
from .base import BaseAPIIntegrationTest, BUSYBOX
@ -27,13 +30,15 @@ class ServiceTest(BaseAPIIntegrationTest):
def get_service_name(self):
return 'dockerpytest_{0:x}'.format(random.getrandbits(64))
def get_service_container(self, service_name, attempts=20, interval=0.5):
def get_service_container(self, service_name, attempts=20, interval=0.5,
include_stopped=False):
# There is some delay between the service's creation and the creation
# of the service's containers. This method deals with the uncertainty
# when trying to retrieve the container associated with a service.
while True:
containers = self.client.containers(
filters={'name': [service_name]}, quiet=True
filters={'name': [service_name]}, quiet=True,
all=include_stopped
)
if len(containers) > 0:
return containers[0]
@ -97,6 +102,20 @@ class ServiceTest(BaseAPIIntegrationTest):
assert len(services) == 1
assert services[0]['ID'] == svc_id['ID']
@requires_api_version('1.25')
@requires_experimental
def test_service_logs(self):
name, svc_id = self.create_simple_service()
assert self.get_service_container(name, include_stopped=True)
logs = self.client.service_logs(svc_id, stdout=True, is_tty=False)
log_line = next(logs)
if six.PY3:
log_line = log_line.decode('utf-8')
assert 'hello\n' in log_line
assert 'com.docker.swarm.service.id={}'.format(
svc_id['ID']
) in log_line
def test_create_service_custom_log_driver(self):
container_spec = docker.types.ContainerSpec(
BUSYBOX, ['echo', 'hello']