|
@@ -4,6 +4,7 @@ import (
|
|
"encoding/json"
|
|
"encoding/json"
|
|
"fmt"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"io/ioutil"
|
|
|
|
+ "net"
|
|
"os"
|
|
"os"
|
|
"path/filepath"
|
|
"path/filepath"
|
|
"strings"
|
|
"strings"
|
|
@@ -13,7 +14,6 @@ import (
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc"
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/Sirupsen/logrus"
|
|
- "github.com/docker/distribution/digest"
|
|
|
|
"github.com/docker/docker/daemon/cluster/convert"
|
|
"github.com/docker/docker/daemon/cluster/convert"
|
|
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
|
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
|
"github.com/docker/docker/daemon/cluster/executor/container"
|
|
"github.com/docker/docker/daemon/cluster/executor/container"
|
|
@@ -32,6 +32,7 @@ import (
|
|
const swarmDirName = "swarm"
|
|
const swarmDirName = "swarm"
|
|
const controlSocket = "control.sock"
|
|
const controlSocket = "control.sock"
|
|
const swarmConnectTimeout = 20 * time.Second
|
|
const swarmConnectTimeout = 20 * time.Second
|
|
|
|
+const swarmRequestTimeout = 20 * time.Second
|
|
const stateFile = "docker-state.json"
|
|
const stateFile = "docker-state.json"
|
|
const defaultAddr = "0.0.0.0:2377"
|
|
const defaultAddr = "0.0.0.0:2377"
|
|
|
|
|
|
@@ -41,16 +42,16 @@ const (
|
|
)
|
|
)
|
|
|
|
|
|
// ErrNoSwarm is returned on leaving a cluster that was never initialized
|
|
// ErrNoSwarm is returned on leaving a cluster that was never initialized
|
|
-var ErrNoSwarm = fmt.Errorf("This node is not part of Swarm")
|
|
|
|
|
|
+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
|
|
// 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 cluster. Use \"docker swarm leave\" to leave this cluster and join another one.")
|
|
|
|
|
|
+var ErrSwarmExists = fmt.Errorf("This node is already part of a swarm cluster. Use \"docker swarm leave\" to leave this cluster and join another one.")
|
|
|
|
|
|
// ErrPendingSwarmExists is returned on initialize or join request for a cluster that is already processing a similar request but has not succeeded yet.
|
|
// ErrPendingSwarmExists is returned on initialize or join request for a cluster that is already processing a similar request but has not succeeded yet.
|
|
var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join request that has not succeeded yet. Use \"docker swarm leave\" to cancel the current request.")
|
|
var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join request that has not succeeded yet. Use \"docker swarm leave\" to cancel the current request.")
|
|
|
|
|
|
// ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
|
|
// ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
|
|
-var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current Swarm status of your node.")
|
|
|
|
|
|
+var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current swarm status of your node.")
|
|
|
|
|
|
// defaultSpec contains some sane defaults if cluster options are missing on init
|
|
// defaultSpec contains some sane defaults if cluster options are missing on init
|
|
var defaultSpec = types.Spec{
|
|
var defaultSpec = types.Spec{
|
|
@@ -73,14 +74,35 @@ var defaultSpec = types.Spec{
|
|
}
|
|
}
|
|
|
|
|
|
type state struct {
|
|
type state struct {
|
|
|
|
+ // LocalAddr is this machine's local IP or hostname, if specified.
|
|
|
|
+ LocalAddr string
|
|
|
|
+ // RemoteAddr is the address that was given to "swarm join. It is used
|
|
|
|
+ // to find LocalAddr if necessary.
|
|
|
|
+ RemoteAddr string
|
|
|
|
+ // ListenAddr is the address we bind to, including a port.
|
|
ListenAddr string
|
|
ListenAddr string
|
|
|
|
+ // AdvertiseAddr is the address other nodes should connect to,
|
|
|
|
+ // including a port.
|
|
|
|
+ AdvertiseAddr string
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// NetworkSubnetsProvider exposes functions for retrieving the subnets
|
|
|
|
+// of networks managed by Docker, so they can be filtered.
|
|
|
|
+type NetworkSubnetsProvider interface {
|
|
|
|
+ V4Subnets() []net.IPNet
|
|
|
|
+ V6Subnets() []net.IPNet
|
|
}
|
|
}
|
|
|
|
|
|
// Config provides values for Cluster.
|
|
// Config provides values for Cluster.
|
|
type Config struct {
|
|
type Config struct {
|
|
- Root string
|
|
|
|
- Name string
|
|
|
|
- Backend executorpkg.Backend
|
|
|
|
|
|
+ Root string
|
|
|
|
+ Name string
|
|
|
|
+ Backend executorpkg.Backend
|
|
|
|
+ NetworkSubnetsProvider NetworkSubnetsProvider
|
|
|
|
+
|
|
|
|
+ // DefaultAdvertiseAddr is the default host/IP or network interface to use
|
|
|
|
+ // if no AdvertiseAddr value is specified.
|
|
|
|
+ DefaultAdvertiseAddr string
|
|
}
|
|
}
|
|
|
|
|
|
// Cluster provides capabilities to participate in a cluster as a worker or a
|
|
// Cluster provides capabilities to participate in a cluster as a worker or a
|
|
@@ -88,13 +110,17 @@ type Config struct {
|
|
type Cluster struct {
|
|
type Cluster struct {
|
|
sync.RWMutex
|
|
sync.RWMutex
|
|
*node
|
|
*node
|
|
- root string
|
|
|
|
- config Config
|
|
|
|
- configEvent chan struct{} // todo: make this array and goroutine safe
|
|
|
|
- listenAddr string
|
|
|
|
- stop bool
|
|
|
|
- err error
|
|
|
|
- cancelDelay func()
|
|
|
|
|
|
+ root string
|
|
|
|
+ config Config
|
|
|
|
+ configEvent chan struct{} // todo: make this array and goroutine safe
|
|
|
|
+ localAddr string
|
|
|
|
+ actualLocalAddr string // after resolution, not persisted
|
|
|
|
+ remoteAddr string
|
|
|
|
+ listenAddr string
|
|
|
|
+ advertiseAddr string
|
|
|
|
+ stop bool
|
|
|
|
+ err error
|
|
|
|
+ cancelDelay func()
|
|
}
|
|
}
|
|
|
|
|
|
type node struct {
|
|
type node struct {
|
|
@@ -126,7 +152,7 @@ func New(config Config) (*Cluster, error) {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
- n, err := c.startNewNode(false, st.ListenAddr, "", "", "", false)
|
|
|
|
|
|
+ n, err := c.startNewNode(false, st.LocalAddr, st.RemoteAddr, st.ListenAddr, st.AdvertiseAddr, "", "")
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
@@ -162,7 +188,12 @@ func (c *Cluster) loadState() (*state, error) {
|
|
}
|
|
}
|
|
|
|
|
|
func (c *Cluster) saveState() error {
|
|
func (c *Cluster) saveState() error {
|
|
- dt, err := json.Marshal(state{ListenAddr: c.listenAddr})
|
|
|
|
|
|
+ dt, err := json.Marshal(state{
|
|
|
|
+ LocalAddr: c.localAddr,
|
|
|
|
+ RemoteAddr: c.remoteAddr,
|
|
|
|
+ ListenAddr: c.listenAddr,
|
|
|
|
+ AdvertiseAddr: c.advertiseAddr,
|
|
|
|
+ })
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -195,7 +226,7 @@ func (c *Cluster) reconnectOnFailure(n *node) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
var err error
|
|
var err error
|
|
- n, err = c.startNewNode(false, c.listenAddr, c.getRemoteAddress(), "", "", false)
|
|
|
|
|
|
+ n, err = c.startNewNode(false, c.localAddr, c.getRemoteAddress(), c.listenAddr, c.advertiseAddr, c.getRemoteAddress(), "")
|
|
if err != nil {
|
|
if err != nil {
|
|
c.err = err
|
|
c.err = err
|
|
close(n.done)
|
|
close(n.done)
|
|
@@ -204,26 +235,55 @@ func (c *Cluster) reconnectOnFailure(n *node) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secret, cahash string, ismanager bool) (*node, error) {
|
|
|
|
|
|
+func (c *Cluster) startNewNode(forceNewCluster bool, localAddr, remoteAddr, listenAddr, advertiseAddr, joinAddr, joinToken string) (*node, error) {
|
|
if err := c.config.Backend.IsSwarmCompatible(); err != nil {
|
|
if err := c.config.Backend.IsSwarmCompatible(); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ actualLocalAddr := localAddr
|
|
|
|
+ if actualLocalAddr == "" {
|
|
|
|
+ // If localAddr was not specified, resolve it automatically
|
|
|
|
+ // based on the route to joinAddr. localAddr can only be left
|
|
|
|
+ // empty on "join".
|
|
|
|
+ listenHost, _, err := net.SplitHostPort(listenAddr)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, fmt.Errorf("could not parse listen address: %v", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ listenAddrIP := net.ParseIP(listenHost)
|
|
|
|
+ if listenAddrIP == nil || !listenAddrIP.IsUnspecified() {
|
|
|
|
+ actualLocalAddr = listenHost
|
|
|
|
+ } else {
|
|
|
|
+ if remoteAddr == "" {
|
|
|
|
+ // Should never happen except using swarms created by
|
|
|
|
+ // old versions that didn't save remoteAddr.
|
|
|
|
+ remoteAddr = "8.8.8.8:53"
|
|
|
|
+ }
|
|
|
|
+ conn, err := net.Dial("udp", remoteAddr)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, fmt.Errorf("could not find local IP address: %v", err)
|
|
|
|
+ }
|
|
|
|
+ localHostPort := conn.LocalAddr().String()
|
|
|
|
+ actualLocalAddr, _, _ = net.SplitHostPort(localHostPort)
|
|
|
|
+ conn.Close()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
c.node = nil
|
|
c.node = nil
|
|
c.cancelDelay = nil
|
|
c.cancelDelay = nil
|
|
c.stop = false
|
|
c.stop = false
|
|
n, err := swarmagent.NewNode(&swarmagent.NodeConfig{
|
|
n, 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,
|
|
|
|
|
|
+ Hostname: c.config.Name,
|
|
|
|
+ ForceNewCluster: forceNewCluster,
|
|
|
|
+ ListenControlAPI: filepath.Join(c.root, controlSocket),
|
|
|
|
+ ListenRemoteAPI: listenAddr,
|
|
|
|
+ AdvertiseRemoteAPI: advertiseAddr,
|
|
|
|
+ JoinAddr: joinAddr,
|
|
|
|
+ StateDir: c.root,
|
|
|
|
+ JoinToken: joinToken,
|
|
|
|
+ Executor: container.NewExecutor(c.config.Backend),
|
|
|
|
+ HeartbeatTick: 1,
|
|
|
|
+ ElectionTick: 3,
|
|
})
|
|
})
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
@@ -238,8 +298,13 @@ func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secre
|
|
reconnectDelay: initialReconnectDelay,
|
|
reconnectDelay: initialReconnectDelay,
|
|
}
|
|
}
|
|
c.node = node
|
|
c.node = node
|
|
|
|
+ c.localAddr = localAddr
|
|
|
|
+ c.actualLocalAddr = actualLocalAddr // not saved
|
|
|
|
+ c.remoteAddr = remoteAddr
|
|
c.listenAddr = listenAddr
|
|
c.listenAddr = listenAddr
|
|
|
|
+ c.advertiseAddr = advertiseAddr
|
|
c.saveState()
|
|
c.saveState()
|
|
|
|
+
|
|
c.config.Backend.SetClusterProvider(c)
|
|
c.config.Backend.SetClusterProvider(c)
|
|
go func() {
|
|
go func() {
|
|
err := n.Err(ctx)
|
|
err := n.Err(ctx)
|
|
@@ -290,7 +355,7 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
|
if node := c.node; node != nil {
|
|
if node := c.node; node != nil {
|
|
if !req.ForceNewCluster {
|
|
if !req.ForceNewCluster {
|
|
c.Unlock()
|
|
c.Unlock()
|
|
- return "", errSwarmExists(node)
|
|
|
|
|
|
+ return "", ErrSwarmExists
|
|
}
|
|
}
|
|
if err := c.stopNode(); err != nil {
|
|
if err := c.stopNode(); err != nil {
|
|
c.Unlock()
|
|
c.Unlock()
|
|
@@ -303,8 +368,49 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
|
return "", err
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.Unlock()
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ advertiseHost, advertisePort, err := c.resolveAdvertiseAddr(req.AdvertiseAddr, listenPort)
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.Unlock()
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ localAddr := listenHost
|
|
|
|
+
|
|
|
|
+ // If the advertise address is not one of the system's
|
|
|
|
+ // addresses, we also require a listen address.
|
|
|
|
+ listenAddrIP := net.ParseIP(listenHost)
|
|
|
|
+ if listenAddrIP != nil && listenAddrIP.IsUnspecified() {
|
|
|
|
+ advertiseIP := net.ParseIP(advertiseHost)
|
|
|
|
+ if advertiseIP == nil {
|
|
|
|
+ // not an IP
|
|
|
|
+ c.Unlock()
|
|
|
|
+ return "", errMustSpecifyListenAddr
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ systemIPs := listSystemIPs()
|
|
|
|
+
|
|
|
|
+ found := false
|
|
|
|
+ for _, systemIP := range systemIPs {
|
|
|
|
+ if systemIP.Equal(advertiseIP) {
|
|
|
|
+ found = true
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if !found {
|
|
|
|
+ c.Unlock()
|
|
|
|
+ return "", errMustSpecifyListenAddr
|
|
|
|
+ }
|
|
|
|
+ localAddr = advertiseIP.String()
|
|
|
|
+ }
|
|
|
|
+
|
|
// todo: check current state existing
|
|
// todo: check current state existing
|
|
- n, err := c.startNewNode(req.ForceNewCluster, req.ListenAddr, "", "", "", false)
|
|
|
|
|
|
+ n, err := c.startNewNode(req.ForceNewCluster, localAddr, "", net.JoinHostPort(listenHost, listenPort), net.JoinHostPort(advertiseHost, advertisePort), "", "")
|
|
if err != nil {
|
|
if err != nil {
|
|
c.Unlock()
|
|
c.Unlock()
|
|
return "", err
|
|
return "", err
|
|
@@ -335,40 +441,47 @@ func (c *Cluster) Join(req types.JoinRequest) error {
|
|
c.Lock()
|
|
c.Lock()
|
|
if node := c.node; node != nil {
|
|
if node := c.node; node != nil {
|
|
c.Unlock()
|
|
c.Unlock()
|
|
- return errSwarmExists(node)
|
|
|
|
|
|
+ return ErrSwarmExists
|
|
}
|
|
}
|
|
if err := validateAndSanitizeJoinRequest(&req); err != nil {
|
|
if err := validateAndSanitizeJoinRequest(&req); err != nil {
|
|
c.Unlock()
|
|
c.Unlock()
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.Unlock()
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var advertiseAddr string
|
|
|
|
+ advertiseHost, advertisePort, err := c.resolveAdvertiseAddr(req.AdvertiseAddr, listenPort)
|
|
|
|
+ // For joining, we don't need to provide an advertise address,
|
|
|
|
+ // since the remote side can detect it.
|
|
|
|
+ if err == nil {
|
|
|
|
+ advertiseAddr = net.JoinHostPort(advertiseHost, advertisePort)
|
|
|
|
+ }
|
|
|
|
+
|
|
// todo: check current state existing
|
|
// todo: check current state existing
|
|
- n, err := c.startNewNode(false, req.ListenAddr, req.RemoteAddrs[0], req.Secret, req.CACertHash, req.Manager)
|
|
|
|
|
|
+ n, err := c.startNewNode(false, "", req.RemoteAddrs[0], net.JoinHostPort(listenHost, listenPort), advertiseAddr, req.RemoteAddrs[0], req.JoinToken)
|
|
if err != nil {
|
|
if err != nil {
|
|
c.Unlock()
|
|
c.Unlock()
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
c.Unlock()
|
|
c.Unlock()
|
|
|
|
|
|
- certificateRequested := n.CertificateRequested()
|
|
|
|
- for {
|
|
|
|
- select {
|
|
|
|
- case <-certificateRequested:
|
|
|
|
- if n.NodeMembership() == swarmapi.NodeMembershipPending {
|
|
|
|
- return fmt.Errorf("Your node is in the process of joining the cluster but needs to be accepted by existing cluster member.\nTo accept this node into cluster run \"docker node accept %v\" in an existing cluster manager. Use \"docker info\" command to see the current Swarm status of your node.", n.NodeID())
|
|
|
|
- }
|
|
|
|
- certificateRequested = nil
|
|
|
|
- case <-time.After(swarmConnectTimeout):
|
|
|
|
- // attempt to connect will continue in background, also reconnecting
|
|
|
|
- go c.reconnectOnFailure(n)
|
|
|
|
- return ErrSwarmJoinTimeoutReached
|
|
|
|
- case <-n.Ready():
|
|
|
|
- go c.reconnectOnFailure(n)
|
|
|
|
- return nil
|
|
|
|
- case <-n.done:
|
|
|
|
- c.RLock()
|
|
|
|
- defer c.RUnlock()
|
|
|
|
- return c.err
|
|
|
|
- }
|
|
|
|
|
|
+ select {
|
|
|
|
+ case <-time.After(swarmConnectTimeout):
|
|
|
|
+ // attempt to connect will continue in background, also reconnecting
|
|
|
|
+ go c.reconnectOnFailure(n)
|
|
|
|
+ return ErrSwarmJoinTimeoutReached
|
|
|
|
+ case <-n.Ready():
|
|
|
|
+ go c.reconnectOnFailure(n)
|
|
|
|
+ return nil
|
|
|
|
+ case <-n.done:
|
|
|
|
+ c.RLock()
|
|
|
|
+ defer c.RUnlock()
|
|
|
|
+ return c.err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -459,9 +572,8 @@ func (c *Cluster) clearState() error {
|
|
return 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
|
|
|
|
|
|
+func (c *Cluster) getRequestContext() (context.Context, func()) { // TODO: not needed when requests don't block on qourum lost
|
|
|
|
+ return context.WithTimeout(context.Background(), swarmRequestTimeout)
|
|
}
|
|
}
|
|
|
|
|
|
// Inspect retrieves the configuration properties of a managed swarm cluster.
|
|
// Inspect retrieves the configuration properties of a managed swarm cluster.
|
|
@@ -473,7 +585,10 @@ func (c *Cluster) Inspect() (types.Swarm, error) {
|
|
return types.Swarm{}, c.errNoManager()
|
|
return types.Swarm{}, c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- swarm, err := getSwarm(c.getRequestContext(), c.client)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ swarm, err := getSwarm(ctx, c.client)
|
|
if err != nil {
|
|
if err != nil {
|
|
return types.Swarm{}, err
|
|
return types.Swarm{}, err
|
|
}
|
|
}
|
|
@@ -486,7 +601,7 @@ func (c *Cluster) Inspect() (types.Swarm, error) {
|
|
}
|
|
}
|
|
|
|
|
|
// Update updates configuration of a managed swarm cluster.
|
|
// Update updates configuration of a managed swarm cluster.
|
|
-func (c *Cluster) Update(version uint64, spec types.Spec) error {
|
|
|
|
|
|
+func (c *Cluster) Update(version uint64, spec types.Spec, flags types.UpdateFlags) error {
|
|
c.RLock()
|
|
c.RLock()
|
|
defer c.RUnlock()
|
|
defer c.RUnlock()
|
|
|
|
|
|
@@ -494,24 +609,31 @@ func (c *Cluster) Update(version uint64, spec types.Spec) error {
|
|
return c.errNoManager()
|
|
return c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- swarm, err := getSwarm(c.getRequestContext(), c.client)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ swarm, err := getSwarm(ctx, c.client)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- swarmSpec, err := convert.SwarmSpecToGRPCandMerge(spec, &swarm.Spec)
|
|
|
|
|
|
+ swarmSpec, err := convert.SwarmSpecToGRPC(spec)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
_, err = c.client.UpdateCluster(
|
|
_, err = c.client.UpdateCluster(
|
|
- c.getRequestContext(),
|
|
|
|
|
|
+ ctx,
|
|
&swarmapi.UpdateClusterRequest{
|
|
&swarmapi.UpdateClusterRequest{
|
|
ClusterID: swarm.ID,
|
|
ClusterID: swarm.ID,
|
|
Spec: &swarmSpec,
|
|
Spec: &swarmSpec,
|
|
ClusterVersion: &swarmapi.Version{
|
|
ClusterVersion: &swarmapi.Version{
|
|
Index: version,
|
|
Index: version,
|
|
},
|
|
},
|
|
|
|
+ Rotation: swarmapi.JoinTokenRotation{
|
|
|
|
+ RotateWorkerToken: flags.RotateWorkerToken,
|
|
|
|
+ RotateManagerToken: flags.RotateManagerToken,
|
|
|
|
+ },
|
|
},
|
|
},
|
|
)
|
|
)
|
|
return err
|
|
return err
|
|
@@ -531,15 +653,22 @@ func (c *Cluster) IsAgent() bool {
|
|
return c.node != nil && c.ready
|
|
return c.node != nil && c.ready
|
|
}
|
|
}
|
|
|
|
|
|
-// GetListenAddress returns the listening address for current manager's
|
|
|
|
-// consensus and dispatcher APIs.
|
|
|
|
-func (c *Cluster) GetListenAddress() string {
|
|
|
|
|
|
+// GetLocalAddress returns the local address.
|
|
|
|
+func (c *Cluster) GetLocalAddress() string {
|
|
c.RLock()
|
|
c.RLock()
|
|
defer c.RUnlock()
|
|
defer c.RUnlock()
|
|
- if c.isActiveManager() {
|
|
|
|
- return c.listenAddr
|
|
|
|
|
|
+ return c.actualLocalAddr
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// GetAdvertiseAddress returns the remotely reachable address of this node.
|
|
|
|
+func (c *Cluster) GetAdvertiseAddress() string {
|
|
|
|
+ c.RLock()
|
|
|
|
+ defer c.RUnlock()
|
|
|
|
+ if c.advertiseAddr != "" {
|
|
|
|
+ advertiseHost, _, _ := net.SplitHostPort(c.advertiseAddr)
|
|
|
|
+ return advertiseHost
|
|
}
|
|
}
|
|
- return ""
|
|
|
|
|
|
+ return c.actualLocalAddr
|
|
}
|
|
}
|
|
|
|
|
|
// GetRemoteAddress returns a known advertise address of a remote manager if
|
|
// GetRemoteAddress returns a known advertise address of a remote manager if
|
|
@@ -573,7 +702,10 @@ func (c *Cluster) ListenClusterEvents() <-chan struct{} {
|
|
|
|
|
|
// Info returns information about the current cluster state.
|
|
// Info returns information about the current cluster state.
|
|
func (c *Cluster) Info() types.Info {
|
|
func (c *Cluster) Info() types.Info {
|
|
- var info types.Info
|
|
|
|
|
|
+ info := types.Info{
|
|
|
|
+ NodeAddr: c.GetAdvertiseAddress(),
|
|
|
|
+ }
|
|
|
|
+
|
|
c.RLock()
|
|
c.RLock()
|
|
defer c.RUnlock()
|
|
defer c.RUnlock()
|
|
|
|
|
|
@@ -591,9 +723,13 @@ func (c *Cluster) Info() types.Info {
|
|
if c.err != nil {
|
|
if c.err != nil {
|
|
info.Error = c.err.Error()
|
|
info.Error = c.err.Error()
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
if c.isActiveManager() {
|
|
if c.isActiveManager() {
|
|
info.ControlAvailable = true
|
|
info.ControlAvailable = true
|
|
- if r, err := c.client.ListNodes(c.getRequestContext(), &swarmapi.ListNodesRequest{}); err == nil {
|
|
|
|
|
|
+ if r, err := c.client.ListNodes(ctx, &swarmapi.ListNodesRequest{}); err == nil {
|
|
info.Nodes = len(r.Nodes)
|
|
info.Nodes = len(r.Nodes)
|
|
for _, n := range r.Nodes {
|
|
for _, n := range r.Nodes {
|
|
if n.ManagerStatus != nil {
|
|
if n.ManagerStatus != nil {
|
|
@@ -601,10 +737,6 @@ func (c *Cluster) Info() types.Info {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- if swarm, err := getSwarm(c.getRequestContext(), c.client); err == nil && swarm != nil {
|
|
|
|
- info.CACertHash = swarm.RootCA.CACertHash
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if c.node != nil {
|
|
if c.node != nil {
|
|
@@ -626,12 +758,12 @@ func (c *Cluster) isActiveManager() bool {
|
|
// Call with read lock.
|
|
// Call with read lock.
|
|
func (c *Cluster) errNoManager() error {
|
|
func (c *Cluster) errNoManager() error {
|
|
if c.node == nil {
|
|
if c.node == nil {
|
|
- return fmt.Errorf("This node is not a Swarm manager. Use \"docker swarm init\" or \"docker swarm join --manager\" to connect this node to Swarm and try again.")
|
|
|
|
|
|
+ return fmt.Errorf("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
|
|
}
|
|
}
|
|
if c.node.Manager() != nil {
|
|
if c.node.Manager() != nil {
|
|
- return fmt.Errorf("This node is not a Swarm manager. Manager is being prepared or has trouble connecting to the cluster.")
|
|
|
|
|
|
+ return fmt.Errorf("This node is not a swarm manager. Manager is being prepared or has trouble connecting to the cluster.")
|
|
}
|
|
}
|
|
- return fmt.Errorf("This node is not a Swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.")
|
|
|
|
|
|
+ return fmt.Errorf("This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.")
|
|
}
|
|
}
|
|
|
|
|
|
// GetServices returns all services of a managed swarm cluster.
|
|
// GetServices returns all services of a managed swarm cluster.
|
|
@@ -647,8 +779,11 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
r, err := c.client.ListServices(
|
|
r, err := c.client.ListServices(
|
|
- c.getRequestContext(),
|
|
|
|
|
|
+ ctx,
|
|
&swarmapi.ListServicesRequest{Filters: filters})
|
|
&swarmapi.ListServicesRequest{Filters: filters})
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
@@ -672,9 +807,10 @@ func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (string
|
|
return "", c.errNoManager()
|
|
return "", c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- ctx := c.getRequestContext()
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
|
|
- err := populateNetworkID(ctx, c.client, &s)
|
|
|
|
|
|
+ err := c.populateNetworkID(ctx, c.client, &s)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", err
|
|
return "", err
|
|
}
|
|
}
|
|
@@ -709,7 +845,10 @@ func (c *Cluster) GetService(input string) (types.Service, error) {
|
|
return types.Service{}, c.errNoManager()
|
|
return types.Service{}, c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- service, err := getService(c.getRequestContext(), c.client, input)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ service, err := getService(ctx, c.client, input)
|
|
if err != nil {
|
|
if err != nil {
|
|
return types.Service{}, err
|
|
return types.Service{}, err
|
|
}
|
|
}
|
|
@@ -725,9 +864,10 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
|
|
return c.errNoManager()
|
|
return c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- ctx := c.getRequestContext()
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
|
|
- err := populateNetworkID(ctx, c.client, &spec)
|
|
|
|
|
|
+ err := c.populateNetworkID(ctx, c.client, &spec)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -746,7 +886,7 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
|
|
} else {
|
|
} else {
|
|
// this is needed because if the encodedAuth isn't being updated then we
|
|
// this is needed because if the encodedAuth isn't being updated then we
|
|
// shouldn't lose it, and continue to use the one that was already present
|
|
// shouldn't lose it, and continue to use the one that was already present
|
|
- currentService, err := getService(c.getRequestContext(), c.client, serviceID)
|
|
|
|
|
|
+ currentService, err := getService(ctx, c.client, serviceID)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -758,7 +898,7 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
|
|
}
|
|
}
|
|
|
|
|
|
_, err = c.client.UpdateService(
|
|
_, err = c.client.UpdateService(
|
|
- c.getRequestContext(),
|
|
|
|
|
|
+ ctx,
|
|
&swarmapi.UpdateServiceRequest{
|
|
&swarmapi.UpdateServiceRequest{
|
|
ServiceID: serviceID,
|
|
ServiceID: serviceID,
|
|
Spec: &serviceSpec,
|
|
Spec: &serviceSpec,
|
|
@@ -779,12 +919,15 @@ func (c *Cluster) RemoveService(input string) error {
|
|
return c.errNoManager()
|
|
return c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- service, err := getService(c.getRequestContext(), c.client, input)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ service, err := getService(ctx, c.client, input)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- if _, err := c.client.RemoveService(c.getRequestContext(), &swarmapi.RemoveServiceRequest{ServiceID: service.ID}); err != nil {
|
|
|
|
|
|
+ if _, err := c.client.RemoveService(ctx, &swarmapi.RemoveServiceRequest{ServiceID: service.ID}); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
return nil
|
|
@@ -803,8 +946,12 @@ func (c *Cluster) GetNodes(options apitypes.NodeListOptions) ([]types.Node, erro
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
r, err := c.client.ListNodes(
|
|
r, err := c.client.ListNodes(
|
|
- c.getRequestContext(),
|
|
|
|
|
|
+ ctx,
|
|
&swarmapi.ListNodesRequest{Filters: filters})
|
|
&swarmapi.ListNodesRequest{Filters: filters})
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
@@ -827,7 +974,10 @@ func (c *Cluster) GetNode(input string) (types.Node, error) {
|
|
return types.Node{}, c.errNoManager()
|
|
return types.Node{}, c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- node, err := getNode(c.getRequestContext(), c.client, input)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ node, err := getNode(ctx, c.client, input)
|
|
if err != nil {
|
|
if err != nil {
|
|
return types.Node{}, err
|
|
return types.Node{}, err
|
|
}
|
|
}
|
|
@@ -848,8 +998,11 @@ func (c *Cluster) UpdateNode(nodeID string, version uint64, spec types.NodeSpec)
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
_, err = c.client.UpdateNode(
|
|
_, err = c.client.UpdateNode(
|
|
- c.getRequestContext(),
|
|
|
|
|
|
+ ctx,
|
|
&swarmapi.UpdateNodeRequest{
|
|
&swarmapi.UpdateNodeRequest{
|
|
NodeID: nodeID,
|
|
NodeID: nodeID,
|
|
Spec: &nodeSpec,
|
|
Spec: &nodeSpec,
|
|
@@ -870,7 +1023,8 @@ func (c *Cluster) RemoveNode(input string) error {
|
|
return c.errNoManager()
|
|
return c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- ctx := c.getRequestContext()
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
|
|
node, err := getNode(ctx, c.client, input)
|
|
node, err := getNode(ctx, c.client, input)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -922,8 +1076,12 @@ func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, erro
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
r, err := c.client.ListTasks(
|
|
r, err := c.client.ListTasks(
|
|
- c.getRequestContext(),
|
|
|
|
|
|
+ ctx,
|
|
&swarmapi.ListTasksRequest{Filters: filters})
|
|
&swarmapi.ListTasksRequest{Filters: filters})
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
@@ -946,7 +1104,10 @@ func (c *Cluster) GetTask(input string) (types.Task, error) {
|
|
return types.Task{}, c.errNoManager()
|
|
return types.Task{}, c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- task, err := getTask(c.getRequestContext(), c.client, input)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ task, err := getTask(ctx, c.client, input)
|
|
if err != nil {
|
|
if err != nil {
|
|
return types.Task{}, err
|
|
return types.Task{}, err
|
|
}
|
|
}
|
|
@@ -962,7 +1123,10 @@ func (c *Cluster) GetNetwork(input string) (apitypes.NetworkResource, error) {
|
|
return apitypes.NetworkResource{}, c.errNoManager()
|
|
return apitypes.NetworkResource{}, c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- network, err := getNetwork(c.getRequestContext(), c.client, input)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ network, err := getNetwork(ctx, c.client, input)
|
|
if err != nil {
|
|
if err != nil {
|
|
return apitypes.NetworkResource{}, err
|
|
return apitypes.NetworkResource{}, err
|
|
}
|
|
}
|
|
@@ -978,7 +1142,10 @@ func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
|
|
return nil, c.errNoManager()
|
|
return nil, c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- r, err := c.client.ListNetworks(c.getRequestContext(), &swarmapi.ListNetworksRequest{})
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ r, err := c.client.ListNetworks(ctx, &swarmapi.ListNetworksRequest{})
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
@@ -1006,8 +1173,11 @@ func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error)
|
|
return "", errors.NewRequestForbiddenError(err)
|
|
return "", errors.NewRequestForbiddenError(err)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
networkSpec := convert.BasicNetworkCreateToGRPC(s)
|
|
networkSpec := convert.BasicNetworkCreateToGRPC(s)
|
|
- r, err := c.client.CreateNetwork(c.getRequestContext(), &swarmapi.CreateNetworkRequest{Spec: &networkSpec})
|
|
|
|
|
|
+ r, err := c.client.CreateNetwork(ctx, &swarmapi.CreateNetworkRequest{Spec: &networkSpec})
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", err
|
|
return "", err
|
|
}
|
|
}
|
|
@@ -1024,21 +1194,28 @@ func (c *Cluster) RemoveNetwork(input string) error {
|
|
return c.errNoManager()
|
|
return c.errNoManager()
|
|
}
|
|
}
|
|
|
|
|
|
- network, err := getNetwork(c.getRequestContext(), c.client, input)
|
|
|
|
|
|
+ ctx, cancel := c.getRequestContext()
|
|
|
|
+ defer cancel()
|
|
|
|
+
|
|
|
|
+ network, err := getNetwork(ctx, c.client, input)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- if _, err := c.client.RemoveNetwork(c.getRequestContext(), &swarmapi.RemoveNetworkRequest{NetworkID: network.ID}); err != nil {
|
|
|
|
|
|
+ if _, err := c.client.RemoveNetwork(ctx, &swarmapi.RemoveNetworkRequest{NetworkID: network.ID}); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func populateNetworkID(ctx context.Context, c swarmapi.ControlClient, s *types.ServiceSpec) error {
|
|
|
|
|
|
+func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.ControlClient, s *types.ServiceSpec) error {
|
|
for i, n := range s.Networks {
|
|
for i, n := range s.Networks {
|
|
- apiNetwork, err := getNetwork(ctx, c, n.Target)
|
|
|
|
|
|
+ apiNetwork, err := getNetwork(ctx, client, n.Target)
|
|
if err != nil {
|
|
if err != nil {
|
|
|
|
+ if ln, _ := c.config.Backend.FindNetwork(n.Target); ln != nil && !ln.Info().Dynamic() {
|
|
|
|
+ err = fmt.Errorf("network %s is not eligible for docker services", ln.Name())
|
|
|
|
+ return errors.NewRequestForbiddenError(err)
|
|
|
|
+ }
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
s.Networks[i].Target = apiNetwork.ID
|
|
s.Networks[i].Target = apiNetwork.ID
|
|
@@ -1095,7 +1272,8 @@ func (c *Cluster) Cleanup() {
|
|
}
|
|
}
|
|
|
|
|
|
func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, err error) {
|
|
func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, err error) {
|
|
- ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
|
|
|
|
|
|
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
+ defer cancel()
|
|
nodes, err := c.client.ListNodes(ctx, &swarmapi.ListNodesRequest{})
|
|
nodes, err := c.client.ListNodes(ctx, &swarmapi.ListNodesRequest{})
|
|
if err != nil {
|
|
if err != nil {
|
|
return false, 0, 0, err
|
|
return false, 0, 0, err
|
|
@@ -1167,11 +1345,6 @@ func validateAndSanitizeJoinRequest(req *types.JoinRequest) error {
|
|
return fmt.Errorf("invalid remoteAddr %q: %v", req.RemoteAddrs[i], err)
|
|
return fmt.Errorf("invalid remoteAddr %q: %v", req.RemoteAddrs[i], err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if req.CACertHash != "" {
|
|
|
|
- if _, err := digest.ParseDigest(req.CACertHash); err != nil {
|
|
|
|
- return fmt.Errorf("invalid CACertHash %q, %v", req.CACertHash, err)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1186,13 +1359,6 @@ func validateAddr(addr string) (string, error) {
|
|
return strings.TrimPrefix(newaddr, "tcp://"), nil
|
|
return strings.TrimPrefix(newaddr, "tcp://"), nil
|
|
}
|
|
}
|
|
|
|
|
|
-func errSwarmExists(node *node) error {
|
|
|
|
- if node.NodeMembership() != swarmapi.NodeMembershipAccepted {
|
|
|
|
- return ErrPendingSwarmExists
|
|
|
|
- }
|
|
|
|
- return ErrSwarmExists
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
func initClusterSpec(node *node, spec types.Spec) error {
|
|
func initClusterSpec(node *node, spec types.Spec) error {
|
|
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
|
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
|
for conn := range node.ListenControlSocket(ctx) {
|
|
for conn := range node.ListenControlSocket(ctx) {
|
|
@@ -1217,7 +1383,7 @@ func initClusterSpec(node *node, spec types.Spec) error {
|
|
cluster = lcr.Clusters[0]
|
|
cluster = lcr.Clusters[0]
|
|
break
|
|
break
|
|
}
|
|
}
|
|
- newspec, err := convert.SwarmSpecToGRPCandMerge(spec, &cluster.Spec)
|
|
|
|
|
|
+ newspec, err := convert.SwarmSpecToGRPC(spec)
|
|
if err != nil {
|
|
if err != nil {
|
|
return fmt.Errorf("error updating cluster settings: %v", err)
|
|
return fmt.Errorf("error updating cluster settings: %v", err)
|
|
}
|
|
}
|