Browse Source

etcdctl: add snapshot status support

Xiang Li 9 years ago
parent
commit
b5292f6fce
2 changed files with 105 additions and 2 deletions
  1. 21 0
      etcdctl/ctlv3/command/printer.go
  2. 84 2
      etcdctl/ctlv3/command/snapshot_command.go

+ 21 - 0
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 {

+ 84 - 2
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 <filename>",
+		Short: "status gets backend snapshot status of a given file.",
+		Run:   snapshotStatusCommandFunc,
+	}
+}
+
 func NewSnapshotRestoreCommand() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "restore <filename>",
-		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:])),
+	}
+}