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") } }
// makeRaft returns a Raft and its FSM, with snapshots based in the given dir. func makeRaft(t *testing.T, dir string) (*raft.Raft, *MockFSM) { snaps, err := raft.NewFileSnapshotStore(dir, 5, nil) if err != nil { t.Fatalf("err: %v", err) } fsm := &MockFSM{} store := raft.NewInmemStore() addr, trans := raft.NewInmemTransport("") config := raft.DefaultConfig() config.LocalID = raft.ServerID(fmt.Sprintf("server-%s", addr)) var members raft.Configuration members.Servers = append(members.Servers, raft.Server{ Suffrage: raft.Voter, ID: config.LocalID, Address: addr, }) err = raft.BootstrapCluster(config, store, store, snaps, trans, members) if err != nil { t.Fatalf("err: %v", err) } raft, err := raft.NewRaft(config, fsm, store, store, snaps, trans) if err != nil { t.Fatalf("err: %v", err) } timeout := time.After(10 * time.Second) for { if raft.Leader() != "" { break } select { case <-raft.LeaderCh(): case <-time.After(1 * time.Second): // Need to poll because we might have missed the first // go with the leader channel. case <-timeout: t.Fatalf("timed out waiting for leader") } } return raft, fsm }
// setupRaft is used to setup and initialize Raft func (s *Server) setupRaft() error { // If we have an unclean exit then attempt to close the Raft store. defer func() { if s.raft == nil && s.raftStore != nil { if err := s.raftStore.Close(); err != nil { s.logger.Printf("[ERR] consul: failed to close Raft store: %v", err) } } }() // Create the FSM. var err error s.fsm, err = NewFSM(s.tombstoneGC, s.config.LogOutput) if err != nil { return err } // Create a transport layer. trans := raft.NewNetworkTransport(s.raftLayer, 3, 10*time.Second, s.config.LogOutput) s.raftTransport = trans // Make sure we set the LogOutput. s.config.RaftConfig.LogOutput = s.config.LogOutput // Our version of Raft protocol requires the LocalID to match the network // address of the transport. s.config.RaftConfig.LocalID = raft.ServerID(trans.LocalAddr()) // Build an all in-memory setup for dev mode, otherwise prepare a full // disk-based setup. var log raft.LogStore var stable raft.StableStore var snap raft.SnapshotStore if s.config.DevMode { store := raft.NewInmemStore() s.raftInmem = store stable = store log = store snap = raft.NewInmemSnapshotStore() } else { // Create the base raft path. path := filepath.Join(s.config.DataDir, raftState) if err := ensurePath(path, true); err != nil { return err } // Create the backend raft store for logs and stable storage. store, err := raftboltdb.NewBoltStore(filepath.Join(path, "raft.db")) if err != nil { return err } s.raftStore = store stable = store // Wrap the store in a LogCache to improve performance. cacheStore, err := raft.NewLogCache(raftLogCacheSize, store) if err != nil { return err } log = cacheStore // Create the snapshot store. snapshots, err := raft.NewFileSnapshotStore(path, snapshotsRetained, s.config.LogOutput) if err != nil { return err } snap = snapshots // For an existing cluster being upgraded to the new version of // Raft, we almost never want to run recovery based on the old // peers.json file. We create a peers.info file with a helpful // note about where peers.json went, and use that as a sentinel // to avoid ingesting the old one that first time (if we have to // create the peers.info file because it's not there, we also // blow away any existing peers.json file). peersFile := filepath.Join(path, "peers.json") peersInfoFile := filepath.Join(path, "peers.info") if _, err := os.Stat(peersInfoFile); os.IsNotExist(err) { if err := ioutil.WriteFile(peersInfoFile, []byte(peersInfoContent), 0755); err != nil { return fmt.Errorf("failed to write peers.info file: %v", err) } // Blow away the peers.json file if present, since the // peers.info sentinel wasn't there. if _, err := os.Stat(peersFile); err == nil { if err := os.Remove(peersFile); err != nil { return fmt.Errorf("failed to delete peers.json, please delete manually (see peers.info for details): %v", err) } s.logger.Printf("[INFO] consul: deleted peers.json file (see peers.info for details)") } } else if _, err := os.Stat(peersFile); err == nil { s.logger.Printf("[INFO] consul: found peers.json file, recovering Raft configuration...") configuration, err := raft.ReadPeersJSON(peersFile) if err != nil { return fmt.Errorf("recovery failed to parse peers.json: %v", err) } tmpFsm, err := NewFSM(s.tombstoneGC, s.config.LogOutput) if err != nil { return fmt.Errorf("recovery failed to make temp FSM: %v", err) } if err := raft.RecoverCluster(s.config.RaftConfig, tmpFsm, log, stable, snap, trans, configuration); err != nil { return fmt.Errorf("recovery failed: %v", err) } if err := os.Remove(peersFile); err != nil { return fmt.Errorf("recovery failed to delete peers.json, please delete manually (see peers.info for details): %v", err) } s.logger.Printf("[INFO] consul: deleted peers.json file after successful recovery") } } // If we are in bootstrap or dev mode and the state is clean then we can // bootstrap now. if s.config.Bootstrap || s.config.DevMode { hasState, err := raft.HasExistingState(log, stable, snap) if err != nil { return err } if !hasState { // TODO (slackpad) - This will need to be updated when // we add support for node IDs. configuration := raft.Configuration{ Servers: []raft.Server{ raft.Server{ ID: raft.ServerID(trans.LocalAddr()), Address: trans.LocalAddr(), }, }, } if err := raft.BootstrapCluster(s.config.RaftConfig, log, stable, snap, trans, configuration); err != nil { return err } } } // Setup the Raft store. s.raft, err = raft.NewRaft(s.config.RaftConfig, s.fsm, log, stable, snap, trans) if err != nil { return err } // Start monitoring leadership. go s.monitorLeadership() return nil }
// 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 }