diff --git a/fig/service.py b/fig/service.py index 29ad34874..d7416cbbc 100644 --- a/fig/service.py +++ b/fig/service.py @@ -1,11 +1,13 @@ from __future__ import unicode_literals from __future__ import absolute_import from collections import namedtuple -from docker.errors import APIError import logging import re import os import sys + +from docker.errors import APIError + from .container import Container from .progress_stream import stream_output, StreamOutputError @@ -43,6 +45,9 @@ class ConfigError(ValueError): VolumeSpec = namedtuple('VolumeSpec', 'external internal mode') +ServiceName = namedtuple('ServiceName', 'project service number') + + class Service(object): def __init__(self, name, client=None, project='default', links=None, volumes_from=None, **options): if not re.match('^%s+$' % VALID_NAME_CHARS, name): @@ -185,8 +190,8 @@ class Service(object): """ containers = self.containers(stopped=True) - if len(containers) == 0: - log.info("Creating %s..." % self.next_container_name()) + if not containers: + log.info("Creating %s..." % self._next_container_name(containers)) container = self.create_container(**override_options) self.start_container(container) return [(None, container)] @@ -264,7 +269,7 @@ class Service(object): containers = self.containers(stopped=True) if not containers: - log.info("Creating %s..." % self.next_container_name()) + log.info("Creating %s..." % self._next_container_name(containers)) new_container = self.create_container() return [self.start_container(new_container)] else: @@ -273,19 +278,15 @@ class Service(object): def get_linked_names(self): return [s.name for (s, _) in self.links] - def next_container_name(self, one_off=False): + def _next_container_name(self, all_containers, one_off=False): bits = [self.project, self.name] if one_off: bits.append('run') - return '_'.join(bits + [str(self.next_container_number(one_off=one_off))]) + return '_'.join(bits + [str(self._next_container_number(all_containers))]) - def next_container_number(self, one_off=False): - numbers = [parse_name(c.name)[2] for c in self.containers(stopped=True, one_off=one_off)] - - if len(numbers) == 0: - return 1 - else: - return max(numbers) + 1 + def _next_container_number(self, all_containers): + numbers = [parse_name(c.name).number for c in all_containers] + return 1 if not numbers else max(numbers) + 1 def _get_links(self, link_to_self): links = [] @@ -319,7 +320,9 @@ class Service(object): container_options = dict((k, self.options[k]) for k in DOCKER_CONFIG_KEYS if k in self.options) container_options.update(override_options) - container_options['name'] = self.next_container_name(one_off) + container_options['name'] = self._next_container_name( + self.containers(stopped=True, one_off=one_off), + one_off) # If a qualified hostname was given, split it into an # unqualified hostname and a domainname unless domainname @@ -424,10 +427,10 @@ def is_valid_name(name, one_off=False): return match.group(3) is None -def parse_name(name, one_off=False): +def parse_name(name): match = NAME_RE.match(name) (project, service_name, _, suffix) = match.groups() - return (project, service_name, int(suffix)) + return ServiceName(project, service_name, int(suffix)) def get_container_name(container): diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index d8f8e4d96..c882f20e5 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -10,7 +10,6 @@ class DockerClientTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls.client = Client(docker_url()) - cls.client.pull('busybox', tag='latest') def setUp(self): for c in self.client.containers(all=True): diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index 34bdd7346..650afa5a6 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -17,6 +17,10 @@ from fig.service import ( class ServiceTest(unittest.TestCase): + + def setUp(self): + self.mock_client = mock.create_autospec(docker.Client) + def test_name_validations(self): self.assertRaises(ConfigError, lambda: Service(name='')) @@ -70,29 +74,27 @@ class ServiceTest(unittest.TestCase): split_port("0.0.0.0:1000:2000:tcp") def test_split_domainname_none(self): - service = Service('foo', - hostname = 'name', - ) - service.next_container_name = lambda x: 'foo' + service = Service('foo', hostname='name', client=self.mock_client) + self.mock_client.containers.return_value = [] opts = service._get_container_create_options({}) self.assertEqual(opts['hostname'], 'name', 'hostname') self.assertFalse('domainname' in opts, 'domainname') def test_split_domainname_fqdn(self): service = Service('foo', - hostname = 'name.domain.tld', - ) - service.next_container_name = lambda x: 'foo' + hostname='name.domain.tld', + client=self.mock_client) + self.mock_client.containers.return_value = [] opts = service._get_container_create_options({}) self.assertEqual(opts['hostname'], 'name', 'hostname') self.assertEqual(opts['domainname'], 'domain.tld', 'domainname') def test_split_domainname_both(self): service = Service('foo', - hostname = 'name', - domainname = 'domain.tld', - ) - service.next_container_name = lambda x: 'foo' + hostname='name', + domainname='domain.tld', + client=self.mock_client) + self.mock_client.containers.return_value = [] opts = service._get_container_create_options({}) self.assertEqual(opts['hostname'], 'name', 'hostname') self.assertEqual(opts['domainname'], 'domain.tld', 'domainname') @@ -101,8 +103,8 @@ class ServiceTest(unittest.TestCase): service = Service('foo', hostname='name.sub', domainname='domain.tld', - ) - service.next_container_name = lambda x: 'foo' + client=self.mock_client) + self.mock_client.containers.return_value = [] opts = service._get_container_create_options({}) self.assertEqual(opts['hostname'], 'name.sub', 'hostname') self.assertEqual(opts['domainname'], 'domain.tld', 'domainname')