Skip to content

Commit 5b777ee

Browse files
committed
Cleanup service unit tests and restructure some service create logic.
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
1 parent 70c3676 commit 5b777ee

File tree

3 files changed

+98
-62
lines changed

3 files changed

+98
-62
lines changed

fig/service.py

+35-14
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@
5050
'workdir' : 'working_dir',
5151
}
5252

53+
DOCKER_START_KEYS = [
54+
'cap_add',
55+
'cap_drop',
56+
'dns',
57+
'dns_search',
58+
'env_file',
59+
'net',
60+
'privileged',
61+
'restart',
62+
]
63+
5364
VALID_NAME_CHARS = '[a-zA-Z0-9]'
5465

5566

@@ -145,7 +156,8 @@ def restart(self, **options):
145156

146157
def scale(self, desired_num):
147158
"""
148-
Adjusts the number of containers to the specified number and ensures they are running.
159+
Adjusts the number of containers to the specified number and ensures
160+
they are running.
149161
150162
- creates containers until there are at least `desired_num`
151163
- stops containers until there are at most `desired_num` running
@@ -192,12 +204,24 @@ def remove_stopped(self, **options):
192204
log.info("Removing %s..." % c.name)
193205
c.remove(**options)
194206

195-
def create_container(self, one_off=False, insecure_registry=False, **override_options):
207+
def create_container(self,
208+
one_off=False,
209+
insecure_registry=False,
210+
do_build=True,
211+
**override_options):
196212
"""
197213
Create a container for this service. If the image doesn't exist, attempt to pull
198214
it.
199215
"""
200-
container_options = self._get_container_create_options(override_options, one_off=one_off)
216+
container_options = self._get_container_create_options(
217+
override_options,
218+
one_off=one_off)
219+
220+
if (do_build and
221+
self.can_be_built() and
222+
not self.client.images(name=self.full_name)):
223+
self.build()
224+
201225
try:
202226
return Container.create(self.client, **container_options)
203227
except APIError as e:
@@ -273,8 +297,7 @@ def start_container_if_stopped(self, container, **options):
273297
log.info("Starting %s..." % container.name)
274298
return self.start_container(container, **options)
275299

276-
def start_container(self, container=None, intermediate_container=None, **override_options):
277-
container = container or self.create_container(**override_options)
300+
def start_container(self, container, intermediate_container=None, **override_options):
278301
options = dict(self.options, **override_options)
279302
port_bindings = build_port_bindings(options.get('ports') or [])
280303

@@ -407,16 +430,13 @@ def _get_container_create_options(self, override_options, one_off=False):
407430
container_options['environment'] = merge_environment(container_options)
408431

409432
if self.can_be_built():
410-
if len(self.client.images(name=self._build_tag_name())) == 0:
411-
self.build()
412-
container_options['image'] = self._build_tag_name()
433+
container_options['image'] = self.full_name
413434
else:
414435
container_options['image'] = self._get_image_name(container_options['image'])
415436

416437
# Delete options which are only used when starting
417-
for key in ['privileged', 'net', 'dns', 'dns_search', 'restart', 'cap_add', 'cap_drop', 'env_file']:
418-
if key in container_options:
419-
del container_options[key]
438+
for key in DOCKER_START_KEYS:
439+
container_options.pop(key, None)
420440

421441
return container_options
422442

@@ -431,7 +451,7 @@ def build(self, no_cache=False):
431451

432452
build_output = self.client.build(
433453
self.options['build'],
434-
tag=self._build_tag_name(),
454+
tag=self.full_name,
435455
stream=True,
436456
rm=True,
437457
nocache=no_cache,
@@ -451,14 +471,15 @@ def build(self, no_cache=False):
451471
image_id = match.group(1)
452472

453473
if image_id is None:
454-
raise BuildError(self)
474+
raise BuildError(self, event if all_events else 'Unknown')
455475

456476
return image_id
457477

458478
def can_be_built(self):
459479
return 'build' in self.options
460480

461-
def _build_tag_name(self):
481+
@property
482+
def full_name(self):
462483
"""
463484
The tag to give to images built for this service.
464485
"""

tests/integration/service_test.py

+45-40
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,24 @@
1010
from .testcases import DockerClientTestCase
1111

1212

13+
def create_and_start_container(service, **override_options):
14+
container = service.create_container(**override_options)
15+
return service.start_container(container, **override_options)
16+
17+
1318
class ServiceTest(DockerClientTestCase):
1419
def test_containers(self):
1520
foo = self.create_service('foo')
1621
bar = self.create_service('bar')
1722

18-
foo.start_container()
23+
create_and_start_container(foo)
1924

2025
self.assertEqual(len(foo.containers()), 1)
2126
self.assertEqual(foo.containers()[0].name, 'figtest_foo_1')
2227
self.assertEqual(len(bar.containers()), 0)
2328

24-
bar.start_container()
25-
bar.start_container()
29+
create_and_start_container(bar)
30+
create_and_start_container(bar)
2631

2732
self.assertEqual(len(foo.containers()), 1)
2833
self.assertEqual(len(bar.containers()), 2)
@@ -39,7 +44,7 @@ def test_containers_one_off(self):
3944

4045
def test_project_is_added_to_container_name(self):
4146
service = self.create_service('web')
42-
service.start_container()
47+
create_and_start_container(service)
4348
self.assertEqual(service.containers()[0].name, 'figtest_web_1')
4449

4550
def test_start_stop(self):
@@ -65,7 +70,7 @@ def test_start_stop(self):
6570
def test_kill_remove(self):
6671
service = self.create_service('scalingtest')
6772

68-
service.start_container()
73+
create_and_start_container(service)
6974
self.assertEqual(len(service.containers()), 1)
7075

7176
service.remove_stopped()
@@ -177,21 +182,21 @@ def test_recreate_containers_when_containers_are_stopped(self):
177182

178183
def test_start_container_passes_through_options(self):
179184
db = self.create_service('db')
180-
db.start_container(environment={'FOO': 'BAR'})
185+
create_and_start_container(db, environment={'FOO': 'BAR'})
181186
self.assertEqual(db.containers()[0].environment['FOO'], 'BAR')
182187

183188
def test_start_container_inherits_options_from_constructor(self):
184189
db = self.create_service('db', environment={'FOO': 'BAR'})
185-
db.start_container()
190+
create_and_start_container(db)
186191
self.assertEqual(db.containers()[0].environment['FOO'], 'BAR')
187192

188193
def test_start_container_creates_links(self):
189194
db = self.create_service('db')
190195
web = self.create_service('web', links=[(db, None)])
191196

192-
db.start_container()
193-
db.start_container()
194-
web.start_container()
197+
create_and_start_container(db)
198+
create_and_start_container(db)
199+
create_and_start_container(web)
195200

196201
self.assertEqual(
197202
set(web.containers()[0].links()),
@@ -206,9 +211,9 @@ def test_start_container_creates_links_with_names(self):
206211
db = self.create_service('db')
207212
web = self.create_service('web', links=[(db, 'custom_link_name')])
208213

209-
db.start_container()
210-
db.start_container()
211-
web.start_container()
214+
create_and_start_container(db)
215+
create_and_start_container(db)
216+
create_and_start_container(web)
212217

213218
self.assertEqual(
214219
set(web.containers()[0].links()),
@@ -222,19 +227,19 @@ def test_start_container_creates_links_with_names(self):
222227
def test_start_normal_container_does_not_create_links_to_its_own_service(self):
223228
db = self.create_service('db')
224229

225-
db.start_container()
226-
db.start_container()
230+
create_and_start_container(db)
231+
create_and_start_container(db)
227232

228-
c = db.start_container()
233+
c = create_and_start_container(db)
229234
self.assertEqual(set(c.links()), set([]))
230235

231236
def test_start_one_off_container_creates_links_to_its_own_service(self):
232237
db = self.create_service('db')
233238

234-
db.start_container()
235-
db.start_container()
239+
create_and_start_container(db)
240+
create_and_start_container(db)
236241

237-
c = db.start_container(one_off=True)
242+
c = create_and_start_container(db, one_off=True)
238243

239244
self.assertEqual(
240245
set(c.links()),
@@ -252,7 +257,7 @@ def test_start_container_builds_images(self):
252257
build='tests/fixtures/simple-dockerfile',
253258
project='figtest',
254259
)
255-
container = service.start_container()
260+
container = create_and_start_container(service)
256261
container.wait()
257262
self.assertIn('success', container.logs())
258263
self.assertEqual(len(self.client.images(name='figtest_test')), 1)
@@ -265,45 +270,45 @@ def test_start_container_uses_tagged_image_if_it_exists(self):
265270
build='this/does/not/exist/and/will/throw/error',
266271
project='figtest',
267272
)
268-
container = service.start_container()
273+
container = create_and_start_container(service)
269274
container.wait()
270275
self.assertIn('success', container.logs())
271276

272277
def test_start_container_creates_ports(self):
273278
service = self.create_service('web', ports=[8000])
274-
container = service.start_container().inspect()
279+
container = create_and_start_container(service).inspect()
275280
self.assertEqual(list(container['NetworkSettings']['Ports'].keys()), ['8000/tcp'])
276281
self.assertNotEqual(container['NetworkSettings']['Ports']['8000/tcp'][0]['HostPort'], '8000')
277282

278283
def test_start_container_stays_unpriviliged(self):
279284
service = self.create_service('web')
280-
container = service.start_container().inspect()
285+
container = create_and_start_container(service).inspect()
281286
self.assertEqual(container['HostConfig']['Privileged'], False)
282287

283288
def test_start_container_becomes_priviliged(self):
284289
service = self.create_service('web', privileged = True)
285-
container = service.start_container().inspect()
290+
container = create_and_start_container(service).inspect()
286291
self.assertEqual(container['HostConfig']['Privileged'], True)
287292

288293
def test_expose_does_not_publish_ports(self):
289294
service = self.create_service('web', expose=[8000])
290-
container = service.start_container().inspect()
295+
container = create_and_start_container(service).inspect()
291296
self.assertEqual(container['NetworkSettings']['Ports'], {'8000/tcp': None})
292297

293298
def test_start_container_creates_port_with_explicit_protocol(self):
294299
service = self.create_service('web', ports=['8000/udp'])
295-
container = service.start_container().inspect()
300+
container = create_and_start_container(service).inspect()
296301
self.assertEqual(list(container['NetworkSettings']['Ports'].keys()), ['8000/udp'])
297302

298303
def test_start_container_creates_fixed_external_ports(self):
299304
service = self.create_service('web', ports=['8000:8000'])
300-
container = service.start_container().inspect()
305+
container = create_and_start_container(service).inspect()
301306
self.assertIn('8000/tcp', container['NetworkSettings']['Ports'])
302307
self.assertEqual(container['NetworkSettings']['Ports']['8000/tcp'][0]['HostPort'], '8000')
303308

304309
def test_start_container_creates_fixed_external_ports_when_it_is_different_to_internal_port(self):
305310
service = self.create_service('web', ports=['8001:8000'])
306-
container = service.start_container().inspect()
311+
container = create_and_start_container(service).inspect()
307312
self.assertIn('8000/tcp', container['NetworkSettings']['Ports'])
308313
self.assertEqual(container['NetworkSettings']['Ports']['8000/tcp'][0]['HostPort'], '8001')
309314

@@ -312,7 +317,7 @@ def test_port_with_explicit_interface(self):
312317
'127.0.0.1:8001:8000',
313318
'0.0.0.0:9001:9000/udp',
314319
])
315-
container = service.start_container().inspect()
320+
container = create_and_start_container(service).inspect()
316321
self.assertEqual(container['NetworkSettings']['Ports'], {
317322
'8000/tcp': [
318323
{
@@ -361,28 +366,28 @@ def test_scale_sets_ports(self):
361366

362367
def test_network_mode_none(self):
363368
service = self.create_service('web', net='none')
364-
container = service.start_container()
369+
container = create_and_start_container(service)
365370
self.assertEqual(container.get('HostConfig.NetworkMode'), 'none')
366371

367372
def test_network_mode_bridged(self):
368373
service = self.create_service('web', net='bridge')
369-
container = service.start_container()
374+
container = create_and_start_container(service)
370375
self.assertEqual(container.get('HostConfig.NetworkMode'), 'bridge')
371376

372377
def test_network_mode_host(self):
373378
service = self.create_service('web', net='host')
374-
container = service.start_container()
379+
container = create_and_start_container(service)
375380
self.assertEqual(container.get('HostConfig.NetworkMode'), 'host')
376381

377382
def test_dns_single_value(self):
378383
service = self.create_service('web', dns='8.8.8.8')
379-
container = service.start_container().inspect()
380-
self.assertEqual(container['HostConfig']['Dns'], ['8.8.8.8'])
384+
container = create_and_start_container(service)
385+
self.assertEqual(container.get('HostConfig.Dns'), ['8.8.8.8'])
381386

382387
def test_dns_list(self):
383388
service = self.create_service('web', dns=['8.8.8.8', '9.9.9.9'])
384-
container = service.start_container().inspect()
385-
self.assertEqual(container['HostConfig']['Dns'], ['8.8.8.8', '9.9.9.9'])
389+
container = create_and_start_container(service)
390+
self.assertEqual(container.get('HostConfig.Dns'), ['8.8.8.8', '9.9.9.9'])
386391

387392
def test_restart_always_value(self):
388393
service = self.create_service('web', restart='always')
@@ -417,12 +422,12 @@ def test_dns_search_list(self):
417422

418423
def test_working_dir_param(self):
419424
service = self.create_service('container', working_dir='/working/dir/sample')
420-
container = service.create_container().inspect()
421-
self.assertEqual(container['Config']['WorkingDir'], '/working/dir/sample')
425+
container = service.create_container()
426+
self.assertEqual(container.get('Config.WorkingDir'), '/working/dir/sample')
422427

423428
def test_split_env(self):
424429
service = self.create_service('web', environment=['NORMAL=F1', 'CONTAINS_EQUALS=F=2', 'TRAILING_EQUALS='])
425-
env = service.start_container().environment
430+
env = create_and_start_container(service).environment
426431
for k,v in {'NORMAL': 'F1', 'CONTAINS_EQUALS': 'F=2', 'TRAILING_EQUALS': ''}.iteritems():
427432
self.assertEqual(env[k], v)
428433

@@ -438,7 +443,7 @@ def test_resolve_env(self):
438443
os.environ['FILE_DEF_EMPTY'] = 'E2'
439444
os.environ['ENV_DEF'] = 'E3'
440445
try:
441-
env = service.start_container().environment
446+
env = create_and_start_container(service).environment
442447
for k,v in {'FILE_DEF': 'F1', 'FILE_DEF_EMPTY': '', 'ENV_DEF': 'E3', 'NO_DEF': ''}.iteritems():
443448
self.assertEqual(env[k], v)
444449
finally:

tests/unit/service_test.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -189,20 +189,30 @@ def test_pull_image(self, mock_log):
189189
self.mock_client.pull.assert_called_once_with('someimage:sometag', insecure_registry=True)
190190
mock_log.info.assert_called_once_with('Pulling foo (someimage:sometag)...')
191191

192+
@mock.patch('fig.service.Container', autospec=True)
192193
@mock.patch('fig.service.log', autospec=True)
193-
def test_create_container_from_insecure_registry(self, mock_log):
194+
def test_create_container_from_insecure_registry(
195+
self,
196+
mock_log,
197+
mock_container):
194198
service = Service('foo', client=self.mock_client, image='someimage:sometag')
195199
mock_response = mock.Mock(Response)
196200
mock_response.status_code = 404
197201
mock_response.reason = "Not Found"
198-
Container.create = mock.Mock()
199-
Container.create.side_effect = APIError('Mock error', mock_response, "No such image")
200-
try:
202+
mock_container.create.side_effect = APIError(
203+
'Mock error', mock_response, "No such image")
204+
205+
# We expect the APIError because our service requires a
206+
# non-existent image.
207+
with self.assertRaises(APIError):
201208
service.create_container(insecure_registry=True)
202-
except APIError: # We expect the APIError because our service requires a non-existent image.
203-
pass
204-
self.mock_client.pull.assert_called_once_with('someimage:sometag', insecure_registry=True, stream=True)
205-
mock_log.info.assert_called_once_with('Pulling image someimage:sometag...')
209+
210+
self.mock_client.pull.assert_called_once_with(
211+
'someimage:sometag',
212+
insecure_registry=True,
213+
stream=True)
214+
mock_log.info.assert_called_once_with(
215+
'Pulling image someimage:sometag...')
206216

207217
def test_parse_repository_tag(self):
208218
self.assertEqual(parse_repository_tag("root"), ("root", ""))

0 commit comments

Comments
 (0)