mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
cli: Add etcdutl snapshot hashkv command
Signed-off-by: Cenk Alti <cenkalti@gmail.com> Apply suggestions from code review Co-authored-by: Benjamin Wang <benjamin.wang@broadcom.com>
This commit is contained in:
parent
0b2f15f616
commit
b107d2437f
@ -119,6 +119,44 @@ Prints a line of JSON encoding the database hash, revision, total keys, and size
|
|||||||
+----------+----------+------------+------------+
|
+----------+----------+------------+------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### HASHKV [options] \<filename\>
|
||||||
|
|
||||||
|
HASHKV prints hash of keys and values up to given revision.
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
- rev -- Revision number. Default is 0 which means the latest revision.
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
##### Simple format
|
||||||
|
|
||||||
|
Prints a humanized table of the KV hash, hash revision and compact revision.
|
||||||
|
|
||||||
|
##### JSON format
|
||||||
|
|
||||||
|
Prints a line of JSON encoding the KV hash, hash revision and compact revision.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
```bash
|
||||||
|
./etcdutl hashkv file.db
|
||||||
|
# 35c86e9b, 214, 150
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./etcdutl --write-out=json hashkv file.db
|
||||||
|
# {"hash":902327963,"hashRevision":214,"compactRevision":150}
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./etcdutl --write-out=table hashkv file.db
|
||||||
|
+----------+---------------+------------------+
|
||||||
|
| HASH | HASH REVISION | COMPACT REVISION |
|
||||||
|
+----------+---------------+------------------+
|
||||||
|
| 35c86e9b | 214 | 150 |
|
||||||
|
+----------+---------------+------------------+
|
||||||
|
```
|
||||||
|
|
||||||
### VERSION
|
### VERSION
|
||||||
|
|
||||||
Prints the version of etcdutl.
|
Prints the version of etcdutl.
|
||||||
|
@ -43,6 +43,7 @@ func init() {
|
|||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
etcdutl.NewDefragCommand(),
|
etcdutl.NewDefragCommand(),
|
||||||
etcdutl.NewSnapshotCommand(),
|
etcdutl.NewSnapshotCommand(),
|
||||||
|
etcdutl.NewHashKVCommand(),
|
||||||
etcdutl.NewVersionCommand(),
|
etcdutl.NewVersionCommand(),
|
||||||
etcdutl.NewCompletionCommand(),
|
etcdutl.NewCompletionCommand(),
|
||||||
etcdutl.NewMigrateCommand(),
|
etcdutl.NewMigrateCommand(),
|
||||||
|
74
etcdutl/etcdutl/hashkv_command.go
Normal file
74
etcdutl/etcdutl/hashkv_command.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2024 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package etcdutl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"go.etcd.io/etcd/pkg/v3/cobrautl"
|
||||||
|
"go.etcd.io/etcd/server/v3/storage/backend"
|
||||||
|
"go.etcd.io/etcd/server/v3/storage/mvcc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
hashKVRevision int64
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHashKVCommand returns the cobra command for "hashkv".
|
||||||
|
func NewHashKVCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "hashkv <filename>",
|
||||||
|
Short: "Prints the KV history hash of a given file",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: hashKVCommandFunc,
|
||||||
|
}
|
||||||
|
cmd.Flags().Int64Var(&hashKVRevision, "rev", 0, "maximum revision to hash (default: latest revision)")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashKVCommandFunc(cmd *cobra.Command, args []string) {
|
||||||
|
printer := initPrinterFromCmd(cmd)
|
||||||
|
|
||||||
|
ds, err := calculateHashKV(args[0], hashKVRevision)
|
||||||
|
if err != nil {
|
||||||
|
cobrautl.ExitWithError(cobrautl.ExitError, err)
|
||||||
|
}
|
||||||
|
printer.DBHashKV(ds)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HashKV struct {
|
||||||
|
Hash uint32 `json:"hash"`
|
||||||
|
HashRevision int64 `json:"hashRevision"`
|
||||||
|
CompactRevision int64 `json:"compactRevision"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateHashKV(dbPath string, rev int64) (HashKV, error) {
|
||||||
|
cfg := backend.DefaultBackendConfig(zap.NewNop())
|
||||||
|
cfg.Path = dbPath
|
||||||
|
b := backend.New(cfg)
|
||||||
|
st := mvcc.NewStore(zap.NewNop(), b, nil, mvcc.StoreConfig{})
|
||||||
|
hst := mvcc.NewHashStorage(zap.NewNop(), st)
|
||||||
|
|
||||||
|
h, _, err := hst.HashByRev(rev)
|
||||||
|
if err != nil {
|
||||||
|
return HashKV{}, err
|
||||||
|
}
|
||||||
|
return HashKV{
|
||||||
|
Hash: h.Hash,
|
||||||
|
HashRevision: h.Revision,
|
||||||
|
CompactRevision: h.CompactRevision,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -31,6 +31,7 @@ var (
|
|||||||
|
|
||||||
type printer interface {
|
type printer interface {
|
||||||
DBStatus(snapshot.Status)
|
DBStatus(snapshot.Status)
|
||||||
|
DBHashKV(HashKV)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPrinter(printerType string) printer {
|
func NewPrinter(printerType string) printer {
|
||||||
@ -64,6 +65,7 @@ func newPrinterUnsupported(n string) printer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *printerUnsupported) DBStatus(snapshot.Status) { p.p(nil) }
|
func (p *printerUnsupported) DBStatus(snapshot.Status) { p.p(nil) }
|
||||||
|
func (p *printerUnsupported) DBHashKV(HashKV) { p.p(nil) }
|
||||||
|
|
||||||
func makeDBStatusTable(ds snapshot.Status) (hdr []string, rows [][]string) {
|
func makeDBStatusTable(ds snapshot.Status) (hdr []string, rows [][]string) {
|
||||||
hdr = []string{"hash", "revision", "total keys", "total size", "version"}
|
hdr = []string{"hash", "revision", "total keys", "total size", "version"}
|
||||||
@ -77,6 +79,16 @@ func makeDBStatusTable(ds snapshot.Status) (hdr []string, rows [][]string) {
|
|||||||
return hdr, rows
|
return hdr, rows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeDBHashKVTable(ds HashKV) (hdr []string, rows [][]string) {
|
||||||
|
hdr = []string{"hash", "hash revision", "compact revision"}
|
||||||
|
rows = append(rows, []string{
|
||||||
|
fmt.Sprint(ds.Hash),
|
||||||
|
fmt.Sprint(ds.HashRevision),
|
||||||
|
fmt.Sprint(ds.CompactRevision),
|
||||||
|
})
|
||||||
|
return hdr, rows
|
||||||
|
}
|
||||||
|
|
||||||
func initPrinterFromCmd(cmd *cobra.Command) (p printer) {
|
func initPrinterFromCmd(cmd *cobra.Command) (p printer) {
|
||||||
outputType, err := cmd.Flags().GetString("write-out")
|
outputType, err := cmd.Flags().GetString("write-out")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -29,3 +29,9 @@ func (p *fieldsPrinter) DBStatus(r snapshot.Status) {
|
|||||||
fmt.Println(`"Size" :`, r.TotalSize)
|
fmt.Println(`"Size" :`, r.TotalSize)
|
||||||
fmt.Println(`"Version" :`, r.Version)
|
fmt.Println(`"Version" :`, r.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *fieldsPrinter) DBHashKV(r HashKV) {
|
||||||
|
fmt.Println(`"Hash" :`, r.Hash)
|
||||||
|
fmt.Println(`"Hash revision" :`, r.HashRevision)
|
||||||
|
fmt.Println(`"Compact revision" :`, r.CompactRevision)
|
||||||
|
}
|
||||||
|
@ -33,6 +33,7 @@ func newJSONPrinter() printer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *jsonPrinter) DBStatus(r snapshot.Status) { printJSON(r) }
|
func (p *jsonPrinter) DBStatus(r snapshot.Status) { printJSON(r) }
|
||||||
|
func (p *jsonPrinter) DBHashKV(r HashKV) { printJSON(r) }
|
||||||
|
|
||||||
// !!! Share ??
|
// !!! Share ??
|
||||||
func printJSON(v any) {
|
func printJSON(v any) {
|
||||||
|
@ -30,3 +30,10 @@ func (s *simplePrinter) DBStatus(ds snapshot.Status) {
|
|||||||
fmt.Println(strings.Join(row, ", "))
|
fmt.Println(strings.Join(row, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *simplePrinter) DBHashKV(ds HashKV) {
|
||||||
|
_, rows := makeDBHashKVTable(ds)
|
||||||
|
for _, row := range rows {
|
||||||
|
fmt.Println(strings.Join(row, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -34,3 +34,14 @@ func (tp *tablePrinter) DBStatus(r snapshot.Status) {
|
|||||||
table.SetAlignment(tablewriter.ALIGN_RIGHT)
|
table.SetAlignment(tablewriter.ALIGN_RIGHT)
|
||||||
table.Render()
|
table.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tp *tablePrinter) DBHashKV(r HashKV) {
|
||||||
|
hdr, rows := makeDBHashKVTable(r)
|
||||||
|
table := tablewriter.NewWriter(os.Stdout)
|
||||||
|
table.SetHeader(hdr)
|
||||||
|
for _, row := range rows {
|
||||||
|
table.Append(row)
|
||||||
|
}
|
||||||
|
table.SetAlignment(tablewriter.ALIGN_RIGHT)
|
||||||
|
table.Render()
|
||||||
|
}
|
||||||
|
@ -111,7 +111,7 @@ type hashStorage struct {
|
|||||||
lg *zap.Logger
|
lg *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHashStorage(lg *zap.Logger, s *store) *hashStorage {
|
func NewHashStorage(lg *zap.Logger, s *store) HashStorage {
|
||||||
return &hashStorage{
|
return &hashStorage{
|
||||||
store: s,
|
store: s,
|
||||||
lg: lg,
|
lg: lg,
|
||||||
|
@ -178,8 +178,9 @@ func (tc hashTestCase) Compact(ctx context.Context, rev int64) error {
|
|||||||
|
|
||||||
func TestHasherStore(t *testing.T) {
|
func TestHasherStore(t *testing.T) {
|
||||||
lg := zaptest.NewLogger(t)
|
lg := zaptest.NewLogger(t)
|
||||||
s := newHashStorage(lg, newFakeStore(lg))
|
store := newFakeStore(lg)
|
||||||
defer s.store.Close()
|
s := NewHashStorage(lg, store)
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
var hashes []KeyValueHash
|
var hashes []KeyValueHash
|
||||||
for i := 0; i < hashStorageMaxSize; i++ {
|
for i := 0; i < hashStorageMaxSize; i++ {
|
||||||
@ -207,8 +208,9 @@ func TestHasherStore(t *testing.T) {
|
|||||||
|
|
||||||
func TestHasherStoreFull(t *testing.T) {
|
func TestHasherStoreFull(t *testing.T) {
|
||||||
lg := zaptest.NewLogger(t)
|
lg := zaptest.NewLogger(t)
|
||||||
s := newHashStorage(lg, newFakeStore(lg))
|
store := newFakeStore(lg)
|
||||||
defer s.store.Close()
|
s := NewHashStorage(lg, store)
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
var minRevision int64 = 100
|
var minRevision int64 = 100
|
||||||
var maxRevision = minRevision + hashStorageMaxSize
|
var maxRevision = minRevision + hashStorageMaxSize
|
||||||
|
@ -106,7 +106,7 @@ func NewStore(lg *zap.Logger, b backend.Backend, le lease.Lessor, cfg StoreConfi
|
|||||||
|
|
||||||
lg: lg,
|
lg: lg,
|
||||||
}
|
}
|
||||||
s.hashes = newHashStorage(lg, s)
|
s.hashes = NewHashStorage(lg, s)
|
||||||
s.ReadView = &readView{s}
|
s.ReadView = &readView{s}
|
||||||
s.WriteView = &writeView{s}
|
s.WriteView = &writeView{s}
|
||||||
if s.le != nil {
|
if s.le != nil {
|
||||||
|
@ -929,7 +929,7 @@ func newFakeStore(lg *zap.Logger) *store {
|
|||||||
lg: lg,
|
lg: lg,
|
||||||
}
|
}
|
||||||
s.ReadView, s.WriteView = &readView{s}, &writeView{s}
|
s.ReadView, s.WriteView = &readView{s}, &writeView{s}
|
||||||
s.hashes = newHashStorage(lg, s)
|
s.hashes = NewHashStorage(lg, s)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user