From 227584b8640be269f60975d7c7f361e856c9e9f6 Mon Sep 17 00:00:00 2001 From: Frank Sachsenheim Date: Sat, 25 Jul 2015 22:20:58 +0200 Subject: [PATCH] Adds pause and unpause-commands Signed-off-by: Frank Sachsenheim --- compose/cli/main.py | 16 +++++++++++++ compose/container.py | 12 ++++++++++ compose/project.py | 8 +++++++ compose/service.py | 16 +++++++++++-- contrib/completion/bash/docker-compose | 31 ++++++++++++++++++++++++++ docs/reference/docker-compose.md | 2 ++ docs/reference/pause.md | 18 +++++++++++++++ docs/reference/unpause.md | 18 +++++++++++++++ tests/integration/cli_test.py | 11 +++++++++ tests/integration/project_test.py | 19 ++++++++++++++-- 10 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 docs/reference/pause.md create mode 100644 docs/reference/unpause.md diff --git a/compose/cli/main.py b/compose/cli/main.py index 6c2a8edb6..df0dfe9f3 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -172,6 +172,14 @@ class TopLevelCommand(Command): print("Attaching to", list_containers(containers)) LogPrinter(containers, attach_params={'logs': True}, monochrome=monochrome).run() + def pause(self, project, options): + """ + Pause services. + + Usage: pause [SERVICE...] + """ + project.pause(service_names=options['SERVICE']) + def port(self, project, options): """ Print the public port for a port binding. @@ -444,6 +452,14 @@ class TopLevelCommand(Command): timeout = int(options.get('--timeout') or DEFAULT_TIMEOUT) project.restart(service_names=options['SERVICE'], timeout=timeout) + def unpause(self, project, options): + """ + Unpause services. + + Usage: unpause [SERVICE...] + """ + project.unpause(service_names=options['SERVICE']) + def up(self, project, options): """ Builds, (re)creates, starts, and attaches to containers for a service. diff --git a/compose/container.py b/compose/container.py index 40aea98a4..37ed1fe59 100644 --- a/compose/container.py +++ b/compose/container.py @@ -100,6 +100,8 @@ class Container(object): @property def human_readable_state(self): + if self.is_paused: + return 'Paused' if self.is_running: return 'Ghost' if self.get('State.Ghost') else 'Up' else: @@ -119,6 +121,10 @@ class Container(object): def is_running(self): return self.get('State.Running') + @property + def is_paused(self): + return self.get('State.Paused') + def get(self, key): """Return a value from the container or None if the value is not set. @@ -142,6 +148,12 @@ class Container(object): def stop(self, **options): return self.client.stop(self.id, **options) + def pause(self, **options): + return self.client.pause(self.id, **options) + + def unpause(self, **options): + return self.client.unpause(self.id, **options) + def kill(self, **options): return self.client.kill(self.id, **options) diff --git a/compose/project.py b/compose/project.py index 6d86a4a87..276afb543 100644 --- a/compose/project.py +++ b/compose/project.py @@ -205,6 +205,14 @@ class Project(object): msg="Stopping" ) + def pause(self, service_names=None, **options): + for service in reversed(self.get_services(service_names)): + service.pause(**options) + + def unpause(self, service_names=None, **options): + for service in self.get_services(service_names): + service.unpause(**options) + def kill(self, service_names=None, **options): parallel_execute( objects=self.containers(service_names), diff --git a/compose/service.py b/compose/service.py index e49acf0c6..28d289ffa 100644 --- a/compose/service.py +++ b/compose/service.py @@ -96,12 +96,14 @@ class Service(object): self.net = net or None self.options = options - def containers(self, stopped=False, one_off=False): + def containers(self, stopped=False, one_off=False, filters={}): + filters.update({'label': self.labels(one_off=one_off)}) + containers = filter(None, [ Container.from_ps(self.client, container) for container in self.client.containers( all=stopped, - filters={'label': self.labels(one_off=one_off)})]) + filters=filters)]) if not containers: check_for_legacy_containers( @@ -132,6 +134,16 @@ class Service(object): log.info("Stopping %s..." % c.name) c.stop(**options) + def pause(self, **options): + for c in self.containers(filters={'status': 'running'}): + log.info("Pausing %s..." % c.name) + c.pause(**options) + + def unpause(self, **options): + for c in self.containers(filters={'status': 'paused'}): + log.info("Unpausing %s..." % c.name) + c.unpause() + def kill(self, **options): for c in self.containers(): log.info("Killing %s..." % c.name) diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index 66eb6c8bf..5692f0e4b 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -68,6 +68,11 @@ __docker_compose_services_with() { COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") ) } +# The services for which at least one paused container exists +__docker_compose_services_paused() { + __docker_compose_services_with '.State.Paused' +} + # The services for which at least one running container exists __docker_compose_services_running() { __docker_compose_services_with '.State.Running' @@ -158,6 +163,18 @@ _docker_compose_migrate_to_labels() { } +_docker_compose_pause() { + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + __docker_compose_services_running + ;; + esac +} + + _docker_compose_port() { case "$prev" in --protocol) @@ -306,6 +323,18 @@ _docker_compose_stop() { } +_docker_compose_unpause() { + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + __docker_compose_services_paused + ;; + esac +} + + _docker_compose_up() { case "$prev" in -t | --timeout) @@ -343,6 +372,7 @@ _docker_compose() { kill logs migrate-to-labels + pause port ps pull @@ -352,6 +382,7 @@ _docker_compose() { scale start stop + unpause up version ) diff --git a/docs/reference/docker-compose.md b/docs/reference/docker-compose.md index e252da0a7..46afba13c 100644 --- a/docs/reference/docker-compose.md +++ b/docs/reference/docker-compose.md @@ -28,6 +28,7 @@ Commands: help Get help on a command kill Kill containers logs View output from containers + pause Pause services port Print the public port for a port binding ps List containers pull Pulls service images @@ -37,6 +38,7 @@ Commands: scale Set number of containers for a service start Start services stop Stop services + unpause Unpause services up Create and start containers migrate-to-labels Recreate containers to add labels ``` diff --git a/docs/reference/pause.md b/docs/reference/pause.md new file mode 100644 index 000000000..a0ffab035 --- /dev/null +++ b/docs/reference/pause.md @@ -0,0 +1,18 @@ + + +# pause + +``` +Usage: pause [SERVICE...] +``` + +Pauses running containers of a service. They can be unpaused with `docker-compose unpause`. diff --git a/docs/reference/unpause.md b/docs/reference/unpause.md new file mode 100644 index 000000000..6434b09cc --- /dev/null +++ b/docs/reference/unpause.md @@ -0,0 +1,18 @@ + + +# pause + +``` +Usage: unpause [SERVICE...] +``` + +Unpauses paused containers of a service. diff --git a/tests/integration/cli_test.py b/tests/integration/cli_test.py index a02e072fd..38f8ee464 100644 --- a/tests/integration/cli_test.py +++ b/tests/integration/cli_test.py @@ -415,6 +415,17 @@ class CLITestCase(DockerClientTestCase): self.assertEqual(len(service.containers(stopped=True)), 1) self.assertFalse(service.containers(stopped=True)[0].is_running) + def test_pause_unpause(self): + self.command.dispatch(['up', '-d'], None) + service = self.project.get_service('simple') + self.assertFalse(service.containers()[0].is_paused) + + self.command.dispatch(['pause'], None) + self.assertTrue(service.containers()[0].is_paused) + + self.command.dispatch(['unpause'], None) + self.assertFalse(service.containers()[0].is_paused) + def test_logs_invalid_service_name(self): with self.assertRaises(NoSuchService): self.command.dispatch(['logs', 'madeupname'], None) diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 9788c1861..ad2fe4fea 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -140,7 +140,7 @@ class ProjectTest(DockerClientTestCase): web = project.get_service('web') self.assertEqual(web._get_net(), 'container:' + net_container.id) - def test_start_stop_kill_remove(self): + def test_start_pause_unpause_stop_kill_remove(self): web = self.create_service('web') db = self.create_service('db') project = Project('composetest', [web, db], self.client) @@ -158,7 +158,22 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(set(c.name for c in project.containers()), set([web_container_1.name, web_container_2.name])) project.start() - self.assertEqual(set(c.name for c in project.containers()), set([web_container_1.name, web_container_2.name, db_container.name])) + self.assertEqual(set(c.name for c in project.containers()), + set([web_container_1.name, web_container_2.name, db_container.name])) + + project.pause(service_names=['web']) + self.assertEqual(set([c.name for c in project.containers() if c.is_paused]), + set([web_container_1.name, web_container_2.name])) + + project.pause() + self.assertEqual(set([c.name for c in project.containers() if c.is_paused]), + set([web_container_1.name, web_container_2.name, db_container.name])) + + project.unpause(service_names=['db']) + self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 2) + + project.unpause() + self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 0) project.stop(service_names=['web'], timeout=1) self.assertEqual(set(c.name for c in project.containers()), set([db_container.name]))