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
from .exec_api import ExecApiMixin
from .image import ImageApiMixin
from .network import NetworkApiMixin
from .service import (
ServiceApiMixin, TaskTemplate, ContainerSpec, Mount, Resources,
RestartPolicy, UpdateConfig
)
from .service import ServiceApiMixin
from .swarm import SwarmApiMixin
from .volume import VolumeApiMixin
import six
from .. import errors
from .. import utils
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')
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
):
url = self._url('/services/create')
data = {
'Name': name,
'Labels': labels,
'TaskTemplate': task_config,
'TaskTemplate': task_template,
'Mode': mode,
'UpdateConfig': update_config,
'Networks': networks,
......@@ -44,6 +33,14 @@ class ServiceApiMixin(object):
self._raise_for_status(resp)
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.check_resource
def update_service(self, service, version, task_template=None, name=None,
......@@ -69,167 +66,3 @@ class ServiceApiMixin(object):
resp = self._post_json(url, data=data, params={'version': version})
self._raise_for_status(resp)
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
from .base import DictType
class LogConfigTypesEnum(object):
_values = (
......@@ -13,12 +15,6 @@ class LogConfigTypesEnum(object):
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):
types = LogConfigTypesEnum
......@@ -94,45 +90,3 @@ class Ulimit(DictType):
@hard.setter
def hard(self, 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 (
create_ipam_config, create_ipam_pool, parse_devices, normalize_links,
)
from .types import LogConfig, Ulimit
from .types import (
SwarmExternalCA, SwarmSpec,
)
from ..types import LogConfig, Ulimit
from ..types import SwarmExternalCA, SwarmSpec
from .decorators import check_resource, minimum_version, update_headers
......@@ -31,7 +31,7 @@ import six
from .. import constants
from .. import errors
from .. import tls
from .types import Ulimit, LogConfig
from ..types import Ulimit, LogConfig
if six.PY2:
from urllib import splitnport
......
......@@ -95,7 +95,7 @@ Initialize a new Swarm using the current connected engine as the first node.
#### 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`.
**Params:**
......@@ -113,12 +113,12 @@ argument in `Client.init_swarm`.
heartbeat to the dispatcher.
* node_cert_expiry (int): Automatic expiry for nodes certificates.
* 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
**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
`SwarmSpec`.
......
......@@ -36,7 +36,8 @@ setup(
url='https://github.com/docker/docker-py/',
packages=[
'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,
tests_require=test_requirements,
......
......@@ -40,8 +40,10 @@ class ServiceTest(helpers.BaseTestCase):
else:
name = self.get_service_name()
container_spec = docker.api.ContainerSpec('busybox', ['echo', 'hello'])
task_tmpl = docker.api.TaskTemplate(container_spec)
container_spec = docker.types.ContainerSpec(
'busybox', ['echo', 'hello']
)
task_tmpl = docker.types.TaskTemplate(container_spec)
return name, self.client.create_service(task_tmpl, name=name)
@requires_api_version('1.24')
......@@ -74,7 +76,7 @@ class ServiceTest(helpers.BaseTestCase):
test_services = self.client.services(filters={'name': 'dockerpytest_'})
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()
assert self.client.remove_service(svc_name)
test_services = self.client.services(filters={'name': 'dockerpytest_'})
......@@ -87,6 +89,94 @@ class ServiceTest(helpers.BaseTestCase):
assert len(services) == 1
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):
name, svc_id = self.create_simple_service()
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