Prevent `docker-compose scale` to be used with a v2.2 config file

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2017-04-19 16:47:43 -07:00
parent 457c16a7b1
commit ef40e3c6b9
6 changed files with 61 additions and 24 deletions

View File

@ -26,6 +26,7 @@ from ..config import resolve_build_args
from ..config.environment import Environment from ..config.environment import Environment
from ..config.serialize import serialize_config from ..config.serialize import serialize_config
from ..config.types import VolumeSpec from ..config.types import VolumeSpec
from ..const import COMPOSEFILE_V2_2 as V2_2
from ..const import IS_WINDOWS_PLATFORM from ..const import IS_WINDOWS_PLATFORM
from ..errors import StreamParseError from ..errors import StreamParseError
from ..progress_stream import StreamOutputError from ..progress_stream import StreamOutputError
@ -771,6 +772,12 @@ class TopLevelCommand(object):
""" """
timeout = timeout_from_opts(options) timeout = timeout_from_opts(options)
if self.project.config_version == V2_2:
raise UserError(
'The scale command is incompatible with the v2.2 format. '
'Use the up command with the --scale flag instead.'
)
for service_name, num in parse_scale_args(options['SERVICE=NUM']).items(): for service_name, num in parse_scale_args(options['SERVICE=NUM']).items():
self.project.get_service(service_name).scale(num, timeout=timeout) self.project.get_service(service_name).scale(num, timeout=timeout)

View File

@ -57,12 +57,13 @@ class Project(object):
""" """
A collection of services. A collection of services.
""" """
def __init__(self, name, services, client, networks=None, volumes=None): def __init__(self, name, services, client, networks=None, volumes=None, config_version=None):
self.name = name self.name = name
self.services = services self.services = services
self.client = client self.client = client
self.volumes = volumes or ProjectVolumes({}) self.volumes = volumes or ProjectVolumes({})
self.networks = networks or ProjectNetworks({}, False) self.networks = networks or ProjectNetworks({}, False)
self.config_version = config_version
def labels(self, one_off=OneOffFilter.exclude): def labels(self, one_off=OneOffFilter.exclude):
labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)] labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)]
@ -82,7 +83,7 @@ class Project(object):
networks, networks,
use_networking) use_networking)
volumes = ProjectVolumes.from_config(name, config_data, client) volumes = ProjectVolumes.from_config(name, config_data, client)
project = cls(name, [], client, project_networks, volumes) project = cls(name, [], client, project_networks, volumes, config_data.version)
for service_dict in config_data.services: for service_dict in config_data.services:
service_dict = dict(service_dict) service_dict = dict(service_dict)

View File

@ -383,8 +383,8 @@ class Service(object):
lambda n: self.get_container_name(n), lambda n: self.get_container_name(n),
"Creating" "Creating"
) )
if errors: for error in errors.values():
raise OperationFailedError(errors.values()[0]) raise OperationFailedError(error)
return containers return containers
@ -404,8 +404,9 @@ class Service(object):
lambda c: c.name, lambda c: c.name,
"Recreating" "Recreating"
) )
if errors: for error in errors.values():
raise OperationFailedError(errors.values()[0]) raise OperationFailedError(error)
if len(containers) < scale: if len(containers) < scale:
containers.extend(self._execute_convergence_create( containers.extend(self._execute_convergence_create(
scale - len(containers), detached, start scale - len(containers), detached, start
@ -424,8 +425,8 @@ class Service(object):
"Starting" "Starting"
) )
if errors: for error in errors.values():
raise OperationFailedError(errors.values()[0]) raise OperationFailedError(error)
if len(containers) < scale: if len(containers) < scale:
containers.extend(self._execute_convergence_create( containers.extend(self._execute_convergence_create(

View File

@ -1866,9 +1866,27 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(project.get_service('simple').containers()), 0) self.assertEqual(len(project.get_service('simple').containers()), 0)
self.assertEqual(len(project.get_service('another').containers()), 0) self.assertEqual(len(project.get_service('another').containers()), 0)
def test_up_scale(self): def test_scale_v2_2(self):
self.base_dir = 'tests/fixtures/scale'
result = self.dispatch(['scale', 'web=1'], returncode=1)
assert 'incompatible with the v2.2 format' in result.stderr
def test_up_scale_scale_up(self):
self.base_dir = 'tests/fixtures/scale' self.base_dir = 'tests/fixtures/scale'
project = self.project project = self.project
self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2
assert len(project.get_service('db').containers()) == 1
self.dispatch(['up', '-d', '--scale', 'web=3'])
assert len(project.get_service('web').containers()) == 3
assert len(project.get_service('db').containers()) == 1
def test_up_scale_scale_down(self):
self.base_dir = 'tests/fixtures/scale'
project = self.project
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2 assert len(project.get_service('web').containers()) == 2
assert len(project.get_service('db').containers()) == 1 assert len(project.get_service('db').containers()) == 1
@ -1877,13 +1895,21 @@ class CLITestCase(DockerClientTestCase):
assert len(project.get_service('web').containers()) == 1 assert len(project.get_service('web').containers()) == 1
assert len(project.get_service('db').containers()) == 1 assert len(project.get_service('db').containers()) == 1
self.dispatch(['up', '-d', '--scale', 'web=3']) def test_up_scale_reset(self):
self.base_dir = 'tests/fixtures/scale'
project = self.project
self.dispatch(['up', '-d', '--scale', 'web=3', '--scale', 'db=3'])
assert len(project.get_service('web').containers()) == 3 assert len(project.get_service('web').containers()) == 3
assert len(project.get_service('db').containers()) == 3
self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2
assert len(project.get_service('db').containers()) == 1 assert len(project.get_service('db').containers()) == 1
self.dispatch(['up', '-d', '--scale', 'web=1', '--scale', 'db=2']) def test_up_scale_to_zero(self):
assert len(project.get_service('web').containers()) == 1 self.base_dir = 'tests/fixtures/scale'
assert len(project.get_service('db').containers()) == 2 project = self.project
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2 assert len(project.get_service('web').containers()) == 2

View File

@ -565,12 +565,12 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(service.containers()), 3) self.assertEqual(len(service.containers()), 3)
project.up() project.up()
service = project.get_service('web') service = project.get_service('web')
self.assertEqual(len(service.containers()), 3) self.assertEqual(len(service.containers()), 1)
service.scale(1) service.scale(1)
self.assertEqual(len(service.containers()), 1) self.assertEqual(len(service.containers()), 1)
project.up() project.up(scale_override={'web': 3})
service = project.get_service('web') service = project.get_service('web')
self.assertEqual(len(service.containers()), 1) self.assertEqual(len(service.containers()), 3)
# does scale=0 ,makes any sense? after recreating at least 1 container is running # does scale=0 ,makes any sense? after recreating at least 1 container is running
service.scale(0) service.scale(0)
project.up() project.up()

View File

@ -26,6 +26,7 @@ from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE from compose.const import LABEL_SERVICE
from compose.const import LABEL_VERSION from compose.const import LABEL_VERSION
from compose.container import Container from compose.container import Container
from compose.errors import OperationFailedError
from compose.project import OneOffFilter from compose.project import OneOffFilter
from compose.service import ConvergencePlan from compose.service import ConvergencePlan
from compose.service import ConvergenceStrategy from compose.service import ConvergenceStrategy
@ -777,15 +778,15 @@ class ServiceTest(DockerClientTestCase):
message="testing", message="testing",
response={}, response={},
explanation="Boom")): explanation="Boom")):
with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr: with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
service.scale(3) with pytest.raises(OperationFailedError):
service.scale(3)
self.assertEqual(len(service.containers()), 1) assert len(service.containers()) == 1
self.assertTrue(service.containers()[0].is_running) assert service.containers()[0].is_running
self.assertIn( assert (
"ERROR: for composetest_web_2 Cannot create container for service web: Boom", "ERROR: for composetest_web_2 Cannot create container for service"
mock_stderr.getvalue() " web: Boom" in mock_stderr.getvalue()
) )
def test_scale_with_unexpected_exception(self): def test_scale_with_unexpected_exception(self):
@ -837,7 +838,8 @@ class ServiceTest(DockerClientTestCase):
service = self.create_service('app', container_name='custom-container') service = self.create_service('app', container_name='custom-container')
self.assertEqual(service.custom_container_name, 'custom-container') self.assertEqual(service.custom_container_name, 'custom-container')
service.scale(3) with pytest.raises(OperationFailedError):
service.scale(3)
captured_output = mock_log.warn.call_args[0][0] captured_output = mock_log.warn.call_args[0][0]