Browse Source

Set "VolumesFrom" when starting containers

This is necessary when working with Docker 0.10.0 and up. Fortunately,
we can set it both when creating and starting, and retain compatibility
with 0.8.x and 0.9.x.

recreate_containers() is now responsible for starting containers, as
well as creating them. This greatly simplifies usage of the Service
class.
Aanand Prasad 11 years ago
parent
commit
80991f1521
6 changed files with 46 additions and 70 deletions
  1. 1 2
      fig/cli/main.py
  2. 7 2
      fig/packages/docker/client.py
  3. 5 24
      fig/project.py
  4. 14 11
      fig/service.py
  5. 12 24
      tests/project_test.py
  6. 7 7
      tests/service_test.py

+ 1 - 2
fig/cli/main.py

@@ -301,10 +301,9 @@ class TopLevelCommand(Command):
         """
         """
         detached = options['-d']
         detached = options['-d']
 
 
-        new = self.project.up(service_names=options['SERVICE'])
+        to_attach = self.project.up(service_names=options['SERVICE'])
 
 
         if not detached:
         if not detached:
-            to_attach = [c for (s, c) in new]
             print("Attaching to", list_containers(to_attach))
             print("Attaching to", list_containers(to_attach))
             log_printer = LogPrinter(to_attach, attach_params={"logs": True})
             log_printer = LogPrinter(to_attach, attach_params={"logs": True})
 
 

+ 7 - 2
fig/packages/docker/client.py

@@ -698,8 +698,8 @@ class Client(requests.Session):
                                       params={'term': term}),
                                       params={'term': term}),
                             True)
                             True)
 
 
-    def start(self, container, binds=None, port_bindings=None, lxc_conf=None,
-              publish_all_ports=False, links=None, privileged=False):
+    def start(self, container, binds=None, volumes_from=None, port_bindings=None,
+              lxc_conf=None, publish_all_ports=False, links=None, privileged=False):
         if isinstance(container, dict):
         if isinstance(container, dict):
             container = container.get('Id')
             container = container.get('Id')
 
 
@@ -718,6 +718,11 @@ class Client(requests.Session):
             ]
             ]
             start_config['Binds'] = bind_pairs
             start_config['Binds'] = bind_pairs
 
 
+        if volumes_from and not isinstance(volumes_from, six.string_types):
+            volumes_from = ','.join(volumes_from)
+
+        start_config['VolumesFrom'] = volumes_from
+
         if port_bindings:
         if port_bindings:
             start_config['PortBindings'] = utils.convert_port_bindings(
             start_config['PortBindings'] = utils.convert_port_bindings(
                 port_bindings
                 port_bindings

+ 5 - 24
fig/project.py

@@ -105,23 +105,6 @@ class Project(object):
             unsorted = [self.get_service(name) for name in service_names]
             unsorted = [self.get_service(name) for name in service_names]
             return [s for s in self.services if s in unsorted]
             return [s for s in self.services if s in unsorted]
 
 
-    def recreate_containers(self, service_names=None):
-        """
-        For each service, create or recreate their containers.
-        Returns a tuple with two lists. The first is a list of
-        (service, old_container) tuples; the second is a list
-        of (service, new_container) tuples.
-        """
-        old = []
-        new = []
-
-        for service in self.get_services(service_names):
-            (s_old, s_new) = service.recreate_containers()
-            old += [(service, container) for container in s_old]
-            new += [(service, container) for container in s_new]
-
-        return (old, new)
-
     def start(self, service_names=None, **options):
     def start(self, service_names=None, **options):
         for service in self.get_services(service_names):
         for service in self.get_services(service_names):
             service.start(**options)
             service.start(**options)
@@ -142,15 +125,13 @@ class Project(object):
                 log.info('%s uses an image, skipping' % service.name)
                 log.info('%s uses an image, skipping' % service.name)
 
 
     def up(self, service_names=None):
     def up(self, service_names=None):
-        (old, new) = self.recreate_containers(service_names=service_names)
+        new_containers = []
 
 
-        for (service, container) in new:
-            service.start_container(container)
-
-        for (service, container) in old:
-            container.remove()
+        for service in self.get_services(service_names):
+            for (_, new) in service.recreate_containers():
+                new_containers.append(new)
 
 
-        return new
+        return new_containers
 
 
     def remove_stopped(self, service_names=None, **options):
     def remove_stopped(self, service_names=None, **options):
         for service in self.get_services(service_names):
         for service in self.get_services(service_names):

+ 14 - 11
fig/service.py

@@ -154,25 +154,24 @@ class Service(object):
 
 
     def recreate_containers(self, **override_options):
     def recreate_containers(self, **override_options):
         """
         """
-        If a container for this service doesn't exist, create one. If there are
-        any, stop them and create new ones. Does not remove the old containers.
+        If a container for this service doesn't exist, create and start one. If there are
+        any, stop them, create+start new ones, and remove the old containers.
         """
         """
         containers = self.containers(stopped=True)
         containers = self.containers(stopped=True)
 
 
         if len(containers) == 0:
         if len(containers) == 0:
             log.info("Creating %s..." % self.next_container_name())
             log.info("Creating %s..." % self.next_container_name())
-            return ([], [self.create_container(**override_options)])
+            container = self.create_container(**override_options)
+            self.start_container(container)
+            return [(None, container)]
         else:
         else:
-            old_containers = []
-            new_containers = []
+            tuples = []
 
 
             for c in containers:
             for c in containers:
                 log.info("Recreating %s..." % c.name)
                 log.info("Recreating %s..." % c.name)
-                (old_container, new_container) = self.recreate_container(c, **override_options)
-                old_containers.append(old_container)
-                new_containers.append(new_container)
+                tuples.append(self.recreate_container(c, **override_options))
 
 
-            return (old_containers, new_containers)
+            return tuples
 
 
     def recreate_container(self, container, **override_options):
     def recreate_container(self, container, **override_options):
         if container.is_running:
         if container.is_running:
@@ -185,17 +184,20 @@ class Service(object):
             entrypoint=['echo'],
             entrypoint=['echo'],
             command=[],
             command=[],
         )
         )
-        intermediate_container.start()
+        intermediate_container.start(volumes_from=container.id)
         intermediate_container.wait()
         intermediate_container.wait()
         container.remove()
         container.remove()
 
 
         options = dict(override_options)
         options = dict(override_options)
         options['volumes_from'] = intermediate_container.id
         options['volumes_from'] = intermediate_container.id
         new_container = self.create_container(**options)
         new_container = self.create_container(**options)
+        self.start_container(new_container, volumes_from=intermediate_container.id)
+
+        intermediate_container.remove()
 
 
         return (intermediate_container, new_container)
         return (intermediate_container, new_container)
 
 
-    def start_container(self, container=None, **override_options):
+    def start_container(self, container=None, volumes_from=None, **override_options):
         if container is None:
         if container is None:
             container = self.create_container(**override_options)
             container = self.create_container(**override_options)
 
 
@@ -228,6 +230,7 @@ class Service(object):
             links=self._get_links(link_to_self=override_options.get('one_off', False)),
             links=self._get_links(link_to_self=override_options.get('one_off', False)),
             port_bindings=port_bindings,
             port_bindings=port_bindings,
             binds=volume_bindings,
             binds=volume_bindings,
+            volumes_from=volumes_from,
             privileged=privileged,
             privileged=privileged,
         )
         )
         return container
         return container

+ 12 - 24
tests/project_test.py

@@ -63,29 +63,6 @@ class ProjectTest(DockerClientTestCase):
         project = Project('test', [web], self.client)
         project = Project('test', [web], self.client)
         self.assertEqual(project.get_service('web'), web)
         self.assertEqual(project.get_service('web'), web)
 
 
-    def test_recreate_containers(self):
-        web = self.create_service('web')
-        db = self.create_service('db')
-        project = Project('test', [web, db], self.client)
-
-        old_web_container = web.create_container()
-        self.assertEqual(len(web.containers(stopped=True)), 1)
-        self.assertEqual(len(db.containers(stopped=True)), 0)
-
-        (old, new) = project.recreate_containers()
-        self.assertEqual(len(old), 1)
-        self.assertEqual(old[0][0], web)
-        self.assertEqual(len(new), 2)
-        self.assertEqual(new[0][0], web)
-        self.assertEqual(new[1][0], db)
-
-        self.assertEqual(len(web.containers(stopped=True)), 1)
-        self.assertEqual(len(db.containers(stopped=True)), 1)
-
-        # remove intermediate containers
-        for (service, container) in old:
-            container.remove()
-
     def test_start_stop_kill_remove(self):
     def test_start_stop_kill_remove(self):
         web = self.create_service('web')
         web = self.create_service('web')
         db = self.create_service('db')
         db = self.create_service('db')
@@ -121,12 +98,23 @@ class ProjectTest(DockerClientTestCase):
 
 
     def test_project_up(self):
     def test_project_up(self):
         web = self.create_service('web')
         web = self.create_service('web')
-        db = self.create_service('db')
+        db = self.create_service('db', volumes=['/var/db'])
         project = Project('figtest', [web, db], self.client)
         project = Project('figtest', [web, db], self.client)
         project.start()
         project.start()
         self.assertEqual(len(project.containers()), 0)
         self.assertEqual(len(project.containers()), 0)
+
+        project.up(['db'])
+        self.assertEqual(len(project.containers()), 1)
+        old_db_id = project.containers()[0].id
+        db_volume_path = project.containers()[0].inspect()['Volumes']['/var/db']
+
         project.up()
         project.up()
         self.assertEqual(len(project.containers()), 2)
         self.assertEqual(len(project.containers()), 2)
+
+        db_container = [c for c in project.containers() if 'db' in c.name][0]
+        self.assertNotEqual(c.id, old_db_id)
+        self.assertEqual(c.inspect()['Volumes']['/var/db'], db_volume_path)
+
         project.kill()
         project.kill()
         project.remove_stopped()
         project.remove_stopped()
 
 

+ 7 - 7
tests/service_test.py

@@ -2,6 +2,7 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 from __future__ import absolute_import
 from fig import Service
 from fig import Service
 from fig.service import CannotBeScaledError, ConfigError
 from fig.service import CannotBeScaledError, ConfigError
+from fig.packages.docker.client import APIError
 from .testcases import DockerClientTestCase
 from .testcases import DockerClientTestCase
 
 
 
 
@@ -132,23 +133,22 @@ class ServiceTest(DockerClientTestCase):
         num_containers_before = len(self.client.containers(all=True))
         num_containers_before = len(self.client.containers(all=True))
 
 
         service.options['environment']['FOO'] = '2'
         service.options['environment']['FOO'] = '2'
-        (intermediate, new) = service.recreate_containers()
-        self.assertEqual(len(intermediate), 1)
-        self.assertEqual(len(new), 1)
+        tuples = service.recreate_containers()
+        self.assertEqual(len(tuples), 1)
 
 
-        new_container = new[0]
-        intermediate_container = intermediate[0]
+        intermediate_container = tuples[0][0]
+        new_container = tuples[0][1]
         self.assertEqual(intermediate_container.dictionary['Config']['Entrypoint'], ['echo'])
         self.assertEqual(intermediate_container.dictionary['Config']['Entrypoint'], ['echo'])
 
 
         self.assertEqual(new_container.dictionary['Config']['Entrypoint'], ['ps'])
         self.assertEqual(new_container.dictionary['Config']['Entrypoint'], ['ps'])
         self.assertEqual(new_container.dictionary['Config']['Cmd'], ['ax'])
         self.assertEqual(new_container.dictionary['Config']['Cmd'], ['ax'])
         self.assertIn('FOO=2', new_container.dictionary['Config']['Env'])
         self.assertIn('FOO=2', new_container.dictionary['Config']['Env'])
         self.assertEqual(new_container.name, 'figtest_db_1')
         self.assertEqual(new_container.name, 'figtest_db_1')
-        service.start_container(new_container)
         self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path)
         self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path)
 
 
-        self.assertEqual(len(self.client.containers(all=True)), num_containers_before + 1)
+        self.assertEqual(len(self.client.containers(all=True)), num_containers_before)
         self.assertNotEqual(old_container.id, new_container.id)
         self.assertNotEqual(old_container.id, new_container.id)
+        self.assertRaises(APIError, lambda: self.client.inspect_container(intermediate_container.id))
 
 
     def test_start_container_passes_through_options(self):
     def test_start_container_passes_through_options(self):
         db = self.create_service('db')
         db = self.create_service('db')