func migrateSnapshots(legacySnapDir, snapDir string) error { // use temporary snaphot directory so initialization appears atomic tmpdirpath := filepath.Clean(snapDir) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return errors.Wrap(err, "could not remove temporary snapshot directory") } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return errors.Wrap(err, "could not create temporary snapshot directory") } snapshotNames, err := fileutil.ReadDir(legacySnapDir) if err != nil { return errors.Wrapf(err, "could not list snapshot directory %s", legacySnapDir) } for _, fname := range snapshotNames { err := os.Link(filepath.Join(legacySnapDir, fname), filepath.Join(tmpdirpath, fname)) if err != nil { return errors.Wrap(err, "error linking snapshot file") } } if err := os.Rename(tmpdirpath, snapDir); err != nil { return err } return nil }
// MigrateSnapshot reads the latest existing snapshot from one directory, encoded one way, and writes // it to a new directory, encoded a different way func MigrateSnapshot(oldDir, newDir string, oldFactory, newFactory SnapFactory) error { // use temporary snapshot directory so initialization appears atomic oldSnapshotter := oldFactory.New(oldDir) snapshot, err := oldSnapshotter.Load() switch err { case snap.ErrNoSnapshot: // if there's no snapshot, the migration succeeded return nil case nil: break default: return err } tmpdirpath := filepath.Clean(newDir) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return errors.Wrap(err, "could not remove temporary snapshot directory") } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return errors.Wrap(err, "could not create temporary snapshot directory") } tmpSnapshotter := newFactory.New(tmpdirpath) // write the new snapshot to the temporary location if err = tmpSnapshotter.SaveSnap(*snapshot); err != nil { return err } return os.Rename(tmpdirpath, newDir) }
func migrateWALs(legacyWALDir, walDir string) error { // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := filepath.Clean(walDir) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return errors.Wrap(err, "could not remove temporary WAL directory") } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return errors.Wrap(err, "could not create temporary WAL directory") } walNames, err := fileutil.ReadDir(legacyWALDir) if err != nil { return errors.Wrapf(err, "could not list WAL directory %s", legacyWALDir) } for _, fname := range walNames { _, err := copyFile(filepath.Join(legacyWALDir, fname), filepath.Join(tmpdirpath, fname), 0600) if err != nil { return errors.Wrap(err, "error copying WAL file") } } if err := os.Rename(tmpdirpath, walDir); err != nil { return err } return nil }
// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := path.Clean(dirpath) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return nil, err } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return nil, err } p := path.Join(tmpdirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode) if err != nil { return nil, err } if _, err := f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err := fileutil.Preallocate(f.File, segmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, encoder: newEncoder(f, 0), } w.locks = append(w.locks, f) if err := w.saveCrc(0); err != nil { return nil, err } if err := w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil { return nil, err } if err := w.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } if err := os.RemoveAll(dirpath); err != nil { return nil, err } if err := os.Rename(tmpdirpath, dirpath); err != nil { return nil, err } w.fp = newFilePipeline(w.dir, segmentSizeBytes) return w, nil }
// makeWAL creates a WAL for the initial cluster func makeWAL(waldir string, cl *membership.RaftCluster) { if err := fileutil.CreateDirAll(waldir); err != nil { ExitWithError(ExitIO, err) } m := cl.MemberByName(restoreName) md := &etcdserverpb.Metadata{NodeID: uint64(m.ID), ClusterID: uint64(cl.ID())} metadata, merr := md.Marshal() if merr != nil { ExitWithError(ExitInvalidInput, merr) } w, walerr := wal.Create(waldir, metadata) if walerr != nil { ExitWithError(ExitIO, walerr) } defer w.Close() peers := make([]raft.Peer, len(cl.MemberIDs())) for i, id := range cl.MemberIDs() { ctx, err := json.Marshal((*cl).Member(id)) if err != nil { ExitWithError(ExitInvalidInput, err) } peers[i] = raft.Peer{ID: uint64(id), Context: ctx} } ents := make([]raftpb.Entry, len(peers)) for i, p := range peers { cc := raftpb.ConfChange{ Type: raftpb.ConfChangeAddNode, NodeID: p.ID, Context: p.Context} d, err := cc.Marshal() if err != nil { ExitWithError(ExitInvalidInput, err) } e := raftpb.Entry{ Type: raftpb.EntryConfChange, Term: 1, Index: uint64(i + 1), Data: d, } ents[i] = e } w.Save(raftpb.HardState{ Term: 1, Vote: peers[0].ID, Commit: uint64(len(ents))}, ents) }
func makeMemberDir(dir string) error { membdir := path.Join(dir, "member") _, err := os.Stat(membdir) switch { case err == nil: return nil case !os.IsNotExist(err): return err } if err := fileutil.CreateDirAll(membdir); err != nil { return err } names := []string{"snap", "wal"} for _, name := range names { if err := os.Rename(path.Join(dir, name), path.Join(membdir, name)); err != nil { return err } } return nil }
// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := path.Clean(dirpath) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return nil, err } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return nil, err } p := path.Join(tmpdirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode) if err != nil { return nil, err } if _, err := f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err := fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, encoder: newEncoder(f, 0), } w.locks = append(w.locks, f) if err := w.saveCrc(0); err != nil { return nil, err } if err := w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil { return nil, err } if err := w.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } // rename of directory with locked files doesn't work on windows; close // the WAL to release the locks so the directory can be renamed w.Close() if err := os.Rename(tmpdirpath, dirpath); err != nil { return nil, err } // reopen and relock newWAL, oerr := Open(dirpath, walpb.Snapshot{}) if oerr != nil { return nil, oerr } if _, _, _, err := newWAL.ReadAll(); err != nil { newWAL.Close() return nil, err } return newWAL, nil }
// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := path.Clean(dirpath) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return nil, err } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return nil, err } p := path.Join(tmpdirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode) if err != nil { return nil, err } if _, err = f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err = fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, } w.encoder, err = newFileEncoder(f.File, 0) if err != nil { return nil, err } w.locks = append(w.locks, f) if err = w.saveCrc(0); err != nil { return nil, err } if err = w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil { return nil, err } if err = w.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } if w, err = w.renameWal(tmpdirpath); err != nil { return nil, err } // directory was renamed; sync parent dir to persist rename pdir, perr := fileutil.OpenDir(path.Dir(w.dir)) if perr != nil { return nil, perr } if perr = fileutil.Fsync(pdir); perr != nil { return nil, perr } if perr = pdir.Close(); err != nil { return nil, perr } return w, nil }
// handleBackup handles a request that intends to do a backup. func handleBackup(c *cli.Context) error { var srcWAL string var destWAL string srcSnap := path.Join(c.String("data-dir"), "member", "snap") destSnap := path.Join(c.String("backup-dir"), "member", "snap") if c.String("wal-dir") != "" { srcWAL = c.String("wal-dir") } else { srcWAL = path.Join(c.String("data-dir"), "member", "wal") } if c.String("backup-wal-dir") != "" { destWAL = c.String("backup-wal-dir") } else { destWAL = path.Join(c.String("backup-dir"), "member", "wal") } if err := fileutil.CreateDirAll(destSnap); err != nil { log.Fatalf("failed creating backup snapshot dir %v: %v", destSnap, err) } ss := snap.New(srcSnap) snapshot, err := ss.Load() if err != nil && err != snap.ErrNoSnapshot { log.Fatal(err) } var walsnap walpb.Snapshot if snapshot != nil { walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term newss := snap.New(destSnap) if err = newss.SaveSnap(*snapshot); err != nil { log.Fatal(err) } } w, err := wal.OpenForRead(srcWAL, walsnap) if err != nil { log.Fatal(err) } defer w.Close() wmetadata, state, ents, err := w.ReadAll() switch err { case nil: case wal.ErrSnapshotNotFound: fmt.Printf("Failed to find the match snapshot record %+v in wal %v.", walsnap, srcWAL) fmt.Printf("etcdctl will add it back. Start auto fixing...") default: log.Fatal(err) } var metadata etcdserverpb.Metadata pbutil.MustUnmarshal(&metadata, wmetadata) idgen := idutil.NewGenerator(0, time.Now()) metadata.NodeID = idgen.Next() metadata.ClusterID = idgen.Next() neww, err := wal.Create(destWAL, pbutil.MustMarshal(&metadata)) if err != nil { log.Fatal(err) } defer neww.Close() if err := neww.Save(state, ents); err != nil { log.Fatal(err) } if err := neww.SaveSnapshot(walsnap); err != nil { log.Fatal(err) } return nil }
// makeDB copies the database snapshot to the snapshot directory func makeDB(snapdir, dbfile string) { f, ferr := os.OpenFile(dbfile, os.O_RDONLY, 0600) if ferr != nil { ExitWithError(ExitInvalidInput, ferr) } defer f.Close() // get snapshot integrity hash if _, err := f.Seek(-sha256.Size, os.SEEK_END); err != nil { ExitWithError(ExitIO, err) } sha := make([]byte, sha256.Size) if _, err := f.Read(sha); err != nil { ExitWithError(ExitIO, err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { ExitWithError(ExitIO, err) } if err := fileutil.CreateDirAll(snapdir); err != nil { ExitWithError(ExitIO, err) } dbpath := path.Join(snapdir, "db") db, dberr := os.OpenFile(dbpath, os.O_RDWR|os.O_CREATE, 0600) if dberr != nil { ExitWithError(ExitIO, dberr) } if _, err := io.Copy(db, f); err != nil { ExitWithError(ExitIO, err) } // truncate away integrity hash, if any. off, serr := db.Seek(0, os.SEEK_END) if serr != nil { ExitWithError(ExitIO, serr) } hasHash := (off % 512) == sha256.Size if hasHash { if err := db.Truncate(off - sha256.Size); err != nil { ExitWithError(ExitIO, err) } } if !hasHash && !skipHashCheck { err := fmt.Errorf("snapshot missing hash but --skip-hash-check=false") ExitWithError(ExitBadArgs, err) } if hasHash && !skipHashCheck { // check for match if _, err := db.Seek(0, os.SEEK_SET); err != nil { ExitWithError(ExitIO, err) } h := sha256.New() if _, err := io.Copy(h, db); err != nil { ExitWithError(ExitIO, err) } dbsha := h.Sum(nil) if !reflect.DeepEqual(sha, dbsha) { err := fmt.Errorf("expected sha256 %v, got %v", sha, dbsha) ExitWithError(ExitInvalidInput, err) } } // db hash is OK, can now modify DB so it can be part of a new cluster db.Close() // update consistentIndex so applies go through on etcdserver despite // having a new raft instance be := backend.NewDefaultBackend(dbpath) s := mvcc.NewStore(be, nil, &initIndex{}) id := s.TxnBegin() btx := be.BatchTx() del := func(k, v []byte) error { _, _, err := s.TxnDeleteRange(id, k, nil) return err } // delete stored members from old cluster since using new members btx.UnsafeForEach([]byte("members"), del) btx.UnsafeForEach([]byte("members_removed"), del) // trigger write-out of new consistent index s.TxnEnd(id) s.Commit() s.Close() }
// makeWAL creates a WAL for the initial cluster func makeWALAndSnap(waldir, snapdir string, cl *membership.RaftCluster) { if err := fileutil.CreateDirAll(waldir); err != nil { ExitWithError(ExitIO, err) } // add members again to persist them to the store we create. st := store.New(etcdserver.StoreClusterPrefix, etcdserver.StoreKeysPrefix) cl.SetStore(st) for _, m := range cl.Members() { cl.AddMember(m) } m := cl.MemberByName(restoreName) md := &etcdserverpb.Metadata{NodeID: uint64(m.ID), ClusterID: uint64(cl.ID())} metadata, merr := md.Marshal() if merr != nil { ExitWithError(ExitInvalidInput, merr) } w, walerr := wal.Create(waldir, metadata) if walerr != nil { ExitWithError(ExitIO, walerr) } defer w.Close() peers := make([]raft.Peer, len(cl.MemberIDs())) for i, id := range cl.MemberIDs() { ctx, err := json.Marshal((*cl).Member(id)) if err != nil { ExitWithError(ExitInvalidInput, err) } peers[i] = raft.Peer{ID: uint64(id), Context: ctx} } ents := make([]raftpb.Entry, len(peers)) nodeIDs := make([]uint64, len(peers)) for i, p := range peers { nodeIDs[i] = p.ID cc := raftpb.ConfChange{ Type: raftpb.ConfChangeAddNode, NodeID: p.ID, Context: p.Context} d, err := cc.Marshal() if err != nil { ExitWithError(ExitInvalidInput, err) } e := raftpb.Entry{ Type: raftpb.EntryConfChange, Term: 1, Index: uint64(i + 1), Data: d, } ents[i] = e } commit, term := uint64(len(ents)), uint64(1) if err := w.Save(raftpb.HardState{ Term: term, Vote: peers[0].ID, Commit: commit}, ents); err != nil { ExitWithError(ExitIO, err) } b, berr := st.Save() if berr != nil { ExitWithError(ExitError, berr) } raftSnap := raftpb.Snapshot{ Data: b, Metadata: raftpb.SnapshotMetadata{ Index: commit, Term: term, ConfState: raftpb.ConfState{ Nodes: nodeIDs, }, }, } snapshotter := snap.New(snapdir) if err := snapshotter.SaveSnap(raftSnap); err != nil { panic(err) } if err := w.SaveSnapshot(walpb.Snapshot{Index: commit, Term: term}); err != nil { ExitWithError(ExitIO, err) } }