diff --git a/manager/manager.go b/manager/manager.go index d17e8ec231..517b37817e 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -86,7 +86,11 @@ type Config struct { // cluster to join. JoinRaft string - // Top-level state directory + // ForceJoin causes us to invoke raft's Join RPC even if already part + // of a cluster. + ForceJoin bool + + // StateDir is the top-level state directory StateDir string // ForceNewCluster defines if we have to force a new cluster @@ -201,6 +205,7 @@ func New(config *Config) (*Manager, error) { newNodeOpts := raft.NodeOptions{ ID: config.SecurityConfig.ClientTLSCreds.NodeID(), JoinAddr: config.JoinRaft, + ForceJoin: config.ForceJoin, Config: raftCfg, StateDir: raftStateDir, ForceNewCluster: config.ForceNewCluster, diff --git a/manager/state/raft/raft.go b/manager/state/raft/raft.go index 89f19b5cdd..9422c0e3b4 100644 --- a/manager/state/raft/raft.go +++ b/manager/state/raft/raft.go @@ -166,6 +166,8 @@ type NodeOptions struct { // JoinAddr is the cluster to join. May be an empty string to create // a standalone cluster. JoinAddr string + // ForceJoin tells us to join even if already part of a cluster. + ForceJoin bool // Config is the raft config. Config *raft.Config // StateDir is the directory to store durable state. @@ -393,8 +395,10 @@ func (n *Node) JoinAndStart(ctx context.Context) (err error) { // restore from snapshot if loadAndStartErr == nil { - if n.opts.JoinAddr != "" { - log.G(ctx).Warning("ignoring request to join cluster, because raft state already exists") + if n.opts.JoinAddr != "" && n.opts.ForceJoin { + if err := n.joinCluster(ctx); err != nil { + return errors.Wrap(err, "failed to rejoin cluster") + } } n.campaignWhenAble = true n.initTransport() @@ -402,7 +406,6 @@ func (n *Node) JoinAndStart(ctx context.Context) (err error) { return nil } - // first member of cluster if n.opts.JoinAddr == "" { // First member in the cluster, self-assign ID n.Config.ID = uint64(rand.Int63()) + 1 @@ -417,6 +420,22 @@ func (n *Node) JoinAndStart(ctx context.Context) (err error) { } // join to existing cluster + + if err := n.joinCluster(ctx); err != nil { + return err + } + + if _, err := n.newRaftLogs(n.opts.ID); err != nil { + return err + } + + n.initTransport() + n.raftNode = raft.StartNode(n.Config, nil) + + return nil +} + +func (n *Node) joinCluster(ctx context.Context) error { if n.opts.Addr == "" { return errors.New("attempted to join raft cluster without knowing own address") } @@ -438,15 +457,7 @@ func (n *Node) JoinAndStart(ctx context.Context) (err error) { } n.Config.ID = resp.RaftID - - if _, err := n.newRaftLogs(n.opts.ID); err != nil { - return err - } n.bootstrapMembers = resp.Members - - n.initTransport() - n.raftNode = raft.StartNode(n.Config, nil) - return nil } diff --git a/node/node.go b/node/node.go index 77fe5b3d75..68f81f0b92 100644 --- a/node/node.go +++ b/node/node.go @@ -828,14 +828,22 @@ func (n *Node) runManager(ctx context.Context, securityConfig *ca.SecurityConfig } } - remoteAddr, _ := n.remotes.Select(n.NodeID()) + joinAddr := n.config.JoinAddr + if joinAddr == "" { + remoteAddr, err := n.remotes.Select(n.NodeID()) + if err == nil { + joinAddr = remoteAddr.Addr + } + } + m, err := manager.New(&manager.Config{ ForceNewCluster: n.config.ForceNewCluster, RemoteAPI: remoteAPI, ControlAPI: n.config.ListenControlAPI, SecurityConfig: securityConfig, ExternalCAs: n.config.ExternalCAs, - JoinRaft: remoteAddr.Addr, + JoinRaft: joinAddr, + ForceJoin: n.config.JoinAddr != "", StateDir: n.config.StateDir, HeartbeatTick: n.config.HeartbeatTick, ElectionTick: n.config.ElectionTick,