Kaydet (Commit) 97094e4e authored tarafından Joffrey F's avatar Joffrey F

New docker.types subpackage containing advanced config dictionary types

Tests and docs updated to match
docker.utils.types has been moved to docker.types
Signed-off-by: 's avatarJoffrey F <joffrey@docker.com>
üst 02e99e49
...@@ -5,9 +5,6 @@ from .daemon import DaemonApiMixin ...@@ -5,9 +5,6 @@ from .daemon import DaemonApiMixin
from .exec_api import ExecApiMixin from .exec_api import ExecApiMixin
from .image import ImageApiMixin from .image import ImageApiMixin
from .network import NetworkApiMixin from .network import NetworkApiMixin
from .service import ( from .service import ServiceApiMixin
ServiceApiMixin, TaskTemplate, ContainerSpec, Mount, Resources,
RestartPolicy, UpdateConfig
)
from .swarm import SwarmApiMixin from .swarm import SwarmApiMixin
from .volume import VolumeApiMixin from .volume import VolumeApiMixin
import six
from .. import errors
from .. import utils from .. import utils
class ServiceApiMixin(object): class ServiceApiMixin(object):
@utils.minimum_version('1.24')
def services(self, filters=None):
params = {
'filters': utils.convert_filters(filters) if filters else None
}
url = self._url('/services')
return self._result(self._get(url, params=params), True)
@utils.minimum_version('1.24') @utils.minimum_version('1.24')
def create_service( def create_service(
self, task_config, name=None, labels=None, mode=None, self, task_template, name=None, labels=None, mode=None,
update_config=None, networks=None, endpoint_config=None update_config=None, networks=None, endpoint_config=None
): ):
url = self._url('/services/create') url = self._url('/services/create')
data = { data = {
'Name': name, 'Name': name,
'Labels': labels, 'Labels': labels,
'TaskTemplate': task_config, 'TaskTemplate': task_template,
'Mode': mode, 'Mode': mode,
'UpdateConfig': update_config, 'UpdateConfig': update_config,
'Networks': networks, 'Networks': networks,
...@@ -44,6 +33,14 @@ class ServiceApiMixin(object): ...@@ -44,6 +33,14 @@ class ServiceApiMixin(object):
self._raise_for_status(resp) self._raise_for_status(resp)
return True return True
@utils.minimum_version('1.24')
def services(self, filters=None):
params = {
'filters': utils.convert_filters(filters) if filters else None
}
url = self._url('/services')
return self._result(self._get(url, params=params), True)
@utils.minimum_version('1.24') @utils.minimum_version('1.24')
@utils.check_resource @utils.check_resource
def update_service(self, service, version, task_template=None, name=None, def update_service(self, service, version, task_template=None, name=None,
...@@ -69,167 +66,3 @@ class ServiceApiMixin(object): ...@@ -69,167 +66,3 @@ class ServiceApiMixin(object):
resp = self._post_json(url, data=data, params={'version': version}) resp = self._post_json(url, data=data, params={'version': version})
self._raise_for_status(resp) self._raise_for_status(resp)
return True return True
class TaskTemplate(dict):
def __init__(self, container_spec, resources=None, restart_policy=None,
placement=None, log_driver=None):
self['ContainerSpec'] = container_spec
if resources:
self['Resources'] = resources
if restart_policy:
self['RestartPolicy'] = restart_policy
if placement:
self['Placement'] = placement
if log_driver:
self['LogDriver'] = log_driver
@property
def container_spec(self):
return self.get('ContainerSpec')
@property
def resources(self):
return self.get('Resources')
@property
def restart_policy(self):
return self.get('RestartPolicy')
@property
def placement(self):
return self.get('Placement')
class ContainerSpec(dict):
def __init__(self, image, command=None, args=None, env=None, workdir=None,
user=None, labels=None, mounts=None, stop_grace_period=None):
self['Image'] = image
self['Command'] = command
self['Args'] = args
if env is not None:
self['Env'] = env
if workdir is not None:
self['Dir'] = workdir
if user is not None:
self['User'] = user
if labels is not None:
self['Labels'] = labels
if mounts is not None:
for mount in mounts:
if isinstance(mount, six.string_types):
mounts.append(Mount.parse_mount_string(mount))
mounts.remove(mount)
self['Mounts'] = mounts
if stop_grace_period is not None:
self['StopGracePeriod'] = stop_grace_period
class Mount(dict):
def __init__(self, target, source, type='volume', read_only=False,
propagation=None, no_copy=False, labels=None,
driver_config=None):
self['Target'] = target
self['Source'] = source
if type not in ('bind', 'volume'):
raise errors.DockerError(
'Only acceptable mount types are `bind` and `volume`.'
)
self['Type'] = type
if type == 'bind':
if propagation is not None:
self['BindOptions'] = {
'Propagation': propagation
}
if any(labels, driver_config, no_copy):
raise errors.DockerError(
'Mount type is binding but volume options have been '
'provided.'
)
else:
volume_opts = {}
if no_copy:
volume_opts['NoCopy'] = True
if labels:
volume_opts['Labels'] = labels
if driver_config:
volume_opts['driver_config'] = driver_config
if volume_opts:
self['VolumeOptions'] = volume_opts
if propagation:
raise errors.DockerError(
'Mount type is volume but `propagation` argument has been '
'provided.'
)
@classmethod
def parse_mount_string(cls, string):
parts = string.split(':')
if len(parts) > 3:
raise errors.DockerError(
'Invalid mount format "{0}"'.format(string)
)
if len(parts) == 1:
return cls(target=parts[0])
else:
target = parts[1]
source = parts[0]
read_only = not (len(parts) == 3 or parts[2] == 'ro')
return cls(target, source, read_only=read_only)
class Resources(dict):
def __init__(self, cpu_limit=None, mem_limit=None, cpu_reservation=None,
mem_reservation=None):
limits = {}
reservation = {}
if cpu_limit is not None:
limits['NanoCPUs'] = cpu_limit
if mem_limit is not None:
limits['MemoryBytes'] = mem_limit
if cpu_reservation is not None:
reservation['NanoCPUs'] = cpu_reservation
if mem_reservation is not None:
reservation['MemoryBytes'] = mem_reservation
self['Limits'] = limits
self['Reservation'] = reservation
class UpdateConfig(dict):
def __init__(self, parallelism=0, delay=None, failure_action='continue'):
self['Parallelism'] = parallelism
if delay is not None:
self['Delay'] = delay
if failure_action not in ('pause', 'continue'):
raise errors.DockerError(
'failure_action must be either `pause` or `continue`.'
)
self['FailureAction'] = failure_action
class RestartConditionTypesEnum(object):
_values = (
'none',
'on_failure',
'any',
)
NONE, ON_FAILURE, ANY = _values
class RestartPolicy(dict):
condition_types = RestartConditionTypesEnum
def __init__(self, condition=RestartConditionTypesEnum.NONE, delay=0,
attempts=0, window=0):
if condition not in self.condition_types._values:
raise TypeError(
'Invalid RestartPolicy condition {0}'.format(condition)
)
self['Condition'] = condition
self['Delay'] = delay
self['Attempts'] = attempts
self['Window'] = window
# flake8: noqa
from .containers import LogConfig, Ulimit
from .services import (
ContainerSpec, LogDriver, Mount, Resources, RestartPolicy, TaskTemplate,
UpdateConfig
)
from .swarm import SwarmSpec, SwarmExternalCA
import six
class DictType(dict):
def __init__(self, init):
for k, v in six.iteritems(init):
self[k] = v
import six import six
from .base import DictType
class LogConfigTypesEnum(object): class LogConfigTypesEnum(object):
_values = ( _values = (
...@@ -13,12 +15,6 @@ class LogConfigTypesEnum(object): ...@@ -13,12 +15,6 @@ class LogConfigTypesEnum(object):
JSON, SYSLOG, JOURNALD, GELF, FLUENTD, NONE = _values JSON, SYSLOG, JOURNALD, GELF, FLUENTD, NONE = _values
class DictType(dict):
def __init__(self, init):
for k, v in six.iteritems(init):
self[k] = v
class LogConfig(DictType): class LogConfig(DictType):
types = LogConfigTypesEnum types = LogConfigTypesEnum
...@@ -94,45 +90,3 @@ class Ulimit(DictType): ...@@ -94,45 +90,3 @@ class Ulimit(DictType):
@hard.setter @hard.setter
def hard(self, value): def hard(self, value):
self['Hard'] = value self['Hard'] = value
class SwarmSpec(DictType):
def __init__(self, task_history_retention_limit=None,
snapshot_interval=None, keep_old_snapshots=None,
log_entries_for_slow_followers=None, heartbeat_tick=None,
election_tick=None, dispatcher_heartbeat_period=None,
node_cert_expiry=None, external_ca=None, name=None):
if task_history_retention_limit is not None:
self['Orchestration'] = {
'TaskHistoryRetentionLimit': task_history_retention_limit
}
if any([snapshot_interval, keep_old_snapshots,
log_entries_for_slow_followers, heartbeat_tick, election_tick]):
self['Raft'] = {
'SnapshotInterval': snapshot_interval,
'KeepOldSnapshots': keep_old_snapshots,
'LogEntriesForSlowFollowers': log_entries_for_slow_followers,
'HeartbeatTick': heartbeat_tick,
'ElectionTick': election_tick
}
if dispatcher_heartbeat_period:
self['Dispatcher'] = {
'HeartbeatPeriod': dispatcher_heartbeat_period
}
if node_cert_expiry or external_ca:
self['CAConfig'] = {
'NodeCertExpiry': node_cert_expiry,
'ExternalCA': external_ca
}
if name is not None:
self['Name'] = name
class SwarmExternalCA(DictType):
def __init__(self, url, protocol=None, options=None):
self['URL'] = url
self['Protocol'] = protocol
self['Options'] = options
import six
from .. import errors
class TaskTemplate(dict):
def __init__(self, container_spec, resources=None, restart_policy=None,
placement=None, log_driver=None):
self['ContainerSpec'] = container_spec
if resources:
self['Resources'] = resources
if restart_policy:
self['RestartPolicy'] = restart_policy
if placement:
self['Placement'] = placement
if log_driver:
self['LogDriver'] = log_driver
@property
def container_spec(self):
return self.get('ContainerSpec')
@property
def resources(self):
return self.get('Resources')
@property
def restart_policy(self):
return self.get('RestartPolicy')
@property
def placement(self):
return self.get('Placement')
class ContainerSpec(dict):
def __init__(self, image, command=None, args=None, env=None, workdir=None,
user=None, labels=None, mounts=None, stop_grace_period=None):
self['Image'] = image
self['Command'] = command
self['Args'] = args
if env is not None:
self['Env'] = env
if workdir is not None:
self['Dir'] = workdir
if user is not None:
self['User'] = user
if labels is not None:
self['Labels'] = labels
if mounts is not None:
for mount in mounts:
if isinstance(mount, six.string_types):
mounts.append(Mount.parse_mount_string(mount))
mounts.remove(mount)
self['Mounts'] = mounts
if stop_grace_period is not None:
self['StopGracePeriod'] = stop_grace_period
class Mount(dict):
def __init__(self, target, source, type='volume', read_only=False,
propagation=None, no_copy=False, labels=None,
driver_config=None):
self['Target'] = target
self['Source'] = source
if type not in ('bind', 'volume'):
raise errors.DockerError(
'Only acceptable mount types are `bind` and `volume`.'
)
self['Type'] = type
if type == 'bind':
if propagation is not None:
self['BindOptions'] = {
'Propagation': propagation
}
if any(labels, driver_config, no_copy):
raise errors.DockerError(
'Mount type is binding but volume options have been '
'provided.'
)
else:
volume_opts = {}
if no_copy:
volume_opts['NoCopy'] = True
if labels:
volume_opts['Labels'] = labels
if driver_config:
volume_opts['driver_config'] = driver_config
if volume_opts:
self['VolumeOptions'] = volume_opts
if propagation:
raise errors.DockerError(
'Mount type is volume but `propagation` argument has been '
'provided.'
)
@classmethod
def parse_mount_string(cls, string):
parts = string.split(':')
if len(parts) > 3:
raise errors.DockerError(
'Invalid mount format "{0}"'.format(string)
)
if len(parts) == 1:
return cls(target=parts[0])
else:
target = parts[1]
source = parts[0]
read_only = not (len(parts) == 3 or parts[2] == 'ro')
return cls(target, source, read_only=read_only)
class Resources(dict):
def __init__(self, cpu_limit=None, mem_limit=None, cpu_reservation=None,
mem_reservation=None):
limits = {}
reservation = {}
if cpu_limit is not None:
limits['NanoCPUs'] = cpu_limit
if mem_limit is not None:
limits['MemoryBytes'] = mem_limit
if cpu_reservation is not None:
reservation['NanoCPUs'] = cpu_reservation
if mem_reservation is not None:
reservation['MemoryBytes'] = mem_reservation
if limits:
self['Limits'] = limits
if reservation:
self['Reservations'] = reservation
class UpdateConfig(dict):
def __init__(self, parallelism=0, delay=None, failure_action='continue'):
self['Parallelism'] = parallelism
if delay is not None:
self['Delay'] = delay
if failure_action not in ('pause', 'continue'):
raise errors.DockerError(
'failure_action must be either `pause` or `continue`.'
)
self['FailureAction'] = failure_action
class RestartConditionTypesEnum(object):
_values = (
'none',
'on_failure',
'any',
)
NONE, ON_FAILURE, ANY = _values
class RestartPolicy(dict):
condition_types = RestartConditionTypesEnum
def __init__(self, condition=RestartConditionTypesEnum.NONE, delay=0,
max_attempts=0, window=0):
if condition not in self.condition_types._values:
raise TypeError(
'Invalid RestartPolicy condition {0}'.format(condition)
)
self['Condition'] = condition
self['Delay'] = delay
self['MaxAttempts'] = max_attempts
self['Window'] = window
class LogDriver(dict):
def __init__(self, name, options=None):
self['Name'] = name
if options:
self['Options'] = options
class SwarmSpec(dict):
def __init__(self, task_history_retention_limit=None,
snapshot_interval=None, keep_old_snapshots=None,
log_entries_for_slow_followers=None, heartbeat_tick=None,
election_tick=None, dispatcher_heartbeat_period=None,
node_cert_expiry=None, external_ca=None, name=None):
if task_history_retention_limit is not None:
self['Orchestration'] = {
'TaskHistoryRetentionLimit': task_history_retention_limit
}
if any([snapshot_interval, keep_old_snapshots,
log_entries_for_slow_followers, heartbeat_tick, election_tick]):
self['Raft'] = {
'SnapshotInterval': snapshot_interval,
'KeepOldSnapshots': keep_old_snapshots,
'LogEntriesForSlowFollowers': log_entries_for_slow_followers,
'HeartbeatTick': heartbeat_tick,
'ElectionTick': election_tick
}
if dispatcher_heartbeat_period:
self['Dispatcher'] = {
'HeartbeatPeriod': dispatcher_heartbeat_period
}
if node_cert_expiry or external_ca:
self['CAConfig'] = {
'NodeCertExpiry': node_cert_expiry,
'ExternalCA': external_ca
}
if name is not None:
self['Name'] = name
class SwarmExternalCA(dict):
def __init__(self, url, protocol=None, options=None):
self['URL'] = url
self['Protocol'] = protocol
self['Options'] = options
...@@ -8,8 +8,6 @@ from .utils import ( ...@@ -8,8 +8,6 @@ from .utils import (
create_ipam_config, create_ipam_pool, parse_devices, normalize_links, create_ipam_config, create_ipam_pool, parse_devices, normalize_links,
) )
from .types import LogConfig, Ulimit from ..types import LogConfig, Ulimit
from .types import ( from ..types import SwarmExternalCA, SwarmSpec
SwarmExternalCA, SwarmSpec,
)
from .decorators import check_resource, minimum_version, update_headers from .decorators import check_resource, minimum_version, update_headers
...@@ -31,7 +31,7 @@ import six ...@@ -31,7 +31,7 @@ import six
from .. import constants from .. import constants
from .. import errors from .. import errors
from .. import tls from .. import tls
from .types import Ulimit, LogConfig from ..types import Ulimit, LogConfig
if six.PY2: if six.PY2:
from urllib import splitnport from urllib import splitnport
......
...@@ -95,7 +95,7 @@ Initialize a new Swarm using the current connected engine as the first node. ...@@ -95,7 +95,7 @@ Initialize a new Swarm using the current connected engine as the first node.
#### Client.create_swarm_spec #### Client.create_swarm_spec
Create a `docker.utils.SwarmSpec` instance that can be used as the `swarm_spec` Create a `docker.types.SwarmSpec` instance that can be used as the `swarm_spec`
argument in `Client.init_swarm`. argument in `Client.init_swarm`.
**Params:** **Params:**
...@@ -113,12 +113,12 @@ argument in `Client.init_swarm`. ...@@ -113,12 +113,12 @@ argument in `Client.init_swarm`.
heartbeat to the dispatcher. heartbeat to the dispatcher.
* node_cert_expiry (int): Automatic expiry for nodes certificates. * node_cert_expiry (int): Automatic expiry for nodes certificates.
* external_ca (dict): Configuration for forwarding signing requests to an * external_ca (dict): Configuration for forwarding signing requests to an
external certificate authority. Use `docker.utils.SwarmExternalCA`. external certificate authority. Use `docker.types.SwarmExternalCA`.
* name (string): Swarm's name * name (string): Swarm's name
**Returns:** `docker.utils.SwarmSpec` instance. **Returns:** `docker.types.SwarmSpec` instance.
#### docker.utils.SwarmExternalCA #### docker.types.SwarmExternalCA
Create a configuration dictionary for the `external_ca` argument in a Create a configuration dictionary for the `external_ca` argument in a
`SwarmSpec`. `SwarmSpec`.
......
...@@ -36,7 +36,8 @@ setup( ...@@ -36,7 +36,8 @@ setup(
url='https://github.com/docker/docker-py/', url='https://github.com/docker/docker-py/',
packages=[ packages=[
'docker', 'docker.api', 'docker.auth', 'docker.transport', 'docker', 'docker.api', 'docker.auth', 'docker.transport',
'docker.utils', 'docker.utils.ports', 'docker.ssladapter' 'docker.utils', 'docker.utils.ports', 'docker.ssladapter',
'docker.types',
], ],
install_requires=requirements, install_requires=requirements,
tests_require=test_requirements, tests_require=test_requirements,
......
...@@ -40,8 +40,10 @@ class ServiceTest(helpers.BaseTestCase): ...@@ -40,8 +40,10 @@ class ServiceTest(helpers.BaseTestCase):
else: else:
name = self.get_service_name() name = self.get_service_name()
container_spec = docker.api.ContainerSpec('busybox', ['echo', 'hello']) container_spec = docker.types.ContainerSpec(
task_tmpl = docker.api.TaskTemplate(container_spec) 'busybox', ['echo', 'hello']
)
task_tmpl = docker.types.TaskTemplate(container_spec)
return name, self.client.create_service(task_tmpl, name=name) return name, self.client.create_service(task_tmpl, name=name)
@requires_api_version('1.24') @requires_api_version('1.24')
...@@ -74,7 +76,7 @@ class ServiceTest(helpers.BaseTestCase): ...@@ -74,7 +76,7 @@ class ServiceTest(helpers.BaseTestCase):
test_services = self.client.services(filters={'name': 'dockerpytest_'}) test_services = self.client.services(filters={'name': 'dockerpytest_'})
assert len(test_services) == 0 assert len(test_services) == 0
def test_rempve_service_by_name(self): def test_remove_service_by_name(self):
svc_name, svc_id = self.create_simple_service() svc_name, svc_id = self.create_simple_service()
assert self.client.remove_service(svc_name) assert self.client.remove_service(svc_name)
test_services = self.client.services(filters={'name': 'dockerpytest_'}) test_services = self.client.services(filters={'name': 'dockerpytest_'})
...@@ -87,6 +89,94 @@ class ServiceTest(helpers.BaseTestCase): ...@@ -87,6 +89,94 @@ class ServiceTest(helpers.BaseTestCase):
assert len(services) == 1 assert len(services) == 1
assert services[0]['ID'] == svc_id['ID'] assert services[0]['ID'] == svc_id['ID']
def test_create_service_custom_log_driver(self):
container_spec = docker.types.ContainerSpec(
'busybox', ['echo', 'hello']
)
log_cfg = docker.types.LogDriver('none')
task_tmpl = docker.types.TaskTemplate(
container_spec, log_driver=log_cfg
)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'TaskTemplate' in svc_info['Spec']
res_template = svc_info['Spec']['TaskTemplate']
assert 'LogDriver' in res_template
assert 'Name' in res_template['LogDriver']
assert res_template['LogDriver']['Name'] == 'none'
def test_create_service_with_volume_mount(self):
vol_name = self.get_service_name()
container_spec = docker.types.ContainerSpec(
'busybox', ['ls'],
mounts=[
docker.types.Mount(target='/test', source=vol_name)
]
)
self.tmp_volumes.append(vol_name)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'ContainerSpec' in svc_info['Spec']['TaskTemplate']
cspec = svc_info['Spec']['TaskTemplate']['ContainerSpec']
assert 'Mounts' in cspec
assert len(cspec['Mounts']) == 1
mount = cspec['Mounts'][0]
assert mount['Target'] == '/test'
assert mount['Source'] == vol_name
assert mount['Type'] == 'volume'
def test_create_service_with_resources_constraints(self):
container_spec = docker.types.ContainerSpec('busybox', ['true'])
resources = docker.types.Resources(
cpu_limit=4000000, mem_limit=3 * 1024 * 1024 * 1024,
cpu_reservation=3500000, mem_reservation=2 * 1024 * 1024 * 1024
)
task_tmpl = docker.types.TaskTemplate(
container_spec, resources=resources
)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'TaskTemplate' in svc_info['Spec']
res_template = svc_info['Spec']['TaskTemplate']
assert 'Resources' in res_template
assert res_template['Resources']['Limits'] == resources['Limits']
assert res_template['Resources']['Reservations'] == resources[
'Reservations'
]
def test_create_service_with_update_config(self):
container_spec = docker.types.ContainerSpec('busybox', ['true'])
task_tmpl = docker.types.TaskTemplate(container_spec)
update_config = docker.types.UpdateConfig(
parallelism=10, delay=5, failure_action='pause'
)
name = self.get_service_name()
svc_id = self.client.create_service(
task_tmpl, update_config=update_config, name=name
)
svc_info = self.client.inspect_service(svc_id)
assert 'UpdateConfig' in svc_info['Spec']
assert update_config == svc_info['Spec']['UpdateConfig']
def test_create_service_with_restart_policy(self):
container_spec = docker.types.ContainerSpec('busybox', ['true'])
policy = docker.types.RestartPolicy(
docker.types.RestartPolicy.condition_types.ANY,
delay=5, max_attempts=5
)
task_tmpl = docker.types.TaskTemplate(
container_spec, restart_policy=policy
)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'RestartPolicy' in svc_info['Spec']['TaskTemplate']
assert policy == svc_info['Spec']['TaskTemplate']['RestartPolicy']
def test_update_service_name(self): def test_update_service_name(self):
name, svc_id = self.create_simple_service() name, svc_id = self.create_simple_service()
svc_info = self.client.inspect_service(svc_id) svc_info = self.client.inspect_service(svc_id)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment