// Restore takes the snapshot from the reader and attempts to apply it to the // given Raft instance. func Restore(logger *log.Logger, in io.Reader, r *raft.Raft) error { // Wrap the reader in a gzip decompressor. decomp, err := gzip.NewReader(in) if err != nil { return fmt.Errorf("failed to decompress snapshot: %v", err) } defer func() { if err := decomp.Close(); err != nil { logger.Printf("[ERR] snapshot: Failed to close snapshot decompressor: %v", err) } }() // Make a scratch file to receive the contents of the snapshot data so // we can avoid buffering in memory. snap, err := ioutil.TempFile("", "snapshot") if err != nil { return fmt.Errorf("failed to create temp snapshot file: %v", err) } defer func() { if err := snap.Close(); err != nil { logger.Printf("[ERR] snapshot: Failed to close temp snapshot: %v", err) } if err := os.Remove(snap.Name()); err != nil { logger.Printf("[ERR] snapshot: Failed to clean up temp snapshot: %v", err) } }() // Read the archive. var metadata raft.SnapshotMeta if err := read(decomp, &metadata, snap); err != nil { return fmt.Errorf("failed to read snapshot file: %v", err) } // Sync and rewind the file so it's ready to be read again. if err := snap.Sync(); err != nil { return fmt.Errorf("failed to sync temp snapshot: %v", err) } if _, err := snap.Seek(0, 0); err != nil { return fmt.Errorf("failed to rewind temp snapshot: %v", err) } // Feed the snapshot into Raft. if err := r.Restore(&metadata, snap, 60*time.Second); err != nil { return fmt.Errorf("Raft error when restoring snapshot: %v", err) } return nil }
// New takes a state snapshot of the given Raft instance into a temporary file // and returns an object that gives access to the file as an io.Reader. You must // arrange to call Close() on the returned object or else you will leak a // temporary file. func New(logger *log.Logger, r *raft.Raft) (*Snapshot, error) { // Take the snapshot. future := r.Snapshot() if err := future.Error(); err != nil { return nil, fmt.Errorf("Raft error when taking snapshot: %v", err) } // Open up the snapshot. metadata, snap, err := future.Open() if err != nil { return nil, fmt.Errorf("failed to open snapshot: %v:", err) } defer func() { if err := snap.Close(); err != nil { logger.Printf("[ERR] snapshot: Failed to close Raft snapshot: %v", err) } }() // Make a scratch file to receive the contents so that we don't buffer // everything in memory. This gets deleted in Close() since we keep it // around for re-reading. archive, err := ioutil.TempFile("", "snapshot") if err != nil { return nil, fmt.Errorf("failed to create snapshot file: %v", err) } // If anything goes wrong after this point, we will attempt to clean up // the temp file. The happy path will disarm this. var keep bool defer func() { if keep { return } if err := os.Remove(archive.Name()); err != nil { logger.Printf("[ERR] snapshot: Failed to clean up temp snapshot: %v", err) } }() // Wrap the file writer in a gzip compressor. compressor := gzip.NewWriter(archive) // Write the archive. if err := write(compressor, metadata, snap); err != nil { return nil, fmt.Errorf("failed to write snapshot file: %v", err) } // Finish the compressed stream. if err := compressor.Close(); err != nil { return nil, fmt.Errorf("failed to compress snapshot file: %v", err) } // Sync the compressed file and rewind it so it's ready to be streamed // out by the caller. if err := archive.Sync(); err != nil { return nil, fmt.Errorf("failed to sync snapshot: %v", err) } if _, err := archive.Seek(0, 0); err != nil { return nil, fmt.Errorf("failed to rewind snapshot: %v", err) } keep = true return &Snapshot{archive, metadata.Index}, nil }
var ( raftDir = flag.String("raftdir", "/var/lib/robustirc", "Directory in which raft state is stored. If this directory is empty, you need to specify -join.") listen = flag.String("listen", ":443", "[host]:port to listen on. Set to a port in the dynamic port range (49152 to 65535) and use DNS SRV records.") version = flag.Bool("version", false, "Print version and exit") singleNode = flag.Bool("singlenode", false, "Become a raft leader without any followers. Set to true if and only if starting the first node for the first time.") join = flag.String("join", "", "host:port of an existing raft node in the network that should be joined. Will also be loaded from -raftdir.") dumpCanaryState = flag.String("dump_canary_state", "", "If specified, initializes the raft node (from a snapshot), then dumps all message state to the specified file. To be used via robustirc-canary.") dumpHeapProfile = flag.String("dump_heap_profile", "", "If specified, a heap profile will be dumped to the specified file. Only relevant when -dump_canary_state is set.") canaryCompactionStart = flag.Int64("canary_compaction_start", 0, "If > 0, a nanosecond precision UNIX timestamp of when the compaction was started (for deterministic results across runs).") network = flag.String("network_name", "", `Name of the network (e.g. "robustirc.net") to use in IRC messages. Ideally also a DNS name pointing to one or more servers.`) peerAddr = flag.String("peer_addr", "", `host:port of this raft node (e.g. "fastbox.robustirc.net:60667"). Must be publically reachable.`) tlsCertPath = flag.String("tls_cert_path", "", "Path to a .pem file containing the TLS certificate.") tlsKeyPath = flag.String("tls_key_path", "", "Path to a .pem file containing the TLS private key.") networkPassword = flag.String("network_password", "", "A secure password to protect the communication between raft nodes. Use pwgen(1) or similar. If empty, the ROBUSTIRC_NETWORK_PASSWORD environment variable is used.") node *raft.Raft peerStore *raft.JSONPeers ircStore *raft_store.LevelDBStore ircServer *ircserver.IRCServer executablehash = executableHash() // Version is overwritten by Makefile. Version = "unknown" isLeaderGauge = prometheus.NewGaugeFunc( prometheus.GaugeOpts{ Subsystem: "raft", Name: "isleader", Help: "1 if this node is the raft leader, 0 otherwise", }, func() float64 { if node.State() == raft.Leader { return 1 } return 0 }, ) sessionsGauge = prometheus.NewGaugeFunc( prometheus.GaugeOpts{ Subsystem: "irc", Name: "sessions", Help: "Number of IRC sessions", }, func() float64 { return float64(ircServer.NumSessions()) }, ) appliedMessages = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "applied_messages", Help: "How many raft messages were applied, partitioned by message type", }, []string{"type"}, ) secondsInState = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "seconds_in_state", Help: "How many seconds the node was in each raft state", }, []string{"state"}, ) )