etcdctl: add snapshot status support

release-3.0
Xiang Li 2016-04-11 19:18:54 -07:00
parent 8c2225f251
commit b5292f6fce
2 changed files with 105 additions and 2 deletions

View File

@ -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 {

View File

@ -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:])),
}
}