mirror of https://github.com/docker/compose.git
Merge pull request #7417 from EricHripko/ipc-service
[Compose Spec] Implement service mode for ipc
This commit is contained in:
commit
b23eb2deab
|
@ -47,6 +47,7 @@ from .validation import validate_credential_spec
|
|||
from .validation import validate_depends_on
|
||||
from .validation import validate_extends_file_path
|
||||
from .validation import validate_healthcheck
|
||||
from .validation import validate_ipc_mode
|
||||
from .validation import validate_links
|
||||
from .validation import validate_network_mode
|
||||
from .validation import validate_pid_mode
|
||||
|
@ -734,6 +735,7 @@ def validate_service(service_config, service_names, config_file):
|
|||
|
||||
validate_cpu(service_config)
|
||||
validate_ulimits(service_config)
|
||||
validate_ipc_mode(service_config, service_names)
|
||||
validate_network_mode(service_config, service_names)
|
||||
validate_pid_mode(service_config, service_names)
|
||||
validate_depends_on(service_config, service_names)
|
||||
|
|
|
@ -36,6 +36,7 @@ def get_service_dependents(service_dict, services):
|
|||
name in get_service_names_from_volumes_from(service.get('volumes_from', [])) or
|
||||
name == get_service_name_from_network_mode(service.get('network_mode')) or
|
||||
name == get_service_name_from_network_mode(service.get('pid')) or
|
||||
name == get_service_name_from_network_mode(service.get('ipc')) or
|
||||
name in service.get('depends_on', []))
|
||||
]
|
||||
|
||||
|
|
|
@ -218,6 +218,21 @@ def validate_pid_mode(service_config, service_names):
|
|||
)
|
||||
|
||||
|
||||
def validate_ipc_mode(service_config, service_names):
|
||||
ipc_mode = service_config.config.get('ipc')
|
||||
if not ipc_mode:
|
||||
return
|
||||
|
||||
dependency = get_service_name_from_network_mode(ipc_mode)
|
||||
if not dependency:
|
||||
return
|
||||
if dependency not in service_names:
|
||||
raise ConfigurationError(
|
||||
"Service '{s.name}' uses the IPC namespace of service '{dep}' which "
|
||||
"is undefined.".format(s=service_config, dep=dependency)
|
||||
)
|
||||
|
||||
|
||||
def validate_links(service_config, service_names):
|
||||
for link in service_config.config.get('links', []):
|
||||
if link.split(':')[0] not in service_names:
|
||||
|
|
|
@ -26,14 +26,17 @@ from .network import get_networks
|
|||
from .network import ProjectNetworks
|
||||
from .progress_stream import read_status
|
||||
from .service import BuildAction
|
||||
from .service import ContainerIpcMode
|
||||
from .service import ContainerNetworkMode
|
||||
from .service import ContainerPidMode
|
||||
from .service import ConvergenceStrategy
|
||||
from .service import IpcMode
|
||||
from .service import NetworkMode
|
||||
from .service import NoSuchImageError
|
||||
from .service import parse_repository_tag
|
||||
from .service import PidMode
|
||||
from .service import Service
|
||||
from .service import ServiceIpcMode
|
||||
from .service import ServiceNetworkMode
|
||||
from .service import ServicePidMode
|
||||
from .utils import microseconds_from_time_nano
|
||||
|
@ -106,6 +109,7 @@ class Project(object):
|
|||
|
||||
service_dict.pop('networks', None)
|
||||
links = project.get_links(service_dict)
|
||||
ipc_mode = project.get_ipc_mode(service_dict)
|
||||
network_mode = project.get_network_mode(
|
||||
service_dict, list(service_networks.keys())
|
||||
)
|
||||
|
@ -147,6 +151,7 @@ class Project(object):
|
|||
volumes_from=volumes_from,
|
||||
secrets=secrets,
|
||||
pid_mode=pid_mode,
|
||||
ipc_mode=ipc_mode,
|
||||
platform=service_dict.pop('platform', None),
|
||||
default_platform=default_platform,
|
||||
extra_labels=extra_labels,
|
||||
|
@ -274,6 +279,27 @@ class Project(object):
|
|||
|
||||
return PidMode(pid_mode)
|
||||
|
||||
def get_ipc_mode(self, service_dict):
|
||||
ipc_mode = service_dict.pop('ipc', None)
|
||||
if not ipc_mode:
|
||||
return IpcMode(None)
|
||||
|
||||
service_name = get_service_name_from_network_mode(ipc_mode)
|
||||
if service_name:
|
||||
return ServiceIpcMode(self.get_service(service_name))
|
||||
|
||||
container_name = get_container_name_from_network_mode(ipc_mode)
|
||||
if container_name:
|
||||
try:
|
||||
return ContainerIpcMode(Container.from_id(self.client, container_name))
|
||||
except APIError:
|
||||
raise ConfigurationError(
|
||||
"Service '{name}' uses the IPC namespace of container '{dep}' which "
|
||||
"does not exist.".format(name=service_dict['name'], dep=container_name)
|
||||
)
|
||||
|
||||
return IpcMode(ipc_mode)
|
||||
|
||||
def get_service_scale(self, service_dict):
|
||||
# service.scale for v2 and deploy.replicas for v3
|
||||
scale = service_dict.get('scale', None)
|
||||
|
|
|
@ -176,6 +176,7 @@ class Service(object):
|
|||
networks=None,
|
||||
secrets=None,
|
||||
scale=1,
|
||||
ipc_mode=None,
|
||||
pid_mode=None,
|
||||
default_platform=None,
|
||||
extra_labels=None,
|
||||
|
@ -187,6 +188,7 @@ class Service(object):
|
|||
self.use_networking = use_networking
|
||||
self.links = links or []
|
||||
self.volumes_from = volumes_from or []
|
||||
self.ipc_mode = ipc_mode or IpcMode(None)
|
||||
self.network_mode = network_mode or NetworkMode(None)
|
||||
self.pid_mode = pid_mode or PidMode(None)
|
||||
self.networks = networks or {}
|
||||
|
@ -719,17 +721,20 @@ class Service(object):
|
|||
def get_dependency_names(self):
|
||||
net_name = self.network_mode.service_name
|
||||
pid_namespace = self.pid_mode.service_name
|
||||
ipc_namespace = self.ipc_mode.service_name
|
||||
return (
|
||||
self.get_linked_service_names() +
|
||||
self.get_volumes_from_names() +
|
||||
([net_name] if net_name else []) +
|
||||
([pid_namespace] if pid_namespace else []) +
|
||||
([ipc_namespace] if ipc_namespace else []) +
|
||||
list(self.options.get('depends_on', {}).keys())
|
||||
)
|
||||
|
||||
def get_dependency_configs(self):
|
||||
net_name = self.network_mode.service_name
|
||||
pid_namespace = self.pid_mode.service_name
|
||||
ipc_namespace = self.ipc_mode.service_name
|
||||
|
||||
configs = dict(
|
||||
[(name, None) for name in self.get_linked_service_names()]
|
||||
|
@ -739,6 +744,7 @@ class Service(object):
|
|||
))
|
||||
configs.update({net_name: None} if net_name else {})
|
||||
configs.update({pid_namespace: None} if pid_namespace else {})
|
||||
configs.update({ipc_namespace: None} if ipc_namespace else {})
|
||||
configs.update(self.options.get('depends_on', {}))
|
||||
for svc, config in self.options.get('depends_on', {}).items():
|
||||
if config['condition'] == CONDITION_STARTED:
|
||||
|
@ -1025,7 +1031,7 @@ class Service(object):
|
|||
read_only=options.get('read_only'),
|
||||
pid_mode=self.pid_mode.mode,
|
||||
security_opt=security_opt,
|
||||
ipc_mode=options.get('ipc'),
|
||||
ipc_mode=self.ipc_mode.mode,
|
||||
cgroup_parent=options.get('cgroup_parent'),
|
||||
cpu_quota=options.get('cpu_quota'),
|
||||
shm_size=options.get('shm_size'),
|
||||
|
@ -1329,6 +1335,46 @@ def short_id_alias_exists(container, network):
|
|||
return container.short_id in aliases
|
||||
|
||||
|
||||
class IpcMode(object):
|
||||
def __init__(self, mode):
|
||||
self._mode = mode
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
return self._mode
|
||||
|
||||
@property
|
||||
def service_name(self):
|
||||
return None
|
||||
|
||||
|
||||
class ServiceIpcMode(IpcMode):
|
||||
def __init__(self, service):
|
||||
self.service = service
|
||||
|
||||
@property
|
||||
def service_name(self):
|
||||
return self.service.name
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
containers = self.service.containers()
|
||||
if containers:
|
||||
return 'container:' + containers[0].id
|
||||
|
||||
log.warning(
|
||||
"Service %s is trying to use reuse the IPC namespace "
|
||||
"of another service that is not running." % (self.service_name)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
class ContainerIpcMode(IpcMode):
|
||||
def __init__(self, container):
|
||||
self.container = container
|
||||
self._mode = 'container:{}'.format(container.id)
|
||||
|
||||
|
||||
class PidMode(object):
|
||||
def __init__(self, mode):
|
||||
self._mode = mode
|
||||
|
|
|
@ -1681,6 +1681,31 @@ services:
|
|||
host_mode_container = self.project.get_service('host').containers()[0]
|
||||
assert host_mode_container.get('HostConfig.PidMode') == 'host'
|
||||
|
||||
@no_cluster('Container IPC mode does not work across clusters')
|
||||
def test_up_with_ipc_mode(self):
|
||||
c = self.client.create_container(
|
||||
'busybox', 'top', name='composetest_ipc_mode_container',
|
||||
host_config={}
|
||||
)
|
||||
self.addCleanup(self.client.remove_container, c, force=True)
|
||||
self.client.start(c)
|
||||
container_mode_source = 'container:{}'.format(c['Id'])
|
||||
|
||||
self.base_dir = 'tests/fixtures/ipc-mode'
|
||||
|
||||
self.dispatch(['up', '-d'], None)
|
||||
|
||||
service_mode_source = 'container:{}'.format(
|
||||
self.project.get_service('shareable').containers()[0].id)
|
||||
service_mode_container = self.project.get_service('service').containers()[0]
|
||||
assert service_mode_container.get('HostConfig.IpcMode') == service_mode_source
|
||||
|
||||
container_mode_container = self.project.get_service('container').containers()[0]
|
||||
assert container_mode_container.get('HostConfig.IpcMode') == container_mode_source
|
||||
|
||||
shareable_mode_container = self.project.get_service('shareable').containers()[0]
|
||||
assert shareable_mode_container.get('HostConfig.IpcMode') == 'shareable'
|
||||
|
||||
def test_exec_without_tty(self):
|
||||
self.base_dir = 'tests/fixtures/links-composefile'
|
||||
self.dispatch(['up', '-d', 'console'])
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
version: "2.4"
|
||||
|
||||
services:
|
||||
service:
|
||||
image: busybox
|
||||
command: top
|
||||
ipc: "service:shareable"
|
||||
|
||||
container:
|
||||
image: busybox
|
||||
command: top
|
||||
ipc: "container:composetest_ipc_mode_container"
|
||||
|
||||
shareable:
|
||||
image: busybox
|
||||
command: top
|
||||
ipc: shareable
|
|
@ -38,6 +38,7 @@ from compose.project import Project
|
|||
from compose.service import BuildAction
|
||||
from compose.service import ConvergencePlan
|
||||
from compose.service import ConvergenceStrategy
|
||||
from compose.service import IpcMode
|
||||
from compose.service import NetworkMode
|
||||
from compose.service import PidMode
|
||||
from compose.service import Service
|
||||
|
@ -1480,6 +1481,17 @@ class ServiceTest(DockerClientTestCase):
|
|||
container = create_and_start_container(service)
|
||||
assert container.get('HostConfig.PidMode') == 'host'
|
||||
|
||||
def test_ipc_mode_none_defined(self):
|
||||
service = self.create_service('web', ipc_mode=None)
|
||||
container = create_and_start_container(service)
|
||||
print(container.get('HostConfig.IpcMode'))
|
||||
assert container.get('HostConfig.IpcMode') == 'shareable'
|
||||
|
||||
def test_ipc_mode_host(self):
|
||||
service = self.create_service('web', ipc_mode=IpcMode('host'))
|
||||
container = create_and_start_container(service)
|
||||
assert container.get('HostConfig.IpcMode') == 'host'
|
||||
|
||||
@v2_1_only()
|
||||
def test_userns_mode_none_defined(self):
|
||||
service = self.create_service('web', userns_mode=None)
|
||||
|
|
Loading…
Reference in New Issue