Add support for service:name pid config

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2017-06-15 17:01:41 -07:00 committed by Joffrey F
parent a891fc1d9a
commit 41976b0f7f
8 changed files with 137 additions and 3 deletions

View File

@ -44,6 +44,7 @@ from .validation import validate_depends_on
from .validation import validate_extends_file_path
from .validation import validate_links
from .validation import validate_network_mode
from .validation import validate_pid_mode
from .validation import validate_service_constraints
from .validation import validate_top_level_object
from .validation import validate_ulimits
@ -667,6 +668,7 @@ def validate_service(service_config, service_names, config_file):
validate_cpu(service_config)
validate_ulimits(service_config)
validate_network_mode(service_config, service_names)
validate_pid_mode(service_config, service_names)
validate_depends_on(service_config, service_names)
validate_links(service_config, service_names)

View File

@ -38,6 +38,7 @@ def get_service_dependents(service_dict, services):
if (name in get_service_names(service.get('links', [])) 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('pid')) or
name in service.get('depends_on', []))
]

View File

@ -172,6 +172,21 @@ def validate_network_mode(service_config, service_names):
"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):
for link in service_config.config.get('links', []):
if link.split(':')[0] not in service_names:

View File

@ -24,10 +24,13 @@ from .network import get_networks
from .network import ProjectNetworks
from .service import BuildAction
from .service import ContainerNetworkMode
from .service import ContainerPidMode
from .service import ConvergenceStrategy
from .service import NetworkMode
from .service import PidMode
from .service import Service
from .service import ServiceNetworkMode
from .service import ServicePidMode
from .utils import microseconds_from_time_nano
from .volume import ProjectVolumes
@ -97,6 +100,7 @@ class Project(object):
network_mode = project.get_network_mode(
service_dict, list(service_networks.keys())
)
pid_mode = project.get_pid_mode(service_dict)
volumes_from = get_volumes_from(project, service_dict)
if config_data.version != V1:
@ -121,6 +125,7 @@ class Project(object):
network_mode=network_mode,
volumes_from=volumes_from,
secrets=secrets,
pid_mode=pid_mode,
**service_dict)
)
@ -224,6 +229,27 @@ class Project(object):
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):
containers = []

View File

@ -157,6 +157,7 @@ class Service(object):
networks=None,
secrets=None,
scale=None,
pid_mode=None,
**options
):
self.name = name
@ -166,6 +167,7 @@ class Service(object):
self.links = links or []
self.volumes_from = volumes_from or []
self.network_mode = network_mode or NetworkMode(None)
self.pid_mode = pid_mode or PidMode(None)
self.networks = networks or {}
self.secrets = secrets or []
self.scale_num = scale or 1
@ -607,15 +609,19 @@ class Service(object):
def get_dependency_names(self):
net_name = self.network_mode.service_name
pid_namespace = self.pid_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 []) +
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
configs = dict(
[(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()]
))
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', {}))
for svc, config in self.options.get('depends_on', {}).items():
if config['condition'] == CONDITION_STARTED:
@ -833,7 +840,7 @@ class Service(object):
log_config=log_config,
extra_hosts=options.get('extra_hosts'),
read_only=options.get('read_only'),
pid_mode=options.get('pid'),
pid_mode=self.pid_mode.mode,
security_opt=options.get('security_opt'),
ipc_mode=options.get('ipc'),
cgroup_parent=options.get('cgroup_parent'),
@ -1056,6 +1063,46 @@ def short_id_alias_exists(container, network):
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):
"""A `standard` network mode (ex: host, bridge)"""

View File

@ -1183,6 +1183,31 @@ class CLITestCase(DockerClientTestCase):
proc.wait()
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):
self.base_dir = 'tests/fixtures/links-composefile'
self.dispatch(['up', '-d', 'console'])

View 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

View File

@ -36,6 +36,7 @@ from compose.project import OneOffFilter
from compose.service import ConvergencePlan
from compose.service import ConvergenceStrategy
from compose.service import NetworkMode
from compose.service import PidMode
from compose.service import Service
from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_2_only
@ -968,12 +969,12 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(container.get('HostConfig.NetworkMode'), 'host')
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)
self.assertEqual(container.get('HostConfig.PidMode'), '')
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)
self.assertEqual(container.get('HostConfig.PidMode'), 'host')