diff --git a/integration-cli/docker_api_swarm_service_test.go b/integration-cli/docker_api_swarm_service_test.go index 9213eae75b..a96f684965 100644 --- a/integration-cli/docker_api_swarm_service_test.go +++ b/integration-cli/docker_api_swarm_service_test.go @@ -175,6 +175,115 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesUpdate(c *check.C) { map[string]int{image1: instances}) } +func (s *DockerSwarmSuite) TestAPISwarmServicesUpdateStartFirst(c *check.C) { + d := s.AddDaemon(c, true, true) + + // service image at start + image1 := "busybox:latest" + // target image in update + image2 := "testhealth" + + // service started from this image won't pass health check + _, _, err := d.BuildImageWithOut(image2, + `FROM busybox + HEALTHCHECK --interval=1s --timeout=1s --retries=1024\ + CMD cat /status`, + true) + c.Check(err, check.IsNil) + + // create service + instances := 5 + parallelism := 2 + rollbackParallelism := 3 + id := d.CreateService(c, serviceForUpdate, setInstances(instances), setUpdateOrder(swarm.UpdateOrderStartFirst), setRollbackOrder(swarm.UpdateOrderStartFirst)) + + checkStartingTasks := func(expected int) []swarm.Task { + var startingTasks []swarm.Task + waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { + tasks := d.GetServiceTasks(c, id) + startingTasks = nil + for _, t := range tasks { + if t.Status.State == swarm.TaskStateStarting { + startingTasks = append(startingTasks, t) + } + } + return startingTasks, nil + }, checker.HasLen, expected) + + return startingTasks + } + + makeTasksHealthy := func(tasks []swarm.Task) { + for _, t := range tasks { + containerID := t.Status.ContainerStatus.ContainerID + d.Cmd("exec", containerID, "touch", "/status") + } + } + + // wait for tasks ready + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image1: instances}) + + // issue service update + service := d.GetService(c, id) + d.UpdateService(c, service, setImage(image2)) + + // first batch + + // The old tasks should be running, and the new ones should be starting. + startingTasks := checkStartingTasks(parallelism) + + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image1: instances}) + + // make it healthy + makeTasksHealthy(startingTasks) + + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image1: instances - parallelism, image2: parallelism}) + + // 2nd batch + + // The old tasks should be running, and the new ones should be starting. + startingTasks = checkStartingTasks(parallelism) + + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image1: instances - parallelism, image2: parallelism}) + + // make it healthy + makeTasksHealthy(startingTasks) + + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image1: instances - 2*parallelism, image2: 2 * parallelism}) + + // 3nd batch + + // The old tasks should be running, and the new ones should be starting. + startingTasks = checkStartingTasks(1) + + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image1: instances - 2*parallelism, image2: 2 * parallelism}) + + // make it healthy + makeTasksHealthy(startingTasks) + + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image2: instances}) + + // Roll back to the previous version. This uses the CLI because + // rollback is a client-side operation. + out, err := d.Cmd("service", "update", "--rollback", id) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + // first batch + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image2: instances - rollbackParallelism, image1: rollbackParallelism}) + + // 2nd batch + waitAndAssert(c, defaultReconciliationTimeout, d.CheckRunningTaskImages, checker.DeepEquals, + map[string]int{image1: instances}) +} + func (s *DockerSwarmSuite) TestAPISwarmServicesFailedUpdate(c *check.C) { const nodeCount = 3 var daemons [nodeCount]*daemon.Swarm diff --git a/integration-cli/docker_api_swarm_test.go b/integration-cli/docker_api_swarm_test.go index b087880c20..5ba14e7737 100644 --- a/integration-cli/docker_api_swarm_test.go +++ b/integration-cli/docker_api_swarm_test.go @@ -596,6 +596,24 @@ func setInstances(replicas int) daemon.ServiceConstructor { } } +func setUpdateOrder(order string) daemon.ServiceConstructor { + return func(s *swarm.Service) { + if s.Spec.UpdateConfig == nil { + s.Spec.UpdateConfig = &swarm.UpdateConfig{} + } + s.Spec.UpdateConfig.Order = order + } +} + +func setRollbackOrder(order string) daemon.ServiceConstructor { + return func(s *swarm.Service) { + if s.Spec.RollbackConfig == nil { + s.Spec.RollbackConfig = &swarm.UpdateConfig{} + } + s.Spec.RollbackConfig.Order = order + } +} + func setImage(image string) daemon.ServiceConstructor { return func(s *swarm.Service) { s.Spec.TaskTemplate.ContainerSpec.Image = image