From b5292f6fce475eee4c71e6258100043fb773642e Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Mon, 11 Apr 2016 19:18:54 -0700 Subject: [PATCH] etcdctl: add snapshot status support --- etcdctl/ctlv3/command/printer.go | 21 ++++++ etcdctl/ctlv3/command/snapshot_command.go | 86 ++++++++++++++++++++++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index d6ffdf746..1432d82cd 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/etcdctl/ctlv3/command/printer.go @@ -24,6 +24,7 @@ import ( v3 "github.com/coreos/etcd/clientv3" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" spb "github.com/coreos/etcd/storage/storagepb" + "github.com/dustin/go-humanize" "github.com/olekukonko/tablewriter" ) @@ -39,6 +40,7 @@ type printer interface { MemberStatus([]statusInfo) Alarm(v3.AlarmResponse) + DBStatus(dbstatus) } func NewPrinter(printerType string, isHex bool) printer { @@ -142,6 +144,20 @@ func (s *simplePrinter) MemberStatus(statusList []statusInfo) { table.Render() } +func (s *simplePrinter) DBStatus(ds dbstatus) { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"hash", "revision", "total keys", "total size"}) + + table.Append([]string{ + fmt.Sprintf("%x", ds.hash), + fmt.Sprint(ds.revision), + fmt.Sprint(ds.totalKey), + humanize.Bytes(uint64(ds.totalSize)), + }) + + table.Render() +} + type jsonPrinter struct{} func (p *jsonPrinter) Del(r v3.DeleteResponse) { printJSON(r) } @@ -156,6 +172,7 @@ func (p *jsonPrinter) Watch(r v3.WatchResponse) { printJSON(r) } func (p *jsonPrinter) Alarm(r v3.AlarmResponse) { printJSON(r) } func (p *jsonPrinter) MemberList(r v3.MemberListResponse) { printJSON(r) } func (p *jsonPrinter) MemberStatus(r []statusInfo) { printJSON(r) } +func (p *jsonPrinter) DBStatus(r dbstatus) { printJSON(r) } func printJSON(v interface{}) { b, err := json.Marshal(v) @@ -206,6 +223,10 @@ func (pb *pbPrinter) MemberStatus(r []statusInfo) { ExitWithError(ExitBadFeature, errors.New("only support simple or json as output format")) } +func (pb *pbPrinter) DBStatus(r dbstatus) { + ExitWithError(ExitBadFeature, errors.New("only support simple or json as output format")) +} + func printPB(m pbMarshal) { b, err := m.Marshal() if err != nil { diff --git a/etcdctl/ctlv3/command/snapshot_command.go b/etcdctl/ctlv3/command/snapshot_command.go index 4ef4a1f84..616634b4c 100644 --- a/etcdctl/ctlv3/command/snapshot_command.go +++ b/etcdctl/ctlv3/command/snapshot_command.go @@ -15,13 +15,16 @@ package command import ( + "encoding/binary" "encoding/json" "fmt" + "hash/crc32" "io" "os" "path" "strings" + "github.com/boltdb/bolt" "github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/etcdserver/membership" @@ -56,6 +59,7 @@ func NewSnapshotCommand() *cobra.Command { } cmd.AddCommand(NewSnapshotSaveCommand()) cmd.AddCommand(NewSnapshotRestoreCommand()) + cmd.AddCommand(newSnapshotStatusCommand()) return cmd } @@ -67,10 +71,18 @@ func NewSnapshotSaveCommand() *cobra.Command { } } +func newSnapshotStatusCommand() *cobra.Command { + return &cobra.Command{ + Use: "status ", + Short: "status gets backend snapshot status of a given file.", + Run: snapshotStatusCommandFunc, + } +} + func NewSnapshotRestoreCommand() *cobra.Command { cmd := &cobra.Command{ Use: "restore ", - Short: "restore an etcd node snapshot to an etcd directory", + Short: "restore an etcd member snapshot to an etcd directory", Run: snapshotRestoreCommandFunc, } cmd.Flags().StringVar(&restoreDataDir, "data-dir", "", "Path to the data directory.") @@ -117,9 +129,18 @@ func snapshotSaveCommandFunc(cmd *cobra.Command, args []string) { } } +func snapshotStatusCommandFunc(cmd *cobra.Command, args []string) { + if len(args) != 1 { + err := fmt.Errorf("snapshot status requires exactly one argument") + ExitWithError(ExitBadArgs, err) + } + ds := dbStatus(args[0]) + display.DBStatus(ds) +} + func snapshotRestoreCommandFunc(cmd *cobra.Command, args []string) { if len(args) != 1 { - err := fmt.Errorf("snapshot restore exactly one argument") + err := fmt.Errorf("snapshot restore requires exactly one argument") ExitWithError(ExitBadArgs, err) } @@ -266,3 +287,64 @@ func makeDB(snapdir, dbfile string) { s.Commit() s.Close() } + +type dbstatus struct { + hash uint32 + revision int64 + totalKey int + totalSize int64 +} + +func dbStatus(p string) dbstatus { + ds := dbstatus{} + + db, err := bolt.Open(p, 0600, nil) + if err != nil { + ExitWithError(ExitError, err) + } + + h := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + + err = db.View(func(tx *bolt.Tx) error { + ds.totalSize = tx.Size() + c := tx.Cursor() + for next, _ := c.First(); next != nil; next, _ = c.Next() { + b := tx.Bucket(next) + if b == nil { + return fmt.Errorf("cannot get hash of bucket %s", string(next)) + } + h.Write(next) + iskeyb := (string(next) == "key") + b.ForEach(func(k, v []byte) error { + h.Write(k) + h.Write(v) + if iskeyb { + rev := bytesToRev(k) + ds.revision = rev.main + } + ds.totalKey++ + return nil + }) + } + return nil + }) + + if err != nil { + ExitWithError(ExitError, err) + } + + ds.hash = h.Sum32() + return ds +} + +type revision struct { + main int64 + sub int64 +} + +func bytesToRev(bytes []byte) revision { + return revision{ + main: int64(binary.BigEndian.Uint64(bytes[0:8])), + sub: int64(binary.BigEndian.Uint64(bytes[9:])), + } +}