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.serialize import serialize_config
from ..config.types import VolumeSpec
from ..const import COMPOSEFILE_V2_2 as V2_2
from ..const import IS_WINDOWS_PLATFORM
from ..errors import StreamParseError
from ..progress_stream import StreamOutputError
@ -771,6 +772,12 @@ class TopLevelCommand(object):
"""
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():
self.project.get_service(service_name).scale(num, timeout=timeout)

View File

@ -57,12 +57,13 @@ class Project(object):
"""
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.services = services
self.client = client
self.volumes = volumes or ProjectVolumes({})
self.networks = networks or ProjectNetworks({}, False)
self.config_version = config_version
def labels(self, one_off=OneOffFilter.exclude):
labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)]
@ -82,7 +83,7 @@ class Project(object):
networks,
use_networking)
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:
service_dict = dict(service_dict)

View File

@ -383,8 +383,8 @@ class Service(object):
lambda n: self.get_container_name(n),
"Creating"
)
if errors:
raise OperationFailedError(errors.values()[0])
for error in errors.values():
raise OperationFailedError(error)
return containers
@ -404,8 +404,9 @@ class Service(object):
lambda c: c.name,
"Recreating"
)
if errors:
raise OperationFailedError(errors.values()[0])
for error in errors.values():
raise OperationFailedError(error)
if len(containers) < scale:
containers.extend(self._execute_convergence_create(
scale - len(containers), detached, start
@ -424,8 +425,8 @@ class Service(object):
"Starting"
)
if errors:
raise OperationFailedError(errors.values()[0])
for error in errors.values():
raise OperationFailedError(error)
if len(containers) < scale:
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('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'
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'])
assert len(project.get_service('web').containers()) == 2
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('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('db').containers()) == 3
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=1', '--scale', 'db=2'])
assert len(project.get_service('web').containers()) == 1
assert len(project.get_service('db').containers()) == 2
def test_up_scale_to_zero(self):
self.base_dir = 'tests/fixtures/scale'
project = self.project
self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2

View File

@ -565,12 +565,12 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(service.containers()), 3)
project.up()
service = project.get_service('web')
self.assertEqual(len(service.containers()), 3)
self.assertEqual(len(service.containers()), 1)
service.scale(1)
self.assertEqual(len(service.containers()), 1)
project.up()
project.up(scale_override={'web': 3})
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
service.scale(0)
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_VERSION
from compose.container import Container
from compose.errors import OperationFailedError
from compose.project import OneOffFilter
from compose.service import ConvergencePlan
from compose.service import ConvergenceStrategy
@ -777,15 +778,15 @@ class ServiceTest(DockerClientTestCase):
message="testing",
response={},
explanation="Boom")):
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)
self.assertTrue(service.containers()[0].is_running)
self.assertIn(
"ERROR: for composetest_web_2 Cannot create container for service web: Boom",
mock_stderr.getvalue()
assert len(service.containers()) == 1
assert service.containers()[0].is_running
assert (
"ERROR: for composetest_web_2 Cannot create container for service"
" web: Boom" in mock_stderr.getvalue()
)
def test_scale_with_unexpected_exception(self):
@ -837,7 +838,8 @@ class ServiceTest(DockerClientTestCase):
service = self.create_service('app', 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]