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"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmbackup/snapshot"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/actions"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
|
||||
|
@ -14,9 +16,13 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
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")
|
||||
dst = flag.String("dst", "", "Where to put the backup on the remote storage. "+
|
||||
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")
|
||||
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"+
|
||||
"-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")
|
||||
|
@ -29,6 +35,34 @@ func main() {
|
|||
envflag.Parse()
|
||||
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()
|
||||
if err != nil {
|
||||
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) {
|
||||
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
|
||||
|
||||
|
|
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