1
0
Эх сурвалжийг харах

Add support for service:name pid config

Signed-off-by: Joffrey F <[email protected]>
Joffrey F 8 жил өмнө
parent
commit
41976b0f7f

+ 2 - 0
compose/config/config.py

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

+ 1 - 0
compose/config/sort_services.py

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

+ 15 - 0
compose/config/validation.py

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

+ 26 - 0
compose/project.py

@@ -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 = []
 

+ 48 - 1
compose/service.py

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

+ 25 - 0
tests/acceptance/cli_test.py

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

+ 17 - 0
tests/fixtures/pid-mode/docker-compose.yml

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

+ 3 - 2
tests/integration/service_test.py

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