Skip to content

Commit 80991f1

Browse files
committed
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.
1 parent f8ee52c commit 80991f1

6 files changed

Lines changed: 46 additions & 70 deletions

File tree

fig/cli/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,9 @@ def up(self, options):
301301
"""
302302
detached = options['-d']
303303

304-
new = self.project.up(service_names=options['SERVICE'])
304+
to_attach = self.project.up(service_names=options['SERVICE'])
305305

306306
if not detached:
307-
to_attach = [c for (s, c) in new]
308307
print("Attaching to", list_containers(to_attach))
309308
log_printer = LogPrinter(to_attach, attach_params={"logs": True})
310309

fig/packages/docker/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -698,8 +698,8 @@ def search(self, term):
698698
params={'term': term}),
699699
True)
700700

701-
def start(self, container, binds=None, port_bindings=None, lxc_conf=None,
702-
publish_all_ports=False, links=None, privileged=False):
701+
def start(self, container, binds=None, volumes_from=None, port_bindings=None,
702+
lxc_conf=None, publish_all_ports=False, links=None, privileged=False):
703703
if isinstance(container, dict):
704704
container = container.get('Id')
705705

@@ -718,6 +718,11 @@ def start(self, container, binds=None, port_bindings=None, lxc_conf=None,
718718
]
719719
start_config['Binds'] = bind_pairs
720720

721+
if volumes_from and not isinstance(volumes_from, six.string_types):
722+
volumes_from = ','.join(volumes_from)
723+
724+
start_config['VolumesFrom'] = volumes_from
725+
721726
if port_bindings:
722727
start_config['PortBindings'] = utils.convert_port_bindings(
723728
port_bindings

fig/project.py

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,6 @@ def get_services(self, service_names=None):
105105
unsorted = [self.get_service(name) for name in service_names]
106106
return [s for s in self.services if s in unsorted]
107107

108-
def recreate_containers(self, service_names=None):
109-
"""
110-
For each service, create or recreate their containers.
111-
Returns a tuple with two lists. The first is a list of
112-
(service, old_container) tuples; the second is a list
113-
of (service, new_container) tuples.
114-
"""
115-
old = []
116-
new = []
117-
118-
for service in self.get_services(service_names):
119-
(s_old, s_new) = service.recreate_containers()
120-
old += [(service, container) for container in s_old]
121-
new += [(service, container) for container in s_new]
122-
123-
return (old, new)
124-
125108
def start(self, service_names=None, **options):
126109
for service in self.get_services(service_names):
127110
service.start(**options)
@@ -142,15 +125,13 @@ def build(self, service_names=None, **options):
142125
log.info('%s uses an image, skipping' % service.name)
143126

144127
def up(self, service_names=None):
145-
(old, new) = self.recreate_containers(service_names=service_names)
128+
new_containers = []
146129

147-
for (service, container) in new:
148-
service.start_container(container)
149-
150-
for (service, container) in old:
151-
container.remove()
130+
for service in self.get_services(service_names):
131+
for (_, new) in service.recreate_containers():
132+
new_containers.append(new)
152133

153-
return new
134+
return new_containers
154135

155136
def remove_stopped(self, service_names=None, **options):
156137
for service in self.get_services(service_names):

fig/service.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,25 +154,24 @@ def create_container(self, one_off=False, **override_options):
154154

155155
def recreate_containers(self, **override_options):
156156
"""
157-
If a container for this service doesn't exist, create one. If there are
158-
any, stop them and create new ones. Does not remove the old containers.
157+
If a container for this service doesn't exist, create and start one. If there are
158+
any, stop them, create+start new ones, and remove the old containers.
159159
"""
160160
containers = self.containers(stopped=True)
161161

162162
if len(containers) == 0:
163163
log.info("Creating %s..." % self.next_container_name())
164-
return ([], [self.create_container(**override_options)])
164+
container = self.create_container(**override_options)
165+
self.start_container(container)
166+
return [(None, container)]
165167
else:
166-
old_containers = []
167-
new_containers = []
168+
tuples = []
168169

169170
for c in containers:
170171
log.info("Recreating %s..." % c.name)
171-
(old_container, new_container) = self.recreate_container(c, **override_options)
172-
old_containers.append(old_container)
173-
new_containers.append(new_container)
172+
tuples.append(self.recreate_container(c, **override_options))
174173

175-
return (old_containers, new_containers)
174+
return tuples
176175

177176
def recreate_container(self, container, **override_options):
178177
if container.is_running:
@@ -185,17 +184,20 @@ def recreate_container(self, container, **override_options):
185184
entrypoint=['echo'],
186185
command=[],
187186
)
188-
intermediate_container.start()
187+
intermediate_container.start(volumes_from=container.id)
189188
intermediate_container.wait()
190189
container.remove()
191190

192191
options = dict(override_options)
193192
options['volumes_from'] = intermediate_container.id
194193
new_container = self.create_container(**options)
194+
self.start_container(new_container, volumes_from=intermediate_container.id)
195+
196+
intermediate_container.remove()
195197

196198
return (intermediate_container, new_container)
197199

198-
def start_container(self, container=None, **override_options):
200+
def start_container(self, container=None, volumes_from=None, **override_options):
199201
if container is None:
200202
container = self.create_container(**override_options)
201203

@@ -228,6 +230,7 @@ def start_container(self, container=None, **override_options):
228230
links=self._get_links(link_to_self=override_options.get('one_off', False)),
229231
port_bindings=port_bindings,
230232
binds=volume_bindings,
233+
volumes_from=volumes_from,
231234
privileged=privileged,
232235
)
233236
return container

tests/project_test.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,29 +63,6 @@ def test_get_service(self):
6363
project = Project('test', [web], self.client)
6464
self.assertEqual(project.get_service('web'), web)
6565

66-
def test_recreate_containers(self):
67-
web = self.create_service('web')
68-
db = self.create_service('db')
69-
project = Project('test', [web, db], self.client)
70-
71-
old_web_container = web.create_container()
72-
self.assertEqual(len(web.containers(stopped=True)), 1)
73-
self.assertEqual(len(db.containers(stopped=True)), 0)
74-
75-
(old, new) = project.recreate_containers()
76-
self.assertEqual(len(old), 1)
77-
self.assertEqual(old[0][0], web)
78-
self.assertEqual(len(new), 2)
79-
self.assertEqual(new[0][0], web)
80-
self.assertEqual(new[1][0], db)
81-
82-
self.assertEqual(len(web.containers(stopped=True)), 1)
83-
self.assertEqual(len(db.containers(stopped=True)), 1)
84-
85-
# remove intermediate containers
86-
for (service, container) in old:
87-
container.remove()
88-
8966
def test_start_stop_kill_remove(self):
9067
web = self.create_service('web')
9168
db = self.create_service('db')
@@ -121,12 +98,23 @@ def test_start_stop_kill_remove(self):
12198

12299
def test_project_up(self):
123100
web = self.create_service('web')
124-
db = self.create_service('db')
101+
db = self.create_service('db', volumes=['/var/db'])
125102
project = Project('figtest', [web, db], self.client)
126103
project.start()
127104
self.assertEqual(len(project.containers()), 0)
105+
106+
project.up(['db'])
107+
self.assertEqual(len(project.containers()), 1)
108+
old_db_id = project.containers()[0].id
109+
db_volume_path = project.containers()[0].inspect()['Volumes']['/var/db']
110+
128111
project.up()
129112
self.assertEqual(len(project.containers()), 2)
113+
114+
db_container = [c for c in project.containers() if 'db' in c.name][0]
115+
self.assertNotEqual(c.id, old_db_id)
116+
self.assertEqual(c.inspect()['Volumes']['/var/db'], db_volume_path)
117+
130118
project.kill()
131119
project.remove_stopped()
132120

tests/service_test.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import absolute_import
33
from fig import Service
44
from fig.service import CannotBeScaledError, ConfigError
5+
from fig.packages.docker.client import APIError
56
from .testcases import DockerClientTestCase
67

78

@@ -132,23 +133,22 @@ def test_recreate_containers(self):
132133
num_containers_before = len(self.client.containers(all=True))
133134

134135
service.options['environment']['FOO'] = '2'
135-
(intermediate, new) = service.recreate_containers()
136-
self.assertEqual(len(intermediate), 1)
137-
self.assertEqual(len(new), 1)
136+
tuples = service.recreate_containers()
137+
self.assertEqual(len(tuples), 1)
138138

139-
new_container = new[0]
140-
intermediate_container = intermediate[0]
139+
intermediate_container = tuples[0][0]
140+
new_container = tuples[0][1]
141141
self.assertEqual(intermediate_container.dictionary['Config']['Entrypoint'], ['echo'])
142142

143143
self.assertEqual(new_container.dictionary['Config']['Entrypoint'], ['ps'])
144144
self.assertEqual(new_container.dictionary['Config']['Cmd'], ['ax'])
145145
self.assertIn('FOO=2', new_container.dictionary['Config']['Env'])
146146
self.assertEqual(new_container.name, 'figtest_db_1')
147-
service.start_container(new_container)
148147
self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path)
149148

150-
self.assertEqual(len(self.client.containers(all=True)), num_containers_before + 1)
149+
self.assertEqual(len(self.client.containers(all=True)), num_containers_before)
151150
self.assertNotEqual(old_container.id, new_container.id)
151+
self.assertRaises(APIError, lambda: self.client.inspect_container(intermediate_container.id))
152152

153153
def test_start_container_passes_through_options(self):
154154
db = self.create_service('db')

0 commit comments

Comments
 (0)