2020-04-24 19:35:03 +00:00
|
|
|
package snapshot
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-08-21 21:13:44 +00:00
|
|
|
"io"
|
2020-04-24 19:35:03 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2022-05-04 19:12:03 +00:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
2020-04-24 19:35:03 +00:00
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
|
|
|
)
|
|
|
|
|
2022-05-04 19:12:03 +00:00
|
|
|
var snapshotNameRegexp = regexp.MustCompile(`^[0-9]{14}-[0-9A-Fa-f]+$`)
|
|
|
|
|
2020-04-24 19:35:03 +00:00
|
|
|
type snapshot struct {
|
|
|
|
Status string `json:"status"`
|
|
|
|
Snapshot string `json:"snapshot"`
|
|
|
|
Msg string `json:"msg"`
|
|
|
|
}
|
|
|
|
|
2022-05-04 19:12:03 +00:00
|
|
|
// Create creates a snapshot via the provided api endpoint and returns the snapshot name
|
2020-04-24 19:35:03 +00:00
|
|
|
func Create(createSnapshotURL string) (string, error) {
|
2020-11-23 15:09:59 +00:00
|
|
|
logger.Infof("Creating snapshot")
|
2020-04-24 19:35:03 +00:00
|
|
|
u, err := url.Parse(createSnapshotURL)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
resp, err := http.Get(u.String())
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2022-08-21 21:13:44 +00:00
|
|
|
body, err := io.ReadAll(resp.Body)
|
2020-04-24 19:35:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2020-11-30 12:49:07 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
2023-01-18 19:35:21 +00:00
|
|
|
return "", fmt.Errorf("unexpected status code returned from %q: %d; expecting %d; response body: %q", u.Redacted(), resp.StatusCode, http.StatusOK, body)
|
2020-11-30 12:49:07 +00:00
|
|
|
}
|
2020-04-24 19:35:03 +00:00
|
|
|
|
|
|
|
snap := snapshot{}
|
|
|
|
err = json.Unmarshal(body, &snap)
|
|
|
|
if err != nil {
|
2023-01-18 19:35:21 +00:00
|
|
|
return "", fmt.Errorf("cannot parse JSON response from %q: %w; response body: %q", u.Redacted(), err, body)
|
2020-04-24 19:35:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if snap.Status == "ok" {
|
|
|
|
logger.Infof("Snapshot %s created", snap.Snapshot)
|
|
|
|
return snap.Snapshot, nil
|
2023-11-02 20:37:49 +00:00
|
|
|
}
|
|
|
|
if snap.Status == "error" {
|
2020-04-24 19:35:03 +00:00
|
|
|
return "", errors.New(snap.Msg)
|
|
|
|
}
|
2023-11-02 20:37:49 +00:00
|
|
|
return "", fmt.Errorf("Unkown status: %v", snap.Status)
|
2020-04-24 19:35:03 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 19:12:03 +00:00
|
|
|
// Delete deletes a snapshot via the provided api endpoint
|
2020-04-24 19:35:03 +00:00
|
|
|
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
|
|
|
|
}
|
2022-08-21 21:13:44 +00:00
|
|
|
body, err := io.ReadAll(resp.Body)
|
2020-04-24 19:35:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-30 12:49:07 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
2023-01-18 19:35:21 +00:00
|
|
|
return fmt.Errorf("unexpected status code returned from %q: %d; expecting %d; response body: %q", u.Redacted(), resp.StatusCode, http.StatusOK, body)
|
2020-11-30 12:49:07 +00:00
|
|
|
}
|
2020-04-24 19:35:03 +00:00
|
|
|
|
|
|
|
snap := snapshot{}
|
|
|
|
err = json.Unmarshal(body, &snap)
|
|
|
|
if err != nil {
|
2023-01-18 19:35:21 +00:00
|
|
|
return fmt.Errorf("cannot parse JSON response from %q: %w; response body: %q", u.Redacted(), err, body)
|
2020-04-24 19:35:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if snap.Status == "ok" {
|
|
|
|
logger.Infof("Snapshot %s deleted", snapshotName)
|
|
|
|
return nil
|
2023-11-02 20:37:49 +00:00
|
|
|
}
|
|
|
|
if snap.Status == "error" {
|
2020-04-24 19:35:03 +00:00
|
|
|
return errors.New(snap.Msg)
|
|
|
|
}
|
2023-11-02 20:37:49 +00:00
|
|
|
return fmt.Errorf("Unkown status: %v", snap.Status)
|
2020-04-24 19:35:03 +00:00
|
|
|
}
|
2022-05-04 19:12:03 +00:00
|
|
|
|
|
|
|
// Validate validates the snapshotName
|
|
|
|
func Validate(snapshotName string) error {
|
|
|
|
_, err := Time(snapshotName)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Time returns snapshot creation time from the given snapshotName
|
|
|
|
func Time(snapshotName string) (time.Time, error) {
|
|
|
|
if !snapshotNameRegexp.MatchString(snapshotName) {
|
|
|
|
return time.Time{}, fmt.Errorf("unexpected snapshot name=%q; it must match %q regexp", snapshotName, snapshotNameRegexp.String())
|
|
|
|
}
|
|
|
|
n := strings.IndexByte(snapshotName, '-')
|
|
|
|
if n < 0 {
|
|
|
|
logger.Panicf("BUG: cannot find `-` in snapshotName=%q", snapshotName)
|
|
|
|
}
|
|
|
|
s := snapshotName[:n]
|
|
|
|
t, err := time.Parse("20060102150405", s)
|
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, fmt.Errorf("unexpected timestamp=%q in snapshot name: %w; it must match YYYYMMDDhhmmss pattern", s, err)
|
|
|
|
}
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewName returns new name for new snapshot
|
|
|
|
func NewName() string {
|
|
|
|
return fmt.Sprintf("%s-%08X", time.Now().UTC().Format("20060102150405"), nextSnapshotIdx())
|
|
|
|
}
|
|
|
|
|
|
|
|
func nextSnapshotIdx() uint64 {
|
|
|
|
return atomic.AddUint64(&snapshotIdx, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
var snapshotIdx = uint64(time.Now().UnixNano())
|