mirror of https://github.com/status-im/consul.git
Add snapshot inspect subcommand (#2451)
This commit is contained in:
parent
4be39290e5
commit
606662c502
|
@ -20,10 +20,10 @@ func (c *SnapshotCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: consul snapshot <subcommand> [options] [args]
|
Usage: consul snapshot <subcommand> [options] [args]
|
||||||
|
|
||||||
This command has subcommands for saving and restoring the state of the Consul
|
This command has subcommands for saving, restoring, and inspecting the state
|
||||||
servers for disaster recovery. These are atomic, point-in-time snapshots which
|
of the Consul servers for disaster recovery. These are atomic, point-in-time
|
||||||
include key/value entries, service catalog, prepared queries, sessions, and
|
snapshots which include key/value entries, service catalog, prepared queries,
|
||||||
ACLs.
|
sessions, and ACLs.
|
||||||
|
|
||||||
If ACLs are enabled, a management token must be supplied in order to perform
|
If ACLs are enabled, a management token must be supplied in order to perform
|
||||||
snapshot operations.
|
snapshot operations.
|
||||||
|
@ -36,6 +36,10 @@ Usage: consul snapshot <subcommand> [options] [args]
|
||||||
|
|
||||||
$ consul snapshot restore backup.snap
|
$ consul snapshot restore backup.snap
|
||||||
|
|
||||||
|
Inspect a snapshot:
|
||||||
|
|
||||||
|
$ consul snapshot inspect backup.snap
|
||||||
|
|
||||||
|
|
||||||
For more examples, ask for subcommand help or view the documentation.
|
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 {
|
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))
|
c.Ui.Error(fmt.Sprintf("Error opening snapshot file for verify: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if err := snapshot.Verify(f); err != nil {
|
if _, err := snapshot.Verify(f); err != nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
c.Ui.Error(fmt.Sprintf("Error verifying snapshot file: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error verifying snapshot file: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -169,6 +169,12 @@ func init() {
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"snapshot inspect": func() (cli.Command, error) {
|
||||||
|
return &command.SnapshotInspectCommand{
|
||||||
|
Ui: ui,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
"version": func() (cli.Command, error) {
|
"version": func() (cli.Command, error) {
|
||||||
return &command.VersionCommand{
|
return &command.VersionCommand{
|
||||||
HumanVersion: GetHumanVersion(),
|
HumanVersion: GetHumanVersion(),
|
||||||
|
|
|
@ -125,20 +125,20 @@ func (s *Snapshot) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify takes the snapshot from the reader and verifies its contents.
|
// 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.
|
// Wrap the reader in a gzip decompressor.
|
||||||
decomp, err := gzip.NewReader(in)
|
decomp, err := gzip.NewReader(in)
|
||||||
if err != nil {
|
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()
|
defer decomp.Close()
|
||||||
|
|
||||||
// Read the archive, throwing away the snapshot data.
|
// Read the archive, throwing away the snapshot data.
|
||||||
var metadata raft.SnapshotMeta
|
var metadata raft.SnapshotMeta
|
||||||
if err := read(decomp, &metadata, ioutil.Discard); err != nil {
|
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
|
// 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
|
// Make a Raft and populate it with some data. We tee everything we
|
||||||
// apply off to a buffer for checking post-snapshot.
|
// apply off to a buffer for checking post-snapshot.
|
||||||
var expected []bytes.Buffer
|
var expected []bytes.Buffer
|
||||||
|
entries := 64 * 1024
|
||||||
before, _ := makeRaft(t, path.Join(dir, "before"))
|
before, _ := makeRaft(t, path.Join(dir, "before"))
|
||||||
defer before.Shutdown()
|
defer before.Shutdown()
|
||||||
for i := 0; i < 64*1024; i++ {
|
for i := 0; i < entries; i++ {
|
||||||
var log bytes.Buffer
|
var log bytes.Buffer
|
||||||
var copy bytes.Buffer
|
var copy bytes.Buffer
|
||||||
both := io.MultiWriter(&log, ©)
|
both := io.MultiWriter(&log, ©)
|
||||||
|
@ -160,12 +161,22 @@ func TestSnapshot(t *testing.T) {
|
||||||
defer snap.Close()
|
defer snap.Close()
|
||||||
|
|
||||||
// Verify the snapshot. We have to rewind it after for the restore.
|
// 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)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := snap.file.Seek(0, 0); err != nil {
|
if _, err := snap.file.Seek(0, 0); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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.
|
// Make a new, independent Raft.
|
||||||
after, fsm := makeRaft(t, path.Join(dir, "after"))
|
after, fsm := makeRaft(t, path.Join(dir, "after"))
|
||||||
|
@ -220,7 +231,7 @@ func TestSnapshot_Nil(t *testing.T) {
|
||||||
|
|
||||||
func TestSnapshot_BadVerify(t *testing.T) {
|
func TestSnapshot_BadVerify(t *testing.T) {
|
||||||
buf := bytes.NewBuffer([]byte("nope"))
|
buf := bytes.NewBuffer([]byte("nope"))
|
||||||
err := Verify(buf)
|
_, err := Verify(buf)
|
||||||
if err == nil || !strings.Contains(err.Error(), "unexpected EOF") {
|
if err == nil || !strings.Contains(err.Error(), "unexpected EOF") {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ sidebar_current: "docs-commands-snapshot"
|
||||||
|
|
||||||
Command: `consul snapshot`
|
Command: `consul snapshot`
|
||||||
|
|
||||||
The `snapshot` command has subcommands for saving and restoring the state of the
|
The `snapshot` command has subcommands for saving, restoring, and inspecting the
|
||||||
Consul servers for disaster recovery. These are atomic, point-in-time snapshots
|
state of the Consul servers for disaster recovery. These are atomic, point-in-time
|
||||||
which include key/value entries, service catalog, prepared queries, sessions, and
|
snapshots which include key/value entries, service catalog, prepared queries,
|
||||||
ACLs. This command is available in Consul 0.7.1 and later.
|
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).
|
Snapshots are also accessible via the [HTTP API](/docs/agent/http/snapshot.html).
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ Usage: consul snapshot <subcommand> [options] [args]
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
|
inspect Displays information about a Consul snapshot file
|
||||||
restore Restores snapshot of Consul server state
|
restore Restores snapshot of Consul server state
|
||||||
save Saves 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
|
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:
|
of the subcommand in the sidebar or one of the links below:
|
||||||
|
|
||||||
|
- [inspect] (/docs/commands/snapshot/inspect.html)
|
||||||
- [restore](/docs/commands/snapshot/restore.html)
|
- [restore](/docs/commands/snapshot/restore.html)
|
||||||
- [save](/docs/commands/snapshot/save.html)
|
- [save](/docs/commands/snapshot/save.html)
|
||||||
|
|
||||||
|
@ -55,5 +57,16 @@ $ consul snapshot restore backup.snap
|
||||||
Restored snapshot
|
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
|
For more examples, ask for subcommand help or view the subcommand documentation
|
||||||
by clicking on one of the links in the sidebar.
|
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") %>>
|
<li<%= sidebar_current("docs-commands-snapshot") %>>
|
||||||
<a href="/docs/commands/snapshot.html">snapshot</a>
|
<a href="/docs/commands/snapshot.html">snapshot</a>
|
||||||
<ul class="subnav">
|
<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") %>>
|
<li<%= sidebar_current("docs-commands-snapshot-restore") %>>
|
||||||
<a href="/docs/commands/snapshot/restore.html">restore</a>
|
<a href="/docs/commands/snapshot/restore.html">restore</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue