Browse Source

Merge pull request #1766 from funkyfuture/pause_unpause

Adds pause and unpause-commands
Aanand Prasad 10 years ago
parent
commit
60ff4549c9

+ 16 - 0
compose/cli/main.py

@@ -180,6 +180,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.
@@ -452,6 +460,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.

+ 12 - 0
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)
 

+ 8 - 0
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),

+ 17 - 4
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)
@@ -331,7 +343,7 @@ class Service(object):
         config_hash = None
 
         try:
-            config_hash = self.config_hash()
+            config_hash = self.config_hash
         except NoSuchImageError as e:
             log.debug(
                 'Service %s has diverged: %s',
@@ -456,6 +468,7 @@ class Service(object):
             else:
                 numbers.add(c.number)
 
+    @property
     def config_hash(self):
         return json_hash(self.config_dict())
 
@@ -573,7 +586,7 @@ class Service(object):
             container_options['name'] = self.get_container_name(number, one_off)
 
         if add_config_hash:
-            config_hash = self.config_hash()
+            config_hash = self.config_hash
             if 'labels' not in container_options:
                 container_options['labels'] = {}
             container_options['labels'][LABEL_CONFIG_HASH] = config_hash

+ 31 - 0
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
 	)

+ 2 - 0
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
 ```

+ 18 - 0
docs/reference/pause.md

@@ -0,0 +1,18 @@
+<!--[metadata]>
++++
+title = "pause"
+description = "Pauses running containers for a service."
+keywords = ["fig, composition, compose, docker, orchestration, cli, pause"]
+[menu.main]
+identifier="pause.compose"
+parent = "smn_compose_cli"
++++
+<![end-metadata]-->
+
+# pause
+
+```
+Usage: pause [SERVICE...]
+```
+
+Pauses running containers of a service. They can be unpaused with `docker-compose unpause`.

+ 18 - 0
docs/reference/unpause.md

@@ -0,0 +1,18 @@
+<!--[metadata]>
++++
+title = "unpause"
+description = "Unpauses paused containers for a service."
+keywords = ["fig, composition, compose, docker, orchestration, cli, unpause"]
+[menu.main]
+identifier="unpause.compose"
+parent = "smn_compose_cli"
++++
+<![end-metadata]-->
+
+# pause
+
+```
+Usage: unpause [SERVICE...]
+```
+
+Unpauses paused containers of a service.

+ 11 - 0
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)

+ 17 - 2
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]))