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.
This commit is contained in:
Aanand Prasad 2014-04-23 15:46:26 +01:00
parent f8ee52ca2a
commit 80991f1521
6 changed files with 46 additions and 70 deletions

View File

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

View File

@ -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, def start(self, container, binds=None, volumes_from=None, port_bindings=None,
publish_all_ports=False, links=None, privileged=False): 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

View File

@ -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: for service in self.get_services(service_names):
service.start_container(container) for (_, new) in service.recreate_containers():
new_containers.append(new)
for (service, container) in old: return new_containers
container.remove()
return new
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):

View File

@ -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 If a container for this service doesn't exist, create and start one. If there are
any, stop them and create new ones. Does not remove the old containers. 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 = [] tuples = []
new_containers = []
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) tuples.append(self.recreate_container(c, **override_options))
old_containers.append(old_container)
new_containers.append(new_container)
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

View File

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

View File

@ -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() tuples = service.recreate_containers()
self.assertEqual(len(intermediate), 1) self.assertEqual(len(tuples), 1)
self.assertEqual(len(new), 1)
new_container = new[0] intermediate_container = tuples[0][0]
intermediate_container = intermediate[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')