mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #15965 from cenkalti/hashkv
cli: Add etcdutl snapshot hashkv command
This commit is contained in:
commit
dc8510ce47
@ -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.
|
||||
|
@ -43,6 +43,7 @@ func init() {
|
||||
rootCmd.AddCommand(
|
||||
etcdutl.NewDefragCommand(),
|
||||
etcdutl.NewSnapshotCommand(),
|
||||
etcdutl.NewHashKVCommand(),
|
||||
etcdutl.NewVersionCommand(),
|
||||
etcdutl.NewCompletionCommand(),
|
||||
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 {
|
||||
DBStatus(snapshot.Status)
|
||||
DBHashKV(HashKV)
|
||||
}
|
||||
|
||||
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) DBHashKV(HashKV) { p.p(nil) }
|
||||
|
||||
func makeDBStatusTable(ds snapshot.Status) (hdr []string, rows [][]string) {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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, ", "))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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++ {
|
||||
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user