2015-05-29 20:12:00 +00:00
|
|
|
package backup
|
2015-03-20 04:23:52 +00:00
|
|
|
|
|
|
|
import (
|
2015-03-24 21:57:03 +00:00
|
|
|
"encoding/json"
|
2015-06-08 19:07:05 +00:00
|
|
|
"errors"
|
2015-03-22 16:28:04 +00:00
|
|
|
"flag"
|
2015-03-20 04:23:52 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
2015-06-08 19:07:05 +00:00
|
|
|
"net"
|
2015-03-20 04:23:52 +00:00
|
|
|
"os"
|
2015-03-24 21:57:03 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
"github.com/influxdb/influxdb/services/snapshotter"
|
|
|
|
"github.com/influxdb/influxdb/snapshot"
|
2015-03-20 04:23:52 +00:00
|
|
|
)
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Suffix is a suffix added to the backup while it's in-process.
|
|
|
|
const Suffix = ".pending"
|
2015-03-20 04:23:52 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Command represents the program execution for "influxd backup".
|
|
|
|
type Command struct {
|
2015-03-22 16:28:04 +00:00
|
|
|
// The logger passed to the ticker during execution.
|
|
|
|
Logger *log.Logger
|
2015-03-20 04:23:52 +00:00
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// Standard input/output, overridden for testing.
|
|
|
|
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{
|
2015-03-22 16:28:04 +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 16:28:04 +00:00
|
|
|
// Set up logger.
|
|
|
|
cmd.Logger = log.New(cmd.Stderr, "", log.LstdFlags)
|
2015-06-08 19:07:05 +00:00
|
|
|
cmd.Logger.Printf("influxdb backup")
|
2015-03-22 16:28:04 +00:00
|
|
|
|
|
|
|
// Parse command line arguments.
|
2015-06-08 19:07:05 +00:00
|
|
|
host, path, err := cmd.parseFlags(args)
|
2015-03-20 04:23:52 +00:00
|
|
|
if err != nil {
|
2015-03-22 16:28:04 +00:00
|
|
|
return err
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 21:57:03 +00:00
|
|
|
// Retrieve snapshot from local file.
|
2015-06-08 19:07:05 +00:00
|
|
|
m, err := snapshot.ReadFileManifest(path)
|
2015-03-24 21:57:03 +00:00
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
return fmt.Errorf("read file snapshot: %s", err)
|
|
|
|
}
|
2015-03-20 04:23:52 +00:00
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// Determine temporary path to download to.
|
2015-06-08 19:07:05 +00:00
|
|
|
tmppath := path + Suffix
|
2015-03-20 04:23:52 +00:00
|
|
|
|
2015-03-24 21:57:03 +00:00
|
|
|
// Calculate path of next backup file.
|
|
|
|
// This uses the path if it doesn't exist.
|
|
|
|
// Otherwise it appends an autoincrementing number.
|
|
|
|
path, err = cmd.nextPath(path)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("next path: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// Retrieve snapshot.
|
2015-06-08 19:07:05 +00:00
|
|
|
if err := cmd.download(host, m, tmppath); err != nil {
|
2015-03-22 16:28:04 +00:00
|
|
|
return fmt.Errorf("download: %s", err)
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// Rename temporary file to final path.
|
2015-03-20 04:23:52 +00:00
|
|
|
if err := os.Rename(tmppath, path); err != nil {
|
2015-03-22 16:28:04 +00:00
|
|
|
return fmt.Errorf("rename: %s", err)
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// TODO: Check file integrity.
|
|
|
|
|
2015-03-20 04:23:52 +00:00
|
|
|
// Notify user of completion.
|
2015-03-22 16:28:04 +00:00
|
|
|
cmd.Logger.Println("backup complete")
|
|
|
|
|
|
|
|
return nil
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// parseFlags parses and validates the command line arguments.
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) parseFlags(args []string) (host string, path string, err error) {
|
2015-03-22 16:28:04 +00:00
|
|
|
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
2015-06-08 19:07:05 +00:00
|
|
|
fs.StringVar(&host, "host", "localhost:8088", "")
|
2015-03-22 16:28:04 +00:00
|
|
|
fs.SetOutput(cmd.Stderr)
|
|
|
|
fs.Usage = cmd.printUsage
|
|
|
|
if err := fs.Parse(args); err != nil {
|
2015-06-08 19:07:05 +00:00
|
|
|
return "", "", err
|
2015-03-22 16:28:04 +00:00
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Ensure that only one arg is specified.
|
|
|
|
if fs.NArg() == 0 {
|
|
|
|
return "", "", errors.New("snapshot path required")
|
|
|
|
} else if fs.NArg() != 1 {
|
|
|
|
return "", "", errors.New("only one snapshot path allowed")
|
2015-03-22 16:28:04 +00:00
|
|
|
}
|
2015-06-08 19:07:05 +00:00
|
|
|
path = fs.Arg(0)
|
2015-03-22 16:28:04 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
return host, path, nil
|
2015-03-22 16:28:04 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 21:57:03 +00:00
|
|
|
// nextPath returns the next file to write to.
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) nextPath(path string) (string, error) {
|
2015-03-24 21:57:03 +00:00
|
|
|
// Use base path if it doesn't exist.
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
|
|
return path, nil
|
|
|
|
} else if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise iterate through incremental files until one is available.
|
|
|
|
for i := 0; ; i++ {
|
|
|
|
s := fmt.Sprintf(path+".%d", i)
|
|
|
|
if _, err := os.Stat(s); os.IsNotExist(err) {
|
|
|
|
return s, nil
|
|
|
|
} else if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// download downloads a snapshot from a host to a given path.
|
2015-06-08 19:07:05 +00:00
|
|
|
func (cmd *Command) download(host string, m *snapshot.Manifest, path string) error {
|
2015-03-22 16:28:04 +00:00
|
|
|
// Create local file to write to.
|
|
|
|
f, err := os.Create(path)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("open temp file: %s", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Connect to snapshotter service.
|
|
|
|
conn, err := net.Dial("tcp", host)
|
2015-03-24 21:57:03 +00:00
|
|
|
if err != nil {
|
2015-06-08 19:07:05 +00:00
|
|
|
return err
|
2015-03-24 21:57:03 +00:00
|
|
|
}
|
2015-06-08 19:07:05 +00:00
|
|
|
defer conn.Close()
|
2015-03-24 21:57:03 +00:00
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Send snapshotter marker byte.
|
|
|
|
if _, err := conn.Write([]byte{snapshotter.MuxHeader}); err != nil {
|
|
|
|
return fmt.Errorf("write snapshot header byte: %s", err)
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Write the manifest we currently have.
|
|
|
|
if err := json.NewEncoder(conn).Encode(m); err != nil {
|
|
|
|
return fmt.Errorf("encode snapshot manifest: %s", err)
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// Read snapshot from the connection.
|
|
|
|
if _, err := io.Copy(f, conn); err != nil {
|
|
|
|
return fmt.Errorf("copy snapshot to file: %s", err)
|
2015-03-20 04:23:52 +00:00
|
|
|
}
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
// FIXME(benbjohnson): Verify integrity of snapshot.
|
|
|
|
|
2015-03-20 04:23:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-22 16:28:04 +00:00
|
|
|
// 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 backup [flags] PATH
|
2015-03-20 04:23:52 +00:00
|
|
|
|
|
|
|
backup downloads a snapshot of a data node and saves it to disk.
|
|
|
|
|
2015-06-08 19:07:05 +00:00
|
|
|
-host <host:port>
|
2015-03-20 04:23:52 +00:00
|
|
|
The host to connect to snapshot.
|
2015-06-08 19:07:05 +00:00
|
|
|
Defaults to 127.0.0.1:8088.
|
2015-03-20 04:23:52 +00:00
|
|
|
`)
|
|
|
|
}
|