mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/vmbackup: added ability to create and delete snapshots during backup (#428)
* app/vmbackup: added ability to create and delete snapshots during backup Resolves: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/422 * Add snapshot create and delete url flags * Fixed errcheck warnings in build
This commit is contained in:
parent
13b4069c59
commit
7a6b2839b4
3 changed files with 235 additions and 4 deletions
|
@ -4,7 +4,9 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmbackup/snapshot"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/actions"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/actions"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
|
||||||
|
@ -14,9 +16,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage")
|
storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage")
|
||||||
snapshotName = flag.String("snapshotName", "", "Name for the snapshot to backup. See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots")
|
snapshotName = flag.String("snapshotName", "", "Name for the snapshot to backup. See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots")
|
||||||
dst = flag.String("dst", "", "Where to put the backup on the remote storage. "+
|
snapshotCreateURL = flag.String("snapshot.createURL", "", "VictoriaMetrics create snapshot url. When this is given a snapshot will automatically be created during backup."+
|
||||||
|
"Example: http://victoriametrics:8428/snaphsot/create")
|
||||||
|
snapshotDeleteURL = flag.String("snapshot.deleteURL", "", "VictoriaMetrics delete snapshot url. Optional. Will be generated from snapshotCreateURL if not provided. All created snaphosts will be automatically deleted."+
|
||||||
|
"Example: http://victoriametrics:8428/snaphsot/delete")
|
||||||
|
dst = flag.String("dst", "", "Where to put the backup on the remote storage. "+
|
||||||
"Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir\n"+
|
"Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir\n"+
|
||||||
"-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded")
|
"-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded")
|
||||||
origin = flag.String("origin", "", "Optional origin directory on the remote storage with old backup for server-side copying when performing full backup. This speeds up full backups")
|
origin = flag.String("origin", "", "Optional origin directory on the remote storage with old backup for server-side copying when performing full backup. This speeds up full backups")
|
||||||
|
@ -29,6 +35,34 @@ func main() {
|
||||||
envflag.Parse()
|
envflag.Parse()
|
||||||
buildinfo.Init()
|
buildinfo.Init()
|
||||||
|
|
||||||
|
if len(*snapshotCreateURL) > 0 {
|
||||||
|
logger.Infof("%s", "Snapshots enabled")
|
||||||
|
logger.Infof("Snapshot create url %s", *snapshotCreateURL)
|
||||||
|
if len(*snapshotDeleteURL) <= 0 {
|
||||||
|
err := flag.Set("snapshot.deleteURL", strings.Replace(*snapshotCreateURL, "/create", "/delete", 1))
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf("Failed to set snapshot.deleteURL flag: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Infof("Snapshot delete url %s", *snapshotDeleteURL)
|
||||||
|
|
||||||
|
name, err := snapshot.Create(*snapshotCreateURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
err = flag.Set("snapshotName", name)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf("Failed to set snapshotName flag: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := snapshot.Delete(*snapshotDeleteURL, name)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
srcFS, err := newSrcFS()
|
srcFS, err := newSrcFS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("%s", err)
|
logger.Fatalf("%s", err)
|
||||||
|
@ -67,7 +101,7 @@ See the docs at https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/a
|
||||||
|
|
||||||
func newSrcFS() (*fslocal.FS, error) {
|
func newSrcFS() (*fslocal.FS, error) {
|
||||||
if len(*snapshotName) == 0 {
|
if len(*snapshotName) == 0 {
|
||||||
return nil, fmt.Errorf("`-snapshotName` cannot be empty")
|
return nil, fmt.Errorf("`-snapshotName` or `-snapshot.createURL` must be provided")
|
||||||
}
|
}
|
||||||
snapshotPath := *storageDataPath + "/snapshots/" + *snapshotName
|
snapshotPath := *storageDataPath + "/snapshots/" + *snapshotName
|
||||||
|
|
||||||
|
|
91
app/vmbackup/snapshot/snapshot.go
Normal file
91
app/vmbackup/snapshot/snapshot.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package snapshot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type snapshot struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Snapshot string `json:"snapshot"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a snapshot and the provided api endpoint and returns
|
||||||
|
// the snapshot name
|
||||||
|
func Create(createSnapshotURL string) (string, error) {
|
||||||
|
logger.Infof("%s", "Creating snapshot")
|
||||||
|
u, err := url.Parse(createSnapshotURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
snap := snapshot{}
|
||||||
|
err = json.Unmarshal(body, &snap)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if snap.Status == "ok" {
|
||||||
|
logger.Infof("Snapshot %s created", snap.Snapshot)
|
||||||
|
return snap.Snapshot, nil
|
||||||
|
} else if snap.Status == "error" {
|
||||||
|
return "", errors.New(snap.Msg)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("Unkown status: %v", snap.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a snapshot and the provided api endpoint returns any failure
|
||||||
|
func Delete(deleteSnapshotURL string, snapshotName string) error {
|
||||||
|
logger.Infof("Deleting snapshot %s", snapshotName)
|
||||||
|
formData := url.Values{
|
||||||
|
"snapshot": {snapshotName},
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(deleteSnapshotURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(u.String(), formData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
snap := snapshot{}
|
||||||
|
err = json.Unmarshal(body, &snap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if snap.Status == "ok" {
|
||||||
|
logger.Infof("Snapshot %s deleted", snapshotName)
|
||||||
|
return nil
|
||||||
|
} else if snap.Status == "error" {
|
||||||
|
return errors.New(snap.Msg)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unkown status: %v", snap.Status)
|
||||||
|
}
|
||||||
|
}
|
106
app/vmbackup/snapshot/snapshot_test.go
Normal file
106
app/vmbackup/snapshot/snapshot_test.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package snapshot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateSnapshot(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/snapshot/create" {
|
||||||
|
_, err := io.WriteString(w, `{"status":"ok","snapshot":"mysnapshot"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write response output: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Invalid path, got %v", r.URL.Path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
snapshotName, err := Create(server.URL + "/snapshot/create")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed taking snapshot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if snapshotName != "mysnapshot" {
|
||||||
|
t.Fatalf("Snapshot name is not correct, got %v", snapshotName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSnapshotFailed(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/snapshot/create" {
|
||||||
|
_, err := io.WriteString(w, `{"status":"error","msg":"I am unwell"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write response output: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Invalid path, got %v", r.URL.Path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
snapshotName, err := Create(server.URL + "/snapshot/create")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Snapshot did not fail, got snapshot: %v", snapshotName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteSnapshot(t *testing.T) {
|
||||||
|
snapshotName := "mysnapshot"
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/snapshot/delete" {
|
||||||
|
_, err := io.WriteString(w, `{"status":"ok"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write response output: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Invalid path, got %v", r.URL.Path)
|
||||||
|
}
|
||||||
|
if r.FormValue("snapshot") != snapshotName {
|
||||||
|
t.Fatalf("Invalid snapshot name, got %v", snapshotName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := Delete(server.URL+"/snapshot/delete", snapshotName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to delete snapshot: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteSnapshotFailed(t *testing.T) {
|
||||||
|
snapshotName := "mysnapshot"
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/snapshot/delete" {
|
||||||
|
_, err := io.WriteString(w, `{"status":"error", "msg":"failed to delete"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write response output: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Invalid path, got %v", r.URL.Path)
|
||||||
|
}
|
||||||
|
if r.FormValue("snapshot") != snapshotName {
|
||||||
|
t.Fatalf("Invalid snapshot name, got %v", snapshotName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := Delete(server.URL+"/snapshot/delete", snapshotName)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Snapshot should have failed, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue