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 }