Merge pull request #7417 from EricHripko/ipc-service

[Compose Spec] Implement service mode for ipc
This commit is contained in:
Anca Iordache 2020-08-10 10:14:43 +02:00 committed by GitHub
commit b23eb2deab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 1 deletions

View File

@ -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)

View File

@ -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', []))
]

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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'])

View File

@ -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

View File

@ -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)