Skip to content

Commit

Permalink
Merge pull request #15965 from cenkalti/hashkv
Browse files Browse the repository at this point in the history
cli: Add etcdutl snapshot hashkv command
  • Loading branch information
ahrtr authored Jun 3, 2024
2 parents 0b2f15f + b107d24 commit dc8510c
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 7 deletions.
38 changes: 38 additions & 0 deletions etcdutl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Prints the version of etcdutl.
Expand Down
1 change: 1 addition & 0 deletions etcdutl/ctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func init() {
rootCmd.AddCommand(
etcdutl.NewDefragCommand(),
etcdutl.NewSnapshotCommand(),
etcdutl.NewHashKVCommand(),
etcdutl.NewVersionCommand(),
etcdutl.NewCompletionCommand(),
etcdutl.NewMigrateCommand(),
Expand Down
74 changes: 74 additions & 0 deletions etcdutl/etcdutl/hashkv_command.go
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions etcdutl/etcdutl/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (

type printer interface {
DBStatus(snapshot.Status)
DBHashKV(HashKV)
}

func NewPrinter(printerType string) printer {
Expand Down Expand Up @@ -64,6 +65,7 @@ func newPrinterUnsupported(n string) printer {
}

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) {
hdr = []string{"hash", "revision", "total keys", "total size", "version"}
Expand All @@ -77,6 +79,16 @@ func makeDBStatusTable(ds snapshot.Status) (hdr []string, rows [][]string) {
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) {
outputType, err := cmd.Flags().GetString("write-out")
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions etcdutl/etcdutl/printer_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ func (p *fieldsPrinter) DBStatus(r snapshot.Status) {
fmt.Println(`"Size" :`, r.TotalSize)
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)
}
1 change: 1 addition & 0 deletions etcdutl/etcdutl/printer_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func newJSONPrinter() printer {
}

func (p *jsonPrinter) DBStatus(r snapshot.Status) { printJSON(r) }
func (p *jsonPrinter) DBHashKV(r HashKV) { printJSON(r) }

// !!! Share ??
func printJSON(v any) {
Expand Down
7 changes: 7 additions & 0 deletions etcdutl/etcdutl/printer_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ func (s *simplePrinter) DBStatus(ds snapshot.Status) {
fmt.Println(strings.Join(row, ", "))
}
}

func (s *simplePrinter) DBHashKV(ds HashKV) {
_, rows := makeDBHashKVTable(ds)
for _, row := range rows {
fmt.Println(strings.Join(row, ", "))
}
}
11 changes: 11 additions & 0 deletions etcdutl/etcdutl/printer_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,14 @@ func (tp *tablePrinter) DBStatus(r snapshot.Status) {
table.SetAlignment(tablewriter.ALIGN_RIGHT)
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()
}
2 changes: 1 addition & 1 deletion server/storage/mvcc/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ type hashStorage struct {
lg *zap.Logger
}

func newHashStorage(lg *zap.Logger, s *store) *hashStorage {
func NewHashStorage(lg *zap.Logger, s *store) HashStorage {
return &hashStorage{
store: s,
lg: lg,
Expand Down
10 changes: 6 additions & 4 deletions server/storage/mvcc/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@ func (tc hashTestCase) Compact(ctx context.Context, rev int64) error {

func TestHasherStore(t *testing.T) {
lg := zaptest.NewLogger(t)
s := newHashStorage(lg, newFakeStore(lg))
defer s.store.Close()
store := newFakeStore(lg)
s := NewHashStorage(lg, store)
defer store.Close()

var hashes []KeyValueHash
for i := 0; i < hashStorageMaxSize; i++ {
Expand Down Expand Up @@ -207,8 +208,9 @@ func TestHasherStore(t *testing.T) {

func TestHasherStoreFull(t *testing.T) {
lg := zaptest.NewLogger(t)
s := newHashStorage(lg, newFakeStore(lg))
defer s.store.Close()
store := newFakeStore(lg)
s := NewHashStorage(lg, store)
defer store.Close()

var minRevision int64 = 100
var maxRevision = minRevision + hashStorageMaxSize
Expand Down
2 changes: 1 addition & 1 deletion server/storage/mvcc/kvstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func NewStore(lg *zap.Logger, b backend.Backend, le lease.Lessor, cfg StoreConfi

lg: lg,
}
s.hashes = newHashStorage(lg, s)
s.hashes = NewHashStorage(lg, s)
s.ReadView = &readView{s}
s.WriteView = &writeView{s}
if s.le != nil {
Expand Down
2 changes: 1 addition & 1 deletion server/storage/mvcc/kvstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ func newFakeStore(lg *zap.Logger) *store {
lg: lg,
}
s.ReadView, s.WriteView = &readView{s}, &writeView{s}
s.hashes = newHashStorage(lg, s)
s.hashes = NewHashStorage(lg, s)
return s
}

Expand Down

0 comments on commit dc8510c

Please sign in to comment.