github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/daemon/cluster/noderunner.go (about)

     1  package cluster // import "github.com/docker/docker/daemon/cluster"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	types "github.com/docker/docker/api/types/swarm"
    13  	"github.com/docker/docker/daemon/cluster/executor/container"
    14  	lncluster "github.com/docker/libnetwork/cluster"
    15  	swarmapi "github.com/docker/swarmkit/api"
    16  	swarmallocator "github.com/docker/swarmkit/manager/allocator/cnmallocator"
    17  	swarmnode "github.com/docker/swarmkit/node"
    18  	"github.com/pkg/errors"
    19  	"github.com/sirupsen/logrus"
    20  	"google.golang.org/grpc"
    21  	"google.golang.org/grpc/codes"
    22  	"google.golang.org/grpc/status"
    23  )
    24  
    25  // nodeRunner implements a manager for continuously running swarmkit node, restarting them with backoff delays if needed.
    26  type nodeRunner struct {
    27  	nodeState
    28  	mu             sync.RWMutex
    29  	done           chan struct{} // closed when swarmNode exits
    30  	ready          chan struct{} // closed when swarmNode becomes active
    31  	reconnectDelay time.Duration
    32  	config         nodeStartConfig
    33  
    34  	repeatedRun     bool
    35  	cancelReconnect func()
    36  	stopping        bool
    37  	cluster         *Cluster // only for accessing config helpers, never call any methods. TODO: change to config struct
    38  }
    39  
    40  // nodeStartConfig holds configuration needed to start a new node. Exported
    41  // fields of this structure are saved to disk in json. Unexported fields
    42  // contain data that shouldn't be persisted between daemon reloads.
    43  type nodeStartConfig struct {
    44  	// LocalAddr is this machine's local IP or hostname, if specified.
    45  	LocalAddr string
    46  	// RemoteAddr is the address that was given to "swarm join". It is used
    47  	// to find LocalAddr if necessary.
    48  	RemoteAddr string
    49  	// ListenAddr is the address we bind to, including a port.
    50  	ListenAddr string
    51  	// AdvertiseAddr is the address other nodes should connect to,
    52  	// including a port.
    53  	AdvertiseAddr string
    54  	// DataPathAddr is the address that has to be used for the data path
    55  	DataPathAddr string
    56  	// DefaultAddressPool contains list of subnets
    57  	DefaultAddressPool []string
    58  	// SubnetSize contains subnet size of DefaultAddressPool
    59  	SubnetSize uint32
    60  	// DataPathPort contains Data path port (VXLAN UDP port) number that is used for data traffic.
    61  	DataPathPort uint32
    62  	// JoinInProgress is set to true if a join operation has started, but
    63  	// not completed yet.
    64  	JoinInProgress bool
    65  
    66  	joinAddr        string
    67  	forceNewCluster bool
    68  	joinToken       string
    69  	lockKey         []byte
    70  	autolock        bool
    71  	availability    types.NodeAvailability
    72  }
    73  
    74  func (n *nodeRunner) Ready() chan error {
    75  	c := make(chan error, 1)
    76  	n.mu.RLock()
    77  	ready, done := n.ready, n.done
    78  	n.mu.RUnlock()
    79  	go func() {
    80  		select {
    81  		case <-ready:
    82  		case <-done:
    83  		}
    84  		select {
    85  		case <-ready:
    86  		default:
    87  			n.mu.RLock()
    88  			c <- n.err
    89  			n.mu.RUnlock()
    90  		}
    91  		close(c)
    92  	}()
    93  	return c
    94  }
    95  
    96  func (n *nodeRunner) Start(conf nodeStartConfig) error {
    97  	n.mu.Lock()
    98  	defer n.mu.Unlock()
    99  
   100  	n.reconnectDelay = initialReconnectDelay
   101  
   102  	return n.start(conf)
   103  }
   104  
   105  func (n *nodeRunner) start(conf nodeStartConfig) error {
   106  	var control string
   107  	if runtime.GOOS == "windows" {
   108  		control = `\\.\pipe\` + controlSocket
   109  	} else {
   110  		control = filepath.Join(n.cluster.runtimeRoot, controlSocket)
   111  	}
   112  
   113  	joinAddr := conf.joinAddr
   114  	if joinAddr == "" && conf.JoinInProgress {
   115  		// We must have been restarted while trying to join a cluster.
   116  		// Continue trying to join instead of forming our own cluster.
   117  		joinAddr = conf.RemoteAddr
   118  	}
   119  
   120  	// Hostname is not set here. Instead, it is obtained from
   121  	// the node description that is reported periodically
   122  	swarmnodeConfig := swarmnode.Config{
   123  		ForceNewCluster:    conf.forceNewCluster,
   124  		ListenControlAPI:   control,
   125  		ListenRemoteAPI:    conf.ListenAddr,
   126  		AdvertiseRemoteAPI: conf.AdvertiseAddr,
   127  		NetworkConfig: &swarmallocator.NetworkConfig{
   128  			DefaultAddrPool: conf.DefaultAddressPool,
   129  			SubnetSize:      conf.SubnetSize,
   130  			VXLANUDPPort:    conf.DataPathPort,
   131  		},
   132  		JoinAddr:  joinAddr,
   133  		StateDir:  n.cluster.root,
   134  		JoinToken: conf.joinToken,
   135  		Executor: container.NewExecutor(
   136  			n.cluster.config.Backend,
   137  			n.cluster.config.PluginBackend,
   138  			n.cluster.config.ImageBackend,
   139  			n.cluster.config.VolumeBackend,
   140  		),
   141  		HeartbeatTick: n.cluster.config.RaftHeartbeatTick,
   142  		// Recommended value in etcd/raft is 10 x (HeartbeatTick).
   143  		// Lower values were seen to have caused instability because of
   144  		// frequent leader elections when running on flakey networks.
   145  		ElectionTick:     n.cluster.config.RaftElectionTick,
   146  		UnlockKey:        conf.lockKey,
   147  		AutoLockManagers: conf.autolock,
   148  		PluginGetter:     n.cluster.config.Backend.PluginGetter(),
   149  	}
   150  	if conf.availability != "" {
   151  		avail, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(conf.availability))]
   152  		if !ok {
   153  			return fmt.Errorf("invalid Availability: %q", conf.availability)
   154  		}
   155  		swarmnodeConfig.Availability = swarmapi.NodeSpec_Availability(avail)
   156  	}
   157  	node, err := swarmnode.New(&swarmnodeConfig)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	if err := node.Start(context.Background()); err != nil {
   162  		return err
   163  	}
   164  
   165  	n.done = make(chan struct{})
   166  	n.ready = make(chan struct{})
   167  	n.swarmNode = node
   168  	if conf.joinAddr != "" {
   169  		conf.JoinInProgress = true
   170  	}
   171  	n.config = conf
   172  	savePersistentState(n.cluster.root, conf)
   173  
   174  	ctx, cancel := context.WithCancel(context.Background())
   175  
   176  	go func() {
   177  		n.handleNodeExit(node)
   178  		cancel()
   179  	}()
   180  
   181  	go n.handleReadyEvent(ctx, node, n.ready)
   182  	go n.handleControlSocketChange(ctx, node)
   183  
   184  	return nil
   185  }
   186  
   187  func (n *nodeRunner) handleControlSocketChange(ctx context.Context, node *swarmnode.Node) {
   188  	for conn := range node.ListenControlSocket(ctx) {
   189  		n.mu.Lock()
   190  		if n.grpcConn != conn {
   191  			if conn == nil {
   192  				n.controlClient = nil
   193  				n.logsClient = nil
   194  			} else {
   195  				n.controlClient = swarmapi.NewControlClient(conn)
   196  				n.logsClient = swarmapi.NewLogsClient(conn)
   197  				// push store changes to daemon
   198  				go n.watchClusterEvents(ctx, conn)
   199  			}
   200  		}
   201  		n.grpcConn = conn
   202  		n.mu.Unlock()
   203  		n.cluster.SendClusterEvent(lncluster.EventSocketChange)
   204  	}
   205  }
   206  
   207  func (n *nodeRunner) watchClusterEvents(ctx context.Context, conn *grpc.ClientConn) {
   208  	client := swarmapi.NewWatchClient(conn)
   209  	watch, err := client.Watch(ctx, &swarmapi.WatchRequest{
   210  		Entries: []*swarmapi.WatchRequest_WatchEntry{
   211  			{
   212  				Kind:   "node",
   213  				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
   214  			},
   215  			{
   216  				Kind:   "service",
   217  				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
   218  			},
   219  			{
   220  				Kind:   "network",
   221  				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
   222  			},
   223  			{
   224  				Kind:   "secret",
   225  				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
   226  			},
   227  			{
   228  				Kind:   "config",
   229  				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
   230  			},
   231  		},
   232  		IncludeOldObject: true,
   233  	})
   234  	if err != nil {
   235  		logrus.WithError(err).Error("failed to watch cluster store")
   236  		return
   237  	}
   238  	for {
   239  		msg, err := watch.Recv()
   240  		if err != nil {
   241  			// store watch is broken
   242  			errStatus, ok := status.FromError(err)
   243  			if !ok || errStatus.Code() != codes.Canceled {
   244  				logrus.WithError(err).Error("failed to receive changes from store watch API")
   245  			}
   246  			return
   247  		}
   248  		select {
   249  		case <-ctx.Done():
   250  			return
   251  		case n.cluster.watchStream <- msg:
   252  		}
   253  	}
   254  }
   255  
   256  func (n *nodeRunner) handleReadyEvent(ctx context.Context, node *swarmnode.Node, ready chan struct{}) {
   257  	select {
   258  	case <-node.Ready():
   259  		n.mu.Lock()
   260  		n.err = nil
   261  		if n.config.JoinInProgress {
   262  			n.config.JoinInProgress = false
   263  			savePersistentState(n.cluster.root, n.config)
   264  		}
   265  		n.mu.Unlock()
   266  		close(ready)
   267  	case <-ctx.Done():
   268  	}
   269  	n.cluster.SendClusterEvent(lncluster.EventNodeReady)
   270  }
   271  
   272  func (n *nodeRunner) handleNodeExit(node *swarmnode.Node) {
   273  	err := detectLockedError(node.Err(context.Background()))
   274  	if err != nil {
   275  		logrus.Errorf("cluster exited with error: %v", err)
   276  	}
   277  	n.mu.Lock()
   278  	n.swarmNode = nil
   279  	n.err = err
   280  	close(n.done)
   281  	select {
   282  	case <-n.ready:
   283  		n.enableReconnectWatcher()
   284  	default:
   285  		if n.repeatedRun {
   286  			n.enableReconnectWatcher()
   287  		}
   288  	}
   289  	n.repeatedRun = true
   290  	n.mu.Unlock()
   291  }
   292  
   293  // Stop stops the current swarm node if it is running.
   294  func (n *nodeRunner) Stop() error {
   295  	n.mu.Lock()
   296  	if n.cancelReconnect != nil { // between restarts
   297  		n.cancelReconnect()
   298  		n.cancelReconnect = nil
   299  	}
   300  	if n.swarmNode == nil {
   301  		// even though the swarm node is nil we still may need
   302  		// to send a node leave event to perform any cleanup required.
   303  		if n.cluster != nil {
   304  			n.cluster.SendClusterEvent(lncluster.EventNodeLeave)
   305  		}
   306  		n.mu.Unlock()
   307  		return nil
   308  	}
   309  	n.stopping = true
   310  	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
   311  	defer cancel()
   312  	n.mu.Unlock()
   313  	if err := n.swarmNode.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
   314  		return err
   315  	}
   316  	n.cluster.SendClusterEvent(lncluster.EventNodeLeave)
   317  	<-n.done
   318  	return nil
   319  }
   320  
   321  func (n *nodeRunner) State() nodeState {
   322  	if n == nil {
   323  		return nodeState{status: types.LocalNodeStateInactive}
   324  	}
   325  	n.mu.RLock()
   326  	defer n.mu.RUnlock()
   327  
   328  	ns := n.nodeState
   329  
   330  	if ns.err != nil || n.cancelReconnect != nil {
   331  		if errors.Cause(ns.err) == errSwarmLocked {
   332  			ns.status = types.LocalNodeStateLocked
   333  		} else {
   334  			ns.status = types.LocalNodeStateError
   335  		}
   336  	} else {
   337  		select {
   338  		case <-n.ready:
   339  			ns.status = types.LocalNodeStateActive
   340  		default:
   341  			ns.status = types.LocalNodeStatePending
   342  		}
   343  	}
   344  
   345  	return ns
   346  }
   347  
   348  func (n *nodeRunner) enableReconnectWatcher() {
   349  	if n.stopping {
   350  		return
   351  	}
   352  	n.reconnectDelay *= 2
   353  	if n.reconnectDelay > maxReconnectDelay {
   354  		n.reconnectDelay = maxReconnectDelay
   355  	}
   356  	logrus.Warnf("Restarting swarm in %.2f seconds", n.reconnectDelay.Seconds())
   357  	delayCtx, cancel := context.WithTimeout(context.Background(), n.reconnectDelay)
   358  	n.cancelReconnect = cancel
   359  
   360  	go func() {
   361  		<-delayCtx.Done()
   362  		if delayCtx.Err() != context.DeadlineExceeded {
   363  			return
   364  		}
   365  		n.mu.Lock()
   366  		defer n.mu.Unlock()
   367  		if n.stopping {
   368  			return
   369  		}
   370  
   371  		if err := n.start(n.config); err != nil {
   372  			n.err = err
   373  		}
   374  	}()
   375  }
   376  
   377  // nodeState represents information about the current state of the cluster and
   378  // provides access to the grpc clients.
   379  type nodeState struct {
   380  	swarmNode       *swarmnode.Node
   381  	grpcConn        *grpc.ClientConn
   382  	controlClient   swarmapi.ControlClient
   383  	logsClient      swarmapi.LogsClient
   384  	status          types.LocalNodeState
   385  	actualLocalAddr string
   386  	err             error
   387  }
   388  
   389  // IsActiveManager returns true if node is a manager ready to accept control requests. It is safe to access the client properties if this returns true.
   390  func (ns nodeState) IsActiveManager() bool {
   391  	return ns.controlClient != nil
   392  }
   393  
   394  // IsManager returns true if node is a manager.
   395  func (ns nodeState) IsManager() bool {
   396  	return ns.swarmNode != nil && ns.swarmNode.Manager() != nil
   397  }
   398  
   399  // NodeID returns node's ID or empty string if node is inactive.
   400  func (ns nodeState) NodeID() string {
   401  	if ns.swarmNode != nil {
   402  		return ns.swarmNode.NodeID()
   403  	}
   404  	return ""
   405  }