2019-11-07 19:05:39 +00:00
package main
import (
"flag"
"fmt"
2023-01-18 19:35:21 +00:00
"net/url"
2019-11-07 19:05:39 +00:00
"os"
2022-05-06 15:04:09 +00:00
"path/filepath"
2020-04-24 19:35:03 +00:00
"strings"
2021-12-02 09:55:58 +00:00
"time"
2019-11-07 19:05:39 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/actions"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
2020-10-28 18:09:10 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fsnil"
2019-11-07 19:05:39 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
2020-02-10 11:26:18 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
2020-08-16 14:05:52 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
2021-12-02 09:55:58 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
2019-11-07 19:05:39 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
2022-07-21 16:58:22 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/pushmetrics"
2022-05-04 19:12:03 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/snapshot"
2019-11-07 19:05:39 +00:00
)
var (
2021-12-02 09:55:58 +00:00
httpListenAddr = flag . String ( "httpListenAddr" , ":8420" , "TCP address for exporting metrics at /metrics page" )
2020-04-24 19:35:03 +00:00
storageDataPath = flag . String ( "storageDataPath" , "victoria-metrics-data" , "Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage" )
2021-05-07 05:40:30 +00:00
snapshotName = flag . String ( "snapshotName" , "" , "Name for the snapshot to backup. See https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-work-with-snapshots. There is no need in setting -snapshotName if -snapshot.createURL is set" )
2020-09-09 18:10:34 +00:00
snapshotCreateURL = flag . String ( "snapshot.createURL" , "" , "VictoriaMetrics create snapshot url. When this is given a snapshot will automatically be created during backup. " +
2021-05-07 05:40:30 +00:00
"Example: http://victoriametrics:8428/snapshot/create . There is no need in setting -snapshotName if -snapshot.createURL is set" )
2020-09-09 18:10:34 +00:00
snapshotDeleteURL = flag . String ( "snapshot.deleteURL" , "" , "VictoriaMetrics delete snapshot url. Optional. Will be generated from -snapshot.createURL if not provided. " +
2021-05-17 22:12:34 +00:00
"All created snapshots will be automatically deleted. Example: http://victoriametrics:8428/snapshot/delete" )
2020-04-24 19:35:03 +00:00
dst = flag . String ( "dst" , "" , "Where to put the backup on the remote storage. " +
2022-12-12 19:17:22 +00:00
"Example: gs://bucket/path/to/backup, s3://bucket/path/to/backup, azblob://container/path/to/backup or fs:///path/to/local/backup/dir\n" +
2019-11-07 19:05:39 +00:00
"-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded" )
2019-11-19 18:31:52 +00:00
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" )
concurrency = flag . Int ( "concurrency" , 10 , "The number of concurrent workers. Higher concurrency may reduce backup duration" )
2020-08-16 14:05:52 +00:00
maxBytesPerSecond = flagutil . NewBytes ( "maxBytesPerSecond" , 0 , "The maximum upload speed. There is no limit if it is set to 0" )
2019-11-07 19:05:39 +00:00
)
func main ( ) {
2020-05-16 08:59:30 +00:00
// Write flags and help message to stdout, since it is easier to grep or pipe.
flag . CommandLine . SetOutput ( os . Stdout )
2019-11-07 19:05:39 +00:00
flag . Usage = usage
2023-01-18 19:35:21 +00:00
flagutil . RegisterSecretFlag ( "snapshot.createURL" )
flagutil . RegisterSecretFlag ( "snapshot.deleteURL" )
2020-02-10 11:26:18 +00:00
envflag . Parse ( )
2019-11-07 19:05:39 +00:00
buildinfo . Init ( )
2020-08-11 19:54:13 +00:00
logger . Init ( )
2019-11-07 19:05:39 +00:00
2023-03-25 04:49:58 +00:00
// Storing snapshot delete function to be able to call it in case
// of error since logger.Fatal will exit the program without
// calling deferred functions.
2023-03-25 05:03:11 +00:00
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2055
deleteSnapshot := func ( ) { }
2023-03-25 04:49:58 +00:00
2020-04-24 19:35:03 +00:00
if len ( * snapshotCreateURL ) > 0 {
2023-01-18 19:35:21 +00:00
// create net/url object
2023-02-26 20:18:59 +00:00
createURL , err := url . Parse ( * snapshotCreateURL )
2023-01-18 19:35:21 +00:00
if err != nil {
logger . Fatalf ( "cannot parse snapshotCreateURL: %s" , err )
}
2021-05-07 05:40:30 +00:00
if len ( * snapshotName ) > 0 {
logger . Fatalf ( "-snapshotName shouldn't be set if -snapshot.createURL is set, since snapshots are created automatically in this case" )
}
2023-02-26 20:18:59 +00:00
logger . Infof ( "Snapshot create url %s" , createURL . Redacted ( ) )
2020-04-24 19:35:03 +00:00
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 )
}
}
2023-02-26 20:18:59 +00:00
deleteURL , err := url . Parse ( * snapshotDeleteURL )
2023-01-18 19:35:21 +00:00
if err != nil {
logger . Fatalf ( "cannot parse snapshotDeleteURL: %s" , err )
}
2023-02-26 20:18:59 +00:00
logger . Infof ( "Snapshot delete url %s" , deleteURL . Redacted ( ) )
2020-04-24 19:35:03 +00:00
2023-02-26 20:18:59 +00:00
name , err := snapshot . Create ( createURL . String ( ) )
2020-04-24 19:35:03 +00:00
if err != nil {
2020-11-23 15:09:59 +00:00
logger . Fatalf ( "cannot create snapshot: %s" , err )
2020-04-24 19:35:03 +00:00
}
err = flag . Set ( "snapshotName" , name )
if err != nil {
2020-11-23 15:09:59 +00:00
logger . Fatalf ( "cannot set snapshotName flag: %v" , err )
2020-04-24 19:35:03 +00:00
}
2023-03-25 04:49:58 +00:00
deleteSnapshot = func ( ) {
2023-02-26 20:18:59 +00:00
err := snapshot . Delete ( deleteURL . String ( ) , name )
2020-04-24 19:35:03 +00:00
if err != nil {
2020-11-23 15:09:59 +00:00
logger . Fatalf ( "cannot delete snapshot: %s" , err )
2020-04-24 19:35:03 +00:00
}
2023-03-25 04:49:58 +00:00
}
2022-05-04 19:12:03 +00:00
} else if len ( * snapshotName ) == 0 {
logger . Fatalf ( "`-snapshotName` or `-snapshot.createURL` must be provided" )
}
2020-04-24 19:35:03 +00:00
2023-01-27 07:08:35 +00:00
go httpserver . Serve ( * httpListenAddr , false , nil )
2021-12-02 09:55:58 +00:00
2024-01-15 11:37:02 +00:00
pushmetrics . Init ( )
2023-03-25 05:03:11 +00:00
err := makeBackup ( )
deleteSnapshot ( )
if err != nil {
logger . Fatalf ( "cannot create backup: %s" , err )
}
2024-01-15 11:37:02 +00:00
pushmetrics . Stop ( )
2023-03-25 05:03:11 +00:00
startTime := time . Now ( )
logger . Infof ( "gracefully shutting down http server for metrics at %q" , * httpListenAddr )
if err := httpserver . Stop ( * httpListenAddr ) ; err != nil {
logger . Fatalf ( "cannot stop http server for metrics: %s" , err )
}
logger . Infof ( "successfully shut down http server for metrics in %.3f seconds" , time . Since ( startTime ) . Seconds ( ) )
}
func makeBackup ( ) error {
if err := snapshot . Validate ( * snapshotName ) ; err != nil {
return fmt . Errorf ( "invalid -snapshotName=%q: %s" , * snapshotName , err )
}
2019-11-07 19:05:39 +00:00
srcFS , err := newSrcFS ( )
if err != nil {
2023-03-25 05:03:11 +00:00
return err
2019-11-07 19:05:39 +00:00
}
dstFS , err := newDstFS ( )
if err != nil {
2023-03-25 05:03:11 +00:00
return err
2019-11-07 19:05:39 +00:00
}
originFS , err := newOriginFS ( )
if err != nil {
2023-03-25 05:03:11 +00:00
return err
2019-11-07 19:05:39 +00:00
}
a := & actions . Backup {
Concurrency : * concurrency ,
Src : srcFS ,
Dst : dstFS ,
Origin : originFS ,
}
if err := a . Run ( ) ; err != nil {
2023-03-25 05:03:11 +00:00
return err
2019-11-07 19:05:39 +00:00
}
2020-10-09 12:11:28 +00:00
srcFS . MustStop ( )
2020-10-09 12:31:39 +00:00
dstFS . MustStop ( )
originFS . MustStop ( )
2023-03-25 05:03:11 +00:00
return nil
2019-11-07 19:05:39 +00:00
}
func usage ( ) {
const s = `
2023-01-04 05:52:40 +00:00
vmbackup performs backups for VictoriaMetrics data from instant snapshots to gcs , s3 , azblob
2019-11-07 19:05:39 +00:00
or local filesystem . Backed up data can be restored with vmrestore .
2021-05-18 13:37:28 +00:00
See the docs at https : //docs.victoriametrics.com/vmbackup.html .
2019-11-07 19:05:39 +00:00
`
2020-12-03 19:40:30 +00:00
flagutil . Usage ( s )
2019-11-07 19:05:39 +00:00
}
func newSrcFS ( ) ( * fslocal . FS , error ) {
2023-05-03 08:48:53 +00:00
snapshotPath := filepath . Join ( * storageDataPath , "snapshots" , * snapshotName )
2019-11-07 19:05:39 +00:00
// Verify the snapshot exists.
f , err := os . Open ( snapshotPath )
if err != nil {
2020-06-30 19:58:18 +00:00
return nil , fmt . Errorf ( "cannot open snapshot at %q: %w" , snapshotPath , err )
2019-11-07 19:05:39 +00:00
}
fi , err := f . Stat ( )
_ = f . Close ( )
if err != nil {
2020-06-30 19:58:18 +00:00
return nil , fmt . Errorf ( "cannot stat %q: %w" , snapshotPath , err )
2019-11-07 19:05:39 +00:00
}
if ! fi . IsDir ( ) {
return nil , fmt . Errorf ( "snapshot %q must be a directory" , snapshotPath )
}
fs := & fslocal . FS {
2019-11-19 18:31:52 +00:00
Dir : snapshotPath ,
2022-12-15 03:26:24 +00:00
MaxBytesPerSecond : maxBytesPerSecond . IntN ( ) ,
2019-11-19 18:31:52 +00:00
}
if err := fs . Init ( ) ; err != nil {
2020-06-30 19:58:18 +00:00
return nil , fmt . Errorf ( "cannot initialize fs: %w" , err )
2019-11-07 19:05:39 +00:00
}
return fs , nil
}
func newDstFS ( ) ( common . RemoteFS , error ) {
fs , err := actions . NewRemoteFS ( * dst )
if err != nil {
2020-06-30 19:58:18 +00:00
return nil , fmt . Errorf ( "cannot parse `-dst`=%q: %w" , * dst , err )
2019-11-07 19:05:39 +00:00
}
2022-05-06 15:04:09 +00:00
if hasFilepathPrefix ( * dst , * storageDataPath ) {
return nil , fmt . Errorf ( "-dst=%q can not point to the directory with VictoriaMetrics data (aka -storageDataPath=%q)" , * dst , * storageDataPath )
}
2019-11-07 19:05:39 +00:00
return fs , nil
}
2022-05-06 15:04:09 +00:00
func hasFilepathPrefix ( path , prefix string ) bool {
if ! strings . HasPrefix ( path , "fs://" ) {
return false
}
path = path [ len ( "fs://" ) : ]
pathAbs , err := filepath . Abs ( path )
if err != nil {
return false
}
prefixAbs , err := filepath . Abs ( prefix )
if err != nil {
return false
}
2023-08-16 12:45:35 +00:00
if prefixAbs == pathAbs {
return true
}
rel , err := filepath . Rel ( prefixAbs , pathAbs )
if err != nil {
// if paths can't be related - they don't match
return false
}
if i := strings . Index ( rel , "." ) ; i == 0 {
// if path can be related only with . as first char - they still don't match
return false
}
// if paths are related - it is a match
return true
2022-05-06 15:04:09 +00:00
}
2020-10-28 18:09:10 +00:00
func newOriginFS ( ) ( common . OriginFS , error ) {
2019-11-07 19:05:39 +00:00
if len ( * origin ) == 0 {
2020-10-28 18:09:10 +00:00
return & fsnil . FS { } , nil
2019-11-07 19:05:39 +00:00
}
fs , err := actions . NewRemoteFS ( * origin )
if err != nil {
2020-06-30 19:58:18 +00:00
return nil , fmt . Errorf ( "cannot parse `-origin`=%q: %w" , * origin , err )
2019-11-07 19:05:39 +00:00
}
return fs , nil
}