vitess.io/vitess@v0.16.2/go/vt/vtctl/backup.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vtctl
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/spf13/pflag"
    25  	"google.golang.org/grpc"
    26  
    27  	"vitess.io/vitess/go/protoutil"
    28  	"vitess.io/vitess/go/vt/logutil"
    29  	"vitess.io/vitess/go/vt/mysqlctl"
    30  	"vitess.io/vitess/go/vt/mysqlctl/backupstorage"
    31  	"vitess.io/vitess/go/vt/topo/topoproto"
    32  	"vitess.io/vitess/go/vt/vterrors"
    33  	"vitess.io/vitess/go/vt/wrangler"
    34  
    35  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    36  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    37  )
    38  
    39  func init() {
    40  	addCommand("Shards", command{
    41  		name:   "ListBackups",
    42  		method: commandListBackups,
    43  		params: "<keyspace/shard>",
    44  		help:   "Lists all the backups for a shard.",
    45  	})
    46  	addCommand("Shards", command{
    47  		name:   "BackupShard",
    48  		method: commandBackupShard,
    49  		params: "[--allow_primary=false] <keyspace/shard>",
    50  		help:   "Chooses a tablet and creates a backup for a shard.",
    51  	})
    52  	addCommand("Shards", command{
    53  		name:   "RemoveBackup",
    54  		method: commandRemoveBackup,
    55  		params: "<keyspace/shard> <backup name>",
    56  		help:   "Removes a backup for the BackupStorage.",
    57  	})
    58  	addCommand("Tablets", command{
    59  		name:   "Backup",
    60  		method: commandBackup,
    61  		params: "[--concurrency=4] [--allow_primary=false] [--incremental_from_pos=<pos>] <tablet alias>",
    62  		help:   "Run a full or an incremental backup. Uses the BackupStorage service to store a new backup. With full backup, stops mysqld, takes the backup, starts mysqld and resumes replication. With incremental backup (indicated by '--incremental_from_pos', rotate and copy binary logs without disrupting the mysqld service).",
    63  	})
    64  	addCommand("Tablets", command{
    65  		name:   "RestoreFromBackup",
    66  		method: commandRestoreFromBackup,
    67  		params: "[--backup_timestamp=yyyy-MM-dd.HHmmss] [--restore_to_pos=<pos>] [--dry_run] <tablet alias>",
    68  		help:   "Stops mysqld and restores the data from the latest backup or if a timestamp is specified then the most recent backup at or before that time. If '--restore_to_pos' is given, then a point in time restore based on one full backup followed by zero or more incremental backups. dry-run only validates restore steps without actually restoring data",
    69  	})
    70  }
    71  
    72  func commandBackup(ctx context.Context, wr *wrangler.Wrangler, subFlags *pflag.FlagSet, args []string) error {
    73  	concurrency := subFlags.Int("concurrency", 4, "Specifies the number of compression/checksum jobs to run simultaneously")
    74  	allowPrimary := subFlags.Bool("allow_primary", false, "Allows backups to be taken on primary. Warning!! If you are using the builtin backup engine, this will shutdown your primary mysql for as long as it takes to create a backup.")
    75  	incrementalFromPos := subFlags.String("incremental_from_pos", "", "Position of previous backup. Default: empty. If given, then this backup becomes an incremental backup from given position. If value is 'auto', backup taken from last successful backup position")
    76  
    77  	if err := subFlags.Parse(args); err != nil {
    78  		return err
    79  	}
    80  	if subFlags.NArg() != 1 {
    81  		return fmt.Errorf("the Backup command requires the <tablet alias> argument")
    82  	}
    83  
    84  	tabletAlias, err := topoproto.ParseTabletAlias(subFlags.Arg(0))
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	return wr.VtctldServer().Backup(&vtctldatapb.BackupRequest{
    90  		TabletAlias:        tabletAlias,
    91  		Concurrency:        uint64(*concurrency),
    92  		AllowPrimary:       *allowPrimary,
    93  		IncrementalFromPos: *incrementalFromPos,
    94  	}, &backupEventStreamLogger{logger: wr.Logger(), ctx: ctx})
    95  }
    96  
    97  // backupEventStreamLogger takes backup events from the vtctldserver and emits
    98  // them via logutil.LogEvent, preserving legacy behavior.
    99  type backupEventStreamLogger struct {
   100  	grpc.ServerStream
   101  	logger logutil.Logger
   102  	ctx    context.Context
   103  }
   104  
   105  func (b *backupEventStreamLogger) Context() context.Context { return b.ctx }
   106  
   107  func (b *backupEventStreamLogger) Send(resp *vtctldatapb.BackupResponse) error {
   108  	logutil.LogEvent(b.logger, resp.Event)
   109  	return nil
   110  }
   111  
   112  func commandBackupShard(ctx context.Context, wr *wrangler.Wrangler, subFlags *pflag.FlagSet, args []string) error {
   113  	concurrency := subFlags.Int("concurrency", 4, "Specifies the number of compression/checksum jobs to run simultaneously")
   114  	allowPrimary := subFlags.Bool("allow_primary", false, "Whether to use primary tablet for backup. Warning!! If you are using the builtin backup engine, this will shutdown your primary mysql for as long as it takes to create a backup.")
   115  
   116  	if err := subFlags.Parse(args); err != nil {
   117  		return err
   118  	}
   119  	if subFlags.NArg() != 1 {
   120  		return fmt.Errorf("action BackupShard requires <keyspace/shard>")
   121  	}
   122  
   123  	keyspace, shard, err := topoproto.ParseKeyspaceShard(subFlags.Arg(0))
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	return wr.VtctldServer().BackupShard(&vtctldatapb.BackupShardRequest{
   129  		Keyspace:     keyspace,
   130  		Shard:        shard,
   131  		Concurrency:  uint64(*concurrency),
   132  		AllowPrimary: *allowPrimary,
   133  	}, &backupEventStreamLogger{logger: wr.Logger(), ctx: ctx})
   134  }
   135  
   136  func commandListBackups(ctx context.Context, wr *wrangler.Wrangler, subFlags *pflag.FlagSet, args []string) error {
   137  	if err := subFlags.Parse(args); err != nil {
   138  		return err
   139  	}
   140  	if subFlags.NArg() != 1 {
   141  		return fmt.Errorf("action ListBackups requires <keyspace/shard>")
   142  	}
   143  
   144  	keyspace, shard, err := topoproto.ParseKeyspaceShard(subFlags.Arg(0))
   145  	if err != nil {
   146  		return err
   147  	}
   148  	bucket := fmt.Sprintf("%v/%v", keyspace, shard)
   149  
   150  	bs, err := backupstorage.GetBackupStorage()
   151  	if err != nil {
   152  		return err
   153  	}
   154  	defer bs.Close()
   155  	bhs, err := bs.ListBackups(ctx, bucket)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	for _, bh := range bhs {
   160  		wr.Logger().Printf("%v\n", bh.Name())
   161  	}
   162  	return nil
   163  }
   164  
   165  func commandRemoveBackup(ctx context.Context, wr *wrangler.Wrangler, subFlags *pflag.FlagSet, args []string) error {
   166  	if err := subFlags.Parse(args); err != nil {
   167  		return err
   168  	}
   169  	if subFlags.NArg() != 2 {
   170  		return fmt.Errorf("action RemoveBackup requires <keyspace/shard> <backup name>")
   171  	}
   172  
   173  	keyspace, shard, err := topoproto.ParseKeyspaceShard(subFlags.Arg(0))
   174  	if err != nil {
   175  		return err
   176  	}
   177  	name := subFlags.Arg(1)
   178  
   179  	_, err = wr.VtctldServer().RemoveBackup(ctx, &vtctldatapb.RemoveBackupRequest{
   180  		Keyspace: keyspace,
   181  		Shard:    shard,
   182  		Name:     name,
   183  	})
   184  	return err
   185  }
   186  
   187  // backupRestoreEventStreamLogger takes backup restore events from the
   188  // vtctldserver and emits them via logutil.LogEvent, preserving legacy behavior.
   189  type backupRestoreEventStreamLogger struct {
   190  	grpc.ServerStream
   191  	logger logutil.Logger
   192  	ctx    context.Context
   193  }
   194  
   195  func (b *backupRestoreEventStreamLogger) Context() context.Context { return b.ctx }
   196  
   197  func (b *backupRestoreEventStreamLogger) Send(resp *vtctldatapb.RestoreFromBackupResponse) error {
   198  	logutil.LogEvent(b.logger, resp.Event)
   199  	return nil
   200  }
   201  
   202  func commandRestoreFromBackup(ctx context.Context, wr *wrangler.Wrangler, subFlags *pflag.FlagSet, args []string) error {
   203  	backupTimestampStr := subFlags.String("backup_timestamp", "", "Use the backup taken at or before this timestamp rather than using the latest backup.")
   204  	restoreToPos := subFlags.String("restore_to_pos", "", "Run a point in time recovery that ends with the given position. This will attempt to use one full backup followed by zero or more incremental backups")
   205  	dryRun := subFlags.Bool("dry_run", false, "Only validate restore steps, do not actually restore data")
   206  	if err := subFlags.Parse(args); err != nil {
   207  		return err
   208  	}
   209  	if subFlags.NArg() != 1 {
   210  		return fmt.Errorf("the RestoreFromBackup command requires the <tablet alias> argument")
   211  	}
   212  
   213  	// Zero date will cause us to use the latest, which is the default
   214  	backupTime := time.Time{}
   215  
   216  	// Or if a backup timestamp was specified then we use the last backup taken at or before that time
   217  	if *backupTimestampStr != "" {
   218  		var err error
   219  		backupTime, err = time.Parse(mysqlctl.BackupTimestampFormat, *backupTimestampStr)
   220  		if err != nil {
   221  			return vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, fmt.Sprintf("unable to parse the backup timestamp value provided of '%s'", *backupTimestampStr))
   222  		}
   223  	}
   224  
   225  	tabletAlias, err := topoproto.ParseTabletAlias(subFlags.Arg(0))
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	req := &vtctldatapb.RestoreFromBackupRequest{
   231  		TabletAlias:  tabletAlias,
   232  		RestoreToPos: *restoreToPos,
   233  		DryRun:       *dryRun,
   234  	}
   235  
   236  	if !backupTime.IsZero() {
   237  		req.BackupTime = protoutil.TimeToProto(backupTime)
   238  	}
   239  
   240  	return wr.VtctldServer().RestoreFromBackup(req, &backupRestoreEventStreamLogger{logger: wr.Logger(), ctx: ctx})
   241  }