2020-04-24 21:35:03 +02:00
package snapshot
import (
"encoding/json"
"errors"
"fmt"
2022-08-21 23:13:44 +02:00
"io"
2020-04-24 21:35:03 +02:00
"net/http"
"net/url"
2022-05-04 21:12:03 +02:00
"regexp"
"strings"
"sync/atomic"
"time"
2020-04-24 21:35:03 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
)
2022-05-04 21:12:03 +02:00
var snapshotNameRegexp = regexp . MustCompile ( ` ^[0-9] { 14}-[0-9A-Fa-f]+$ ` )
2020-04-24 21:35:03 +02:00
type snapshot struct {
Status string ` json:"status" `
Snapshot string ` json:"snapshot" `
Msg string ` json:"msg" `
}
2022-05-04 21:12:03 +02:00
// Create creates a snapshot via the provided api endpoint and returns the snapshot name
2020-04-24 21:35:03 +02:00
func Create ( createSnapshotURL string ) ( string , error ) {
2020-11-23 16:09:59 +01:00
logger . Infof ( "Creating snapshot" )
2020-04-24 21:35:03 +02: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 23:13:44 +02:00
body , err := io . ReadAll ( resp . Body )
2020-04-24 21:35:03 +02:00
if err != nil {
return "" , err
}
2020-11-30 13:49:07 +01:00
if resp . StatusCode != http . StatusOK {
return "" , fmt . Errorf ( "unexpected status code returned from %q; expecting %d; got %d; response body: %q" , createSnapshotURL , resp . StatusCode , http . StatusOK , body )
}
2020-04-24 21:35:03 +02:00
snap := snapshot { }
err = json . Unmarshal ( body , & snap )
if err != nil {
2020-11-29 11:15:31 +01:00
return "" , fmt . Errorf ( "cannot parse JSON response from %q: %w; response body: %q" , createSnapshotURL , err , body )
2020-04-24 21:35:03 +02:00
}
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 )
}
}
2022-05-04 21:12:03 +02:00
// Delete deletes a snapshot via the provided api endpoint
2020-04-24 21:35:03 +02: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 23:13:44 +02:00
body , err := io . ReadAll ( resp . Body )
2020-04-24 21:35:03 +02:00
if err != nil {
return err
}
2020-11-30 13:49:07 +01:00
if resp . StatusCode != http . StatusOK {
return fmt . Errorf ( "unexpected status code returned from %q; expecting %d; got %d; response body: %q" , deleteSnapshotURL , resp . StatusCode , http . StatusOK , body )
}
2020-04-24 21:35:03 +02:00
snap := snapshot { }
err = json . Unmarshal ( body , & snap )
if err != nil {
2020-11-29 11:15:31 +01:00
return fmt . Errorf ( "cannot parse JSON response from %q: %w; response body: %q" , deleteSnapshotURL , err , body )
2020-04-24 21:35:03 +02:00
}
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 )
}
}
2022-05-04 21:12:03 +02: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 ( ) )