mirror of
https://github.com/docker/compose.git
synced 2025-07-20 04:04:29 +02:00
Add support for service:name pid config
Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
90e5d3447e
commit
259b96748c
@ -44,6 +44,7 @@ from .validation import validate_depends_on
|
|||||||
from .validation import validate_extends_file_path
|
from .validation import validate_extends_file_path
|
||||||
from .validation import validate_links
|
from .validation import validate_links
|
||||||
from .validation import validate_network_mode
|
from .validation import validate_network_mode
|
||||||
|
from .validation import validate_pid_mode
|
||||||
from .validation import validate_service_constraints
|
from .validation import validate_service_constraints
|
||||||
from .validation import validate_top_level_object
|
from .validation import validate_top_level_object
|
||||||
from .validation import validate_ulimits
|
from .validation import validate_ulimits
|
||||||
@ -667,6 +668,7 @@ def validate_service(service_config, service_names, config_file):
|
|||||||
validate_cpu(service_config)
|
validate_cpu(service_config)
|
||||||
validate_ulimits(service_config)
|
validate_ulimits(service_config)
|
||||||
validate_network_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)
|
validate_depends_on(service_config, service_names)
|
||||||
validate_links(service_config, service_names)
|
validate_links(service_config, service_names)
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ def get_service_dependents(service_dict, services):
|
|||||||
if (name in get_service_names(service.get('links', [])) or
|
if (name in get_service_names(service.get('links', [])) or
|
||||||
name in get_service_names_from_volumes_from(service.get('volumes_from', [])) or
|
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('network_mode')) or
|
||||||
|
name == get_service_name_from_network_mode(service.get('pid')) or
|
||||||
name in service.get('depends_on', []))
|
name in service.get('depends_on', []))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -172,6 +172,21 @@ def validate_network_mode(service_config, service_names):
|
|||||||
"is undefined.".format(s=service_config, dep=dependency))
|
"is undefined.".format(s=service_config, dep=dependency))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_pid_mode(service_config, service_names):
|
||||||
|
pid_mode = service_config.config.get('pid')
|
||||||
|
if not pid_mode:
|
||||||
|
return
|
||||||
|
|
||||||
|
dependency = get_service_name_from_network_mode(pid_mode)
|
||||||
|
if not dependency:
|
||||||
|
return
|
||||||
|
if dependency not in service_names:
|
||||||
|
raise ConfigurationError(
|
||||||
|
"Service '{s.name}' uses the PID namespace of service '{dep}' which "
|
||||||
|
"is undefined.".format(s=service_config, dep=dependency)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_links(service_config, service_names):
|
def validate_links(service_config, service_names):
|
||||||
for link in service_config.config.get('links', []):
|
for link in service_config.config.get('links', []):
|
||||||
if link.split(':')[0] not in service_names:
|
if link.split(':')[0] not in service_names:
|
||||||
|
@ -24,10 +24,13 @@ from .network import get_networks
|
|||||||
from .network import ProjectNetworks
|
from .network import ProjectNetworks
|
||||||
from .service import BuildAction
|
from .service import BuildAction
|
||||||
from .service import ContainerNetworkMode
|
from .service import ContainerNetworkMode
|
||||||
|
from .service import ContainerPidMode
|
||||||
from .service import ConvergenceStrategy
|
from .service import ConvergenceStrategy
|
||||||
from .service import NetworkMode
|
from .service import NetworkMode
|
||||||
|
from .service import PidMode
|
||||||
from .service import Service
|
from .service import Service
|
||||||
from .service import ServiceNetworkMode
|
from .service import ServiceNetworkMode
|
||||||
|
from .service import ServicePidMode
|
||||||
from .utils import microseconds_from_time_nano
|
from .utils import microseconds_from_time_nano
|
||||||
from .volume import ProjectVolumes
|
from .volume import ProjectVolumes
|
||||||
|
|
||||||
@ -97,6 +100,7 @@ class Project(object):
|
|||||||
network_mode = project.get_network_mode(
|
network_mode = project.get_network_mode(
|
||||||
service_dict, list(service_networks.keys())
|
service_dict, list(service_networks.keys())
|
||||||
)
|
)
|
||||||
|
pid_mode = project.get_pid_mode(service_dict)
|
||||||
volumes_from = get_volumes_from(project, service_dict)
|
volumes_from = get_volumes_from(project, service_dict)
|
||||||
|
|
||||||
if config_data.version != V1:
|
if config_data.version != V1:
|
||||||
@ -121,6 +125,7 @@ class Project(object):
|
|||||||
network_mode=network_mode,
|
network_mode=network_mode,
|
||||||
volumes_from=volumes_from,
|
volumes_from=volumes_from,
|
||||||
secrets=secrets,
|
secrets=secrets,
|
||||||
|
pid_mode=pid_mode,
|
||||||
**service_dict)
|
**service_dict)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -224,6 +229,27 @@ class Project(object):
|
|||||||
|
|
||||||
return NetworkMode(network_mode)
|
return NetworkMode(network_mode)
|
||||||
|
|
||||||
|
def get_pid_mode(self, service_dict):
|
||||||
|
pid_mode = service_dict.pop('pid', None)
|
||||||
|
if not pid_mode:
|
||||||
|
return PidMode(None)
|
||||||
|
|
||||||
|
service_name = get_service_name_from_network_mode(pid_mode)
|
||||||
|
if service_name:
|
||||||
|
return ServicePidMode(self.get_service(service_name))
|
||||||
|
|
||||||
|
container_name = get_container_name_from_network_mode(pid_mode)
|
||||||
|
if container_name:
|
||||||
|
try:
|
||||||
|
return ContainerPidMode(Container.from_id(self.client, container_name))
|
||||||
|
except APIError:
|
||||||
|
raise ConfigurationError(
|
||||||
|
"Service '{name}' uses the PID namespace of container '{dep}' which "
|
||||||
|
"does not exist.".format(name=service_dict['name'], dep=container_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
return PidMode(pid_mode)
|
||||||
|
|
||||||
def start(self, service_names=None, **options):
|
def start(self, service_names=None, **options):
|
||||||
containers = []
|
containers = []
|
||||||
|
|
||||||
|
@ -157,6 +157,7 @@ class Service(object):
|
|||||||
networks=None,
|
networks=None,
|
||||||
secrets=None,
|
secrets=None,
|
||||||
scale=None,
|
scale=None,
|
||||||
|
pid_mode=None,
|
||||||
**options
|
**options
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -166,6 +167,7 @@ class Service(object):
|
|||||||
self.links = links or []
|
self.links = links or []
|
||||||
self.volumes_from = volumes_from or []
|
self.volumes_from = volumes_from or []
|
||||||
self.network_mode = network_mode or NetworkMode(None)
|
self.network_mode = network_mode or NetworkMode(None)
|
||||||
|
self.pid_mode = pid_mode or PidMode(None)
|
||||||
self.networks = networks or {}
|
self.networks = networks or {}
|
||||||
self.secrets = secrets or []
|
self.secrets = secrets or []
|
||||||
self.scale_num = scale or 1
|
self.scale_num = scale or 1
|
||||||
@ -607,15 +609,19 @@ class Service(object):
|
|||||||
|
|
||||||
def get_dependency_names(self):
|
def get_dependency_names(self):
|
||||||
net_name = self.network_mode.service_name
|
net_name = self.network_mode.service_name
|
||||||
|
pid_namespace = self.pid_mode.service_name
|
||||||
return (
|
return (
|
||||||
self.get_linked_service_names() +
|
self.get_linked_service_names() +
|
||||||
self.get_volumes_from_names() +
|
self.get_volumes_from_names() +
|
||||||
([net_name] if net_name else []) +
|
([net_name] if net_name else []) +
|
||||||
|
([pid_namespace] if pid_namespace else []) +
|
||||||
list(self.options.get('depends_on', {}).keys())
|
list(self.options.get('depends_on', {}).keys())
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_dependency_configs(self):
|
def get_dependency_configs(self):
|
||||||
net_name = self.network_mode.service_name
|
net_name = self.network_mode.service_name
|
||||||
|
pid_namespace = self.pid_mode.service_name
|
||||||
|
|
||||||
configs = dict(
|
configs = dict(
|
||||||
[(name, None) for name in self.get_linked_service_names()]
|
[(name, None) for name in self.get_linked_service_names()]
|
||||||
)
|
)
|
||||||
@ -623,6 +629,7 @@ class Service(object):
|
|||||||
[(name, None) for name in self.get_volumes_from_names()]
|
[(name, None) for name in self.get_volumes_from_names()]
|
||||||
))
|
))
|
||||||
configs.update({net_name: None} if net_name else {})
|
configs.update({net_name: None} if net_name else {})
|
||||||
|
configs.update({pid_namespace: None} if pid_namespace else {})
|
||||||
configs.update(self.options.get('depends_on', {}))
|
configs.update(self.options.get('depends_on', {}))
|
||||||
for svc, config in self.options.get('depends_on', {}).items():
|
for svc, config in self.options.get('depends_on', {}).items():
|
||||||
if config['condition'] == CONDITION_STARTED:
|
if config['condition'] == CONDITION_STARTED:
|
||||||
@ -833,7 +840,7 @@ class Service(object):
|
|||||||
log_config=log_config,
|
log_config=log_config,
|
||||||
extra_hosts=options.get('extra_hosts'),
|
extra_hosts=options.get('extra_hosts'),
|
||||||
read_only=options.get('read_only'),
|
read_only=options.get('read_only'),
|
||||||
pid_mode=options.get('pid'),
|
pid_mode=self.pid_mode.mode,
|
||||||
security_opt=options.get('security_opt'),
|
security_opt=options.get('security_opt'),
|
||||||
ipc_mode=options.get('ipc'),
|
ipc_mode=options.get('ipc'),
|
||||||
cgroup_parent=options.get('cgroup_parent'),
|
cgroup_parent=options.get('cgroup_parent'),
|
||||||
@ -1056,6 +1063,46 @@ def short_id_alias_exists(container, network):
|
|||||||
return container.short_id in aliases
|
return container.short_id in aliases
|
||||||
|
|
||||||
|
|
||||||
|
class PidMode(object):
|
||||||
|
def __init__(self, mode):
|
||||||
|
self._mode = mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self):
|
||||||
|
return self._mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_name(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ServicePidMode(PidMode):
|
||||||
|
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.warn(
|
||||||
|
"Service %s is trying to use reuse the PID namespace "
|
||||||
|
"of another service that is not running." % (self.service_name)
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerPidMode(PidMode):
|
||||||
|
def __init__(self, container):
|
||||||
|
self.container = container
|
||||||
|
self._mode = 'container:{}'.format(container.id)
|
||||||
|
|
||||||
|
|
||||||
class NetworkMode(object):
|
class NetworkMode(object):
|
||||||
"""A `standard` network mode (ex: host, bridge)"""
|
"""A `standard` network mode (ex: host, bridge)"""
|
||||||
|
|
||||||
|
@ -1183,6 +1183,31 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
proc.wait()
|
proc.wait()
|
||||||
self.assertEqual(proc.returncode, 1)
|
self.assertEqual(proc.returncode, 1)
|
||||||
|
|
||||||
|
@v2_only()
|
||||||
|
def test_up_with_pid_mode(self):
|
||||||
|
c = self.client.create_container(
|
||||||
|
'busybox', 'top', name='composetest_pid_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/pid-mode'
|
||||||
|
|
||||||
|
self.dispatch(['up', '-d'], None)
|
||||||
|
|
||||||
|
service_mode_source = 'container:{}'.format(
|
||||||
|
self.project.get_service('container').containers()[0].id)
|
||||||
|
service_mode_container = self.project.get_service('service').containers()[0]
|
||||||
|
assert service_mode_container.get('HostConfig.PidMode') == service_mode_source
|
||||||
|
|
||||||
|
container_mode_container = self.project.get_service('container').containers()[0]
|
||||||
|
assert container_mode_container.get('HostConfig.PidMode') == container_mode_source
|
||||||
|
|
||||||
|
host_mode_container = self.project.get_service('host').containers()[0]
|
||||||
|
assert host_mode_container.get('HostConfig.PidMode') == 'host'
|
||||||
|
|
||||||
def test_exec_without_tty(self):
|
def test_exec_without_tty(self):
|
||||||
self.base_dir = 'tests/fixtures/links-composefile'
|
self.base_dir = 'tests/fixtures/links-composefile'
|
||||||
self.dispatch(['up', '-d', 'console'])
|
self.dispatch(['up', '-d', 'console'])
|
||||||
|
17
tests/fixtures/pid-mode/docker-compose.yml
vendored
Normal file
17
tests/fixtures/pid-mode/docker-compose.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
version: "2.2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
service:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
pid: "service:container"
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
pid: "container:composetest_pid_mode_container"
|
||||||
|
|
||||||
|
host:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
pid: host
|
@ -36,6 +36,7 @@ from compose.project import OneOffFilter
|
|||||||
from compose.service import ConvergencePlan
|
from compose.service import ConvergencePlan
|
||||||
from compose.service import ConvergenceStrategy
|
from compose.service import ConvergenceStrategy
|
||||||
from compose.service import NetworkMode
|
from compose.service import NetworkMode
|
||||||
|
from compose.service import PidMode
|
||||||
from compose.service import Service
|
from compose.service import Service
|
||||||
from tests.integration.testcases import v2_1_only
|
from tests.integration.testcases import v2_1_only
|
||||||
from tests.integration.testcases import v2_2_only
|
from tests.integration.testcases import v2_2_only
|
||||||
@ -968,12 +969,12 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
self.assertEqual(container.get('HostConfig.NetworkMode'), 'host')
|
self.assertEqual(container.get('HostConfig.NetworkMode'), 'host')
|
||||||
|
|
||||||
def test_pid_mode_none_defined(self):
|
def test_pid_mode_none_defined(self):
|
||||||
service = self.create_service('web', pid=None)
|
service = self.create_service('web', pid_mode=None)
|
||||||
container = create_and_start_container(service)
|
container = create_and_start_container(service)
|
||||||
self.assertEqual(container.get('HostConfig.PidMode'), '')
|
self.assertEqual(container.get('HostConfig.PidMode'), '')
|
||||||
|
|
||||||
def test_pid_mode_host(self):
|
def test_pid_mode_host(self):
|
||||||
service = self.create_service('web', pid='host')
|
service = self.create_service('web', pid_mode=PidMode('host'))
|
||||||
container = create_and_start_container(service)
|
container = create_and_start_container(service)
|
||||||
self.assertEqual(container.get('HostConfig.PidMode'), 'host')
|
self.assertEqual(container.get('HostConfig.PidMode'), 'host')
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user