Skip to content

Commit f57db07

Browse files
committed
Merge pull request #863 from dnephin/revert_volume_recreate_changes
Revert #711 from dnephin/fix_volumes_on_recreate
2 parents 55095ef + 2dd1cc8 commit f57db07

4 files changed

Lines changed: 64 additions & 142 deletions

File tree

fig/project.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,18 @@ def up(self,
176176
do_build=True):
177177
running_containers = []
178178
for service in self.get_services(service_names, include_links=start_links):
179-
create_func = (service.recreate_containers if recreate
180-
else service.start_or_create_containers)
181-
182-
for container in create_func(
183-
insecure_registry=insecure_registry,
184-
detach=detach,
185-
do_build=do_build):
186-
running_containers.append(container)
179+
if recreate:
180+
for (_, container) in service.recreate_containers(
181+
insecure_registry=insecure_registry,
182+
detach=detach,
183+
do_build=do_build):
184+
running_containers.append(container)
185+
else:
186+
for container in service.start_or_create_containers(
187+
insecure_registry=insecure_registry,
188+
detach=detach,
189+
do_build=do_build):
190+
running_containers.append(container)
187191

188192
return running_containers
189193

fig/service.py

Lines changed: 26 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,8 @@ def create_container(self,
242242

243243
def recreate_containers(self, insecure_registry=False, do_build=True, **override_options):
244244
"""
245-
If a container for this service doesn't exist, create and start one. If
246-
there are any, stop them, create+start new ones, and remove the old
247-
containers.
245+
If a container for this service doesn't exist, create and start one. If there are
246+
any, stop them, create+start new ones, and remove the old containers.
248247
"""
249248
containers = self.containers(stopped=True)
250249
if not containers:
@@ -254,22 +253,21 @@ def recreate_containers(self, insecure_registry=False, do_build=True, **override
254253
do_build=do_build,
255254
**override_options)
256255
self.start_container(container)
257-
return [container]
256+
return [(None, container)]
258257
else:
259-
return [
260-
self.recreate_container(
261-
container,
262-
insecure_registry=insecure_registry,
263-
**override_options)
264-
for container in containers
265-
]
258+
tuples = []
259+
260+
for c in containers:
261+
log.info("Recreating %s..." % c.name)
262+
tuples.append(self.recreate_container(c, insecure_registry=insecure_registry, **override_options))
263+
264+
return tuples
266265

267266
def recreate_container(self, container, **override_options):
268267
"""Recreate a container. An intermediate container is created so that
269268
the new container has the same name, while still supporting
270269
`volumes-from` the original container.
271270
"""
272-
log.info("Recreating %s..." % container.name)
273271
try:
274272
container.stop()
275273
except APIError as e:
@@ -280,30 +278,24 @@ def recreate_container(self, container, **override_options):
280278
else:
281279
raise
282280

283-
intermediate_options = dict(self.options, **override_options)
284281
intermediate_container = Container.create(
285282
self.client,
286283
image=container.image,
287284
entrypoint=['/bin/echo'],
288285
command=[],
289286
detach=True,
290287
)
291-
intermediate_container.start(
292-
binds=get_container_data_volumes(
293-
container, intermediate_options.get('volumes')))
288+
intermediate_container.start(volumes_from=container.id)
294289
intermediate_container.wait()
295290
container.remove()
296291

297-
# TODO: volumes are being passed to both start and create, this is
298-
# probably unnecessary
299292
options = dict(override_options)
300293
new_container = self.create_container(do_build=False, **options)
301-
self.start_container(
302-
new_container,
303-
intermediate_container=intermediate_container)
294+
self.start_container(new_container, intermediate_container=intermediate_container)
304295

305296
intermediate_container.remove()
306-
return new_container
297+
298+
return (intermediate_container, new_container)
307299

308300
def start_container_if_stopped(self, container, **options):
309301
if container.is_running:
@@ -315,6 +307,12 @@ def start_container_if_stopped(self, container, **options):
315307
def start_container(self, container, intermediate_container=None, **override_options):
316308
options = dict(self.options, **override_options)
317309
port_bindings = build_port_bindings(options.get('ports') or [])
310+
311+
volume_bindings = dict(
312+
build_volume_binding(parse_volume_spec(volume))
313+
for volume in options.get('volumes') or []
314+
if ':' in volume)
315+
318316
privileged = options.get('privileged', False)
319317
net = options.get('net', 'bridge')
320318
dns = options.get('dns', None)
@@ -323,14 +321,12 @@ def start_container(self, container, intermediate_container=None, **override_opt
323321
cap_drop = options.get('cap_drop', None)
324322

325323
restart = parse_restart_spec(options.get('restart', None))
326-
binds = get_volume_bindings(
327-
options.get('volumes'), intermediate_container)
328324

329325
container.start(
330326
links=self._get_links(link_to_self=options.get('one_off', False)),
331327
port_bindings=port_bindings,
332-
binds=binds,
333-
volumes_from=self._get_volumes_from(),
328+
binds=volume_bindings,
329+
volumes_from=self._get_volumes_from(intermediate_container),
334330
privileged=privileged,
335331
network_mode=net,
336332
dns=dns,
@@ -392,7 +388,7 @@ def _get_links(self, link_to_self):
392388
links.append((external_link, link_name))
393389
return links
394390

395-
def _get_volumes_from(self):
391+
def _get_volumes_from(self, intermediate_container=None):
396392
volumes_from = []
397393
for volume_source in self.volumes_from:
398394
if isinstance(volume_source, Service):
@@ -406,6 +402,9 @@ def _get_volumes_from(self):
406402
elif isinstance(volume_source, Container):
407403
volumes_from.append(volume_source.id)
408404

405+
if intermediate_container:
406+
volumes_from.append(intermediate_container.id)
407+
409408
return volumes_from
410409

411410
def _get_container_create_options(self, override_options, one_off=False):
@@ -520,45 +519,6 @@ def pull(self, insecure_registry=False):
520519
)
521520

522521

523-
def get_container_data_volumes(container, volumes_option):
524-
"""Find the container data volumes that are in `volumes_option`, and return
525-
a mapping of volume bindings for those volumes.
526-
"""
527-
volumes = []
528-
for volume in volumes_option or []:
529-
volume = parse_volume_spec(volume)
530-
# No need to preserve host volumes
531-
if volume.external:
532-
continue
533-
534-
volume_path = (container.get('Volumes') or {}).get(volume.internal)
535-
# New volume, doesn't exist in the old container
536-
if not volume_path:
537-
continue
538-
539-
# Copy existing volume from old container
540-
volume = volume._replace(external=volume_path)
541-
volumes.append(build_volume_binding(volume))
542-
543-
return dict(volumes)
544-
545-
546-
def get_volume_bindings(volumes_option, intermediate_container):
547-
"""Return a list of volume bindings for a container. Container data volume
548-
bindings are replaced by those in the intermediate container.
549-
"""
550-
volume_bindings = dict(
551-
build_volume_binding(parse_volume_spec(volume))
552-
for volume in volumes_option or []
553-
if ':' in volume)
554-
555-
if intermediate_container:
556-
volume_bindings.update(
557-
get_container_data_volumes(intermediate_container, volumes_option))
558-
559-
return volume_bindings
560-
561-
562522
NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$')
563523

564524

tests/integration/service_test.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def test_create_container_with_unspecified_volume(self):
9898
service = self.create_service('db', volumes=['/var/db'])
9999
container = service.create_container()
100100
service.start_container(container)
101-
self.assertIn('/var/db', container.get('Volumes'))
101+
self.assertIn('/var/db', container.inspect()['Volumes'])
102102

103103
def test_create_container_with_cpu_shares(self):
104104
service = self.create_service('db', cpu_shares=73)
@@ -148,23 +148,30 @@ def test_recreate_containers(self):
148148
self.assertIn('FOO=1', old_container.dictionary['Config']['Env'])
149149
self.assertEqual(old_container.name, 'figtest_db_1')
150150
service.start_container(old_container)
151-
volume_path = old_container.get('Volumes')['/etc']
151+
volume_path = old_container.inspect()['Volumes']['/etc']
152152

153153
num_containers_before = len(self.client.containers(all=True))
154154

155155
service.options['environment']['FOO'] = '2'
156-
containers = service.recreate_containers()
157-
self.assertEqual(len(containers), 1)
156+
tuples = service.recreate_containers()
157+
self.assertEqual(len(tuples), 1)
158158

159-
new_container = containers[0]
160-
self.assertEqual(new_container.get('Config.Entrypoint'), ['sleep'])
161-
self.assertEqual(new_container.get('Config.Cmd'), ['300'])
162-
self.assertIn('FOO=2', new_container.get('Config.Env'))
159+
intermediate_container = tuples[0][0]
160+
new_container = tuples[0][1]
161+
self.assertEqual(intermediate_container.dictionary['Config']['Entrypoint'], ['/bin/echo'])
162+
163+
self.assertEqual(new_container.dictionary['Config']['Entrypoint'], ['sleep'])
164+
self.assertEqual(new_container.dictionary['Config']['Cmd'], ['300'])
165+
self.assertIn('FOO=2', new_container.dictionary['Config']['Env'])
163166
self.assertEqual(new_container.name, 'figtest_db_1')
164-
self.assertEqual(new_container.get('Volumes')['/etc'], volume_path)
167+
self.assertEqual(new_container.inspect()['Volumes']['/etc'], volume_path)
168+
self.assertIn(intermediate_container.id, new_container.dictionary['HostConfig']['VolumesFrom'])
165169

166170
self.assertEqual(len(self.client.containers(all=True)), num_containers_before)
167171
self.assertNotEqual(old_container.id, new_container.id)
172+
self.assertRaises(APIError,
173+
self.client.inspect_container,
174+
intermediate_container.id)
168175

169176
def test_recreate_containers_when_containers_are_stopped(self):
170177
service = self.create_service(
@@ -179,16 +186,6 @@ def test_recreate_containers_when_containers_are_stopped(self):
179186
service.recreate_containers()
180187
self.assertEqual(len(service.containers(stopped=True)), 1)
181188

182-
def test_recreate_containers_with_volume_changes(self):
183-
service = self.create_service('withvolumes', volumes=['/etc'])
184-
old_container = create_and_start_container(service)
185-
self.assertEqual(old_container.get('Volumes').keys(), ['/etc'])
186-
187-
service = self.create_service('withvolumes')
188-
container, = service.recreate_containers()
189-
service.start_container(container)
190-
self.assertEqual(container.get('Volumes'), {})
191-
192189
def test_start_container_passes_through_options(self):
193190
db = self.create_service('db')
194191
create_and_start_container(db, environment={'FOO': 'BAR'})

tests/unit/service_test.py

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111
from fig import Service
1212
from fig.container import Container
1313
from fig.service import (
14-
APIError,
1514
ConfigError,
15+
split_port,
1616
build_port_bindings,
17+
parse_volume_spec,
1718
build_volume_binding,
18-
get_container_data_volumes,
19-
get_volume_bindings,
19+
APIError,
2020
parse_repository_tag,
21-
parse_volume_spec,
22-
split_port,
2321
)
2422

2523

@@ -59,6 +57,13 @@ def test_get_volumes_from_container(self):
5957

6058
self.assertEqual(service._get_volumes_from(), [container_id])
6159

60+
def test_get_volumes_from_intermediate_container(self):
61+
container_id = 'aabbccddee'
62+
service = Service('test')
63+
container = mock.Mock(id=container_id, spec=Container)
64+
65+
self.assertEqual(service._get_volumes_from(container), [container_id])
66+
6267
def test_get_volumes_from_service_container_exists(self):
6368
container_ids = ['aabbccddee', '12345']
6469
from_service = mock.create_autospec(Service)
@@ -283,50 +288,6 @@ def test_building_volume_binding_with_home(self):
283288
binding,
284289
('/home/user', dict(bind='/home/user', ro=False)))
285290

286-
def test_get_container_data_volumes(self):
287-
options = [
288-
'/host/volume:/host/volume:ro',
289-
'/new/volume',
290-
'/existing/volume',
291-
]
292-
293-
container = Container(None, {
294-
'Volumes': {
295-
'/host/volume': '/host/volume',
296-
'/existing/volume': '/var/lib/docker/aaaaaaaa',
297-
'/removed/volume': '/var/lib/docker/bbbbbbbb',
298-
},
299-
}, has_been_inspected=True)
300-
301-
expected = {
302-
'/var/lib/docker/aaaaaaaa': {'bind': '/existing/volume', 'ro': False},
303-
}
304-
305-
binds = get_container_data_volumes(container, options)
306-
self.assertEqual(binds, expected)
307-
308-
def test_get_volume_bindings(self):
309-
options = [
310-
'/host/volume:/host/volume:ro',
311-
'/host/rw/volume:/host/rw/volume',
312-
'/new/volume',
313-
'/existing/volume',
314-
]
315-
316-
intermediate_container = Container(None, {
317-
'Volumes': {'/existing/volume': '/var/lib/docker/aaaaaaaa'},
318-
}, has_been_inspected=True)
319-
320-
expected = {
321-
'/host/volume': {'bind': '/host/volume', 'ro': True},
322-
'/host/rw/volume': {'bind': '/host/rw/volume', 'ro': False},
323-
'/var/lib/docker/aaaaaaaa': {'bind': '/existing/volume', 'ro': False},
324-
}
325-
326-
binds = get_volume_bindings(options, intermediate_container)
327-
self.assertEqual(binds, expected)
328-
329-
330291
class ServiceEnvironmentTest(unittest.TestCase):
331292

332293
def setUp(self):

0 commit comments

Comments
 (0)