Browse Source

Make internal/test/daemon.Daemon swarm aware

This remove the daemon.Swarm construction by make the new test Daemon
struct aware of swarm.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
Vincent Demeester 7 years ago
parent
commit
83d18cf4e3

+ 9 - 13
integration-cli/check_test.go

@@ -311,7 +311,7 @@ func init() {
 type DockerSwarmSuite struct {
 	server      *httptest.Server
 	ds          *DockerSuite
-	daemons     []*daemon.Swarm
+	daemons     []*daemon.Daemon
 	daemonsLock sync.Mutex // protect access to daemons
 	portIndex   int
 }
@@ -328,14 +328,10 @@ func (s *DockerSwarmSuite) SetUpTest(c *check.C) {
 	testRequires(c, DaemonIsLinux, SameHostDaemon)
 }
 
-func (s *DockerSwarmSuite) AddDaemon(c *check.C, joinSwarm, manager bool) *daemon.Swarm {
-	d := &daemon.Swarm{
-		Daemon: daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
-			Experimental: testEnv.DaemonInfo.ExperimentalBuild,
-		}),
-		Port: defaultSwarmPort + s.portIndex,
-	}
-	d.ListenAddr = fmt.Sprintf("0.0.0.0:%d", d.Port)
+func (s *DockerSwarmSuite) AddDaemon(c *check.C, joinSwarm, manager bool) *daemon.Daemon {
+	d := daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
+		Experimental: testEnv.DaemonInfo.ExperimentalBuild,
+	}, testdaemon.WithSwarmPort(defaultSwarmPort+s.portIndex))
 	args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"} // avoid networking conflicts
 	d.StartWithBusybox(c, args...)
 
@@ -346,12 +342,12 @@ func (s *DockerSwarmSuite) AddDaemon(c *check.C, joinSwarm, manager bool) *daemo
 			if manager {
 				token = tokens.Manager
 			}
-			c.Assert(d.Join(swarm.JoinRequest{
-				RemoteAddrs: []string{s.daemons[0].ListenAddr},
+			d.SwarmJoin(c, swarm.JoinRequest{
+				RemoteAddrs: []string{s.daemons[0].SwarmListenAddr()},
 				JoinToken:   token,
-			}), check.IsNil)
+			})
 		} else {
-			c.Assert(d.Init(swarm.InitRequest{}), check.IsNil)
+			d.SwarmInit(c, swarm.InitRequest{})
 		}
 	}
 

+ 17 - 4
integration-cli/daemon/daemon.go

@@ -38,10 +38,8 @@ type Config struct {
 // New returns a Daemon instance to be used for testing.
 // This will create a directory such as d123456789 in the folder specified by $DOCKER_INTEGRATION_DAEMON_DEST or $DEST.
 // The daemon will not automatically start.
-func New(t testingT, dockerBinary string, dockerdBinary string, config Config) *Daemon {
-	ops := []func(*daemon.Daemon){
-		daemon.WithDockerdBinary(dockerdBinary),
-	}
+func New(t testingT, dockerBinary string, dockerdBinary string, config Config, ops ...func(*daemon.Daemon)) *Daemon {
+	ops = append(ops, daemon.WithDockerdBinary(dockerdBinary))
 	if config.Experimental {
 		ops = append(ops, daemon.WithExperimental)
 	}
@@ -150,6 +148,21 @@ func (d *Daemon) WaitRun(contID string) error {
 	return WaitInspectWithArgs(d.dockerBinary, contID, "{{.State.Running}}", "true", 10*time.Second, args...)
 }
 
+// CmdRetryOutOfSequence tries the specified command against the current daemon for 10 times
+func (d *Daemon) CmdRetryOutOfSequence(args ...string) (string, error) {
+	for i := 0; ; i++ {
+		out, err := d.Cmd(args...)
+		if err != nil {
+			if strings.Contains(out, "update out of sequence") {
+				if i < 10 {
+					continue
+				}
+			}
+		}
+		return out, err
+	}
+}
+
 // WaitInspectWithArgs waits for the specified expression to be equals to the specified expected string in the given time.
 // Deprecated: use cli.WaitCmd instead
 func WaitInspectWithArgs(dockerBinary, name, expr, expected string, timeout time.Duration, arg ...string) error {

+ 15 - 479
integration-cli/daemon/daemon_swarm.go

@@ -3,7 +3,6 @@ package daemon // import "github.com/docker/docker/integration-cli/daemon"
 import (
 	"fmt"
 	"strings"
-	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
@@ -12,178 +11,12 @@ import (
 	"github.com/docker/docker/integration-cli/checker"
 	"github.com/go-check/check"
 	"github.com/gotestyourself/gotestyourself/assert"
-	"github.com/pkg/errors"
 	"golang.org/x/net/context"
 )
 
-// Swarm is a test daemon with helpers for participating in a swarm.
-type Swarm struct {
-	*Daemon
-	swarm.Info
-	Port       int
-	ListenAddr string
-}
-
-// Init initializes a new swarm cluster.
-func (d *Swarm) Init(req swarm.InitRequest) error {
-	if req.ListenAddr == "" {
-		req.ListenAddr = d.ListenAddr
-	}
-	cli, err := d.NewClient()
-	if err != nil {
-		return fmt.Errorf("initializing swarm: failed to create client %v", err)
-	}
-	defer cli.Close()
-	_, err = cli.SwarmInit(context.Background(), req)
-	if err != nil {
-		return fmt.Errorf("initializing swarm: %v", err)
-	}
-	info, err := d.SwarmInfo()
-	if err != nil {
-		return err
-	}
-	d.Info = info
-	return nil
-}
-
-// Join joins a daemon to an existing cluster.
-func (d *Swarm) Join(req swarm.JoinRequest) error {
-	if req.ListenAddr == "" {
-		req.ListenAddr = d.ListenAddr
-	}
-	cli, err := d.NewClient()
-	if err != nil {
-		return fmt.Errorf("joining swarm: failed to create client %v", err)
-	}
-	defer cli.Close()
-	err = cli.SwarmJoin(context.Background(), req)
-	if err != nil {
-		return fmt.Errorf("joining swarm: %v", err)
-	}
-	info, err := d.SwarmInfo()
-	if err != nil {
-		return err
-	}
-	d.Info = info
-	return nil
-}
-
-// Leave forces daemon to leave current cluster.
-func (d *Swarm) Leave(force bool) error {
-	cli, err := d.NewClient()
-	if err != nil {
-		return fmt.Errorf("leaving swarm: failed to create client %v", err)
-	}
-	defer cli.Close()
-	err = cli.SwarmLeave(context.Background(), force)
-	if err != nil {
-		err = fmt.Errorf("leaving swarm: %v", err)
-	}
-	return err
-}
-
-// SwarmInfo returns the swarm information of the daemon
-func (d *Swarm) SwarmInfo() (swarm.Info, error) {
-	cli, err := d.NewClient()
-	if err != nil {
-		return swarm.Info{}, fmt.Errorf("get swarm info: %v", err)
-	}
-
-	info, err := cli.Info(context.Background())
-	if err != nil {
-		return swarm.Info{}, fmt.Errorf("get swarm info: %v", err)
-	}
-
-	return info.Swarm, nil
-}
-
-// Unlock tries to unlock a locked swarm
-func (d *Swarm) Unlock(req swarm.UnlockRequest) error {
-	cli, err := d.NewClient()
-	if err != nil {
-		return fmt.Errorf("unlocking swarm: failed to create client %v", err)
-	}
-	defer cli.Close()
-	err = cli.SwarmUnlock(context.Background(), req)
-	if err != nil {
-		err = errors.Wrap(err, "unlocking swarm")
-	}
-	return err
-}
-
-// ServiceConstructor defines a swarm service constructor function
-type ServiceConstructor func(*swarm.Service)
-
-// NodeConstructor defines a swarm node constructor
-type NodeConstructor func(*swarm.Node)
-
-// SecretConstructor defines a swarm secret constructor
-type SecretConstructor func(*swarm.Secret)
-
-// ConfigConstructor defines a swarm config constructor
-type ConfigConstructor func(*swarm.Config)
-
-// SpecConstructor defines a swarm spec constructor
-type SpecConstructor func(*swarm.Spec)
-
-// CreateServiceWithOptions creates a swarm service given the specified service constructors
-// and auth config
-func (d *Swarm) CreateServiceWithOptions(c *check.C, opts types.ServiceCreateOptions, f ...ServiceConstructor) string {
-	var service swarm.Service
-	for _, fn := range f {
-		fn(&service)
-	}
-
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-	defer cancel()
-
-	res, err := cli.ServiceCreate(ctx, service.Spec, opts)
-	c.Assert(err, checker.IsNil)
-	return res.ID
-}
-
-// CreateService creates a swarm service given the specified service constructor
-func (d *Swarm) CreateService(c *check.C, f ...ServiceConstructor) string {
-	return d.CreateServiceWithOptions(c, types.ServiceCreateOptions{}, f...)
-}
-
-// GetService returns the swarm service corresponding to the specified id
-func (d *Swarm) GetService(c *check.C, id string) *swarm.Service {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	service, _, err := cli.ServiceInspectWithRaw(context.Background(), id, types.ServiceInspectOptions{})
-	c.Assert(err, checker.IsNil)
-	return &service
-}
-
-// GetServiceTasks returns the swarm tasks for the specified service
-func (d *Swarm) GetServiceTasks(c *check.C, service string) []swarm.Task {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	filterArgs := filters.NewArgs()
-	filterArgs.Add("desired-state", "running")
-	filterArgs.Add("service", service)
-
-	options := types.TaskListOptions{
-		Filters: filterArgs,
-	}
-
-	tasks, err := cli.TaskList(context.Background(), options)
-	c.Assert(err, checker.IsNil)
-	return tasks
-}
-
 // CheckServiceTasksInState returns the number of tasks with a matching state,
 // and optional message substring.
-func (d *Swarm) CheckServiceTasksInState(service string, state swarm.TaskState, message string) func(*check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckServiceTasksInState(service string, state swarm.TaskState, message string) func(*check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		tasks := d.GetServiceTasks(c, service)
 		var count int
@@ -200,7 +33,7 @@ func (d *Swarm) CheckServiceTasksInState(service string, state swarm.TaskState,
 
 // CheckServiceTasksInStateWithError returns the number of tasks with a matching state,
 // and optional message substring.
-func (d *Swarm) CheckServiceTasksInStateWithError(service string, state swarm.TaskState, errorMessage string) func(*check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckServiceTasksInStateWithError(service string, state swarm.TaskState, errorMessage string) func(*check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		tasks := d.GetServiceTasks(c, service)
 		var count int
@@ -216,12 +49,12 @@ func (d *Swarm) CheckServiceTasksInStateWithError(service string, state swarm.Ta
 }
 
 // CheckServiceRunningTasks returns the number of running tasks for the specified service
-func (d *Swarm) CheckServiceRunningTasks(service string) func(*check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckServiceRunningTasks(service string) func(*check.C) (interface{}, check.CommentInterface) {
 	return d.CheckServiceTasksInState(service, swarm.TaskStateRunning, "")
 }
 
 // CheckServiceUpdateState returns the current update state for the specified service
-func (d *Swarm) CheckServiceUpdateState(service string) func(*check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckServiceUpdateState(service string) func(*check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		service := d.GetService(c, service)
 		if service.UpdateStatus == nil {
@@ -232,7 +65,7 @@ func (d *Swarm) CheckServiceUpdateState(service string) func(*check.C) (interfac
 }
 
 // CheckPluginRunning returns the runtime state of the plugin
-func (d *Swarm) CheckPluginRunning(plugin string) func(c *check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckPluginRunning(plugin string) func(c *check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		apiclient, err := d.NewClient()
 		assert.NilError(c, err)
@@ -246,7 +79,7 @@ func (d *Swarm) CheckPluginRunning(plugin string) func(c *check.C) (interface{},
 }
 
 // CheckPluginImage returns the runtime state of the plugin
-func (d *Swarm) CheckPluginImage(plugin string) func(c *check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckPluginImage(plugin string) func(c *check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		apiclient, err := d.NewClient()
 		assert.NilError(c, err)
@@ -260,7 +93,7 @@ func (d *Swarm) CheckPluginImage(plugin string) func(c *check.C) (interface{}, c
 }
 
 // CheckServiceTasks returns the number of tasks for the specified service
-func (d *Swarm) CheckServiceTasks(service string) func(*check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckServiceTasks(service string) func(*check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		tasks := d.GetServiceTasks(c, service)
 		return len(tasks), nil
@@ -268,7 +101,7 @@ func (d *Swarm) CheckServiceTasks(service string) func(*check.C) (interface{}, c
 }
 
 // CheckRunningTaskNetworks returns the number of times each network is referenced from a task.
-func (d *Swarm) CheckRunningTaskNetworks(c *check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckRunningTaskNetworks(c *check.C) (interface{}, check.CommentInterface) {
 	cli, err := d.NewClient()
 	c.Assert(err, checker.IsNil)
 	defer cli.Close()
@@ -293,7 +126,7 @@ func (d *Swarm) CheckRunningTaskNetworks(c *check.C) (interface{}, check.Comment
 }
 
 // CheckRunningTaskImages returns the times each image is running as a task.
-func (d *Swarm) CheckRunningTaskImages(c *check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckRunningTaskImages(c *check.C) (interface{}, check.CommentInterface) {
 	cli, err := d.NewClient()
 	c.Assert(err, checker.IsNil)
 	defer cli.Close()
@@ -318,7 +151,7 @@ func (d *Swarm) CheckRunningTaskImages(c *check.C) (interface{}, check.CommentIn
 }
 
 // CheckNodeReadyCount returns the number of ready node on the swarm
-func (d *Swarm) CheckNodeReadyCount(c *check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckNodeReadyCount(c *check.C) (interface{}, check.CommentInterface) {
 	nodes := d.ListNodes(c)
 	var readyCount int
 	for _, node := range nodes {
@@ -329,303 +162,21 @@ func (d *Swarm) CheckNodeReadyCount(c *check.C) (interface{}, check.CommentInter
 	return readyCount, nil
 }
 
-// GetTask returns the swarm task identified by the specified id
-func (d *Swarm) GetTask(c *check.C, id string) swarm.Task {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	task, _, err := cli.TaskInspectWithRaw(context.Background(), id)
-	c.Assert(err, checker.IsNil)
-	return task
-}
-
-// UpdateService updates a swarm service with the specified service constructor
-func (d *Swarm) UpdateService(c *check.C, service *swarm.Service, f ...ServiceConstructor) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	for _, fn := range f {
-		fn(service)
-	}
-
-	_, err = cli.ServiceUpdate(context.Background(), service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
-	c.Assert(err, checker.IsNil)
-}
-
-// RemoveService removes the specified service
-func (d *Swarm) RemoveService(c *check.C, id string) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	err = cli.ServiceRemove(context.Background(), id)
-	c.Assert(err, checker.IsNil)
-}
-
-// GetNode returns a swarm node identified by the specified id
-func (d *Swarm) GetNode(c *check.C, id string) *swarm.Node {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	node, _, err := cli.NodeInspectWithRaw(context.Background(), id)
-	c.Assert(err, checker.IsNil)
-	c.Assert(node.ID, checker.Equals, id)
-	return &node
-}
-
-// RemoveNode removes the specified node
-func (d *Swarm) RemoveNode(c *check.C, id string, force bool) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	options := types.NodeRemoveOptions{
-		Force: force,
-	}
-	err = cli.NodeRemove(context.Background(), id, options)
-	c.Assert(err, checker.IsNil)
-}
-
-// UpdateNode updates a swarm node with the specified node constructor
-func (d *Swarm) UpdateNode(c *check.C, id string, f ...NodeConstructor) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	for i := 0; ; i++ {
-		node := d.GetNode(c, id)
-		for _, fn := range f {
-			fn(node)
-		}
-
-		err = cli.NodeUpdate(context.Background(), node.ID, node.Version, node.Spec)
-		if i < 10 && err != nil && strings.Contains(err.Error(), "update out of sequence") {
-			time.Sleep(100 * time.Millisecond)
-			continue
-		}
-		c.Assert(err, checker.IsNil)
-		return
-	}
-}
-
-// ListNodes returns the list of the current swarm nodes
-func (d *Swarm) ListNodes(c *check.C) []swarm.Node {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	nodes, err := cli.NodeList(context.Background(), types.NodeListOptions{})
-	c.Assert(err, checker.IsNil)
-
-	return nodes
-}
-
-// ListServices returns the list of the current swarm services
-func (d *Swarm) ListServices(c *check.C) []swarm.Service {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	services, err := cli.ServiceList(context.Background(), types.ServiceListOptions{})
-	c.Assert(err, checker.IsNil)
-	return services
-}
-
-// CreateSecret creates a secret given the specified spec
-func (d *Swarm) CreateSecret(c *check.C, secretSpec swarm.SecretSpec) string {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	scr, err := cli.SecretCreate(context.Background(), secretSpec)
-	c.Assert(err, checker.IsNil)
-
-	return scr.ID
-}
-
-// ListSecrets returns the list of the current swarm secrets
-func (d *Swarm) ListSecrets(c *check.C) []swarm.Secret {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	secrets, err := cli.SecretList(context.Background(), types.SecretListOptions{})
-	c.Assert(err, checker.IsNil)
-	return secrets
-}
-
-// GetSecret returns a swarm secret identified by the specified id
-func (d *Swarm) GetSecret(c *check.C, id string) *swarm.Secret {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	secret, _, err := cli.SecretInspectWithRaw(context.Background(), id)
-	c.Assert(err, checker.IsNil)
-	return &secret
-}
-
-// DeleteSecret removes the swarm secret identified by the specified id
-func (d *Swarm) DeleteSecret(c *check.C, id string) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	err = cli.SecretRemove(context.Background(), id)
-	c.Assert(err, checker.IsNil)
-}
-
-// UpdateSecret updates the swarm secret identified by the specified id
-// Currently, only label update is supported.
-func (d *Swarm) UpdateSecret(c *check.C, id string, f ...SecretConstructor) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	secret := d.GetSecret(c, id)
-	for _, fn := range f {
-		fn(secret)
-	}
-
-	err = cli.SecretUpdate(context.Background(), secret.ID, secret.Version, secret.Spec)
-
-	c.Assert(err, checker.IsNil)
-}
-
-// CreateConfig creates a config given the specified spec
-func (d *Swarm) CreateConfig(c *check.C, configSpec swarm.ConfigSpec) string {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	scr, err := cli.ConfigCreate(context.Background(), configSpec)
-	c.Assert(err, checker.IsNil)
-	return scr.ID
-}
-
-// ListConfigs returns the list of the current swarm configs
-func (d *Swarm) ListConfigs(c *check.C) []swarm.Config {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	configs, err := cli.ConfigList(context.Background(), types.ConfigListOptions{})
-	c.Assert(err, checker.IsNil)
-	return configs
-}
-
-// GetConfig returns a swarm config identified by the specified id
-func (d *Swarm) GetConfig(c *check.C, id string) *swarm.Config {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	config, _, err := cli.ConfigInspectWithRaw(context.Background(), id)
-	c.Assert(err, checker.IsNil)
-	return &config
-}
-
-// DeleteConfig removes the swarm config identified by the specified id
-func (d *Swarm) DeleteConfig(c *check.C, id string) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	err = cli.ConfigRemove(context.Background(), id)
-	c.Assert(err, checker.IsNil)
-}
-
-// UpdateConfig updates the swarm config identified by the specified id
-// Currently, only label update is supported.
-func (d *Swarm) UpdateConfig(c *check.C, id string, f ...ConfigConstructor) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	config := d.GetConfig(c, id)
-	for _, fn := range f {
-		fn(config)
-	}
-
-	err = cli.ConfigUpdate(context.Background(), config.ID, config.Version, config.Spec)
-	c.Assert(err, checker.IsNil)
-}
-
-// GetSwarm returns the current swarm object
-func (d *Swarm) GetSwarm(c *check.C) swarm.Swarm {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	sw, err := cli.SwarmInspect(context.Background())
-	c.Assert(err, checker.IsNil)
-	return sw
-}
-
-// UpdateSwarm updates the current swarm object with the specified spec constructors
-func (d *Swarm) UpdateSwarm(c *check.C, f ...SpecConstructor) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	sw := d.GetSwarm(c)
-	for _, fn := range f {
-		fn(&sw.Spec)
-	}
-
-	err = cli.SwarmUpdate(context.Background(), sw.Version, sw.Spec, swarm.UpdateFlags{})
-	c.Assert(err, checker.IsNil)
-}
-
-// RotateTokens update the swarm to rotate tokens
-func (d *Swarm) RotateTokens(c *check.C) {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	sw, err := cli.SwarmInspect(context.Background())
-	c.Assert(err, checker.IsNil)
-
-	flags := swarm.UpdateFlags{
-		RotateManagerToken: true,
-		RotateWorkerToken:  true,
-	}
-
-	err = cli.SwarmUpdate(context.Background(), sw.Version, sw.Spec, flags)
-	c.Assert(err, checker.IsNil)
-}
-
-// JoinTokens returns the current swarm join tokens
-func (d *Swarm) JoinTokens(c *check.C) swarm.JoinTokens {
-	cli, err := d.NewClient()
-	c.Assert(err, checker.IsNil)
-	defer cli.Close()
-
-	sw, err := cli.SwarmInspect(context.Background())
-	c.Assert(err, checker.IsNil)
-	return sw.JoinTokens
-}
-
 // CheckLocalNodeState returns the current swarm node state
-func (d *Swarm) CheckLocalNodeState(c *check.C) (interface{}, check.CommentInterface) {
-	info, err := d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+func (d *Daemon) CheckLocalNodeState(c *check.C) (interface{}, check.CommentInterface) {
+	info := d.SwarmInfo(c)
 	return info.LocalNodeState, nil
 }
 
 // CheckControlAvailable returns the current swarm control available
-func (d *Swarm) CheckControlAvailable(c *check.C) (interface{}, check.CommentInterface) {
-	info, err := d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+func (d *Daemon) CheckControlAvailable(c *check.C) (interface{}, check.CommentInterface) {
+	info := d.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 	return info.ControlAvailable, nil
 }
 
 // CheckLeader returns whether there is a leader on the swarm or not
-func (d *Swarm) CheckLeader(c *check.C) (interface{}, check.CommentInterface) {
+func (d *Daemon) CheckLeader(c *check.C) (interface{}, check.CommentInterface) {
 	cli, err := d.NewClient()
 	c.Assert(err, checker.IsNil)
 	defer cli.Close()
@@ -644,18 +195,3 @@ func (d *Swarm) CheckLeader(c *check.C) (interface{}, check.CommentInterface) {
 	}
 	return fmt.Errorf("no leader"), check.Commentf("could not find leader")
 }
-
-// CmdRetryOutOfSequence tries the specified command against the current daemon for 10 times
-func (d *Swarm) CmdRetryOutOfSequence(args ...string) (string, error) {
-	for i := 0; ; i++ {
-		out, err := d.Cmd(args...)
-		if err != nil {
-			if strings.Contains(out, "update out of sequence") {
-				if i < 10 {
-					continue
-				}
-			}
-		}
-		return out, err
-	}
-}

+ 2 - 2
integration-cli/daemon_swarm_hack_test.go

@@ -5,11 +5,11 @@ import (
 	"github.com/go-check/check"
 )
 
-func (s *DockerSwarmSuite) getDaemon(c *check.C, nodeID string) *daemon.Swarm {
+func (s *DockerSwarmSuite) getDaemon(c *check.C, nodeID string) *daemon.Daemon {
 	s.daemonsLock.Lock()
 	defer s.daemonsLock.Unlock()
 	for _, d := range s.daemons {
-		if d.NodeID == nodeID {
+		if d.NodeID() == nodeID {
 			return d
 		}
 	}

+ 6 - 7
integration-cli/docker_api_swarm_node_test.go

@@ -21,8 +21,8 @@ func (s *DockerSwarmSuite) TestAPISwarmListNodes(c *check.C) {
 
 loop0:
 	for _, n := range nodes {
-		for _, d := range []*daemon.Swarm{d1, d2, d3} {
-			if n.ID == d.NodeID {
+		for _, d := range []*daemon.Daemon{d1, d2, d3} {
+			if n.ID == d.NodeID() {
 				continue loop0
 			}
 		}
@@ -53,8 +53,7 @@ func (s *DockerSwarmSuite) TestAPISwarmNodeRemove(c *check.C) {
 	c.Assert(len(nodes), checker.Equals, 3, check.Commentf("nodes: %#v", nodes))
 
 	// Getting the info so we can take the NodeID
-	d2Info, err := d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	d2Info := d2.SwarmInfo(c)
 
 	// forceful removal of d2 should work
 	d1.RemoveNode(c, d2Info.NodeID, true)
@@ -88,14 +87,14 @@ func (s *DockerSwarmSuite) TestAPISwarmNodeDrainPause(c *check.C) {
 	waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d2.CheckActiveContainerCount), checker.Equals, instances)
 
 	// drain d2, all containers should move to d1
-	d1.UpdateNode(c, d2.NodeID, func(n *swarm.Node) {
+	d1.UpdateNode(c, d2.NodeID(), func(n *swarm.Node) {
 		n.Spec.Availability = swarm.NodeAvailabilityDrain
 	})
 	waitAndAssert(c, defaultReconciliationTimeout, d1.CheckActiveContainerCount, checker.Equals, instances)
 	waitAndAssert(c, defaultReconciliationTimeout, d2.CheckActiveContainerCount, checker.Equals, 0)
 
 	// set d2 back to active
-	d1.UpdateNode(c, d2.NodeID, func(n *swarm.Node) {
+	d1.UpdateNode(c, d2.NodeID(), func(n *swarm.Node) {
 		n.Spec.Availability = swarm.NodeAvailabilityActive
 	})
 
@@ -115,7 +114,7 @@ func (s *DockerSwarmSuite) TestAPISwarmNodeDrainPause(c *check.C) {
 	d2ContainerCount := len(d2.ActiveContainers())
 
 	// set d2 to paused, scale service up, only d1 gets new tasks
-	d1.UpdateNode(c, d2.NodeID, func(n *swarm.Node) {
+	d1.UpdateNode(c, d2.NodeID(), func(n *swarm.Node) {
 		n.Spec.Availability = swarm.NodeAvailabilityPause
 	})
 

+ 10 - 9
integration-cli/docker_api_swarm_service_test.go

@@ -15,12 +15,13 @@ import (
 	"github.com/docker/docker/integration-cli/checker"
 	"github.com/docker/docker/integration-cli/daemon"
 	"github.com/docker/docker/integration-cli/fixtures/plugin"
+	testdaemon "github.com/docker/docker/internal/test/daemon"
 	"github.com/go-check/check"
 	"golang.org/x/net/context"
 	"golang.org/x/sys/unix"
 )
 
-func setPortConfig(portConfig []swarm.PortConfig) daemon.ServiceConstructor {
+func setPortConfig(portConfig []swarm.PortConfig) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		if s.Spec.EndpointSpec == nil {
 			s.Spec.EndpointSpec = &swarm.EndpointSpec{}
@@ -140,7 +141,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesCreateGlobal(c *check.C) {
 
 func (s *DockerSwarmSuite) TestAPISwarmServicesUpdate(c *check.C) {
 	const nodeCount = 3
-	var daemons [nodeCount]*daemon.Swarm
+	var daemons [nodeCount]*daemon.Daemon
 	for i := 0; i < nodeCount; i++ {
 		daemons[i] = s.AddDaemon(c, true, i == 0)
 	}
@@ -309,7 +310,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesUpdateStartFirst(c *check.C) {
 
 func (s *DockerSwarmSuite) TestAPISwarmServicesFailedUpdate(c *check.C) {
 	const nodeCount = 3
-	var daemons [nodeCount]*daemon.Swarm
+	var daemons [nodeCount]*daemon.Daemon
 	for i := 0; i < nodeCount; i++ {
 		daemons[i] = s.AddDaemon(c, true, i == 0)
 	}
@@ -349,7 +350,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesFailedUpdate(c *check.C) {
 
 func (s *DockerSwarmSuite) TestAPISwarmServiceConstraintRole(c *check.C) {
 	const nodeCount = 3
-	var daemons [nodeCount]*daemon.Swarm
+	var daemons [nodeCount]*daemon.Daemon
 	for i := 0; i < nodeCount; i++ {
 		daemons[i] = s.AddDaemon(c, true, i == 0)
 	}
@@ -401,7 +402,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServiceConstraintRole(c *check.C) {
 
 func (s *DockerSwarmSuite) TestAPISwarmServiceConstraintLabel(c *check.C) {
 	const nodeCount = 3
-	var daemons [nodeCount]*daemon.Swarm
+	var daemons [nodeCount]*daemon.Daemon
 	for i := 0; i < nodeCount; i++ {
 		daemons[i] = s.AddDaemon(c, true, i == 0)
 	}
@@ -496,7 +497,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServiceConstraintLabel(c *check.C) {
 
 func (s *DockerSwarmSuite) TestAPISwarmServicePlacementPrefs(c *check.C) {
 	const nodeCount = 3
-	var daemons [nodeCount]*daemon.Swarm
+	var daemons [nodeCount]*daemon.Daemon
 	for i := 0; i < nodeCount; i++ {
 		daemons[i] = s.AddDaemon(c, true, i == 0)
 	}
@@ -551,9 +552,9 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesStateReporting(c *check.C) {
 
 	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} {
+	getContainers := func() map[string]*daemon.Daemon {
+		m := make(map[string]*daemon.Daemon)
+		for _, d := range []*daemon.Daemon{d1, d2, d3} {
 			for _, id := range d.ActiveContainers() {
 				m[id] = d
 			}

+ 126 - 114
integration-cli/docker_api_swarm_test.go

@@ -23,6 +23,7 @@ import (
 	"github.com/docker/docker/integration-cli/checker"
 	"github.com/docker/docker/integration-cli/daemon"
 	"github.com/docker/docker/integration-cli/request"
+	testdaemon "github.com/docker/docker/internal/test/daemon"
 	"github.com/docker/swarmkit/ca"
 	"github.com/go-check/check"
 	"github.com/gotestyourself/gotestyourself/assert"
@@ -35,30 +36,30 @@ var defaultReconciliationTimeout = 30 * time.Second
 func (s *DockerSwarmSuite) TestAPISwarmInit(c *check.C) {
 	// todo: should find a better way to verify that components are running than /info
 	d1 := s.AddDaemon(c, true, true)
-	info, err := d1.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d1.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.True)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 	c.Assert(info.Cluster.RootRotationInProgress, checker.False)
 
 	d2 := s.AddDaemon(c, true, false)
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.False)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 
 	// Leaving cluster
-	c.Assert(d2.Leave(false), checker.IsNil)
+	c.Assert(d2.SwarmLeave(false), checker.IsNil)
 
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.False)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
-	c.Assert(d2.Join(swarm.JoinRequest{JoinToken: d1.JoinTokens(c).Worker, RemoteAddrs: []string{d1.ListenAddr}}), checker.IsNil)
+	d2.SwarmJoin(c, swarm.JoinRequest{
+		ListenAddr:  d1.SwarmListenAddr(),
+		JoinToken:   d1.JoinTokens(c).Worker,
+		RemoteAddrs: []string{d1.SwarmListenAddr()},
+	})
 
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.False)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 
@@ -69,93 +70,100 @@ func (s *DockerSwarmSuite) TestAPISwarmInit(c *check.C) {
 	d1.Start(c)
 	d2.Start(c)
 
-	info, err = d1.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d1.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.True)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.False)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 }
 
 func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *check.C) {
 	d1 := s.AddDaemon(c, false, false)
-	c.Assert(d1.Init(swarm.InitRequest{}), checker.IsNil)
+	d1.SwarmInit(c, swarm.InitRequest{})
 
 	// todo: error message differs depending if some components of token are valid
 
 	d2 := s.AddDaemon(c, false, false)
-	err := d2.Join(swarm.JoinRequest{RemoteAddrs: []string{d1.ListenAddr}})
+	c2 := d2.NewClientT(c)
+	err := c2.SwarmJoin(context.Background(), swarm.JoinRequest{
+		ListenAddr:  d2.SwarmListenAddr(),
+		RemoteAddrs: []string{d1.SwarmListenAddr()},
+	})
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "join token is necessary")
-	info, err := d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
-	err = d2.Join(swarm.JoinRequest{JoinToken: "foobaz", RemoteAddrs: []string{d1.ListenAddr}})
+	err = c2.SwarmJoin(context.Background(), swarm.JoinRequest{
+		ListenAddr:  d2.SwarmListenAddr(),
+		JoinToken:   "foobaz",
+		RemoteAddrs: []string{d1.SwarmListenAddr()},
+	})
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "invalid join token")
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
 	workerToken := d1.JoinTokens(c).Worker
 
-	c.Assert(d2.Join(swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.ListenAddr}}), checker.IsNil)
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	d2.SwarmJoin(c, swarm.JoinRequest{
+		ListenAddr:  d2.SwarmListenAddr(),
+		JoinToken:   workerToken,
+		RemoteAddrs: []string{d1.SwarmListenAddr()},
+	})
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
-	c.Assert(d2.Leave(false), checker.IsNil)
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	c.Assert(d2.SwarmLeave(false), checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
 	// change tokens
 	d1.RotateTokens(c)
 
-	err = d2.Join(swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.ListenAddr}})
+	err = c2.SwarmJoin(context.Background(), swarm.JoinRequest{
+		ListenAddr:  d2.SwarmListenAddr(),
+		JoinToken:   workerToken,
+		RemoteAddrs: []string{d1.SwarmListenAddr()},
+	})
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "join token is necessary")
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
 	workerToken = d1.JoinTokens(c).Worker
 
-	c.Assert(d2.Join(swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.ListenAddr}}), checker.IsNil)
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	d2.SwarmJoin(c, swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.SwarmListenAddr()}})
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
-	c.Assert(d2.Leave(false), checker.IsNil)
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	c.Assert(d2.SwarmLeave(false), checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
 	// change spec, don't change tokens
 	d1.UpdateSwarm(c, func(s *swarm.Spec) {})
 
-	err = d2.Join(swarm.JoinRequest{RemoteAddrs: []string{d1.ListenAddr}})
+	err = c2.SwarmJoin(context.Background(), swarm.JoinRequest{
+		ListenAddr:  d2.SwarmListenAddr(),
+		RemoteAddrs: []string{d1.SwarmListenAddr()},
+	})
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "join token is necessary")
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
-	c.Assert(d2.Join(swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.ListenAddr}}), checker.IsNil)
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	d2.SwarmJoin(c, swarm.JoinRequest{JoinToken: workerToken, RemoteAddrs: []string{d1.SwarmListenAddr()}})
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
-	c.Assert(d2.Leave(false), checker.IsNil)
-	info, err = d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	c.Assert(d2.SwarmLeave(false), checker.IsNil)
+	info = d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 }
 
 func (s *DockerSwarmSuite) TestUpdateSwarmAddExternalCA(c *check.C) {
 	d1 := s.AddDaemon(c, false, false)
-	c.Assert(d1.Init(swarm.InitRequest{}), checker.IsNil)
+	d1.SwarmInit(c, swarm.InitRequest{})
 	d1.UpdateSwarm(c, func(s *swarm.Spec) {
 		s.CAConfig.ExternalCAs = []*swarm.ExternalCA{
 			{
@@ -169,8 +177,7 @@ func (s *DockerSwarmSuite) TestUpdateSwarmAddExternalCA(c *check.C) {
 			},
 		}
 	})
-	info, err := d1.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d1.SwarmInfo(c)
 	c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs, checker.HasLen, 2)
 	c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "")
 	c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, "cacert")
@@ -182,28 +189,32 @@ func (s *DockerSwarmSuite) TestAPISwarmCAHash(c *check.C) {
 	splitToken := strings.Split(d1.JoinTokens(c).Worker, "-")
 	splitToken[2] = "1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e"
 	replacementToken := strings.Join(splitToken, "-")
-	err := d2.Join(swarm.JoinRequest{JoinToken: replacementToken, RemoteAddrs: []string{d1.ListenAddr}})
+	c2 := d2.NewClientT(c)
+	err := c2.SwarmJoin(context.Background(), swarm.JoinRequest{
+		ListenAddr:  d2.SwarmListenAddr(),
+		JoinToken:   replacementToken,
+		RemoteAddrs: []string{d1.SwarmListenAddr()},
+	})
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "remote CA does not match fingerprint")
 }
 
 func (s *DockerSwarmSuite) TestAPISwarmPromoteDemote(c *check.C) {
 	d1 := s.AddDaemon(c, false, false)
-	c.Assert(d1.Init(swarm.InitRequest{}), checker.IsNil)
+	d1.SwarmInit(c, swarm.InitRequest{})
 	d2 := s.AddDaemon(c, true, false)
 
-	info, err := d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d2.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.False)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 
-	d1.UpdateNode(c, d2.NodeID, func(n *swarm.Node) {
+	d1.UpdateNode(c, d2.NodeID(), func(n *swarm.Node) {
 		n.Spec.Role = swarm.NodeRoleManager
 	})
 
 	waitAndAssert(c, defaultReconciliationTimeout, d2.CheckControlAvailable, checker.True)
 
-	d1.UpdateNode(c, d2.NodeID, func(n *swarm.Node) {
+	d1.UpdateNode(c, d2.NodeID(), func(n *swarm.Node) {
 		n.Spec.Role = swarm.NodeRoleWorker
 	})
 
@@ -228,7 +239,7 @@ func (s *DockerSwarmSuite) TestAPISwarmPromoteDemote(c *check.C) {
 	}, checker.Equals, "swarm-worker")
 
 	// Demoting last node should fail
-	node := d1.GetNode(c, d1.NodeID)
+	node := d1.GetNode(c, d1.NodeID())
 	node.Spec.Role = swarm.NodeRoleWorker
 	url := fmt.Sprintf("/nodes/%s/update?version=%d", node.ID, node.Version.Index)
 	res, body, err := request.DoOnHost(d1.Sock(), url, request.Method("POST"), request.JSONBody(node.Spec))
@@ -246,13 +257,12 @@ func (s *DockerSwarmSuite) TestAPISwarmPromoteDemote(c *check.C) {
 	if !strings.Contains(string(b), "last manager of the swarm") {
 		c.Assert(string(b), checker.Contains, "this would result in a loss of quorum")
 	}
-	info, err = d1.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d1.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 	c.Assert(info.ControlAvailable, checker.True)
 
 	// Promote already demoted node
-	d1.UpdateNode(c, d2.NodeID, func(n *swarm.Node) {
+	d1.UpdateNode(c, d2.NodeID(), func(n *swarm.Node) {
 		n.Spec.Role = swarm.NodeRoleManager
 	})
 
@@ -278,7 +288,7 @@ func (s *DockerSwarmSuite) TestAPISwarmLeaderProxy(c *check.C) {
 
 	// 3 services should be started now, because the requests were proxied to leader
 	// query each node and make sure it returns 3 services
-	for _, d := range []*daemon.Swarm{d1, d2, d3} {
+	for _, d := range []*daemon.Daemon{d1, d2, d3} {
 		services := d.ListServices(c)
 		c.Assert(services, checker.HasLen, 3)
 	}
@@ -291,23 +301,23 @@ func (s *DockerSwarmSuite) TestAPISwarmLeaderElection(c *check.C) {
 	d3 := s.AddDaemon(c, true, true)
 
 	// assert that the first node we made is the leader, and the other two are followers
-	c.Assert(d1.GetNode(c, d1.NodeID).ManagerStatus.Leader, checker.True)
-	c.Assert(d1.GetNode(c, d2.NodeID).ManagerStatus.Leader, checker.False)
-	c.Assert(d1.GetNode(c, d3.NodeID).ManagerStatus.Leader, checker.False)
+	c.Assert(d1.GetNode(c, d1.NodeID()).ManagerStatus.Leader, checker.True)
+	c.Assert(d1.GetNode(c, d2.NodeID()).ManagerStatus.Leader, checker.False)
+	c.Assert(d1.GetNode(c, d3.NodeID()).ManagerStatus.Leader, checker.False)
 
 	d1.Stop(c)
 
 	var (
-		leader    *daemon.Swarm   // keep track of leader
-		followers []*daemon.Swarm // keep track of followers
+		leader    *daemon.Daemon   // keep track of leader
+		followers []*daemon.Daemon // keep track of followers
 	)
-	checkLeader := func(nodes ...*daemon.Swarm) checkF {
+	checkLeader := func(nodes ...*daemon.Daemon) checkF {
 		return func(c *check.C) (interface{}, check.CommentInterface) {
 			// clear these out before each run
 			leader = nil
 			followers = nil
 			for _, d := range nodes {
-				if d.GetNode(c, d.NodeID).ManagerStatus.Leader {
+				if d.GetNode(c, d.NodeID()).ManagerStatus.Leader {
 					leader = d
 				} else {
 					followers = append(followers, d)
@@ -344,7 +354,7 @@ func (s *DockerSwarmSuite) TestAPISwarmLeaderElection(c *check.C) {
 	c.Assert(leader, checker.NotNil)
 	c.Assert(followers, checker.HasLen, 2)
 	// and that after we added d1 back, the leader hasn't changed
-	c.Assert(leader.NodeID, checker.Equals, stableleader.NodeID)
+	c.Assert(leader.NodeID(), checker.Equals, stableleader.NodeID())
 }
 
 func (s *DockerSwarmSuite) TestAPISwarmRaftQuorum(c *check.C) {
@@ -400,8 +410,8 @@ func (s *DockerSwarmSuite) TestAPISwarmLeaveRemovesContainer(c *check.C) {
 
 	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, instances+1)
 
-	c.Assert(d.Leave(false), checker.NotNil)
-	c.Assert(d.Leave(true), checker.IsNil)
+	c.Assert(d.SwarmLeave(false), checker.NotNil)
+	c.Assert(d.SwarmLeave(true), checker.IsNil)
 
 	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
 
@@ -420,17 +430,18 @@ func (s *DockerSwarmSuite) TestAPISwarmLeaveOnPendingJoin(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	id = strings.TrimSpace(id)
 
-	err = d2.Join(swarm.JoinRequest{
+	c2 := d2.NewClientT(c)
+	err = c2.SwarmJoin(context.Background(), swarm.JoinRequest{
+		ListenAddr:  d2.SwarmListenAddr(),
 		RemoteAddrs: []string{"123.123.123.123:1234"},
 	})
 	c.Assert(err, check.NotNil)
 	c.Assert(err.Error(), checker.Contains, "Timeout was reached")
 
-	info, err := d2.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d2.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStatePending)
 
-	c.Assert(d2.Leave(true), checker.IsNil)
+	c.Assert(d2.SwarmLeave(true), checker.IsNil)
 
 	waitAndAssert(c, defaultReconciliationTimeout, d2.CheckActiveContainerCount, checker.Equals, 1)
 
@@ -443,7 +454,9 @@ func (s *DockerSwarmSuite) TestAPISwarmLeaveOnPendingJoin(c *check.C) {
 func (s *DockerSwarmSuite) TestAPISwarmRestoreOnPendingJoin(c *check.C) {
 	testRequires(c, Network)
 	d := s.AddDaemon(c, false, false)
-	err := d.Join(swarm.JoinRequest{
+	client := d.NewClientT(c)
+	err := client.SwarmJoin(context.Background(), swarm.JoinRequest{
+		ListenAddr:  d.SwarmListenAddr(),
 		RemoteAddrs: []string{"123.123.123.123:1234"},
 	})
 	c.Assert(err, check.NotNil)
@@ -454,8 +467,7 @@ func (s *DockerSwarmSuite) TestAPISwarmRestoreOnPendingJoin(c *check.C) {
 	d.Stop(c)
 	d.Start(c)
 
-	info, err := d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 }
 
@@ -539,7 +551,7 @@ func (s *DockerSwarmSuite) TestAPISwarmForceNewCluster(c *check.C) {
 	waitAndAssert(c, defaultReconciliationTimeout, reducedCheck(sumAsIntegers, d1.CheckActiveContainerCount, d2.CheckActiveContainerCount), checker.Equals, instances)
 
 	// drain d2, all containers should move to d1
-	d1.UpdateNode(c, d2.NodeID, func(n *swarm.Node) {
+	d1.UpdateNode(c, d2.NodeID(), func(n *swarm.Node) {
 		n.Spec.Availability = swarm.NodeAvailabilityDrain
 	})
 	waitAndAssert(c, defaultReconciliationTimeout, d1.CheckActiveContainerCount, checker.Equals, instances)
@@ -547,16 +559,15 @@ func (s *DockerSwarmSuite) TestAPISwarmForceNewCluster(c *check.C) {
 
 	d2.Stop(c)
 
-	c.Assert(d1.Init(swarm.InitRequest{
+	d1.SwarmInit(c, swarm.InitRequest{
 		ForceNewCluster: true,
 		Spec:            swarm.Spec{},
-	}), checker.IsNil)
+	})
 
 	waitAndAssert(c, defaultReconciliationTimeout, d1.CheckActiveContainerCount, checker.Equals, instances)
 
 	d3 := s.AddDaemon(c, true, true)
-	info, err := d3.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d3.SwarmInfo(c)
 	c.Assert(info.ControlAvailable, checker.True)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 
@@ -622,7 +633,7 @@ func serviceForUpdate(s *swarm.Service) {
 	s.Spec.Name = "updatetest"
 }
 
-func setInstances(replicas int) daemon.ServiceConstructor {
+func setInstances(replicas int) testdaemon.ServiceConstructor {
 	ureplicas := uint64(replicas)
 	return func(s *swarm.Service) {
 		s.Spec.Mode = swarm.ServiceMode{
@@ -633,7 +644,7 @@ func setInstances(replicas int) daemon.ServiceConstructor {
 	}
 }
 
-func setUpdateOrder(order string) daemon.ServiceConstructor {
+func setUpdateOrder(order string) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		if s.Spec.UpdateConfig == nil {
 			s.Spec.UpdateConfig = &swarm.UpdateConfig{}
@@ -642,7 +653,7 @@ func setUpdateOrder(order string) daemon.ServiceConstructor {
 	}
 }
 
-func setRollbackOrder(order string) daemon.ServiceConstructor {
+func setRollbackOrder(order string) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		if s.Spec.RollbackConfig == nil {
 			s.Spec.RollbackConfig = &swarm.UpdateConfig{}
@@ -651,7 +662,7 @@ func setRollbackOrder(order string) daemon.ServiceConstructor {
 	}
 }
 
-func setImage(image string) daemon.ServiceConstructor {
+func setImage(image string) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		if s.Spec.TaskTemplate.ContainerSpec == nil {
 			s.Spec.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
@@ -660,25 +671,25 @@ func setImage(image string) daemon.ServiceConstructor {
 	}
 }
 
-func setFailureAction(failureAction string) daemon.ServiceConstructor {
+func setFailureAction(failureAction string) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		s.Spec.UpdateConfig.FailureAction = failureAction
 	}
 }
 
-func setMaxFailureRatio(maxFailureRatio float32) daemon.ServiceConstructor {
+func setMaxFailureRatio(maxFailureRatio float32) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		s.Spec.UpdateConfig.MaxFailureRatio = maxFailureRatio
 	}
 }
 
-func setParallelism(parallelism uint64) daemon.ServiceConstructor {
+func setParallelism(parallelism uint64) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		s.Spec.UpdateConfig.Parallelism = parallelism
 	}
 }
 
-func setConstraints(constraints []string) daemon.ServiceConstructor {
+func setConstraints(constraints []string) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		if s.Spec.TaskTemplate.Placement == nil {
 			s.Spec.TaskTemplate.Placement = &swarm.Placement{}
@@ -687,7 +698,7 @@ func setConstraints(constraints []string) daemon.ServiceConstructor {
 	}
 }
 
-func setPlacementPrefs(prefs []swarm.PlacementPreference) daemon.ServiceConstructor {
+func setPlacementPrefs(prefs []swarm.PlacementPreference) testdaemon.ServiceConstructor {
 	return func(s *swarm.Service) {
 		if s.Spec.TaskTemplate.Placement == nil {
 			s.Spec.TaskTemplate.Placement = &swarm.Placement{}
@@ -702,18 +713,19 @@ func setGlobalMode(s *swarm.Service) {
 	}
 }
 
-func checkClusterHealth(c *check.C, cl []*daemon.Swarm, managerCount, workerCount int) {
+func checkClusterHealth(c *check.C, cl []*daemon.Daemon, managerCount, workerCount int) {
 	var totalMCount, totalWCount int
 
 	for _, d := range cl {
 		var (
 			info swarm.Info
-			err  error
 		)
 
 		// check info in a waitAndAssert, because if the cluster doesn't have a leader, `info` will return an error
 		checkInfo := func(c *check.C) (interface{}, check.CommentInterface) {
-			info, err = d.SwarmInfo()
+			client := d.NewClientT(c)
+			daemonInfo, err := client.Info(context.Background())
+			info = daemonInfo.Swarm
 			return err, check.Commentf("cluster not ready in time")
 		}
 		waitAndAssert(c, defaultReconciliationTimeout, checkInfo, checker.IsNil)
@@ -733,7 +745,7 @@ func checkClusterHealth(c *check.C, cl []*daemon.Swarm, managerCount, workerCoun
 				}
 				nn := d.GetNode(c, n.ID)
 				n = *nn
-				return n.Status.State == swarm.NodeStateReady, check.Commentf("state of node %s, reported by %s", n.ID, d.Info.NodeID)
+				return n.Status.State == swarm.NodeStateReady, check.Commentf("state of node %s, reported by %s", n.ID, d.NodeID())
 			}
 			waitAndAssert(c, defaultReconciliationTimeout, waitReady, checker.True)
 
@@ -743,18 +755,18 @@ func checkClusterHealth(c *check.C, cl []*daemon.Swarm, managerCount, workerCoun
 				}
 				nn := d.GetNode(c, n.ID)
 				n = *nn
-				return n.Spec.Availability == swarm.NodeAvailabilityActive, check.Commentf("availability of node %s, reported by %s", n.ID, d.Info.NodeID)
+				return n.Spec.Availability == swarm.NodeAvailabilityActive, check.Commentf("availability of node %s, reported by %s", n.ID, d.NodeID())
 			}
 			waitAndAssert(c, defaultReconciliationTimeout, waitActive, checker.True)
 
 			if n.Spec.Role == swarm.NodeRoleManager {
-				c.Assert(n.ManagerStatus, checker.NotNil, check.Commentf("manager status of node %s (manager), reported by %s", n.ID, d.Info.NodeID))
+				c.Assert(n.ManagerStatus, checker.NotNil, check.Commentf("manager status of node %s (manager), reported by %s", n.ID, d.NodeID()))
 				if n.ManagerStatus.Leader {
 					leaderFound = true
 				}
 				mCount++
 			} else {
-				c.Assert(n.ManagerStatus, checker.IsNil, check.Commentf("manager status of node %s (worker), reported by %s", n.ID, d.Info.NodeID))
+				c.Assert(n.ManagerStatus, checker.IsNil, check.Commentf("manager status of node %s (worker), reported by %s", n.ID, d.NodeID()))
 				wCount++
 			}
 		}
@@ -769,11 +781,10 @@ func checkClusterHealth(c *check.C, cl []*daemon.Swarm, managerCount, workerCoun
 func (s *DockerSwarmSuite) TestAPISwarmRestartCluster(c *check.C) {
 	mCount, wCount := 5, 1
 
-	var nodes []*daemon.Swarm
+	var nodes []*daemon.Daemon
 	for i := 0; i < mCount; i++ {
 		manager := s.AddDaemon(c, true, true)
-		info, err := manager.SwarmInfo()
-		c.Assert(err, checker.IsNil)
+		info := manager.SwarmInfo(c)
 		c.Assert(info.ControlAvailable, checker.True)
 		c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 		nodes = append(nodes, manager)
@@ -781,8 +792,7 @@ func (s *DockerSwarmSuite) TestAPISwarmRestartCluster(c *check.C) {
 
 	for i := 0; i < wCount; i++ {
 		worker := s.AddDaemon(c, true, false)
-		info, err := worker.SwarmInfo()
-		c.Assert(err, checker.IsNil)
+		info := worker.SwarmInfo(c)
 		c.Assert(info.ControlAvailable, checker.False)
 		c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 		nodes = append(nodes, worker)
@@ -795,7 +805,7 @@ func (s *DockerSwarmSuite) TestAPISwarmRestartCluster(c *check.C) {
 		errs := make(chan error, len(nodes))
 
 		for _, d := range nodes {
-			go func(daemon *daemon.Swarm) {
+			go func(daemon *daemon.Daemon) {
 				defer wg.Done()
 				if err := daemon.StopWithError(); err != nil {
 					errs <- err
@@ -820,7 +830,7 @@ func (s *DockerSwarmSuite) TestAPISwarmRestartCluster(c *check.C) {
 		errs := make(chan error, len(nodes))
 
 		for _, d := range nodes {
-			go func(daemon *daemon.Swarm) {
+			go func(daemon *daemon.Daemon) {
 				defer wg.Done()
 				if err := daemon.StartWithError("--iptables=false"); err != nil {
 					errs <- err
@@ -859,7 +869,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesUpdateWithName(c *check.C) {
 // Unlocking an unlocked swarm results in an error
 func (s *DockerSwarmSuite) TestAPISwarmUnlockNotLocked(c *check.C) {
 	d := s.AddDaemon(c, true, true)
-	err := d.Unlock(swarm.UnlockRequest{UnlockKey: "wrong-key"})
+	err := d.SwarmUnlock(swarm.UnlockRequest{UnlockKey: "wrong-key"})
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "swarm is not locked")
 }
@@ -870,7 +880,10 @@ func (s *DockerSwarmSuite) TestAPISwarmErrorHandling(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	defer ln.Close()
 	d := s.AddDaemon(c, false, false)
-	err = d.Init(swarm.InitRequest{})
+	client := d.NewClientT(c)
+	_, err = client.SwarmInit(context.Background(), swarm.InitRequest{
+		ListenAddr: d.SwarmListenAddr(),
+	})
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "address already in use")
 }
@@ -940,13 +953,13 @@ func (s *DockerSwarmSuite) TestSwarmRepeatedRootRotation(c *check.C) {
 	m := s.AddDaemon(c, true, true)
 	w := s.AddDaemon(c, true, false)
 
-	info, err := m.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := m.SwarmInfo(c)
 
 	currentTrustRoot := info.Cluster.TLSInfo.TrustRoot
 
 	// rotate multiple times
 	for i := 0; i < 4; i++ {
+		var err error
 		var cert, key []byte
 		if i%2 != 0 {
 			cert, _, key, err = initca.New(&csr.CertificateRequest{
@@ -966,8 +979,7 @@ func (s *DockerSwarmSuite) TestSwarmRepeatedRootRotation(c *check.C) {
 		// poll to make sure update succeeds
 		var clusterTLSInfo swarm.TLSInfo
 		for j := 0; j < 18; j++ {
-			info, err := m.SwarmInfo()
-			c.Assert(err, checker.IsNil)
+			info := m.SwarmInfo(c)
 
 			// the desired CA cert and key is always redacted
 			c.Assert(info.Cluster.Spec.CAConfig.SigningCAKey, checker.Equals, "")
@@ -989,8 +1001,8 @@ func (s *DockerSwarmSuite) TestSwarmRepeatedRootRotation(c *check.C) {
 		// could take another second or two for the nodes to trust the new roots after they've all gotten
 		// new TLS certificates
 		for j := 0; j < 18; j++ {
-			mInfo := m.GetNode(c, m.NodeID).Description.TLSInfo
-			wInfo := m.GetNode(c, w.NodeID).Description.TLSInfo
+			mInfo := m.GetNode(c, m.NodeID()).Description.TLSInfo
+			wInfo := m.GetNode(c, w.NodeID()).Description.TLSInfo
 
 			if mInfo.TrustRoot == clusterTLSInfo.TrustRoot && wInfo.TrustRoot == clusterTLSInfo.TrustRoot {
 				break
@@ -1000,8 +1012,8 @@ func (s *DockerSwarmSuite) TestSwarmRepeatedRootRotation(c *check.C) {
 			time.Sleep(250 * time.Millisecond)
 		}
 
-		c.Assert(m.GetNode(c, m.NodeID).Description.TLSInfo, checker.DeepEquals, clusterTLSInfo)
-		c.Assert(m.GetNode(c, w.NodeID).Description.TLSInfo, checker.DeepEquals, clusterTLSInfo)
+		c.Assert(m.GetNode(c, m.NodeID()).Description.TLSInfo, checker.DeepEquals, clusterTLSInfo)
+		c.Assert(m.GetNode(c, w.NodeID()).Description.TLSInfo, checker.DeepEquals, clusterTLSInfo)
 		currentTrustRoot = clusterTLSInfo.TrustRoot
 	}
 }

+ 1 - 1
integration-cli/docker_cli_prune_unix_test.go

@@ -16,7 +16,7 @@ import (
 	"github.com/go-check/check"
 )
 
-func pruneNetworkAndVerify(c *check.C, d *daemon.Swarm, kept, pruned []string) {
+func pruneNetworkAndVerify(c *check.C, d *daemon.Daemon, kept, pruned []string) {
 	_, err := d.Cmd("network", "prune", "--force")
 	c.Assert(err, checker.IsNil)
 

+ 1 - 1
integration-cli/docker_cli_service_logs_test.go

@@ -53,7 +53,7 @@ func (s *DockerSwarmSuite) TestServiceLogs(c *check.C) {
 // countLogLines returns a closure that can be used with waitAndAssert to
 // verify that a minimum number of expected container log messages have been
 // output.
-func countLogLines(d *daemon.Swarm, name string) func(*check.C) (interface{}, check.CommentInterface) {
+func countLogLines(d *daemon.Daemon, name string) func(*check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		result := icmd.RunCmd(d.Command("service", "logs", "-t", "--raw", name))
 		result.Assert(c, icmd.Expected{})

+ 31 - 36
integration-cli/docker_cli_swarm_test.go

@@ -57,7 +57,7 @@ func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) {
 	// passing an external CA (this is without starting a root rotation) does not fail
 	cli.Docker(cli.Args("swarm", "update", "--external-ca", "protocol=cfssl,url=https://something.org",
 		"--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"),
-		cli.Daemon(d.Daemon)).Assert(c, icmd.Success)
+		cli.Daemon(d)).Assert(c, icmd.Success)
 
 	expected, err := ioutil.ReadFile("fixtures/https/ca.pem")
 	c.Assert(err, checker.IsNil)
@@ -73,7 +73,7 @@ func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) {
 
 	result := cli.Docker(cli.Args("swarm", "update",
 		"--external-ca", fmt.Sprintf("protocol=cfssl,url=https://something.org,cacert=%s", tempFile.Path())),
-		cli.Daemon(d.Daemon))
+		cli.Daemon(d))
 	result.Assert(c, icmd.Expected{
 		ExitCode: 125,
 		Err:      "must be in PEM format",
@@ -94,7 +94,7 @@ func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) {
 
 	result := cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s",
 		"--external-ca", fmt.Sprintf("protocol=cfssl,url=https://somethingelse.org,cacert=%s", tempFile.Path())),
-		cli.Daemon(d.Daemon))
+		cli.Daemon(d))
 	result.Assert(c, icmd.Expected{
 		ExitCode: 125,
 		Err:      "must be in PEM format",
@@ -103,7 +103,7 @@ func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) {
 	cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s",
 		"--external-ca", "protocol=cfssl,url=https://something.org",
 		"--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"),
-		cli.Daemon(d.Daemon)).Assert(c, icmd.Success)
+		cli.Daemon(d)).Assert(c, icmd.Success)
 
 	expected, err := ioutil.ReadFile("fixtures/https/ca.pem")
 	c.Assert(err, checker.IsNil)
@@ -115,8 +115,8 @@ func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) {
 	c.Assert(spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "")
 	c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected))
 
-	c.Assert(d.Leave(true), checker.IsNil)
-	cli.Docker(cli.Args("swarm", "init"), cli.Daemon(d.Daemon)).Assert(c, icmd.Success)
+	c.Assert(d.SwarmLeave(true), checker.IsNil)
+	cli.Docker(cli.Args("swarm", "init"), cli.Daemon(d)).Assert(c, icmd.Success)
 
 	spec = getSpec()
 	c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 90*24*time.Hour)
@@ -126,12 +126,12 @@ func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) {
 func (s *DockerSwarmSuite) TestSwarmInitIPv6(c *check.C) {
 	testRequires(c, IPv6)
 	d1 := s.AddDaemon(c, false, false)
-	cli.Docker(cli.Args("swarm", "init", "--listen-add", "::1"), cli.Daemon(d1.Daemon)).Assert(c, icmd.Success)
+	cli.Docker(cli.Args("swarm", "init", "--listen-add", "::1"), cli.Daemon(d1)).Assert(c, icmd.Success)
 
 	d2 := s.AddDaemon(c, false, false)
-	cli.Docker(cli.Args("swarm", "join", "::1"), cli.Daemon(d2.Daemon)).Assert(c, icmd.Success)
+	cli.Docker(cli.Args("swarm", "join", "::1"), cli.Daemon(d2)).Assert(c, icmd.Success)
 
-	out := cli.Docker(cli.Args("info"), cli.Daemon(d2.Daemon)).Assert(c, icmd.Success).Combined()
+	out := cli.Docker(cli.Args("info"), cli.Daemon(d2)).Assert(c, icmd.Success).Combined()
 	c.Assert(out, checker.Contains, "Swarm: active")
 }
 
@@ -145,13 +145,12 @@ func (s *DockerSwarmSuite) TestSwarmInitUnspecifiedAdvertiseAddr(c *check.C) {
 func (s *DockerSwarmSuite) TestSwarmIncompatibleDaemon(c *check.C) {
 	// init swarm mode and stop a daemon
 	d := s.AddDaemon(c, true, true)
-	info, err := d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 	d.Stop(c)
 
 	// start a daemon with --cluster-store and --cluster-advertise
-	err = d.StartWithError("--cluster-store=consul://consuladdr:consulport/some/path", "--cluster-advertise=1.1.1.1:2375")
+	err := d.StartWithError("--cluster-store=consul://consuladdr:consulport/some/path", "--cluster-advertise=1.1.1.1:2375")
 	c.Assert(err, checker.NotNil)
 	content, err := d.ReadLogFile()
 	c.Assert(err, checker.IsNil)
@@ -426,7 +425,7 @@ func (s *DockerSwarmSuite) TestOverlayAttachableOnSwarmLeave(c *check.C) {
 	c.Assert(err, checker.IsNil, check.Commentf(out))
 
 	// Leave the swarm
-	err = d.Leave(true)
+	err = d.SwarmLeave(true)
 	c.Assert(err, checker.IsNil)
 
 	// Check the container is disconnected
@@ -989,13 +988,12 @@ func (s *DockerSwarmSuite) TestDNSConfigUpdate(c *check.C) {
 	c.Assert(strings.TrimSpace(out), checker.Equals, "{[1.2.3.4] [example.com] [timeout:3]}")
 }
 
-func getNodeStatus(c *check.C, d *daemon.Swarm) swarm.LocalNodeState {
-	info, err := d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+func getNodeStatus(c *check.C, d *daemon.Daemon) swarm.LocalNodeState {
+	info := d.SwarmInfo(c)
 	return info.LocalNodeState
 }
 
-func checkKeyIsEncrypted(d *daemon.Swarm) func(*check.C) (interface{}, check.CommentInterface) {
+func checkKeyIsEncrypted(d *daemon.Daemon) func(*check.C) (interface{}, check.CommentInterface) {
 	return func(c *check.C) (interface{}, check.CommentInterface) {
 		keyBytes, err := ioutil.ReadFile(filepath.Join(d.Folder, "root", "swarm", "certificates", "swarm-node.key"))
 		if err != nil {
@@ -1011,7 +1009,7 @@ func checkKeyIsEncrypted(d *daemon.Swarm) func(*check.C) (interface{}, check.Com
 	}
 }
 
-func checkSwarmLockedToUnlocked(c *check.C, d *daemon.Swarm, unlockKey string) {
+func checkSwarmLockedToUnlocked(c *check.C, d *daemon.Daemon, unlockKey string) {
 	// Wait for the PEM file to become unencrypted
 	waitAndAssert(c, defaultReconciliationTimeout, checkKeyIsEncrypted(d), checker.Equals, false)
 
@@ -1019,7 +1017,7 @@ func checkSwarmLockedToUnlocked(c *check.C, d *daemon.Swarm, unlockKey string) {
 	c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateActive)
 }
 
-func checkSwarmUnlockedToLocked(c *check.C, d *daemon.Swarm) {
+func checkSwarmUnlockedToLocked(c *check.C, d *daemon.Daemon) {
 	// Wait for the PEM file to become encrypted
 	waitAndAssert(c, defaultReconciliationTimeout, checkKeyIsEncrypted(d), checker.Equals, true)
 
@@ -1117,8 +1115,7 @@ func (s *DockerSwarmSuite) TestSwarmLeaveLocked(c *check.C) {
 	// It starts off locked
 	d.Restart(c, "--swarm-default-advertise-addr=lo")
 
-	info, err := d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info := d.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateLocked)
 
 	outs, _ = d.Cmd("node", "ls")
@@ -1132,15 +1129,13 @@ func (s *DockerSwarmSuite) TestSwarmLeaveLocked(c *check.C) {
 	outs, err = d.Cmd("swarm", "leave", "--force")
 	c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
 
-	info, err = d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateInactive)
 
 	outs, err = d.Cmd("swarm", "init")
 	c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
 
-	info, err = d.SwarmInfo()
-	c.Assert(err, checker.IsNil)
+	info = d.SwarmInfo(c)
 	c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive)
 }
 
@@ -1176,7 +1171,7 @@ func (s *DockerSwarmSuite) TestSwarmLockUnlockCluster(c *check.C) {
 	c.Assert(outs, checker.Equals, unlockKey+"\n")
 
 	// The ones that got the cluster update should be set to locked
-	for _, d := range []*daemon.Swarm{d1, d3} {
+	for _, d := range []*daemon.Daemon{d1, d3} {
 		checkSwarmUnlockedToLocked(c, d)
 
 		cmd := d.Command("swarm", "unlock")
@@ -1197,7 +1192,7 @@ func (s *DockerSwarmSuite) TestSwarmLockUnlockCluster(c *check.C) {
 	c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
 
 	// the ones that got the update are now set to unlocked
-	for _, d := range []*daemon.Swarm{d1, d3} {
+	for _, d := range []*daemon.Daemon{d1, d3} {
 		checkSwarmLockedToUnlocked(c, d, unlockKey)
 	}
 
@@ -1247,7 +1242,7 @@ func (s *DockerSwarmSuite) TestSwarmJoinPromoteLocked(c *check.C) {
 	c.Assert(getNodeStatus(c, d2), checker.Equals, swarm.LocalNodeStateActive)
 
 	// promote worker
-	outs, err = d1.Cmd("node", "promote", d2.Info.NodeID)
+	outs, err = d1.Cmd("node", "promote", d2.NodeID())
 	c.Assert(err, checker.IsNil)
 	c.Assert(outs, checker.Contains, "promoted to a manager in the swarm")
 
@@ -1255,7 +1250,7 @@ func (s *DockerSwarmSuite) TestSwarmJoinPromoteLocked(c *check.C) {
 	d3 := s.AddDaemon(c, true, true)
 
 	// both new nodes are locked
-	for _, d := range []*daemon.Swarm{d2, d3} {
+	for _, d := range []*daemon.Daemon{d2, d3} {
 		checkSwarmUnlockedToLocked(c, d)
 
 		cmd := d.Command("swarm", "unlock")
@@ -1265,7 +1260,7 @@ func (s *DockerSwarmSuite) TestSwarmJoinPromoteLocked(c *check.C) {
 	}
 
 	// demote manager back to worker - workers are not locked
-	outs, err = d1.Cmd("node", "demote", d3.Info.NodeID)
+	outs, err = d1.Cmd("node", "demote", d3.NodeID())
 	c.Assert(err, checker.IsNil)
 	c.Assert(outs, checker.Contains, "demoted in the swarm")
 
@@ -1409,7 +1404,7 @@ func (s *DockerSwarmSuite) TestSwarmClusterRotateUnlockKey(c *check.C) {
 		d2.Restart(c)
 		d3.Restart(c)
 
-		for _, d := range []*daemon.Swarm{d2, d3} {
+		for _, d := range []*daemon.Daemon{d2, d3} {
 			c.Assert(getNodeStatus(c, d), checker.Equals, swarm.LocalNodeStateLocked)
 
 			outs, _ := d.Cmd("node", "ls")
@@ -1521,7 +1516,7 @@ func (s *DockerSwarmSuite) TestSwarmManagerAddress(c *check.C) {
 	d3 := s.AddDaemon(c, true, false)
 
 	// Manager Addresses will always show Node 1's address
-	expectedOutput := fmt.Sprintf("Manager Addresses:\n  127.0.0.1:%d\n", d1.Port)
+	expectedOutput := fmt.Sprintf("Manager Addresses:\n  127.0.0.1:%d\n", d1.SwarmPort)
 
 	out, err := d1.Cmd("info")
 	c.Assert(err, checker.IsNil)
@@ -1641,7 +1636,7 @@ func (s *DockerSwarmSuite) TestSwarmJoinWithDrain(c *check.C) {
 
 	d1 := s.AddDaemon(c, false, false)
 
-	out, err = d1.Cmd("swarm", "join", "--availability=drain", "--token", token, d.ListenAddr)
+	out, err = d1.Cmd("swarm", "join", "--availability=drain", "--token", token, d.SwarmListenAddr())
 	c.Assert(err, checker.IsNil)
 	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
 
@@ -1835,7 +1830,7 @@ func (s *DockerSwarmSuite) TestSwarmJoinLeave(c *check.C) {
 	// Verify that back to back join/leave does not cause panics
 	d1 := s.AddDaemon(c, false, false)
 	for i := 0; i < 10; i++ {
-		out, err = d1.Cmd("swarm", "join", "--token", token, d.ListenAddr)
+		out, err = d1.Cmd("swarm", "join", "--token", token, d.SwarmListenAddr())
 		c.Assert(err, checker.IsNil)
 		c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
 
@@ -1846,7 +1841,7 @@ func (s *DockerSwarmSuite) TestSwarmJoinLeave(c *check.C) {
 
 const defaultRetryCount = 10
 
-func waitForEvent(c *check.C, d *daemon.Swarm, since string, filter string, event string, retry int) string {
+func waitForEvent(c *check.C, d *daemon.Daemon, since string, filter string, event string, retry int) string {
 	if retry < 1 {
 		c.Fatalf("retry count %d is invalid. It should be no less than 1", retry)
 		return ""
@@ -1982,7 +1977,7 @@ func (s *DockerSwarmSuite) TestSwarmClusterEventsNode(c *check.C) {
 	s.AddDaemon(c, true, true)
 	d3 := s.AddDaemon(c, true, true)
 
-	d3ID := d3.NodeID
+	d3ID := d3.NodeID()
 	waitForEvent(c, d1, "0", "-f scope=swarm", "node create "+d3ID, defaultRetryCount)
 
 	t1 := daemonUnixTime(c)

+ 10 - 22
integration/internal/swarm/service.go

@@ -2,7 +2,6 @@ package swarm
 
 import (
 	"context"
-	"fmt"
 	"runtime"
 	"testing"
 	"time"
@@ -11,18 +10,13 @@ import (
 	"github.com/docker/docker/api/types/filters"
 	swarmtypes "github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/client"
-	"github.com/docker/docker/integration-cli/daemon"
+	"github.com/docker/docker/internal/test/daemon"
 	"github.com/docker/docker/internal/test/environment"
 	"github.com/gotestyourself/gotestyourself/assert"
 	"github.com/gotestyourself/gotestyourself/poll"
 	"github.com/gotestyourself/gotestyourself/skip"
 )
 
-const (
-	dockerdBinary    = "dockerd"
-	defaultSwarmPort = 2477
-)
-
 // ServicePoll tweaks the pollSettings for `service`
 func ServicePoll(config *poll.Settings) {
 	// Override the default pollSettings for `service` resource here ...
@@ -55,23 +49,17 @@ func ContainerPoll(config *poll.Settings) {
 }
 
 // NewSwarm creates a swarm daemon for testing
-func NewSwarm(t *testing.T, testEnv *environment.Execution) *daemon.Swarm {
+func NewSwarm(t *testing.T, testEnv *environment.Execution, ops ...func(*daemon.Daemon)) *daemon.Daemon {
 	skip.IfCondition(t, testEnv.IsRemoteDaemon())
-	d := &daemon.Swarm{
-		Daemon: daemon.New(t, "", dockerdBinary, daemon.Config{
-			Experimental: testEnv.DaemonInfo.ExperimentalBuild,
-		}),
-		// TODO: better method of finding an unused port
-		Port: defaultSwarmPort,
+	if testEnv.DaemonInfo.ExperimentalBuild {
+		ops = append(ops, daemon.WithExperimental)
 	}
-	// TODO: move to a NewSwarm constructor
-	d.ListenAddr = fmt.Sprintf("0.0.0.0:%d", d.Port)
-
+	d := daemon.New(t, ops...)
 	// avoid networking conflicts
 	args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"}
 	d.StartWithBusybox(t, args...)
 
-	assert.NilError(t, d.Init(swarmtypes.InitRequest{}))
+	d.SwarmInit(t, swarmtypes.InitRequest{})
 	return d
 }
 
@@ -79,7 +67,7 @@ func NewSwarm(t *testing.T, testEnv *environment.Execution) *daemon.Swarm {
 type ServiceSpecOpt func(*swarmtypes.ServiceSpec)
 
 // CreateService creates a service on the passed in swarm daemon.
-func CreateService(t *testing.T, d *daemon.Swarm, opts ...ServiceSpecOpt) string {
+func CreateService(t *testing.T, d *daemon.Daemon, opts ...ServiceSpecOpt) string {
 	spec := defaultServiceSpec()
 	for _, o := range opts {
 		o(&spec)
@@ -151,7 +139,7 @@ func ServiceWithName(name string) ServiceSpecOpt {
 }
 
 // GetRunningTasks gets the list of running tasks for a service
-func GetRunningTasks(t *testing.T, d *daemon.Swarm, serviceID string) []swarmtypes.Task {
+func GetRunningTasks(t *testing.T, d *daemon.Daemon, serviceID string) []swarmtypes.Task {
 	client := GetClient(t, d)
 
 	filterArgs := filters.NewArgs()
@@ -167,7 +155,7 @@ func GetRunningTasks(t *testing.T, d *daemon.Swarm, serviceID string) []swarmtyp
 }
 
 // ExecTask runs the passed in exec config on the given task
-func ExecTask(t *testing.T, d *daemon.Swarm, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse {
+func ExecTask(t *testing.T, d *daemon.Daemon, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse {
 	client := GetClient(t, d)
 
 	ctx := context.Background()
@@ -187,7 +175,7 @@ func ensureContainerSpec(spec *swarmtypes.ServiceSpec) {
 }
 
 // GetClient creates a new client for the passed in swarm daemon.
-func GetClient(t *testing.T, d *daemon.Swarm) client.APIClient {
+func GetClient(t *testing.T, d *daemon.Daemon) client.APIClient {
 	client, err := client.NewClientWithOpts(client.WithHost((d.Sock())))
 	assert.NilError(t, err)
 	return client

+ 66 - 0
internal/test/daemon/config.go

@@ -0,0 +1,66 @@
+package daemon
+
+import (
+	"context"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/gotestyourself/gotestyourself/assert"
+)
+
+// ConfigConstructor defines a swarm config constructor
+type ConfigConstructor func(*swarm.Config)
+
+// CreateConfig creates a config given the specified spec
+func (d *Daemon) CreateConfig(t assert.TestingT, configSpec swarm.ConfigSpec) string {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	scr, err := cli.ConfigCreate(context.Background(), configSpec)
+	assert.NilError(t, err)
+	return scr.ID
+}
+
+// ListConfigs returns the list of the current swarm configs
+func (d *Daemon) ListConfigs(t assert.TestingT) []swarm.Config {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	configs, err := cli.ConfigList(context.Background(), types.ConfigListOptions{})
+	assert.NilError(t, err)
+	return configs
+}
+
+// GetConfig returns a swarm config identified by the specified id
+func (d *Daemon) GetConfig(t assert.TestingT, id string) *swarm.Config {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	config, _, err := cli.ConfigInspectWithRaw(context.Background(), id)
+	assert.NilError(t, err)
+	return &config
+}
+
+// DeleteConfig removes the swarm config identified by the specified id
+func (d *Daemon) DeleteConfig(t assert.TestingT, id string) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	err := cli.ConfigRemove(context.Background(), id)
+	assert.NilError(t, err)
+}
+
+// UpdateConfig updates the swarm config identified by the specified id
+// Currently, only label update is supported.
+func (d *Daemon) UpdateConfig(t assert.TestingT, id string, f ...ConfigConstructor) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	config := d.GetConfig(t, id)
+	for _, fn := range f {
+		fn(config)
+	}
+
+	err := cli.ConfigUpdate(context.Background(), config.ID, config.Version, config.Spec)
+	assert.NilError(t, err)
+}

+ 29 - 9
internal/test/daemon/daemon.go

@@ -67,6 +67,13 @@ type Daemon struct {
 	experimental  bool
 	dockerdBinary string
 	log           logT
+
+	// swarm related field
+	swarmListenAddr string
+	SwarmPort       int // FIXME(vdemeester) should probably not be exported
+
+	// cached information
+	CachedInfo types.Info
 }
 
 // New returns a Daemon instance to be used for testing.
@@ -98,14 +105,16 @@ func New(t testingT, ops ...func(*Daemon)) *Daemon {
 		}
 	}
 	d := &Daemon{
-		id:            id,
-		Folder:        daemonFolder,
-		Root:          daemonRoot,
-		storageDriver: storageDriver,
-		userlandProxy: userlandProxy,
-		execRoot:      filepath.Join(os.TempDir(), "docker-execroot", id),
-		dockerdBinary: defaultDockerdBinary,
-		log:           t,
+		id:              id,
+		Folder:          daemonFolder,
+		Root:            daemonRoot,
+		storageDriver:   storageDriver,
+		userlandProxy:   userlandProxy,
+		execRoot:        filepath.Join(os.TempDir(), "docker-execroot", id),
+		dockerdBinary:   defaultDockerdBinary,
+		swarmListenAddr: defaultSwarmListenAddr,
+		SwarmPort:       defaultSwarmPort,
+		log:             t,
 	}
 
 	for _, op := range ops {
@@ -150,12 +159,23 @@ func (d *Daemon) ReadLogFile() ([]byte, error) {
 }
 
 // NewClient creates new client based on daemon's socket path
+// FIXME(vdemeester): replace NewClient with NewClientT
 func (d *Daemon) NewClient() (*client.Client, error) {
 	return client.NewClientWithOpts(
 		client.FromEnv,
 		client.WithHost(d.Sock()))
 }
 
+// NewClientT creates new client based on daemon's socket path
+// FIXME(vdemeester): replace NewClient with NewClientT
+func (d *Daemon) NewClientT(t assert.TestingT) *client.Client {
+	c, err := client.NewClientWithOpts(
+		client.FromEnv,
+		client.WithHost(d.Sock()))
+	assert.NilError(t, err, "cannot create daemon client")
+	return c
+}
+
 // CleanupExecRoot cleans the daemon exec root (network namespaces, ...)
 func (d *Daemon) CleanupExecRoot(t testingT) {
 	cleanupExecRoot(t, d.execRoot)
@@ -610,7 +630,7 @@ func (d *Daemon) queryRootDir() (string, error) {
 
 // Info returns the info struct for this daemon
 func (d *Daemon) Info(t assert.TestingT) types.Info {
-	apiclient, err := client.NewClientWithOpts(client.WithHost((d.Sock())))
+	apiclient, err := d.NewClient()
 	assert.NilError(t, err)
 	info, err := apiclient.Info(context.Background())
 	assert.NilError(t, err)

+ 69 - 0
internal/test/daemon/node.go

@@ -0,0 +1,69 @@
+package daemon
+
+import (
+	"context"
+	"strings"
+	"time"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/gotestyourself/gotestyourself/assert"
+)
+
+// NodeConstructor defines a swarm node constructor
+type NodeConstructor func(*swarm.Node)
+
+// GetNode returns a swarm node identified by the specified id
+func (d *Daemon) GetNode(t assert.TestingT, id string) *swarm.Node {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	node, _, err := cli.NodeInspectWithRaw(context.Background(), id)
+	assert.NilError(t, err)
+	assert.Check(t, node.ID == id)
+	return &node
+}
+
+// RemoveNode removes the specified node
+func (d *Daemon) RemoveNode(t assert.TestingT, id string, force bool) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	options := types.NodeRemoveOptions{
+		Force: force,
+	}
+	err := cli.NodeRemove(context.Background(), id, options)
+	assert.NilError(t, err)
+}
+
+// UpdateNode updates a swarm node with the specified node constructor
+func (d *Daemon) UpdateNode(t assert.TestingT, id string, f ...NodeConstructor) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	for i := 0; ; i++ {
+		node := d.GetNode(t, id)
+		for _, fn := range f {
+			fn(node)
+		}
+
+		err := cli.NodeUpdate(context.Background(), node.ID, node.Version, node.Spec)
+		if i < 10 && err != nil && strings.Contains(err.Error(), "update out of sequence") {
+			time.Sleep(100 * time.Millisecond)
+			continue
+		}
+		assert.NilError(t, err)
+		return
+	}
+}
+
+// ListNodes returns the list of the current swarm nodes
+func (d *Daemon) ListNodes(t assert.TestingT) []swarm.Node {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	nodes, err := cli.NodeList(context.Background(), types.NodeListOptions{})
+	assert.NilError(t, err)
+
+	return nodes
+}

+ 14 - 0
internal/test/daemon/ops.go

@@ -11,3 +11,17 @@ func WithDockerdBinary(dockerdBinary string) func(*Daemon) {
 		d.dockerdBinary = dockerdBinary
 	}
 }
+
+// WithSwarmPort sets the swarm port to use for swarm mode
+func WithSwarmPort(port int) func(*Daemon) {
+	return func(d *Daemon) {
+		d.SwarmPort = port
+	}
+}
+
+// WithSwarmListenAddr sets the swarm listen addr to use for swarm mode
+func WithSwarmListenAddr(listenAddr string) func(*Daemon) {
+	return func(d *Daemon) {
+		d.swarmListenAddr = listenAddr
+	}
+}

+ 68 - 0
internal/test/daemon/secret.go

@@ -0,0 +1,68 @@
+package daemon
+
+import (
+	"context"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/gotestyourself/gotestyourself/assert"
+)
+
+// SecretConstructor defines a swarm secret constructor
+type SecretConstructor func(*swarm.Secret)
+
+// CreateSecret creates a secret given the specified spec
+func (d *Daemon) CreateSecret(t assert.TestingT, secretSpec swarm.SecretSpec) string {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	scr, err := cli.SecretCreate(context.Background(), secretSpec)
+	assert.NilError(t, err)
+
+	return scr.ID
+}
+
+// ListSecrets returns the list of the current swarm secrets
+func (d *Daemon) ListSecrets(t assert.TestingT) []swarm.Secret {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	secrets, err := cli.SecretList(context.Background(), types.SecretListOptions{})
+	assert.NilError(t, err)
+	return secrets
+}
+
+// GetSecret returns a swarm secret identified by the specified id
+func (d *Daemon) GetSecret(t assert.TestingT, id string) *swarm.Secret {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	secret, _, err := cli.SecretInspectWithRaw(context.Background(), id)
+	assert.NilError(t, err)
+	return &secret
+}
+
+// DeleteSecret removes the swarm secret identified by the specified id
+func (d *Daemon) DeleteSecret(t assert.TestingT, id string) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	err := cli.SecretRemove(context.Background(), id)
+	assert.NilError(t, err)
+}
+
+// UpdateSecret updates the swarm secret identified by the specified id
+// Currently, only label update is supported.
+func (d *Daemon) UpdateSecret(t assert.TestingT, id string, f ...SecretConstructor) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	secret := d.GetSecret(t, id)
+	for _, fn := range f {
+		fn(secret)
+	}
+
+	err := cli.SecretUpdate(context.Background(), secret.ID, secret.Version, secret.Spec)
+
+	assert.NilError(t, err)
+}

+ 108 - 0
internal/test/daemon/service.go

@@ -0,0 +1,108 @@
+package daemon
+
+import (
+	"context"
+	"time"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/gotestyourself/gotestyourself/assert"
+)
+
+// ServiceConstructor defines a swarm service constructor function
+type ServiceConstructor func(*swarm.Service)
+
+// CreateServiceWithOptions creates a swarm service given the specified service constructors
+// and auth config
+func (d *Daemon) CreateServiceWithOptions(t assert.TestingT, opts types.ServiceCreateOptions, f ...ServiceConstructor) string {
+	var service swarm.Service
+	for _, fn := range f {
+		fn(&service)
+	}
+
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+
+	res, err := cli.ServiceCreate(ctx, service.Spec, opts)
+	assert.NilError(t, err)
+	return res.ID
+}
+
+// CreateService creates a swarm service given the specified service constructor
+func (d *Daemon) CreateService(t assert.TestingT, f ...ServiceConstructor) string {
+	return d.CreateServiceWithOptions(t, types.ServiceCreateOptions{}, f...)
+}
+
+// GetService returns the swarm service corresponding to the specified id
+func (d *Daemon) GetService(t assert.TestingT, id string) *swarm.Service {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	service, _, err := cli.ServiceInspectWithRaw(context.Background(), id, types.ServiceInspectOptions{})
+	assert.NilError(t, err)
+	return &service
+}
+
+// GetServiceTasks returns the swarm tasks for the specified service
+func (d *Daemon) GetServiceTasks(t assert.TestingT, service string) []swarm.Task {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	filterArgs := filters.NewArgs()
+	filterArgs.Add("desired-state", "running")
+	filterArgs.Add("service", service)
+
+	options := types.TaskListOptions{
+		Filters: filterArgs,
+	}
+
+	tasks, err := cli.TaskList(context.Background(), options)
+	assert.NilError(t, err)
+	return tasks
+}
+
+// UpdateService updates a swarm service with the specified service constructor
+func (d *Daemon) UpdateService(t assert.TestingT, service *swarm.Service, f ...ServiceConstructor) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	for _, fn := range f {
+		fn(service)
+	}
+
+	_, err := cli.ServiceUpdate(context.Background(), service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
+	assert.NilError(t, err)
+}
+
+// RemoveService removes the specified service
+func (d *Daemon) RemoveService(t assert.TestingT, id string) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	err := cli.ServiceRemove(context.Background(), id)
+	assert.NilError(t, err)
+}
+
+// ListServices returns the list of the current swarm services
+func (d *Daemon) ListServices(t assert.TestingT) []swarm.Service {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	services, err := cli.ServiceList(context.Background(), types.ServiceListOptions{})
+	assert.NilError(t, err)
+	return services
+}
+
+// GetTask returns the swarm task identified by the specified id
+func (d *Daemon) GetTask(t assert.TestingT, id string) swarm.Task {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	task, _, err := cli.TaskInspectWithRaw(context.Background(), id)
+	assert.NilError(t, err)
+	return task
+}

+ 139 - 0
internal/test/daemon/swarm.go

@@ -0,0 +1,139 @@
+package daemon
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/gotestyourself/gotestyourself/assert"
+	"github.com/pkg/errors"
+)
+
+const (
+	defaultSwarmPort       = 2477
+	defaultSwarmListenAddr = "0.0.0.0"
+)
+
+// SpecConstructor defines a swarm spec constructor
+type SpecConstructor func(*swarm.Spec)
+
+// SwarmListenAddr returns the listen-addr used for the daemon
+func (d *Daemon) SwarmListenAddr() string {
+	return fmt.Sprintf("%s:%d", d.swarmListenAddr, d.SwarmPort)
+}
+
+// NodeID returns the swarm mode node ID
+func (d *Daemon) NodeID() string {
+	return d.CachedInfo.Swarm.NodeID
+}
+
+// SwarmInit initializes a new swarm cluster.
+func (d *Daemon) SwarmInit(t assert.TestingT, req swarm.InitRequest) {
+	if req.ListenAddr == "" {
+		req.ListenAddr = fmt.Sprintf("%s:%d", d.swarmListenAddr, d.SwarmPort)
+	}
+	cli := d.NewClientT(t)
+	defer cli.Close()
+	_, err := cli.SwarmInit(context.Background(), req)
+	assert.NilError(t, err, "initializing swarm")
+	d.CachedInfo = d.Info(t)
+}
+
+// SwarmJoin joins a daemon to an existing cluster.
+func (d *Daemon) SwarmJoin(t assert.TestingT, req swarm.JoinRequest) {
+	if req.ListenAddr == "" {
+		req.ListenAddr = fmt.Sprintf("%s:%d", d.swarmListenAddr, d.SwarmPort)
+	}
+	cli := d.NewClientT(t)
+	defer cli.Close()
+	err := cli.SwarmJoin(context.Background(), req)
+	assert.NilError(t, err, "initializing swarm")
+	d.CachedInfo = d.Info(t)
+}
+
+// SwarmLeave forces daemon to leave current cluster.
+func (d *Daemon) SwarmLeave(force bool) error {
+	cli, err := d.NewClient()
+	if err != nil {
+		return fmt.Errorf("leaving swarm: failed to create client %v", err)
+	}
+	defer cli.Close()
+	err = cli.SwarmLeave(context.Background(), force)
+	if err != nil {
+		err = fmt.Errorf("leaving swarm: %v", err)
+	}
+	return err
+}
+
+// SwarmInfo returns the swarm information of the daemon
+func (d *Daemon) SwarmInfo(t assert.TestingT) swarm.Info {
+	cli := d.NewClientT(t)
+	info, err := cli.Info(context.Background())
+	assert.NilError(t, err, "get swarm info")
+	return info.Swarm
+}
+
+// SwarmUnlock tries to unlock a locked swarm
+func (d *Daemon) SwarmUnlock(req swarm.UnlockRequest) error {
+	cli, err := d.NewClient()
+	if err != nil {
+		return fmt.Errorf("unlocking swarm: failed to create client %v", err)
+	}
+	defer cli.Close()
+	err = cli.SwarmUnlock(context.Background(), req)
+	if err != nil {
+		err = errors.Wrap(err, "unlocking swarm")
+	}
+	return err
+}
+
+// GetSwarm returns the current swarm object
+func (d *Daemon) GetSwarm(t assert.TestingT) swarm.Swarm {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	sw, err := cli.SwarmInspect(context.Background())
+	assert.NilError(t, err)
+	return sw
+}
+
+// UpdateSwarm updates the current swarm object with the specified spec constructors
+func (d *Daemon) UpdateSwarm(t assert.TestingT, f ...SpecConstructor) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	sw := d.GetSwarm(t)
+	for _, fn := range f {
+		fn(&sw.Spec)
+	}
+
+	err := cli.SwarmUpdate(context.Background(), sw.Version, sw.Spec, swarm.UpdateFlags{})
+	assert.NilError(t, err)
+}
+
+// RotateTokens update the swarm to rotate tokens
+func (d *Daemon) RotateTokens(t assert.TestingT) {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	sw, err := cli.SwarmInspect(context.Background())
+	assert.NilError(t, err)
+
+	flags := swarm.UpdateFlags{
+		RotateManagerToken: true,
+		RotateWorkerToken:  true,
+	}
+
+	err = cli.SwarmUpdate(context.Background(), sw.Version, sw.Spec, flags)
+	assert.NilError(t, err)
+}
+
+// JoinTokens returns the current swarm join tokens
+func (d *Daemon) JoinTokens(t assert.TestingT) swarm.JoinTokens {
+	cli := d.NewClientT(t)
+	defer cli.Close()
+
+	sw, err := cli.SwarmInspect(context.Background())
+	assert.NilError(t, err)
+	return sw.JoinTokens
+}