Skip to content

Commit

Permalink
Add support for writing a backup to restore from
Browse files Browse the repository at this point in the history
Since we are making changes to a user's configuration, we want to
be careful and let them have a way of restoring the old version in
case something goes wrong. In the future we could handle this
restore process via the tool as well.
  • Loading branch information
RobAtticus committed Dec 12, 2018
1 parent 55d5cd0 commit b4c970b
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 12 deletions.
38 changes: 31 additions & 7 deletions pkg/tstune/config_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,32 @@ import (
"fmt"
"io"
"os"
"path"
"strings"
"time"
)

const (
osMac = "darwin"
osLinux = "linux"
fileNameMac = "/usr/local/var/postgres/postgresql.conf"
fileNameDebianFmt = "/etc/postgresql/%s/main/postgresql.conf"
fileNameRPMFmt = "/var/lib/pgsql/%s/data/postgresql.conf"
fileNameArch = "/var/lib/postgres/data/postgresql.conf"
errConfigNotFoundFmt = "could not find postgresql.conf at any of these locations:\n%v"
osMac = "darwin"
osLinux = "linux"

fileNameMac = "/usr/local/var/postgres/postgresql.conf"
fileNameDebianFmt = "/etc/postgresql/%s/main/postgresql.conf"
fileNameRPMFmt = "/var/lib/pgsql/%s/data/postgresql.conf"
fileNameArch = "/var/lib/postgres/data/postgresql.conf"

errConfigNotFoundFmt = "could not find postgresql.conf at any of these locations:\n%v"
errBackupNotCreatedFmt = "could not create backup at %s: %v"

backupFilePrefix = "timescaledb_tune.backup"
backupDateFmt = "200601021504"
)

// allows us to substitute mock versions in tests
var osStatFn = os.Stat
var osCreateFn = func(path string) (io.Writer, error) {
return os.Create(path)
}

// fileExists is a simple check for stating if a file exists and if any error
// occurs it returns false.
Expand Down Expand Up @@ -119,6 +130,19 @@ func getConfigFileState(r io.Reader) (*configFileState, error) {
return cfs, nil
}

// Backup writes the conf file state to the system's temporary directory
// with a well known name format so it can potentially be restored.
func (cfs *configFileState) Backup() (string, error) {
backupName := backupFilePrefix + time.Now().Format(backupDateFmt)
backupPath := path.Join(os.TempDir(), backupName)
bf, err := osCreateFn(backupPath)
if err != nil {
return backupPath, fmt.Errorf(errBackupNotCreatedFmt, backupPath, err)
}
_, err = cfs.WriteTo(bf)
return backupPath, err
}

func (cfs *configFileState) WriteTo(w io.Writer) (int64, error) {
ret := int64(0)
for _, l := range cfs.lines {
Expand Down
59 changes: 59 additions & 0 deletions pkg/tstune/config_file_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package tstune

import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"path"
"strings"
"testing"
"time"

"github.com/timescale/timescaledb-tune/pkg/pgtune"
)
Expand Down Expand Up @@ -324,6 +328,61 @@ func (w *testWriter) Write(buf []byte) (int, error) {
return 0, nil
}

func TestConfigFileStateBackup(t *testing.T) {
oldOSCreateFn := osCreateFn
now := time.Now()
lines := []string{"foo", "bar", "baz", "quaz"}
cfs := &configFileState{lines: lines}
wantFileName := backupFilePrefix + now.Format(backupDateFmt)
wantPath := path.Join(os.TempDir(), wantFileName)

osCreateFn = func(_ string) (io.Writer, error) {
return nil, fmt.Errorf("erroring")
}

path, err := cfs.Backup()
if path != wantPath {
t.Errorf("incorrect path in error case: got\n%s\nwant\n%s", path, wantPath)
}
if err == nil {
t.Errorf("unexpected lack of error for bad create")
}
want := fmt.Sprintf(errBackupNotCreatedFmt, wantPath, "erroring")
if got := err.Error(); got != want {
t.Errorf("incorrect error: got\n%s\nwant\n%s", got, want)
}

var buf bytes.Buffer
osCreateFn = func(p string) (io.Writer, error) {
if p != wantPath {
t.Errorf("incorrect backup path: got %s want %s", p, wantPath)
}
return &buf, nil
}
path, err = cfs.Backup()
if path != wantPath {
t.Errorf("incorrect path in correct case: got\n%s\nwant\n%s", path, wantPath)
}
if err != nil {
t.Errorf("unexpected error for backup: %v", err)
}

scanner := bufio.NewScanner(bytes.NewReader(buf.Bytes()))
i := 0
for scanner.Scan() {
if scanner.Err() != nil {
t.Errorf("unexpected error while scanning: %v", scanner.Err())
}
got := scanner.Text()
if want := lines[i]; got != want {
t.Errorf("incorrect line at %d: got\n%s\nwant\n%s", i, got, want)
}
i++
}

osCreateFn = oldOSCreateFn
}

func TestConfigFileStateWriteTo(t *testing.T) {
cases := []struct {
desc string
Expand Down
14 changes: 10 additions & 4 deletions pkg/tstune/tuner.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ const (
fmtTunableParam = "%s = %s%s\n"
fmtLastTuned = "timescaledb.last_tuned = '%s'"

dateFmt = "2006-01-02 15:04"
fudgeFactor = 0.05
lastTunedDateFmt = "2006-01-02 15:04"
fudgeFactor = 0.05

pgMajor96 = "9.6"
pgMajor10 = "10"
Expand Down Expand Up @@ -192,6 +192,12 @@ func (t *Tuner) Run(flags *TunerFlags, in io.Reader, out io.Writer, outErr io.Wr
ifErrHandle(err)
t.cfs = cfs

// Write backup
backupPath, err := cfs.Backup()
t.handler.p.Statement("Writing backup to:")
printFn(os.Stderr, backupPath+"\n\n")
ifErrHandle(err)

// Process the tuning of settings
if t.flags.Quiet {
err = t.processQuiet(config)
Expand All @@ -211,7 +217,7 @@ func (t *Tuner) Run(flags *TunerFlags, in io.Reader, out io.Writer, outErr io.Wr
}

// Append the current time to mark when database was last tuned
lastTunedLine := fmt.Sprintf(fmtLastTuned, time.Now().Format(dateFmt))
lastTunedLine := fmt.Sprintf(fmtLastTuned, time.Now().Format(lastTunedDateFmt))
cfs.lines = append(cfs.lines, lastTunedLine)

// Wrap up: Either write it out, or show success in --dry-run
Expand Down Expand Up @@ -519,7 +525,7 @@ func (t *Tuner) processQuiet(config *pgtune.SystemConfig) error {
return err
}
if changedSettings > 0 {
printFn(os.Stdout, fmtLastTuned+"\n", time.Now().Format(dateFmt))
printFn(os.Stdout, fmtLastTuned+"\n", time.Now().Format(lastTunedDateFmt))
checker := newYesNoChecker("not using these settings could lead to suboptimal performance")
err = t.promptUntilValidInput("Use these recommendations? "+promptYesNo, checker)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/tstune/tuner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,7 @@ var (
)

func TestTunerProcessQuiet(t *testing.T) {
lastTuned := fmt.Sprintf(fmtLastTuned, time.Now().Format(dateFmt))
lastTuned := fmt.Sprintf(fmtLastTuned, time.Now().Format(lastTunedDateFmt))
cases := []struct {
desc string
lines []string
Expand Down

0 comments on commit b4c970b

Please sign in to comment.