Add snapshot inspect subcommand (#2451)
This commit is contained in:
parent
72412237fd
commit
440611f9f7
|
@ -20,10 +20,10 @@ func (c *SnapshotCommand) Help() string {
|
|||
helpText := `
|
||||
Usage: consul snapshot <subcommand> [options] [args]
|
||||
|
||||
This command has subcommands for saving and restoring the state of the Consul
|
||||
servers for disaster recovery. These are atomic, point-in-time snapshots which
|
||||
include key/value entries, service catalog, prepared queries, sessions, and
|
||||
ACLs.
|
||||
This command has subcommands for saving, restoring, and inspecting the state
|
||||
of the Consul servers for disaster recovery. These are atomic, point-in-time
|
||||
snapshots which include key/value entries, service catalog, prepared queries,
|
||||
sessions, and ACLs.
|
||||
|
||||
If ACLs are enabled, a management token must be supplied in order to perform
|
||||
snapshot operations.
|
||||
|
@ -36,6 +36,10 @@ Usage: consul snapshot <subcommand> [options] [args]
|
|||
|
||||
$ consul snapshot restore backup.snap
|
||||
|
||||
Inspect a snapshot:
|
||||
|
||||
$ consul snapshot inspect backup.snap
|
||||
|
||||
|
||||
For more examples, ask for subcommand help or view the documentation.
|
||||
|
||||
|
@ -44,5 +48,5 @@ Usage: consul snapshot <subcommand> [options] [args]
|
|||
}
|
||||
|
||||
func (c *SnapshotCommand) Synopsis() string {
|
||||
return "Saves and restores snapshots of Consul server state"
|
||||
return "Saves, restores and inspects snapshots of Consul server state"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/hashicorp/consul/consul/snapshot"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// SnapshotInspectCommand is a Command implementation that is used to display
|
||||
// metadata about a snapshot file
|
||||
type SnapshotInspectCommand struct {
|
||||
Ui cli.Ui
|
||||
}
|
||||
|
||||
func (c *SnapshotInspectCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: consul snapshot inspect [options] FILE
|
||||
|
||||
Displays information about a snapshot file on disk.
|
||||
|
||||
To inspect the file "backup.snap":
|
||||
|
||||
$ consul snapshot inspect backup.snap
|
||||
|
||||
For a full list of options and examples, please see the Consul documentation.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *SnapshotInspectCommand) Run(args []string) int {
|
||||
cmdFlags := flag.NewFlagSet("get", flag.ContinueOnError)
|
||||
cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
var file string
|
||||
|
||||
args = cmdFlags.Args()
|
||||
switch len(args) {
|
||||
case 0:
|
||||
c.Ui.Error("Missing FILE argument")
|
||||
return 1
|
||||
case 1:
|
||||
file = args[0]
|
||||
default:
|
||||
c.Ui.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Open the file.
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error opening snapshot file: %s", err))
|
||||
return 1
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
meta, err := snapshot.Verify(f)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error verifying snapshot: %s", err))
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
tw := tabwriter.NewWriter(&b, 0, 2, 6, ' ', 0)
|
||||
fmt.Fprintf(tw, "ID\t%s\n", meta.ID)
|
||||
fmt.Fprintf(tw, "Size\t%d\n", meta.Size)
|
||||
fmt.Fprintf(tw, "Index\t%d\n", meta.Index)
|
||||
fmt.Fprintf(tw, "Term\t%d\n", meta.Term)
|
||||
fmt.Fprintf(tw, "Version\t%d\n", meta.Version)
|
||||
if err = tw.Flush(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error rendering snapshot info: %s", err))
|
||||
}
|
||||
|
||||
c.Ui.Info(b.String())
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *SnapshotInspectCommand) Synopsis() string {
|
||||
return "Displays information about a Consul snapshot file"
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestSnapshotInspectCommand_implements(t *testing.T) {
|
||||
var _ cli.Command = &SnapshotInspectCommand{}
|
||||
}
|
||||
|
||||
func TestSnapshotInspectCommand_noTabs(t *testing.T) {
|
||||
assertNoTabs(t, new(SnapshotInspectCommand))
|
||||
}
|
||||
|
||||
func TestSnapshotInspectCommand_Validation(t *testing.T) {
|
||||
ui := new(cli.MockUi)
|
||||
c := &SnapshotInspectCommand{Ui: ui}
|
||||
|
||||
cases := map[string]struct {
|
||||
args []string
|
||||
output string
|
||||
}{
|
||||
"no file": {
|
||||
[]string{},
|
||||
"Missing FILE argument",
|
||||
},
|
||||
"extra args": {
|
||||
[]string{"foo", "bar", "baz"},
|
||||
"Too many arguments",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
// Ensure our buffer is always clear
|
||||
if ui.ErrorWriter != nil {
|
||||
ui.ErrorWriter.Reset()
|
||||
}
|
||||
if ui.OutputWriter != nil {
|
||||
ui.OutputWriter.Reset()
|
||||
}
|
||||
|
||||
code := c.Run(tc.args)
|
||||
if code == 0 {
|
||||
t.Errorf("%s: expected non-zero exit", name)
|
||||
}
|
||||
|
||||
output := ui.ErrorWriter.String()
|
||||
if !strings.Contains(output, tc.output) {
|
||||
t.Errorf("%s: expected %q to contain %q", name, output, tc.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotInspectCommand_Run(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
waitForLeader(t, srv.httpAddr)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
|
||||
dir, err := ioutil.TempDir("", "snapshot")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
file := path.Join(dir, "backup.tgz")
|
||||
|
||||
// Save a snapshot of the current Consul state
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
snap, _, err := client.Snapshot().Save(nil)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if _, err := io.Copy(f, snap); err != nil {
|
||||
f.Close()
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Inspect the snapshot
|
||||
inspect := &SnapshotInspectCommand{Ui: ui}
|
||||
args := []string{file}
|
||||
|
||||
code := inspect.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
for _, key := range []string{
|
||||
"ID",
|
||||
"Size",
|
||||
"Index",
|
||||
"Term",
|
||||
"Version",
|
||||
} {
|
||||
if !strings.Contains(output, key) {
|
||||
t.Fatalf("bad %#v, missing %q", output, key)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -113,7 +113,7 @@ func (c *SnapshotSaveCommand) Run(args []string) int {
|
|||
c.Ui.Error(fmt.Sprintf("Error opening snapshot file for verify: %s", err))
|
||||
return 1
|
||||
}
|
||||
if err := snapshot.Verify(f); err != nil {
|
||||
if _, err := snapshot.Verify(f); err != nil {
|
||||
f.Close()
|
||||
c.Ui.Error(fmt.Sprintf("Error verifying snapshot file: %s", err))
|
||||
return 1
|
||||
|
|
|
@ -169,6 +169,12 @@ func init() {
|
|||
}, nil
|
||||
},
|
||||
|
||||
"snapshot inspect": func() (cli.Command, error) {
|
||||
return &command.SnapshotInspectCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"version": func() (cli.Command, error) {
|
||||
return &command.VersionCommand{
|
||||
HumanVersion: GetHumanVersion(),
|
||||
|
|
|
@ -125,20 +125,20 @@ func (s *Snapshot) Close() error {
|
|||
}
|
||||
|
||||
// Verify takes the snapshot from the reader and verifies its contents.
|
||||
func Verify(in io.Reader) error {
|
||||
func Verify(in io.Reader) (*raft.SnapshotMeta, error) {
|
||||
// Wrap the reader in a gzip decompressor.
|
||||
decomp, err := gzip.NewReader(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decompress snapshot: %v", err)
|
||||
return nil, fmt.Errorf("failed to decompress snapshot: %v", err)
|
||||
}
|
||||
defer decomp.Close()
|
||||
|
||||
// Read the archive, throwing away the snapshot data.
|
||||
var metadata raft.SnapshotMeta
|
||||
if err := read(decomp, &metadata, ioutil.Discard); err != nil {
|
||||
return fmt.Errorf("failed to read snapshot file: %v", err)
|
||||
return nil, fmt.Errorf("failed to read snapshot file: %v", err)
|
||||
}
|
||||
return nil
|
||||
return &metadata, nil
|
||||
}
|
||||
|
||||
// Restore takes the snapshot from the reader and attempts to apply it to the
|
||||
|
|
|
@ -135,9 +135,10 @@ func TestSnapshot(t *testing.T) {
|
|||
// Make a Raft and populate it with some data. We tee everything we
|
||||
// apply off to a buffer for checking post-snapshot.
|
||||
var expected []bytes.Buffer
|
||||
entries := 64 * 1024
|
||||
before, _ := makeRaft(t, path.Join(dir, "before"))
|
||||
defer before.Shutdown()
|
||||
for i := 0; i < 64*1024; i++ {
|
||||
for i := 0; i < entries; i++ {
|
||||
var log bytes.Buffer
|
||||
var copy bytes.Buffer
|
||||
both := io.MultiWriter(&log, ©)
|
||||
|
@ -160,12 +161,22 @@ func TestSnapshot(t *testing.T) {
|
|||
defer snap.Close()
|
||||
|
||||
// Verify the snapshot. We have to rewind it after for the restore.
|
||||
if err := Verify(snap); err != nil {
|
||||
metadata, err := Verify(snap)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if _, err := snap.file.Seek(0, 0); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if int(metadata.Index) != entries+2 {
|
||||
t.Fatalf("bad: %d", metadata.Index)
|
||||
}
|
||||
if metadata.Term != 2 {
|
||||
t.Fatalf("bad: %d", metadata.Index)
|
||||
}
|
||||
if metadata.Version != raft.SnapshotVersionMax {
|
||||
t.Fatalf("bad: %d", metadata.Version)
|
||||
}
|
||||
|
||||
// Make a new, independent Raft.
|
||||
after, fsm := makeRaft(t, path.Join(dir, "after"))
|
||||
|
@ -220,7 +231,7 @@ func TestSnapshot_Nil(t *testing.T) {
|
|||
|
||||
func TestSnapshot_BadVerify(t *testing.T) {
|
||||
buf := bytes.NewBuffer([]byte("nope"))
|
||||
err := Verify(buf)
|
||||
_, err := Verify(buf)
|
||||
if err == nil || !strings.Contains(err.Error(), "unexpected EOF") {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ sidebar_current: "docs-commands-snapshot"
|
|||
|
||||
Command: `consul snapshot`
|
||||
|
||||
The `snapshot` command has subcommands for saving and restoring the state of the
|
||||
Consul servers for disaster recovery. These are atomic, point-in-time snapshots
|
||||
which include key/value entries, service catalog, prepared queries, sessions, and
|
||||
ACLs. This command is available in Consul 0.7.1 and later.
|
||||
The `snapshot` command has subcommands for saving, restoring, and inspecting the
|
||||
state of the Consul servers for disaster recovery. These are atomic, point-in-time
|
||||
snapshots which include key/value entries, service catalog, prepared queries,
|
||||
sessions, and ACLs. This command is available in Consul 0.7.1 and later.
|
||||
|
||||
Snapshots are also accessible via the [HTTP API](/docs/agent/http/snapshot.html).
|
||||
|
||||
|
@ -29,6 +29,7 @@ Usage: consul snapshot <subcommand> [options] [args]
|
|||
|
||||
Subcommands:
|
||||
|
||||
inspect Displays information about a Consul snapshot file
|
||||
restore Restores snapshot of Consul server state
|
||||
save Saves snapshot of Consul server state
|
||||
```
|
||||
|
@ -36,6 +37,7 @@ Subcommands:
|
|||
For more information, examples, and usage about a subcommand, click on the name
|
||||
of the subcommand in the sidebar or one of the links below:
|
||||
|
||||
- [inspect] (/docs/commands/snapshot/inspect.html)
|
||||
- [restore](/docs/commands/snapshot/restore.html)
|
||||
- [save](/docs/commands/snapshot/save.html)
|
||||
|
||||
|
@ -55,5 +57,16 @@ $ consul snapshot restore backup.snap
|
|||
Restored snapshot
|
||||
```
|
||||
|
||||
To inspect a snapshot from the file "backup.snap":
|
||||
|
||||
```text
|
||||
$ consul snapshot inspect backup.snap
|
||||
ID 2-5-1477944140022
|
||||
Size 667
|
||||
Index 5
|
||||
Term 2
|
||||
Version 1
|
||||
```
|
||||
|
||||
For more examples, ask for subcommand help or view the subcommand documentation
|
||||
by clicking on one of the links in the sidebar.
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Commands: Snapshot Inspect"
|
||||
sidebar_current: "docs-commands-snapshot-inspect"
|
||||
---
|
||||
|
||||
# Consul Snapshot Inspect
|
||||
|
||||
Command: `consul snapshot inspect`
|
||||
|
||||
The `snapshot inspect` command is used to inspect an atomic, point-in-time
|
||||
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.
|
||||
|
||||
The following fields are displayed when inspecting a snapshot:
|
||||
|
||||
* `ID` - A unique ID for the snapshot, only used for differentiation purposes.
|
||||
|
||||
* `Size` - The size of the snapshot, in bytes.
|
||||
|
||||
* `Index` - The Raft index of the latest log entry in the snapshot.
|
||||
|
||||
* `Term` - The Raft term of the latest log entry in the snapshot.
|
||||
|
||||
* `Version` - The snapshot format version. This only refers to the structure of
|
||||
the snapshot, not the data contained within.
|
||||
|
||||
## Usage
|
||||
|
||||
Usage: `consul snapshot inspect [options] FILE`
|
||||
|
||||
## Examples
|
||||
|
||||
To inspect a snapshot from the file "backup.snap":
|
||||
|
||||
```text
|
||||
$ consul snapshot inspect backup.snap
|
||||
ID 2-5-1477944140022
|
||||
Size 667
|
||||
Index 5
|
||||
Term 2
|
||||
Version 1
|
||||
```
|
||||
|
||||
Please see the [HTTP API](/docs/agent/http/snapshot.html) documentation for
|
||||
more details about snapshot internals.
|
|
@ -152,6 +152,9 @@
|
|||
<li<%= sidebar_current("docs-commands-snapshot") %>>
|
||||
<a href="/docs/commands/snapshot.html">snapshot</a>
|
||||
<ul class="subnav">
|
||||
<li<%= sidebar_current("docs-commands-snapshot-inspect") %>>
|
||||
<a href="/docs/commands/snapshot/inspect.html">inspect</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-commands-snapshot-restore") %>>
|
||||
<a href="/docs/commands/snapshot/restore.html">restore</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue