// removeConsulServer is used to try to remove a consul server that has left func (s *Server) removeConsulServer(m serf.Member, port int) error { // TODO (slackpad) - This will need to be changed once we support node IDs. addr := (&net.TCPAddr{IP: m.Addr, Port: port}).String() // See if it's already in the configuration. It's harmless to re-remove it // but we want to avoid doing that if possible to prevent useless Raft // log entries. configFuture := s.raft.GetConfiguration() if err := configFuture.Error(); err != nil { s.logger.Printf("[ERR] consul: failed to get raft configuration: %v", err) return err } for _, server := range configFuture.Configuration().Servers { if server.Address == raft.ServerAddress(addr) { goto REMOVE } } return nil REMOVE: // Attempt to remove as a peer. future := s.raft.RemovePeer(raft.ServerAddress(addr)) if err := future.Error(); err != nil { s.logger.Printf("[ERR] consul: failed to remove raft peer '%v': %v", addr, err) return err } return nil }
// OperatorRaftPeer supports actions on Raft peers. Currently we only support // removing peers by address. func (s *HTTPServer) OperatorRaftPeer(resp http.ResponseWriter, req *http.Request) (interface{}, error) { if req.Method != "DELETE" { resp.WriteHeader(http.StatusMethodNotAllowed) return nil, nil } var args structs.RaftPeerByAddressRequest s.parseDC(req, &args.Datacenter) s.parseToken(req, &args.Token) params := req.URL.Query() if _, ok := params["address"]; ok { args.Address = raft.ServerAddress(params.Get("address")) } else { resp.WriteHeader(http.StatusBadRequest) resp.Write([]byte("Must specify ?address with IP:port of peer to remove")) return nil, nil } var reply struct{} if err := s.agent.RPC("Operator.RaftRemovePeerByAddress", &args, &reply); err != nil { return nil, err } return nil, nil }
func TestOperator_RaftRemovePeerByAddress(t *testing.T) { dir1, s1 := testServer(t) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") // Try to remove a peer that's not there. arg := structs.RaftPeerByAddressRequest{ Datacenter: "dc1", Address: raft.ServerAddress(fmt.Sprintf("127.0.0.1:%d", getPort())), } var reply struct{} err := msgpackrpc.CallWithCodec(codec, "Operator.RaftRemovePeerByAddress", &arg, &reply) if err == nil || !strings.Contains(err.Error(), "not found in the Raft configuration") { t.Fatalf("err: %v", err) } // Add it manually to Raft. { future := s1.raft.AddPeer(arg.Address) if err := future.Error(); err != nil { t.Fatalf("err: %v", err) } } // Make sure it's there. { future := s1.raft.GetConfiguration() if err := future.Error(); err != nil { t.Fatalf("err: %v", err) } configuration := future.Configuration() if len(configuration.Servers) != 2 { t.Fatalf("bad: %v", configuration) } } // Remove it, now it should go through. if err := msgpackrpc.CallWithCodec(codec, "Operator.RaftRemovePeerByAddress", &arg, &reply); err != nil { t.Fatalf("err: %v", err) } // Make sure it's not there. { future := s1.raft.GetConfiguration() if err := future.Error(); err != nil { t.Fatalf("err: %v", err) } configuration := future.Configuration() if len(configuration.Servers) != 1 { t.Fatalf("bad: %v", configuration) } } }
// joinConsulServer is used to try to join another consul server func (s *Server) joinConsulServer(m serf.Member, parts *agent.Server) error { // Do not join ourself if m.Name == s.config.NodeName { return nil } // Check for possibility of multiple bootstrap nodes if parts.Bootstrap { members := s.serfLAN.Members() for _, member := range members { valid, p := agent.IsConsulServer(member) if valid && member.Name != m.Name && p.Bootstrap { s.logger.Printf("[ERR] consul: '%v' and '%v' are both in bootstrap mode. Only one node should be in bootstrap mode, not adding Raft peer.", m.Name, member.Name) return nil } } } // TODO (slackpad) - This will need to be changed once we support node IDs. addr := (&net.TCPAddr{IP: m.Addr, Port: parts.Port}).String() // See if it's already in the configuration. It's harmless to re-add it // but we want to avoid doing that if possible to prevent useless Raft // log entries. configFuture := s.raft.GetConfiguration() if err := configFuture.Error(); err != nil { s.logger.Printf("[ERR] consul: failed to get raft configuration: %v", err) return err } for _, server := range configFuture.Configuration().Servers { if server.Address == raft.ServerAddress(addr) { return nil } } // Attempt to add as a peer addFuture := s.raft.AddPeer(raft.ServerAddress(addr)) if err := addFuture.Error(); err != nil { s.logger.Printf("[ERR] consul: failed to add raft peer: %v", err) return err } return nil }
func TestOperator_RaftRemovePeerByAddress_ACLDeny(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc1" c.ACLMasterToken = "root" c.ACLDefaultPolicy = "deny" }) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") // Make a request with no token to make sure it gets denied. arg := structs.RaftPeerByAddressRequest{ Datacenter: "dc1", Address: raft.ServerAddress(s1.config.RPCAddr.String()), } var reply struct{} err := msgpackrpc.CallWithCodec(codec, "Operator.RaftRemovePeerByAddress", &arg, &reply) if err == nil || !strings.Contains(err.Error(), permissionDenied) { t.Fatalf("err: %v", err) } // Create an ACL with operator write permissions. var token string { var rules = ` operator = "write" ` req := structs.ACLRequest{ Datacenter: "dc1", Op: structs.ACLSet, ACL: structs.ACL{ Name: "User token", Type: structs.ACLTypeClient, Rules: rules, }, WriteRequest: structs.WriteRequest{Token: "root"}, } if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token); err != nil { t.Fatalf("err: %v", err) } } // Now it should kick back for being an invalid config, which means it // tried to do the operation. arg.Token = token err = msgpackrpc.CallWithCodec(codec, "Operator.RaftRemovePeerByAddress", &arg, &reply) if err == nil || !strings.Contains(err.Error(), "at least one voter") { t.Fatalf("err: %v", err) } }
// RaftGetConfiguration is used to retrieve the current Raft configuration. func (op *Operator) RaftGetConfiguration(args *structs.DCSpecificRequest, reply *structs.RaftConfigurationResponse) error { if done, err := op.srv.forward("Operator.RaftGetConfiguration", args, args, reply); done { return err } // This action requires operator read access. acl, err := op.srv.resolveToken(args.Token) if err != nil { return err } if acl != nil && !acl.OperatorRead() { return permissionDeniedErr } // We can't fetch the leader and the configuration atomically with // the current Raft API. future := op.srv.raft.GetConfiguration() if err := future.Error(); err != nil { return err } // Index the Consul information about the servers. serverMap := make(map[raft.ServerAddress]serf.Member) for _, member := range op.srv.serfLAN.Members() { valid, parts := agent.IsConsulServer(member) if !valid { continue } addr := (&net.TCPAddr{IP: member.Addr, Port: parts.Port}).String() serverMap[raft.ServerAddress(addr)] = member } // Fill out the reply. leader := op.srv.raft.Leader() reply.Index = future.Index() for _, server := range future.Configuration().Servers { node := "(unknown)" if member, ok := serverMap[server.Address]; ok { node = member.Name } entry := &structs.RaftServer{ ID: server.ID, Node: node, Address: server.Address, Leader: server.Address == leader, Voter: server.Suffrage == raft.Voter, } reply.Servers = append(reply.Servers, entry) } return nil }
// lanNodeFailed is used to handle fail events on the LAN pool. func (s *Server) lanNodeFailed(me serf.MemberEvent) { for _, m := range me.Members { ok, parts := agent.IsConsulServer(m) if !ok { continue } s.logger.Printf("[INFO] consul: Removing LAN server %s", parts) s.localLock.Lock() delete(s.localConsuls, raft.ServerAddress(parts.Addr.String())) s.localLock.Unlock() } }
func TestArchive(t *testing.T) { // Create some fake snapshot data. metadata := raft.SnapshotMeta{ Index: 2005, Term: 2011, Configuration: raft.Configuration{ Servers: []raft.Server{ raft.Server{ Suffrage: raft.Voter, ID: raft.ServerID("hello"), Address: raft.ServerAddress("127.0.0.1:8300"), }, }, }, Size: 1024, } var snap bytes.Buffer var expected bytes.Buffer both := io.MultiWriter(&snap, &expected) if _, err := io.Copy(both, io.LimitReader(rand.Reader, 1024)); err != nil { t.Fatalf("err: %v", err) } // Write out the snapshot. var archive bytes.Buffer if err := write(&archive, &metadata, &snap); err != nil { t.Fatalf("err: %v", err) } // Read the snapshot back. var newMeta raft.SnapshotMeta var newSnap bytes.Buffer if err := read(&archive, &newMeta, &newSnap); err != nil { t.Fatalf("err: %v", err) } // Check the contents. if !reflect.DeepEqual(newMeta, metadata) { t.Fatalf("bad: %#v", newMeta) } var buf bytes.Buffer if _, err := io.Copy(&buf, &newSnap); err != nil { t.Fatalf("err: %v", err) } if !bytes.Equal(buf.Bytes(), expected.Bytes()) { t.Fatalf("snapshot contents didn't match") } }
// lanNodeJoin is used to handle join events on the LAN pool. func (s *Server) lanNodeJoin(me serf.MemberEvent) { for _, m := range me.Members { ok, parts := agent.IsConsulServer(m) if !ok { continue } s.logger.Printf("[INFO] consul: Adding LAN server %s", parts) // See if it's configured as part of our DC. if parts.Datacenter == s.config.Datacenter { s.localLock.Lock() s.localConsuls[raft.ServerAddress(parts.Addr.String())] = parts s.localLock.Unlock() } // If we still expecting to bootstrap, may need to handle this. if s.config.BootstrapExpect != 0 { s.maybeBootstrap() } } }
// maybeBootsrap is used to handle bootstrapping when a new consul server joins func (s *Server) maybeBootstrap() { // Bootstrap can only be done if there are no committed logs, remove our // expectations of bootstrapping. This is slightly cheaper than the full // check that BootstrapCluster will do, so this is a good pre-filter. index, err := s.raftStore.LastIndex() if err != nil { s.logger.Printf("[ERR] consul: Failed to read last raft index: %v", err) return } if index != 0 { s.config.BootstrapExpect = 0 return } // Scan for all the known servers. members := s.serfLAN.Members() addrs := make([]string, 0) for _, member := range members { valid, p := agent.IsConsulServer(member) if !valid { continue } if p.Datacenter != s.config.Datacenter { s.logger.Printf("[ERR] consul: Member %v has a conflicting datacenter, ignoring", member) continue } if p.Expect != 0 && p.Expect != s.config.BootstrapExpect { s.logger.Printf("[ERR] consul: Member %v has a conflicting expect value. All nodes should expect the same number.", member) return } if p.Bootstrap { s.logger.Printf("[ERR] consul: Member %v has bootstrap mode. Expect disabled.", member) return } addr := &net.TCPAddr{IP: member.Addr, Port: p.Port} addrs = append(addrs, addr.String()) } // Skip if we haven't met the minimum expect count. if len(addrs) < s.config.BootstrapExpect { return } // Attempt a live bootstrap! var configuration raft.Configuration for _, addr := range addrs { // TODO (slackpad) - This will need to be updated once we support // node IDs. server := raft.Server{ ID: raft.ServerID(addr), Address: raft.ServerAddress(addr), } configuration.Servers = append(configuration.Servers, server) } s.logger.Printf("[INFO] consul: Found expected number of peers (%s), attempting to bootstrap cluster...", strings.Join(addrs, ",")) future := s.raft.BootstrapCluster(configuration) if err := future.Error(); err != nil { s.logger.Printf("[ERR] consul: Failed to bootstrap cluster: %v", err) } // Bootstrapping complete, don't enter this again. s.config.BootstrapExpect = 0 }
// maybeBootstrap is used to handle bootstrapping when a new consul server joins. func (s *Server) maybeBootstrap() { // Bootstrap can only be done if there are no committed logs, remove our // expectations of bootstrapping. This is slightly cheaper than the full // check that BootstrapCluster will do, so this is a good pre-filter. index, err := s.raftStore.LastIndex() if err != nil { s.logger.Printf("[ERR] consul: Failed to read last raft index: %v", err) return } if index != 0 { s.logger.Printf("[INFO] consul: Raft data found, disabling bootstrap mode") s.config.BootstrapExpect = 0 return } // Scan for all the known servers. members := s.serfLAN.Members() var servers []agent.Server for _, member := range members { valid, p := agent.IsConsulServer(member) if !valid { continue } if p.Datacenter != s.config.Datacenter { s.logger.Printf("[ERR] consul: Member %v has a conflicting datacenter, ignoring", member) continue } if p.Expect != 0 && p.Expect != s.config.BootstrapExpect { s.logger.Printf("[ERR] consul: Member %v has a conflicting expect value. All nodes should expect the same number.", member) return } if p.Bootstrap { s.logger.Printf("[ERR] consul: Member %v has bootstrap mode. Expect disabled.", member) return } servers = append(servers, *p) } // Skip if we haven't met the minimum expect count. if len(servers) < s.config.BootstrapExpect { return } // Query each of the servers and make sure they report no Raft peers. for _, server := range servers { var peers []string // Retry with exponential backoff to get peer status from this server for attempt := uint(0); attempt < maxPeerRetries; attempt++ { if err := s.connPool.RPC(s.config.Datacenter, server.Addr, server.Version, "Status.Peers", &struct{}{}, &peers); err != nil { nextRetry := time.Duration((1 << attempt) * peerRetryBase) s.logger.Printf("[ERR] consul: Failed to confirm peer status for %s: %v. Retrying in "+ "%v...", server.Name, err, nextRetry.String()) time.Sleep(nextRetry) } else { break } } // Found a node with some Raft peers, stop bootstrap since there's // evidence of an existing cluster. We should get folded in by the // existing servers if that's the case, so it's cleaner to sit as a // candidate with no peers so we don't cause spurious elections. // It's OK this is racy, because even with an initial bootstrap // as long as one peer runs bootstrap things will work, and if we // have multiple peers bootstrap in the same way, that's OK. We // just don't want a server added much later to do a live bootstrap // and interfere with the cluster. This isn't required for Raft's // correctness because no server in the existing cluster will vote // for this server, but it makes things much more stable. if len(peers) > 0 { s.logger.Printf("[INFO] consul: Existing Raft peers reported by %s, disabling bootstrap mode", server.Name) s.config.BootstrapExpect = 0 return } } // Attempt a live bootstrap! var configuration raft.Configuration var addrs []string for _, server := range servers { addr := server.Addr.String() addrs = append(addrs, addr) peer := raft.Server{ ID: raft.ServerID(addr), Address: raft.ServerAddress(addr), } configuration.Servers = append(configuration.Servers, peer) } s.logger.Printf("[INFO] consul: Found expected number of peers, attempting bootstrap: %s", strings.Join(addrs, ",")) future := s.raft.BootstrapCluster(configuration) if err := future.Error(); err != nil { s.logger.Printf("[ERR] consul: Failed to bootstrap cluster: %v", err) } // Bootstrapping complete, or failed for some reason, don't enter this // again. s.config.BootstrapExpect = 0 }