From d717d2cdc4d6fe6523106cc07f99ec51323b1d12 Mon Sep 17 00:00:00 2001 From: Paul Banks Date: Fri, 23 Apr 2021 16:17:08 +0100 Subject: [PATCH] CLI: Allow snapshot inspect to work on internal raft snapshots directly. (#10089) * CLI: Add support for reading internal raft snapshots to snapshot inspect * Add snapshot inspect test for raw state files * Add changelog entry * Update .changelog/10089.txt --- .changelog/10089.txt | 4 ++ command/snapshot/inspect/snapshot_inspect.go | 48 ++++++++++++++---- .../snapshot/inspect/snapshot_inspect_test.go | 20 ++++++++ .../TestSnapshotInspectCommandRaw.golden | 19 +++++++ .../snapshot/inspect/testdata/raw/meta.json | 1 + .../snapshot/inspect/testdata/raw/state.bin | Bin 0 -> 5141 bytes website/content/commands/snapshot/inspect.mdx | 32 ++++++++++++ 7 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 .changelog/10089.txt create mode 100644 command/snapshot/inspect/testdata/TestSnapshotInspectCommandRaw.golden create mode 100644 command/snapshot/inspect/testdata/raw/meta.json create mode 100644 command/snapshot/inspect/testdata/raw/state.bin diff --git a/.changelog/10089.txt b/.changelog/10089.txt new file mode 100644 index 0000000000..6db075bafe --- /dev/null +++ b/.changelog/10089.txt @@ -0,0 +1,4 @@ +```release-note:improvement +cli: snapshot inspect command can now inspect raw snapshots from a server's data +dir. +``` diff --git a/command/snapshot/inspect/snapshot_inspect.go b/command/snapshot/inspect/snapshot_inspect.go index e0bbd648c2..a3b876426b 100644 --- a/command/snapshot/inspect/snapshot_inspect.go +++ b/command/snapshot/inspect/snapshot_inspect.go @@ -1,10 +1,13 @@ package inspect import ( + "encoding/json" "flag" "fmt" "io" + "io/ioutil" "os" + "path" "sort" "strings" @@ -111,18 +114,41 @@ func (c *cmd) Run(args []string) int { } defer f.Close() - readFile, meta, err := snapshot.Read(hclog.New(nil), f) - if err != nil { - c.UI.Error(fmt.Sprintf("Error reading snapshot: %s", err)) + var readFile *os.File + var meta *raft.SnapshotMeta + + if strings.ToLower(path.Base(file)) == "state.bin" { + // This is an internal raw raft snapshot not a gzipped archive one + // downloaded from the API, we can read it directly + readFile = f + + // Assume the meta is colocated and error if not. + metaRaw, err := ioutil.ReadFile(path.Join(path.Dir(file), "meta.json")) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading meta.json from internal snapshot dir: %s", err)) + return 1 + } + var metaDecoded raft.SnapshotMeta + err = json.Unmarshal(metaRaw, &metaDecoded) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing meta.json from internal snapshot dir: %s", err)) + return 1 + } + meta = &metaDecoded + } else { + readFile, meta, err = snapshot.Read(hclog.New(nil), f) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading snapshot: %s", err)) + } + defer func() { + if err := readFile.Close(); err != nil { + c.UI.Error(fmt.Sprintf("Failed to close temp snapshot: %v", err)) + } + if err := os.Remove(readFile.Name()); err != nil { + c.UI.Error(fmt.Sprintf("Failed to clean up temp snapshot: %v", err)) + } + }() } - defer func() { - if err := readFile.Close(); err != nil { - c.UI.Error(fmt.Sprintf("Failed to close temp snapshot: %v", err)) - } - if err := os.Remove(readFile.Name()); err != nil { - c.UI.Error(fmt.Sprintf("Failed to clean up temp snapshot: %v", err)) - } - }() info, err := c.enhance(readFile) if err != nil { diff --git a/command/snapshot/inspect/snapshot_inspect_test.go b/command/snapshot/inspect/snapshot_inspect_test.go index fbfc60feb0..a9a7992dde 100644 --- a/command/snapshot/inspect/snapshot_inspect_test.go +++ b/command/snapshot/inspect/snapshot_inspect_test.go @@ -149,3 +149,23 @@ func TestSnapshotInspectKVDetailsDepthFilterCommand(t *testing.T) { want := golden(t, t.Name(), ui.OutputWriter.String()) require.Equal(t, want, ui.OutputWriter.String()) } + +// TestSnapshotInspectCommandRaw test reading a snaphost directly from a raft +// data dir. +func TestSnapshotInspectCommandRaw(t *testing.T) { + + filepath := "./testdata/raw/state.bin" + + // Inspect the snapshot + ui := cli.NewMockUi() + c := New(ui) + args := []string{filepath} + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + + want := golden(t, t.Name(), ui.OutputWriter.String()) + require.Equal(t, want, ui.OutputWriter.String()) +} diff --git a/command/snapshot/inspect/testdata/TestSnapshotInspectCommandRaw.golden b/command/snapshot/inspect/testdata/TestSnapshotInspectCommandRaw.golden new file mode 100644 index 0000000000..fc5af33af8 --- /dev/null +++ b/command/snapshot/inspect/testdata/TestSnapshotInspectCommandRaw.golden @@ -0,0 +1,19 @@ + ID 2-13-1602222343947 + Size 5141 + Index 13 + Term 2 + Version 1 + + Type Count Size + ---- ---- ---- + Register 3 1.7KB + ConnectCA 1 1.2KB + ConnectCAProviderState 1 1.1KB + Index 12 344B + Autopilot 1 199B + ConnectCAConfig 1 197B + FederationState 1 139B + SystemMetadata 1 68B + ChunkingState 1 12B + ---- ---- ---- + Total 5KB diff --git a/command/snapshot/inspect/testdata/raw/meta.json b/command/snapshot/inspect/testdata/raw/meta.json new file mode 100644 index 0000000000..d88ce9ead6 --- /dev/null +++ b/command/snapshot/inspect/testdata/raw/meta.json @@ -0,0 +1 @@ +{"Version":1,"ID":"2-13-1602222343947","Index":13,"Term":2,"Peers":"ka4xMjcuMC4wLjE6ODMwMA==","Configuration":{"Servers":[{"Suffrage":0,"ID":"a577b288-b354-770e-e909-da0972eb20e8","Address":"127.0.0.1:8300"}]},"ConfigurationIndex":1,"Size":5141} diff --git a/command/snapshot/inspect/testdata/raw/state.bin b/command/snapshot/inspect/testdata/raw/state.bin new file mode 100644 index 0000000000000000000000000000000000000000..daa0082a5227f64cc8ffa3339ebdd2f869e6380a GIT binary patch literal 5141 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 literal 0 HcmV?d00001 diff --git a/website/content/commands/snapshot/inspect.mdx b/website/content/commands/snapshot/inspect.mdx index adef211723..9db0bac0c9 100644 --- a/website/content/commands/snapshot/inspect.mdx +++ b/website/content/commands/snapshot/inspect.mdx @@ -12,6 +12,15 @@ snapshot of the state of the Consul servers which includes key/value entries, service catalog, prepared queries, sessions, and ACLs. The snapshot is read from the given file. +-> Typically this is used with Consul self-contained Snapshot files obtained +using the [`consul snapshot`](/commands/snapshot) command or [Snapshot +API](/api-docs/snapshot#generate-snapshot). If the file provided is named +`state.bin` however, the command will assume it is a raw raft snapshot in a +Consul server data directory and will attempt to read it directly. The +`state.bin` file must still be in the same directory as it's associated +`meta.json` file. This is useful for debugging data on live servers without +making a complete new snapshot via the CLI or API first. + The following fields are displayed when inspecting a snapshot: - `ID` - A unique ID for the snapshot, only used for differentiation purposes. @@ -106,6 +115,29 @@ $ consul snapshot inspect -kvdetails -kvdepth 3 -kvfilter vault/core backup.snap Please see the [HTTP API](/api/snapshot) documentation for more details about snapshot internals. +To inspect an internal snapshot directly from a Consul server data directory: + +```shell-session +$ consul snapshot inspect /opt/consul/raft/snapshots/9-4600669-1618935304715/state.bin + ID 9-4600669-1618935304715 + Size 4625420898 + Index 4600669 + Term 9 + Version 1 + + Type Count Size + ---- ---- ---- + KVS 4089785 4.3GB + Register 9 5.2KB + CoordinateBatchUpdate 3 465B + Index 8 224B + Autopilot 1 199B + FederationState 1 139B + ChunkingState 1 12B + ---- ---- ---- + Total 4.3GB +``` + #### Command Options - `-kvdetails` - Optional, provides a space usage breakdown for any KV data stored in Consul.