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 {
2022-12-28 19:41:27 +00:00
return "" , fmt . Errorf ( "unexpected status code returned from %q: %d; expecting %d; response body: %q" , createSnapshotURL , 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 {
2020-11-29 10:15:31 +00:00
return "" , fmt . Errorf ( "cannot parse JSON response from %q: %w; response body: %q" , createSnapshotURL , 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
} else if snap . Status == "error" {
return "" , errors . New ( snap . Msg )
} else {
return "" , fmt . Errorf ( "Unkown status: %v" , snap . Status )
}
}
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 {
2022-12-28 19:41:27 +00:00
return fmt . Errorf ( "unexpected status code returned from %q: %d; expecting %d; response body: %q" , deleteSnapshotURL , 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 {
2020-11-29 10:15:31 +00:00
return fmt . Errorf ( "cannot parse JSON response from %q: %w; response body: %q" , deleteSnapshotURL , err , body )
2020-04-24 19:35:03 +00: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 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 ( ) )