2015-05-29 20:12:00 +00:00
|
|
|
package restore
|
2015-03-20 04:23:52 +00:00
|
|
|
|
|
|
|
import (
|
2015-06-08 19:07:05 +00:00
|
|
|
"bytes"
|
|
|
|
"errors"
|
2015-03-22 20:58:40 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-06-08 19:07:05 +00:00
|
|
|
"net"
|
2015-03-22 20:58:40 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
"github.com/BurntSushi/toml"
|
|
|
|
"github.com/influxdb/influxdb/meta"
|
|
|
|
"github.com/influxdb/influxdb/snapshot"
|
|
|
|
"github.com/influxdb/influxdb/tsdb"
|
2015-03-20 04:23:52 +00:00
|
|
|
)
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Command represents the program execution for "influxd restore".
|
|
|
|
type Command struct {
|
|
|
|
Stdout io.Writer
|
2015-03-22 20:58:40 +00:00
|
|
|
Stderr io.Writer
|
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// NewCommand returns a new instance of Command with default settings.
|
|
|
|
func NewCommand() *Command {
|
|
|
|
return &Command{
|
|
|
|
Stdout: os.Stdout,
|
2015-03-22 20:58:40 +00:00
|
|
|
Stderr: os.Stderr,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-28 06:54:34 +00:00
|
|
|
// Run executes the program.
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) Run(args ...string) error {
|
2015-03-22 20:58:40 +00:00
|
|
|
config, path, err := cmd.parseFlags(args)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-03-25 20:27:20 +00:00
|
|
|
return cmd.Restore(config, path)
|
|
|
|
}
|
|
|
|
|
2015-11-07 07:30:27 +00:00
|
|
|
// Restore restores a database snapshot
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) Restore(config *Config, path string) error {
|
|
|
|
// Remove meta and data directories.
|
|
|
|
if err := os.RemoveAll(config.Meta.Dir); err != nil {
|
|
|
|
return fmt.Errorf("remove meta dir: %s", err)
|
|
|
|
} else if err := os.RemoveAll(config.Data.Dir); err != nil {
|
2015-03-22 20:58:40 +00:00
|
|
|
return fmt.Errorf("remove data dir: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-03-24 21:57:03 +00:00
|
|
|
// Open snapshot file and all incremental backups.
|
2015-06-08 19:07:05 +00:00
|
|
|
mr, files, err := snapshot.OpenFileMultiReader(path)
|
2015-03-22 20:58:40 +00:00
|
|
|
if err != nil {
|
2015-06-08 19:07:05 +00:00
|
|
|
return fmt.Errorf("open multireader: %s", err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
2015-03-24 21:57:03 +00:00
|
|
|
defer closeAll(files)
|
2015-03-22 20:58:40 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Unpack files from archive.
|
|
|
|
if err := cmd.unpack(mr, config); err != nil {
|
2015-03-22 20:58:40 +00:00
|
|
|
return fmt.Errorf("unpack: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify user of completion.
|
2015-06-08 19:07:05 +00:00
|
|
|
fmt.Fprintf(os.Stdout, "restore complete using %s", path)
|
2015-03-22 20:58:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseFlags parses and validates the command line arguments.
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) parseFlags(args []string) (*Config, string, error) {
|
2015-03-22 20:58:40 +00:00
|
|
|
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
|
|
|
configPath := fs.String("config", "", "")
|
|
|
|
fs.SetOutput(cmd.Stderr)
|
|
|
|
fs.Usage = cmd.printUsage
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse configuration file from disk.
|
2015-04-13 16:45:39 +00:00
|
|
|
if *configPath == "" {
|
2015-06-08 19:07:05 +00:00
|
|
|
return nil, "", fmt.Errorf("config required")
|
2015-04-13 16:45:39 +00:00
|
|
|
}
|
2015-06-08 19:07:05 +00:00
|
|
|
|
|
|
|
// Parse config.
|
|
|
|
config := Config{
|
|
|
|
Meta: meta.NewConfig(),
|
|
|
|
Data: tsdb.NewConfig(),
|
|
|
|
}
|
|
|
|
if _, err := toml.DecodeFile(*configPath, &config); err != nil {
|
|
|
|
return nil, "", err
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Require output path.
|
|
|
|
path := fs.Arg(0)
|
|
|
|
if path == "" {
|
|
|
|
return nil, "", fmt.Errorf("snapshot path required")
|
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
return &config, path, nil
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 21:57:03 +00:00
|
|
|
func closeAll(a []io.Closer) {
|
|
|
|
for _, c := range a {
|
|
|
|
_ = c.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 20:58:40 +00:00
|
|
|
// unpack expands the files in the snapshot archive into a directory.
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) unpack(mr *snapshot.MultiReader, config *Config) error {
|
2015-03-22 20:58:40 +00:00
|
|
|
// Loop over files and extract.
|
|
|
|
for {
|
|
|
|
// Read entry header.
|
2015-06-08 19:07:05 +00:00
|
|
|
sf, err := mr.Next()
|
2015-03-22 20:58:40 +00:00
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
return fmt.Errorf("next: entry=%s, err=%s", sf.Name, err)
|
|
|
|
}
|
|
|
|
|
2015-03-24 22:04:39 +00:00
|
|
|
// Log progress.
|
2015-06-08 19:07:05 +00:00
|
|
|
fmt.Fprintf(os.Stdout, "unpacking: %s (%d bytes)\n", sf.Name, sf.Size)
|
2015-03-24 22:04:39 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Handle meta and tsdb files separately.
|
|
|
|
switch sf.Name {
|
|
|
|
case "meta":
|
|
|
|
if err := cmd.unpackMeta(mr, sf, config); err != nil {
|
|
|
|
return fmt.Errorf("meta: %s", err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
2015-06-08 19:07:05 +00:00
|
|
|
default:
|
|
|
|
if err := cmd.unpackData(mr, sf, config); err != nil {
|
|
|
|
return fmt.Errorf("data: %s", err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// unpackMeta reads the metadata from the snapshot and initializes a raft
|
|
|
|
// cluster and replaces the root metadata.
|
|
|
|
func (cmd *Command) unpackMeta(mr *snapshot.MultiReader, sf snapshot.File, config *Config) error {
|
|
|
|
// Read meta into buffer.
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if _, err := io.CopyN(&buf, mr, sf.Size); err != nil {
|
|
|
|
return fmt.Errorf("copy: %s", err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Unpack into metadata.
|
|
|
|
var data meta.Data
|
|
|
|
if err := data.UnmarshalBinary(buf.Bytes()); err != nil {
|
|
|
|
return fmt.Errorf("unmarshal: %s", err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Copy meta config and remove peers so it starts in single mode.
|
|
|
|
c := config.Meta
|
|
|
|
c.Peers = nil
|
|
|
|
|
|
|
|
// Initialize meta store.
|
|
|
|
store := meta.NewStore(config.Meta)
|
|
|
|
store.RaftListener = newNopListener()
|
|
|
|
store.ExecListener = newNopListener()
|
2015-11-03 11:25:33 +00:00
|
|
|
store.RPCListener = newNopListener()
|
2015-03-22 20:58:40 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Determine advertised address.
|
|
|
|
_, port, err := net.SplitHostPort(config.Meta.BindAddress)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("split bind address: %s", err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
2015-06-08 19:07:05 +00:00
|
|
|
hostport := net.JoinHostPort(config.Meta.Hostname, port)
|
2015-03-22 20:58:40 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Resolve address.
|
|
|
|
addr, err := net.ResolveTCPAddr("tcp", hostport)
|
2015-03-22 20:58:40 +00:00
|
|
|
if err != nil {
|
2015-06-08 19:07:05 +00:00
|
|
|
return fmt.Errorf("resolve tcp: addr=%s, err=%s", hostport, err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
2015-06-08 19:07:05 +00:00
|
|
|
store.Addr = addr
|
2015-11-05 17:31:04 +00:00
|
|
|
store.RemoteAddr = addr
|
2015-03-22 20:58:40 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Open the meta store.
|
|
|
|
if err := store.Open(); err != nil {
|
|
|
|
return fmt.Errorf("open store: %s", err)
|
|
|
|
}
|
|
|
|
defer store.Close()
|
2015-03-22 20:58:40 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Wait for the store to be ready or error.
|
|
|
|
select {
|
|
|
|
case <-store.Ready():
|
|
|
|
case err := <-store.Err():
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Force set the full metadata.
|
|
|
|
if err := store.SetData(&data); err != nil {
|
|
|
|
return fmt.Errorf("set data: %s", err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) unpackData(mr *snapshot.MultiReader, sf snapshot.File, config *Config) error {
|
|
|
|
path := filepath.Join(config.Data.Dir, sf.Name)
|
|
|
|
// Create parent directory for output file.
|
|
|
|
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
|
|
|
|
return fmt.Errorf("mkdir: entry=%s, err=%s", sf.Name, err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Create output file.
|
|
|
|
f, err := os.Create(path)
|
2015-03-22 20:58:40 +00:00
|
|
|
if err != nil {
|
2015-06-08 19:07:05 +00:00
|
|
|
return fmt.Errorf("create: entry=%s, err=%s", sf.Name, err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Copy contents from reader.
|
|
|
|
if _, err := io.CopyN(f, mr, sf.Size); err != nil {
|
|
|
|
return fmt.Errorf("copy: entry=%s, err=%s", sf.Name, err)
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// printUsage prints the usage message to STDERR.
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) printUsage() {
|
2015-03-22 20:58:40 +00:00
|
|
|
fmt.Fprintf(cmd.Stderr, `usage: influxd restore [flags] PATH
|
|
|
|
|
|
|
|
restore uses a snapshot of a data node to rebuild a cluster.
|
2015-03-20 04:23:52 +00:00
|
|
|
|
|
|
|
-config <path>
|
2015-03-22 20:58:40 +00:00
|
|
|
Set the path to the configuration file.
|
2015-03-20 04:23:52 +00:00
|
|
|
`)
|
|
|
|
}
|
2015-03-22 20:58:40 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Config represents a partial config for rebuilding the server.
|
|
|
|
type Config struct {
|
2015-07-17 21:38:47 +00:00
|
|
|
Meta *meta.Config `toml:"meta"`
|
|
|
|
Data tsdb.Config `toml:"data"`
|
2015-06-08 19:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type nopListener struct {
|
|
|
|
closing chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newNopListener() *nopListener {
|
|
|
|
return &nopListener{make(chan struct{})}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ln *nopListener) Accept() (net.Conn, error) {
|
|
|
|
<-ln.closing
|
|
|
|
return nil, errors.New("listener closing")
|
2015-03-22 20:58:40 +00:00
|
|
|
}
|
|
|
|
|
2015-11-09 15:34:24 +00:00
|
|
|
func (ln *nopListener) Close() error {
|
|
|
|
if ln.closing != nil {
|
|
|
|
close(ln.closing)
|
|
|
|
ln.closing = nil
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
func (ln *nopListener) Addr() net.Addr { return nil }
|