// +build !windows package main import ( "fmt" "path" "strconv" "strings" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm/runtime" "github.com/docker/docker/integration-cli/checker" "github.com/docker/docker/integration-cli/daemon" "github.com/docker/docker/integration-cli/fixtures/plugin" "github.com/go-check/check" "golang.org/x/net/context" "golang.org/x/sys/unix" ) func setPortConfig(portConfig []swarm.PortConfig) daemon.ServiceConstructor { return func(s *swarm.Service) { if s.Spec.EndpointSpec == nil { s.Spec.EndpointSpec = &swarm.EndpointSpec{} } s.Spec.EndpointSpec.Ports = portConfig } } func (s *DockerSwarmSuite) TestAPIServiceUpdatePort(c *check.C) { d := s.AddDaemon(c, true, true) // Create a service with a port mapping of 8080:8081. portConfig := []swarm.PortConfig{{TargetPort: 8081, PublishedPort: 8080}} serviceID := d.CreateService(c, simpleTestService, setInstances(1), setPortConfig(portConfig)) waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1) // Update the service: changed the port mapping from 8080:8081 to 8082:8083. updatedPortConfig := []swarm.PortConfig{{TargetPort: 8083, PublishedPort: 8082}} remoteService := d.GetService(c, serviceID) d.UpdateService(c, remoteService, setPortConfig(updatedPortConfig)) // Inspect the service and verify port mapping. updatedService := d.GetService(c, serviceID) c.Assert(updatedService.Spec.EndpointSpec, check.NotNil) c.Assert(len(updatedService.Spec.EndpointSpec.Ports), check.Equals, 1) c.Assert(updatedService.Spec.EndpointSpec.Ports[0].TargetPort, check.Equals, uint32(8083)) c.Assert(updatedService.Spec.EndpointSpec.Ports[0].PublishedPort, check.Equals, uint32(8082)) } func (s *DockerSwarmSuite) TestAPISwarmServicesEmptyList(c *check.C) { d := s.AddDaemon(c, true, true) services := d.ListServices(c) c.Assert(services, checker.NotNil) c.Assert(len(services), checker.Equals, 0, check.Commentf("services: %#v", services)) } func (s *DockerSwarmSuite) TestAPISwarmServicesCreate(c *check.C) { d := s.AddDaemon(c, true, true) instances := 2 id := d.CreateService(c, simpleTestService, setInstances(instances)) waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, instances) cli, err := d.NewClient() c.Assert(err, checker.IsNil) defer cli.Close() options := types.ServiceInspectOptions{InsertDefaults: true} // insertDefaults inserts UpdateConfig when service is fetched by ID resp, _, err := cli.ServiceInspectWithRaw(context.Background(), id, options) out := fmt.Sprintf("%+v", resp) c.Assert(err, checker.IsNil) c.Assert(out, checker.Contains, "UpdateConfig") // insertDefaults inserts UpdateConfig when service is fetched by ID resp, _, err = cli.ServiceInspectWithRaw(context.Background(), "top", options) out = fmt.Sprintf("%+v", resp) c.Assert(err, checker.IsNil) c.Assert(string(out), checker.Contains, "UpdateConfig") service := d.GetService(c, id) instances = 5 d.UpdateService(c, service, setInstances(instances)) waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, instances) d.RemoveService(c, service.ID) waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 0) } func (s *DockerSwarmSuite) TestAPISwarmServicesMultipleAgents(c *check.C) { d1 := s.AddDaemon(c, true, true) d2 := s.AddDaemon(c, true, false) d3 := s.AddDaemon(c, true, false) time.Sleep(1 * time.Second) // make sure all daemons are ready to accept tasks instances := 9 id := d1.CreateService(c, simpleTestService, setInstances(instances)) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckActiveContainerCount, checker.GreaterThan, 0) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckActiveContainerCount, checker.GreaterThan, 0) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckActiveContainerCount, checker.GreaterThan, 0) waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d2.CheckActiveContainerCount, d3.CheckActiveContainerCount), checker.Equals, instances) // reconciliation on d2 node down d2.Stop(c) waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d3.CheckActiveContainerCount), checker.Equals, instances) // test downscaling instances = 5 d1.UpdateService(c, d1.GetService(c, id), setInstances(instances)) waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d3.CheckActiveContainerCount), checker.Equals, instances) } func (s *DockerSwarmSuite) TestAPISwarmServicesCreateGlobal(c *check.C) { d1 := s.AddDaemon(c, true, true) d2 := s.AddDaemon(c, true, false) d3 := s.AddDaemon(c, true, false) d1.CreateService(c, simpleTestService, setGlobalMode) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckActiveContainerCount, checker.Equals, 1) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckActiveContainerCount, checker.Equals, 1) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckActiveContainerCount, checker.Equals, 1) d4 := s.AddDaemon(c, true, false) d5 := s.AddDaemon(c, true, false) waitAndAssert(c, defaultReconciliationTimeout, d4.CheckActiveContainerCount, checker.Equals, 1) waitAndAssert(c, defaultReconciliationTimeout, d5.CheckActiveContainerCount, checker.Equals, 1) } func (s *DockerSwarmSuite) TestAPISwarmServicesUpdate(c *check.C) { const nodeCount = 3 var daemons [nodeCount]*daemon.Swarm for i := 0; i < nodeCount; i++ { daemons[i] = s.AddDaemon(c, true, i == 0) } // wait for nodes ready waitAndAssert(c, 5*time.Second, daemons[0].CheckNodeReadyCount, checker.Equals, nodeCount) // service image at start image1 := "busybox:latest" // target image in update image2 := "busybox:test" // create a different tag for _, d := range daemons { out, err := d.Cmd("tag", image1, image2) c.Assert(err, checker.IsNil, check.Commentf(out)) } // create service instances := 5 parallelism := 2 rollbackParallelism := 3 id := daemons[0].CreateService(c, serviceForUpdate, setInstances(instances)) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, map[string]int{image1: instances}) // issue service update service := daemons[0].GetService(c, id) daemons[0].UpdateService(c, service, setImage(image2)) // first batch waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, map[string]int{image1: instances - parallelism, image2: parallelism}) // 2nd batch waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, map[string]int{image1: instances - 2*parallelism, image2: 2 * parallelism}) // 3nd batch waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, map[string]int{image2: instances}) // Roll back to the previous version. This uses the CLI because // rollback used to be a client-side operation. out, err := daemons[0].Cmd("service", "update", "--rollback", id) c.Assert(err, checker.IsNil, check.Commentf(out)) // first batch waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, map[string]int{image2: instances - rollbackParallelism, image1: rollbackParallelism}) // 2nd batch waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, 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:latest" // service started from this image won't pass health check _, _, err := d.BuildImageWithOut(image2, `FROM busybox HEALTHCHECK --interval=1s --timeout=30s --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 for i := 0; i < nodeCount; i++ { daemons[i] = s.AddDaemon(c, true, i == 0) } // wait for nodes ready waitAndAssert(c, 5*time.Second, daemons[0].CheckNodeReadyCount, checker.Equals, nodeCount) // service image at start image1 := "busybox:latest" // target image in update image2 := "busybox:badtag" // create service instances := 5 id := daemons[0].CreateService(c, serviceForUpdate, setInstances(instances)) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, map[string]int{image1: instances}) // issue service update service := daemons[0].GetService(c, id) daemons[0].UpdateService(c, service, setImage(image2), setFailureAction(swarm.UpdateFailureActionPause), setMaxFailureRatio(0.25), setParallelism(1)) // should update 2 tasks and then pause waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceUpdateState(id), checker.Equals, swarm.UpdateStatePaused) v, _ := daemons[0].CheckServiceRunningTasks(id)(c) c.Assert(v, checker.Equals, instances-2) // Roll back to the previous version. This uses the CLI because // rollback used to be a client-side operation. out, err := daemons[0].Cmd("service", "update", "--rollback", id) c.Assert(err, checker.IsNil, check.Commentf(out)) waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckRunningTaskImages, checker.DeepEquals, map[string]int{image1: instances}) } func (s *DockerSwarmSuite) TestAPISwarmServiceConstraintRole(c *check.C) { const nodeCount = 3 var daemons [nodeCount]*daemon.Swarm for i := 0; i < nodeCount; i++ { daemons[i] = s.AddDaemon(c, true, i == 0) } // wait for nodes ready waitAndAssert(c, 5*time.Second, daemons[0].CheckNodeReadyCount, checker.Equals, nodeCount) // create service constraints := []string{"node.role==worker"} instances := 3 id := daemons[0].CreateService(c, simpleTestService, setConstraints(constraints), setInstances(instances)) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceRunningTasks(id), checker.Equals, instances) // validate tasks are running on worker nodes tasks := daemons[0].GetServiceTasks(c, id) for _, task := range tasks { node := daemons[0].GetNode(c, task.NodeID) c.Assert(node.Spec.Role, checker.Equals, swarm.NodeRoleWorker) } //remove service daemons[0].RemoveService(c, id) // create service constraints = []string{"node.role!=worker"} id = daemons[0].CreateService(c, simpleTestService, setConstraints(constraints), setInstances(instances)) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceRunningTasks(id), checker.Equals, instances) tasks = daemons[0].GetServiceTasks(c, id) // validate tasks are running on manager nodes for _, task := range tasks { node := daemons[0].GetNode(c, task.NodeID) c.Assert(node.Spec.Role, checker.Equals, swarm.NodeRoleManager) } //remove service daemons[0].RemoveService(c, id) // create service constraints = []string{"node.role==nosuchrole"} id = daemons[0].CreateService(c, simpleTestService, setConstraints(constraints), setInstances(instances)) // wait for tasks created waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceTasks(id), checker.Equals, instances) // let scheduler try time.Sleep(250 * time.Millisecond) // validate tasks are not assigned to any node tasks = daemons[0].GetServiceTasks(c, id) for _, task := range tasks { c.Assert(task.NodeID, checker.Equals, "") } } func (s *DockerSwarmSuite) TestAPISwarmServiceConstraintLabel(c *check.C) { const nodeCount = 3 var daemons [nodeCount]*daemon.Swarm for i := 0; i < nodeCount; i++ { daemons[i] = s.AddDaemon(c, true, i == 0) } // wait for nodes ready waitAndAssert(c, 5*time.Second, daemons[0].CheckNodeReadyCount, checker.Equals, nodeCount) nodes := daemons[0].ListNodes(c) c.Assert(len(nodes), checker.Equals, nodeCount) // add labels to nodes daemons[0].UpdateNode(c, nodes[0].ID, func(n *swarm.Node) { n.Spec.Annotations.Labels = map[string]string{ "security": "high", } }) for i := 1; i < nodeCount; i++ { daemons[0].UpdateNode(c, nodes[i].ID, func(n *swarm.Node) { n.Spec.Annotations.Labels = map[string]string{ "security": "low", } }) } // create service instances := 3 constraints := []string{"node.labels.security==high"} id := daemons[0].CreateService(c, simpleTestService, setConstraints(constraints), setInstances(instances)) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceRunningTasks(id), checker.Equals, instances) tasks := daemons[0].GetServiceTasks(c, id) // validate all tasks are running on nodes[0] for _, task := range tasks { c.Assert(task.NodeID, checker.Equals, nodes[0].ID) } //remove service daemons[0].RemoveService(c, id) // create service constraints = []string{"node.labels.security!=high"} id = daemons[0].CreateService(c, simpleTestService, setConstraints(constraints), setInstances(instances)) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceRunningTasks(id), checker.Equals, instances) tasks = daemons[0].GetServiceTasks(c, id) // validate all tasks are NOT running on nodes[0] for _, task := range tasks { c.Assert(task.NodeID, checker.Not(checker.Equals), nodes[0].ID) } //remove service daemons[0].RemoveService(c, id) constraints = []string{"node.labels.security==medium"} id = daemons[0].CreateService(c, simpleTestService, setConstraints(constraints), setInstances(instances)) // wait for tasks created waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceTasks(id), checker.Equals, instances) // let scheduler try time.Sleep(250 * time.Millisecond) tasks = daemons[0].GetServiceTasks(c, id) // validate tasks are not assigned for _, task := range tasks { c.Assert(task.NodeID, checker.Equals, "") } //remove service daemons[0].RemoveService(c, id) // multiple constraints constraints = []string{ "node.labels.security==high", fmt.Sprintf("node.id==%s", nodes[1].ID), } id = daemons[0].CreateService(c, simpleTestService, setConstraints(constraints), setInstances(instances)) // wait for tasks created waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceTasks(id), checker.Equals, instances) // let scheduler try time.Sleep(250 * time.Millisecond) tasks = daemons[0].GetServiceTasks(c, id) // validate tasks are not assigned for _, task := range tasks { c.Assert(task.NodeID, checker.Equals, "") } // make nodes[1] fulfills the constraints daemons[0].UpdateNode(c, nodes[1].ID, func(n *swarm.Node) { n.Spec.Annotations.Labels = map[string]string{ "security": "high", } }) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceRunningTasks(id), checker.Equals, instances) tasks = daemons[0].GetServiceTasks(c, id) for _, task := range tasks { c.Assert(task.NodeID, checker.Equals, nodes[1].ID) } } func (s *DockerSwarmSuite) TestAPISwarmServicePlacementPrefs(c *check.C) { const nodeCount = 3 var daemons [nodeCount]*daemon.Swarm for i := 0; i < nodeCount; i++ { daemons[i] = s.AddDaemon(c, true, i == 0) } // wait for nodes ready waitAndAssert(c, 5*time.Second, daemons[0].CheckNodeReadyCount, checker.Equals, nodeCount) nodes := daemons[0].ListNodes(c) c.Assert(len(nodes), checker.Equals, nodeCount) // add labels to nodes daemons[0].UpdateNode(c, nodes[0].ID, func(n *swarm.Node) { n.Spec.Annotations.Labels = map[string]string{ "rack": "a", } }) for i := 1; i < nodeCount; i++ { daemons[0].UpdateNode(c, nodes[i].ID, func(n *swarm.Node) { n.Spec.Annotations.Labels = map[string]string{ "rack": "b", } }) } // create service instances := 4 prefs := []swarm.PlacementPreference{{Spread: &swarm.SpreadOver{SpreadDescriptor: "node.labels.rack"}}} id := daemons[0].CreateService(c, simpleTestService, setPlacementPrefs(prefs), setInstances(instances)) // wait for tasks ready waitAndAssert(c, defaultReconciliationTimeout, daemons[0].CheckServiceRunningTasks(id), checker.Equals, instances) tasks := daemons[0].GetServiceTasks(c, id) // validate all tasks are running on nodes[0] tasksOnNode := make(map[string]int) for _, task := range tasks { tasksOnNode[task.NodeID]++ } c.Assert(tasksOnNode[nodes[0].ID], checker.Equals, 2) c.Assert(tasksOnNode[nodes[1].ID], checker.Equals, 1) c.Assert(tasksOnNode[nodes[2].ID], checker.Equals, 1) } func (s *DockerSwarmSuite) TestAPISwarmServicesStateReporting(c *check.C) { testRequires(c, SameHostDaemon) testRequires(c, DaemonIsLinux) d1 := s.AddDaemon(c, true, true) d2 := s.AddDaemon(c, true, true) d3 := s.AddDaemon(c, true, false) time.Sleep(1 * time.Second) // make sure all daemons are ready to accept instances := 9 d1.CreateService(c, simpleTestService, setInstances(instances)) waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d2.CheckActiveContainerCount, d3.CheckActiveContainerCount), checker.Equals, instances) getContainers := func() map[string]*daemon.Swarm { m := make(map[string]*daemon.Swarm) for _, d := range []*daemon.Swarm{d1, d2, d3} { for _, id := range d.ActiveContainers() { m[id] = d } } return m } containers := getContainers() c.Assert(containers, checker.HasLen, instances) var toRemove string for i := range containers { toRemove = i } _, err := containers[toRemove].Cmd("stop", toRemove) c.Assert(err, checker.IsNil) waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d2.CheckActiveContainerCount, d3.CheckActiveContainerCount), checker.Equals, instances) containers2 := getContainers() c.Assert(containers2, checker.HasLen, instances) for i := range containers { if i == toRemove { c.Assert(containers2[i], checker.IsNil) } else { c.Assert(containers2[i], checker.NotNil) } } containers = containers2 for i := range containers { toRemove = i } // try with killing process outside of docker pidStr, err := containers[toRemove].Cmd("inspect", "-f", "{{.State.Pid}}", toRemove) c.Assert(err, checker.IsNil) pid, err := strconv.Atoi(strings.TrimSpace(pidStr)) c.Assert(err, checker.IsNil) c.Assert(unix.Kill(pid, unix.SIGKILL), checker.IsNil) time.Sleep(time.Second) // give some time to handle the signal waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d2.CheckActiveContainerCount, d3.CheckActiveContainerCount), checker.Equals, instances) containers2 = getContainers() c.Assert(containers2, checker.HasLen, instances) for i := range containers { if i == toRemove { c.Assert(containers2[i], checker.IsNil) } else { c.Assert(containers2[i], checker.NotNil) } } } // Test plugins deployed via swarm services func (s *DockerSwarmSuite) TestAPISwarmServicesPlugin(c *check.C) { testRequires(c, ExperimentalDaemon, DaemonIsLinux, IsAmd64) reg := setupRegistry(c, false, "", "") defer reg.Close() repo := path.Join(privateRegistryURL, "swarm", "test:v1") repo2 := path.Join(privateRegistryURL, "swarm", "test:v2") name := "test" err := plugin.CreateInRegistry(context.Background(), repo, nil) c.Assert(err, checker.IsNil, check.Commentf("failed to create plugin")) err = plugin.CreateInRegistry(context.Background(), repo2, nil) c.Assert(err, checker.IsNil, check.Commentf("failed to create plugin")) d1 := s.AddDaemon(c, true, true) d2 := s.AddDaemon(c, true, true) d3 := s.AddDaemon(c, true, false) makePlugin := func(repo, name string, constraints []string) func(*swarm.Service) { return func(s *swarm.Service) { s.Spec.TaskTemplate.Runtime = "plugin" s.Spec.TaskTemplate.PluginSpec = &runtime.PluginSpec{ Name: name, Remote: repo, } if constraints != nil { s.Spec.TaskTemplate.Placement = &swarm.Placement{ Constraints: constraints, } } } } id := d1.CreateService(c, makePlugin(repo, name, nil)) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.True) service := d1.GetService(c, id) d1.UpdateService(c, service, makePlugin(repo2, name, nil)) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginImage(name), checker.Equals, repo2) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginImage(name), checker.Equals, repo2) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginImage(name), checker.Equals, repo2) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.True) d1.RemoveService(c, id) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.False) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.False) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.False) // constrain to managers only id = d1.CreateService(c, makePlugin(repo, name, []string{"node.role==manager"})) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.False) // Not a manager, not running it d1.RemoveService(c, id) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(name), checker.False) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(name), checker.False) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(name), checker.False) // with no name id = d1.CreateService(c, makePlugin(repo, "", nil)) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(repo), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(repo), checker.True) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(repo), checker.True) d1.RemoveService(c, id) waitAndAssert(c, defaultReconciliationTimeout, d1.CheckPluginRunning(repo), checker.False) waitAndAssert(c, defaultReconciliationTimeout, d2.CheckPluginRunning(repo), checker.False) waitAndAssert(c, defaultReconciliationTimeout, d3.CheckPluginRunning(repo), checker.False) }