Skip to content

Commit

Permalink
Refactor backup into etcd package
Browse files Browse the repository at this point in the history
Also remove dependency on etcdctl for v3 for backup.  We still need it
for restore, but this makes the separate etcd-backup tool smaller.
  • Loading branch information
justinsb committed Feb 19, 2018
1 parent e6250ca commit 63896da
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 134 deletions.
74 changes: 2 additions & 72 deletions pkg/backupcontroller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ package backupcontroller
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/golang/glog"
Expand Down Expand Up @@ -126,73 +121,8 @@ func (m *BackupController) doClusterBackup(ctx context.Context, members []*etcdc
MemberCount: int32(len(members)),
EtcdVersion: m.etcdVersion,
},
EtcdVersion: m.etcdVersion,
}

info.EtcdVersion = m.etcdVersion

tempDir, err := ioutil.TempDir("", "")
if err != nil {
return nil, fmt.Errorf("error creating etcd backup temp directory: %v", err)
}

defer func() {
err := os.RemoveAll(tempDir)
if err != nil {
glog.Warningf("error deleting backup temp directory %q: %v", tempDir, err)
}
}()

binDir, err := etcd.BindirForEtcdVersion(m.etcdVersion, "etcdctl")
if err != nil {
return nil, fmt.Errorf("etdctl not available for version %q", m.etcdVersion)
}

c := exec.Command(filepath.Join(binDir, "etcdctl"))

if etcdclient.IsV2(m.etcdVersion) {
c.Args = append(c.Args, "backup")
c.Args = append(c.Args, "--data-dir", m.dataDir)
c.Args = append(c.Args, "--backup-dir", tempDir)
glog.Infof("executing command %s %s", c.Path, c.Args)

env := make(map[string]string)
for k, v := range env {
c.Env = append(c.Env, k+"="+v)
}
} else {
c.Args = append(c.Args, "--endpoints", strings.Join(m.clientUrls, ","))
c.Args = append(c.Args, "snapshot", "save", filepath.Join(tempDir, "snapshot.db"))
glog.Infof("executing command %s %s", c.Path, c.Args)

env := make(map[string]string)
env["ETCDCTL_API"] = "3"

for k, v := range env {
c.Env = append(c.Env, k+"="+v)
}
}
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Start(); err != nil {
return nil, fmt.Errorf("error running etcdctl backup: %v", err)
}
processState, err := c.Process.Wait()
if err != nil {
return nil, fmt.Errorf("etcdctl backup returned an error: %v", err)
}

if !processState.Success() {
return nil, fmt.Errorf("etcdctl backup returned a non-zero exit code")
}

name, err := m.backupStore.AddBackup(tempDir, info)
if err != nil {
return nil, fmt.Errorf("error copying backup to storage: %v", err)
}

response := &protoetcd.DoBackupResponse{
Name: name,
}
glog.Infof("backup complete: %v", response)
return response, nil
return etcd.DoBackup(m.backupStore, info, m.dataDir, m.clientUrls)
}
1 change: 1 addition & 0 deletions pkg/etcd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"backup.go",
"etcdprocess.go",
"etcdserver.go",
"restore.go",
Expand Down
129 changes: 129 additions & 0 deletions pkg/etcd/backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package etcd

import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"

"github.com/golang/glog"
protoetcd "kope.io/etcd-manager/pkg/apis/etcd"
"kope.io/etcd-manager/pkg/backup"
"kope.io/etcd-manager/pkg/etcdclient"
)

// DoBackup performs a backup of etcd v2 or v3
func DoBackup(backupStore backup.Store, info *protoetcd.BackupInfo, dataDir string, clientUrls []string) (*protoetcd.DoBackupResponse, error) {
etcdVersion := info.EtcdVersion
if etcdVersion == "" {
return nil, fmt.Errorf("EtcdVersion not set")
}

if etcdclient.IsV2(etcdVersion) {
return DoBackupV2(backupStore, info, dataDir)
} else {
return DoBackupV3(backupStore, info, clientUrls)
}
}

// DoBackupV2 performs a backup of etcd v2, it needs etcdctl available
func DoBackupV2(backupStore backup.Store, info *protoetcd.BackupInfo, dataDir string) (*protoetcd.DoBackupResponse, error) {
etcdVersion := info.EtcdVersion

if dataDir == "" {
return nil, fmt.Errorf("dataDir must be set for etcd version 2")
}
if etcdVersion == "" {
return nil, fmt.Errorf("EtcdVersion not set")
}

tempDir, err := ioutil.TempDir("", "")
if err != nil {
return nil, fmt.Errorf("error creating etcd backup temp directory: %v", err)
}

defer func() {
err := os.RemoveAll(tempDir)
if err != nil {
glog.Warningf("error deleting backup temp directory %q: %v", tempDir, err)
}
}()

binDir, err := BindirForEtcdVersion(etcdVersion, "etcdctl")
if err != nil {
return nil, fmt.Errorf("etdctl not available for version %q", etcdVersion)
}

c := exec.Command(filepath.Join(binDir, "etcdctl"))

c.Args = append(c.Args, "backup")
c.Args = append(c.Args, "--data-dir", dataDir)
c.Args = append(c.Args, "--backup-dir", tempDir)
glog.Infof("executing command %s %s", c.Path, c.Args)

env := make(map[string]string)
for k, v := range env {
c.Env = append(c.Env, k+"="+v)
}

c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Start(); err != nil {
return nil, fmt.Errorf("error running etcdctl backup: %v", err)
}
processState, err := c.Process.Wait()
if err != nil {
return nil, fmt.Errorf("etcdctl backup returned an error: %v", err)
}

if !processState.Success() {
return nil, fmt.Errorf("etcdctl backup returned a non-zero exit code")
}
return uploadBackup(backupStore, info, tempDir)
}

// DoBackupV3 performs a backup of etcd v3; using the etcd v3 API
func DoBackupV3(backupStore backup.Store, info *protoetcd.BackupInfo, clientUrls []string) (*protoetcd.DoBackupResponse, error) {
etcdVersion := info.EtcdVersion

tempDir, err := ioutil.TempDir("", "")
if err != nil {
return nil, fmt.Errorf("error creating etcd backup temp directory: %v", err)
}

defer func() {
err := os.RemoveAll(tempDir)
if err != nil {
glog.Warningf("error deleting backup temp directory %q: %v", tempDir, err)
}
}()

client, err := etcdclient.NewClient(etcdVersion, clientUrls)
if err != nil {
return nil, fmt.Errorf("error building etcd client to etcd: %v", err)
}

snapshotFile := filepath.Join(tempDir, "snapshot.db")
glog.Infof("performing snapshot save to %s", snapshotFile)
if err := client.SnapshotSave(context.TODO(), snapshotFile); err != nil {
return nil, fmt.Errorf("error performing snapshot save: %v", err)
}

return uploadBackup(backupStore, info, tempDir)
}

// uploadBackup uploads a backup directory to a backup.Store
func uploadBackup(backupStore backup.Store, info *protoetcd.BackupInfo, dir string) (*protoetcd.DoBackupResponse, error) {
name, err := backupStore.AddBackup(dir, info)
if err != nil {
return nil, fmt.Errorf("error copying backup to storage: %v", err)
}

response := &protoetcd.DoBackupResponse{
Name: name,
}
glog.Infof("backup complete: %v", response)
return response, nil
}
62 changes: 1 addition & 61 deletions pkg/etcd/etcdprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package etcd

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
Expand Down Expand Up @@ -214,71 +213,12 @@ func (p *etcdProcess) DoBackup(store backup.Store, info *protoetcd.BackupInfo) (
return nil, fmt.Errorf("unable to find self node %q in %v", p.MyNodeName, p.Cluster.Nodes)
}

response := &protoetcd.DoBackupResponse{}

tempDir, err := ioutil.TempDir("", "")
if err != nil {
return nil, fmt.Errorf("error creating etcd backup temp directory: %v", err)
}

defer func() {
err := os.RemoveAll(tempDir)
if err != nil {
glog.Warningf("error deleting backup temp directory %q: %v", tempDir, err)
}
}()

clientUrls := me.ClientUrls
if p.Quarantined {
clientUrls = me.QuarantinedClientUrls
}

c := exec.Command(path.Join(p.BinDir, "etcdctl"))

if p.isV2() {
c.Args = append(c.Args, "backup")
c.Args = append(c.Args, "--data-dir", p.DataDir)
c.Args = append(c.Args, "--backup-dir", tempDir)
glog.Infof("executing command %s %s", c.Path, c.Args)

env := make(map[string]string)
for k, v := range env {
c.Env = append(c.Env, k+"="+v)
}
} else {
c.Args = append(c.Args, "--endpoints", strings.Join(clientUrls, ","))
c.Args = append(c.Args, "snapshot", "save", filepath.Join(tempDir, "snapshot.db"))
glog.Infof("executing command %s %s", c.Path, c.Args)

env := make(map[string]string)
env["ETCDCTL_API"] = "3"

for k, v := range env {
c.Env = append(c.Env, k+"="+v)
}
}
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Start(); err != nil {
return nil, fmt.Errorf("error running etcdctl backup: %v", err)
}
processState, err := c.Process.Wait()
if err != nil {
return nil, fmt.Errorf("etcdctl backup returned an error: %v", err)
}

if !processState.Success() {
return nil, fmt.Errorf("etcdctl backup returned a non-zero exit code")
}

name, err := store.AddBackup(tempDir, info)
if err != nil {
return nil, fmt.Errorf("error copying backup to storage: %v", err)
}
response.Name = name

glog.Infof("backup complete: %v", response)
return response, nil
return DoBackup(store, info, p.DataDir, clientUrls)
}

// RestoreV3Snapshot calls etcdctl snapshot restore
Expand Down
6 changes: 6 additions & 0 deletions pkg/etcdclient/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type EtcdClient interface {

// LocalNodeInfo returns information about the etcd member node we are connected to
LocalNodeInfo(ctx context.Context) (*LocalNodeInfo, error)

// SnapshotSave makes a snapshot (backup) of the data in path. Only supported in V3.
SnapshotSave(ctx context.Context, path string) error

// SupportsSnapshot checks if the Snapshot method is supported (i.e. if we are V3)
SupportsSnapshot() bool
}

// LocalNodeInfo has information about the etcd member node we are connected to
Expand Down
8 changes: 8 additions & 0 deletions pkg/etcdclient/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,11 @@ func (c *V2Client) copySubtree(ctx context.Context, p string, dest EtcdClient) (

return count, nil
}

func (c *V2Client) SnapshotSave(ctx context.Context, path string) error {
return fmt.Errorf("SnapshotSave is not supported in V2")
}

func (c *V2Client) SupportsSnapshot() bool {
return false
}
26 changes: 26 additions & 0 deletions pkg/etcdclient/v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -191,3 +193,27 @@ func (c *V3Client) RemoveMember(ctx context.Context, member *EtcdProcessMember)
_, err := c.cluster.MemberRemove(ctx, member.idv3)
return err
}

func (c *V3Client) SnapshotSave(ctx context.Context, path string) error {
out, err := os.Create(path)
if err != nil {
return fmt.Errorf("error creating snapshot file: %v", err)
}
defer out.Close()

in, err := c.client.Snapshot(ctx)
if err != nil {
return fmt.Errorf("error making snapshot: %v", err)
}
defer in.Close()

if _, err := io.Copy(out, in); err != nil {
return fmt.Errorf("error copying snapshot: %v", err)
}

return nil
}

func (c *V3Client) SupportsSnapshot() bool {
return true
}
2 changes: 1 addition & 1 deletion test/integration/etcdinstalled_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func TestEtcdInstalled(t *testing.T) {
versions := []string{"2.2.1", "3.2.12"}
for _, version := range versions {
bindir, err := etcd.BindirForEtcdVersion(version)
bindir, err := etcd.BindirForEtcdVersion(version, "etcd")
if err != nil {
t.Errorf("etcd %q not installed in /opt: %v", version, err)
}
Expand Down

0 comments on commit 63896da

Please sign in to comment.