From 2f65ace2aa4e7b225f411d928879d7b792ffd00f Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 6 Nov 2024 13:18:00 +0100 Subject: [PATCH] remove obsolete containers first on scale down Signed-off-by: Nicolas De Loof --- pkg/compose/convergence.go | 5 ++++- pkg/e2e/fixtures/scale/Dockerfile | 17 +++++++++++++++++ pkg/e2e/fixtures/scale/build.yaml | 3 +++ pkg/e2e/scale_test.go | 31 +++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 pkg/e2e/fixtures/scale/Dockerfile create mode 100644 pkg/e2e/fixtures/scale/build.yaml diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index 2c2c85f82..d57c13b8b 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "slices" "sort" "strconv" "strings" @@ -136,16 +137,18 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project, ni, erri := strconv.Atoi(containers[i].Labels[api.ContainerNumberLabel]) nj, errj := strconv.Atoi(containers[j].Labels[api.ContainerNumberLabel]) if erri == nil && errj == nil { - return ni < nj + return ni > nj } // If we don't get a container number (?) just sort by creation date return containers[i].Created < containers[j].Created }) + slices.Reverse(containers) for i, container := range containers { if i >= expected { // Scale Down + // As we sorted containers, obsolete ones and/or highest number will be removed container := container traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...) eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error { diff --git a/pkg/e2e/fixtures/scale/Dockerfile b/pkg/e2e/fixtures/scale/Dockerfile new file mode 100644 index 000000000..7f341f952 --- /dev/null +++ b/pkg/e2e/fixtures/scale/Dockerfile @@ -0,0 +1,17 @@ +# Copyright 2020 Docker Compose CLI authors + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM nginx:alpine +ARG FOO +LABEL FOO=$FOO \ No newline at end of file diff --git a/pkg/e2e/fixtures/scale/build.yaml b/pkg/e2e/fixtures/scale/build.yaml new file mode 100644 index 000000000..cd109c7a8 --- /dev/null +++ b/pkg/e2e/fixtures/scale/build.yaml @@ -0,0 +1,3 @@ +services: + test: + build: . diff --git a/pkg/e2e/scale_test.go b/pkg/e2e/scale_test.go index 21595dd47..7a859187b 100644 --- a/pkg/e2e/scale_test.go +++ b/pkg/e2e/scale_test.go @@ -184,3 +184,34 @@ func checkServiceContainer(t *testing.T, stdout, containerName, containerState s } testify.Fail(t, errMessage, stdout) } + +func TestScaleDownNoRecreate(t *testing.T) { + const projectName = "scale-down-recreated-test" + c := NewCLI(t, WithEnv( + "COMPOSE_PROJECT_NAME="+projectName)) + + reset := func() { + c.RunDockerComposeCmd(t, "down", "--rmi", "all") + } + t.Cleanup(reset) + c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "build", "--build-arg", "FOO=test") + c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "up", "-d", "--scale", "test=2") + + c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "build", "--build-arg", "FOO=updated") + c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "up", "-d", "--scale", "test=4", "--no-recreate") + + res := c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "test") + res.Assert(t, icmd.Success) + assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-1")) + assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-2")) + assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-3")) + assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-4")) + + t.Log("scale down removes obsolete replica #1 and #2") + c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "test=2") + + res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "test") + res.Assert(t, icmd.Success) + assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-3")) + assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-4")) +}