Browse Source

Merge pull request #23361 from docker/swarm

Add dependency to docker/swarmkit
Tibor Vass 9 years ago
parent
commit
a1e319e847
100 changed files with 9086 additions and 71 deletions
  1. 70 0
      api/client/idresolver/idresolver.go
  2. 16 0
      api/client/info.go
  3. 25 6
      api/client/inspect.go
  4. 6 4
      api/client/network/list.go
  5. 40 0
      api/client/node/accept.go
  6. 49 0
      api/client/node/cmd.go
  7. 40 0
      api/client/node/demote.go
  8. 141 0
      api/client/node/inspect.go
  9. 119 0
      api/client/node/list.go
  10. 50 0
      api/client/node/opts.go
  11. 40 0
      api/client/node/promote.go
  12. 36 0
      api/client/node/remove.go
  13. 72 0
      api/client/node/tasks.go
  14. 100 0
      api/client/node/update.go
  15. 32 0
      api/client/service/cmd.go
  16. 47 0
      api/client/service/create.go
  17. 127 0
      api/client/service/inspect.go
  18. 97 0
      api/client/service/list.go
  19. 462 0
      api/client/service/opts.go
  20. 47 0
      api/client/service/remove.go
  21. 86 0
      api/client/service/scale.go
  22. 65 0
      api/client/service/tasks.go
  23. 244 0
      api/client/service/update.go
  24. 30 0
      api/client/swarm/cmd.go
  25. 61 0
      api/client/swarm/init.go
  26. 56 0
      api/client/swarm/inspect.go
  27. 65 0
      api/client/swarm/join.go
  28. 44 0
      api/client/swarm/leave.go
  29. 120 0
      api/client/swarm/opts.go
  30. 93 0
      api/client/swarm/update.go
  31. 20 0
      api/client/tag.go
  32. 79 0
      api/client/task/print.go
  33. 25 0
      api/client/utils.go
  34. 3 1
      api/server/httputils/errors.go
  35. 1 2
      api/server/router/network/backend.go
  36. 98 0
      api/server/router/network/filter.go
  37. 10 5
      api/server/router/network/network.go
  38. 42 9
      api/server/router/network/network_routes.go
  39. 26 0
      api/server/router/swarm/backend.go
  40. 44 0
      api/server/router/swarm/cluster.go
  41. 229 0
      api/server/router/swarm/cluster_routes.go
  42. 10 5
      api/server/router/system/system.go
  43. 3 0
      api/server/router/system/system_routes.go
  44. 6 0
      cli/cobraadaptor/adaptor.go
  45. 1 1
      cli/usage.go
  46. 20 4
      cmd/dockerd/daemon.go
  47. 23 1
      container/container.go
  48. 28 0
      container/state.go
  49. 1056 0
      daemon/cluster/cluster.go
  50. 116 0
      daemon/cluster/convert/container.go
  51. 194 0
      daemon/cluster/convert/network.go
  52. 95 0
      daemon/cluster/convert/node.go
  53. 252 0
      daemon/cluster/convert/service.go
  54. 116 0
      daemon/cluster/convert/swarm.go
  55. 53 0
      daemon/cluster/convert/task.go
  56. 35 0
      daemon/cluster/executor/backend.go
  57. 229 0
      daemon/cluster/executor/container/adapter.go
  58. 415 0
      daemon/cluster/executor/container/container.go
  59. 305 0
      daemon/cluster/executor/container/controller.go
  60. 12 0
      daemon/cluster/executor/container/errors.go
  61. 139 0
      daemon/cluster/executor/container/executor.go
  62. 93 0
      daemon/cluster/filters.go
  63. 108 0
      daemon/cluster/helpers.go
  64. 36 0
      daemon/cluster/provider/network.go
  65. 2 1
      daemon/container.go
  66. 7 0
      daemon/container_operations.go
  67. 13 4
      daemon/create.go
  68. 12 0
      daemon/daemon.go
  69. 4 2
      daemon/inspect.go
  70. 1 1
      daemon/inspect_windows.go
  71. 11 0
      daemon/list.go
  72. 137 15
      daemon/network.go
  73. 2 0
      daemon/network/settings.go
  74. 16 1
      daemon/wait.go
  75. 1113 1
      docs/reference/api/docker_remote_api_v1.24.md
  76. 20 0
      docs/reference/commandline/index.md
  77. 6 1
      docs/reference/commandline/info.md
  78. 11 7
      docs/reference/commandline/inspect.md
  79. 28 0
      docs/reference/commandline/node_accept.md
  80. 28 0
      docs/reference/commandline/node_demote.md
  81. 108 0
      docs/reference/commandline/node_inspect.md
  82. 89 0
      docs/reference/commandline/node_ls.md
  83. 28 0
      docs/reference/commandline/node_promote.md
  84. 28 0
      docs/reference/commandline/node_reject.md
  85. 38 0
      docs/reference/commandline/node_rm.md
  86. 94 0
      docs/reference/commandline/node_tasks.md
  87. 26 0
      docs/reference/commandline/node_update.md
  88. 69 0
      docs/reference/commandline/swarm_init.md
  89. 68 0
      docs/reference/commandline/swarm_join.md
  90. 52 0
      docs/reference/commandline/swarm_leave.md
  91. 37 0
      docs/reference/commandline/swarm_update.md
  92. 79 0
      docs/swarm/index.md
  93. 85 0
      docs/swarm/key-concepts.md
  94. 21 0
      docs/swarm/menu.md
  95. 64 0
      docs/swarm/swarm-tutorial/add-nodes.md
  96. 77 0
      docs/swarm/swarm-tutorial/create-swarm.md
  97. 44 0
      docs/swarm/swarm-tutorial/delete-service.md
  98. 50 0
      docs/swarm/swarm-tutorial/deploy-service.md
  99. 129 0
      docs/swarm/swarm-tutorial/drain-node.md
  100. 87 0
      docs/swarm/swarm-tutorial/index.md

+ 70 - 0
api/client/idresolver/idresolver.go

@@ -0,0 +1,70 @@
+package idresolver
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/engine-api/client"
+	"github.com/docker/engine-api/types/swarm"
+)
+
+// IDResolver provides ID to Name resolution.
+type IDResolver struct {
+	client    client.APIClient
+	noResolve bool
+	cache     map[string]string
+}
+
+// New creates a new IDResolver.
+func New(client client.APIClient, noResolve bool) *IDResolver {
+	return &IDResolver{
+		client:    client,
+		noResolve: noResolve,
+		cache:     make(map[string]string),
+	}
+}
+
+func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string, error) {
+	switch t.(type) {
+	case swarm.Node:
+		node, err := r.client.NodeInspect(ctx, id)
+		if err != nil {
+			return id, nil
+		}
+		if node.Spec.Annotations.Name != "" {
+			return node.Spec.Annotations.Name, nil
+		}
+		if node.Description.Hostname != "" {
+			return node.Description.Hostname, nil
+		}
+		return id, nil
+	case swarm.Service:
+		service, err := r.client.ServiceInspect(ctx, id)
+		if err != nil {
+			return id, nil
+		}
+		return service.Spec.Annotations.Name, nil
+	default:
+		return "", fmt.Errorf("unsupported type")
+	}
+
+}
+
+// Resolve will attempt to resolve an ID to a Name by querying the manager.
+// Results are stored into a cache.
+// If the `-n` flag is used in the command-line, resolution is disabled.
+func (r *IDResolver) Resolve(ctx context.Context, t interface{}, id string) (string, error) {
+	if r.noResolve {
+		return id, nil
+	}
+	if name, ok := r.cache[id]; ok {
+		return name, nil
+	}
+	name, err := r.get(ctx, t, id)
+	if err != nil {
+		return "", err
+	}
+	r.cache[id] = name
+	return name, nil
+}

+ 16 - 0
api/client/info.go

@@ -10,6 +10,7 @@ import (
 	"github.com/docker/docker/pkg/ioutils"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/utils"
+	"github.com/docker/engine-api/types/swarm"
 	"github.com/docker/go-units"
 )
 
@@ -68,6 +69,21 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 		fmt.Fprintf(cli.out, "\n")
 	}
 
+	fmt.Fprintf(cli.out, "Swarm: %v\n", info.Swarm.LocalNodeState)
+	if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive {
+		fmt.Fprintf(cli.out, " NodeID: %s\n", info.Swarm.NodeID)
+		if info.Swarm.Error != "" {
+			fmt.Fprintf(cli.out, " Error: %v\n", info.Swarm.Error)
+		}
+		if info.Swarm.ControlAvailable {
+			fmt.Fprintf(cli.out, " IsManager: Yes\n")
+			fmt.Fprintf(cli.out, " Managers: %d\n", info.Swarm.Managers)
+			fmt.Fprintf(cli.out, " Nodes: %d\n", info.Swarm.Nodes)
+			ioutils.FprintfIfNotEmpty(cli.out, " CACertHash: %s\n", info.Swarm.CACertHash)
+		} else {
+			fmt.Fprintf(cli.out, " IsManager: No\n")
+		}
+	}
 	ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
 	ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
 	ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType)

+ 25 - 6
api/client/inspect.go

@@ -11,19 +11,19 @@ import (
 	"github.com/docker/engine-api/client"
 )
 
-// CmdInspect displays low-level information on one or more containers or images.
+// CmdInspect displays low-level information on one or more containers, images or tasks.
 //
-// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
+// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
 func (cli *DockerCli) CmdInspect(args ...string) error {
-	cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
+	cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]"}, Cli.DockerCommands["inspect"].Description, true)
 	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
-	inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
+	inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image, container or task)")
 	size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
 	cmd.Require(flag.Min, 1)
 
 	cmd.ParseFlags(args, true)
 
-	if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
+	if *inspectType != "" && *inspectType != "container" && *inspectType != "image" && *inspectType != "task" {
 		return fmt.Errorf("%q is not a valid value for --type", *inspectType)
 	}
 
@@ -35,6 +35,11 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
 		elementSearcher = cli.inspectContainers(ctx, *size)
 	case "image":
 		elementSearcher = cli.inspectImages(ctx, *size)
+	case "task":
+		if *size {
+			fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
+		}
+		elementSearcher = cli.inspectTasks(ctx)
 	default:
 		elementSearcher = cli.inspectAll(ctx, *size)
 	}
@@ -54,6 +59,12 @@ func (cli *DockerCli) inspectImages(ctx context.Context, getSize bool) inspect.G
 	}
 }
 
+func (cli *DockerCli) inspectTasks(ctx context.Context) inspect.GetRefFunc {
+	return func(ref string) (interface{}, []byte, error) {
+		return cli.client.TaskInspectWithRaw(ctx, ref)
+	}
+}
+
 func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetRefFunc {
 	return func(ref string) (interface{}, []byte, error) {
 		c, rawContainer, err := cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
@@ -63,7 +74,15 @@ func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetR
 				i, rawImage, err := cli.client.ImageInspectWithRaw(ctx, ref, getSize)
 				if err != nil {
 					if client.IsErrImageNotFound(err) {
-						return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
+						// Search for task with that id if an image doesn't exists.
+						t, rawTask, err := cli.client.TaskInspectWithRaw(ctx, ref)
+						if err != nil {
+							return nil, nil, fmt.Errorf("Error: No such image, container or task: %s", ref)
+						}
+						if getSize {
+							fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
+						}
+						return t, rawTask, nil
 					}
 					return nil, nil, err
 				}

+ 6 - 4
api/client/network/list.go

@@ -71,7 +71,7 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
 
 	w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
 	if !opts.quiet {
-		fmt.Fprintf(w, "NETWORK ID\tNAME\tDRIVER")
+		fmt.Fprintf(w, "NETWORK ID\tNAME\tDRIVER\tSCOPE")
 		fmt.Fprintf(w, "\n")
 	}
 
@@ -79,6 +79,8 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
 	for _, networkResource := range networkResources {
 		ID := networkResource.ID
 		netName := networkResource.Name
+		driver := networkResource.Driver
+		scope := networkResource.Scope
 		if !opts.noTrunc {
 			ID = stringid.TruncateID(ID)
 		}
@@ -86,11 +88,11 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
 			fmt.Fprintln(w, ID)
 			continue
 		}
-		driver := networkResource.Driver
-		fmt.Fprintf(w, "%s\t%s\t%s\t",
+		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t",
 			ID,
 			netName,
-			driver)
+			driver,
+			scope)
 		fmt.Fprint(w, "\n")
 	}
 	w.Flush()

+ 40 - 0
api/client/node/accept.go

@@ -0,0 +1,40 @@
+package node
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+func newAcceptCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var flags *pflag.FlagSet
+
+	cmd := &cobra.Command{
+		Use:   "accept NODE [NODE...]",
+		Short: "Accept a node in the swarm",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runAccept(dockerCli, flags, args)
+		},
+	}
+
+	flags = cmd.Flags()
+	return cmd
+}
+
+func runAccept(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
+	for _, id := range args {
+		if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
+			node.Spec.Membership = swarm.NodeMembershipAccepted
+		}); err != nil {
+			return err
+		}
+		fmt.Println(id, "attempting to accept a node in the swarm.")
+	}
+
+	return nil
+}

+ 49 - 0
api/client/node/cmd.go

@@ -0,0 +1,49 @@
+package node
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/spf13/cobra"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	apiclient "github.com/docker/engine-api/client"
+)
+
+// NewNodeCommand returns a cobra command for `node` subcommands
+func NewNodeCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "node",
+		Short: "Manage docker swarm nodes",
+		Args:  cli.NoArgs,
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
+		},
+	}
+	cmd.AddCommand(
+		newAcceptCommand(dockerCli),
+		newDemoteCommand(dockerCli),
+		newInspectCommand(dockerCli),
+		newListCommand(dockerCli),
+		newPromoteCommand(dockerCli),
+		newRemoveCommand(dockerCli),
+		newTasksCommand(dockerCli),
+		newUpdateCommand(dockerCli),
+	)
+	return cmd
+}
+
+func nodeReference(client apiclient.APIClient, ctx context.Context, ref string) (string, error) {
+	// The special value "self" for a node reference is mapped to the current
+	// node, hence the node ID is retrieved using the `/info` endpoint.
+	if ref == "self" {
+		info, err := client.Info(ctx)
+		if err != nil {
+			return "", err
+		}
+		return info.Swarm.NodeID, nil
+	}
+	return ref, nil
+}

+ 40 - 0
api/client/node/demote.go

@@ -0,0 +1,40 @@
+package node
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+func newDemoteCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var flags *pflag.FlagSet
+
+	cmd := &cobra.Command{
+		Use:   "demote NODE [NODE...]",
+		Short: "Demote a node from manager in the swarm",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runDemote(dockerCli, flags, args)
+		},
+	}
+
+	flags = cmd.Flags()
+	return cmd
+}
+
+func runDemote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
+	for _, id := range args {
+		if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
+			node.Spec.Role = swarm.NodeRoleWorker
+		}); err != nil {
+			return err
+		}
+		fmt.Println(id, "attempting to demote a manager in the swarm.")
+	}
+
+	return nil
+}

+ 141 - 0
api/client/node/inspect.go

@@ -0,0 +1,141 @@
+package node
+
+import (
+	"fmt"
+	"io"
+	"sort"
+	"strings"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/inspect"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/docker/go-units"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+type inspectOptions struct {
+	nodeIds []string
+	format  string
+	pretty  bool
+}
+
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts inspectOptions
+
+	cmd := &cobra.Command{
+		Use:   "inspect [OPTIONS] self|NODE [NODE...]",
+		Short: "Inspect a node in the swarm",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.nodeIds = args
+			return runInspect(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
+	flags.BoolVarP(&opts.pretty, "pretty", "p", false, "Print the information in a human friendly format.")
+	return cmd
+}
+
+func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+	getRef := func(ref string) (interface{}, []byte, error) {
+		nodeRef, err := nodeReference(client, ctx, ref)
+		if err != nil {
+			return nil, nil, err
+		}
+		node, err := client.NodeInspect(ctx, nodeRef)
+		return node, nil, err
+	}
+
+	if !opts.pretty {
+		return inspect.Inspect(dockerCli.Out(), opts.nodeIds, opts.format, getRef)
+	}
+	return printHumanFriendly(dockerCli.Out(), opts.nodeIds, getRef)
+}
+
+func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error {
+	for idx, ref := range refs {
+		obj, _, err := getRef(ref)
+		if err != nil {
+			return err
+		}
+		printNode(out, obj.(swarm.Node))
+
+		// TODO: better way to do this?
+		// print extra space between objects, but not after the last one
+		if idx+1 != len(refs) {
+			fmt.Fprintf(out, "\n\n")
+		}
+	}
+	return nil
+}
+
+// TODO: use a template
+func printNode(out io.Writer, node swarm.Node) {
+	fmt.Fprintf(out, "ID:\t\t\t%s\n", node.ID)
+	ioutils.FprintfIfNotEmpty(out, "Name:\t\t\t%s\n", node.Spec.Name)
+	if node.Spec.Labels != nil {
+		fmt.Fprintln(out, "Labels:")
+		for k, v := range node.Spec.Labels {
+			fmt.Fprintf(out, " - %s = %s\n", k, v)
+		}
+	}
+
+	ioutils.FprintfIfNotEmpty(out, "Hostname:\t\t%s\n", node.Description.Hostname)
+	fmt.Fprintln(out, "Status:")
+	fmt.Fprintf(out, " State:\t\t\t%s\n", client.PrettyPrint(node.Status.State))
+	ioutils.FprintfIfNotEmpty(out, " Message:\t\t%s\n", client.PrettyPrint(node.Status.Message))
+	fmt.Fprintf(out, " Availability:\t\t%s\n", client.PrettyPrint(node.Spec.Availability))
+
+	if node.ManagerStatus != nil {
+		fmt.Fprintln(out, "Manager Status:")
+		fmt.Fprintf(out, " Address:\t\t%s\n", node.ManagerStatus.Addr)
+		fmt.Fprintf(out, " Raft status:\t\t%s\n", client.PrettyPrint(node.ManagerStatus.Reachability))
+		leader := "No"
+		if node.ManagerStatus.Leader {
+			leader = "Yes"
+		}
+		fmt.Fprintf(out, " Leader:\t\t%s\n", leader)
+	}
+
+	fmt.Fprintln(out, "Platform:")
+	fmt.Fprintf(out, " Operating System:\t%s\n", node.Description.Platform.OS)
+	fmt.Fprintf(out, " Architecture:\t\t%s\n", node.Description.Platform.Architecture)
+
+	fmt.Fprintln(out, "Resources:")
+	fmt.Fprintf(out, " CPUs:\t\t\t%d\n", node.Description.Resources.NanoCPUs/1e9)
+	fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(node.Description.Resources.MemoryBytes)))
+
+	var pluginTypes []string
+	pluginNamesByType := map[string][]string{}
+	for _, p := range node.Description.Engine.Plugins {
+		// append to pluginTypes only if not done previously
+		if _, ok := pluginNamesByType[p.Type]; !ok {
+			pluginTypes = append(pluginTypes, p.Type)
+		}
+		pluginNamesByType[p.Type] = append(pluginNamesByType[p.Type], p.Name)
+	}
+
+	if len(pluginTypes) > 0 {
+		fmt.Fprintln(out, "Plugins:")
+		sort.Strings(pluginTypes) // ensure stable output
+		for _, pluginType := range pluginTypes {
+			fmt.Fprintf(out, "  %s:\t\t%s\n", pluginType, strings.Join(pluginNamesByType[pluginType], ", "))
+		}
+	}
+	fmt.Fprintf(out, "Engine Version:\t\t%s\n", node.Description.Engine.EngineVersion)
+
+	if len(node.Description.Engine.Labels) != 0 {
+		fmt.Fprintln(out, "Engine Labels:")
+		for k, v := range node.Description.Engine.Labels {
+			fmt.Fprintf(out, " - %s = %s", k, v)
+		}
+	}
+
+}

+ 119 - 0
api/client/node/list.go

@@ -0,0 +1,119 @@
+package node
+
+import (
+	"fmt"
+	"io"
+	"text/tabwriter"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+)
+
+const (
+	listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
+)
+
+type listOptions struct {
+	quiet  bool
+	filter opts.FilterOpt
+}
+
+func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := listOptions{filter: opts.NewFilterOpt()}
+
+	cmd := &cobra.Command{
+		Use:     "ls",
+		Aliases: []string{"list"},
+		Short:   "List nodes in the swarm",
+		Args:    cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runList(dockerCli, opts)
+		},
+	}
+	flags := cmd.Flags()
+	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
+	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
+
+	return cmd
+}
+
+func runList(dockerCli *client.DockerCli, opts listOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	nodes, err := client.NodeList(
+		ctx,
+		types.NodeListOptions{Filter: opts.filter.Value()})
+	if err != nil {
+		return err
+	}
+
+	info, err := client.Info(ctx)
+	if err != nil {
+		return err
+	}
+
+	out := dockerCli.Out()
+	if opts.quiet {
+		printQuiet(out, nodes)
+	} else {
+		printTable(out, nodes, info)
+	}
+	return nil
+}
+
+func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
+	writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
+
+	// Ignore flushing errors
+	defer writer.Flush()
+
+	fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS", "LEADER")
+	for _, node := range nodes {
+		name := node.Spec.Name
+		availability := string(node.Spec.Availability)
+		membership := string(node.Spec.Membership)
+
+		if name == "" {
+			name = node.Description.Hostname
+		}
+
+		leader := ""
+		if node.ManagerStatus != nil && node.ManagerStatus.Leader {
+			leader = "Yes"
+		}
+
+		reachability := ""
+		if node.ManagerStatus != nil {
+			reachability = string(node.ManagerStatus.Reachability)
+		}
+
+		ID := node.ID
+		if node.ID == info.Swarm.NodeID {
+			ID = ID + " *"
+		}
+
+		fmt.Fprintf(
+			writer,
+			listItemFmt,
+			ID,
+			name,
+			client.PrettyPrint(membership),
+			client.PrettyPrint(string(node.Status.State)),
+			client.PrettyPrint(availability),
+			client.PrettyPrint(reachability),
+			leader)
+	}
+}
+
+func printQuiet(out io.Writer, nodes []swarm.Node) {
+	for _, node := range nodes {
+		fmt.Fprintln(out, node.ID)
+	}
+}

+ 50 - 0
api/client/node/opts.go

@@ -0,0 +1,50 @@
+package node
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/docker/engine-api/types/swarm"
+)
+
+type nodeOptions struct {
+	role         string
+	membership   string
+	availability string
+}
+
+func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) {
+	var spec swarm.NodeSpec
+
+	switch swarm.NodeRole(strings.ToLower(opts.role)) {
+	case swarm.NodeRoleWorker:
+		spec.Role = swarm.NodeRoleWorker
+	case swarm.NodeRoleManager:
+		spec.Role = swarm.NodeRoleManager
+	case "":
+	default:
+		return swarm.NodeSpec{}, fmt.Errorf("invalid role %q, only worker and manager are supported", opts.role)
+	}
+
+	switch swarm.NodeMembership(strings.ToLower(opts.membership)) {
+	case swarm.NodeMembershipAccepted:
+		spec.Membership = swarm.NodeMembershipAccepted
+	case "":
+	default:
+		return swarm.NodeSpec{}, fmt.Errorf("invalid membership %q, only accepted is supported", opts.membership)
+	}
+
+	switch swarm.NodeAvailability(strings.ToLower(opts.availability)) {
+	case swarm.NodeAvailabilityActive:
+		spec.Availability = swarm.NodeAvailabilityActive
+	case swarm.NodeAvailabilityPause:
+		spec.Availability = swarm.NodeAvailabilityPause
+	case swarm.NodeAvailabilityDrain:
+		spec.Availability = swarm.NodeAvailabilityDrain
+	case "":
+	default:
+		return swarm.NodeSpec{}, fmt.Errorf("invalid availability %q, only active, pause and drain are supported", opts.availability)
+	}
+
+	return spec, nil
+}

+ 40 - 0
api/client/node/promote.go

@@ -0,0 +1,40 @@
+package node
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+func newPromoteCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var flags *pflag.FlagSet
+
+	cmd := &cobra.Command{
+		Use:   "promote NODE [NODE...]",
+		Short: "Promote a node to a manager in the swarm",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runPromote(dockerCli, flags, args)
+		},
+	}
+
+	flags = cmd.Flags()
+	return cmd
+}
+
+func runPromote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
+	for _, id := range args {
+		if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
+			node.Spec.Role = swarm.NodeRoleManager
+		}); err != nil {
+			return err
+		}
+		fmt.Println(id, "attempting to promote a node to a manager in the swarm.")
+	}
+
+	return nil
+}

+ 36 - 0
api/client/node/remove.go

@@ -0,0 +1,36 @@
+package node
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
+	return &cobra.Command{
+		Use:     "rm NODE [NODE...]",
+		Aliases: []string{"remove"},
+		Short:   "Remove a node from the swarm",
+		Args:    cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runRemove(dockerCli, args)
+		},
+	}
+}
+
+func runRemove(dockerCli *client.DockerCli, args []string) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+	for _, nodeID := range args {
+		err := client.NodeRemove(ctx, nodeID)
+		if err != nil {
+			return err
+		}
+		fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
+	}
+	return nil
+}

+ 72 - 0
api/client/node/tasks.go

@@ -0,0 +1,72 @@
+package node
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/idresolver"
+	"github.com/docker/docker/api/client/task"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+)
+
+type tasksOptions struct {
+	nodeID    string
+	all       bool
+	noResolve bool
+	filter    opts.FilterOpt
+}
+
+func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := tasksOptions{filter: opts.NewFilterOpt()}
+
+	cmd := &cobra.Command{
+		Use:   "tasks [OPTIONS] self|NODE",
+		Short: "List tasks running on a node",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.nodeID = args[0]
+			return runTasks(dockerCli, opts)
+		},
+	}
+	flags := cmd.Flags()
+	flags.BoolVarP(&opts.all, "all", "a", false, "Display all instances")
+	flags.BoolVarP(&opts.noResolve, "no-resolve", "n", false, "Do not map IDs to Names")
+	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
+
+	return cmd
+}
+
+func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	nodeRef, err := nodeReference(client, ctx, opts.nodeID)
+	if err != nil {
+		return nil
+	}
+	node, err := client.NodeInspect(ctx, nodeRef)
+	if err != nil {
+		return err
+	}
+
+	filter := opts.filter.Value()
+	filter.Add("node", node.ID)
+	if !opts.all {
+		filter.Add("desired_state", string(swarm.TaskStateRunning))
+		filter.Add("desired_state", string(swarm.TaskStateAccepted))
+
+	}
+
+	tasks, err := client.TaskList(
+		ctx,
+		types.TaskListOptions{Filter: filter})
+	if err != nil {
+		return err
+	}
+
+	return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve))
+}

+ 100 - 0
api/client/node/update.go

@@ -0,0 +1,100 @@
+package node
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+	"golang.org/x/net/context"
+)
+
+func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts nodeOptions
+	var flags *pflag.FlagSet
+
+	cmd := &cobra.Command{
+		Use:   "update [OPTIONS] NODE",
+		Short: "Update a node",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runUpdate(dockerCli, args[0], mergeNodeUpdate(flags))
+		},
+	}
+
+	flags = cmd.Flags()
+	flags.StringVar(&opts.role, "role", "", "Role of the node (worker/manager)")
+	flags.StringVar(&opts.membership, "membership", "", "Membership of the node (accepted/rejected)")
+	flags.StringVar(&opts.availability, "availability", "", "Availability of the node (active/pause/drain)")
+	return cmd
+}
+
+func runUpdate(dockerCli *client.DockerCli, nodeID string, mergeNode func(node *swarm.Node)) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	node, err := client.NodeInspect(ctx, nodeID)
+	if err != nil {
+		return err
+	}
+
+	mergeNode(&node)
+	err = client.NodeUpdate(ctx, nodeID, node.Version, node.Spec)
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
+	return nil
+}
+
+func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) {
+	return func(node *swarm.Node) {
+		mergeString := func(flag string, field *string) {
+			if flags.Changed(flag) {
+				*field, _ = flags.GetString(flag)
+			}
+		}
+
+		mergeRole := func(flag string, field *swarm.NodeRole) {
+			if flags.Changed(flag) {
+				str, _ := flags.GetString(flag)
+				*field = swarm.NodeRole(str)
+			}
+		}
+
+		mergeMembership := func(flag string, field *swarm.NodeMembership) {
+			if flags.Changed(flag) {
+				str, _ := flags.GetString(flag)
+				*field = swarm.NodeMembership(str)
+			}
+		}
+
+		mergeAvailability := func(flag string, field *swarm.NodeAvailability) {
+			if flags.Changed(flag) {
+				str, _ := flags.GetString(flag)
+				*field = swarm.NodeAvailability(str)
+			}
+		}
+
+		mergeLabels := func(flag string, field *map[string]string) {
+			if flags.Changed(flag) {
+				values, _ := flags.GetStringSlice(flag)
+				for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
+					(*field)[key] = value
+				}
+			}
+		}
+
+		spec := &node.Spec
+		mergeString("name", &spec.Name)
+		// TODO: setting labels is not working
+		mergeLabels("label", &spec.Labels)
+		mergeRole("role", &spec.Role)
+		mergeMembership("membership", &spec.Membership)
+		mergeAvailability("availability", &spec.Availability)
+	}
+}

+ 32 - 0
api/client/service/cmd.go

@@ -0,0 +1,32 @@
+package service
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+)
+
+// NewServiceCommand returns a cobra command for `service` subcommands
+func NewServiceCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "service",
+		Short: "Manage docker services",
+		Args:  cli.NoArgs,
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
+		},
+	}
+	cmd.AddCommand(
+		newCreateCommand(dockerCli),
+		newInspectCommand(dockerCli),
+		newTasksCommand(dockerCli),
+		newListCommand(dockerCli),
+		newRemoveCommand(dockerCli),
+		newScaleCommand(dockerCli),
+		newUpdateCommand(dockerCli),
+	)
+	return cmd
+}

+ 47 - 0
api/client/service/create.go

@@ -0,0 +1,47 @@
+package service
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := newServiceOptions()
+
+	cmd := &cobra.Command{
+		Use:   "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
+		Short: "Create a new service",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.image = args[0]
+			if len(args) > 1 {
+				opts.args = args[1:]
+			}
+			return runCreate(dockerCli, opts)
+		},
+	}
+	addServiceFlags(cmd, opts)
+	cmd.Flags().SetInterspersed(false)
+	return cmd
+}
+
+func runCreate(dockerCli *client.DockerCli, opts *serviceOptions) error {
+	client := dockerCli.Client()
+
+	service, err := opts.ToService()
+	if err != nil {
+		return err
+	}
+
+	response, err := client.ServiceCreate(context.Background(), service)
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "%s\n", response.ID)
+	return nil
+}

+ 127 - 0
api/client/service/inspect.go

@@ -0,0 +1,127 @@
+package service
+
+import (
+	"fmt"
+	"io"
+	"strings"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/inspect"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/pkg/ioutils"
+	apiclient "github.com/docker/engine-api/client"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+)
+
+type inspectOptions struct {
+	refs   []string
+	format string
+	pretty bool
+}
+
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts inspectOptions
+
+	cmd := &cobra.Command{
+		Use:   "inspect [OPTIONS] SERVICE [SERVICE...]",
+		Short: "Inspect a service",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.refs = args
+
+			if opts.pretty && len(opts.format) > 0 {
+				return fmt.Errorf("--format is incompatible with human friendly format")
+			}
+			return runInspect(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
+	flags.BoolVarP(&opts.pretty, "pretty", "p", false, "Print the information in a human friendly format.")
+	return cmd
+}
+
+func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	getRef := func(ref string) (interface{}, []byte, error) {
+		service, err := client.ServiceInspect(ctx, ref)
+		if err == nil || !apiclient.IsErrServiceNotFound(err) {
+			return service, nil, err
+		}
+		return nil, nil, fmt.Errorf("Error: no such service: %s", ref)
+	}
+
+	if !opts.pretty {
+		return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRef)
+	}
+
+	return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
+}
+
+func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error {
+	for idx, ref := range refs {
+		obj, _, err := getRef(ref)
+		if err != nil {
+			return err
+		}
+		printService(out, obj.(swarm.Service))
+
+		// TODO: better way to do this?
+		// print extra space between objects, but not after the last one
+		if idx+1 != len(refs) {
+			fmt.Fprintf(out, "\n\n")
+		}
+	}
+	return nil
+}
+
+// TODO: use a template
+func printService(out io.Writer, service swarm.Service) {
+	fmt.Fprintf(out, "ID:\t\t%s\n", service.ID)
+	fmt.Fprintf(out, "Name:\t\t%s\n", service.Spec.Name)
+	if service.Spec.Labels != nil {
+		fmt.Fprintln(out, "Labels:")
+		for k, v := range service.Spec.Labels {
+			fmt.Fprintf(out, " - %s=%s\n", k, v)
+		}
+	}
+
+	if service.Spec.Mode.Global != nil {
+		fmt.Fprintln(out, "Mode:\t\tGLOBAL")
+	} else {
+		fmt.Fprintln(out, "Mode:\t\tREPLICATED")
+		if service.Spec.Mode.Replicated.Replicas != nil {
+			fmt.Fprintf(out, " Replicas:\t\t%d\n", *service.Spec.Mode.Replicated.Replicas)
+		}
+	}
+	fmt.Fprintln(out, "Placement:")
+	fmt.Fprintln(out, " Strategy:\tSPREAD")
+	fmt.Fprintf(out, "UpateConfig:\n")
+	fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism)
+	if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
+		fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
+	}
+	fmt.Fprintf(out, "ContainerSpec:\n")
+	printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
+}
+
+func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
+	fmt.Fprintf(out, " Image:\t\t%s\n", containerSpec.Image)
+	if len(containerSpec.Command) > 0 {
+		fmt.Fprintf(out, " Command:\t%s\n", strings.Join(containerSpec.Command, " "))
+	}
+	if len(containerSpec.Args) > 0 {
+		fmt.Fprintf(out, " Args:\t%s\n", strings.Join(containerSpec.Args, " "))
+	}
+	if len(containerSpec.Env) > 0 {
+		fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))
+	}
+	ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir)
+	ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User)
+}

+ 97 - 0
api/client/service/list.go

@@ -0,0 +1,97 @@
+package service
+
+import (
+	"fmt"
+	"io"
+	"strings"
+	"text/tabwriter"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+)
+
+const (
+	listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
+)
+
+type listOptions struct {
+	quiet  bool
+	filter opts.FilterOpt
+}
+
+func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := listOptions{filter: opts.NewFilterOpt()}
+
+	cmd := &cobra.Command{
+		Use:     "ls",
+		Aliases: []string{"list"},
+		Short:   "List services",
+		Args:    cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runList(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
+	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
+
+	return cmd
+}
+
+func runList(dockerCli *client.DockerCli, opts listOptions) error {
+	client := dockerCli.Client()
+
+	services, err := client.ServiceList(
+		context.Background(),
+		types.ServiceListOptions{Filter: opts.filter.Value()})
+	if err != nil {
+		return err
+	}
+
+	out := dockerCli.Out()
+	if opts.quiet {
+		printQuiet(out, services)
+	} else {
+		printTable(out, services)
+	}
+	return nil
+}
+
+func printTable(out io.Writer, services []swarm.Service) {
+	writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
+
+	// Ignore flushing errors
+	defer writer.Flush()
+
+	fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "SCALE", "IMAGE", "COMMAND")
+	for _, service := range services {
+		scale := ""
+		if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
+			scale = fmt.Sprintf("%d", *service.Spec.Mode.Replicated.Replicas)
+		} else if service.Spec.Mode.Global != nil {
+			scale = "global"
+		}
+		fmt.Fprintf(
+			writer,
+			listItemFmt,
+			stringid.TruncateID(service.ID),
+			service.Spec.Name,
+			scale,
+			service.Spec.TaskTemplate.ContainerSpec.Image,
+			strings.Join(service.Spec.TaskTemplate.ContainerSpec.Args, " "))
+	}
+}
+
+func printQuiet(out io.Writer, services []swarm.Service) {
+	for _, service := range services {
+		fmt.Fprintln(out, service.ID)
+	}
+}

+ 462 - 0
api/client/service/opts.go

@@ -0,0 +1,462 @@
+package service
+
+import (
+	"encoding/csv"
+	"fmt"
+	"math/big"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/docker/go-connections/nat"
+	units "github.com/docker/go-units"
+	"github.com/spf13/cobra"
+)
+
+var (
+	// DefaultReplicas is the default replicas to use for a replicated service
+	DefaultReplicas uint64 = 1
+)
+
+type int64Value interface {
+	Value() int64
+}
+
+type memBytes int64
+
+func (m *memBytes) String() string {
+	return strconv.FormatInt(m.Value(), 10)
+}
+
+func (m *memBytes) Set(value string) error {
+	val, err := units.RAMInBytes(value)
+	*m = memBytes(val)
+	return err
+}
+
+func (m *memBytes) Type() string {
+	return "MemoryBytes"
+}
+
+func (m *memBytes) Value() int64 {
+	return int64(*m)
+}
+
+type nanoCPUs int64
+
+func (c *nanoCPUs) String() string {
+	return strconv.FormatInt(c.Value(), 10)
+}
+
+func (c *nanoCPUs) Set(value string) error {
+	cpu, ok := new(big.Rat).SetString(value)
+	if !ok {
+		return fmt.Errorf("Failed to parse %v as a rational number", value)
+	}
+	nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
+	if !nano.IsInt() {
+		return fmt.Errorf("value is too precise")
+	}
+	*c = nanoCPUs(nano.Num().Int64())
+	return nil
+}
+
+func (c *nanoCPUs) Type() string {
+	return "NanoCPUs"
+}
+
+func (c *nanoCPUs) Value() int64 {
+	return int64(*c)
+}
+
+// DurationOpt is an option type for time.Duration that uses a pointer. This
+// allows us to get nil values outside, instead of defaulting to 0
+type DurationOpt struct {
+	value *time.Duration
+}
+
+// Set a new value on the option
+func (d *DurationOpt) Set(s string) error {
+	v, err := time.ParseDuration(s)
+	d.value = &v
+	return err
+}
+
+// Type returns the type of this option
+func (d *DurationOpt) Type() string {
+	return "duration-ptr"
+}
+
+// String returns a string repr of this option
+func (d *DurationOpt) String() string {
+	if d.value != nil {
+		return d.value.String()
+	}
+	return "none"
+}
+
+// Value returns the time.Duration
+func (d *DurationOpt) Value() *time.Duration {
+	return d.value
+}
+
+// Uint64Opt represents a uint64.
+type Uint64Opt struct {
+	value *uint64
+}
+
+// Set a new value on the option
+func (i *Uint64Opt) Set(s string) error {
+	v, err := strconv.ParseUint(s, 0, 64)
+	i.value = &v
+	return err
+}
+
+// Type returns the type of this option
+func (i *Uint64Opt) Type() string {
+	return "uint64-ptr"
+}
+
+// String returns a string repr of this option
+func (i *Uint64Opt) String() string {
+	if i.value != nil {
+		return fmt.Sprintf("%v", *i.value)
+	}
+	return "none"
+}
+
+// Value returns the uint64
+func (i *Uint64Opt) Value() *uint64 {
+	return i.value
+}
+
+// MountOpt is a Value type for parsing mounts
+type MountOpt struct {
+	values []swarm.Mount
+}
+
+// Set a new mount value
+func (m *MountOpt) Set(value string) error {
+	csvReader := csv.NewReader(strings.NewReader(value))
+	fields, err := csvReader.Read()
+	if err != nil {
+		return err
+	}
+
+	mount := swarm.Mount{}
+
+	volumeOptions := func() *swarm.VolumeOptions {
+		if mount.VolumeOptions == nil {
+			mount.VolumeOptions = &swarm.VolumeOptions{
+				Labels: make(map[string]string),
+			}
+		}
+		return mount.VolumeOptions
+	}
+
+	setValueOnMap := func(target map[string]string, value string) {
+		parts := strings.SplitN(value, "=", 2)
+		if len(parts) == 1 {
+			target[value] = ""
+		} else {
+			target[parts[0]] = parts[1]
+		}
+	}
+
+	for _, field := range fields {
+		parts := strings.SplitN(field, "=", 2)
+		if len(parts) == 1 && strings.ToLower(parts[0]) == "writable" {
+			mount.Writable = true
+			continue
+		}
+
+		if len(parts) != 2 {
+			return fmt.Errorf("invald field '%s' must be a key=value pair", field)
+		}
+
+		key, value := parts[0], parts[1]
+		switch strings.ToLower(key) {
+		case "type":
+			mount.Type = swarm.MountType(strings.ToUpper(value))
+		case "source":
+			mount.Source = value
+		case "target":
+			mount.Target = value
+		case "writable":
+			mount.Writable, err = strconv.ParseBool(value)
+			if err != nil {
+				return fmt.Errorf("invald value for writable: %s", err.Error())
+			}
+		case "bind-propagation":
+			mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value))
+		case "volume-populate":
+			volumeOptions().Populate, err = strconv.ParseBool(value)
+			if err != nil {
+				return fmt.Errorf("invald value for populate: %s", err.Error())
+			}
+		case "volume-label":
+			setValueOnMap(volumeOptions().Labels, value)
+		case "volume-driver":
+			volumeOptions().DriverConfig.Name = value
+		case "volume-driver-opt":
+			if volumeOptions().DriverConfig.Options == nil {
+				volumeOptions().DriverConfig.Options = make(map[string]string)
+			}
+			setValueOnMap(volumeOptions().DriverConfig.Options, value)
+		default:
+			return fmt.Errorf("unexpected key '%s' in '%s'", key, value)
+		}
+	}
+
+	if mount.Type == "" {
+		return fmt.Errorf("type is required")
+	}
+
+	if mount.Target == "" {
+		return fmt.Errorf("target is required")
+	}
+
+	m.values = append(m.values, mount)
+	return nil
+}
+
+// Type returns the type of this option
+func (m *MountOpt) Type() string {
+	return "mount"
+}
+
+// String returns a string repr of this option
+func (m *MountOpt) String() string {
+	mounts := []string{}
+	for _, mount := range m.values {
+		mounts = append(mounts, fmt.Sprintf("%v", mount))
+	}
+	return strings.Join(mounts, ", ")
+}
+
+// Value returns the mounts
+func (m *MountOpt) Value() []swarm.Mount {
+	return m.values
+}
+
+type updateOptions struct {
+	parallelism uint64
+	delay       time.Duration
+}
+
+type resourceOptions struct {
+	limitCPU      nanoCPUs
+	limitMemBytes memBytes
+	resCPU        nanoCPUs
+	resMemBytes   memBytes
+}
+
+func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
+	return &swarm.ResourceRequirements{
+		Limits: &swarm.Resources{
+			NanoCPUs:    r.limitCPU.Value(),
+			MemoryBytes: r.limitMemBytes.Value(),
+		},
+		Reservations: &swarm.Resources{
+			NanoCPUs:    r.resCPU.Value(),
+			MemoryBytes: r.resMemBytes.Value(),
+		},
+	}
+}
+
+type restartPolicyOptions struct {
+	condition   string
+	delay       DurationOpt
+	maxAttempts Uint64Opt
+	window      DurationOpt
+}
+
+func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
+	return &swarm.RestartPolicy{
+		Condition:   swarm.RestartPolicyCondition(r.condition),
+		Delay:       r.delay.Value(),
+		MaxAttempts: r.maxAttempts.Value(),
+		Window:      r.window.Value(),
+	}
+}
+
+func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
+	nets := []swarm.NetworkAttachmentConfig{}
+	for _, network := range networks {
+		nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
+	}
+	return nets
+}
+
+type endpointOptions struct {
+	mode  string
+	ports opts.ListOpts
+}
+
+func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
+	portConfigs := []swarm.PortConfig{}
+	// We can ignore errors because the format was already validated by ValidatePort
+	ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
+
+	for port := range ports {
+		portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...)
+	}
+
+	return &swarm.EndpointSpec{
+		Mode:  swarm.ResolutionMode(e.mode),
+		Ports: portConfigs,
+	}
+}
+
+func convertPortToPortConfig(
+	port nat.Port,
+	portBindings map[nat.Port][]nat.PortBinding,
+) []swarm.PortConfig {
+	ports := []swarm.PortConfig{}
+
+	for _, binding := range portBindings[port] {
+		hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
+		ports = append(ports, swarm.PortConfig{
+			//TODO Name: ?
+			Protocol:      swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
+			TargetPort:    uint32(port.Int()),
+			PublishedPort: uint32(hostPort),
+		})
+	}
+	return ports
+}
+
+// ValidatePort validates a string is in the expected format for a port definition
+func ValidatePort(value string) (string, error) {
+	portMappings, err := nat.ParsePortSpec(value)
+	for _, portMapping := range portMappings {
+		if portMapping.Binding.HostIP != "" {
+			return "", fmt.Errorf("HostIP is not supported by a service.")
+		}
+	}
+	return value, err
+}
+
+type serviceOptions struct {
+	name    string
+	labels  opts.ListOpts
+	image   string
+	command []string
+	args    []string
+	env     opts.ListOpts
+	workdir string
+	user    string
+	mounts  MountOpt
+
+	resources resourceOptions
+	stopGrace DurationOpt
+
+	replicas Uint64Opt
+	mode     string
+
+	restartPolicy restartPolicyOptions
+	constraints   []string
+	update        updateOptions
+	networks      []string
+	endpoint      endpointOptions
+}
+
+func newServiceOptions() *serviceOptions {
+	return &serviceOptions{
+		labels: opts.NewListOpts(runconfigopts.ValidateEnv),
+		env:    opts.NewListOpts(runconfigopts.ValidateEnv),
+		endpoint: endpointOptions{
+			ports: opts.NewListOpts(ValidatePort),
+		},
+	}
+}
+
+func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
+	var service swarm.ServiceSpec
+
+	service = swarm.ServiceSpec{
+		Annotations: swarm.Annotations{
+			Name:   opts.name,
+			Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
+		},
+		TaskTemplate: swarm.TaskSpec{
+			ContainerSpec: swarm.ContainerSpec{
+				Image:           opts.image,
+				Command:         opts.command,
+				Args:            opts.args,
+				Env:             opts.env.GetAll(),
+				Dir:             opts.workdir,
+				User:            opts.user,
+				Mounts:          opts.mounts.Value(),
+				StopGracePeriod: opts.stopGrace.Value(),
+			},
+			Resources:     opts.resources.ToResourceRequirements(),
+			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
+			Placement: &swarm.Placement{
+				Constraints: opts.constraints,
+			},
+		},
+		Mode: swarm.ServiceMode{},
+		UpdateConfig: &swarm.UpdateConfig{
+			Parallelism: opts.update.parallelism,
+			Delay:       opts.update.delay,
+		},
+		Networks:     convertNetworks(opts.networks),
+		EndpointSpec: opts.endpoint.ToEndpointSpec(),
+	}
+
+	switch opts.mode {
+	case "global":
+		if opts.replicas.Value() != nil {
+			return service, fmt.Errorf("replicas can only be used with replicated mode")
+		}
+
+		service.Mode.Global = &swarm.GlobalService{}
+	case "replicated":
+		service.Mode.Replicated = &swarm.ReplicatedService{
+			Replicas: opts.replicas.Value(),
+		}
+	default:
+		return service, fmt.Errorf("Unknown mode: %s", opts.mode)
+	}
+	return service, nil
+}
+
+// addServiceFlags adds all flags that are common to both `create` and `update.
+// Any flags that are not common are added separately in the individual command
+func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
+	flags := cmd.Flags()
+	flags.StringVar(&opts.name, "name", "", "Service name")
+	flags.VarP(&opts.labels, "label", "l", "Service labels")
+
+	flags.VarP(&opts.env, "env", "e", "Set environment variables")
+	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
+	flags.StringVarP(&opts.user, "user", "u", "", "Username or UID")
+	flags.VarP(&opts.mounts, "mount", "m", "Attach a mount to the service")
+
+	flags.Var(&opts.resources.limitCPU, "limit-cpu", "Limit CPUs")
+	flags.Var(&opts.resources.limitMemBytes, "limit-memory", "Limit Memory")
+	flags.Var(&opts.resources.resCPU, "reserve-cpu", "Reserve CPUs")
+	flags.Var(&opts.resources.resMemBytes, "reserve-memory", "Reserve Memory")
+	flags.Var(&opts.stopGrace, "stop-grace-period", "Time to wait before force killing a container")
+
+	flags.StringVar(&opts.mode, "mode", "replicated", "Service mode (replicated or global)")
+	flags.Var(&opts.replicas, "replicas", "Number of tasks")
+
+	flags.StringVar(&opts.restartPolicy.condition, "restart-condition", "", "Restart when condition is met (none, on_failure, or any)")
+	flags.Var(&opts.restartPolicy.delay, "restart-delay", "Delay between restart attempts")
+	flags.Var(&opts.restartPolicy.maxAttempts, "restart-max-attempts", "Maximum number of restarts before giving up")
+	flags.Var(&opts.restartPolicy.window, "restart-window", "Window used to evalulate the restart policy")
+
+	flags.StringSliceVar(&opts.constraints, "constraint", []string{}, "Placement constraints")
+
+	flags.Uint64Var(&opts.update.parallelism, "update-parallelism", 1, "Maximum number of tasks updated simultaneously")
+	flags.DurationVar(&opts.update.delay, "update-delay", time.Duration(0), "Delay between updates")
+
+	flags.StringSliceVar(&opts.networks, "network", []string{}, "Network attachments")
+	flags.StringVar(&opts.endpoint.mode, "endpoint-mode", "", "Endpoint mode(Valid values: VIP, DNSRR)")
+	flags.VarP(&opts.endpoint.ports, "publish", "p", "Publish a port as a node port")
+}

+ 47 - 0
api/client/service/remove.go

@@ -0,0 +1,47 @@
+package service
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
+
+	cmd := &cobra.Command{
+		Use:     "rm [OPTIONS] SERVICE",
+		Aliases: []string{"remove"},
+		Short:   "Remove a service",
+		Args:    cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runRemove(dockerCli, args)
+		},
+	}
+	cmd.Flags()
+
+	return cmd
+}
+
+func runRemove(dockerCli *client.DockerCli, sids []string) error {
+	client := dockerCli.Client()
+
+	ctx := context.Background()
+
+	var errs []string
+	for _, sid := range sids {
+		err := client.ServiceRemove(ctx, sid)
+		if err != nil {
+			errs = append(errs, err.Error())
+			continue
+		}
+		fmt.Fprintf(dockerCli.Out(), "%s\n", sid)
+	}
+	if len(errs) > 0 {
+		return fmt.Errorf(strings.Join(errs, "\n"))
+	}
+	return nil
+}

+ 86 - 0
api/client/service/scale.go

@@ -0,0 +1,86 @@
+package service
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+func newScaleCommand(dockerCli *client.DockerCli) *cobra.Command {
+	return &cobra.Command{
+		Use:   "scale SERVICE=SCALE [SERVICE=SCALE...]",
+		Short: "Scale one or multiple services",
+		Args:  scaleArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runScale(dockerCli, args)
+		},
+	}
+}
+
+func scaleArgs(cmd *cobra.Command, args []string) error {
+	if err := cli.RequiresMinArgs(1)(cmd, args); err != nil {
+		return err
+	}
+	for _, arg := range args {
+		if parts := strings.SplitN(arg, "=", 2); len(parts) != 2 {
+			return fmt.Errorf(
+				"Invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage:  %s\n\n%s",
+				arg,
+				cmd.CommandPath(),
+				cmd.UseLine(),
+				cmd.Short,
+			)
+		}
+	}
+	return nil
+}
+
+func runScale(dockerCli *client.DockerCli, args []string) error {
+	var errors []string
+	for _, arg := range args {
+		parts := strings.SplitN(arg, "=", 2)
+		serviceID, scale := parts[0], parts[1]
+		if err := runServiceScale(dockerCli, serviceID, scale); err != nil {
+			errors = append(errors, fmt.Sprintf("%s: %s", serviceID, err.Error()))
+		}
+	}
+
+	if len(errors) == 0 {
+		return nil
+	}
+	return fmt.Errorf(strings.Join(errors, "\n"))
+}
+
+func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	service, err := client.ServiceInspect(ctx, serviceID)
+	if err != nil {
+		return err
+	}
+
+	serviceMode := &service.Spec.Mode
+	if serviceMode.Replicated == nil {
+		return fmt.Errorf("scale can only be used with replicated mode")
+	}
+	uintScale, err := strconv.ParseUint(scale, 10, 64)
+	if err != nil {
+		return fmt.Errorf("invalid replicas value %s: %s", scale, err.Error())
+	}
+	serviceMode.Replicated.Replicas = &uintScale
+
+	err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "%s scaled to %s\n", serviceID, scale)
+	return nil
+}

+ 65 - 0
api/client/service/tasks.go

@@ -0,0 +1,65 @@
+package service
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/idresolver"
+	"github.com/docker/docker/api/client/task"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+)
+
+type tasksOptions struct {
+	serviceID string
+	all       bool
+	noResolve bool
+	filter    opts.FilterOpt
+}
+
+func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := tasksOptions{filter: opts.NewFilterOpt()}
+
+	cmd := &cobra.Command{
+		Use:   "tasks [OPTIONS] SERVICE",
+		Short: "List the tasks of a service",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.serviceID = args[0]
+			return runTasks(dockerCli, opts)
+		},
+	}
+	flags := cmd.Flags()
+	flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
+	flags.BoolVarP(&opts.noResolve, "no-resolve", "n", false, "Do not map IDs to Names")
+	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
+
+	return cmd
+}
+
+func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	service, err := client.ServiceInspect(ctx, opts.serviceID)
+	if err != nil {
+		return err
+	}
+
+	filter := opts.filter.Value()
+	filter.Add("service", service.ID)
+	if !opts.all && !filter.Include("desired_state") {
+		filter.Add("desired_state", string(swarm.TaskStateRunning))
+		filter.Add("desired_state", string(swarm.TaskStateAccepted))
+	}
+
+	tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: filter})
+	if err != nil {
+		return err
+	}
+
+	return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve))
+}

+ 244 - 0
api/client/service/update.go

@@ -0,0 +1,244 @@
+package service
+
+import (
+	"fmt"
+	"time"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/docker/go-connections/nat"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := newServiceOptions()
+	var flags *pflag.FlagSet
+
+	cmd := &cobra.Command{
+		Use:   "update [OPTIONS] SERVICE",
+		Short: "Update a service",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runUpdate(dockerCli, flags, args[0])
+		},
+	}
+
+	flags = cmd.Flags()
+	flags.String("image", "", "Service image tag")
+	flags.StringSlice("command", []string{}, "Service command")
+	flags.StringSlice("arg", []string{}, "Service command args")
+	addServiceFlags(cmd, opts)
+	return cmd
+}
+
+func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	service, err := client.ServiceInspect(ctx, serviceID)
+	if err != nil {
+		return err
+	}
+
+	err = mergeService(&service.Spec, flags)
+	if err != nil {
+		return err
+	}
+	err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
+	return nil
+}
+
+func mergeService(spec *swarm.ServiceSpec, flags *pflag.FlagSet) error {
+
+	mergeString := func(flag string, field *string) {
+		if flags.Changed(flag) {
+			*field, _ = flags.GetString(flag)
+		}
+	}
+
+	mergeListOpts := func(flag string, field *[]string) {
+		if flags.Changed(flag) {
+			value := flags.Lookup(flag).Value.(*opts.ListOpts)
+			*field = value.GetAll()
+		}
+	}
+
+	mergeSlice := func(flag string, field *[]string) {
+		if flags.Changed(flag) {
+			*field, _ = flags.GetStringSlice(flag)
+		}
+	}
+
+	mergeInt64Value := func(flag string, field *int64) {
+		if flags.Changed(flag) {
+			*field = flags.Lookup(flag).Value.(int64Value).Value()
+		}
+	}
+
+	mergeDuration := func(flag string, field *time.Duration) {
+		if flags.Changed(flag) {
+			*field, _ = flags.GetDuration(flag)
+		}
+	}
+
+	mergeDurationOpt := func(flag string, field *time.Duration) {
+		if flags.Changed(flag) {
+			*field = *flags.Lookup(flag).Value.(*DurationOpt).Value()
+		}
+	}
+
+	mergeUint64 := func(flag string, field *uint64) {
+		if flags.Changed(flag) {
+			*field, _ = flags.GetUint64(flag)
+		}
+	}
+
+	mergeUint64Opt := func(flag string, field *uint64) {
+		if flags.Changed(flag) {
+			*field = *flags.Lookup(flag).Value.(*Uint64Opt).Value()
+		}
+	}
+
+	cspec := &spec.TaskTemplate.ContainerSpec
+	task := &spec.TaskTemplate
+	mergeString("name", &spec.Name)
+	mergeLabels(flags, &spec.Labels)
+	mergeString("image", &cspec.Image)
+	mergeSlice("command", &cspec.Command)
+	mergeSlice("arg", &cspec.Command)
+	mergeListOpts("env", &cspec.Env)
+	mergeString("workdir", &cspec.Dir)
+	mergeString("user", &cspec.User)
+	mergeMounts(flags, &cspec.Mounts)
+
+	mergeInt64Value("limit-cpu", &task.Resources.Limits.NanoCPUs)
+	mergeInt64Value("limit-memory", &task.Resources.Limits.MemoryBytes)
+	mergeInt64Value("reserve-cpu", &task.Resources.Reservations.NanoCPUs)
+	mergeInt64Value("reserve-memory", &task.Resources.Reservations.MemoryBytes)
+
+	mergeDurationOpt("stop-grace-period", cspec.StopGracePeriod)
+
+	if flags.Changed("restart-policy-condition") {
+		value, _ := flags.GetString("restart-policy-condition")
+		task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
+	}
+	mergeDurationOpt("restart-policy-delay", task.RestartPolicy.Delay)
+	mergeUint64Opt("restart-policy-max-attempts", task.RestartPolicy.MaxAttempts)
+	mergeDurationOpt("restart-policy-window", task.RestartPolicy.Window)
+	mergeSlice("constraint", &task.Placement.Constraints)
+
+	if err := mergeMode(flags, &spec.Mode); err != nil {
+		return err
+	}
+
+	mergeUint64("updateconfig-parallelism", &spec.UpdateConfig.Parallelism)
+	mergeDuration("updateconfig-delay", &spec.UpdateConfig.Delay)
+
+	mergeNetworks(flags, &spec.Networks)
+	if flags.Changed("endpoint-mode") {
+		value, _ := flags.GetString("endpoint-mode")
+		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
+	}
+
+	mergePorts(flags, &spec.EndpointSpec.Ports)
+
+	return nil
+}
+
+func mergeLabels(flags *pflag.FlagSet, field *map[string]string) {
+	if !flags.Changed("label") {
+		return
+	}
+
+	if *field == nil {
+		*field = make(map[string]string)
+	}
+
+	values := flags.Lookup("label").Value.(*opts.ListOpts).GetAll()
+	for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
+		(*field)[key] = value
+	}
+}
+
+// TODO: should this override by destination path, or does swarm handle that?
+func mergeMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
+	if !flags.Changed("mount") {
+		return
+	}
+
+	values := flags.Lookup("mount").Value.(*MountOpt).Value()
+	*mounts = append(*mounts, values...)
+}
+
+// TODO: should this override by name, or does swarm handle that?
+func mergePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
+	if !flags.Changed("ports") {
+		return
+	}
+
+	values := flags.Lookup("ports").Value.(*opts.ListOpts).GetAll()
+	ports, portBindings, _ := nat.ParsePortSpecs(values)
+
+	for port := range ports {
+		*portConfig = append(*portConfig, convertPortToPortConfig(port, portBindings)...)
+	}
+}
+
+func mergeNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) {
+	if !flags.Changed("network") {
+		return
+	}
+	networks, _ := flags.GetStringSlice("network")
+	for _, network := range networks {
+		*attachments = append(*attachments, swarm.NetworkAttachmentConfig{Target: network})
+	}
+}
+
+func mergeMode(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
+	if !flags.Changed("mode") && !flags.Changed("scale") {
+		return nil
+	}
+
+	var mode string
+	if flags.Changed("mode") {
+		mode, _ = flags.GetString("mode")
+	}
+
+	if !(mode == "replicated" || serviceMode.Replicated != nil) && flags.Changed("replicas") {
+		return fmt.Errorf("replicas can only be used with replicated mode")
+	}
+
+	if mode == "global" {
+		serviceMode.Replicated = nil
+		serviceMode.Global = &swarm.GlobalService{}
+		return nil
+	}
+
+	if flags.Changed("replicas") {
+		replicas := flags.Lookup("replicas").Value.(*Uint64Opt).Value()
+		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
+		serviceMode.Global = nil
+		return nil
+	}
+
+	if mode == "replicated" {
+		if serviceMode.Replicated != nil {
+			return nil
+		}
+		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: &DefaultReplicas}
+		serviceMode.Global = nil
+	}
+
+	return nil
+}

+ 30 - 0
api/client/swarm/cmd.go

@@ -0,0 +1,30 @@
+package swarm
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+)
+
+// NewSwarmCommand returns a cobra command for `swarm` subcommands
+func NewSwarmCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "swarm",
+		Short: "Manage docker swarm",
+		Args:  cli.NoArgs,
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
+		},
+	}
+	cmd.AddCommand(
+		newInitCommand(dockerCli),
+		newJoinCommand(dockerCli),
+		newUpdateCommand(dockerCli),
+		newLeaveCommand(dockerCli),
+		newInspectCommand(dockerCli),
+	)
+	return cmd
+}

+ 61 - 0
api/client/swarm/init.go

@@ -0,0 +1,61 @@
+package swarm
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+)
+
+type initOptions struct {
+	listenAddr      NodeAddrOption
+	autoAccept      AutoAcceptOption
+	forceNewCluster bool
+	secret          string
+}
+
+func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := initOptions{
+		listenAddr: NewNodeAddrOption(),
+		autoAccept: NewAutoAcceptOption(),
+	}
+
+	cmd := &cobra.Command{
+		Use:   "init",
+		Short: "Initialize a Swarm.",
+		Args:  cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runInit(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
+	flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager, or none)")
+	flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
+	flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state.")
+	return cmd
+}
+
+func runInit(dockerCli *client.DockerCli, opts initOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	req := swarm.InitRequest{
+		ListenAddr:      opts.listenAddr.String(),
+		ForceNewCluster: opts.forceNewCluster,
+	}
+
+	req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(opts.secret)
+
+	nodeID, err := client.SwarmInit(ctx, req)
+	if err != nil {
+		return err
+	}
+	fmt.Printf("Swarm initialized: current node (%s) is now a manager.\n", nodeID)
+	return nil
+}

+ 56 - 0
api/client/swarm/inspect.go

@@ -0,0 +1,56 @@
+package swarm
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/inspect"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+type inspectOptions struct {
+	format string
+	//	pretty  bool
+}
+
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts inspectOptions
+
+	cmd := &cobra.Command{
+		Use:   "inspect [OPTIONS]",
+		Short: "Inspect the Swarm",
+		Args:  cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			// if opts.pretty && len(opts.format) > 0 {
+			//	return fmt.Errorf("--format is incompatible with human friendly format")
+			// }
+			return runInspect(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
+	//flags.BoolVarP(&opts.pretty, "pretty", "h", false, "Print the information in a human friendly format.")
+	return cmd
+}
+
+func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	swarm, err := client.SwarmInspect(ctx)
+	if err != nil {
+		return err
+	}
+
+	getRef := func(_ string) (interface{}, []byte, error) {
+		return swarm, nil, nil
+	}
+
+	//	if !opts.pretty {
+	return inspect.Inspect(dockerCli.Out(), []string{""}, opts.format, getRef)
+	//	}
+
+	//return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
+}

+ 65 - 0
api/client/swarm/join.go

@@ -0,0 +1,65 @@
+package swarm
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+type joinOptions struct {
+	remote     string
+	listenAddr NodeAddrOption
+	manager    bool
+	secret     string
+	CACertHash string
+}
+
+func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := joinOptions{
+		listenAddr: NodeAddrOption{addr: defaultListenAddr},
+	}
+
+	cmd := &cobra.Command{
+		Use:   "join [OPTIONS] HOST:PORT",
+		Short: "Join a Swarm as a node and/or manager.",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.remote = args[0]
+			return runJoin(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
+	flags.BoolVar(&opts.manager, "manager", false, "Try joining as a manager.")
+	flags.StringVar(&opts.secret, "secret", "", "Secret for node acceptance")
+	flags.StringVar(&opts.CACertHash, "ca-hash", "", "Hash of the Root Certificate Authority certificate used for trusted join")
+	return cmd
+}
+
+func runJoin(dockerCli *client.DockerCli, opts joinOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	req := swarm.JoinRequest{
+		Manager:     opts.manager,
+		Secret:      opts.secret,
+		ListenAddr:  opts.listenAddr.String(),
+		RemoteAddrs: []string{opts.remote},
+		CACertHash:  opts.CACertHash,
+	}
+	err := client.SwarmJoin(ctx, req)
+	if err != nil {
+		return err
+	}
+	if opts.manager {
+		fmt.Fprintln(dockerCli.Out(), "This node joined a Swarm as a manager.")
+	} else {
+		fmt.Fprintln(dockerCli.Out(), "This node joined a Swarm as a worker.")
+	}
+	return nil
+}

+ 44 - 0
api/client/swarm/leave.go

@@ -0,0 +1,44 @@
+package swarm
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+type leaveOptions struct {
+	force bool
+}
+
+func newLeaveCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := leaveOptions{}
+
+	cmd := &cobra.Command{
+		Use:   "leave",
+		Short: "Leave a Swarm.",
+		Args:  cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runLeave(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.BoolVar(&opts.force, "force", false, "Force leave ignoring warnings.")
+	return cmd
+}
+
+func runLeave(dockerCli *client.DockerCli, opts leaveOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	if err := client.SwarmLeave(ctx, opts.force); err != nil {
+		return err
+	}
+
+	fmt.Fprintln(dockerCli.Out(), "Node left the default swarm.")
+	return nil
+}

+ 120 - 0
api/client/swarm/opts.go

@@ -0,0 +1,120 @@
+package swarm
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/docker/engine-api/types/swarm"
+)
+
+const (
+	defaultListenAddr = "0.0.0.0:2377"
+	// WORKER constant for worker name
+	WORKER = "WORKER"
+	// MANAGER constant for manager name
+	MANAGER = "MANAGER"
+)
+
+var (
+	defaultPolicies = []swarm.Policy{
+		{Role: WORKER, Autoaccept: true},
+		{Role: MANAGER, Autoaccept: false},
+	}
+)
+
+// NodeAddrOption is a pflag.Value for listen and remote addresses
+type NodeAddrOption struct {
+	addr string
+}
+
+// String prints the representation of this flag
+func (a *NodeAddrOption) String() string {
+	return a.addr
+}
+
+// Set the value for this flag
+func (a *NodeAddrOption) Set(value string) error {
+	if !strings.Contains(value, ":") {
+		return fmt.Errorf("Invalud url, a host and port are required")
+	}
+
+	parts := strings.Split(value, ":")
+	if len(parts) != 2 {
+		return fmt.Errorf("Invalud url, too many colons")
+	}
+
+	a.addr = value
+	return nil
+}
+
+// Type returns the type of this flag
+func (a *NodeAddrOption) Type() string {
+	return "node-addr"
+}
+
+// NewNodeAddrOption returns a new node address option
+func NewNodeAddrOption() NodeAddrOption {
+	return NodeAddrOption{addr: defaultListenAddr}
+}
+
+// AutoAcceptOption is a value type for auto-accept policy
+type AutoAcceptOption struct {
+	values map[string]bool
+}
+
+// String prints a string representation of this option
+func (o *AutoAcceptOption) String() string {
+	keys := []string{}
+	for key := range o.values {
+		keys = append(keys, key)
+	}
+	return strings.Join(keys, " ")
+}
+
+// Set sets a new value on this option
+func (o *AutoAcceptOption) Set(value string) error {
+	value = strings.ToUpper(value)
+	switch value {
+	case "", "NONE":
+		if accept, ok := o.values[WORKER]; ok && accept {
+			return fmt.Errorf("value NONE is incompatible with %s", WORKER)
+		}
+		if accept, ok := o.values[MANAGER]; ok && accept {
+			return fmt.Errorf("value NONE is incompatible with %s", MANAGER)
+		}
+		o.values[WORKER] = false
+		o.values[MANAGER] = false
+	case WORKER, MANAGER:
+		if accept, ok := o.values[value]; ok && !accept {
+			return fmt.Errorf("value NONE is incompatible with %s", value)
+		}
+		o.values[value] = true
+	default:
+		return fmt.Errorf("must be one of %s, %s, NONE", WORKER, MANAGER)
+	}
+
+	return nil
+}
+
+// Type returns the type of this option
+func (o *AutoAcceptOption) Type() string {
+	return "auto-accept"
+}
+
+// Policies returns a representation of this option for the api
+func (o *AutoAcceptOption) Policies(secret string) []swarm.Policy {
+	policies := []swarm.Policy{}
+	for _, p := range defaultPolicies {
+		if len(o.values) != 0 {
+			p.Autoaccept = o.values[string(p.Role)]
+		}
+		p.Secret = secret
+		policies = append(policies, p)
+	}
+	return policies
+}
+
+// NewAutoAcceptOption returns a new auto-accept option
+func NewAutoAcceptOption() AutoAcceptOption {
+	return AutoAcceptOption{values: make(map[string]bool)}
+}

+ 93 - 0
api/client/swarm/update.go

@@ -0,0 +1,93 @@
+package swarm
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+type updateOptions struct {
+	autoAccept       AutoAcceptOption
+	secret           string
+	taskHistoryLimit int64
+	heartbeatPeriod  uint64
+}
+
+func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := updateOptions{autoAccept: NewAutoAcceptOption()}
+	var flags *pflag.FlagSet
+
+	cmd := &cobra.Command{
+		Use:   "update",
+		Short: "update the Swarm.",
+		Args:  cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runUpdate(dockerCli, flags, opts)
+		},
+	}
+
+	flags = cmd.Flags()
+	flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager or none)")
+	flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
+	flags.Int64Var(&opts.taskHistoryLimit, "task-history-limit", 10, "Task history retention limit")
+	flags.Uint64Var(&opts.heartbeatPeriod, "dispatcher-heartbeat-period", 5000000000, "Dispatcher heartbeat period")
+	return cmd
+}
+
+func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	swarm, err := client.SwarmInspect(ctx)
+	if err != nil {
+		return err
+	}
+
+	err = mergeSwarm(&swarm, flags)
+	if err != nil {
+		return err
+	}
+	err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec)
+	if err != nil {
+		return err
+	}
+
+	fmt.Println("Swarm updated.")
+	return nil
+}
+
+func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
+	spec := &swarm.Spec
+
+	if flags.Changed("auto-accept") {
+		value := flags.Lookup("auto-accept").Value.(*AutoAcceptOption)
+		if len(spec.AcceptancePolicy.Policies) > 0 {
+			spec.AcceptancePolicy.Policies = value.Policies(spec.AcceptancePolicy.Policies[0].Secret)
+		} else {
+			spec.AcceptancePolicy.Policies = value.Policies("")
+		}
+	}
+
+	if flags.Changed("secret") {
+		secret, _ := flags.GetString("secret")
+		for _, policy := range spec.AcceptancePolicy.Policies {
+			policy.Secret = secret
+		}
+	}
+
+	if flags.Changed("task-history-limit") {
+		spec.Orchestration.TaskHistoryRetentionLimit, _ = flags.GetInt64("task-history-limit")
+	}
+
+	if flags.Changed("dispatcher-heartbeat-period") {
+		spec.Dispatcher.HeartbeatPeriod, _ = flags.GetUint64("dispatcher-heartbeat-period")
+	}
+
+	return nil
+}

+ 20 - 0
api/client/tag.go

@@ -0,0 +1,20 @@
+package client
+
+import (
+	"golang.org/x/net/context"
+
+	Cli "github.com/docker/docker/cli"
+	flag "github.com/docker/docker/pkg/mflag"
+)
+
+// CmdTag tags an image into a repository.
+//
+// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
+func (cli *DockerCli) CmdTag(args ...string) error {
+	cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, Cli.DockerCommands["tag"].Description, true)
+	cmd.Require(flag.Exact, 2)
+
+	cmd.ParseFlags(args, true)
+
+	return cli.client.ImageTag(context.Background(), cmd.Arg(0), cmd.Arg(1))
+}

+ 79 - 0
api/client/task/print.go

@@ -0,0 +1,79 @@
+package task
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"text/tabwriter"
+	"time"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/idresolver"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/docker/go-units"
+)
+
+const (
+	psTaskItemFmt = "%s\t%s\t%s\t%s\t%s %s\t%s\t%s\n"
+)
+
+type tasksBySlot []swarm.Task
+
+func (t tasksBySlot) Len() int {
+	return len(t)
+}
+
+func (t tasksBySlot) Swap(i, j int) {
+	t[i], t[j] = t[j], t[i]
+}
+
+func (t tasksBySlot) Less(i, j int) bool {
+	// Sort by slot.
+	if t[i].Slot != t[j].Slot {
+		return t[i].Slot < t[j].Slot
+	}
+
+	// If same slot, sort by most recent.
+	return t[j].Meta.CreatedAt.Before(t[i].CreatedAt)
+}
+
+// Print task information in a table format
+func Print(dockerCli *client.DockerCli, ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver) error {
+	sort.Stable(tasksBySlot(tasks))
+
+	writer := tabwriter.NewWriter(dockerCli.Out(), 0, 4, 2, ' ', 0)
+
+	// Ignore flushing errors
+	defer writer.Flush()
+	fmt.Fprintln(writer, strings.Join([]string{"ID", "NAME", "SERVICE", "IMAGE", "LAST STATE", "DESIRED STATE", "NODE"}, "\t"))
+	for _, task := range tasks {
+		serviceValue, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID)
+		if err != nil {
+			return err
+		}
+		nodeValue, err := resolver.Resolve(ctx, swarm.Node{}, task.NodeID)
+		if err != nil {
+			return err
+		}
+		name := serviceValue
+		if task.Slot > 0 {
+			name = fmt.Sprintf("%s.%d", name, task.Slot)
+		}
+		fmt.Fprintf(
+			writer,
+			psTaskItemFmt,
+			task.ID,
+			name,
+			serviceValue,
+			task.Spec.ContainerSpec.Image,
+			client.PrettyPrint(task.Status.State),
+			units.HumanDuration(time.Since(task.Status.Timestamp)),
+			client.PrettyPrint(task.DesiredState),
+			nodeValue,
+		)
+	}
+
+	return nil
+}

+ 25 - 0
api/client/utils.go

@@ -8,6 +8,7 @@ import (
 	gosignal "os/signal"
 	"path/filepath"
 	"runtime"
+	"strings"
 	"time"
 
 	"golang.org/x/net/context"
@@ -163,3 +164,27 @@ func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os
 	}()
 	return sigc
 }
+
+// capitalizeFirst capitalizes the first character of string
+func capitalizeFirst(s string) string {
+	switch l := len(s); l {
+	case 0:
+		return s
+	case 1:
+		return strings.ToLower(s)
+	default:
+		return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:])
+	}
+}
+
+// PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter.
+func PrettyPrint(i interface{}) string {
+	switch t := i.(type) {
+	case nil:
+		return "None"
+	case string:
+		return capitalizeFirst(t)
+	default:
+		return capitalizeFirst(fmt.Sprintf("%s", t))
+	}
+}

+ 3 - 1
api/server/httputils/errors.go

@@ -8,6 +8,7 @@ import (
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/versions"
 	"github.com/gorilla/mux"
+	"google.golang.org/grpc"
 )
 
 // httpStatusError is an interface
@@ -58,6 +59,7 @@ func GetHTTPErrorStatusCode(err error) int {
 			"wrong login/password":  http.StatusUnauthorized,
 			"unauthorized":          http.StatusUnauthorized,
 			"hasn't been activated": http.StatusForbidden,
+			"this node":             http.StatusNotAcceptable,
 		} {
 			if strings.Contains(errStr, keyword) {
 				statusCode = status
@@ -85,7 +87,7 @@ func MakeErrorHandler(err error) http.HandlerFunc {
 			}
 			WriteJSON(w, statusCode, response)
 		} else {
-			http.Error(w, err.Error(), statusCode)
+			http.Error(w, grpc.ErrorDesc(err), statusCode)
 		}
 	}
 }

+ 1 - 2
api/server/router/network/backend.go

@@ -2,7 +2,6 @@ package network
 
 import (
 	"github.com/docker/engine-api/types"
-	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/network"
 	"github.com/docker/libnetwork"
 )
@@ -13,7 +12,7 @@ type Backend interface {
 	FindNetwork(idName string) (libnetwork.Network, error)
 	GetNetworkByName(idName string) (libnetwork.Network, error)
 	GetNetworksByID(partialID string) []libnetwork.Network
-	FilterNetworks(netFilters filters.Args) ([]libnetwork.Network, error)
+	GetNetworks() []libnetwork.Network
 	CreateNetwork(nc types.NetworkCreateRequest) (*types.NetworkCreateResponse, error)
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	DisconnectContainerFromNetwork(containerName string, network libnetwork.Network, force bool) error

+ 98 - 0
api/server/router/network/filter.go

@@ -0,0 +1,98 @@
+package network
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/runconfig"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/filters"
+)
+
+type filterHandler func([]types.NetworkResource, string) ([]types.NetworkResource, error)
+
+var (
+	// AcceptedFilters is an acceptable filters for validation
+	AcceptedFilters = map[string]bool{
+		"driver": true,
+		"type":   true,
+		"name":   true,
+		"id":     true,
+		"label":  true,
+	}
+)
+
+func filterNetworkByType(nws []types.NetworkResource, netType string) (retNws []types.NetworkResource, err error) {
+	switch netType {
+	case "builtin":
+		for _, nw := range nws {
+			if runconfig.IsPreDefinedNetwork(nw.Name) {
+				retNws = append(retNws, nw)
+			}
+		}
+	case "custom":
+		for _, nw := range nws {
+			if !runconfig.IsPreDefinedNetwork(nw.Name) {
+				retNws = append(retNws, nw)
+			}
+		}
+	default:
+		return nil, fmt.Errorf("Invalid filter: 'type'='%s'", netType)
+	}
+	return retNws, nil
+}
+
+// filterNetworks filters network list according to user specified filter
+// and returns user chosen networks
+func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.NetworkResource, error) {
+	// if filter is empty, return original network list
+	if filter.Len() == 0 {
+		return nws, nil
+	}
+
+	if err := filter.Validate(AcceptedFilters); err != nil {
+		return nil, err
+	}
+
+	var displayNet []types.NetworkResource
+	for _, nw := range nws {
+		if filter.Include("driver") {
+			if !filter.ExactMatch("driver", nw.Driver) {
+				continue
+			}
+		}
+		if filter.Include("name") {
+			if !filter.Match("name", nw.Name) {
+				continue
+			}
+		}
+		if filter.Include("id") {
+			if !filter.Match("id", nw.ID) {
+				continue
+			}
+		}
+		if filter.Include("label") {
+			if !filter.MatchKVList("label", nw.Labels) {
+				continue
+			}
+		}
+		displayNet = append(displayNet, nw)
+	}
+
+	if filter.Include("type") {
+		var typeNet []types.NetworkResource
+		errFilter := filter.WalkValues("type", func(fval string) error {
+			passList, err := filterNetworkByType(displayNet, fval)
+			if err != nil {
+				return err
+			}
+			typeNet = append(typeNet, passList...)
+			return nil
+		})
+		if errFilter != nil {
+			return nil, errFilter
+		}
+		displayNet = typeNet
+	}
+
+	return displayNet, nil
+}

+ 10 - 5
api/server/router/network/network.go

@@ -1,17 +1,22 @@
 package network
 
-import "github.com/docker/docker/api/server/router"
+import (
+	"github.com/docker/docker/api/server/router"
+	"github.com/docker/docker/daemon/cluster"
+)
 
 // networkRouter is a router to talk with the network controller
 type networkRouter struct {
-	backend Backend
-	routes  []router.Route
+	backend         Backend
+	clusterProvider *cluster.Cluster
+	routes          []router.Route
 }
 
 // NewRouter initializes a new network router
-func NewRouter(b Backend) router.Router {
+func NewRouter(b Backend, c *cluster.Cluster) router.Router {
 	r := &networkRouter{
-		backend: b,
+		backend:         b,
+		clusterProvider: c,
 	}
 	r.initRoutes()
 	return r

+ 42 - 9
api/server/router/network/network_routes.go

@@ -24,17 +24,30 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit
 		return err
 	}
 
-	list := []*types.NetworkResource{}
+	list := []types.NetworkResource{}
 
-	nwList, err := n.backend.FilterNetworks(netFilters)
-	if err != nil {
-		return err
+	if nr, err := n.clusterProvider.GetNetworks(); err == nil {
+		for _, nw := range nr {
+			list = append(list, nw)
+		}
 	}
 
-	for _, nw := range nwList {
-		list = append(list, buildNetworkResource(nw))
+	// Combine the network list returned by Docker daemon if it is not already
+	// returned by the cluster manager
+SKIP:
+	for _, nw := range n.backend.GetNetworks() {
+		for _, nl := range list {
+			if nl.ID == nw.ID() {
+				continue SKIP
+			}
+		}
+		list = append(list, *n.buildNetworkResource(nw))
 	}
 
+	list, err = filterNetworks(list, netFilters)
+	if err != nil {
+		return err
+	}
 	return httputils.WriteJSON(w, http.StatusOK, list)
 }
 
@@ -45,9 +58,12 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
 
 	nw, err := n.backend.FindNetwork(vars["id"])
 	if err != nil {
+		if nr, err := n.clusterProvider.GetNetwork(vars["id"]); err == nil {
+			return httputils.WriteJSON(w, http.StatusOK, nr)
+		}
 		return err
 	}
-	return httputils.WriteJSON(w, http.StatusOK, buildNetworkResource(nw))
+	return httputils.WriteJSON(w, http.StatusOK, n.buildNetworkResource(nw))
 }
 
 func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -67,7 +83,14 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr
 
 	nw, err := n.backend.CreateNetwork(create)
 	if err != nil {
-		return err
+		if _, ok := err.(libnetwork.ManagerRedirectError); !ok {
+			return err
+		}
+		id, err := n.clusterProvider.CreateNetwork(create)
+		if err != nil {
+			return err
+		}
+		nw = &types.NetworkCreateResponse{ID: id}
 	}
 
 	return httputils.WriteJSON(w, http.StatusCreated, nw)
@@ -121,6 +144,9 @@ func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 	}
+	if _, err := n.clusterProvider.GetNetwork(vars["id"]); err == nil {
+		return n.clusterProvider.RemoveNetwork(vars["id"])
+	}
 	if err := n.backend.DeleteNetwork(vars["id"]); err != nil {
 		return err
 	}
@@ -128,7 +154,7 @@ func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter
 	return nil
 }
 
-func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
+func (n *networkRouter) buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
 	r := &types.NetworkResource{}
 	if nw == nil {
 		return r
@@ -138,6 +164,13 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
 	r.Name = nw.Name()
 	r.ID = nw.ID()
 	r.Scope = info.Scope()
+	if n.clusterProvider.IsManager() {
+		if _, err := n.clusterProvider.GetNetwork(nw.Name()); err == nil {
+			r.Scope = "swarm"
+		}
+	} else if info.Dynamic() {
+		r.Scope = "swarm"
+	}
 	r.Driver = nw.Type()
 	r.EnableIPv6 = info.IPv6Enabled()
 	r.Internal = info.Internal()

+ 26 - 0
api/server/router/swarm/backend.go

@@ -0,0 +1,26 @@
+package swarm
+
+import (
+	basictypes "github.com/docker/engine-api/types"
+	types "github.com/docker/engine-api/types/swarm"
+)
+
+// Backend abstracts an swarm commands manager.
+type Backend interface {
+	Init(req types.InitRequest) (string, error)
+	Join(req types.JoinRequest) error
+	Leave(force bool) error
+	Inspect() (types.Swarm, error)
+	Update(uint64, types.Spec) error
+	GetServices(basictypes.ServiceListOptions) ([]types.Service, error)
+	GetService(string) (types.Service, error)
+	CreateService(types.ServiceSpec) (string, error)
+	UpdateService(string, uint64, types.ServiceSpec) error
+	RemoveService(string) error
+	GetNodes(basictypes.NodeListOptions) ([]types.Node, error)
+	GetNode(string) (types.Node, error)
+	UpdateNode(string, uint64, types.NodeSpec) error
+	RemoveNode(string) error
+	GetTasks(basictypes.TaskListOptions) ([]types.Task, error)
+	GetTask(string) (types.Task, error)
+}

+ 44 - 0
api/server/router/swarm/cluster.go

@@ -0,0 +1,44 @@
+package swarm
+
+import "github.com/docker/docker/api/server/router"
+
+// buildRouter is a router to talk with the build controller
+type swarmRouter struct {
+	backend Backend
+	routes  []router.Route
+}
+
+// NewRouter initializes a new build router
+func NewRouter(b Backend) router.Router {
+	r := &swarmRouter{
+		backend: b,
+	}
+	r.initRoutes()
+	return r
+}
+
+// Routes returns the available routers to the swarm controller
+func (sr *swarmRouter) Routes() []router.Route {
+	return sr.routes
+}
+
+func (sr *swarmRouter) initRoutes() {
+	sr.routes = []router.Route{
+		router.NewPostRoute("/swarm/init", sr.initCluster),
+		router.NewPostRoute("/swarm/join", sr.joinCluster),
+		router.NewPostRoute("/swarm/leave", sr.leaveCluster),
+		router.NewGetRoute("/swarm", sr.inspectCluster),
+		router.NewPostRoute("/swarm/update", sr.updateCluster),
+		router.NewGetRoute("/services", sr.getServices),
+		router.NewGetRoute("/services/{id:.*}", sr.getService),
+		router.NewPostRoute("/services/create", sr.createService),
+		router.NewPostRoute("/services/{id:.*}/update", sr.updateService),
+		router.NewDeleteRoute("/services/{id:.*}", sr.removeService),
+		router.NewGetRoute("/nodes", sr.getNodes),
+		router.NewGetRoute("/nodes/{id:.*}", sr.getNode),
+		router.NewDeleteRoute("/nodes/{id:.*}", sr.removeNode),
+		router.NewPostRoute("/nodes/{id:.*}/update", sr.updateNode),
+		router.NewGetRoute("/tasks", sr.getTasks),
+		router.NewGetRoute("/tasks/{id:.*}", sr.getTask),
+	}
+}

+ 229 - 0
api/server/router/swarm/cluster_routes.go

@@ -0,0 +1,229 @@
+package swarm
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	basictypes "github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/filters"
+	types "github.com/docker/engine-api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+func (sr *swarmRouter) initCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	var req types.InitRequest
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		return err
+	}
+	nodeID, err := sr.backend.Init(req)
+	if err != nil {
+		logrus.Errorf("Error initializing swarm: %v", err)
+		return err
+	}
+	return httputils.WriteJSON(w, http.StatusOK, nodeID)
+}
+
+func (sr *swarmRouter) joinCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	var req types.JoinRequest
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		return err
+	}
+	return sr.backend.Join(req)
+}
+
+func (sr *swarmRouter) leaveCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	force := httputils.BoolValue(r, "force")
+	return sr.backend.Leave(force)
+}
+
+func (sr *swarmRouter) inspectCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	swarm, err := sr.backend.Inspect()
+	if err != nil {
+		logrus.Errorf("Error getting swarm: %v", err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, swarm)
+}
+
+func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	var swarm types.Spec
+	if err := json.NewDecoder(r.Body).Decode(&swarm); err != nil {
+		return err
+	}
+
+	rawVersion := r.URL.Query().Get("version")
+	version, err := strconv.ParseUint(rawVersion, 10, 64)
+	if err != nil {
+		return fmt.Errorf("Invalid swarm version '%s': %s", rawVersion, err.Error())
+	}
+
+	if err := sr.backend.Update(version, swarm); err != nil {
+		logrus.Errorf("Error configuring swarm: %v", err)
+		return err
+	}
+	return nil
+}
+
+func (sr *swarmRouter) getServices(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+	filter, err := filters.FromParam(r.Form.Get("filters"))
+	if err != nil {
+		return err
+	}
+
+	services, err := sr.backend.GetServices(basictypes.ServiceListOptions{Filter: filter})
+	if err != nil {
+		logrus.Errorf("Error getting services: %v", err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, services)
+}
+
+func (sr *swarmRouter) getService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	service, err := sr.backend.GetService(vars["id"])
+	if err != nil {
+		logrus.Errorf("Error getting service %s: %v", vars["id"], err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, service)
+}
+
+func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	var service types.ServiceSpec
+	if err := json.NewDecoder(r.Body).Decode(&service); err != nil {
+		return err
+	}
+
+	id, err := sr.backend.CreateService(service)
+	if err != nil {
+		logrus.Errorf("Error reating service %s: %v", id, err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusCreated, &basictypes.ServiceCreateResponse{
+		ID: id,
+	})
+}
+
+func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	var service types.ServiceSpec
+	if err := json.NewDecoder(r.Body).Decode(&service); err != nil {
+		return err
+	}
+
+	rawVersion := r.URL.Query().Get("version")
+	version, err := strconv.ParseUint(rawVersion, 10, 64)
+	if err != nil {
+		return fmt.Errorf("Invalid service version '%s': %s", rawVersion, err.Error())
+	}
+
+	if err := sr.backend.UpdateService(vars["id"], version, service); err != nil {
+		logrus.Errorf("Error updating service %s: %v", vars["id"], err)
+		return err
+	}
+	return nil
+}
+
+func (sr *swarmRouter) removeService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := sr.backend.RemoveService(vars["id"]); err != nil {
+		logrus.Errorf("Error removing service %s: %v", vars["id"], err)
+		return err
+	}
+	return nil
+}
+
+func (sr *swarmRouter) getNodes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+	filter, err := filters.FromParam(r.Form.Get("filters"))
+	if err != nil {
+		return err
+	}
+
+	nodes, err := sr.backend.GetNodes(basictypes.NodeListOptions{Filter: filter})
+	if err != nil {
+		logrus.Errorf("Error getting nodes: %v", err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, nodes)
+}
+
+func (sr *swarmRouter) getNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	node, err := sr.backend.GetNode(vars["id"])
+	if err != nil {
+		logrus.Errorf("Error getting node %s: %v", vars["id"], err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, node)
+}
+
+func (sr *swarmRouter) updateNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	var node types.NodeSpec
+	if err := json.NewDecoder(r.Body).Decode(&node); err != nil {
+		return err
+	}
+
+	rawVersion := r.URL.Query().Get("version")
+	version, err := strconv.ParseUint(rawVersion, 10, 64)
+	if err != nil {
+		return fmt.Errorf("Invalid node version '%s': %s", rawVersion, err.Error())
+	}
+
+	if err := sr.backend.UpdateNode(vars["id"], version, node); err != nil {
+		logrus.Errorf("Error updating node %s: %v", vars["id"], err)
+		return err
+	}
+	return nil
+}
+
+func (sr *swarmRouter) removeNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := sr.backend.RemoveNode(vars["id"]); err != nil {
+		logrus.Errorf("Error removing node %s: %v", vars["id"], err)
+		return err
+	}
+	return nil
+}
+
+func (sr *swarmRouter) getTasks(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+	filter, err := filters.FromParam(r.Form.Get("filters"))
+	if err != nil {
+		return err
+	}
+
+	tasks, err := sr.backend.GetTasks(basictypes.TaskListOptions{Filter: filter})
+	if err != nil {
+		logrus.Errorf("Error getting tasks: %v", err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, tasks)
+}
+
+func (sr *swarmRouter) getTask(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	task, err := sr.backend.GetTask(vars["id"])
+	if err != nil {
+		logrus.Errorf("Error getting task %s: %v", vars["id"], err)
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, task)
+}

+ 10 - 5
api/server/router/system/system.go

@@ -1,18 +1,23 @@
 package system
 
-import "github.com/docker/docker/api/server/router"
+import (
+	"github.com/docker/docker/api/server/router"
+	"github.com/docker/docker/daemon/cluster"
+)
 
 // systemRouter provides information about the Docker system overall.
 // It gathers information about host, daemon and container events.
 type systemRouter struct {
-	backend Backend
-	routes  []router.Route
+	backend         Backend
+	clusterProvider *cluster.Cluster
+	routes          []router.Route
 }
 
 // NewRouter initializes a new system router
-func NewRouter(b Backend) router.Router {
+func NewRouter(b Backend, c *cluster.Cluster) router.Router {
 	r := &systemRouter{
-		backend: b,
+		backend:         b,
+		clusterProvider: c,
 	}
 
 	r.routes = []router.Route{

+ 3 - 0
api/server/router/system/system_routes.go

@@ -33,6 +33,9 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht
 	if err != nil {
 		return err
 	}
+	if s.clusterProvider != nil {
+		info.Swarm = s.clusterProvider.Info()
+	}
 
 	return httputils.WriteJSON(w, http.StatusOK, info)
 }

+ 6 - 0
cli/cobraadaptor/adaptor.go

@@ -5,7 +5,10 @@ import (
 	"github.com/docker/docker/api/client/container"
 	"github.com/docker/docker/api/client/image"
 	"github.com/docker/docker/api/client/network"
+	"github.com/docker/docker/api/client/node"
 	"github.com/docker/docker/api/client/registry"
+	"github.com/docker/docker/api/client/service"
+	"github.com/docker/docker/api/client/swarm"
 	"github.com/docker/docker/api/client/system"
 	"github.com/docker/docker/api/client/volume"
 	"github.com/docker/docker/cli"
@@ -36,6 +39,9 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 	rootCmd.SetFlagErrorFunc(cli.FlagErrorFunc)
 	rootCmd.SetOutput(stdout)
 	rootCmd.AddCommand(
+		node.NewNodeCommand(dockerCli),
+		service.NewServiceCommand(dockerCli),
+		swarm.NewSwarmCommand(dockerCli),
 		container.NewAttachCommand(dockerCli),
 		container.NewCommitCommand(dockerCli),
 		container.NewCreateCommand(dockerCli),

+ 1 - 1
cli/usage.go

@@ -11,7 +11,7 @@ var DockerCommandUsage = []Command{
 	{"cp", "Copy files/folders between a container and the local filesystem"},
 	{"exec", "Run a command in a running container"},
 	{"info", "Display system-wide information"},
-	{"inspect", "Return low-level information on a container or image"},
+	{"inspect", "Return low-level information on a container, image or task"},
 	{"update", "Update configuration of one or more containers"},
 }
 

+ 20 - 4
cmd/dockerd/daemon.go

@@ -20,12 +20,14 @@ import (
 	"github.com/docker/docker/api/server/router/container"
 	"github.com/docker/docker/api/server/router/image"
 	"github.com/docker/docker/api/server/router/network"
+	swarmrouter "github.com/docker/docker/api/server/router/swarm"
 	systemrouter "github.com/docker/docker/api/server/router/system"
 	"github.com/docker/docker/api/server/router/volume"
 	"github.com/docker/docker/builder/dockerfile"
 	cliflags "github.com/docker/docker/cli/flags"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/daemon"
+	"github.com/docker/docker/daemon/cluster"
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/dockerversion"
 	"github.com/docker/docker/libcontainerd"
@@ -208,6 +210,7 @@ func (cli *DaemonCli) start() (err error) {
 	}
 
 	api := apiserver.New(serverConfig)
+	cli.api = api
 
 	for i := 0; i < len(cli.Config.Hosts); i++ {
 		var err error
@@ -264,6 +267,17 @@ func (cli *DaemonCli) start() (err error) {
 		return fmt.Errorf("Error starting daemon: %v", err)
 	}
 
+	name, _ := os.Hostname()
+
+	c, err := cluster.New(cluster.Config{
+		Root:    cli.Config.Root,
+		Name:    name,
+		Backend: d,
+	})
+	if err != nil {
+		logrus.Fatalf("Error creating cluster component: %v", err)
+	}
+
 	logrus.Info("Daemon has completed initialization")
 
 	logrus.WithFields(logrus.Fields{
@@ -273,7 +287,7 @@ func (cli *DaemonCli) start() (err error) {
 	}).Info("Docker daemon")
 
 	cli.initMiddlewares(api, serverConfig)
-	initRouter(api, d)
+	initRouter(api, d, c)
 
 	cli.d = d
 	cli.setupConfigReloadTrap()
@@ -290,6 +304,7 @@ func (cli *DaemonCli) start() (err error) {
 	// Daemon is fully initialized and handling API traffic
 	// Wait for serve API to complete
 	errAPI := <-serveAPIWait
+	c.Cleanup()
 	shutdownDaemon(d, 15)
 	containerdRemote.Cleanup()
 	if errAPI != nil {
@@ -385,18 +400,19 @@ func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfi
 	return config, nil
 }
 
-func initRouter(s *apiserver.Server, d *daemon.Daemon) {
+func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
 	decoder := runconfig.ContainerDecoder{}
 
 	routers := []router.Router{
 		container.NewRouter(d, decoder),
 		image.NewRouter(d, decoder),
-		systemrouter.NewRouter(d),
+		systemrouter.NewRouter(d, c),
 		volume.NewRouter(d),
 		build.NewRouter(dockerfile.NewBuildManager(d)),
+		swarmrouter.NewRouter(c),
 	}
 	if d.NetworkControllerEnabled() {
-		routers = append(routers, network.NewRouter(d))
+		routers = append(routers, network.NewRouter(d, c))
 	}
 
 	s.InitRouter(utils.IsDebugEnabled(), routers...)

+ 23 - 1
container/container.go

@@ -66,6 +66,7 @@ type CommonContainer struct {
 	RWLayer         layer.RWLayer  `json:"-"`
 	ID              string
 	Created         time.Time
+	Managed         bool
 	Path            string
 	Args            []string
 	Config          *containertypes.Config
@@ -790,7 +791,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
 		ipam := epConfig.IPAMConfig
 		if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
 			createOptions = append(createOptions,
-				libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
+				libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil, nil))
 		}
 
 		for _, alias := range epConfig.Aliases {
@@ -798,6 +799,27 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
 		}
 	}
 
+	if container.NetworkSettings.Service != nil {
+		svcCfg := container.NetworkSettings.Service
+
+		var vip string
+		if svcCfg.VirtualAddresses[n.ID()] != nil {
+			vip = svcCfg.VirtualAddresses[n.ID()].IPv4
+		}
+
+		var portConfigs []*libnetwork.PortConfig
+		for _, portConfig := range svcCfg.ExposedPorts {
+			portConfigs = append(portConfigs, &libnetwork.PortConfig{
+				Name:          portConfig.Name,
+				Protocol:      libnetwork.PortConfig_Protocol(portConfig.Protocol),
+				TargetPort:    portConfig.TargetPort,
+				PublishedPort: portConfig.PublishedPort,
+			})
+		}
+
+		createOptions = append(createOptions, libnetwork.CreateOptionService(svcCfg.Name, svcCfg.ID, net.ParseIP(vip), portConfigs))
+	}
+
 	if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
 		createOptions = append(createOptions, libnetwork.CreateOptionDisableResolution())
 	}

+ 28 - 0
container/state.go

@@ -5,6 +5,8 @@ import (
 	"sync"
 	"time"
 
+	"golang.org/x/net/context"
+
 	"github.com/docker/go-units"
 )
 
@@ -139,6 +141,32 @@ func (s *State) WaitStop(timeout time.Duration) (int, error) {
 	return s.getExitCode(), nil
 }
 
+// WaitWithContext waits for the container to stop. Optional context can be
+// passed for canceling the request.
+func (s *State) WaitWithContext(ctx context.Context) <-chan int {
+	// todo(tonistiigi): make other wait functions use this
+	c := make(chan int)
+	go func() {
+		s.Lock()
+		if !s.Running {
+			exitCode := s.ExitCode
+			s.Unlock()
+			c <- exitCode
+			close(c)
+			return
+		}
+		waitChan := s.waitChan
+		s.Unlock()
+		select {
+		case <-waitChan:
+			c <- s.getExitCode()
+		case <-ctx.Done():
+		}
+		close(c)
+	}()
+	return c
+}
+
 // IsRunning returns whether the running flag is set. Used by Container to check whether a container is running.
 func (s *State) IsRunning() bool {
 	s.Lock()

+ 1056 - 0
daemon/cluster/cluster.go

@@ -0,0 +1,1056 @@
+package cluster
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+	"time"
+
+	"google.golang.org/grpc"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/daemon/cluster/convert"
+	executorpkg "github.com/docker/docker/daemon/cluster/executor"
+	"github.com/docker/docker/daemon/cluster/executor/container"
+	"github.com/docker/docker/errors"
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/docker/docker/runconfig"
+	apitypes "github.com/docker/engine-api/types"
+	types "github.com/docker/engine-api/types/swarm"
+	swarmagent "github.com/docker/swarmkit/agent"
+	swarmapi "github.com/docker/swarmkit/api"
+	"golang.org/x/net/context"
+)
+
+const swarmDirName = "swarm"
+const controlSocket = "control.sock"
+const swarmConnectTimeout = 5 * time.Second
+const stateFile = "docker-state.json"
+
+const (
+	initialReconnectDelay = 100 * time.Millisecond
+	maxReconnectDelay     = 10 * time.Second
+)
+
+// ErrNoManager is returned then a manager-only function is called on non-manager
+var ErrNoManager = fmt.Errorf("this node is not participating as a Swarm manager")
+
+// ErrNoSwarm is returned on leaving a cluster that was never initialized
+var ErrNoSwarm = fmt.Errorf("this node is not part of Swarm")
+
+// ErrSwarmExists is returned on initialize or join request for a cluster that has already been activated
+var ErrSwarmExists = fmt.Errorf("this node is already part of a Swarm")
+
+// ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
+var ErrSwarmJoinTimeoutReached = fmt.Errorf("timeout reached before node was joined")
+
+type state struct {
+	ListenAddr string
+}
+
+// Config provides values for Cluster.
+type Config struct {
+	Root    string
+	Name    string
+	Backend executorpkg.Backend
+}
+
+// Cluster provides capabilities to pariticipate in a cluster as worker or a
+// manager and a worker.
+type Cluster struct {
+	sync.RWMutex
+	root           string
+	config         Config
+	configEvent    chan struct{} // todo: make this array and goroutine safe
+	node           *swarmagent.Node
+	conn           *grpc.ClientConn
+	client         swarmapi.ControlClient
+	ready          bool
+	listenAddr     string
+	err            error
+	reconnectDelay time.Duration
+	stop           bool
+	cancelDelay    func()
+}
+
+// New creates a new Cluster instance using provided config.
+func New(config Config) (*Cluster, error) {
+	root := filepath.Join(config.Root, swarmDirName)
+	if err := os.MkdirAll(root, 0700); err != nil {
+		return nil, err
+	}
+	c := &Cluster{
+		root:           root,
+		config:         config,
+		configEvent:    make(chan struct{}, 10),
+		reconnectDelay: initialReconnectDelay,
+	}
+
+	dt, err := ioutil.ReadFile(filepath.Join(root, stateFile))
+	if err != nil {
+		if os.IsNotExist(err) {
+			return c, nil
+		}
+		return nil, err
+	}
+
+	var st state
+	if err := json.Unmarshal(dt, &st); err != nil {
+		return nil, err
+	}
+
+	n, ctx, err := c.startNewNode(false, st.ListenAddr, "", "", "", false)
+	if err != nil {
+		return nil, err
+	}
+
+	select {
+	case <-time.After(swarmConnectTimeout):
+		logrus.Errorf("swarm component could not be started before timeout was reached")
+	case <-n.Ready(context.Background()):
+	case <-ctx.Done():
+	}
+	if ctx.Err() != nil {
+		return nil, fmt.Errorf("swarm component could not be started")
+	}
+	go c.reconnectOnFailure(ctx)
+	return c, nil
+}
+
+func (c *Cluster) checkCompatibility() error {
+	info, _ := c.config.Backend.SystemInfo()
+	if info != nil && (info.ClusterStore != "" || info.ClusterAdvertise != "") {
+		return fmt.Errorf("swarm mode is incompatible with `--cluster-store` and `--cluster-advertise daemon configuration")
+	}
+	return nil
+}
+
+func (c *Cluster) saveState() error {
+	dt, err := json.Marshal(state{ListenAddr: c.listenAddr})
+	if err != nil {
+		return err
+	}
+	return ioutils.AtomicWriteFile(filepath.Join(c.root, stateFile), dt, 0600)
+}
+
+func (c *Cluster) reconnectOnFailure(ctx context.Context) {
+	for {
+		<-ctx.Done()
+		c.Lock()
+		if c.stop || c.node != nil {
+			c.Unlock()
+			return
+		}
+		c.reconnectDelay *= 2
+		if c.reconnectDelay > maxReconnectDelay {
+			c.reconnectDelay = maxReconnectDelay
+		}
+		logrus.Warnf("Restarting swarm in %.2f seconds", c.reconnectDelay.Seconds())
+		delayCtx, cancel := context.WithTimeout(context.Background(), c.reconnectDelay)
+		c.cancelDelay = cancel
+		c.Unlock()
+		<-delayCtx.Done()
+		if delayCtx.Err() != context.DeadlineExceeded {
+			return
+		}
+		c.Lock()
+		if c.node != nil {
+			c.Unlock()
+			return
+		}
+		var err error
+		_, ctx, err = c.startNewNode(false, c.listenAddr, c.getRemoteAddress(), "", "", false)
+		if err != nil {
+			c.err = err
+			ctx = delayCtx
+		}
+		c.Unlock()
+	}
+}
+
+func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secret, cahash string, ismanager bool) (*swarmagent.Node, context.Context, error) {
+	if err := c.checkCompatibility(); err != nil {
+		return nil, nil, err
+	}
+	c.node = nil
+	c.cancelDelay = nil
+	node, err := swarmagent.NewNode(&swarmagent.NodeConfig{
+		Hostname:         c.config.Name,
+		ForceNewCluster:  forceNewCluster,
+		ListenControlAPI: filepath.Join(c.root, controlSocket),
+		ListenRemoteAPI:  listenAddr,
+		JoinAddr:         joinAddr,
+		StateDir:         c.root,
+		CAHash:           cahash,
+		Secret:           secret,
+		Executor:         container.NewExecutor(c.config.Backend),
+		HeartbeatTick:    1,
+		ElectionTick:     3,
+		IsManager:        ismanager,
+	})
+	if err != nil {
+		return nil, nil, err
+	}
+	ctx, cancel := context.WithCancel(context.Background())
+	if err := node.Start(ctx); err != nil {
+		return nil, nil, err
+	}
+
+	c.node = node
+	c.listenAddr = listenAddr
+	c.saveState()
+	c.config.Backend.SetClusterProvider(c)
+	go func() {
+		err := node.Err(ctx)
+		if err != nil {
+			logrus.Errorf("cluster exited with error: %v", err)
+		}
+		c.Lock()
+		c.conn = nil
+		c.client = nil
+		c.node = nil
+		c.ready = false
+		c.err = err
+		c.Unlock()
+		cancel()
+	}()
+
+	go func() {
+		select {
+		case <-node.Ready(context.Background()):
+			c.Lock()
+			c.reconnectDelay = initialReconnectDelay
+			c.Unlock()
+		case <-ctx.Done():
+		}
+		if ctx.Err() == nil {
+			c.Lock()
+			c.ready = true
+			c.err = nil
+			c.Unlock()
+		}
+		c.configEvent <- struct{}{}
+	}()
+
+	go func() {
+		for conn := range node.ListenControlSocket(ctx) {
+			c.Lock()
+			if c.conn != conn {
+				c.client = swarmapi.NewControlClient(conn)
+			}
+			if c.conn != nil {
+				c.client = nil
+			}
+			c.conn = conn
+			c.Unlock()
+			c.configEvent <- struct{}{}
+		}
+	}()
+
+	return node, ctx, nil
+}
+
+// Init initializes new cluster from user provided request.
+func (c *Cluster) Init(req types.InitRequest) (string, error) {
+	c.Lock()
+	if c.node != nil {
+		c.Unlock()
+		if !req.ForceNewCluster {
+			return "", ErrSwarmExists
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+		defer cancel()
+		if err := c.node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
+			return "", err
+		}
+		c.Lock()
+		c.node = nil
+		c.conn = nil
+		c.ready = false
+	}
+	// todo: check current state existing
+	n, ctx, err := c.startNewNode(req.ForceNewCluster, req.ListenAddr, "", "", "", false)
+	if err != nil {
+		c.Unlock()
+		return "", err
+	}
+	c.Unlock()
+
+	select {
+	case <-n.Ready(context.Background()):
+		if err := initAcceptancePolicy(n, req.Spec.AcceptancePolicy); err != nil {
+			return "", err
+		}
+		go c.reconnectOnFailure(ctx)
+		return n.NodeID(), nil
+	case <-ctx.Done():
+		c.RLock()
+		defer c.RUnlock()
+		if c.err != nil {
+			if !req.ForceNewCluster { // if failure on first attempt don't keep state
+				if err := c.clearState(); err != nil {
+					return "", err
+				}
+			}
+			return "", c.err
+		}
+		return "", ctx.Err()
+	}
+}
+
+// Join makes current Cluster part of an existing swarm cluster.
+func (c *Cluster) Join(req types.JoinRequest) error {
+	c.Lock()
+	if c.node != nil {
+		c.Unlock()
+		return ErrSwarmExists
+	}
+	// todo: check current state existing
+	if len(req.RemoteAddrs) == 0 {
+		return fmt.Errorf("at least 1 RemoteAddr is required to join")
+	}
+	n, ctx, err := c.startNewNode(false, req.ListenAddr, req.RemoteAddrs[0], req.Secret, req.CACertHash, req.Manager)
+	if err != nil {
+		c.Unlock()
+		return err
+	}
+	c.Unlock()
+
+	select {
+	case <-time.After(swarmConnectTimeout):
+		go c.reconnectOnFailure(ctx)
+		if nodeid := n.NodeID(); nodeid != "" {
+			return fmt.Errorf("Timeout reached before node was joined. Your cluster settings may be preventing this node from automatically joining. To accept this node into cluster run `docker node accept %v` in an existing cluster manager", nodeid)
+		}
+		return ErrSwarmJoinTimeoutReached
+	case <-n.Ready(context.Background()):
+		go c.reconnectOnFailure(ctx)
+		return nil
+	case <-ctx.Done():
+		c.RLock()
+		defer c.RUnlock()
+		if c.err != nil {
+			return c.err
+		}
+		return ctx.Err()
+	}
+}
+
+func (c *Cluster) cancelReconnect() {
+	c.stop = true
+	if c.cancelDelay != nil {
+		c.cancelDelay()
+		c.cancelDelay = nil
+	}
+}
+
+// Leave shuts down Cluster and removes current state.
+func (c *Cluster) Leave(force bool) error {
+	c.Lock()
+	node := c.node
+	if node == nil {
+		c.Unlock()
+		return ErrNoSwarm
+	}
+
+	if node.Manager() != nil && !force {
+		msg := "You are attempting to leave cluster on a node that is participating as a manager. "
+		if c.isActiveManager() {
+			active, reachable, unreachable, err := c.managerStats()
+			if err == nil {
+				if active && reachable-2 <= unreachable {
+					if reachable == 1 && unreachable == 0 {
+						msg += "Leaving last manager will remove all current state of the cluster. Use `--force` to ignore this message. "
+						c.Unlock()
+						return fmt.Errorf(msg)
+					}
+					msg += fmt.Sprintf("Leaving cluster will leave you with  %v managers out of %v. This means Raft quorum will be lost and your cluster will become inaccessible. ", reachable-1, reachable+unreachable)
+				}
+			}
+		} else {
+			msg += "Doing so may lose the consenus of your cluster. "
+		}
+
+		msg += "Only way to restore a cluster that has lost consensus is to reinitialize it with `--force-new-cluster`. Use `--force` to ignore this message."
+		c.Unlock()
+		return fmt.Errorf(msg)
+	}
+	c.cancelReconnect()
+	c.Unlock()
+
+	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+	defer cancel()
+	if err := node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
+		return err
+	}
+	nodeID := node.NodeID()
+	for _, id := range c.config.Backend.ListContainersForNode(nodeID) {
+		if err := c.config.Backend.ContainerRm(id, &apitypes.ContainerRmConfig{ForceRemove: true}); err != nil {
+			logrus.Errorf("error removing %v: %v", id, err)
+		}
+	}
+	c.Lock()
+	defer c.Unlock()
+	c.node = nil
+	c.conn = nil
+	c.ready = false
+	c.configEvent <- struct{}{}
+	// todo: cleanup optional?
+	if err := c.clearState(); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (c *Cluster) clearState() error {
+	if err := os.RemoveAll(c.root); err != nil {
+		return err
+	}
+	if err := os.MkdirAll(c.root, 0700); err != nil {
+		return err
+	}
+	c.config.Backend.SetClusterProvider(nil)
+	return nil
+}
+
+func (c *Cluster) getRequestContext() context.Context { // TODO: not needed when requests don't block on qourum lost
+	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
+	return ctx
+}
+
+// Inspect retrives the confuguration properties of managed swarm cluster.
+func (c *Cluster) Inspect() (types.Swarm, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return types.Swarm{}, ErrNoManager
+	}
+
+	swarm, err := getSwarm(c.getRequestContext(), c.client)
+	if err != nil {
+		return types.Swarm{}, err
+	}
+
+	if err != nil {
+		return types.Swarm{}, err
+	}
+
+	return convert.SwarmFromGRPC(*swarm), nil
+}
+
+// Update updates configuration of a managed swarm cluster.
+func (c *Cluster) Update(version uint64, spec types.Spec) error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return ErrNoManager
+	}
+
+	swarmSpec, err := convert.SwarmSpecToGRPC(spec)
+	if err != nil {
+		return err
+	}
+
+	swarm, err := getSwarm(c.getRequestContext(), c.client)
+	if err != nil {
+		return err
+	}
+
+	_, err = c.client.UpdateCluster(
+		c.getRequestContext(),
+		&swarmapi.UpdateClusterRequest{
+			ClusterID: swarm.ID,
+			Spec:      &swarmSpec,
+			ClusterVersion: &swarmapi.Version{
+				Index: version,
+			},
+		},
+	)
+	return err
+}
+
+// IsManager returns true is Cluster is participating as a manager.
+func (c *Cluster) IsManager() bool {
+	c.RLock()
+	defer c.RUnlock()
+	return c.isActiveManager()
+}
+
+// IsAgent returns true is Cluster is participating as a worker/agent.
+func (c *Cluster) IsAgent() bool {
+	c.RLock()
+	defer c.RUnlock()
+	return c.ready
+}
+
+// GetListenAddress returns the listening address for current maanger's
+// consensus and dispatcher APIs.
+func (c *Cluster) GetListenAddress() string {
+	c.RLock()
+	defer c.RUnlock()
+	if c.conn != nil {
+		return c.listenAddr
+	}
+	return ""
+}
+
+// GetRemoteAddress returns a known advertise address of a remote maanger if
+// available.
+// todo: change to array/connect with info
+func (c *Cluster) GetRemoteAddress() string {
+	c.RLock()
+	defer c.RUnlock()
+	return c.getRemoteAddress()
+}
+
+func (c *Cluster) getRemoteAddress() string {
+	if c.node == nil {
+		return ""
+	}
+	nodeID := c.node.NodeID()
+	for _, r := range c.node.Remotes() {
+		if r.NodeID != nodeID {
+			return r.Addr
+		}
+	}
+	return ""
+}
+
+// ListenClusterEvents returns a channel that receives messages on cluster
+// participation changes.
+// todo: make cancelable and accessible to multiple callers
+func (c *Cluster) ListenClusterEvents() <-chan struct{} {
+	return c.configEvent
+}
+
+// Info returns information about the current cluster state.
+func (c *Cluster) Info() types.Info {
+	var info types.Info
+	c.RLock()
+	defer c.RUnlock()
+
+	if c.node == nil {
+		info.LocalNodeState = types.LocalNodeStateInactive
+		if c.cancelDelay != nil {
+			info.LocalNodeState = types.LocalNodeStateError
+		}
+	} else {
+		info.LocalNodeState = types.LocalNodeStatePending
+		if c.ready == true {
+			info.LocalNodeState = types.LocalNodeStateActive
+		}
+	}
+	if c.err != nil {
+		info.Error = c.err.Error()
+	}
+
+	if c.isActiveManager() {
+		info.ControlAvailable = true
+		if r, err := c.client.ListNodes(c.getRequestContext(), &swarmapi.ListNodesRequest{}); err == nil {
+			info.Nodes = len(r.Nodes)
+			for _, n := range r.Nodes {
+				if n.ManagerStatus != nil {
+					info.Managers = info.Managers + 1
+				}
+			}
+		}
+
+		if swarm, err := getSwarm(c.getRequestContext(), c.client); err == nil && swarm != nil {
+			info.CACertHash = swarm.RootCA.CACertHash
+		}
+	}
+
+	if c.node != nil {
+		for _, r := range c.node.Remotes() {
+			info.RemoteManagers = append(info.RemoteManagers, types.Peer{NodeID: r.NodeID, Addr: r.Addr})
+		}
+		info.NodeID = c.node.NodeID()
+	}
+
+	return info
+}
+
+// isActiveManager should not be called without a read lock
+func (c *Cluster) isActiveManager() bool {
+	return c.conn != nil
+}
+
+// GetServices returns all services of a managed swarm cluster.
+func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Service, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return nil, ErrNoManager
+	}
+
+	filters, err := newListServicesFilters(options.Filter)
+	if err != nil {
+		return nil, err
+	}
+	r, err := c.client.ListServices(
+		c.getRequestContext(),
+		&swarmapi.ListServicesRequest{Filters: filters})
+	if err != nil {
+		return nil, err
+	}
+
+	var services []types.Service
+
+	for _, service := range r.Services {
+		services = append(services, convert.ServiceFromGRPC(*service))
+	}
+
+	return services, nil
+}
+
+// CreateService creates a new service in a managed swarm cluster.
+func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return "", ErrNoManager
+	}
+
+	ctx := c.getRequestContext()
+
+	err := populateNetworkID(ctx, c.client, &s)
+	if err != nil {
+		return "", err
+	}
+
+	serviceSpec, err := convert.ServiceSpecToGRPC(s)
+	if err != nil {
+		return "", err
+	}
+	r, err := c.client.CreateService(ctx, &swarmapi.CreateServiceRequest{Spec: &serviceSpec})
+	if err != nil {
+		return "", err
+	}
+
+	return r.Service.ID, nil
+}
+
+// GetService returns a service based on a ID or name.
+func (c *Cluster) GetService(input string) (types.Service, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return types.Service{}, ErrNoManager
+	}
+
+	service, err := getService(c.getRequestContext(), c.client, input)
+	if err != nil {
+		return types.Service{}, err
+	}
+	return convert.ServiceFromGRPC(*service), nil
+}
+
+// UpdateService updates existing service to match new properties.
+func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.ServiceSpec) error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return ErrNoManager
+	}
+
+	serviceSpec, err := convert.ServiceSpecToGRPC(spec)
+	if err != nil {
+		return err
+	}
+
+	_, err = c.client.UpdateService(
+		c.getRequestContext(),
+		&swarmapi.UpdateServiceRequest{
+			ServiceID: serviceID,
+			Spec:      &serviceSpec,
+			ServiceVersion: &swarmapi.Version{
+				Index: version,
+			},
+		},
+	)
+	return err
+}
+
+// RemoveService removes a service from a managed swarm cluster.
+func (c *Cluster) RemoveService(input string) error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return ErrNoManager
+	}
+
+	service, err := getService(c.getRequestContext(), c.client, input)
+	if err != nil {
+		return err
+	}
+
+	if _, err := c.client.RemoveService(c.getRequestContext(), &swarmapi.RemoveServiceRequest{ServiceID: service.ID}); err != nil {
+		return err
+	}
+	return nil
+}
+
+// GetNodes returns a list of all nodes known to a cluster.
+func (c *Cluster) GetNodes(options apitypes.NodeListOptions) ([]types.Node, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return nil, ErrNoManager
+	}
+
+	filters, err := newListNodesFilters(options.Filter)
+	if err != nil {
+		return nil, err
+	}
+	r, err := c.client.ListNodes(
+		c.getRequestContext(),
+		&swarmapi.ListNodesRequest{Filters: filters})
+	if err != nil {
+		return nil, err
+	}
+
+	nodes := []types.Node{}
+
+	for _, node := range r.Nodes {
+		nodes = append(nodes, convert.NodeFromGRPC(*node))
+	}
+	return nodes, nil
+}
+
+// GetNode returns a node based on a ID or name.
+func (c *Cluster) GetNode(input string) (types.Node, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return types.Node{}, ErrNoManager
+	}
+
+	node, err := getNode(c.getRequestContext(), c.client, input)
+	if err != nil {
+		return types.Node{}, err
+	}
+	return convert.NodeFromGRPC(*node), nil
+}
+
+// UpdateNode updates existing nodes properties.
+func (c *Cluster) UpdateNode(nodeID string, version uint64, spec types.NodeSpec) error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return ErrNoManager
+	}
+
+	nodeSpec, err := convert.NodeSpecToGRPC(spec)
+	if err != nil {
+		return err
+	}
+
+	_, err = c.client.UpdateNode(
+		c.getRequestContext(),
+		&swarmapi.UpdateNodeRequest{
+			NodeID: nodeID,
+			Spec:   &nodeSpec,
+			NodeVersion: &swarmapi.Version{
+				Index: version,
+			},
+		},
+	)
+	return err
+}
+
+// RemoveNode removes a node from a cluster
+func (c *Cluster) RemoveNode(input string) error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return ErrNoManager
+	}
+
+	ctx := c.getRequestContext()
+
+	node, err := getNode(ctx, c.client, input)
+	if err != nil {
+		return err
+	}
+
+	if _, err := c.client.RemoveNode(ctx, &swarmapi.RemoveNodeRequest{NodeID: node.ID}); err != nil {
+		return err
+	}
+	return nil
+}
+
+// GetTasks returns a list of tasks matching the filter options.
+func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return nil, ErrNoManager
+	}
+
+	filters, err := newListTasksFilters(options.Filter)
+	if err != nil {
+		return nil, err
+	}
+	r, err := c.client.ListTasks(
+		c.getRequestContext(),
+		&swarmapi.ListTasksRequest{Filters: filters})
+	if err != nil {
+		return nil, err
+	}
+
+	tasks := []types.Task{}
+
+	for _, task := range r.Tasks {
+		tasks = append(tasks, convert.TaskFromGRPC(*task))
+	}
+	return tasks, nil
+}
+
+// GetTask returns a task by an ID.
+func (c *Cluster) GetTask(input string) (types.Task, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return types.Task{}, ErrNoManager
+	}
+
+	task, err := getTask(c.getRequestContext(), c.client, input)
+	if err != nil {
+		return types.Task{}, err
+	}
+	return convert.TaskFromGRPC(*task), nil
+}
+
+// GetNetwork returns a cluster network by ID.
+func (c *Cluster) GetNetwork(input string) (apitypes.NetworkResource, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return apitypes.NetworkResource{}, ErrNoManager
+	}
+
+	network, err := getNetwork(c.getRequestContext(), c.client, input)
+	if err != nil {
+		return apitypes.NetworkResource{}, err
+	}
+	return convert.BasicNetworkFromGRPC(*network), nil
+}
+
+// GetNetworks returns all current cluster managed networks.
+func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return nil, ErrNoManager
+	}
+
+	r, err := c.client.ListNetworks(c.getRequestContext(), &swarmapi.ListNetworksRequest{})
+	if err != nil {
+		return nil, err
+	}
+
+	var networks []apitypes.NetworkResource
+
+	for _, network := range r.Networks {
+		networks = append(networks, convert.BasicNetworkFromGRPC(*network))
+	}
+
+	return networks, nil
+}
+
+// CreateNetwork creates a new cluster managed network.
+func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return "", ErrNoManager
+	}
+
+	if runconfig.IsPreDefinedNetwork(s.Name) {
+		err := fmt.Errorf("%s is a pre-defined network and cannot be created", s.Name)
+		return "", errors.NewRequestForbiddenError(err)
+	}
+
+	networkSpec := convert.BasicNetworkCreateToGRPC(s)
+	r, err := c.client.CreateNetwork(c.getRequestContext(), &swarmapi.CreateNetworkRequest{Spec: &networkSpec})
+	if err != nil {
+		return "", err
+	}
+
+	return r.Network.ID, nil
+}
+
+// RemoveNetwork removes a cluster network.
+func (c *Cluster) RemoveNetwork(input string) error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if !c.isActiveManager() {
+		return ErrNoManager
+	}
+
+	network, err := getNetwork(c.getRequestContext(), c.client, input)
+	if err != nil {
+		return err
+	}
+
+	if _, err := c.client.RemoveNetwork(c.getRequestContext(), &swarmapi.RemoveNetworkRequest{NetworkID: network.ID}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func populateNetworkID(ctx context.Context, c swarmapi.ControlClient, s *types.ServiceSpec) error {
+	for i, n := range s.Networks {
+		apiNetwork, err := getNetwork(ctx, c, n.Target)
+		if err != nil {
+			return err
+		}
+		s.Networks[i] = types.NetworkAttachmentConfig{Target: apiNetwork.ID}
+	}
+	return nil
+}
+
+func getNetwork(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Network, error) {
+	// GetNetwork to match via full ID.
+	rg, err := c.GetNetwork(ctx, &swarmapi.GetNetworkRequest{NetworkID: input})
+	if err != nil {
+		// If any error (including NotFound), ListNetworks to match via ID prefix and full name.
+		rl, err := c.ListNetworks(ctx, &swarmapi.ListNetworksRequest{Filters: &swarmapi.ListNetworksRequest_Filters{Names: []string{input}}})
+		if err != nil || len(rl.Networks) == 0 {
+			rl, err = c.ListNetworks(ctx, &swarmapi.ListNetworksRequest{Filters: &swarmapi.ListNetworksRequest_Filters{IDPrefixes: []string{input}}})
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		if len(rl.Networks) == 0 {
+			return nil, fmt.Errorf("network %s not found", input)
+		}
+
+		if l := len(rl.Networks); l > 1 {
+			return nil, fmt.Errorf("network %s is ambigious (%d matches found)", input, l)
+		}
+
+		return rl.Networks[0], nil
+	}
+	return rg.Network, nil
+}
+
+// Cleanup stops active swarm node. This is run before daemon shutdown.
+func (c *Cluster) Cleanup() {
+	c.Lock()
+	node := c.node
+	if node == nil {
+		c.Unlock()
+		return
+	}
+
+	if c.isActiveManager() {
+		active, reachable, unreachable, err := c.managerStats()
+		if err == nil {
+			singlenode := active && reachable == 1 && unreachable == 0
+			if active && !singlenode && reachable-2 <= unreachable {
+				logrus.Errorf("Leaving cluster with %v managers left out of %v. Raft quorum will be lost.", reachable-1, reachable+unreachable)
+			}
+		}
+	}
+	c.cancelReconnect()
+	c.Unlock()
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	if err := node.Stop(ctx); err != nil {
+		logrus.Errorf("error cleaning up cluster: %v", err)
+	}
+	c.Lock()
+	c.node = nil
+	c.ready = false
+	c.conn = nil
+	c.Unlock()
+}
+
+func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, err error) {
+	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
+	nodes, err := c.client.ListNodes(ctx, &swarmapi.ListNodesRequest{})
+	if err != nil {
+		return false, 0, 0, err
+	}
+	for _, n := range nodes.Nodes {
+		if n.ManagerStatus != nil {
+			if n.ManagerStatus.Raft.Status.Reachability == swarmapi.RaftMemberStatus_REACHABLE {
+				reachable++
+				if n.ID == c.node.NodeID() {
+					current = true
+				}
+			}
+			if n.ManagerStatus.Raft.Status.Reachability == swarmapi.RaftMemberStatus_UNREACHABLE {
+				unreachable++
+			}
+		}
+	}
+	return
+}
+
+func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.AcceptancePolicy) error {
+	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
+	for conn := range node.ListenControlSocket(ctx) {
+		if ctx.Err() != nil {
+			return ctx.Err()
+		}
+		if conn != nil {
+			client := swarmapi.NewControlClient(conn)
+			var cluster *swarmapi.Cluster
+			for i := 0; ; i++ {
+				lcr, err := client.ListClusters(ctx, &swarmapi.ListClustersRequest{})
+				if err != nil {
+					return fmt.Errorf("error on listing clusters: %v", err)
+				}
+				if len(lcr.Clusters) == 0 {
+					if i < 10 {
+						time.Sleep(200 * time.Millisecond)
+						continue
+					}
+					return fmt.Errorf("empty list of clusters was returned")
+				}
+				cluster = lcr.Clusters[0]
+				break
+			}
+			spec := &cluster.Spec
+
+			if err := convert.SwarmSpecUpdateAcceptancePolicy(spec, acceptancePolicy); err != nil {
+				return fmt.Errorf("error updating cluster settings: %v", err)
+			}
+			_, err := client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{
+				ClusterID:      cluster.ID,
+				ClusterVersion: &cluster.Meta.Version,
+				Spec:           spec,
+			})
+			if err != nil {
+				return fmt.Errorf("error updating cluster settings: %v", err)
+			}
+			return nil
+		}
+	}
+	return ctx.Err()
+}

+ 116 - 0
daemon/cluster/convert/container.go

@@ -0,0 +1,116 @@
+package convert
+
+import (
+	"fmt"
+	"strings"
+
+	types "github.com/docker/engine-api/types/swarm"
+	swarmapi "github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/protobuf/ptypes"
+)
+
+func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
+	containerSpec := types.ContainerSpec{
+		Image:   c.Image,
+		Labels:  c.Labels,
+		Command: c.Command,
+		Args:    c.Args,
+		Env:     c.Env,
+		Dir:     c.Dir,
+		User:    c.User,
+	}
+
+	// Mounts
+	for _, m := range c.Mounts {
+		mount := types.Mount{
+			Target:   m.Target,
+			Source:   m.Source,
+			Type:     types.MountType(strings.ToLower(swarmapi.Mount_MountType_name[int32(m.Type)])),
+			Writable: m.Writable,
+		}
+
+		if m.BindOptions != nil {
+			mount.BindOptions = &types.BindOptions{
+				Propagation: types.MountPropagation(strings.ToLower(swarmapi.Mount_BindOptions_MountPropagation_name[int32(m.BindOptions.Propagation)])),
+			}
+		}
+
+		if m.VolumeOptions != nil {
+			mount.VolumeOptions = &types.VolumeOptions{
+				Populate: m.VolumeOptions.Populate,
+				Labels:   m.VolumeOptions.Labels,
+			}
+			if m.VolumeOptions.DriverConfig != nil {
+				mount.VolumeOptions.DriverConfig = &types.Driver{
+					Name:    m.VolumeOptions.DriverConfig.Name,
+					Options: m.VolumeOptions.DriverConfig.Options,
+				}
+			}
+		}
+		containerSpec.Mounts = append(containerSpec.Mounts, mount)
+	}
+
+	if c.StopGracePeriod != nil {
+		grace, _ := ptypes.Duration(c.StopGracePeriod)
+		containerSpec.StopGracePeriod = &grace
+	}
+	return containerSpec
+}
+
+func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
+	containerSpec := &swarmapi.ContainerSpec{
+		Image:   c.Image,
+		Labels:  c.Labels,
+		Command: c.Command,
+		Args:    c.Args,
+		Env:     c.Env,
+		Dir:     c.Dir,
+		User:    c.User,
+	}
+
+	if c.StopGracePeriod != nil {
+		containerSpec.StopGracePeriod = ptypes.DurationProto(*c.StopGracePeriod)
+	}
+
+	// Mounts
+	for _, m := range c.Mounts {
+		mount := swarmapi.Mount{
+			Target:   m.Target,
+			Source:   m.Source,
+			Writable: m.Writable,
+		}
+
+		if mountType, ok := swarmapi.Mount_MountType_value[strings.ToUpper(string(m.Type))]; ok {
+			mount.Type = swarmapi.Mount_MountType(mountType)
+		} else if string(m.Type) != "" {
+			return nil, fmt.Errorf("invalid MountType: %q", m.Type)
+		}
+
+		if m.BindOptions != nil {
+			if mountPropagation, ok := swarmapi.Mount_BindOptions_MountPropagation_value[strings.ToUpper(string(m.BindOptions.Propagation))]; ok {
+				mount.BindOptions = &swarmapi.Mount_BindOptions{Propagation: swarmapi.Mount_BindOptions_MountPropagation(mountPropagation)}
+			} else if string(m.BindOptions.Propagation) != "" {
+				return nil, fmt.Errorf("invalid MountPropagation: %q", m.BindOptions.Propagation)
+
+			}
+
+		}
+
+		if m.VolumeOptions != nil {
+			mount.VolumeOptions = &swarmapi.Mount_VolumeOptions{
+				Populate: m.VolumeOptions.Populate,
+				Labels:   m.VolumeOptions.Labels,
+			}
+			if m.VolumeOptions.DriverConfig != nil {
+				mount.VolumeOptions.DriverConfig = &swarmapi.Driver{
+					Name:    m.VolumeOptions.DriverConfig.Name,
+					Options: m.VolumeOptions.DriverConfig.Options,
+				}
+			}
+		}
+
+		containerSpec.Mounts = append(containerSpec.Mounts, mount)
+	}
+
+	return containerSpec, nil
+}

+ 194 - 0
daemon/cluster/convert/network.go

@@ -0,0 +1,194 @@
+package convert
+
+import (
+	"strings"
+
+	basictypes "github.com/docker/engine-api/types"
+	networktypes "github.com/docker/engine-api/types/network"
+	types "github.com/docker/engine-api/types/swarm"
+	swarmapi "github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/protobuf/ptypes"
+)
+
+func networkAttachementFromGRPC(na *swarmapi.NetworkAttachment) types.NetworkAttachment {
+	if na != nil {
+		return types.NetworkAttachment{
+			Network:   networkFromGRPC(na.Network),
+			Addresses: na.Addresses,
+		}
+	}
+	return types.NetworkAttachment{}
+}
+
+func networkFromGRPC(n *swarmapi.Network) types.Network {
+	if n != nil {
+		network := types.Network{
+			ID: n.ID,
+			Spec: types.NetworkSpec{
+				IPv6Enabled: n.Spec.Ipv6Enabled,
+				Internal:    n.Spec.Internal,
+				IPAMOptions: ipamFromGRPC(n.Spec.IPAM),
+			},
+			IPAMOptions: ipamFromGRPC(n.IPAM),
+		}
+
+		// Meta
+		network.Version.Index = n.Meta.Version.Index
+		network.CreatedAt, _ = ptypes.Timestamp(n.Meta.CreatedAt)
+		network.UpdatedAt, _ = ptypes.Timestamp(n.Meta.UpdatedAt)
+
+		//Annotations
+		network.Spec.Name = n.Spec.Annotations.Name
+		network.Spec.Labels = n.Spec.Annotations.Labels
+
+		//DriverConfiguration
+		if n.Spec.DriverConfig != nil {
+			network.Spec.DriverConfiguration = &types.Driver{
+				Name:    n.Spec.DriverConfig.Name,
+				Options: n.Spec.DriverConfig.Options,
+			}
+		}
+
+		//DriverState
+		if n.DriverState != nil {
+			network.DriverState = types.Driver{
+				Name:    n.DriverState.Name,
+				Options: n.DriverState.Options,
+			}
+		}
+
+		return network
+	}
+	return types.Network{}
+}
+
+func ipamFromGRPC(i *swarmapi.IPAMOptions) *types.IPAMOptions {
+	var ipam *types.IPAMOptions
+	if i != nil {
+		ipam = &types.IPAMOptions{}
+		if i.Driver != nil {
+			ipam.Driver.Name = i.Driver.Name
+			ipam.Driver.Options = i.Driver.Options
+		}
+
+		for _, config := range i.Configs {
+			ipam.Configs = append(ipam.Configs, types.IPAMConfig{
+				Subnet:  config.Subnet,
+				Range:   config.Range,
+				Gateway: config.Gateway,
+			})
+		}
+	}
+	return ipam
+}
+
+func endpointSpecFromGRPC(es *swarmapi.EndpointSpec) *types.EndpointSpec {
+	var endpointSpec *types.EndpointSpec
+	if es != nil {
+		endpointSpec = &types.EndpointSpec{}
+		endpointSpec.Mode = types.ResolutionMode(strings.ToLower(es.Mode.String()))
+
+		for _, portState := range es.Ports {
+			endpointSpec.Ports = append(endpointSpec.Ports, types.PortConfig{
+				Name:          portState.Name,
+				Protocol:      types.PortConfigProtocol(strings.ToLower(swarmapi.PortConfig_Protocol_name[int32(portState.Protocol)])),
+				TargetPort:    portState.TargetPort,
+				PublishedPort: portState.PublishedPort,
+			})
+		}
+	}
+	return endpointSpec
+}
+
+func endpointFromGRPC(e *swarmapi.Endpoint) types.Endpoint {
+	endpoint := types.Endpoint{}
+	if e != nil {
+		if espec := endpointSpecFromGRPC(e.Spec); espec != nil {
+			endpoint.Spec = *espec
+		}
+
+		for _, portState := range e.Ports {
+			endpoint.Ports = append(endpoint.Ports, types.PortConfig{
+				Name:          portState.Name,
+				Protocol:      types.PortConfigProtocol(strings.ToLower(swarmapi.PortConfig_Protocol_name[int32(portState.Protocol)])),
+				TargetPort:    portState.TargetPort,
+				PublishedPort: portState.PublishedPort,
+			})
+		}
+
+		for _, v := range e.VirtualIPs {
+			endpoint.VirtualIPs = append(endpoint.VirtualIPs, types.EndpointVirtualIP{
+				NetworkID: v.NetworkID,
+				Addr:      v.Addr})
+		}
+
+	}
+
+	return endpoint
+}
+
+// BasicNetworkFromGRPC converts a grpc Network to a NetworkResource.
+func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
+	spec := n.Spec
+	var ipam networktypes.IPAM
+	if spec.IPAM != nil {
+		if spec.IPAM.Driver != nil {
+			ipam.Driver = spec.IPAM.Driver.Name
+			ipam.Options = spec.IPAM.Driver.Options
+		}
+		ipam.Config = make([]networktypes.IPAMConfig, 0, len(spec.IPAM.Configs))
+		for _, ic := range spec.IPAM.Configs {
+			ipamConfig := networktypes.IPAMConfig{
+				Subnet:     ic.Subnet,
+				IPRange:    ic.Range,
+				Gateway:    ic.Gateway,
+				AuxAddress: ic.Reserved,
+			}
+			ipam.Config = append(ipam.Config, ipamConfig)
+		}
+	}
+
+	return basictypes.NetworkResource{
+		ID:         n.ID,
+		Name:       n.Spec.Annotations.Name,
+		Scope:      "swarm",
+		Driver:     n.DriverState.Name,
+		EnableIPv6: spec.Ipv6Enabled,
+		IPAM:       ipam,
+		Internal:   spec.Internal,
+		Options:    n.DriverState.Options,
+		Labels:     n.Spec.Annotations.Labels,
+	}
+}
+
+// BasicNetworkCreateToGRPC converts a NetworkCreateRequest to a grpc NetworkSpec.
+func BasicNetworkCreateToGRPC(create basictypes.NetworkCreateRequest) swarmapi.NetworkSpec {
+	ns := swarmapi.NetworkSpec{
+		Annotations: swarmapi.Annotations{
+			Name:   create.Name,
+			Labels: create.Labels,
+		},
+		DriverConfig: &swarmapi.Driver{
+			Name:    create.Driver,
+			Options: create.Options,
+		},
+		Ipv6Enabled: create.EnableIPv6,
+		Internal:    create.Internal,
+		IPAM: &swarmapi.IPAMOptions{
+			Driver: &swarmapi.Driver{
+				Name:    create.IPAM.Driver,
+				Options: create.IPAM.Options,
+			},
+		},
+	}
+	ipamSpec := make([]*swarmapi.IPAMConfig, 0, len(create.IPAM.Config))
+	for _, ipamConfig := range create.IPAM.Config {
+		ipamSpec = append(ipamSpec, &swarmapi.IPAMConfig{
+			Subnet:  ipamConfig.Subnet,
+			Range:   ipamConfig.IPRange,
+			Gateway: ipamConfig.Gateway,
+		})
+	}
+	ns.IPAM.Configs = ipamSpec
+	return ns
+}

+ 95 - 0
daemon/cluster/convert/node.go

@@ -0,0 +1,95 @@
+package convert
+
+import (
+	"fmt"
+	"strings"
+
+	types "github.com/docker/engine-api/types/swarm"
+	swarmapi "github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/protobuf/ptypes"
+)
+
+// NodeFromGRPC converts a grpc Node to a Node.
+func NodeFromGRPC(n swarmapi.Node) types.Node {
+	node := types.Node{
+		ID: n.ID,
+		Spec: types.NodeSpec{
+			Role:         types.NodeRole(strings.ToLower(n.Spec.Role.String())),
+			Membership:   types.NodeMembership(strings.ToLower(n.Spec.Membership.String())),
+			Availability: types.NodeAvailability(strings.ToLower(n.Spec.Availability.String())),
+		},
+		Status: types.NodeStatus{
+			State:   types.NodeState(strings.ToLower(n.Status.State.String())),
+			Message: n.Status.Message,
+		},
+	}
+
+	// Meta
+	node.Version.Index = n.Meta.Version.Index
+	node.CreatedAt, _ = ptypes.Timestamp(n.Meta.CreatedAt)
+	node.UpdatedAt, _ = ptypes.Timestamp(n.Meta.UpdatedAt)
+
+	//Annotations
+	node.Spec.Name = n.Spec.Annotations.Name
+	node.Spec.Labels = n.Spec.Annotations.Labels
+
+	//Description
+	if n.Description != nil {
+		node.Description.Hostname = n.Description.Hostname
+		if n.Description.Platform != nil {
+			node.Description.Platform.Architecture = n.Description.Platform.Architecture
+			node.Description.Platform.OS = n.Description.Platform.OS
+		}
+		if n.Description.Resources != nil {
+			node.Description.Resources.NanoCPUs = n.Description.Resources.NanoCPUs
+			node.Description.Resources.MemoryBytes = n.Description.Resources.MemoryBytes
+		}
+		if n.Description.Engine != nil {
+			node.Description.Engine.EngineVersion = n.Description.Engine.EngineVersion
+			node.Description.Engine.Labels = n.Description.Engine.Labels
+			for _, plugin := range n.Description.Engine.Plugins {
+				node.Description.Engine.Plugins = append(node.Description.Engine.Plugins, types.PluginDescription{Type: plugin.Type, Name: plugin.Name})
+			}
+		}
+	}
+
+	//Manager
+	if n.ManagerStatus != nil {
+		node.ManagerStatus = &types.ManagerStatus{
+			Leader:       n.ManagerStatus.Raft.Status.Leader,
+			Reachability: types.Reachability(strings.ToLower(n.ManagerStatus.Raft.Status.Reachability.String())),
+			Addr:         n.ManagerStatus.Raft.Addr,
+		}
+	}
+
+	return node
+}
+
+// NodeSpecToGRPC converts a NodeSpec to a grpc NodeSpec.
+func NodeSpecToGRPC(s types.NodeSpec) (swarmapi.NodeSpec, error) {
+	spec := swarmapi.NodeSpec{
+		Annotations: swarmapi.Annotations{
+			Name:   s.Name,
+			Labels: s.Labels,
+		},
+	}
+	if role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(s.Role))]; ok {
+		spec.Role = swarmapi.NodeRole(role)
+	} else {
+		return swarmapi.NodeSpec{}, fmt.Errorf("invalid Role: %q", s.Role)
+	}
+
+	if membership, ok := swarmapi.NodeSpec_Membership_value[strings.ToUpper(string(s.Membership))]; ok {
+		spec.Membership = swarmapi.NodeSpec_Membership(membership)
+	} else {
+		return swarmapi.NodeSpec{}, fmt.Errorf("invalid Membership: %q", s.Membership)
+	}
+
+	if availability, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(s.Availability))]; ok {
+		spec.Availability = swarmapi.NodeSpec_Availability(availability)
+	} else {
+		return swarmapi.NodeSpec{}, fmt.Errorf("invalid Availability: %q", s.Availability)
+	}
+
+	return spec, nil
+}

+ 252 - 0
daemon/cluster/convert/service.go

@@ -0,0 +1,252 @@
+package convert
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/docker/docker/pkg/namesgenerator"
+	types "github.com/docker/engine-api/types/swarm"
+	swarmapi "github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/protobuf/ptypes"
+)
+
+// ServiceFromGRPC converts a grpc Service to a Service.
+func ServiceFromGRPC(s swarmapi.Service) types.Service {
+	spec := s.Spec
+	containerConfig := spec.Task.Runtime.(*swarmapi.TaskSpec_Container).Container
+
+	networks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
+	for _, n := range spec.Networks {
+		networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
+	}
+	service := types.Service{
+		ID: s.ID,
+
+		Spec: types.ServiceSpec{
+			TaskTemplate: types.TaskSpec{
+				ContainerSpec: containerSpecFromGRPC(containerConfig),
+				Resources:     resourcesFromGRPC(s.Spec.Task.Resources),
+				RestartPolicy: restartPolicyFromGRPC(s.Spec.Task.Restart),
+				Placement:     placementFromGRPC(s.Spec.Task.Placement),
+			},
+
+			Networks:     networks,
+			EndpointSpec: endpointSpecFromGRPC(s.Spec.Endpoint),
+		},
+		Endpoint: endpointFromGRPC(s.Endpoint),
+	}
+
+	// Meta
+	service.Version.Index = s.Meta.Version.Index
+	service.CreatedAt, _ = ptypes.Timestamp(s.Meta.CreatedAt)
+	service.UpdatedAt, _ = ptypes.Timestamp(s.Meta.UpdatedAt)
+
+	// Annotations
+	service.Spec.Name = s.Spec.Annotations.Name
+	service.Spec.Labels = s.Spec.Annotations.Labels
+
+	// UpdateConfig
+	if s.Spec.Update != nil {
+		service.Spec.UpdateConfig = &types.UpdateConfig{
+			Parallelism: s.Spec.Update.Parallelism,
+		}
+
+		service.Spec.UpdateConfig.Delay, _ = ptypes.Duration(&s.Spec.Update.Delay)
+	}
+
+	//Mode
+	switch t := s.Spec.GetMode().(type) {
+	case *swarmapi.ServiceSpec_Global:
+		service.Spec.Mode.Global = &types.GlobalService{}
+	case *swarmapi.ServiceSpec_Replicated:
+		service.Spec.Mode.Replicated = &types.ReplicatedService{
+			Replicas: &t.Replicated.Replicas,
+		}
+	}
+
+	return service
+}
+
+// ServiceSpecToGRPC converts a ServiceSpec to a grpc ServiceSpec.
+func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
+	name := s.Name
+	if name == "" {
+		name = namesgenerator.GetRandomName(0)
+	}
+
+	networks := make([]*swarmapi.ServiceSpec_NetworkAttachmentConfig, 0, len(s.Networks))
+	for _, n := range s.Networks {
+		networks = append(networks, &swarmapi.ServiceSpec_NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
+	}
+
+	spec := swarmapi.ServiceSpec{
+		Annotations: swarmapi.Annotations{
+			Name:   name,
+			Labels: s.Labels,
+		},
+		Task: swarmapi.TaskSpec{
+			Resources: resourcesToGRPC(s.TaskTemplate.Resources),
+		},
+		Networks: networks,
+	}
+
+	containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)
+	if err != nil {
+		return swarmapi.ServiceSpec{}, err
+	}
+	spec.Task.Runtime = &swarmapi.TaskSpec_Container{Container: containerSpec}
+
+	restartPolicy, err := restartPolicyToGRPC(s.TaskTemplate.RestartPolicy)
+	if err != nil {
+		return swarmapi.ServiceSpec{}, err
+	}
+	spec.Task.Restart = restartPolicy
+
+	if s.TaskTemplate.Placement != nil {
+		spec.Task.Placement = &swarmapi.Placement{
+			Constraints: s.TaskTemplate.Placement.Constraints,
+		}
+	}
+
+	if s.UpdateConfig != nil {
+		spec.Update = &swarmapi.UpdateConfig{
+			Parallelism: s.UpdateConfig.Parallelism,
+			Delay:       *ptypes.DurationProto(s.UpdateConfig.Delay),
+		}
+	}
+
+	if s.EndpointSpec != nil {
+		if s.EndpointSpec.Mode != "" &&
+			s.EndpointSpec.Mode != types.ResolutionModeVIP &&
+			s.EndpointSpec.Mode != types.ResolutionModeDNSRR {
+			return swarmapi.ServiceSpec{}, fmt.Errorf("invalid resolution mode: %q", s.EndpointSpec.Mode)
+		}
+
+		spec.Endpoint = &swarmapi.EndpointSpec{}
+
+		spec.Endpoint.Mode = swarmapi.EndpointSpec_ResolutionMode(swarmapi.EndpointSpec_ResolutionMode_value[strings.ToUpper(string(s.EndpointSpec.Mode))])
+
+		for _, portConfig := range s.EndpointSpec.Ports {
+			spec.Endpoint.Ports = append(spec.Endpoint.Ports, &swarmapi.PortConfig{
+				Name:          portConfig.Name,
+				Protocol:      swarmapi.PortConfig_Protocol(swarmapi.PortConfig_Protocol_value[strings.ToUpper(string(portConfig.Protocol))]),
+				TargetPort:    portConfig.TargetPort,
+				PublishedPort: portConfig.PublishedPort,
+			})
+		}
+	}
+
+	//Mode
+	if s.Mode.Global != nil {
+		spec.Mode = &swarmapi.ServiceSpec_Global{
+			Global: &swarmapi.GlobalService{},
+		}
+	} else if s.Mode.Replicated != nil && s.Mode.Replicated.Replicas != nil {
+		spec.Mode = &swarmapi.ServiceSpec_Replicated{
+			Replicated: &swarmapi.ReplicatedService{Replicas: *s.Mode.Replicated.Replicas},
+		}
+	} else {
+		spec.Mode = &swarmapi.ServiceSpec_Replicated{
+			Replicated: &swarmapi.ReplicatedService{Replicas: 1},
+		}
+	}
+
+	return spec, nil
+}
+
+func resourcesFromGRPC(res *swarmapi.ResourceRequirements) *types.ResourceRequirements {
+	var resources *types.ResourceRequirements
+	if res != nil {
+		resources = &types.ResourceRequirements{}
+		if res.Limits != nil {
+			resources.Limits = &types.Resources{
+				NanoCPUs:    res.Limits.NanoCPUs,
+				MemoryBytes: res.Limits.MemoryBytes,
+			}
+		}
+		if res.Reservations != nil {
+			resources.Reservations = &types.Resources{
+				NanoCPUs:    res.Reservations.NanoCPUs,
+				MemoryBytes: res.Reservations.MemoryBytes,
+			}
+		}
+	}
+
+	return resources
+}
+
+func resourcesToGRPC(res *types.ResourceRequirements) *swarmapi.ResourceRequirements {
+	var reqs *swarmapi.ResourceRequirements
+	if res != nil {
+		reqs = &swarmapi.ResourceRequirements{}
+		if res.Limits != nil {
+			reqs.Limits = &swarmapi.Resources{
+				NanoCPUs:    res.Limits.NanoCPUs,
+				MemoryBytes: res.Limits.MemoryBytes,
+			}
+		}
+		if res.Reservations != nil {
+			reqs.Reservations = &swarmapi.Resources{
+				NanoCPUs:    res.Reservations.NanoCPUs,
+				MemoryBytes: res.Reservations.MemoryBytes,
+			}
+
+		}
+	}
+	return reqs
+}
+
+func restartPolicyFromGRPC(p *swarmapi.RestartPolicy) *types.RestartPolicy {
+	var rp *types.RestartPolicy
+	if p != nil {
+		rp = &types.RestartPolicy{}
+		rp.Condition = types.RestartPolicyCondition(strings.ToLower(p.Condition.String()))
+		if p.Delay != nil {
+			delay, _ := ptypes.Duration(p.Delay)
+			rp.Delay = &delay
+		}
+		if p.Window != nil {
+			window, _ := ptypes.Duration(p.Window)
+			rp.Window = &window
+		}
+
+		rp.MaxAttempts = &p.MaxAttempts
+	}
+	return rp
+}
+
+func restartPolicyToGRPC(p *types.RestartPolicy) (*swarmapi.RestartPolicy, error) {
+	var rp *swarmapi.RestartPolicy
+	if p != nil {
+		rp = &swarmapi.RestartPolicy{}
+		if condition, ok := swarmapi.RestartPolicy_RestartCondition_value[strings.ToUpper(string(p.Condition))]; ok {
+			rp.Condition = swarmapi.RestartPolicy_RestartCondition(condition)
+		} else if string(p.Condition) == "" {
+			rp.Condition = swarmapi.RestartOnAny
+		} else {
+			return nil, fmt.Errorf("invalid RestartCondition: %q", p.Condition)
+		}
+
+		if p.Delay != nil {
+			rp.Delay = ptypes.DurationProto(*p.Delay)
+		}
+		if p.Window != nil {
+			rp.Window = ptypes.DurationProto(*p.Window)
+		}
+		if p.MaxAttempts != nil {
+			rp.MaxAttempts = *p.MaxAttempts
+
+		}
+	}
+	return rp, nil
+}
+
+func placementFromGRPC(p *swarmapi.Placement) *types.Placement {
+	var r *types.Placement
+	if p != nil {
+		r = &types.Placement{}
+		r.Constraints = p.Constraints
+	}
+
+	return r
+}

+ 116 - 0
daemon/cluster/convert/swarm.go

@@ -0,0 +1,116 @@
+package convert
+
+import (
+	"fmt"
+	"strings"
+
+	"golang.org/x/crypto/bcrypt"
+
+	types "github.com/docker/engine-api/types/swarm"
+	swarmapi "github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/protobuf/ptypes"
+)
+
+// SwarmFromGRPC converts a grpc Cluster to a Swarm.
+func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
+	swarm := types.Swarm{
+		ID: c.ID,
+		Spec: types.Spec{
+			Orchestration: types.OrchestrationConfig{
+				TaskHistoryRetentionLimit: c.Spec.Orchestration.TaskHistoryRetentionLimit,
+			},
+			Raft: types.RaftConfig{
+				SnapshotInterval:           c.Spec.Raft.SnapshotInterval,
+				KeepOldSnapshots:           c.Spec.Raft.KeepOldSnapshots,
+				LogEntriesForSlowFollowers: c.Spec.Raft.LogEntriesForSlowFollowers,
+				HeartbeatTick:              c.Spec.Raft.HeartbeatTick,
+				ElectionTick:               c.Spec.Raft.ElectionTick,
+			},
+			Dispatcher: types.DispatcherConfig{
+				HeartbeatPeriod: c.Spec.Dispatcher.HeartbeatPeriod,
+			},
+		},
+	}
+
+	swarm.Spec.CAConfig.NodeCertExpiry, _ = ptypes.Duration(c.Spec.CAConfig.NodeCertExpiry)
+
+	// Meta
+	swarm.Version.Index = c.Meta.Version.Index
+	swarm.CreatedAt, _ = ptypes.Timestamp(c.Meta.CreatedAt)
+	swarm.UpdatedAt, _ = ptypes.Timestamp(c.Meta.UpdatedAt)
+
+	// Annotations
+	swarm.Spec.Name = c.Spec.Annotations.Name
+	swarm.Spec.Labels = c.Spec.Annotations.Labels
+
+	for _, policy := range c.Spec.AcceptancePolicy.Policies {
+		p := types.Policy{
+			Role:       types.NodeRole(strings.ToLower(policy.Role.String())),
+			Autoaccept: policy.Autoaccept,
+		}
+		if policy.Secret != nil {
+			p.Secret = string(policy.Secret.Data)
+		}
+		swarm.Spec.AcceptancePolicy.Policies = append(swarm.Spec.AcceptancePolicy.Policies, p)
+	}
+
+	return swarm
+}
+
+// SwarmSpecToGRPC converts a Spec to a grpc ClusterSpec.
+func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) {
+	spec := swarmapi.ClusterSpec{
+		Annotations: swarmapi.Annotations{
+			Name:   s.Name,
+			Labels: s.Labels,
+		},
+		Orchestration: swarmapi.OrchestrationConfig{
+			TaskHistoryRetentionLimit: s.Orchestration.TaskHistoryRetentionLimit,
+		},
+		Raft: swarmapi.RaftConfig{
+			SnapshotInterval:           s.Raft.SnapshotInterval,
+			KeepOldSnapshots:           s.Raft.KeepOldSnapshots,
+			LogEntriesForSlowFollowers: s.Raft.LogEntriesForSlowFollowers,
+			HeartbeatTick:              s.Raft.HeartbeatTick,
+			ElectionTick:               s.Raft.ElectionTick,
+		},
+		Dispatcher: swarmapi.DispatcherConfig{
+			HeartbeatPeriod: s.Dispatcher.HeartbeatPeriod,
+		},
+		CAConfig: swarmapi.CAConfig{
+			NodeCertExpiry: ptypes.DurationProto(s.CAConfig.NodeCertExpiry),
+		},
+	}
+
+	if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy); err != nil {
+		return swarmapi.ClusterSpec{}, err
+	}
+	return spec, nil
+}
+
+// SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy.
+func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy) error {
+	spec.AcceptancePolicy.Policies = nil
+	for _, p := range acceptancePolicy.Policies {
+		role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))]
+		if !ok {
+			return fmt.Errorf("invalid Role: %q", p.Role)
+		}
+
+		policy := &swarmapi.AcceptancePolicy_RoleAdmissionPolicy{
+			Role:       swarmapi.NodeRole(role),
+			Autoaccept: p.Autoaccept,
+		}
+
+		if p.Secret != "" {
+			hashPwd, _ := bcrypt.GenerateFromPassword([]byte(p.Secret), 0)
+			policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{
+				Data: hashPwd,
+				Alg:  "bcrypt",
+			}
+		}
+
+		spec.AcceptancePolicy.Policies = append(spec.AcceptancePolicy.Policies, policy)
+	}
+	return nil
+}

+ 53 - 0
daemon/cluster/convert/task.go

@@ -0,0 +1,53 @@
+package convert
+
+import (
+	"strings"
+
+	types "github.com/docker/engine-api/types/swarm"
+	swarmapi "github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/protobuf/ptypes"
+)
+
+// TaskFromGRPC converts a grpc Task to a Task.
+func TaskFromGRPC(t swarmapi.Task) types.Task {
+	containerConfig := t.Spec.Runtime.(*swarmapi.TaskSpec_Container).Container
+	containerStatus := t.Status.GetContainer()
+	task := types.Task{
+		ID:        t.ID,
+		ServiceID: t.ServiceID,
+		Slot:      int(t.Slot),
+		NodeID:    t.NodeID,
+		Spec: types.TaskSpec{
+			ContainerSpec: containerSpecFromGRPC(containerConfig),
+			Resources:     resourcesFromGRPC(t.Spec.Resources),
+			RestartPolicy: restartPolicyFromGRPC(t.Spec.Restart),
+			Placement:     placementFromGRPC(t.Spec.Placement),
+		},
+		Status: types.TaskStatus{
+			State:   types.TaskState(strings.ToLower(t.Status.State.String())),
+			Message: t.Status.Message,
+			Err:     t.Status.Err,
+		},
+		DesiredState: types.TaskState(strings.ToLower(t.DesiredState.String())),
+	}
+
+	// Meta
+	task.Version.Index = t.Meta.Version.Index
+	task.CreatedAt, _ = ptypes.Timestamp(t.Meta.CreatedAt)
+	task.UpdatedAt, _ = ptypes.Timestamp(t.Meta.UpdatedAt)
+
+	task.Status.Timestamp, _ = ptypes.Timestamp(t.Status.Timestamp)
+
+	if containerStatus != nil {
+		task.Status.ContainerStatus.ContainerID = containerStatus.ContainerID
+		task.Status.ContainerStatus.PID = int(containerStatus.PID)
+		task.Status.ContainerStatus.ExitCode = int(containerStatus.ExitCode)
+	}
+
+	// NetworksAttachments
+	for _, na := range t.Networks {
+		task.NetworksAttachments = append(task.NetworksAttachments, networkAttachementFromGRPC(na))
+	}
+
+	return task
+}

+ 35 - 0
daemon/cluster/executor/backend.go

@@ -0,0 +1,35 @@
+package executor
+
+import (
+	"io"
+
+	clustertypes "github.com/docker/docker/daemon/cluster/provider"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/container"
+	"github.com/docker/engine-api/types/network"
+	"github.com/docker/libnetwork/cluster"
+	networktypes "github.com/docker/libnetwork/types"
+	"golang.org/x/net/context"
+)
+
+// Backend defines the executor component for a swarm agent.
+type Backend interface {
+	CreateManagedNetwork(clustertypes.NetworkCreateRequest) error
+	DeleteManagedNetwork(name string) error
+	SetupIngress(req clustertypes.NetworkCreateRequest, nodeIP string) error
+	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
+	CreateManagedContainer(types.ContainerCreateConfig) (types.ContainerCreateResponse, error)
+	ContainerStart(name string, hostConfig *container.HostConfig) error
+	ContainerStop(name string, seconds int) error
+	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
+	UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error
+	ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error)
+	ContainerWaitWithContext(ctx context.Context, name string) (<-chan int, error)
+	ContainerRm(name string, config *types.ContainerRmConfig) error
+	ContainerKill(name string, sig uint64) error
+	SystemInfo() (*types.Info, error)
+	VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error)
+	ListContainersForNode(nodeID string) []string
+	SetNetworkBootstrapKeys([]*networktypes.EncryptionKey) error
+	SetClusterProvider(provider cluster.Provider)
+}

+ 229 - 0
daemon/cluster/executor/container/adapter.go

@@ -0,0 +1,229 @@
+package container
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"strings"
+	"syscall"
+
+	"github.com/Sirupsen/logrus"
+	executorpkg "github.com/docker/docker/daemon/cluster/executor"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/libnetwork"
+	"github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/log"
+	"golang.org/x/net/context"
+)
+
+// containerAdapter conducts remote operations for a container. All calls
+// are mostly naked calls to the client API, seeded with information from
+// containerConfig.
+type containerAdapter struct {
+	backend   executorpkg.Backend
+	container *containerConfig
+}
+
+func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapter, error) {
+	ctnr, err := newContainerConfig(task)
+	if err != nil {
+		return nil, err
+	}
+
+	return &containerAdapter{
+		container: ctnr,
+		backend:   b,
+	}, nil
+}
+
+func (c *containerAdapter) pullImage(ctx context.Context) error {
+	// if the image needs to be pulled, the auth config will be retrieved and updated
+	encodedAuthConfig := c.container.task.ServiceAnnotations.Labels[fmt.Sprintf("%v.registryauth", systemLabelPrefix)]
+
+	authConfig := &types.AuthConfig{}
+	if encodedAuthConfig != "" {
+		if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil {
+			logrus.Warnf("invalid authconfig: %v", err)
+		}
+	}
+
+	pr, pw := io.Pipe()
+	metaHeaders := map[string][]string{}
+	go func() {
+		err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw)
+		pw.CloseWithError(err)
+	}()
+
+	dec := json.NewDecoder(pr)
+	m := map[string]interface{}{}
+	for {
+		if err := dec.Decode(&m); err != nil {
+			if err == io.EOF {
+				break
+			}
+			return err
+		}
+		// TOOD(stevvooe): Report this status somewhere.
+		logrus.Debugln("pull progress", m)
+	}
+	// if the final stream object contained an error, return it
+	if errMsg, ok := m["error"]; ok {
+		return fmt.Errorf("%v", errMsg)
+	}
+	return nil
+}
+
+func (c *containerAdapter) createNetworks(ctx context.Context) error {
+	for _, network := range c.container.networks() {
+		ncr, err := c.container.networkCreateRequest(network)
+		if err != nil {
+			return err
+		}
+
+		if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing
+			if _, ok := err.(libnetwork.NetworkNameError); ok {
+				continue
+			}
+
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c *containerAdapter) removeNetworks(ctx context.Context) error {
+	for _, nid := range c.container.networks() {
+		if err := c.backend.DeleteManagedNetwork(nid); err != nil {
+			if _, ok := err.(*libnetwork.ActiveEndpointsError); ok {
+				continue
+			}
+			log.G(ctx).Errorf("network %s remove failed: %v", nid, err)
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c *containerAdapter) create(ctx context.Context, backend executorpkg.Backend) error {
+	var cr types.ContainerCreateResponse
+	var err error
+	if cr, err = backend.CreateManagedContainer(types.ContainerCreateConfig{
+		Name:       c.container.name(),
+		Config:     c.container.config(),
+		HostConfig: c.container.hostConfig(),
+		// Use the first network in container create
+		NetworkingConfig: c.container.createNetworkingConfig(),
+	}); err != nil {
+		return err
+	}
+
+	// Docker daemon currently doesnt support multiple networks in container create
+	// Connect to all other networks
+	nc := c.container.connectNetworkingConfig()
+
+	if nc != nil {
+		for n, ep := range nc.EndpointsConfig {
+			logrus.Errorf("CONNECT %s : %v", n, ep.IPAMConfig.IPv4Address)
+			if err := backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil {
+				return err
+			}
+		}
+	}
+
+	if err := backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *containerAdapter) start(ctx context.Context) error {
+	return c.backend.ContainerStart(c.container.name(), nil)
+}
+
+func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) {
+	cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false)
+	if ctx.Err() != nil {
+		return types.ContainerJSON{}, ctx.Err()
+	}
+	if err != nil {
+		return types.ContainerJSON{}, err
+	}
+	return *cs, nil
+}
+
+// events issues a call to the events API and returns a channel with all
+// events. The stream of events can be shutdown by cancelling the context.
+//
+// A chan struct{} is returned that will be closed if the event procressing
+// fails and needs to be restarted.
+func (c *containerAdapter) wait(ctx context.Context) (<-chan int, error) {
+	return c.backend.ContainerWaitWithContext(ctx, c.container.name())
+}
+
+func (c *containerAdapter) shutdown(ctx context.Context) error {
+	// Default stop grace period to 10s.
+	stopgrace := 10
+	spec := c.container.spec()
+	if spec.StopGracePeriod != nil {
+		stopgrace = int(spec.StopGracePeriod.Seconds)
+	}
+	return c.backend.ContainerStop(c.container.name(), stopgrace)
+}
+
+func (c *containerAdapter) terminate(ctx context.Context) error {
+	return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL))
+}
+
+func (c *containerAdapter) remove(ctx context.Context) error {
+	return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{
+		RemoveVolume: true,
+		ForceRemove:  true,
+	})
+}
+
+func (c *containerAdapter) createVolumes(ctx context.Context, backend executorpkg.Backend) error {
+	// Create plugin volumes that are embedded inside a Mount
+	for _, mount := range c.container.task.Spec.GetContainer().Mounts {
+		if mount.Type != api.MountTypeVolume {
+			continue
+		}
+
+		if mount.VolumeOptions != nil {
+			continue
+		}
+
+		if mount.VolumeOptions.DriverConfig == nil {
+			continue
+		}
+
+		req := c.container.volumeCreateRequest(&mount)
+
+		// Check if this volume exists on the engine
+		if _, err := backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil {
+			// TODO(amitshukla): Today, volume create through the engine api does not return an error
+			// when the named volume with the same parameters already exists.
+			// It returns an error if the driver name is different - that is a valid error
+			return err
+		}
+
+	}
+
+	return nil
+}
+
+// todo: typed/wrapped errors
+func isContainerCreateNameConflict(err error) bool {
+	return strings.Contains(err.Error(), "Conflict. The name")
+}
+
+func isUnknownContainer(err error) bool {
+	return strings.Contains(err.Error(), "No such container:")
+}
+
+func isStoppedContainer(err error) bool {
+	return strings.Contains(err.Error(), "is already stopped")
+}

+ 415 - 0
daemon/cluster/executor/container/container.go

@@ -0,0 +1,415 @@
+package container
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"strings"
+	"time"
+
+	clustertypes "github.com/docker/docker/daemon/cluster/provider"
+	"github.com/docker/docker/reference"
+	"github.com/docker/engine-api/types"
+	enginecontainer "github.com/docker/engine-api/types/container"
+	"github.com/docker/engine-api/types/network"
+	"github.com/docker/swarmkit/agent/exec"
+	"github.com/docker/swarmkit/api"
+)
+
+const (
+	// Explictly use the kernel's default setting for CPU quota of 100ms.
+	// https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
+	cpuQuotaPeriod = 100 * time.Millisecond
+
+	// systemLabelPrefix represents the reserved namespace for system labels.
+	systemLabelPrefix = "com.docker.swarm"
+)
+
+// containerConfig converts task properties into docker container compatible
+// components.
+type containerConfig struct {
+	task                *api.Task
+	networksAttachments map[string]*api.NetworkAttachment
+}
+
+// newContainerConfig returns a validated container config. No methods should
+// return an error if this function returns without error.
+func newContainerConfig(t *api.Task) (*containerConfig, error) {
+	var c containerConfig
+	return &c, c.setTask(t)
+}
+
+func (c *containerConfig) setTask(t *api.Task) error {
+	container := t.Spec.GetContainer()
+	if container == nil {
+		return exec.ErrRuntimeUnsupported
+	}
+
+	if container.Image == "" {
+		return ErrImageRequired
+	}
+
+	// index the networks by name
+	c.networksAttachments = make(map[string]*api.NetworkAttachment, len(t.Networks))
+	for _, attachment := range t.Networks {
+		c.networksAttachments[attachment.Network.Spec.Annotations.Name] = attachment
+	}
+
+	c.task = t
+	return nil
+}
+
+func (c *containerConfig) endpoint() *api.Endpoint {
+	return c.task.Endpoint
+}
+
+func (c *containerConfig) spec() *api.ContainerSpec {
+	return c.task.Spec.GetContainer()
+}
+
+func (c *containerConfig) name() string {
+	if c.task.Annotations.Name != "" {
+		// if set, use the container Annotations.Name field, set in the orchestrator.
+		return c.task.Annotations.Name
+	}
+
+	// fallback to service.slot.id.
+	return strings.Join([]string{c.task.ServiceAnnotations.Name, fmt.Sprint(c.task.Slot), c.task.ID}, ".")
+}
+
+func (c *containerConfig) image() string {
+	raw := c.spec().Image
+	ref, err := reference.ParseNamed(raw)
+	if err != nil {
+		return raw
+	}
+	return reference.WithDefaultTag(ref).String()
+}
+
+func (c *containerConfig) volumes() map[string]struct{} {
+	r := make(map[string]struct{})
+
+	for _, mount := range c.spec().Mounts {
+		// pick off all the volume mounts.
+		if mount.Type != api.MountTypeVolume {
+			continue
+		}
+
+		r[fmt.Sprintf("%s:%s", mount.Target, getMountMask(&mount))] = struct{}{}
+	}
+
+	return r
+}
+
+func (c *containerConfig) config() *enginecontainer.Config {
+	config := &enginecontainer.Config{
+		Labels:     c.labels(),
+		User:       c.spec().User,
+		Env:        c.spec().Env,
+		WorkingDir: c.spec().Dir,
+		Image:      c.image(),
+		Volumes:    c.volumes(),
+	}
+
+	if len(c.spec().Command) > 0 {
+		// If Command is provided, we replace the whole invocation with Command
+		// by replacing Entrypoint and specifying Cmd. Args is ignored in this
+		// case.
+		config.Entrypoint = append(config.Entrypoint, c.spec().Command[0])
+		config.Cmd = append(config.Cmd, c.spec().Command[1:]...)
+	} else if len(c.spec().Args) > 0 {
+		// In this case, we assume the image has an Entrypoint and Args
+		// specifies the arguments for that entrypoint.
+		config.Cmd = c.spec().Args
+	}
+
+	return config
+}
+
+func (c *containerConfig) labels() map[string]string {
+	var (
+		system = map[string]string{
+			"task":         "", // mark as cluster task
+			"task.id":      c.task.ID,
+			"task.name":    fmt.Sprintf("%v.%v", c.task.ServiceAnnotations.Name, c.task.Slot),
+			"node.id":      c.task.NodeID,
+			"service.id":   c.task.ServiceID,
+			"service.name": c.task.ServiceAnnotations.Name,
+		}
+		labels = make(map[string]string)
+	)
+
+	// base labels are those defined in the spec.
+	for k, v := range c.spec().Labels {
+		labels[k] = v
+	}
+
+	// we then apply the overrides from the task, which may be set via the
+	// orchestrator.
+	for k, v := range c.task.Annotations.Labels {
+		labels[k] = v
+	}
+
+	// finally, we apply the system labels, which override all labels.
+	for k, v := range system {
+		labels[strings.Join([]string{systemLabelPrefix, k}, ".")] = v
+	}
+
+	return labels
+}
+
+func (c *containerConfig) bindMounts() []string {
+	var r []string
+
+	for _, val := range c.spec().Mounts {
+		mask := getMountMask(&val)
+		if val.Type == api.MountTypeBind {
+			r = append(r, fmt.Sprintf("%s:%s:%s", val.Source, val.Target, mask))
+		}
+	}
+
+	return r
+}
+
+func getMountMask(m *api.Mount) string {
+	maskOpts := []string{"ro"}
+	if m.Writable {
+		maskOpts[0] = "rw"
+	}
+
+	if m.BindOptions != nil {
+		switch m.BindOptions.Propagation {
+		case api.MountPropagationPrivate:
+			maskOpts = append(maskOpts, "private")
+		case api.MountPropagationRPrivate:
+			maskOpts = append(maskOpts, "rprivate")
+		case api.MountPropagationShared:
+			maskOpts = append(maskOpts, "shared")
+		case api.MountPropagationRShared:
+			maskOpts = append(maskOpts, "rshared")
+		case api.MountPropagationSlave:
+			maskOpts = append(maskOpts, "slave")
+		case api.MountPropagationRSlave:
+			maskOpts = append(maskOpts, "rslave")
+		}
+	}
+
+	if m.VolumeOptions != nil {
+		if !m.VolumeOptions.Populate {
+			maskOpts = append(maskOpts, "nocopy")
+		}
+	}
+	return strings.Join(maskOpts, ",")
+}
+
+func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
+	return &enginecontainer.HostConfig{
+		Resources: c.resources(),
+		Binds:     c.bindMounts(),
+	}
+}
+
+// This handles the case of volumes that are defined inside a service Mount
+func (c *containerConfig) volumeCreateRequest(mount *api.Mount) *types.VolumeCreateRequest {
+	var (
+		driverName string
+		driverOpts map[string]string
+		labels     map[string]string
+	)
+
+	if mount.VolumeOptions != nil && mount.VolumeOptions.DriverConfig != nil {
+		driverName = mount.VolumeOptions.DriverConfig.Name
+		driverOpts = mount.VolumeOptions.DriverConfig.Options
+		labels = mount.VolumeOptions.Labels
+	}
+
+	if mount.VolumeOptions != nil {
+		return &types.VolumeCreateRequest{
+			Name:       mount.Source,
+			Driver:     driverName,
+			DriverOpts: driverOpts,
+			Labels:     labels,
+		}
+	}
+	return nil
+}
+
+func (c *containerConfig) resources() enginecontainer.Resources {
+	resources := enginecontainer.Resources{}
+
+	// If no limits are specified let the engine use its defaults.
+	//
+	// TODO(aluzzardi): We might want to set some limits anyway otherwise
+	// "unlimited" tasks will step over the reservation of other tasks.
+	r := c.task.Spec.Resources
+	if r == nil || r.Limits == nil {
+		return resources
+	}
+
+	if r.Limits.MemoryBytes > 0 {
+		resources.Memory = r.Limits.MemoryBytes
+	}
+
+	if r.Limits.NanoCPUs > 0 {
+		// CPU Period must be set in microseconds.
+		resources.CPUPeriod = int64(cpuQuotaPeriod / time.Microsecond)
+		resources.CPUQuota = r.Limits.NanoCPUs * resources.CPUPeriod / 1e9
+	}
+
+	return resources
+}
+
+// Docker daemon supports just 1 network during container create.
+func (c *containerConfig) createNetworkingConfig() *network.NetworkingConfig {
+	var networks []*api.NetworkAttachment
+	if c.task.Spec.GetContainer() != nil {
+		networks = c.task.Networks
+	}
+
+	epConfig := make(map[string]*network.EndpointSettings)
+	if len(networks) > 0 {
+		epConfig[networks[0].Network.Spec.Annotations.Name] = getEndpointConfig(networks[0])
+	}
+
+	return &network.NetworkingConfig{EndpointsConfig: epConfig}
+}
+
+// TODO: Merge this function with createNetworkingConfig after daemon supports multiple networks in container create
+func (c *containerConfig) connectNetworkingConfig() *network.NetworkingConfig {
+	var networks []*api.NetworkAttachment
+	if c.task.Spec.GetContainer() != nil {
+		networks = c.task.Networks
+	}
+
+	// First network is used during container create. Other networks are used in "docker network connect"
+	if len(networks) < 2 {
+		return nil
+	}
+
+	epConfig := make(map[string]*network.EndpointSettings)
+	for _, na := range networks[1:] {
+		epConfig[na.Network.Spec.Annotations.Name] = getEndpointConfig(na)
+	}
+	return &network.NetworkingConfig{EndpointsConfig: epConfig}
+}
+
+func getEndpointConfig(na *api.NetworkAttachment) *network.EndpointSettings {
+	var ipv4, ipv6 string
+	for _, addr := range na.Addresses {
+		ip, _, err := net.ParseCIDR(addr)
+		if err != nil {
+			continue
+		}
+
+		if ip.To4() != nil {
+			ipv4 = ip.String()
+			continue
+		}
+
+		if ip.To16() != nil {
+			ipv6 = ip.String()
+		}
+	}
+
+	return &network.EndpointSettings{
+		IPAMConfig: &network.EndpointIPAMConfig{
+			IPv4Address: ipv4,
+			IPv6Address: ipv6,
+		},
+	}
+}
+
+func (c *containerConfig) virtualIP(networkID string) string {
+	if c.task.Endpoint == nil {
+		return ""
+	}
+
+	for _, eVip := range c.task.Endpoint.VirtualIPs {
+		// We only support IPv4 VIPs for now.
+		if eVip.NetworkID == networkID {
+			vip, _, err := net.ParseCIDR(eVip.Addr)
+			if err != nil {
+				return ""
+			}
+
+			return vip.String()
+		}
+	}
+
+	return ""
+}
+
+func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig {
+	if len(c.task.Networks) == 0 {
+		return nil
+	}
+
+	log.Printf("Creating service config in agent for t = %+v", c.task)
+	svcCfg := &clustertypes.ServiceConfig{
+		Name:             c.task.ServiceAnnotations.Name,
+		ID:               c.task.ServiceID,
+		VirtualAddresses: make(map[string]*clustertypes.VirtualAddress),
+	}
+
+	for _, na := range c.task.Networks {
+		svcCfg.VirtualAddresses[na.Network.ID] = &clustertypes.VirtualAddress{
+			// We support only IPv4 virtual IP for now.
+			IPv4: c.virtualIP(na.Network.ID),
+		}
+	}
+
+	if c.task.Endpoint != nil {
+		for _, ePort := range c.task.Endpoint.Ports {
+			svcCfg.ExposedPorts = append(svcCfg.ExposedPorts, &clustertypes.PortConfig{
+				Name:          ePort.Name,
+				Protocol:      int32(ePort.Protocol),
+				TargetPort:    ePort.TargetPort,
+				PublishedPort: ePort.PublishedPort,
+			})
+		}
+	}
+
+	return svcCfg
+}
+
+// networks returns a list of network names attached to the container. The
+// returned name can be used to lookup the corresponding network create
+// options.
+func (c *containerConfig) networks() []string {
+	var networks []string
+
+	for name := range c.networksAttachments {
+		networks = append(networks, name)
+	}
+
+	return networks
+}
+
+func (c *containerConfig) networkCreateRequest(name string) (clustertypes.NetworkCreateRequest, error) {
+	na, ok := c.networksAttachments[name]
+	if !ok {
+		return clustertypes.NetworkCreateRequest{}, errors.New("container: unknown network referenced")
+	}
+
+	options := types.NetworkCreate{
+		// ID:     na.Network.ID,
+		Driver: na.Network.DriverState.Name,
+		IPAM: network.IPAM{
+			Driver: na.Network.IPAM.Driver.Name,
+		},
+		Options:        na.Network.DriverState.Options,
+		CheckDuplicate: true,
+	}
+
+	for _, ic := range na.Network.IPAM.Configs {
+		c := network.IPAMConfig{
+			Subnet:  ic.Subnet,
+			IPRange: ic.Range,
+			Gateway: ic.Gateway,
+		}
+		options.IPAM.Config = append(options.IPAM.Config, c)
+	}
+
+	return clustertypes.NetworkCreateRequest{na.Network.ID, types.NetworkCreateRequest{Name: name, NetworkCreate: options}}, nil
+}

+ 305 - 0
daemon/cluster/executor/container/controller.go

@@ -0,0 +1,305 @@
+package container
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	executorpkg "github.com/docker/docker/daemon/cluster/executor"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/swarmkit/agent/exec"
+	"github.com/docker/swarmkit/api"
+	"github.com/docker/swarmkit/log"
+	"golang.org/x/net/context"
+)
+
+// controller implements agent.Controller against docker's API.
+//
+// Most operations against docker's API are done through the container name,
+// which is unique to the task.
+type controller struct {
+	backend executorpkg.Backend
+	task    *api.Task
+	adapter *containerAdapter
+	closed  chan struct{}
+	err     error
+}
+
+var _ exec.Controller = &controller{}
+
+// NewController returns a dockerexec runner for the provided task.
+func newController(b executorpkg.Backend, task *api.Task) (*controller, error) {
+	adapter, err := newContainerAdapter(b, task)
+	if err != nil {
+		return nil, err
+	}
+
+	return &controller{
+		backend: b,
+		task:    task,
+		adapter: adapter,
+		closed:  make(chan struct{}),
+	}, nil
+}
+
+func (r *controller) Task() (*api.Task, error) {
+	return r.task, nil
+}
+
+// ContainerStatus returns the container-specific status for the task.
+func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) {
+	ctnr, err := r.adapter.inspect(ctx)
+	if err != nil {
+		if isUnknownContainer(err) {
+			return nil, nil
+		}
+		return nil, err
+	}
+	return parseContainerStatus(ctnr)
+}
+
+// Update tasks a recent task update and applies it to the container.
+func (r *controller) Update(ctx context.Context, t *api.Task) error {
+	log.G(ctx).Warnf("task updates not yet supported")
+	// TODO(stevvooe): While assignment of tasks is idempotent, we do allow
+	// updates of metadata, such as labelling, as well as any other properties
+	// that make sense.
+	return nil
+}
+
+// Prepare creates a container and ensures the image is pulled.
+//
+// If the container has already be created, exec.ErrTaskPrepared is returned.
+func (r *controller) Prepare(ctx context.Context) error {
+	if err := r.checkClosed(); err != nil {
+		return err
+	}
+
+	// Make sure all the networks that the task needs are created.
+	if err := r.adapter.createNetworks(ctx); err != nil {
+		return err
+	}
+
+	// Make sure all the volumes that the task needs are created.
+	if err := r.adapter.createVolumes(ctx, r.backend); err != nil {
+		return err
+	}
+
+	for {
+		if err := r.checkClosed(); err != nil {
+			return err
+		}
+		if err := r.adapter.create(ctx, r.backend); err != nil {
+			if isContainerCreateNameConflict(err) {
+				if _, err := r.adapter.inspect(ctx); err != nil {
+					return err
+				}
+
+				// container is already created. success!
+				return exec.ErrTaskPrepared
+			}
+
+			if !strings.Contains(err.Error(), "No such image") { // todo: better error detection
+				return err
+			}
+			if err := r.adapter.pullImage(ctx); err != nil {
+				return err
+			}
+
+			continue // retry to create the container
+		}
+
+		break
+	}
+
+	return nil
+}
+
+// Start the container. An error will be returned if the container is already started.
+func (r *controller) Start(ctx context.Context) error {
+	if err := r.checkClosed(); err != nil {
+		return err
+	}
+
+	ctnr, err := r.adapter.inspect(ctx)
+	if err != nil {
+		return err
+	}
+
+	// Detect whether the container has *ever* been started. If so, we don't
+	// issue the start.
+	//
+	// TODO(stevvooe): This is very racy. While reading inspect, another could
+	// start the process and we could end up starting it twice.
+	if ctnr.State.Status != "created" {
+		return exec.ErrTaskStarted
+	}
+
+	if err := r.adapter.start(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Wait on the container to exit.
+func (r *controller) Wait(pctx context.Context) error {
+	if err := r.checkClosed(); err != nil {
+		return err
+	}
+
+	ctx, cancel := context.WithCancel(pctx)
+	defer cancel()
+
+	c, err := r.adapter.wait(ctx)
+	if err != nil {
+		return err
+	}
+
+	<-c
+	if ctx.Err() != nil {
+		return ctx.Err()
+	}
+	ctnr, err := r.adapter.inspect(ctx)
+	if err != nil {
+		// TODO(stevvooe): Need to handle missing container here. It is likely
+		// that a Wait call with a not found error should result in no waiting
+		// and no error at all.
+		return err
+	}
+
+	if ctnr.State.ExitCode != 0 {
+		var cause error
+		if ctnr.State.Error != "" {
+			cause = errors.New(ctnr.State.Error)
+		}
+		cstatus, _ := parseContainerStatus(ctnr)
+		return &exitError{
+			code:            ctnr.State.ExitCode,
+			cause:           cause,
+			containerStatus: cstatus,
+		}
+	}
+	return nil
+}
+
+// Shutdown the container cleanly.
+func (r *controller) Shutdown(ctx context.Context) error {
+	if err := r.checkClosed(); err != nil {
+		return err
+	}
+
+	if err := r.adapter.shutdown(ctx); err != nil {
+		if isUnknownContainer(err) || isStoppedContainer(err) {
+			return nil
+		}
+
+		return err
+	}
+
+	return nil
+}
+
+// Terminate the container, with force.
+func (r *controller) Terminate(ctx context.Context) error {
+	if err := r.checkClosed(); err != nil {
+		return err
+	}
+
+	if err := r.adapter.terminate(ctx); err != nil {
+		if isUnknownContainer(err) {
+			return nil
+		}
+
+		return err
+	}
+
+	return nil
+}
+
+// Remove the container and its resources.
+func (r *controller) Remove(ctx context.Context) error {
+	if err := r.checkClosed(); err != nil {
+		return err
+	}
+
+	// It may be necessary to shut down the task before removing it.
+	if err := r.Shutdown(ctx); err != nil {
+		if isUnknownContainer(err) {
+			return nil
+		}
+		// This may fail if the task was already shut down.
+		log.G(ctx).WithError(err).Debug("shutdown failed on removal")
+	}
+
+	// Try removing networks referenced in this task in case this
+	// task is the last one referencing it
+	if err := r.adapter.removeNetworks(ctx); err != nil {
+		if isUnknownContainer(err) {
+			return nil
+		}
+		return err
+	}
+
+	if err := r.adapter.remove(ctx); err != nil {
+		if isUnknownContainer(err) {
+			return nil
+		}
+
+		return err
+	}
+	return nil
+}
+
+// Close the runner and clean up any ephemeral resources.
+func (r *controller) Close() error {
+	select {
+	case <-r.closed:
+		return r.err
+	default:
+		r.err = exec.ErrControllerClosed
+		close(r.closed)
+	}
+	return nil
+}
+
+func (r *controller) checkClosed() error {
+	select {
+	case <-r.closed:
+		return r.err
+	default:
+		return nil
+	}
+}
+
+func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error) {
+	status := &api.ContainerStatus{
+		ContainerID: ctnr.ID,
+		PID:         int32(ctnr.State.Pid),
+		ExitCode:    int32(ctnr.State.ExitCode),
+	}
+
+	return status, nil
+}
+
+type exitError struct {
+	code            int
+	cause           error
+	containerStatus *api.ContainerStatus
+}
+
+func (e *exitError) Error() string {
+	if e.cause != nil {
+		return fmt.Sprintf("task: non-zero exit (%v): %v", e.code, e.cause)
+	}
+
+	return fmt.Sprintf("task: non-zero exit (%v)", e.code)
+}
+
+func (e *exitError) ExitCode() int {
+	return int(e.containerStatus.ExitCode)
+}
+
+func (e *exitError) Cause() error {
+	return e.cause
+}

+ 12 - 0
daemon/cluster/executor/container/errors.go

@@ -0,0 +1,12 @@
+package container
+
+import "fmt"
+
+var (
+	// ErrImageRequired returned if a task is missing the image definition.
+	ErrImageRequired = fmt.Errorf("dockerexec: image required")
+
+	// ErrContainerDestroyed returned when a container is prematurely destroyed
+	// during a wait call.
+	ErrContainerDestroyed = fmt.Errorf("dockerexec: container destroyed")
+)

+ 139 - 0
daemon/cluster/executor/container/executor.go

@@ -0,0 +1,139 @@
+package container
+
+import (
+	"strings"
+
+	executorpkg "github.com/docker/docker/daemon/cluster/executor"
+	clustertypes "github.com/docker/docker/daemon/cluster/provider"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/network"
+	networktypes "github.com/docker/libnetwork/types"
+	"github.com/docker/swarmkit/agent/exec"
+	"github.com/docker/swarmkit/api"
+	"golang.org/x/net/context"
+)
+
+type executor struct {
+	backend executorpkg.Backend
+}
+
+// NewExecutor returns an executor from the docker client.
+func NewExecutor(b executorpkg.Backend) exec.Executor {
+	return &executor{
+		backend: b,
+	}
+}
+
+// Describe returns the underlying node description from the docker client.
+func (e *executor) Describe(ctx context.Context) (*api.NodeDescription, error) {
+	info, err := e.backend.SystemInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	var plugins []api.PluginDescription
+	addPlugins := func(typ string, names []string) {
+		for _, name := range names {
+			plugins = append(plugins, api.PluginDescription{
+				Type: typ,
+				Name: name,
+			})
+		}
+	}
+
+	addPlugins("Volume", info.Plugins.Volume)
+	// Add builtin driver "overlay" (the only builtin multi-host driver) to
+	// the plugin list by default.
+	addPlugins("Network", append([]string{"overlay"}, info.Plugins.Network...))
+	addPlugins("Authorization", info.Plugins.Authorization)
+
+	// parse []string labels into a map[string]string
+	labels := map[string]string{}
+	for _, l := range info.Labels {
+		stringSlice := strings.SplitN(l, "=", 2)
+		// this will take the last value in the list for a given key
+		// ideally, one shouldn't assign multiple values to the same key
+		if len(stringSlice) > 1 {
+			labels[stringSlice[0]] = stringSlice[1]
+		}
+	}
+
+	description := &api.NodeDescription{
+		Hostname: info.Name,
+		Platform: &api.Platform{
+			Architecture: info.Architecture,
+			OS:           info.OSType,
+		},
+		Engine: &api.EngineDescription{
+			EngineVersion: info.ServerVersion,
+			Labels:        labels,
+			Plugins:       plugins,
+		},
+		Resources: &api.Resources{
+			NanoCPUs:    int64(info.NCPU) * 1e9,
+			MemoryBytes: info.MemTotal,
+		},
+	}
+
+	return description, nil
+}
+
+func (e *executor) Configure(ctx context.Context, node *api.Node) error {
+	na := node.Attachment
+	if na == nil {
+		return nil
+	}
+
+	options := types.NetworkCreate{
+		Driver: na.Network.DriverState.Name,
+		IPAM: network.IPAM{
+			Driver: na.Network.IPAM.Driver.Name,
+		},
+		Options:        na.Network.DriverState.Options,
+		CheckDuplicate: true,
+	}
+
+	for _, ic := range na.Network.IPAM.Configs {
+		c := network.IPAMConfig{
+			Subnet:  ic.Subnet,
+			IPRange: ic.Range,
+			Gateway: ic.Gateway,
+		}
+		options.IPAM.Config = append(options.IPAM.Config, c)
+	}
+
+	return e.backend.SetupIngress(clustertypes.NetworkCreateRequest{
+		na.Network.ID,
+		types.NetworkCreateRequest{
+			Name:          na.Network.Spec.Annotations.Name,
+			NetworkCreate: options,
+		},
+	}, na.Addresses[0])
+}
+
+// Controller returns a docker container runner.
+func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
+	ctlr, err := newController(e.backend, t)
+	if err != nil {
+		return nil, err
+	}
+
+	return ctlr, nil
+}
+
+func (e *executor) SetNetworkBootstrapKeys(keys []*api.EncryptionKey) error {
+	nwKeys := []*networktypes.EncryptionKey{}
+	for _, key := range keys {
+		nwKey := &networktypes.EncryptionKey{
+			Subsystem:   key.Subsystem,
+			Algorithm:   int32(key.Algorithm),
+			Key:         make([]byte, len(key.Key)),
+			LamportTime: key.LamportTime,
+		}
+		copy(nwKey.Key, key.Key)
+		nwKeys = append(nwKeys, nwKey)
+	}
+	e.backend.SetNetworkBootstrapKeys(nwKeys)
+
+	return nil
+}

+ 93 - 0
daemon/cluster/filters.go

@@ -0,0 +1,93 @@
+package cluster
+
+import (
+	"fmt"
+	"strings"
+
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/engine-api/types/filters"
+	swarmapi "github.com/docker/swarmkit/api"
+)
+
+func newListNodesFilters(filter filters.Args) (*swarmapi.ListNodesRequest_Filters, error) {
+	accepted := map[string]bool{
+		"name":       true,
+		"id":         true,
+		"label":      true,
+		"role":       true,
+		"membership": true,
+	}
+	if err := filter.Validate(accepted); err != nil {
+		return nil, err
+	}
+	f := &swarmapi.ListNodesRequest_Filters{
+		Names:      filter.Get("name"),
+		IDPrefixes: filter.Get("id"),
+		Labels:     runconfigopts.ConvertKVStringsToMap(filter.Get("label")),
+	}
+
+	for _, r := range filter.Get("role") {
+		if role, ok := swarmapi.NodeRole_value[strings.ToUpper(r)]; ok {
+			f.Roles = append(f.Roles, swarmapi.NodeRole(role))
+		} else if r != "" {
+			return nil, fmt.Errorf("Invalid role filter: '%s'", r)
+		}
+	}
+
+	for _, a := range filter.Get("membership") {
+		if membership, ok := swarmapi.NodeSpec_Membership_value[strings.ToUpper(a)]; ok {
+			f.Memberships = append(f.Memberships, swarmapi.NodeSpec_Membership(membership))
+		} else if a != "" {
+			return nil, fmt.Errorf("Invalid membership filter: '%s'", a)
+		}
+	}
+
+	return f, nil
+}
+
+func newListServicesFilters(filter filters.Args) (*swarmapi.ListServicesRequest_Filters, error) {
+	accepted := map[string]bool{
+		"name":  true,
+		"id":    true,
+		"label": true,
+	}
+	if err := filter.Validate(accepted); err != nil {
+		return nil, err
+	}
+	return &swarmapi.ListServicesRequest_Filters{
+		Names:      filter.Get("name"),
+		IDPrefixes: filter.Get("id"),
+		Labels:     runconfigopts.ConvertKVStringsToMap(filter.Get("label")),
+	}, nil
+}
+
+func newListTasksFilters(filter filters.Args) (*swarmapi.ListTasksRequest_Filters, error) {
+	accepted := map[string]bool{
+		"name":          true,
+		"id":            true,
+		"label":         true,
+		"service":       true,
+		"node":          true,
+		"desired_state": true,
+	}
+	if err := filter.Validate(accepted); err != nil {
+		return nil, err
+	}
+	f := &swarmapi.ListTasksRequest_Filters{
+		Names:      filter.Get("name"),
+		IDPrefixes: filter.Get("id"),
+		Labels:     runconfigopts.ConvertKVStringsToMap(filter.Get("label")),
+		ServiceIDs: filter.Get("service"),
+		NodeIDs:    filter.Get("node"),
+	}
+
+	for _, s := range filter.Get("desired_state") {
+		if state, ok := swarmapi.TaskState_value[strings.ToUpper(s)]; ok {
+			f.DesiredStates = append(f.DesiredStates, swarmapi.TaskState(state))
+		} else if s != "" {
+			return nil, fmt.Errorf("Invalid desired_state filter: '%s'", s)
+		}
+	}
+
+	return f, nil
+}

+ 108 - 0
daemon/cluster/helpers.go

@@ -0,0 +1,108 @@
+package cluster
+
+import (
+	"fmt"
+
+	swarmapi "github.com/docker/swarmkit/api"
+	"golang.org/x/net/context"
+)
+
+func getSwarm(ctx context.Context, c swarmapi.ControlClient) (*swarmapi.Cluster, error) {
+	rl, err := c.ListClusters(ctx, &swarmapi.ListClustersRequest{})
+	if err != nil {
+		return nil, err
+	}
+
+	if len(rl.Clusters) == 0 {
+		return nil, fmt.Errorf("swarm not found")
+	}
+
+	// TODO: assume one cluster only
+	return rl.Clusters[0], nil
+}
+
+func getNode(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Node, error) {
+	// GetNode to match via full ID.
+	rg, err := c.GetNode(ctx, &swarmapi.GetNodeRequest{NodeID: input})
+	if err != nil {
+		// If any error (including NotFound), ListNodes to match via full name.
+		rl, err := c.ListNodes(ctx, &swarmapi.ListNodesRequest{Filters: &swarmapi.ListNodesRequest_Filters{Names: []string{input}}})
+
+		if err != nil || len(rl.Nodes) == 0 {
+			// If any error or 0 result, ListNodes to match via ID prefix.
+			rl, err = c.ListNodes(ctx, &swarmapi.ListNodesRequest{Filters: &swarmapi.ListNodesRequest_Filters{IDPrefixes: []string{input}}})
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		if len(rl.Nodes) == 0 {
+			return nil, fmt.Errorf("node %s not found", input)
+		}
+
+		if l := len(rl.Nodes); l > 1 {
+			return nil, fmt.Errorf("node %s is ambigious (%d matches found)", input, l)
+		}
+
+		return rl.Nodes[0], nil
+	}
+	return rg.Node, nil
+}
+
+func getService(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Service, error) {
+	// GetService to match via full ID.
+	rg, err := c.GetService(ctx, &swarmapi.GetServiceRequest{ServiceID: input})
+	if err != nil {
+		// If any error (including NotFound), ListServices to match via full name.
+		rl, err := c.ListServices(ctx, &swarmapi.ListServicesRequest{Filters: &swarmapi.ListServicesRequest_Filters{Names: []string{input}}})
+		if err != nil || len(rl.Services) == 0 {
+			// If any error or 0 result, ListServices to match via ID prefix.
+			rl, err = c.ListServices(ctx, &swarmapi.ListServicesRequest{Filters: &swarmapi.ListServicesRequest_Filters{IDPrefixes: []string{input}}})
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		if len(rl.Services) == 0 {
+			return nil, fmt.Errorf("service %s not found", input)
+		}
+
+		if l := len(rl.Services); l > 1 {
+			return nil, fmt.Errorf("service %s is ambigious (%d matches found)", input, l)
+		}
+
+		return rl.Services[0], nil
+	}
+	return rg.Service, nil
+}
+
+func getTask(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Task, error) {
+	// GetTask to match via full ID.
+	rg, err := c.GetTask(ctx, &swarmapi.GetTaskRequest{TaskID: input})
+	if err != nil {
+		// If any error (including NotFound), ListTasks to match via full name.
+		rl, err := c.ListTasks(ctx, &swarmapi.ListTasksRequest{Filters: &swarmapi.ListTasksRequest_Filters{Names: []string{input}}})
+
+		if err != nil || len(rl.Tasks) == 0 {
+			// If any error or 0 result, ListTasks to match via ID prefix.
+			rl, err = c.ListTasks(ctx, &swarmapi.ListTasksRequest{Filters: &swarmapi.ListTasksRequest_Filters{IDPrefixes: []string{input}}})
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		if len(rl.Tasks) == 0 {
+			return nil, fmt.Errorf("task %s not found", input)
+		}
+
+		if l := len(rl.Tasks); l > 1 {
+			return nil, fmt.Errorf("task %s is ambigious (%d matches found)", input, l)
+		}
+
+		return rl.Tasks[0], nil
+	}
+	return rg.Task, nil
+}

+ 36 - 0
daemon/cluster/provider/network.go

@@ -0,0 +1,36 @@
+package provider
+
+import "github.com/docker/engine-api/types"
+
+// NetworkCreateRequest is a request when creating a network.
+type NetworkCreateRequest struct {
+	ID string
+	types.NetworkCreateRequest
+}
+
+// NetworkCreateResponse is a response when creating a network.
+type NetworkCreateResponse struct {
+	ID string `json:"Id"`
+}
+
+// VirtualAddress represents a virtual adress.
+type VirtualAddress struct {
+	IPv4 string
+	IPv6 string
+}
+
+// PortConfig represents a port configuration.
+type PortConfig struct {
+	Name          string
+	Protocol      int32
+	TargetPort    uint32
+	PublishedPort uint32
+}
+
+// ServiceConfig represents a service configuration.
+type ServiceConfig struct {
+	ID               string
+	Name             string
+	VirtualAddresses map[string]*VirtualAddress
+	ExposedPorts     []*PortConfig
+}

+ 2 - 1
daemon/container.go

@@ -101,7 +101,7 @@ func (daemon *Daemon) Register(c *container.Container) error {
 	return nil
 }
 
-func (daemon *Daemon) newContainer(name string, config *containertypes.Config, imgID image.ID) (*container.Container, error) {
+func (daemon *Daemon) newContainer(name string, config *containertypes.Config, imgID image.ID, managed bool) (*container.Container, error) {
 	var (
 		id             string
 		err            error
@@ -117,6 +117,7 @@ func (daemon *Daemon) newContainer(name string, config *containertypes.Config, i
 
 	base := daemon.newBaseContainer(id)
 	base.Created = time.Now().UTC()
+	base.Managed = managed
 	base.Path = entrypoint
 	base.Args = args //FIXME: de-duplicate from config
 	base.Config = config

+ 7 - 0
daemon/container_operations.go

@@ -324,6 +324,10 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
 	return nil
 }
 
+func errClusterNetworkOnRun(n string) error {
+	return fmt.Errorf("swarm-scoped network (%s) is not compatible with `docker create` or `docker run`. This network can be only used docker service", n)
+}
+
 // updateContainerNetworkSettings update the network settings
 func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
 	var (
@@ -345,6 +349,9 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
 		if err != nil {
 			return err
 		}
+		if !container.Managed && n.Info().Dynamic() {
+			return errClusterNetworkOnRun(networkName)
+		}
 		networkName = n.Name()
 	}
 	if container.NetworkSettings == nil {

+ 13 - 4
daemon/create.go

@@ -19,8 +19,17 @@ import (
 	"github.com/opencontainers/runc/libcontainer/label"
 )
 
-// ContainerCreate creates a container.
+// CreateManagedContainer creates a container that is managed by a Service
+func (daemon *Daemon) CreateManagedContainer(params types.ContainerCreateConfig) (types.ContainerCreateResponse, error) {
+	return daemon.containerCreate(params, true)
+}
+
+// ContainerCreate creates a regular container
 func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (types.ContainerCreateResponse, error) {
+	return daemon.containerCreate(params, false)
+}
+
+func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (types.ContainerCreateResponse, error) {
 	if params.Config == nil {
 		return types.ContainerCreateResponse{}, fmt.Errorf("Config cannot be empty in order to create a container")
 	}
@@ -43,7 +52,7 @@ func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (types
 		return types.ContainerCreateResponse{Warnings: warnings}, err
 	}
 
-	container, err := daemon.create(params)
+	container, err := daemon.create(params, managed)
 	if err != nil {
 		return types.ContainerCreateResponse{Warnings: warnings}, daemon.imageNotExistToErrcode(err)
 	}
@@ -52,7 +61,7 @@ func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (types
 }
 
 // Create creates a new container from the given configuration with a given name.
-func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *container.Container, retErr error) {
+func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) {
 	var (
 		container *container.Container
 		img       *image.Image
@@ -76,7 +85,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe
 		return nil, err
 	}
 
-	if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil {
+	if container, err = daemon.newContainer(params.Name, params.Config, imgID, managed); err != nil {
 		return nil, err
 	}
 	defer func() {

+ 12 - 0
daemon/daemon.go

@@ -28,6 +28,7 @@ import (
 	"github.com/docker/docker/daemon/exec"
 	"github.com/docker/engine-api/types"
 	containertypes "github.com/docker/engine-api/types/container"
+	"github.com/docker/libnetwork/cluster"
 	// register graph drivers
 	_ "github.com/docker/docker/daemon/graphdriver/register"
 	dmetadata "github.com/docker/docker/distribution/metadata"
@@ -94,6 +95,7 @@ type Daemon struct {
 	containerd                libcontainerd.Client
 	containerdRemote          libcontainerd.Remote
 	defaultIsolation          containertypes.Isolation // Default isolation mode on Windows
+	clusterProvider           cluster.Provider
 }
 
 func (daemon *Daemon) restore() error {
@@ -344,6 +346,12 @@ func (daemon *Daemon) registerLink(parent, child *container.Container, alias str
 	return nil
 }
 
+// SetClusterProvider sets a component for quering the current cluster state.
+func (daemon *Daemon) SetClusterProvider(clusterProvider cluster.Provider) {
+	daemon.clusterProvider = clusterProvider
+	daemon.netController.SetClusterProvider(clusterProvider)
+}
+
 // NewDaemon sets up everything for the daemon to be able to service
 // requests from the webserver.
 func NewDaemon(config *Config, registryService registry.Service, containerdRemote libcontainerd.Remote) (daemon *Daemon, err error) {
@@ -893,6 +901,10 @@ func (daemon *Daemon) reloadClusterDiscovery(config *Config) error {
 		return nil
 	}
 
+	if daemon.clusterProvider != nil {
+		return fmt.Errorf("--cluster-store and --cluster-advertise daemon configurations are incompatible with swarm mode")
+	}
+
 	// enable discovery for the first time if it was not previously enabled
 	if daemon.discoveryWatcher == nil {
 		discoveryWatcher, err := initDiscovery(newClusterStore, newAdvertise, config.ClusterOpts)

+ 4 - 2
daemon/inspect.go

@@ -23,10 +23,12 @@ func (daemon *Daemon) ContainerInspect(name string, size bool, version string) (
 	case versions.Equal(version, "1.20"):
 		return daemon.containerInspect120(name)
 	}
-	return daemon.containerInspectCurrent(name, size)
+	return daemon.ContainerInspectCurrent(name, size)
 }
 
-func (daemon *Daemon) containerInspectCurrent(name string, size bool) (*types.ContainerJSON, error) {
+// ContainerInspectCurrent returns low-level information about a
+// container in a most recent api version.
+func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error) {
 	container, err := daemon.GetContainer(name)
 	if err != nil {
 		return nil, err

+ 1 - 1
daemon/inspect_windows.go

@@ -28,7 +28,7 @@ func addMountPoints(container *container.Container) []types.MountPoint {
 
 // containerInspectPre120 get containers for pre 1.20 APIs.
 func (daemon *Daemon) containerInspectPre120(name string) (*types.ContainerJSON, error) {
-	return daemon.containerInspectCurrent(name, false)
+	return daemon.ContainerInspectCurrent(name, false)
 }
 
 func inspectExecProcessConfig(e *exec.Config) *backend.ExecProcessConfig {

+ 11 - 0
daemon/list.go

@@ -91,6 +91,17 @@ func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.C
 	return daemon.reduceContainers(config, daemon.transformContainer)
 }
 
+// ListContainersForNode returns all containerID that match the specified nodeID
+func (daemon *Daemon) ListContainersForNode(nodeID string) []string {
+	var ids []string
+	for _, c := range daemon.List() {
+		if c.Config.Labels["com.docker.swarm.node.id"] == nodeID {
+			ids = append(ids, c.ID)
+		}
+	}
+	return ids
+}
+
 func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Container {
 	idSearch := false
 	names := ctx.filters.Get("name")

+ 137 - 15
daemon/network.go

@@ -5,13 +5,14 @@ import (
 	"net"
 	"strings"
 
-	netsettings "github.com/docker/docker/daemon/network"
+	"github.com/Sirupsen/logrus"
+	clustertypes "github.com/docker/docker/daemon/cluster/provider"
 	"github.com/docker/docker/errors"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/engine-api/types"
-	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/network"
 	"github.com/docker/libnetwork"
+	networktypes "github.com/docker/libnetwork/types"
 )
 
 // NetworkControllerEnabled checks if the networking stack is enabled.
@@ -92,9 +93,106 @@ func (daemon *Daemon) getAllNetworks() []libnetwork.Network {
 	return list
 }
 
+func isIngressNetwork(name string) bool {
+	return name == "ingress"
+}
+
+var ingressChan = make(chan struct{}, 1)
+
+func ingressWait() func() {
+	ingressChan <- struct{}{}
+	return func() { <-ingressChan }
+}
+
+// SetupIngress setups ingress networking.
+func (daemon *Daemon) SetupIngress(create clustertypes.NetworkCreateRequest, nodeIP string) error {
+	ip, _, err := net.ParseCIDR(nodeIP)
+	if err != nil {
+		return err
+	}
+
+	go func() {
+		controller := daemon.netController
+		controller.AgentInitWait()
+
+		if n, err := daemon.GetNetworkByName(create.Name); err == nil && n != nil && n.ID() != create.ID {
+			if err := controller.SandboxDestroy("ingress-sbox"); err != nil {
+				logrus.Errorf("Failed to delete stale ingress sandbox: %v", err)
+				return
+			}
+
+			if err := n.Delete(); err != nil {
+				logrus.Errorf("Failed to delete stale ingress network %s: %v", n.ID(), err)
+				return
+			}
+		}
+
+		if _, err := daemon.createNetwork(create.NetworkCreateRequest, create.ID, true); err != nil {
+			// If it is any other error other than already
+			// exists error log error and return.
+			if _, ok := err.(libnetwork.NetworkNameError); !ok {
+				logrus.Errorf("Failed creating ingress network: %v", err)
+				return
+			}
+
+			// Otherwise continue down the call to create or recreate sandbox.
+		}
+
+		n, err := daemon.GetNetworkByID(create.ID)
+		if err != nil {
+			logrus.Errorf("Failed getting ingress network by id after creating: %v", err)
+			return
+		}
+
+		sb, err := controller.NewSandbox("ingress-sbox", libnetwork.OptionIngress())
+		if err != nil {
+			logrus.Errorf("Failed creating ingress sanbox: %v", err)
+			return
+		}
+
+		ep, err := n.CreateEndpoint("ingress-endpoint", libnetwork.CreateOptionIpam(ip, nil, nil, nil))
+		if err != nil {
+			logrus.Errorf("Failed creating ingress endpoint: %v", err)
+			return
+		}
+
+		if err := ep.Join(sb, nil); err != nil {
+			logrus.Errorf("Failed joining ingress sandbox to ingress endpoint: %v", err)
+		}
+	}()
+
+	return nil
+}
+
+// SetNetworkBootstrapKeys sets the bootstrap keys.
+func (daemon *Daemon) SetNetworkBootstrapKeys(keys []*networktypes.EncryptionKey) error {
+	return daemon.netController.SetKeys(keys)
+}
+
+// CreateManagedNetwork creates an agent network.
+func (daemon *Daemon) CreateManagedNetwork(create clustertypes.NetworkCreateRequest) error {
+	_, err := daemon.createNetwork(create.NetworkCreateRequest, create.ID, true)
+	return err
+}
+
 // CreateNetwork creates a network with the given name, driver and other optional parameters
 func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.NetworkCreateResponse, error) {
-	if runconfig.IsPreDefinedNetwork(create.Name) {
+	resp, err := daemon.createNetwork(create, "", false)
+	if err != nil {
+		return nil, err
+	}
+	return resp, err
+}
+
+func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) {
+	// If there is a pending ingress network creation wait here
+	// since ingress network creation can happen via node download
+	// from manager or task download.
+	if isIngressNetwork(create.Name) {
+		defer ingressWait()()
+	}
+
+	if runconfig.IsPreDefinedNetwork(create.Name) && !agent {
 		err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
 		return nil, errors.NewRequestForbiddenError(err)
 	}
@@ -134,7 +232,16 @@ func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.N
 	if create.Internal {
 		nwOptions = append(nwOptions, libnetwork.NetworkOptionInternalNetwork())
 	}
-	n, err := c.NewNetwork(driver, create.Name, "", nwOptions...)
+	if agent {
+		nwOptions = append(nwOptions, libnetwork.NetworkOptionDynamic())
+		nwOptions = append(nwOptions, libnetwork.NetworkOptionPersist(false))
+	}
+
+	if isIngressNetwork(create.Name) {
+		nwOptions = append(nwOptions, libnetwork.NetworkOptionIngress())
+	}
+
+	n, err := c.NewNetwork(driver, create.Name, id, nwOptions...)
 	if err != nil {
 		return nil, err
 	}
@@ -168,6 +275,17 @@ func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnet
 	return ipamV4Cfg, ipamV6Cfg, nil
 }
 
+// UpdateContainerServiceConfig updates a service configuration.
+func (daemon *Daemon) UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error {
+	container, err := daemon.GetContainer(containerName)
+	if err != nil {
+		return err
+	}
+
+	container.NetworkSettings.Service = serviceConfig
+	return nil
+}
+
 // ConnectContainerToNetwork connects the given container to the given
 // network. If either cannot be found, an err is returned. If the
 // network cannot be set up, an err is returned.
@@ -207,18 +325,29 @@ func (daemon *Daemon) GetNetworkDriverList() map[string]bool {
 		driver := network.Type()
 		pluginList[driver] = true
 	}
+	// TODO : Replace this with proper libnetwork API
+	pluginList["overlay"] = true
 
 	return pluginList
 }
 
+// DeleteManagedNetwork deletes an agent network.
+func (daemon *Daemon) DeleteManagedNetwork(networkID string) error {
+	return daemon.deleteNetwork(networkID, true)
+}
+
 // DeleteNetwork destroys a network unless it's one of docker's predefined networks.
 func (daemon *Daemon) DeleteNetwork(networkID string) error {
+	return daemon.deleteNetwork(networkID, false)
+}
+
+func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
 	nw, err := daemon.FindNetwork(networkID)
 	if err != nil {
 		return err
 	}
 
-	if runconfig.IsPreDefinedNetwork(nw.Name()) {
+	if runconfig.IsPreDefinedNetwork(nw.Name()) && !dynamic {
 		err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
 		return errors.NewRequestForbiddenError(err)
 	}
@@ -230,14 +359,7 @@ func (daemon *Daemon) DeleteNetwork(networkID string) error {
 	return nil
 }
 
-// FilterNetworks returns a list of networks filtered by the given arguments.
-// It returns an error if the filters are not included in the list of accepted filters.
-func (daemon *Daemon) FilterNetworks(netFilters filters.Args) ([]libnetwork.Network, error) {
-	if netFilters.Len() != 0 {
-		if err := netFilters.Validate(netsettings.AcceptedFilters); err != nil {
-			return nil, err
-		}
-	}
-	nwList := daemon.getAllNetworks()
-	return netsettings.FilterNetworks(nwList, netFilters)
+// GetNetworks returns a list of all networks
+func (daemon *Daemon) GetNetworks() []libnetwork.Network {
+	return daemon.getAllNetworks()
 }

+ 2 - 0
daemon/network/settings.go

@@ -1,6 +1,7 @@
 package network
 
 import (
+	clustertypes "github.com/docker/docker/daemon/cluster/provider"
 	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/go-connections/nat"
 )
@@ -14,6 +15,7 @@ type Settings struct {
 	LinkLocalIPv6Address   string
 	LinkLocalIPv6PrefixLen int
 	Networks               map[string]*networktypes.EndpointSettings
+	Service                *clustertypes.ServiceConfig
 	Ports                  nat.PortMap
 	SandboxKey             string
 	SecondaryIPAddresses   []networktypes.Address

+ 16 - 1
daemon/wait.go

@@ -1,6 +1,10 @@
 package daemon
 
-import "time"
+import (
+	"time"
+
+	"golang.org/x/net/context"
+)
 
 // ContainerWait stops processing until the given container is
 // stopped. If the container is not found, an error is returned. On a
@@ -15,3 +19,14 @@ func (daemon *Daemon) ContainerWait(name string, timeout time.Duration) (int, er
 
 	return container.WaitStop(timeout)
 }
+
+// ContainerWaitWithContext returns a channel where exit code is sent
+// when container stops. Channel can be cancelled with a context.
+func (daemon *Daemon) ContainerWaitWithContext(ctx context.Context, name string) (<-chan int, error) {
+	container, err := daemon.GetContainer(name)
+	if err != nil {
+		return nil, err
+	}
+
+	return container.WaitWithContext(ctx), nil
+}

+ 1113 - 1
docs/reference/api/docker_remote_api_v1.24.md

@@ -492,7 +492,6 @@ Status Codes:
 
 Return low-level information on the container `id`
 
-
 **Example request**:
 
       GET /containers/4fa6e0f0c678/json HTTP/1.1
@@ -3306,6 +3305,1119 @@ Status Codes
 -   **404** - no such network
 -   **500** - server error
 
+## 3.6 Nodes
+
+**Note:** Nodes operations require to first be part of a Swarm.
+
+### List nodes
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`GET /nodes`
+
+List nodes
+
+**Example request**:
+
+    GET /nodes HTTP/1.1
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Type: application/json
+
+    [
+      {
+        "ID": "24ifsmvkjbyhk",
+        "Version": {
+          "Index": 8
+        },
+        "CreatedAt": "2016-06-07T20:31:11.853781916Z",
+        "UpdatedAt": "2016-06-07T20:31:11.999868824Z",
+        "Spec": {
+          "Role": "MANAGER",
+          "Membership": "ACCEPTED",
+          "Availability": "ACTIVE"
+        },
+        "Description": {
+          "Hostname": "bf3067039e47",
+          "Platform": {
+            "Architecture": "x86_64",
+            "OS": "linux"
+          },
+          "Resources": {
+            "NanoCPUs": 4000000000,
+            "MemoryBytes": 8272408576
+          },
+          "Engine": {
+            "EngineVersion": "1.12.0-dev",
+            "Plugins": [
+              {
+                "Type": "Volume",
+                "Name": "local"
+              },
+              {
+                "Type": "Network",
+                "Name": "overlay"
+              }
+            ]
+          }
+        },
+        "Status": {
+          "State": "READY"
+        },
+        "Manager": {
+          "Raft": {
+            "RaftID": 10070664527094528000,
+            "Addr": "172.17.0.2:4500",
+            "Status": {
+              "Leader": true,
+              "Reachability": "REACHABLE"
+            }
+          }
+        },
+        "Attachment": {
+          "Network": {
+            "ID": "4qvuz4ko70xaltuqbt8956gd1",
+            "Version": {
+              "Index": 6
+            },
+            "CreatedAt": "2016-06-07T20:31:11.912919752Z",
+            "UpdatedAt": "2016-06-07T20:31:11.921784144Z",
+            "Spec": {
+              "Name": "ingress",
+              "Labels": {
+                "com.docker.swarm.internal": "true"
+              },
+              "DriverConfiguration": {},
+              "IPAM": {
+                "Driver": {},
+                "Configs": [
+                  {
+                    "Family": "UNKNOWN",
+                    "Subnet": "10.255.0.0/16"
+                  }
+                ]
+              }
+            },
+            "DriverState": {
+              "Name": "overlay",
+              "Options": {
+                "com.docker.network.driver.overlay.vxlanid_list": "256"
+              }
+            },
+            "IPAM": {
+              "Driver": {
+                "Name": "default"
+              },
+              "Configs": [
+                {
+                  "Family": "UNKNOWN",
+                  "Subnet": "10.255.0.0/16"
+                }
+              ]
+            }
+          },
+          "Addresses": [
+            "10.255.0.2/16"
+          ]
+        }
+      }
+    ]
+
+Query Parameters:
+
+- **filters** – a JSON encoded value of the filters (a `map[string][]string`) to process on the
+  nodes list. Available filters:
+  - `id=<node id>`
+  - `name=<node name>`
+  - `membership=`(`pending`|`accepted`|`rejected`)`
+  - `role=`(`worker`|`manager`)`
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Inspect a node
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`GET /nodes/<id>`
+
+Return low-level information on the node `id`
+
+**Example request**:
+
+      GET /node/24ifsmvkjbyhk HTTP/1.1
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Type: application/json
+
+    {
+      "ID": "24ifsmvkjbyhk",
+      "Version": {
+        "Index": 8
+      },
+      "CreatedAt": "2016-06-07T20:31:11.853781916Z",
+      "UpdatedAt": "2016-06-07T20:31:11.999868824Z",
+      "Spec": {
+        "Role": "MANAGER",
+        "Membership": "ACCEPTED",
+        "Availability": "ACTIVE"
+      },
+      "Description": {
+        "Hostname": "bf3067039e47",
+        "Platform": {
+          "Architecture": "x86_64",
+          "OS": "linux"
+        },
+        "Resources": {
+          "NanoCPUs": 4000000000,
+          "MemoryBytes": 8272408576
+        },
+        "Engine": {
+          "EngineVersion": "1.12.0-dev",
+          "Plugins": [
+            {
+              "Type": "Volume",
+              "Name": "local"
+            },
+            {
+              "Type": "Network",
+              "Name": "overlay"
+            }
+          ]
+        }
+      },
+      "Status": {
+        "State": "READY"
+      },
+      "Manager": {
+        "Raft": {
+          "RaftID": 10070664527094528000,
+          "Addr": "172.17.0.2:4500",
+          "Status": {
+            "Leader": true,
+            "Reachability": "REACHABLE"
+          }
+        }
+      },
+      "Attachment": {
+        "Network": {
+          "ID": "4qvuz4ko70xaltuqbt8956gd1",
+          "Version": {
+            "Index": 6
+          },
+          "CreatedAt": "2016-06-07T20:31:11.912919752Z",
+          "UpdatedAt": "2016-06-07T20:31:11.921784144Z",
+          "Spec": {
+            "Name": "ingress",
+            "Labels": {
+              "com.docker.swarm.internal": "true"
+            },
+            "DriverConfiguration": {},
+            "IPAM": {
+              "Driver": {},
+              "Configs": [
+                {
+                  "Family": "UNKNOWN",
+                  "Subnet": "10.255.0.0/16"
+                }
+              ]
+            }
+          },
+          "DriverState": {
+            "Name": "overlay",
+            "Options": {
+              "com.docker.network.driver.overlay.vxlanid_list": "256"
+            }
+          },
+          "IPAM": {
+            "Driver": {
+              "Name": "default"
+            },
+            "Configs": [
+              {
+                "Family": "UNKNOWN",
+                "Subnet": "10.255.0.0/16"
+              }
+            ]
+          }
+        },
+        "Addresses": [
+          "10.255.0.2/16"
+        ]
+      }
+    }
+
+Status Codes:
+
+-   **200** – no error
+-   **404** – no such node
+-   **500** – server error
+
+## 3.7 Swarm
+
+### Initialize a new Swarm
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`POST /swarm/init`
+
+Initialize a new Swarm
+
+**Example request**:
+
+    POST /swarm/init HTTP/1.1
+    Content-Type: application/json
+
+    {
+      "ListenAddr": "0.0.0.0:4500",
+      "ForceNewCluster": false,
+      "Spec": {
+        "AcceptancePolicy": {
+          "Policies": [
+            {
+              "Role": "MANAGER",
+              "Autoaccept": false
+            },
+            {
+              "Role": "WORKER",
+              "Autoaccept": true
+            }
+          ]
+        },
+        "Orchestration": {},
+        "Raft": {},
+        "Dispatcher": {},
+        "CAConfig": {}
+      }
+    }
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Length: 0
+    Content-Type: text/plain; charset=utf-8
+
+Status Codes:
+
+- **200** – no error
+- **400** – bad parameter
+- **500** – server error or node is already part of a Swarm
+
+JSON Parameters:
+
+- **ListenAddr** – Listen address used for inter-manager communication, as well as determining.
+  the networking interface used for the VXLAN Tunnel Endpoint (VTEP).
+- **ForceNewCluster** – Force creating a new Swarm even if already part of one.
+- **Spec** – Configuration settings of the new Swarm.
+    - **Policies** – An array of acceptance policies.
+        - **Role** – The role that policy applies to (`MANAGER` or `WORKER`)
+        - **Autoaccept** – A boolean indicating whether nodes joining for that role should be
+          automatically accepted in the Swarm.
+        - **Secret** – An optional secret to provide for nodes to join the Swarm.
+    - **Orchestration** – Configuration settings for the orchestration aspects of the Swarm.
+        - **TaskHistoryRetentionLimit** – Maximum number of tasks history stored.
+    - **RaftConfig** – Raft related configuration.
+        - **SnapshotInterval** – (TODO)
+        - **KeepOldSnapshots** – (TODO)
+        - **LogEntriesForSlowFollowers** – (TODO)
+        - **HeartbeatTick** – (TODO)
+        - **ElectionTick** – (TODO)
+    - **DispatcherConfig** – Configuration settings for the task dispatcher.
+        - **HeartbeatPeriod** – (TODO)
+    - **CAConfig** – CA configuration.
+        - **NodeCertExpiry** – Automatic expiry for nodes certificates.
+
+### Join an existing Swarm
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`POST /swarm/join`
+
+Join an existing new Swarm
+
+**Example request**:
+
+    POST /swarm/join HTTP/1.1
+    Content-Type: application/json
+
+    {
+      "ListenAddr": "0.0.0.0:4500",
+      "RemoteAddr": "node1:4500",
+      "Secret": "",
+      "CAHash": "",
+      "Manager": false
+    }
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Length: 0
+    Content-Type: text/plain; charset=utf-8
+
+Status Codes:
+
+- **200** – no error
+- **400** – bad parameter
+- **500** – server error or node is already part of a Swarm
+
+JSON Parameters:
+
+- **ListenAddr** – Listen address used for inter-manager communication if the node gets promoted to
+  manager, as well as determining the networking interface used for the VXLAN Tunnel Endpoint (VTEP).
+- **RemoteAddr** – Address of any manager node already participating in the Swarm to join.
+- **Secret** – Secret token for joining this Swarm.
+- **CAHash** – Optional hash of the root CA to avoid relying on trust on first use.
+- **Manager** – Directly join as a manager (only for a Swarm configured to autoaccept managers).
+
+### Leave a Swarm
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`POST /swarm/leave`
+
+Leave a Swarm
+
+**Example request**:
+
+    POST /swarm/leave HTTP/1.1
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Length: 0
+    Content-Type: text/plain; charset=utf-8
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error or node is not part of a Swarm
+
+### Update a Swarm
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`POST /swarm/update`
+
+Update a Swarm
+
+**Example request**:
+
+    POST /swarm/update HTTP/1.1
+
+    (TODO)
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Length: 0
+    Content-Type: text/plain; charset=utf-8
+
+    (TODO)
+
+Status Codes:
+
+- **200** – no error
+- **400** – bad parameter
+- **500** – server error or node is not part of a Swarm
+
+## 3.8 Services
+
+**Note:** Service operations require to first be part of a Swarm.
+
+### List services
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`GET /services`
+
+List services
+
+**Example request**:
+
+    GET /services HTTP/1.1
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Type: application/json
+
+    [
+      {
+        "ID": "9mnpnzenvg8p8tdbtq4wvbkcz",
+        "Version": {
+          "Index": 19
+        },
+        "CreatedAt": "2016-06-07T21:05:51.880065305Z",
+        "UpdatedAt": "2016-06-07T21:07:29.962229872Z",
+        "Spec": {
+          "Name": "hopeful_cori",
+          "Task": {
+            "ContainerSpec": {
+              "Image": "redis"
+            },
+            "Resources": {
+              "Limits": {},
+              "Reservations": {}
+            },
+            "RestartPolicy": {
+              "Condition": "ANY"
+            },
+            "Placement": {}
+          },
+          "Mode": {
+            "Replicated": {
+              "Instances": 1
+            }
+          },
+          "UpdateConfig": {
+            "Parallelism": 1
+          },
+          "EndpointSpec": {
+            "Mode": "VIP",
+            "Ingress": "PUBLICPORT",
+            "ExposedPorts": [
+              {
+                "Protocol": "tcp",
+                "Port": 6379
+              }
+            ]
+          }
+        },
+        "Endpoint": {
+          "Spec": {},
+          "ExposedPorts": [
+            {
+              "Protocol": "tcp",
+              "Port": 6379,
+              "PublicPort": 30000
+            }
+          ],
+          "VirtualIPs": [
+            {
+              "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Addr": "10.255.0.2/16"
+            },
+            {
+              "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Addr": "10.255.0.3/16"
+            }
+          ]
+        }
+      }
+    ]
+
+Query Parameters:
+
+- **filters** – a JSON encoded value of the filters (a `map[string][]string`) to process on the
+  services list. Available filters:
+  - `id=<node id>`
+  - `name=<node name>`
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Create a service
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`POST /services/create`
+
+Create a service
+
+**Example request**:
+
+    POST /service/create HTTP/1.1
+    Content-Type: application/json
+
+    {
+      "Name": "redis",
+      "Task": {
+        "ContainerSpec": {
+          "Image": "redis"
+        },
+        "Resources": {
+          "Limits": {},
+          "Reservations": {}
+        },
+        "RestartPolicy": {},
+        "Placement": {}
+      },
+      "Mode": {
+        "Replicated": {
+          "Instances": 1
+        }
+      },
+      "UpdateConfig": {
+        "Parallelism": 1
+      },
+      "EndpointSpec": {
+        "ExposedPorts": [
+          {
+            "Protocol": "tcp",
+            "Port": 6379
+          }
+        ]
+      }
+    }
+
+**Example response**:
+
+    HTTP/1.1 201 Created
+    Content-Type: application/json
+
+    {
+      "Id":"ak7w3gjqoa3kuz8xcpnyy0pvl"
+    }
+
+Status Codes:
+
+- **201** – no error
+- **500** – server error or node is not part of a Swarm
+
+JSON Parameters:
+
+- **Annotations** – Optional medata to associate with the service.
+    - **Name** – User-defined name for the service.
+    - **Labels** – A map of labels to associate with the service (e.g.,
+      `{"key":"value"[,"key2":"value2"]}`).
+- **Task** – Specification of the tasks to start as part of the new service.
+    - **ContainerSpec** - Container settings for containers started as part of this task.
+        - **Image** – A string specifying the image name to use for the container.
+        - **Command** – The command to be run in the image.
+        - **Args** – Arguments to the command.
+        - **Env** – A list of environment variables in the form of `["VAR=value"[,"VAR2=value2"]]`.
+        - **Dir** – A string specifying the working directory for commands to run in.
+        - **User** – A string value specifying the user inside the container.
+        - **Labels** – A map of labels to associate with the service (e.g.,
+          `{"key":"value"[,"key2":"value2"]}`).
+        - **Mounts** – Specification for mounts to be added to containers created as part of the new
+          service.
+            - **Target** – Container path.
+            - **Source** – Optional host path to be mounted in the target.
+            - **Type** – The mount type (`bind`, `epheremal`, or `volume`).
+            - **VolumeName** – A name for the volume.
+            - **Populate** – A boolean indicating if volume should be populated with the data form the
+              target (defaults to false).
+            - **Propagation** – A propagation mode with the value `[r]private`, `[r]shared`, or
+              `[r]slave` (`bind` type mounts only).
+            - **MCSAccessMode** – MCS label for sharing mode (`bind` type mounts only).
+            - **Writable** – A boolean indicating whether the mount should be writable.
+            - **VolumeTemplate** – Optional configuration for the volume.
+                - **Annotations** – User-defined name and labels for the volume.
+                - **Driver** – Name of the driver to be used and driver-specific options.
+        - **StopGracePeriod** – Amount of time to wait for the container to terminate before
+          forcefully killing it.
+    - **Resources** – Resource requirements which apply to each individual container created as part
+      of the service.
+        - **Limits** – Define resources limits.
+            - **CPU** – CPU limit
+            - **Memory** – Memory limit
+        - **Reservation** – Define resources reservation.
+            - **CPU** – CPU reservation
+            - **Memory** – Memory reservation
+    - **RestartPolicy** – Specification for the restart policy which applies to containers created
+      as part of this service.
+        - **Condition** – Condition for restart (`none`, `on_failure`, or `any`).
+        - **Delay** – Delay between restart attempts.
+        - **Attempts** – Maximum attempts to restart a given container before giving up (default value
+          is 0, which is ignored).
+        - **Window** – Windows is the time window used to evaluate the restart policy (default value is
+          0, which is unbounded).
+    - **Placement** – An array of constraints.
+- **Mode** – Scheduling mode for the service (`replicated` or `global`, defaults to `replicated`).
+- **UpdateConfig** – Specification for the update strategy of the service.
+    - **Parallelism** – Maximum number of tasks to be updated in one iteration (0 means unlimited
+      parallelism).
+    - **Delay** – Amount of time between updates.
+- **Networks** – Array of network names or IDs to attach the service to.
+- **EndpointSpec** – (TODO)
+    - **EndpointSpecStrategy** – `network` or `disabled` (TODO)
+    - **ExposedPorts** – An object mapping ports to an empty object in the form of:
+      `"ExposedPorts": { "<port>/<tcp|udp>: {}" }`
+
+### Remove a service
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`DELETE /service/(id or name)`
+
+Stop and remove the service `id`
+
+**Example request**:
+
+    DELETE /service/16253994b7c4 HTTP/1.1
+
+**Example response**:
+
+    HTTP/1.1 204 No Content
+
+Status Codes:
+
+-   **204** – no error
+-   **404** – no such service
+-   **500** – server error
+
+### Inspect a service
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`GET /service/(id or name)`
+
+Return information on the service `id`.
+
+**Example request**:
+
+    GET /service/1cb4dnqcyx6m66g2t538x3rxha HTTP/1.1
+
+**Example response**:
+
+    {
+      "ID": "ak7w3gjqoa3kuz8xcpnyy0pvl",
+      "Version": {
+        "Index": 95
+      },
+      "CreatedAt": "2016-06-07T21:10:20.269723157Z",
+      "UpdatedAt": "2016-06-07T21:10:20.276301259Z",
+      "Spec": {
+        "Name": "redis",
+        "Task": {
+          "ContainerSpec": {
+            "Image": "redis"
+          },
+          "Resources": {
+            "Limits": {},
+            "Reservations": {}
+          },
+          "RestartPolicy": {
+            "Condition": "ANY"
+          },
+          "Placement": {}
+        },
+        "Mode": {
+          "Replicated": {
+            "Instances": 1
+          }
+        },
+        "UpdateConfig": {
+          "Parallelism": 1
+        },
+        "EndpointSpec": {
+          "Mode": "VIP",
+          "Ingress": "PUBLICPORT",
+          "ExposedPorts": [
+            {
+              "Protocol": "tcp",
+              "Port": 6379
+            }
+          ]
+        }
+      },
+      "Endpoint": {
+        "Spec": {},
+        "ExposedPorts": [
+          {
+            "Protocol": "tcp",
+            "Port": 6379,
+            "PublicPort": 30001
+          }
+        ],
+        "VirtualIPs": [
+          {
+            "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+            "Addr": "10.255.0.4/16"
+          }
+        ]
+      }
+    }
+
+Status Codes:
+
+-   **200** – no error
+-   **404** – no such service
+-   **500** – server error
+
+### Update a service
+
+(TODO)
+
+## 3.9 Tasks
+
+**Note:** Tasks operations require to first be part of a Swarm.
+
+### List tasks
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`GET /tasks`
+
+List tasks
+
+**Example request**:
+
+    GET /tasks HTTP/1.1
+
+**Example response**:
+
+    [
+      {
+        "ID": "0kzzo1i0y4jz6027t0k7aezc7",
+        "Version": {
+          "Index": 71
+        },
+        "CreatedAt": "2016-06-07T21:07:31.171892745Z",
+        "UpdatedAt": "2016-06-07T21:07:31.376370513Z",
+        "Name": "hopeful_cori",
+        "Spec": {
+          "ContainerSpec": {
+            "Image": "redis"
+          },
+          "Resources": {
+            "Limits": {},
+            "Reservations": {}
+          },
+          "RestartPolicy": {
+            "Condition": "ANY"
+          },
+          "Placement": {}
+        },
+        "ServiceID": "9mnpnzenvg8p8tdbtq4wvbkcz",
+        "Instance": 1,
+        "NodeID": "24ifsmvkjbyhk",
+        "ServiceAnnotations": {},
+        "Status": {
+          "Timestamp": "2016-06-07T21:07:31.290032978Z",
+          "State": "FAILED",
+          "Message": "execution failed",
+          "ContainerStatus": {}
+        },
+        "DesiredState": "SHUTDOWN",
+        "NetworksAttachments": [
+          {
+            "Network": {
+              "ID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Version": {
+                "Index": 18
+              },
+              "CreatedAt": "2016-06-07T20:31:11.912919752Z",
+              "UpdatedAt": "2016-06-07T21:07:29.955277358Z",
+              "Spec": {
+                "Name": "ingress",
+                "Labels": {
+                  "com.docker.swarm.internal": "true"
+                },
+                "DriverConfiguration": {},
+                "IPAM": {
+                  "Driver": {},
+                  "Configs": [
+                    {
+                      "Family": "UNKNOWN",
+                      "Subnet": "10.255.0.0/16"
+                    }
+                  ]
+                }
+              },
+              "DriverState": {
+                "Name": "overlay",
+                "Options": {
+                  "com.docker.network.driver.overlay.vxlanid_list": "256"
+                }
+              },
+              "IPAM": {
+                "Driver": {
+                  "Name": "default"
+                },
+                "Configs": [
+                  {
+                    "Family": "UNKNOWN",
+                    "Subnet": "10.255.0.0/16"
+                  }
+                ]
+              }
+            },
+            "Addresses": [
+              "10.255.0.10/16"
+            ]
+          }
+        ],
+        "Endpoint": {
+          "Spec": {},
+          "ExposedPorts": [
+            {
+              "Protocol": "tcp",
+              "Port": 6379,
+              "PublicPort": 30000
+            }
+          ],
+          "VirtualIPs": [
+            {
+              "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Addr": "10.255.0.2/16"
+            },
+            {
+              "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Addr": "10.255.0.3/16"
+            }
+          ]
+        }
+      },
+      {
+        "ID": "1yljwbmlr8er2waf8orvqpwms",
+        "Version": {
+          "Index": 30
+        },
+        "CreatedAt": "2016-06-07T21:07:30.019104782Z",
+        "UpdatedAt": "2016-06-07T21:07:30.231958098Z",
+        "Name": "hopeful_cori",
+        "Spec": {
+          "ContainerSpec": {
+            "Image": "redis"
+          },
+          "Resources": {
+            "Limits": {},
+            "Reservations": {}
+          },
+          "RestartPolicy": {
+            "Condition": "ANY"
+          },
+          "Placement": {}
+        },
+        "ServiceID": "9mnpnzenvg8p8tdbtq4wvbkcz",
+        "Instance": 1,
+        "NodeID": "24ifsmvkjbyhk",
+        "ServiceAnnotations": {},
+        "Status": {
+          "Timestamp": "2016-06-07T21:07:30.202183143Z",
+          "State": "FAILED",
+          "Message": "execution failed",
+          "ContainerStatus": {}
+        },
+        "DesiredState": "SHUTDOWN",
+        "NetworksAttachments": [
+          {
+            "Network": {
+              "ID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Version": {
+                "Index": 18
+              },
+              "CreatedAt": "2016-06-07T20:31:11.912919752Z",
+              "UpdatedAt": "2016-06-07T21:07:29.955277358Z",
+              "Spec": {
+                "Name": "ingress",
+                "Labels": {
+                  "com.docker.swarm.internal": "true"
+                },
+                "DriverConfiguration": {},
+                "IPAM": {
+                  "Driver": {},
+                  "Configs": [
+                    {
+                      "Family": "UNKNOWN",
+                      "Subnet": "10.255.0.0/16"
+                    }
+                  ]
+                }
+              },
+              "DriverState": {
+                "Name": "overlay",
+                "Options": {
+                  "com.docker.network.driver.overlay.vxlanid_list": "256"
+                }
+              },
+              "IPAM": {
+                "Driver": {
+                  "Name": "default"
+                },
+                "Configs": [
+                  {
+                    "Family": "UNKNOWN",
+                    "Subnet": "10.255.0.0/16"
+                  }
+                ]
+              }
+            },
+            "Addresses": [
+              "10.255.0.5/16"
+            ]
+          }
+        ],
+        "Endpoint": {
+          "Spec": {},
+          "ExposedPorts": [
+            {
+              "Protocol": "tcp",
+              "Port": 6379,
+              "PublicPort": 30000
+            }
+          ],
+          "VirtualIPs": [
+            {
+              "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Addr": "10.255.0.2/16"
+            },
+            {
+              "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+              "Addr": "10.255.0.3/16"
+            }
+          ]
+        }
+      }
+    ]
+
+Query Parameters:
+
+- **filters** – a JSON encoded value of the filters (a `map[string][]string`) to process on the
+  services list. Available filters:
+  - `id=<task id>`
+  - `name=<task name>`
+  - `service=<service name>`
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Inspect a task
+
+**Warning:** this endpoint is part of the Swarm management feature introduced in Docker 1.12, and
+might be subject to non backward-compatible changes.
+
+`GET /tasks/(task id)`
+
+Get details on a task
+
+**Example request**:
+
+    GET /tasks/0kzzo1i0y4jz6027t0k7aezc7 HTTP/1.1
+
+**Example response**:
+
+    {
+      "ID": "0kzzo1i0y4jz6027t0k7aezc7",
+      "Version": {
+        "Index": 71
+      },
+      "CreatedAt": "2016-06-07T21:07:31.171892745Z",
+      "UpdatedAt": "2016-06-07T21:07:31.376370513Z",
+      "Name": "hopeful_cori",
+      "Spec": {
+        "ContainerSpec": {
+          "Image": "redis"
+        },
+        "Resources": {
+          "Limits": {},
+          "Reservations": {}
+        },
+        "RestartPolicy": {
+          "Condition": "ANY"
+        },
+        "Placement": {}
+      },
+      "ServiceID": "9mnpnzenvg8p8tdbtq4wvbkcz",
+      "Instance": 1,
+      "NodeID": "24ifsmvkjbyhk",
+      "ServiceAnnotations": {},
+      "Status": {
+        "Timestamp": "2016-06-07T21:07:31.290032978Z",
+        "State": "FAILED",
+        "Message": "execution failed",
+        "ContainerStatus": {}
+      },
+      "DesiredState": "SHUTDOWN",
+      "NetworksAttachments": [
+        {
+          "Network": {
+            "ID": "4qvuz4ko70xaltuqbt8956gd1",
+            "Version": {
+              "Index": 18
+            },
+            "CreatedAt": "2016-06-07T20:31:11.912919752Z",
+            "UpdatedAt": "2016-06-07T21:07:29.955277358Z",
+            "Spec": {
+              "Name": "ingress",
+              "Labels": {
+                "com.docker.swarm.internal": "true"
+              },
+              "DriverConfiguration": {},
+              "IPAM": {
+                "Driver": {},
+                "Configs": [
+                  {
+                    "Family": "UNKNOWN",
+                    "Subnet": "10.255.0.0/16"
+                  }
+                ]
+              }
+            },
+            "DriverState": {
+              "Name": "overlay",
+              "Options": {
+                "com.docker.network.driver.overlay.vxlanid_list": "256"
+              }
+            },
+            "IPAM": {
+              "Driver": {
+                "Name": "default"
+              },
+              "Configs": [
+                {
+                  "Family": "UNKNOWN",
+                  "Subnet": "10.255.0.0/16"
+                }
+              ]
+            }
+          },
+          "Addresses": [
+            "10.255.0.10/16"
+          ]
+        }
+      ],
+      "Endpoint": {
+        "Spec": {},
+        "ExposedPorts": [
+          {
+            "Protocol": "tcp",
+            "Port": 6379,
+            "PublicPort": 30000
+          }
+        ],
+        "VirtualIPs": [
+          {
+            "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+            "Addr": "10.255.0.2/16"
+          },
+          {
+            "NetworkID": "4qvuz4ko70xaltuqbt8956gd1",
+            "Addr": "10.255.0.3/16"
+          }
+        ]
+      }
+    }
+
+Status Codes:
+
+- **200** – no error
+- **404** – unknown task
+- **500** – server error
+
 # 4. Going further
 
 ## 4.1 Inside `docker run`

+ 20 - 0
docs/reference/commandline/index.md

@@ -86,3 +86,23 @@ You start the Docker daemon with the command line. How you start the daemon affe
 * [volume_inspect](volume_inspect.md)
 * [volume_ls](volume_ls.md)
 * [volume_rm](volume_rm.md)
+
+### Swarm node commands
+
+* [node_accept](node_accept.md)
+* [node_reject](node_reject.md)
+* [node_promote](node_promote.md)
+* [node_demote](node_demote.md)
+* [node_inspect](node_inspect.md)
+* [node_update](node_update.md)
+* [node_tasks](node_tasks.md)
+* [node_ls](node_ls.md)
+* [node_rm](node_rm.md)
+
+### Swarm swarm commands
+
+* [swarm init](swarm_init.md)
+* [swarm join](swarm_join.md)
+* [swarm leave](swarm_leave.md)
+* [swarm update](swarm_update.md)
+

+ 6 - 1
docs/reference/commandline/info.md

@@ -37,7 +37,7 @@ available on the volume where `/var/lib/docker` is mounted.
 ## Display Docker system information
 
 Here is a sample output for a daemon running on Ubuntu, using the overlay
-storage driver:
+storage driver and a node that is part of a 2 node Swarm cluster:
 
     $ docker -D info
     Containers: 14
@@ -53,6 +53,11 @@ storage driver:
     Plugins:
      Volume: local
      Network: bridge null host
+    Swarm: 
+     NodeID: 0gac67oclbxq7
+     IsManager: YES
+     Managers: 2
+     Nodes: 2
     Kernel Version: 4.4.0-21-generic
     Operating System: Ubuntu 16.04 LTS
     OSType: linux

+ 11 - 7
docs/reference/commandline/inspect.md

@@ -10,15 +10,15 @@ parent = "smn_cli"
 
 # inspect
 
-    Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
+    Usage: docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
 
-    Return low-level information on a container or image
+    Return low-level information on a container or image or task
 
-      -f, --format=""         Format the output using the given go template
-      --help                  Print usage
-      --type=container|image  Return JSON for specified type, permissible
-                              values are "image" or "container"
-      -s, --size              Display total file sizes if the type is container
+      -f, --format=""              Format the output using the given go template
+      --help                       Print usage
+      --type=container|image|task  Return JSON for specified type, permissible
+                                   values are "image" or "container" or "task"
+      -s, --size                   Display total file sizes if the type is container
 
 By default, this will render all results in a JSON array. If the container and
 image have the same name, this will return container JSON for unspecified type.
@@ -47,6 +47,10 @@ straightforward manner.
 
     $ docker inspect --format='{{.LogPath}}' $INSTANCE_ID
 
+**Get a Task's image name:**
+
+    $ docker inspect --format='{{.Container.Spec.Image}}' $INSTANCE_ID
+
 **List All Port Bindings:**
 
 One can loop over arrays and maps in the results to produce simple text

+ 28 - 0
docs/reference/commandline/node_accept.md

@@ -0,0 +1,28 @@
+<!--[metadata]>
++++
+title = "node accept"
+description = "The node accept command description and usage"
+keywords = ["node, accept"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+# node accept
+
+    Usage:  docker node accept NODE [NODE...]
+
+    Accept a node in the swarm
+
+Accept a node into the swarm. This command targets a docker engine that is a manager in the swarm cluster.
+
+
+```bash
+$ docker node accept <node name>
+```
+
+## Related information
+
+* [node reject](node_reject.md)
+* [node promote](node_promote.md)
+* [node demote](node_demote.md)

+ 28 - 0
docs/reference/commandline/node_demote.md

@@ -0,0 +1,28 @@
+<!--[metadata]>
++++
+title = "node demote"
+description = "The node demote command description and usage"
+keywords = ["node, demote"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+# node demote
+
+    Usage:  docker node demote NODE [NODE...]
+
+    Demote a node as manager in the swarm
+
+Demotes an existing Manager so that it is no longer a manager. This command targets a docker engine that is a manager in the swarm cluster.
+
+
+```bash
+$ docker node demote <node name>
+```
+
+## Related information
+
+* [node accept](node_accept.md)
+* [node reject](node_reject.md)
+* [node promote](node_promote.md)

+ 108 - 0
docs/reference/commandline/node_inspect.md

@@ -0,0 +1,108 @@
+<!--[metadata]>
++++
+title = "node inspect"
+description = "The node inspect command description and usage"
+keywords = ["node, inspect"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# node inspect
+
+    Usage: docker node inspect [OPTIONS] self|NODE [NODE...]
+
+    Return low-level information on a volume
+
+      -f, --format=       Format the output using the given go template.
+      --help              Print usage
+      -p, --pretty        Print the information in a human friendly format.
+
+Returns information about a node. By default, this command renders all results
+in a JSON array. You can specify an alternate format to execute a
+given template for each result. Go's
+[text/template](http://golang.org/pkg/text/template/) package describes all the
+details of the format.
+
+Example output:
+
+    $ docker node inspect swarm-manager
+    [
+      {
+        "ID": "0gac67oclbxq7",
+        "Version": {
+            "Index": 2028
+        },
+        "CreatedAt": "2016-06-06T20:49:32.720047494Z",
+        "UpdatedAt": "2016-06-07T00:23:31.207632893Z",
+        "Spec": {
+            "Role": "MANAGER",
+            "Membership": "ACCEPTED",
+            "Availability": "ACTIVE"
+        },
+        "Description": {
+            "Hostname": "swarm-manager",
+            "Platform": {
+                "Architecture": "x86_64",
+                "OS": "linux"
+            },
+            "Resources": {
+                "NanoCPUs": 1000000000,
+                "MemoryBytes": 1044250624
+            },
+            "Engine": {
+                "EngineVersion": "1.12.0",
+                "Labels": {
+                    "provider": "virtualbox"
+                }
+            }
+        },
+        "Status": {
+            "State": "READY"
+        },
+        "Manager": {
+            "Raft": {
+                "RaftID": 2143745093569717375,
+                "Addr": "192.168.99.118:4500",
+                "Status": {
+                    "Leader": true,
+                    "Reachability": "REACHABLE"
+                }
+            }
+        },
+        "Attachment": {},
+      }
+    ]
+
+    $ docker node inspect --format '{{ .Manager.Raft.Status.Leader }}' self
+    false
+
+    $ docker node inspect --pretty self
+    ID:                     2otfhz83efcc7
+    Hostname:               ad960a848573
+    Status:
+     State:                 Ready
+     Availability:          Active
+    Manager Status:
+     Address:               172.17.0.2:2377
+     Raft status:           Reachable
+     Leader:                Yes
+    Platform:
+     Operating System:      linux
+     Architecture:          x86_64
+    Resources:
+     CPUs:                  4
+     Memory:                7.704 GiB
+    Plugins:
+      Network:              overlay, bridge, null, host, overlay
+      Volume:               local
+    Engine Version:         1.12.0
+
+## Related information
+
+* [node update](node_update.md)
+* [node tasks](node_tasks.md)
+* [node ls](node_ls.md)
+* [node rm](node_rm.md)

+ 89 - 0
docs/reference/commandline/node_ls.md

@@ -0,0 +1,89 @@
+<!--[metadata]>
++++
+title = "node ls"
+description = "The node ls command description and usage"
+keywords = ["node, list"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# node ls
+
+    Usage:  docker node ls [OPTIONS]
+
+    List nodes in the swarm
+
+    Aliases:
+      ls, list
+
+    Options:
+      -f, --filter value   Filter output based on conditions provided
+          --help           Print usage
+      -q, --quiet          Only display IDs
+
+Lists all the nodes that the Docker Swarm manager knows about. You can filter using the `-f` or `--filter` flag. Refer to the [filtering](#filtering) section for more information about available filter options.
+
+Example output:
+
+    $ docker node ls
+    ID              NAME           STATUS  AVAILABILITY     MANAGER STATUS  LEADER
+    0gac67oclbxq    swarm-master   Ready   Active           Reachable       Yes
+    0pwvm3ve66q7    swarm-node-02  Ready   Active
+    15xwihgw71aw *  swarm-node-01  Ready   Active           Reachable
+
+
+## Filtering
+
+The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
+than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
+
+The currently supported filters are:
+
+* name
+* id
+* label
+* desired_state
+
+### name
+
+The `name` filter matches on all or part of a tasks's name.
+
+The following filter matches the node with a name equal to `swarm-master` string.
+
+    $ docker node ls -f name=swarm-master
+    ID              NAME          STATUS  AVAILABILITY      MANAGER STATUS  LEADER
+    0gac67oclbxq *  swarm-master  Ready   Active            Reachable       Yes
+
+### id
+
+The `id` filter matches all or part of a node's id.
+
+    $ docker node ls -f id=0
+    ID              NAME           STATUS  AVAILABILITY     MANAGER STATUS  LEADER
+    0gac67oclbxq *  swarm-master   Ready   Active           Reachable       Yes
+    0pwvm3ve66q7    swarm-node-02  Ready   Active
+
+
+#### label
+
+The `label` filter matches tasks based on the presence of a `label` alone or a `label` and a
+value.
+
+The following filter matches nodes with the `usage` label regardless of its value.
+
+```bash
+$ docker node ls -f "label=foo"
+ID              NAME           STATUS  AVAILABILITY     MANAGER STATUS  LEADER
+15xwihgw71aw *  swarm-node-01  Ready   Active           Reachable
+```
+
+
+## Related information
+
+* [node inspect](node_inspect.md)
+* [node update](node_update.md)
+* [node tasks](node_tasks.md)
+* [node rm](node_rm.md)

+ 28 - 0
docs/reference/commandline/node_promote.md

@@ -0,0 +1,28 @@
+<!--[metadata]>
++++
+title = "node promote"
+description = "The node promote command description and usage"
+keywords = ["node, promote"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+# node promote
+
+    Usage:  docker node promote NODE [NODE...]
+
+    Promote a node as manager in the swarm
+
+Promotes a node that is pending a promotion to manager. This command targets a docker engine that is a manager in the swarm cluster.
+
+
+```bash
+$ docker node promote <node name>
+```
+
+## Related information
+
+* [node accept](node_accept.md)
+* [node reject](node_reject.md)
+* [node demote](node_demote.md)

+ 28 - 0
docs/reference/commandline/node_reject.md

@@ -0,0 +1,28 @@
+<!--[metadata]>
++++
+title = "node reject"
+description = "The node reject command description and usage"
+keywords = ["node, reject"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+# node reject
+
+    Usage:  docker node reject NODE [NODE...]
+
+    Reject a node from the swarm
+
+Reject a node from joining the swarm. This command targets a docker engine that is a manager in the swarm cluster.
+
+
+```bash
+$ docker node reject <node name>
+```
+
+## Related information
+
+* [node accept](node_accept.md)
+* [node promote](node_promote.md)
+* [node demote](node_demote.md)

+ 38 - 0
docs/reference/commandline/node_rm.md

@@ -0,0 +1,38 @@
+<!--[metadata]>
++++
+title = "node rm"
+description = "The node rm command description and usage"
+keywords = ["node, remove"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# node rm
+
+	Usage:	docker node rm NODE [NODE...]
+
+	Remove a node from the swarm
+
+	Aliases:
+	  rm, remove
+
+	Options:
+	      --help   Print usage
+
+Removes nodes that are specified. 
+
+Example output:
+
+    $ docker node rm swarm-node-02
+    Node swarm-node-02 removed from Swarm
+
+
+## Related information
+
+* [node inspect](node_inspect.md)
+* [node update](node_update.md)
+* [node tasks](node_tasks.md)
+* [node ls](node_ls.md)

+ 94 - 0
docs/reference/commandline/node_tasks.md

@@ -0,0 +1,94 @@
+<!--[metadata]>
++++
+title = "node tasks"
+description = "The node tasks command description and usage"
+keywords = ["node, tasks"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# node tasks
+
+    Usage:  docker node tasks [OPTIONS] NODE
+
+    List tasks running on a node
+
+    Options:
+      -a, --all            Display all instances
+      -f, --filter value   Filter output based on conditions provided
+      --help           Print usage
+      -n, --no-resolve     Do not map IDs to Names
+
+Lists all the tasks on a Node that Docker knows about. You can filter using the `-f` or `--filter` flag. Refer to the [filtering](#filtering) section for more information about available filter options.
+
+Example output:
+
+    $ docker node tasks swarm-master
+    ID                         NAME     SERVICE  IMAGE        DESIRED STATE  LAST STATE       NODE
+    dx2g0fe3zsdb6y6q453f8dqw2  redis.1  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    f33pcf8lwhs4c1t4kq8szwzta  redis.4  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    5v26yzixl3one3ptjyqqbd0ro  redis.5  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    adcaphlhsfr30d47lby6walg6  redis.8  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    chancjvk9tex6768uzzacslq2  redis.9  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+
+
+## Filtering
+
+The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
+than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
+
+The currently supported filters are:
+
+* name
+* id
+* label
+* desired_state
+
+### name
+
+The `name` filter matches on all or part of a task's name.
+
+The following filter matches all tasks with a name containing the `redis` string.
+
+    $ docker node tasks -f name=redis swarm-master
+    ID                         NAME     SERVICE  IMAGE        DESIRED STATE  LAST STATE       NODE
+    dx2g0fe3zsdb6y6q453f8dqw2  redis.1  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    f33pcf8lwhs4c1t4kq8szwzta  redis.4  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    5v26yzixl3one3ptjyqqbd0ro  redis.5  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    adcaphlhsfr30d47lby6walg6  redis.8  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+    chancjvk9tex6768uzzacslq2  redis.9  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+
+
+### id
+
+The `id` filter matches a task's id.
+
+    $ docker node tasks -f id=f33pcf8lwhs4c1t4kq8szwzta swarm-master
+    ID                         NAME     SERVICE  IMAGE        DESIRED STATE  LAST STATE       NODE
+    f33pcf8lwhs4c1t4kq8szwzta  redis.4  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+
+
+#### label
+
+The `label` filter matches tasks based on the presence of a `label` alone or a `label` and a
+value.
+
+The following filter matches tasks with the `usage` label regardless of its value.
+
+```bash
+$ docker node tasks -f "label=usage"
+ID                         NAME     SERVICE  IMAGE        DESIRED STATE  LAST STATE       NODE
+dx2g0fe3zsdb6y6q453f8dqw2  redis.1  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+f33pcf8lwhs4c1t4kq8szwzta  redis.4  redis    redis:3.0.6  RUNNING        RUNNING 2 hours  swarm-master
+```
+
+
+## Related information
+
+* [node inspect](node_inspect.md)
+* [node update](node_update.md)
+* [node ls](node_ls.md)
+* [node rm](node_rm.md)

+ 26 - 0
docs/reference/commandline/node_update.md

@@ -0,0 +1,26 @@
+<!--[metadata]>
++++
+title = "node update"
+description = "The node update command description and usage"
+keywords = ["resources, update, dynamically"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+## update
+
+    Usage: docker node update [OPTIONS] Node
+
+    Update a node
+
+
+
+## Related information
+
+* [node inspect](node_inspect.md)
+* [node tasks](node_tasks.md)
+* [node ls](node_ls.md)
+* [node rm](node_rm.md)

+ 69 - 0
docs/reference/commandline/swarm_init.md

@@ -0,0 +1,69 @@
+<!--[metadata]>
++++
+title = "swarm init"
+description = "The swarm init command description and usage"
+keywords = ["swarm, init"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# swarm init
+
+	Usage:	docker swarm init [OPTIONS]
+
+	Initialize a Swarm.
+
+	Options:
+	      --auto-accept value   Acceptance policy (default [worker,manager])
+	      --force-new-cluster   Force create a new cluster from current state.
+	      --help                Print usage
+	      --listen-addr value   Listen address (default 0.0.0.0:2377)
+	      --secret string       Set secret value needed to accept nodes into cluster
+
+Initialize a Swarm cluster. The docker engine targeted by this command becomes a manager
+in the newly created one node Swarm cluster.
+
+
+```bash
+$ docker swarm init --listen-addr 192.168.99.121:2377
+Initializing a new swarm
+$ docker node ls
+ID              NAME          STATUS  AVAILABILITY/MEMBERSHIP  MANAGER STATUS  LEADER
+3l1f6uzcuoa3 *  swarm-master  READY   ACTIVE                   REACHABLE       Yes
+```
+
+###	--auto-accept value
+
+This flag controls node acceptance into the cluster. By default, both `worker` and `manager`
+nodes are auto accepted by the cluster. This can be changed by specifing what kinds of nodes
+can be auto-accepted into the cluster. If auto-accept is not turned on, then 
+[node accept](node_accept.md) can be used to explicitly accept a node into the cluster.
+
+For example, the following initializes a cluster with auto-acceptance of workers, but not managers
+
+
+```bash
+$ docker swarm init --listen-addr 192.168.99.121:2377 --auto-accept worker
+Initializing a new swarm
+```
+
+### `--force-new-cluster`
+
+This flag forces an existing node that was part of a quorum that was lost to restart as a single node Manager without losing its data
+
+### `--listen-addr value`
+
+The node listens for inbound Swarm manager traffic on this IP:PORT
+
+### `--secret string`
+
+Secret value needed to accept nodes into the Swarm
+
+## Related information
+
+* [swarm join](swarm_join.md)
+* [swarm leave](swarm_leave.md)
+* [swarm update](swarm_update.md)

+ 68 - 0
docs/reference/commandline/swarm_join.md

@@ -0,0 +1,68 @@
+<!--[metadata]>
++++
+title = "swarm join"
+description = "The swarm join command description and usage"
+keywords = ["swarm, join"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# swarm join
+
+	Usage:	docker swarm join [OPTIONS] HOST:PORT
+
+	Join a Swarm as a node and/or manager.
+
+	Options:
+	      --help                Print usage
+	      --listen-addr value   Listen address (default 0.0.0.0:2377)
+	      --manager             Try joining as a manager.
+	      --secret string       Secret for node acceptance
+
+Join a node to a Swarm cluster. If the `--manager` flag is specified, the docker engine
+targeted by this command becomes a `manager`. If it is not specified, it becomes a `worker`.
+
+### Join a node to swarm as a manager
+
+```bash
+$ docker swarm join --manager --listen-addr 192.168.99.122:2377 192.168.99.121:2377
+This node is attempting to join a Swarm as a manager.
+$ docker node ls
+ID              NAME           STATUS  AVAILABILITY/MEMBERSHIP  MANAGER STATUS  LEADER
+2fg70txcrde2    swarm-node-01  READY   ACTIVE                   REACHABLE       
+3l1f6uzcuoa3 *  swarm-master   READY   ACTIVE                   REACHABLE       Yes
+```
+
+### Join a node to swarm as a worker
+
+```bash
+$ docker swarm join --listen-addr 192.168.99.123:2377 192.168.99.121:2377
+This node is attempting to join a Swarm.
+$ docker node ls
+ID              NAME           STATUS  AVAILABILITY/MEMBERSHIP  MANAGER STATUS  LEADER
+04zm7ue1fd1q    swarm-node-02  READY   ACTIVE                                   
+2fg70txcrde2    swarm-node-01  READY   ACTIVE                   REACHABLE       
+3l1f6uzcuoa3 *  swarm-master   READY   ACTIVE                   REACHABLE       Yes
+```
+
+### `--manager`
+
+Joins the node as a manager
+
+### `--listen-addr value`
+
+The node listens for inbound Swarm manager traffic on this IP:PORT
+
+### `--secret string`
+
+Secret value required for nodes to join the swarm
+
+
+## Related information
+
+* [swarm init](swarm_init.md)
+* [swarm leave](swarm_leave.md)
+* [swarm update](swarm_update.md)

+ 52 - 0
docs/reference/commandline/swarm_leave.md

@@ -0,0 +1,52 @@
+<!--[metadata]>
++++
+title = "swarm leave"
+description = "The swarm leave command description and usage"
+keywords = ["swarm, leave"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# swarm leave
+
+	Usage:	docker swarm leave
+
+	Leave a Swarm swarm.
+
+	Options:
+	      --help   Print usage
+
+This command causes the node to leave the swarm.
+
+On a manager node:
+```bash
+$ docker node ls
+ID              NAME           STATUS  AVAILABILITY/MEMBERSHIP  MANAGER STATUS  LEADER
+04zm7ue1fd1q    swarm-node-02  READY   ACTIVE                                   
+2fg70txcrde2    swarm-node-01  READY   ACTIVE                   REACHABLE       
+3l1f6uzcuoa3 *  swarm-master   READY   ACTIVE                   REACHABLE       Yes
+```
+
+On a worker node:
+```bash
+$ docker swarm leave
+Node left the default swarm.
+```
+
+On a manager node:
+```bash
+$ docker node ls
+ID              NAME           STATUS  AVAILABILITY/MEMBERSHIP  MANAGER STATUS  LEADER
+04zm7ue1fd1q    swarm-node-02  DOWN    ACTIVE                                   
+2fg70txcrde2    swarm-node-01  READY   ACTIVE                   REACHABLE       
+3l1f6uzcuoa3 *  swarm-master   READY   ACTIVE                   REACHABLE       Yes
+```
+
+## Related information
+
+* [swarm init](swarm_init.md)
+* [swarm join](swarm_join.md)
+* [swarm update](swarm_update.md)

+ 37 - 0
docs/reference/commandline/swarm_update.md

@@ -0,0 +1,37 @@
+<!--[metadata]>
++++
+title = "swarm update"
+description = "The swarm update command description and usage"
+keywords = ["swarm, update"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+**Warning:** this command is part of the Swarm management feature introduced in Docker 1.12, and might be subject to non backward-compatible changes.
+
+# swarm update
+
+	Usage:    docker swarm update [OPTIONS]
+
+	update the Swarm.
+
+	Options:
+	      --auto-accept value   Acceptance policy (default [worker,manager])
+	      --help                Print usage
+	      --secret string       Set secret value needed to accept nodes into cluster
+
+
+Updates a Swarm cluster with new parameter values. This command must target a manager node.
+
+
+```bash
+$ docker swarm update --auto-accept manager
+```
+
+## Related information
+
+* [swarm init](swarm_init.md)
+* [swarm join](swarm_join.md)
+* [swarm leave](swarm_leave.md)
+

+ 79 - 0
docs/swarm/index.md

@@ -0,0 +1,79 @@
+<!--[metadata]>
++++
+title = "Swarm overview"
+description = "Docker Swarm overview"
+keywords = ["docker, container, cluster, swarm"]
+[menu.main]
+identifier="swarm_overview"
+parent="engine_swarm"
+weight="1"
+advisory = "rc"
++++
+<![end-metadata]-->
+# Docker Swarm overview
+
+To use this version of Swarm, install the Docker Engine `v1.12.0-rc1` or later
+from the [Docker releases GitHub
+repository](https://github.com/docker/docker/releases). Alternatively, install
+the latest Docker for Mac or Docker for Windows Beta.
+
+Docker Engine 1.12 includes Docker Swarm for natively managing a cluster of
+Docker Engines called a Swarm. Use the Docker CLI to create a Swarm, deploy
+application services to the Swarm, and manage the Swarm behavior.
+
+
+If you’re using a Docker version prior to `v1.12.0-rc1`, see [Docker
+Swarm](https://docs.docker.com/swarm).
+
+## Feature highlights
+
+* **Cluster management integrated with Docker Engine:** Use the Docker Engine
+CLI to create a Swarm of Docker Engines where you can deploy application
+services. You don't need additional orchestration software to create or manage
+a Swarm.
+
+* **Decentralized design:** Instead of handling differentiation between node
+roles at deployment time, Swarm handles any specialization at runtime. You can
+deploy both kinds of nodes, managers and workers, using the Docker Engine.
+This means you can build an entire Swarm from a single disk image.
+
+* **Declarative service model:** Swarm uses a declarative syntax to let you
+define the desired state of the various services in your application stack.
+For example, you might describe an application comprised of a web front end
+service with message queueing services and a database backend.
+
+* **Desired state reconciliation:** Swarm constantly monitors the cluster state
+and reconciles any differences between the actual state your expressed desired
+state.
+
+* **Multi-host networking:** You can specify an overlay network for your
+application. Swarm automatically assigns addresses to the containers on the
+overlay network when it initializes or updates the application.
+
+* **Service discovery:** Swarm assigns each service a unique DNS name and load
+balances running containers. Each Swarm has an internal DNS server that can
+query every container in the cluster using DNS.
+
+* **Load balancing:** Using Swarm, you can expose the ports for services to an
+external load balancer. Internally, Swarm lets you specify how to distribute
+service containers between nodes.
+
+* **Secure by default:** Each node in the Swarm enforces TLS mutual
+authentication and encryption to secure communications between itself and all
+other nodes. You have the option to use self-signed root certificates or
+certificates from a custom root CA.
+
+* **Scaling:** For each service, you can declare the number of instances you
+want to run. When you scale up or down, Swarm automatically adapts by adding
+or removing instances of the service to maintain the desired state.
+
+* **Rolling updates:** At rollout time you can apply service updates to nodes
+incrementally. Swarm lets you control the delay between service deployment to
+different sets of nodes. If anything goes wrong, you can roll-back an instance
+of a service.
+
+## What's next?
+* Learn Swarm [key concepts](key-concepts.md).
+* Get started with the [Swarm tutorial](swarm-tutorial/index.md).
+
+<p style="margin-bottom:300px">&nbsp;</p>

+ 85 - 0
docs/swarm/key-concepts.md

@@ -0,0 +1,85 @@
+<!--[metadata]>
++++
+title = "Swarm key concepts"
+description = "Introducing key concepts for Docker Swarm"
+keywords = ["docker, container, cluster, swarm"]
+[menu.main]
+identifier="swarm-concepts"
+parent="engine_swarm"
+weight="2"
+advisory = "rc"
++++
+<![end-metadata]-->
+# Docker Swarm key concepts
+
+Building upon the core features of Docker Engine, Docker Swarm enables you to
+create a Swarm of Docker Engines and orchestrate services to run in the Swarm.
+This topic describes key concepts to help you begin using Docker Swarm.
+
+## Swarm
+
+**Docker Swarm** is the name for the cluster management and orchestration features
+embedded in the Docker Engine.
+
+A **Swarm** is a cluster of Docker Engines where you deploy a set of application
+services. When you deploy an application to a Swarm, you specify the desired
+state of the services, such as which services to run and how many instances of
+those services. The Swarm takes care of all orchestration duties required to
+keep the services running in the desired state.
+
+## Node
+
+A **node** is an active instance of the Docker Engine in the Swarm.
+
+When you deploy your application to a Swarm, **manager nodes** accept the
+service definition that describes the Swarm's desired state. Manager nodes also
+perform the orchestration and cluster management functions required to maintain
+the desired state of the Swarm. For example, when a manager node receives notice
+to deploy a web server, it dispatches the service tasks to worker nodes.
+
+By default the Docker Engine starts one manager node for a Swarm, but as you
+scale you can add more managers to make the cluster more fault-tolerant. If you
+require high availability Swarm management, Docker recommends three or five
+Managers in your cluster.
+
+Because Swarm manager nodes share data using Raft, there must be an odd number
+of managers. The Swarm cluster can continue functioning in the face of up to
+`N/2` failures where `N` is the number of manager nodes.  More than five
+managers is likely to degrade cluster performance and is not recommended.
+
+**Worker nodes** receive and execute tasks dispatched from manager nodes. By
+default manager nodes are also worker nodes, but you can configure managers to
+be manager-only nodes.
+
+## Services and tasks
+
+A **service** is the definition of how to run the various tasks that make up
+your application. For example, you may create a service that deploys a Redis
+image in your Swarm.
+
+A **task** is the atomic scheduling unit of Swarm. For example a task may be to
+schedule a Redis container to run on a worker node.
+
+
+## Service types
+
+For **replicated services**, Swarm deploys a specific number of replica tasks
+based upon the scale you set in the desired state.
+
+For **global services**, Swarm runs one task for the service on every available
+node in the cluster.
+
+## Load balancing
+
+Swarm uses **ingress load balancing** to expose the services you want to make
+available externally to the Swarm. Swarm can automatically assign the service a
+**PublishedPort** or you can configure a PublishedPort for the service in the
+30000-32767 range. External components, such as cloud load balancers, can access
+the service on the PublishedPort of any node in the cluster, even if the node is
+not currently running the service.
+
+Swarm has an internal DNS component that automatically assigns each service in
+the Swarm DNS entry. Swarm uses **internal load balancing** distribute requests
+among services within the cluster based upon the services' DNS name.
+
+<p style="margin-bottom:300px">&nbsp;</p>

+ 21 - 0
docs/swarm/menu.md

@@ -0,0 +1,21 @@
+<!--[metadata]>
++++
+title = "Manage a Swarm (1.12 RC)"
+description = "How to use Docker Swarm to create and manage Docker Engine clusters"
+keywords = [" docker, documentation, developer, "]
+[menu.main]
+identifier = "engine_swarm"
+parent = "engine_use"
+weight = 0
+advisory = "rc"
++++
+<![end-metadata]-->
+
+
+## Use Docker Swarm to create and manage clusters of Docker Engine called Swarms
+
+This section contains the following topics:
+
+* [Docker Swarm overview](index.md)
+* [Docker Swarm key concepts](key-concepts.md)
+* [Getting Started with Docker Swarm](swarm-tutorial/index.md)

+ 64 - 0
docs/swarm/swarm-tutorial/add-nodes.md

@@ -0,0 +1,64 @@
+<!--[metadata]>
++++
+title = "Add nodes to the Swarm"
+description = "Add nodes to the Swarm"
+keywords = ["tutorial, cluster management, swarm"]
+[menu.main]
+identifier="add-nodes"
+parent="swarm-tutorial"
+weight=13
+advisory = "rc"
++++
+<![end-metadata]-->
+
+# Add nodes to the Swarm
+
+Once you've [created a Swarm](create-swarm.md) with a manager node, you're ready
+to add worker nodes.
+
+1. Open a terminal and ssh into the machine where you want to run a worker node.
+This tutorial uses the name `worker1`.
+
+2. Run `docker swarm join MANAGER-IP:PORT` to create a worker node joined to the
+existing Swarm. Replace MANAGER-IP address of the manager node and the port
+where the manager listens.
+
+    In the tutorial, the following command joins `worker1` to the Swarm on `manager1`:
+
+    ```
+    $ docker swarm join 192.168.99.100:2377
+
+    This node joined a Swarm as a worker.
+    ```
+
+3. Open a terminal and ssh into the machine where you want to run a second
+worker node. This tutorial uses the name `worker2`.
+
+4. Run `docker swarm join MANAGER-IP:PORT` to create a worker node joined to
+the existing Swarm. Replace MANAGER-IP address of the manager node and the port
+where the manager listens.
+
+5. Open a terminal and ssh into the machine where the manager node runs and run
+the `docker node ls` command to see the worker nodes:
+
+    ```bash
+    $ docker node ls
+
+    ID              NAME      MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS  LEADER
+09fm6su6c24q *  manager1  Accepted    Ready   Active        Reachable       Yes
+32ljq6xijzb9    worker1   Accepted    Ready   Active
+38fsncz6fal9    worker2   Accepted    Ready   Active
+    ```
+
+    The `MANAGER` column identifies the manager nodes in the Swarm. The empty
+    status in this column for `worker1` and `worker2` identifies them as worker nodes.
+
+    Swarm management commands like `docker node ls` only work on manager nodes.
+
+
+## What's next?
+
+Now your Swarm consists of a manager and two worker nodes. In the next step of
+the tutorial, you [deploy a service](deploy-service.md) to the Swarm.
+
+<p style="margin-bottom:300px">&nbsp;</p>

+ 77 - 0
docs/swarm/swarm-tutorial/create-swarm.md

@@ -0,0 +1,77 @@
+<!--[metadata]>
++++
+title = "Create a Swarm"
+description = "Initialize the Swarm"
+keywords = ["tutorial, cluster management, swarm"]
+[menu.main]
+identifier="initialize-swarm"
+parent="swarm-tutorial"
+weight=12
+advisory = "rc"
++++
+<![end-metadata]-->
+
+# Create a Swarm
+
+After you complete the [tutorial setup](index.md) steps, you're ready
+to create a Swarm. Make sure the Docker Engine daemon is started on the host
+machines.
+
+1. Open a terminal and ssh into the machine where you want to run your manager
+node. For example, the tutorial uses a machine named `manager1`.
+
+2. Run `docker swarm init --listen-addr MANAGER-IP:PORT` to create a new Swarm.
+
+    In the tutorial, the following command creates a Swarm on the `manager1` machine:
+
+    ```
+    $ docker swarm init --listen-addr 192.168.99.100:2377
+
+    Swarm initialized: current node (09fm6su6c24qn) is now a manager.
+    ```
+
+    The `--listen-addr` flag configures the manager node to listen on port
+    `2377`. The other nodes in the Swarm must be able to access the manager at
+    the IP address.
+
+3. Run `docker info` to view the current state of the Swarm:
+
+     ```
+     $ docker info
+
+     Containers: 2
+      Running: 0
+      Paused: 0
+      Stopped: 2
+     ...snip...
+     Swarm:
+      NodeID: 09fm6su6c24qn
+      IsManager: YES
+      Managers: 1
+      Nodes: 1
+     ...snip...
+     ```
+
+4. Run the `docker node ls` command to view information about nodes:
+
+    ```
+    $ docker node ls
+
+    ID              NAME      MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS  LEADER
+09fm6su6c24q *  manager1  Accepted    Ready   Active        Reachable       Yes
+
+    ```
+
+     The `*` next to the node id, indicates that you're currently connected on
+     this node.
+
+     Docker Swarm automatically names the node for the machine host name. The
+     tutorial covers other columns in later steps.
+
+## What's next?
+
+In the next section of the tutorial, we'll [add two more nodes](add-nodes.md) to
+the cluster.
+
+
+<p style="margin-bottom:300px">&nbsp;</p>

+ 44 - 0
docs/swarm/swarm-tutorial/delete-service.md

@@ -0,0 +1,44 @@
+<!--[metadata]>
++++
+title = "Delete the service"
+description = "Remove the service on the Swarm"
+keywords = ["tutorial, cluster management, swarm, service"]
+[menu.main]
+identifier="swarm-tutorial-delete-service"
+parent="swarm-tutorial"
+weight=19
+advisory = "rc"
++++
+<![end-metadata]-->
+
+# Delete the service running on the Swarm
+
+The remaining steps in the tutorial don't use the `helloworld` service, so now
+you can delete the service from the Swarm.
+
+1. If you haven't already, open a terminal and ssh into the machine where you
+run your manager node. For example, the tutorial uses a machine named
+`manager1`.
+
+2. Run `docker service remove helloworld` to remove the `helloworld` service.
+
+    ```
+    $ docker service rm helloworld
+    helloworld
+    ```
+
+3. Run `docker service inspect SERVICE-ID` to veriy that Swarm removed the
+service. The CLI returns a message that the service is not found:
+
+    ```
+    $ docker service inspect helloworld
+    []
+    Error: no such service or task: helloworld
+    ```
+
+## What's next?
+
+In the next step of the tutorial, you set up a new service and and apply a
+[rolling update](rolling-update.md).
+
+<p style="margin-bottom:300px">&nbsp;</p>

+ 50 - 0
docs/swarm/swarm-tutorial/deploy-service.md

@@ -0,0 +1,50 @@
+<!--[metadata]>
++++
+title = "Deploy a service"
+description = "Deploy the application"
+keywords = ["tutorial, cluster management, swarm"]
+[menu.main]
+identifier="deploy-application"
+parent="swarm-tutorial"
+weight=16
+advisory = "rc"
++++
+<![end-metadata]-->
+
+# Deploy a service to the Swarm
+
+After you [create a Swarm](create-swarm.md), you can deploy a service to the
+Swarm. For this tutorial, you also [added worker nodes](add-nodes.md), but that
+is not a requirement to deploy a service.
+
+1. Open a terminal and ssh into the machine where you run your manager node. For
+example, the tutorial uses a machine named `manager1`.
+
+2. Run the the following command:
+
+    ```bash
+    $ docker service create --scale 1 --name helloworld alpine ping docker.com
+
+    2zs4helqu64f3k3iuwywbk49w
+    ```
+
+    * The `docker service create` command creates the service.
+    * The `--name` flag names the service `helloworld`.
+    * The `--scale` flag specifies the desired state of 1 running instance.
+    * The arguments `alpine ping docker.com` define the service as an Alpine
+    Linux container that executes the command `ping docker.com`.
+
+3. Run `docker service ls` to see the list of running services:
+
+    ```
+    $ docker service ls
+
+    ID            NAME        SCALE  IMAGE   COMMAND
+    2zs4helqu64f  helloworld  1      alpine  ping docker.com
+    ```
+
+## What's next?
+
+Now you've deployed a service to the Swarm, you're ready to [inspect the service](inspect-service.md).
+
+<p style="margin-bottom:300px">&nbsp;</p>

+ 129 - 0
docs/swarm/swarm-tutorial/drain-node.md

@@ -0,0 +1,129 @@
+<!--[metadata]>
++++
+title = "Drain a node"
+description = "Drain nodes on the Swarm"
+keywords = ["tutorial, cluster management, swarm, service, drain"]
+[menu.main]
+identifier="swarm-tutorial-drain-node"
+parent="swarm-tutorial"
+weight=21
++++
+<![end-metadata]-->
+
+# Drain a node on the Swarm
+
+In earlier steps of the tutorial, all the nodes have been running with `ACTIVE`
+availability. The Swarm manager can assign tasks to any `ACTIVE` node, so all
+nodes have been available to receive tasks.
+
+Sometimes, such as planned maintenance times, you need to set a node to `DRAIN`
+availabilty. `DRAIN` availabilty  prevents a node from receiving new tasks
+from the Swarm manager. It also means the manager stops tasks running on the
+node and launches replica tasks on a node with `ACTIVE` availability.
+
+1. If you haven't already, open a terminal and ssh into the machine where you
+run your manager node. For example, the tutorial uses a machine named
+`manager1`.
+
+2. Verify that all your nodes are actively available.
+
+    ```
+    $ docker node ls
+
+    ID               NAME      MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS  LEADER
+  1x2bldyhie1cj    worker1   Accepted    Ready   Active
+  1y3zuia1z224i    worker2   Accepted    Ready   Active
+  2p5bfd34mx4op *  manager1  Accepted    Ready   Active        Reachable       Yes
+    ```
+
+2. If you aren't still running the `redis` service from the [rolling
+update](rolling-update.md) tutorial, start it now:
+
+    ```bash
+    $ docker service create --scale 3 --name redis --update-delay 10s --update-parallelism 1 redis:3.0.6
+
+    69uh57k8o03jtqj9uvmteodbb
+    ```
+
+3. Run `docker service tasks redis` to see how the Swarm manager assigned the
+tasks to different nodes:
+
+    ```
+    $ docker service tasks redis
+    ID                         NAME     SERVICE  IMAGE        LAST STATE          DESIRED STATE  NODE
+    3wfqsgxecktpwoyj2zjcrcn4r  redis.1  redis    redis:3.0.6  RUNNING 13 minutes  RUNNING        worker2
+    8lcm041z3v80w0gdkczbot0gg  redis.2  redis    redis:3.0.6  RUNNING 13 minutes  RUNNING        worker1
+    d48skceeph9lkz4nbttig1z4a  redis.3  redis    redis:3.0.6  RUNNING 12 minutes  RUNNING        manager1
+    ```
+
+    In this case the Swarm manager distributed one task to each node. You may
+    see the tasks distributed differently among the nodes in your environment.
+
+4. Run `docker node update --availability drain NODE-ID` to drain a node that
+had a task assigned to it:
+
+    ```bash
+    docker node update --availability drain worker1
+    worker1
+    ```
+
+5. Inspect the node to check its availability:
+
+    ```
+    $ docker node inspect --pretty worker1
+    ID:			1x2bldyhie1cj
+    Hostname:		worker1
+    Status:
+     State:			READY
+     Availability:		DRAIN
+    ...snip...
+    ```
+
+    The drained node shows `Drain` for `AVAILABILITY`.
+
+6. Run `docker service tasks redis` to see how the Swarm manager updated the
+task assignments for the `redis` service:
+
+    ```
+    ID                         NAME     SERVICE  IMAGE        LAST STATE          DESIRED STATE  NODE
+    3wfqsgxecktpwoyj2zjcrcn4r  redis.1  redis    redis:3.0.6  RUNNING 26 minutes  RUNNING        worker2
+    ah7o4u5upostw3up1ns9vbqtc  redis.2  redis    redis:3.0.6  RUNNING 9 minutes   RUNNING        manager1
+    d48skceeph9lkz4nbttig1z4a  redis.3  redis    redis:3.0.6  RUNNING 26 minutes  RUNNING        manager1
+    ```
+
+    The Swarm manager maintains the desired state by ending the task on a node
+    with `Drain` availability and creating a new task on a node with `Active`
+    availability.
+
+7. Run  `docker node update --availability active NODE-ID` to return the drained
+node to an active state:
+
+    ```bash
+    $ docker node update --availability active worker1
+    worker1
+    ```
+
+8. Inspect the node to see the updated state:
+
+   ```
+   $ docker node inspect --pretty worker1
+   ID:			1x2bldyhie1cj
+   Hostname:		worker1
+   Status:
+    State:			READY
+    Availability:		ACTIVE
+  ...snip...
+  ```
+
+  When you set the node back to `Active` availability, it can receive new tasks:
+
+  * during a service update to scale up
+  * during a rolling update
+  * when you set another node to `Drain` availability
+  * when a task fails on another active node
+
+## What's next?
+
+The next topic in the tutorial introduces volumes.
+
+<p style="margin-bottom:300px">&nbsp;</p>

+ 87 - 0
docs/swarm/swarm-tutorial/index.md

@@ -0,0 +1,87 @@
+<!--[metadata]>
++++
+title = "Set up for the tutorial"
+description = "Getting Started tutorial for Docker Swarm"
+keywords = ["tutorial, cluster management, swarm"]
+[menu.main]
+identifier="tutorial-setup"
+parent="swarm-tutorial"
+weight=11
+advisory = "rc"
++++
+<![end-metadata]-->
+
+# Getting Started with Docker Swarm
+
+This tutorial introduces you to the key features of Docker Swarm. It guides you
+through the following activities:
+
+* initializing a cluster of Docker Engines called a Swarm
+* adding nodes to the Swarm
+* deploying application services to the Swarm
+* managing the Swarm once you have everything running
+
+This tutorial uses Docker Engine CLI commands entered on the command line of a
+terminal window. You should be able to install Docker on networked machines and
+be comfortable running commands in the shell of your choice.
+
+If you’re brand new to Docker, see [About Docker Engine](../../index.md).
+
+## Set up
+To run this tutorial, you need the following:
+
+* [three networked host machines](#three-networked-host-machines)
+* [Docker Engine 1.12 or later installed](#docker-engine-1-12-or-later)
+* [the IP address of the manager machine](#the-ip-address-of-the-manager-machine)
+* [open ports between the hosts](#open-ports-between-the-hosts)
+
+### Three networked host machines
+
+The tutorial uses three networked host machines as nodes in the Swarm. These can
+be virtual machines on your PC, in a data center, or on a cloud service
+provider. This tutorial uses the following machine names:
+
+* manager1
+* worker1
+* worker2
+
+###  Docker Engine 1.12 or later
+
+You must install Docker Engine on each one of the host machines. To use this
+version of Swarm, install the Docker Engine `v1.12.0-rc1` or later from the
+[Docker releases GitHub repository](https://github.com/docker/docker/releases).
+Alternatively, install the latest Docker for Mac or Docker for Windows Beta.
+
+Verify that the Docker Engine daemon is running on each of the machines.
+
+<!-- See the following options to install:
+
+* [Install Docker Engine](../../installation/index.md).
+
+* [Example: Manual install on cloud provider](../../installation/cloud/cloud-ex-aws.md).
+-->
+
+### The IP address of the manager machine
+
+The IP address must be assigned to an a network interface available to the host
+operating system. All nodes in the Swarm must be able to access the manager at the IP address.
+
+>**Tip**: You can run `ifconfig` on Linux or Mac OSX to see a list of the
+available network interfaces.
+
+The tutorial uses `manager1` : `192.168.99.100`.
+
+### Open ports between the hosts
+
+* **TCP port 2377** for cluster management communications
+* **TCP** and **UDP port 7946** for communication among nodes
+* **TCP** and **UDP port 4789** for overlay network traffic
+
+>**Tip**: Docker recommends that every node in the cluster be on the same layer
+3 (IP) subnet with all traffic permitted between nodes.
+
+## What's next?
+
+After you have set up your environment, you're ready to [create a Swarm](create-swarm.md).
+
+<p style="margin-bottom:300px">&nbsp;</p>

Some files were not shown because too many files changed in this diff