Containers:

- Improve documentation of docker API param mapping
- Improve sample containers config file
- Fix constraints
- A couple other minor bug/logging fixes
This commit is contained in:
Nate Coraor
2018-04-06 16:07:06 -04:00
parent 9db3d23eac
commit c42d642233
3 changed files with 304 additions and 191 deletions
+211 -172
View File
@@ -1,232 +1,271 @@
---
# Galaxy container interface configuration file
#
# To configure the location of this file, use the `containers_config_file`
# setting in galaxy.ini
# To configure the location of this file, use the `containers_config_file` setting in galaxy.yml. Additionally, the
# containers interface is only used if `enable_beta_containers_interface` is set in galaxy.yml.
###
### Container Interfaces
###
## Define container interfaces beneath the top-level `containers` dictionary. By # default, a single `_default_`
## container of type `docker` is defined:
# Define container interfaces beneath the top-level `containers` dictionary. By default, a single `_default_` interface
# of type `docker` is defined, equivalent to:
#containers:
# _default_:
# type: docker
# _default_:
# type: docker
## Additional options can be specified are specific to the container type:
# The interface name is arbitrary and allows multiple distinct configurations to be defined. The name can be any string,
# but `_default_` is used if a component that uses the containers interface does not specify an interface. Currently
# only Galaxy Interactive Environments use the containers interface, and do not have a way to specify which interface to
# use, so configure under the `_default_` key for now.
# Additional options can be specified are specific to the container type:
#containers:
# _default_:
# type: docker
# host: docker.example.org:2376
# force_tlsverify: yes
# _default_:
# type: docker
# host: docker.example.org:2376
# force_tlsverify: yes
## Keys in `containers` are used to map container consumers to specific # container consumers. If a mapping is not
## configured, the _default_ # configuration will be used. An example of multiple container configurations would be:
# Keys in `containers` are used to map container consumers to specific # container consumers. If a mapping is not
# configured, the _default_ configuration will be used. An example of multiple container configurations would be:
#containers:
# _default_:
# type: docker
# ... additional options ...
# example_swarm:
# type: docker_swarm
# ... additional options ...
# _default_:
# type: docker
# ... additional options ...
# example_swarm:
# type: docker_swarm
# ... additional options ...
#
###
### Container Types and Supported Options
###
## Command-line equivalent arguments are in [brackets] (if applicable)
# Command-line equivalent arguments are in [brackets] (if applicable)
### All types
##
## The following options are applicable to all container types:
##
containers:
#containers:
# _default_:
# type: ...
#
# Supported options for all container types
#
## [`--name` (partial)] Prepend this string to the name of containers created
# name_prefix: galaxy_
_default_:
## Prefix to prepend to
# [`--name` (partial)] Prepend this string to the name of containers created
#name_prefix: galaxy_
### `docker` and `docker_cli` types
##
## `docker_cli` interacts with Docker via its command-line interface. In the future `docker` will use the API (currently
## it's an alias for `docker_cli`)
##
#
# Supported options for `docker` container type
#
#containers:
# _default_:
# type: docker
local_docker:
## [`-H`/`--host] Daemon socket(s) to connect to
# host: null
type: docker
## [`--tlsverify`] Use TLS and verify the remote
# force_tlsverify: no
# [`-H`/`--host] Daemon socket(s) to connect to
#host: null
## [`--cpus`] Number of CPUs (default 0.000)
# cpus: null
# [`--tlsverify`] Use TLS and verify the remote
#force_tlsverify: no
## [`-m`/`--memory`] Memory limit
# memory: null
# [`--cpus`] Number of CPUs (default 0.000)
#cpus: null
## Default image to run if one is not provided to the run method
# image: null
# [`-m`/`--memory`] Memory limit
#memory: null
### `docker_swarm` and `docker_swarm_cli` types
##
## All of the `docker` type arguments are supported. Additionally:
##
# Default image to run if one is not provided to the run method
#image: null
#containers:
# _default_:
# type: docker_swarm
#
# Supported options for `docker_swarm` container type
#
## [`--resverve-cpu` and `--limit-cpu`] Reserve the given number of CPUs when containers are run to prevent other
## containers from being scheduled on the same node once all of its CPUs are allocated. Additionally, prevent container
## from using more than the given number of CPUs
# cpus: null
swarm:
## [`--resverve-memory` and `--limit-memory`] Reserve the given amount of memory when containers are run to prevent
## other containers from being scheduled on the same node once all of its memory is allocated. Additionally, prevent
## container from using more than the given amount of memory
# memory: null
type: docker_swarm
## If set, only nodes whose names begin with this string will be visible to and managed by the swarm manager. Note that
## regardless of whether this is set, the swarm manager will not attempt to control manager nodes
# node_prefix: null
# All of the `docker` interface type arguments are supported. Additionally:
# Convert image from name[:tag] form to name@digest form when possible, to
# avoid dependency on the image registry (e.g. Docker Hub or wherever the image
# was pulled from) at service creation time. For details see:
# https://github.com/docker/docker/issues/31427
# resolve_image_digest: no
# [`--resverve-cpu` and `--limit-cpu`] Reserve the given number of CPUs when containers are run to prevent other
# containers from being scheduled on the same node once all of its CPUs are allocated. Additionally, prevent
# container from using more than the given number of CPUs.
#cpus: null
# If a container run request is received that defines volumes to attach, should those volumes be ignored? Swarm mode
# does not support mounting volumes.
# ignore_volumes: no
# [`--resverve-memory` and `--limit-memory`] Reserve the given amount of memory when containers are run to
# prevent other containers from being scheduled on the same node once all of its memory is allocated.
# Additionally, prevent container from using more than the given amount of memory.
#memory: null
## For the following `service_create_*_constraint` options, if set, the swarm manager automatically sets a corresponding
## label on nodes it spawns for services with the given constraints.
# If set, only nodes whose names begin with this string will be visible to and managed by the swarm manager.
# Note that regardless of whether this is set, the swarm manager will not attempt to control manager nodes
#node_prefix: null
## [`--constraint node.labels._galaxy_image=={image}`]
## Automatically create a constraint based on the requested image name when new services are created (the image name is
## the resolved form if `resolve_image_digest` is set). This is useful if your nodes do not all have all of the possible
## images you'll want to run.
# service_create_image_constraint: no
# Convert image from name[:tag] form to name@digest form when possible, to avoid dependency on the image
# registry (e.g. Docker Hub or wherever the image was pulled from) at service creation time. For details see:
# https://github.com/docker/docker/issues/31427
#resolve_image_digest: no
## [`--constraint node.labels._galaxy_cpus=={cpus}`]
## Automatically create a constraint based on the requested number of CPUs (or 1, if `cpus` is unset). This is useful if
## you are spawning nodes with a mixture of CPU counts and want to prevent (for example) 1-cpu services from being
## scheduled on 2-cpu nodes when 2-cpu jobs are waiting.
# service_create_cpus_constraint: no
# If a container run request is received that defines volumes to attach, should those volumes be ignored? Swarm
# mode does not support mounting volumes.
#ignore_volumes: no
## Use the swarm manager to manage services on this swarm. Even if your swarm is static, using the manager is
## recommended as the manager will automatically remove services after they have terminated.
# managed: yes
#
# For the following `service_create_*_constraint` options, if set, the swarm manager automatically sets a
# corresponding label on nodes it spawns for services with the given constraints.
#
## Automatically start the swarm manager when new services are created. Useful if you want to run the swarm manager but
## control it separately from Galaxy.
# manager_autostart: yes
# [`--constraint node.labels._galaxy_image=={image}`] Automatically create a constraint based on the requested
# image name when new services are created (the image name is the resolved form if `resolve_image_digest` is
# set). This is useful if your nodes do not all have all of the possible images you'll want to run. Note: It's
# currently only possible for nodes to have a single image constraint defined. If your nodes have multiple
# images available, do not use this option.
#service_create_image_constraint: no
### `docker_swarm` swarm manager options
# [`--constraint node.labels._galaxy_cpus=={cpus}`] Automatically create a constraint based on the requested
# number of CPUs (or 1, if `cpus` is unset). This is useful if you are spawning nodes with a mixture of CPU
# counts and want to prevent (for example) 1-cpu services from being scheduled on 2-cpu nodes when 2-cpu jobs
# are waiting.
#service_create_cpus_constraint: no
## Galaxy can manage a Docker swarm using a built-in daemon and callouts to commands to add/remove nodes from your swarm.
## These options are set on `docker_swarm` type containers underneath a `manager_conf` key, like so:
#
# Galaxy can manage a Docker swarm using a built-in daemon and callouts to commands to add/remove nodes from
# your swarm.
#
#containers:
# _default_:
# type: docker_swarm
# manager_conf:
# spawn_command: openstack server create ...
# ...
# Use the swarm manager to manage services on this swarm. Even if your swarm is static, using the manager is
# recommended as the manager will automatically remove services after they have terminated.
#managed: yes
## When the swarm manager daemonizes, it writes a pid file so that only one manager will run at a time. This is the path
## to that pid file. {xdg_data_home} will be templated automatically and defaults to ~/.local/share as per the XDG
## specification
# pid_file: '{xdg_data_home}/galaxy_swarm_manager.pid'
# Automatically start the swarm manager when new services are created. Useful if you want to run the swarm
# manager but control it separately from Galaxy.
#manager_autostart: yes
## Program output will be written to the log
# log_file: '{xdg_data_home}/galaxy_swarm_manager.log'
# Configuration dictionary for the swarm manager
manager_conf:
## Level of log messages (levels should be Python logging module level names (case insensitive)
# log_level: INFO
#
# In order to use the swarm manager for more than just service cleanup, (e.g. its node spawn and destroy
# features), you'll need to configure the `cpus` option in the interface. Otherwise, there are no limits on
# the number of services that will be assigned to a given node, so the swarm manager has no way to determine
# that more nodes should be spawned.
#
# Each service consumes one "slot", and the number of slots available on a node is its number of CPUs
# divided by the value of `cpus`.
#
## Log message format
# log_format: %(name)s %(levelname)s %(asctime)s %(message)s
# When the swarm manager daemonizes, it writes a pid file so that only one manager will run at a time. This
# is the path to that pid file. {xdg_data_home} will be templated automatically and defaults to
# ~/.local/share as per the XDG specification
#pid_file: '{xdg_data_home}/galaxy_swarm_manager.pid'
## Limits:
##
## - service_wait_count_limit: number of services that should be waiting for each set of unique constraints before
## attempting to spawn a node
## - service_wait_time_limit: number of seconds a service should be waiting before attempting to spawn a node
## - node_idle_limit: number of seconds a node should be idle before terminating it
## - slots_min_limit: minimum number of worker slots that should be started and active
## - slots_min_spare: number of spare (unused) worker slots that should be started and active
# service_wait_count_limit: 0
# service_wait_time_limit: 5
# node_idle_limit: 120
# slots_min_limit: 0
# slots_min_spare: 0
# Program output will be written to the log
#log_file: '{xdg_data_home}/galaxy_swarm_manager.log'
# Each set of limits can also be set on a per-constraint basis under the `limits` list. Each member of `limits` is a
# dictionary with at least the key `constraints`, whose value is a list of constraint strings matching constraint
# strings that jobs will be created with, either by the use of `service_create_*_constraint` or manually setting
# constraints. Other keys match the `node_*` and `slots_*` limits above. Any limits unset will default to the "global"
# limits as set above, or their defaults. For example:
# limits:
# - constraints:
# - node.labels._galaxy_image==bgruening/docker-jupyter-notebook:16.01.1
# - node.labels._galaxy_cpus==1
# slots_min_limit: 2
# slots_min_spare: 1
# Level of log messages (levels are Python logging module level names (case insensitive))
#log_level: INFO
# Amount of time to wait for a spawning node to appear in `docker node ls` before considering it failed
# spawn_wait_time: 30
# Log message format
#log_format: %(name)s %(levelname)s %(asctime)s %(message)s
## Command to run to spawn new nodes. This command should join the node to the swarm. It is run once per unique set
## of constraints of waiting services. Can include template variables:
## - {cpus}: Number of CPUs needed by the requested service(s) or minimum limits
## - {slots}: Number of "slots" needed by the requested service(s) or minimum limits, where slots are CPU fractions
#i# determined by use of the "cpus" option in the `docker_swarm` section.
## - {image}: Image requested by service
## - {service_ids}: Comma-separated list of service ids with these constraints currently waiting
## - {service_count}: Number of services with these constraints currently waiting
## If this command does not block until the node is joined to the swarm, make sure it at least completes that step in
## `spawn_wait_time` once it returns control. How the `spawn_command` exits controls the swarm manager's behavior:
## - return code: 0, output: space-separated list of spawned nodes:
## Swarm manager will wait for the nodes to appear in the swarm and manage them. You are responsible for ensuring
## the names returned match the name as it will appear in the output of `docker node ls`.
## - return code: 0, output: empty:
## Refusing to allocate nodes and the swarm manager should not attempt to spawn nodes for the given service(s)
## again.
## - return code: 2, output: anything (it will be logged as a message)
## Refusing to allocate nodes and the swarm manager should attempt to spawn nodes for the given service(s) again.
## This is useful for controlling the maximum number of nodes that will be spawned.
# spawn_command: /bin/true
# List of services' environment variables that should be logged when performing periodic service logging. By
# default, none are logged, but `USER_EMAIL` is useful for GIE containers to log the user who is running the
# container.
#log_environment_variables:
# - USER_EMAIL
## Command to run to destroy idle nodes. Can include template variables:
## - {nodes}: Space-separated list of node names to destroy
## This command should block until at least the point at which any nodes being destroyed no longer appear in
## `docker node ls`. It should return the names of nodes destroyed, separated by spaces.
# destroy_command: /bin/true
# Command to run to spawn new nodes. This command should join the node to the swarm. It is run once per
# unique set of constraints of waiting services. Can include template variables:
#
# - {cpus}: Number of CPUs needed by the requested service(s) or minimum limits
# - {slots}: Number of "slots" needed by the requested service(s) or minimum limits, where slots are CPU
# fractions determined by use of the "cpus" option in the `docker_swarm` section.
# - {image}: Image requested by service
# - {service_ids}: Comma-separated list of service ids with these constraints currently waiting
# - {service_count}: Number of services with these constraints currently waiting
#
# If this command does not block until the node is joined to the swarm, make sure it at least completes that
# step in `spawn_wait_time` once it returns control. How the `spawn_command` exits controls the swarm
# manager's behavior:
#
# - return code: 0, output: space-separated list of spawned nodes:
# Swarm manager will wait for the nodes to appear in the swarm and manage them. You are responsible for ensuring
# the names returned match the name as it will appear in the output of `docker node ls`.
# - return code: 0, output: empty:
# Refusing to allocate nodes and the swarm manager should not attempt to spawn nodes for the given service(s)
# again. This is useful for controlling the maximum number of nodes that will be spawned.
# - return code: 2, output: anything (it will be logged as a message)
# Refusing/failed to allocate nodes but the swarm manager should attempt to spawn nodes for the given
# service(s) again.
#
#spawn_command: /bin/true
## Command to run if either of the above commands failed (e.g. to notify an administrator). Can include template
## variables:
## - {failed_command}: Command line of the command that failed
# command_failure_command: /bin/true
# Command to run to destroy idle nodes. Can include template variables:
#
# - {nodes}: Space-separated list of node names to destroy
#
# This command should block until at least the point at which any nodes being destroyed no longer appear in
# the swarm. It should return the names of nodes destroyed, separated by spaces.
#
#destroy_command: /bin/true
## Number of times to retry spawn/destroy commands before considering them to have failed, and seconds to wait between
## retries
# command_retries: 0
# command_retry_wait: 10
# Command to run if either of the above commands failed (e.g. to notify an administrator). Can include
# template variables:
#
# - {failed_command}: Command line of the command that failed
#
#command_failure_command: /bin/true
## Stop the swarm manager daemon when there are no services or nodes to manage
# terminate_when_idle: yes
# Number of times to retry spawn/destroy commands before considering them to have failed, and seconds to
# wait between retries.
#command_retries: 0
#command_retry_wait: 10
# Amount of time to wait for a spawning node to appear in the swarm before considering it failed
#spawn_wait_time: 30
# Stop the swarm manager daemon when there are no services or nodes to manage
#terminate_when_idle: yes
#
# Limits control when the swarm manager will spawn and terminate nodes.
#
# Number of services that must be waiting to run before attempting to spawn a node. If using constraints,
# then waiting services are grouped by constraint.
#service_wait_count_limit: 0
# Number of seconds a service must be waiting before attempting to spawn a node.
#service_wait_time_limit: 5
# Number of seconds a node must be idle before terminating it.
#node_idle_limit: 120
# Minimum number of worker slots that should be started and active.
#slots_min_limit: 0
# Number of spare (unused) worker slots that should be started and active
#slots_min_spare: 0
#
# Each set of limits can also be set on a per-constraint basis under the `limits` list. Each member of
# `limits` is a dictionary dictionary with at least the key `constraints`, whose value is a list of
# constraint strings matching constraint strings that jobs will be created with, either by the use of
# `service_create_*_constraint` or manually setting constraints. Other keys match the `node_*` and `slots_*`
# limits above. Any limits unset will default to the "global" limits as set above, or their defaults.
#
# If you are using constraints, you should at least define them in the limits section, even if using all
# "global" values for the limits. This allows the swarm manager to know what node types to keep around if
# you're using `slots_min_limit`.
#
# Limits section example:
#limits:
# constraints:
# - node.labels._galaxy_image==bgruening/docker-jupyter-notebook:16.01.1
# - node.labels._galaxy_cpus==1
# slots_min_limit: 2
# slots_min_spare: 1
+80 -12
View File
@@ -49,6 +49,11 @@ class DockerInterface(ContainerInterface):
'cpus': None,
'memory': None,
}
# These values are inserted into kwopts for run commands
conf_run_kwopts = (
'cpus',
'memory',
)
@property
def _default_image(self):
@@ -56,8 +61,7 @@ class DockerInterface(ContainerInterface):
return self._conf.image
def run_in_container(self, command, image=None, **kwopts):
# FIXME: these containers_conf overrides should be defined as class vars
for opt in ('cpus', 'memory'):
for opt in self.conf_run_kwopts:
if self._conf[opt]:
kwopts[opt] = self._conf[opt]
self.set_kwopts_name(kwopts)
@@ -231,8 +235,9 @@ class DockerAPIClient(object):
if r:
log.warning("The request appears to have succeeded, will not retry. Response is: %s", str(r))
return r
elif max_tries > 0 and tries > max_tries:
elif max_tries > 0 and tries >= max_tries:
log.error("Maximum number of attempts (%s) exceeded", max_tries)
return r
else:
tries += 1
of = " of %s" % max_tries if max_tries > 0 else ""
@@ -312,6 +317,10 @@ class DockerAPIInterface(DockerInterface):
@staticmethod
def _kwopt_to_param_names(map_spec, key):
"""For a given containers lib method parameter name, return the matching docker-py parameter name(s).
See :meth:`_create_docker_api_spec`.
"""
params = []
if 'param' not in map_spec and 'params' not in map_spec:
params.append(key)
@@ -322,6 +331,11 @@ class DockerAPIInterface(DockerInterface):
@staticmethod
def _kwopt_to_params(map_spec, key, value):
"""For a given containers lib method parameter name and value, return the matching docker-py parameters with
values set (including transformation with an optional map function).
See :meth:`_create_docker_api_spec`.
"""
params = {}
if 'map' in map_spec:
value = map_spec['map'](value)
@@ -330,54 +344,108 @@ class DockerAPIInterface(DockerInterface):
return params
def _create_docker_api_spec(self, option_map_name, spec_class, kwopts):
"""Creates docker-py objects used as arguments to API methods.
"""Create docker-py objects used as arguments to docker-py methods.
This method modifies ``kwopts`` by removing options that match the spec.
An option map is a class-level variable with name ``<map_name>_option_map`` and is a dict with format:
.. code-block:: python
sample_option_map = {
'containers_lib_option_name': {
'param': docker_lib_positional_argument_int or 'docker_lib_keyword_argument_name',
'params': like 'param' but an iterable containing multiple docker lib params to set,
'default': default value,
'map': function with with to transform the value,
'required': True if this param is required, else False (default),
},
'_spec_param': {
'spec_class': class of param value,
}
}
All members of the mapping value are optional.
For example, a spec map for (some of) the possible values of the :class:`docker.types.TaskTemplate`, which is
used as the ``task_template`` argument to :meth:`docker.APIClient.create_service`, and the possible values of
the :class`:docker.types.ContainerSpec`, which is used as the ``container_spec`` argument to the
``TaskTemplate`` would be:
.. code-block:: python
task_template_option_map = {
# TaskTemplate's 'container_spec' param is a ContainerSpec
'_container_spec': {
'spec_class': docker.types.ContainerSpec,
'required': True
}
}
container_spec_option_map = {
'image': {'param': 0}, # positional argument 0 to ContainerSpec()
'command': {}, # 'command' keyword argument to ContainerSpec()
'environment': { # 'env' keyword argument to ContainerSpec(), 'environment' keyword argument
'param': 'env' # to ContainerInterface.run_in_container()
},
}
Thus, calling ``DockerInterface.run_in_contaner('true', image='busybox', environment={'FOO': 'foo'}`` will
essentially do this (for example, if using Docker Swarm mode):
.. code-block:: python
container_spec = docker.types.ContainerSpec('busybox', command='true', env={'FOO': 'foo'})
task_template = docker.types.TaskTemplate(container_spec=container_spec)
docker.APIClient().create_service(task_template)
:param option_map_name: Name of option map class variable (``_option_map`` is automatically appended)
:type option_map_name: str
:param spec_class: docker-py specification class or callable returning an instance
:type spec_class: :class:`docker.types.Resources`, :class:`docker.types.ContainerSpec`, etc. or
callable
:param kwopts: Keyword options passed to calling method (e.g. :method:`DockerInterface.run()`)
:param kwopts: Keyword options passed to calling method (e.g.
:meth:`DockerInterface.run_in_container`)
:type kwopts: dict
:returns: Instantiated ``spec_class`` object
:rtype: ``type(spec_class)``
"""
def _kwopt_to_arg(map_spec, key, value, param=None):
# determines whether the given param is a positional or keyword argument in docker-py and adds it to the
# list of arguments
if isinstance(map_spec.get('param'), int):
spec_opts.append((map_spec.get('param'), value))
elif param is not None:
spec_kwopts[param] = value
else:
spec_kwopts.update(DockerAPIInterface._kwopt_to_params(map_spec, key, value))
# TODO: make this cleaner
# positional arguments
spec_opts = []
# keyword arguments
spec_kwopts = {}
# retrieve the option map for the docker-py object we're creating
option_map = getattr(self, option_map_name + '_option_map')
# set defaults
for key in filter(lambda k: option_map[k].get('default'), option_map.keys()):
map_spec = option_map[key]
_kwopt_to_arg(map_spec, key, map_spec['default'])
# don't allow kwopts that start with _, those are reserved for "child" classes
# don't allow kwopts that start with _, those are reserved for "child" object params
for kwopt in filter(lambda k: not k.startswith('_') and k in option_map, kwopts.keys()):
map_spec = option_map[kwopt]
_v = kwopts.pop(kwopt)
_kwopt_to_arg(map_spec, kwopt, _v)
# look for any child classes that need to be checked
# find any child objects that need to be created and recurse to create them
for _sub_k in filter(lambda k: k.startswith('_') and 'spec_class' in option_map[k], option_map.keys()):
map_spec = option_map[_sub_k]
param = _sub_k.lstrip('_')
_sub_v = self._create_docker_api_spec(param, map_spec['spec_class'], kwopts)
if _sub_v is not None or map_spec.get('required') or isinstance(map_spec.get('param'), int):
_kwopt_to_arg(map_spec, None, _sub_v, param=param)
# override params with values defined in the config
for key in filter(lambda k: self._conf.get(k) is not None, option_map.keys()):
_kwopt_to_arg(map_spec, key, self._conf[key])
# sort positional args and make into a flat tuple
if spec_opts:
spec_opts = sorted(spec_opts, key=lambda x: x[0])
spec_opts = [i[1] for i in spec_opts]
if spec_kwopts:
# create spec object
if spec_opts or spec_kwopts:
return spec_class(*spec_opts, **spec_kwopts)
else:
return None
+13 -7
View File
@@ -365,7 +365,6 @@ class DockerSwarmAPIInterface(DockerSwarmInterface, DockerAPIInterface):
'image': {'param': 0},
'command': {},
'environment': {'param': 'env'},
# FIXME: is our param name labels?
'labels': {},
}
restart_policy_option_map = {
@@ -375,6 +374,7 @@ class DockerSwarmAPIInterface(DockerSwarmInterface, DockerAPIInterface):
}
task_template_option_map = {
'_container_spec': {'spec_class': docker.types.ContainerSpec, 'required': True},
'_resources': {'spec_class': docker.types.Resources},
'_restart_policy': {'spec_class': docker.types.RestartPolicy},
'_placement': {'spec_class': docker.types.Placement},
}
@@ -398,19 +398,25 @@ class DockerSwarmAPIInterface(DockerSwarmInterface, DockerAPIInterface):
#
def service_create(self, command, image=None, **kwopts):
# TODO: some of this should probably move to run_in_container when the CLI interface is removed
log.debug("Creating docker service with image '%s' for command: %s", image, command)
# FIXME: these should be class level vars
if ('service_create_image_constraint' in self._conf or 'service_create_cpus_constraint' in self._conf) and 'constraint' not in kwopts:
kwopts['constraint'] = []
# insert run kwopts from config
for opt in self.conf_run_kwopts:
if self._conf[opt]:
kwopts[opt] = self._conf[opt]
# image is part of the container spec
kwopts['image'] = self._get_image(image)
# service constraints
kwopts['constraint'] = kwopts.get('constraint', [])
if self._conf.service_create_image_constraint:
kwopts['constraint'].append((IMAGE_CONSTRAINT, '==', image))
kwopts['constraint'].append((IMAGE_CONSTRAINT + '==' + image))
if self._conf.service_create_cpus_constraint:
cpus = kwopts.get('reserve_cpus', kwopts.get('limit_cpus', '1'))
kwopts['constraint'].append((CPUS_CONSTRAINT, '==', cpus))
kwopts['constraint'].append((CPUS_CONSTRAINT + '==' + cpus))
# ports
if 'publish_port_random' in kwopts:
kwopts['ports'] = [DockerSwarmAPIInterface.create_random_port_spec(kwopts.pop('publish_port_random'))]
# create specs
service_mode = self._create_docker_api_spec('service_mode', docker.types.ServiceMode, kwopts)
endpoint_spec = self._create_docker_api_spec('endpoint_spec', docker.types.EndpointSpec, kwopts)
task_template = self._create_docker_api_spec('task_template', docker.types.TaskTemplate, kwopts)
@@ -464,7 +470,7 @@ class DockerSwarmAPIInterface(DockerSwarmInterface, DockerAPIInterface):
spec = node.inspect['Spec']
if 'label_add' in kwopts:
kwopts['labels'] = spec.get('Labels', {})
kwopts['labels'].update(kwopts.pop('labels_add'))
kwopts['labels'].update(kwopts.pop('label_add'))
spec.update(self._create_docker_api_spec('node_spec', dict, kwopts))
return self._client.update_node(node.id, node.version, node_spec=spec)