github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/interlock/brie.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package interlock 15 16 import ( 17 "context" 18 "net/url" 19 "strings" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 fidel "github.com/einsteindb/fidel/client" 25 "github.com/whtcorpsinc/BerolinaSQL/allegrosql" 26 "github.com/whtcorpsinc/BerolinaSQL/ast" 27 "github.com/whtcorpsinc/BerolinaSQL/perceptron" 28 "github.com/whtcorpsinc/BerolinaSQL/terror" 29 "github.com/whtcorpsinc/br/pkg/glue" 30 "github.com/whtcorpsinc/br/pkg/storage" 31 "github.com/whtcorpsinc/br/pkg/task" 32 "github.com/whtcorpsinc/errors" 33 filter "github.com/whtcorpsinc/milevadb-tools/pkg/causet-filter" 34 35 "github.com/whtcorpsinc/milevadb/config" 36 "github.com/whtcorpsinc/milevadb/dbs" 37 "github.com/whtcorpsinc/milevadb/ekv" 38 "github.com/whtcorpsinc/milevadb/memex" 39 "github.com/whtcorpsinc/milevadb/petri" 40 "github.com/whtcorpsinc/milevadb/soliton/chunk" 41 "github.com/whtcorpsinc/milevadb/soliton/sqlexec" 42 "github.com/whtcorpsinc/milevadb/stochastikctx" 43 "github.com/whtcorpsinc/milevadb/stochastikctx/stmtctx" 44 "github.com/whtcorpsinc/milevadb/stochastikctx/variable" 45 "github.com/whtcorpsinc/milevadb/types" 46 ) 47 48 // brieTaskProgress tracks a task's current progress. 49 type brieTaskProgress struct { 50 // current progress of the task. 51 // this field is atomically uFIDelated outside of the dagger below. 52 current int64 53 54 // dagger is the mutex protected the two fields below. 55 dagger sync.Mutex 56 // cmd is the name of the step the BRIE task is currently performing. 57 cmd string 58 // total is the total progress of the task. 59 // the percentage of completeness is `(100%) * current / total`. 60 total int64 61 } 62 63 // Inc implements glue.Progress 64 func (p *brieTaskProgress) Inc() { 65 atomic.AddInt64(&p.current, 1) 66 } 67 68 // Close implements glue.Progress 69 func (p *brieTaskProgress) Close() { 70 p.dagger.Lock() 71 atomic.StoreInt64(&p.current, p.total) 72 p.dagger.Unlock() 73 } 74 75 type brieTaskInfo struct { 76 queueTime types.Time 77 execTime types.Time 78 HoTT ast.BRIEHoTT 79 storage string 80 connID uint64 81 backupTS uint64 82 archiveSize uint64 83 } 84 85 type brieQueueItem struct { 86 info *brieTaskInfo 87 progress *brieTaskProgress 88 cancel func() 89 } 90 91 type brieQueue struct { 92 nextID uint64 93 tasks sync.Map 94 95 workerCh chan struct{} 96 } 97 98 // globalBRIEQueue is the BRIE execution queue. Only one BRIE task can be executed each time. 99 // TODO: perhaps copy the DBS Job queue so only one task can be executed in the whole cluster. 100 var globalBRIEQueue = &brieQueue{ 101 workerCh: make(chan struct{}, 1), 102 } 103 104 // registerTask registers a BRIE task in the queue. 105 func (bq *brieQueue) registerTask( 106 ctx context.Context, 107 info *brieTaskInfo, 108 ) (context.Context, uint64) { 109 taskCtx, taskCancel := context.WithCancel(ctx) 110 item := &brieQueueItem{ 111 info: info, 112 cancel: taskCancel, 113 progress: &brieTaskProgress{ 114 cmd: "Wait", 115 total: 1, 116 }, 117 } 118 119 taskID := atomic.AddUint64(&bq.nextID, 1) 120 bq.tasks.CausetStore(taskID, item) 121 122 return taskCtx, taskID 123 } 124 125 // acquireTask prepares to execute a BRIE task. Only one BRIE task can be 126 // executed at a time, and this function blocks until the task is ready. 127 // 128 // Returns an object to track the task's progress. 129 func (bq *brieQueue) acquireTask(taskCtx context.Context, taskID uint64) (*brieTaskProgress, error) { 130 // wait until we are at the front of the queue. 131 select { 132 case bq.workerCh <- struct{}{}: 133 if item, ok := bq.tasks.Load(taskID); ok { 134 return item.(*brieQueueItem).progress, nil 135 } 136 // cannot find task, perhaps it has been canceled. allow the next task to run. 137 bq.releaseTask() 138 return nil, errors.Errorf("backup/restore task %d is canceled", taskID) 139 case <-taskCtx.Done(): 140 return nil, taskCtx.Err() 141 } 142 } 143 144 func (bq *brieQueue) releaseTask() { 145 <-bq.workerCh 146 } 147 148 func (bq *brieQueue) cancelTask(taskID uint64) { 149 item, ok := bq.tasks.Load(taskID) 150 if !ok { 151 return 152 } 153 bq.tasks.Delete(taskID) 154 item.(*brieQueueItem).cancel() 155 } 156 157 func (b *interlockBuilder) parseTSString(ts string) (uint64, error) { 158 sc := &stmtctx.StatementContext{TimeZone: b.ctx.GetStochastikVars().Location()} 159 t, err := types.ParseTime(sc, ts, allegrosql.TypeTimestamp, types.MaxFsp) 160 if err != nil { 161 return 0, err 162 } 163 t1, err := t.GoTime(sc.TimeZone) 164 if err != nil { 165 return 0, err 166 } 167 return variable.GoTimeToTS(t1), nil 168 } 169 170 func (b *interlockBuilder) buildBRIE(s *ast.BRIEStmt, schemaReplicant *memex.Schema) InterlockingDirectorate { 171 e := &BRIEInterDirc{ 172 baseInterlockingDirectorate: newBaseInterlockingDirectorate(b.ctx, schemaReplicant, 0), 173 info: &brieTaskInfo{ 174 HoTT: s.HoTT, 175 }, 176 } 177 178 milevadbCfg := config.GetGlobalConfig() 179 if milevadbCfg.CausetStore != "einsteindb" { 180 b.err = errors.Errorf("%s requires einsteindb causetstore, not %s", s.HoTT, milevadbCfg.CausetStore) 181 return nil 182 } 183 184 cfg := task.Config{ 185 TLS: task.TLSConfig{ 186 CA: milevadbCfg.Security.ClusterSSLCA, 187 Cert: milevadbCfg.Security.ClusterSSLCert, 188 Key: milevadbCfg.Security.ClusterSSLKey, 189 }, 190 FIDel: strings.Split(milevadbCfg.Path, ","), 191 Concurrency: 4, 192 Checksum: true, 193 SendCreds: true, 194 LogProgress: true, 195 } 196 197 storageURL, err := url.Parse(s.CausetStorage) 198 if err != nil { 199 b.err = errors.Annotate(err, "invalid destination URL") 200 return nil 201 } 202 203 switch storageURL.Scheme { 204 case "s3": 205 storage.ExtractQueryParameters(storageURL, &cfg.S3) 206 case "gs", "gcs": 207 storage.ExtractQueryParameters(storageURL, &cfg.GCS) 208 default: 209 break 210 } 211 212 cfg.CausetStorage = storageURL.String() 213 e.info.storage = cfg.CausetStorage 214 215 for _, opt := range s.Options { 216 switch opt.Tp { 217 case ast.BRIEOptionRateLimit: 218 cfg.RateLimit = opt.UintValue 219 case ast.BRIEOptionConcurrency: 220 cfg.Concurrency = uint32(opt.UintValue) 221 case ast.BRIEOptionChecksum: 222 cfg.Checksum = opt.UintValue != 0 223 case ast.BRIEOptionSendCreds: 224 cfg.SendCreds = opt.UintValue != 0 225 } 226 } 227 228 switch { 229 case len(s.Blocks) != 0: 230 blocks := make([]filter.Block, 0, len(s.Blocks)) 231 for _, tbl := range s.Blocks { 232 blocks = append(blocks, filter.Block{Name: tbl.Name.O, Schema: tbl.Schema.O}) 233 } 234 cfg.BlockFilter = filter.NewBlocksFilter(blocks...) 235 case len(s.Schemas) != 0: 236 cfg.BlockFilter = filter.NewSchemasFilter(s.Schemas...) 237 default: 238 cfg.BlockFilter = filter.All() 239 } 240 241 if milevadbCfg.LowerCaseBlockNames != 0 { 242 cfg.BlockFilter = filter.CaseInsensitive(cfg.BlockFilter) 243 } 244 245 switch s.HoTT { 246 case ast.BRIEHoTTBackup: 247 e.backupCfg = &task.BackupConfig{Config: cfg} 248 249 for _, opt := range s.Options { 250 switch opt.Tp { 251 case ast.BRIEOptionLastBackupTS: 252 tso, err := b.parseTSString(opt.StrValue) 253 if err != nil { 254 b.err = err 255 return nil 256 } 257 e.backupCfg.LastBackupTS = tso 258 case ast.BRIEOptionLastBackupTSO: 259 e.backupCfg.LastBackupTS = opt.UintValue 260 case ast.BRIEOptionBackupTimeAgo: 261 e.backupCfg.TimeAgo = time.Duration(opt.UintValue) 262 case ast.BRIEOptionBackupTSO: 263 e.backupCfg.BackupTS = opt.UintValue 264 case ast.BRIEOptionBackupTS: 265 tso, err := b.parseTSString(opt.StrValue) 266 if err != nil { 267 b.err = err 268 return nil 269 } 270 e.backupCfg.BackupTS = tso 271 } 272 } 273 274 case ast.BRIEHoTTRestore: 275 e.restoreCfg = &task.RestoreConfig{Config: cfg} 276 for _, opt := range s.Options { 277 switch opt.Tp { 278 case ast.BRIEOptionOnline: 279 e.restoreCfg.Online = opt.UintValue != 0 280 } 281 } 282 283 default: 284 b.err = errors.Errorf("unsupported BRIE memex HoTT: %s", s.HoTT) 285 return nil 286 } 287 288 return e 289 } 290 291 // BRIEInterDirc represents an interlock for BRIE memexs (BACKUP, RESTORE, etc) 292 type BRIEInterDirc struct { 293 baseInterlockingDirectorate 294 295 backupCfg *task.BackupConfig 296 restoreCfg *task.RestoreConfig 297 info *brieTaskInfo 298 } 299 300 // Next implements the InterlockingDirectorate Next interface. 301 func (e *BRIEInterDirc) Next(ctx context.Context, req *chunk.Chunk) error { 302 req.Reset() 303 if e.info == nil { 304 return nil 305 } 306 307 bq := globalBRIEQueue 308 309 e.info.connID = e.ctx.GetStochastikVars().ConnectionID 310 e.info.queueTime = types.CurrentTime(allegrosql.TypeDatetime) 311 taskCtx, taskID := bq.registerTask(ctx, e.info) 312 defer bq.cancelTask(taskID) 313 314 // manually monitor the Killed status... 315 go func() { 316 ticker := time.NewTicker(3 * time.Second) 317 defer ticker.Stop() 318 for { 319 select { 320 case <-ticker.C: 321 if atomic.LoadUint32(&e.ctx.GetStochastikVars().Killed) == 1 { 322 bq.cancelTask(taskID) 323 return 324 } 325 case <-taskCtx.Done(): 326 return 327 } 328 } 329 }() 330 331 progress, err := bq.acquireTask(taskCtx, taskID) 332 if err != nil { 333 return err 334 } 335 defer bq.releaseTask() 336 337 e.info.execTime = types.CurrentTime(allegrosql.TypeDatetime) 338 glue := &milevadbGlueStochastik{se: e.ctx, progress: progress, info: e.info} 339 340 switch e.info.HoTT { 341 case ast.BRIEHoTTBackup: 342 err = handleBRIEError(task.RunBackup(taskCtx, glue, "Backup", e.backupCfg), ErrBRIEBackupFailed) 343 case ast.BRIEHoTTRestore: 344 err = handleBRIEError(task.RunRestore(taskCtx, glue, "Restore", e.restoreCfg), ErrBRIERestoreFailed) 345 default: 346 err = errors.Errorf("unsupported BRIE memex HoTT: %s", e.info.HoTT) 347 } 348 if err != nil { 349 return err 350 } 351 352 req.AppendString(0, e.info.storage) 353 req.AppendUint64(1, e.info.archiveSize) 354 req.AppendUint64(2, e.info.backupTS) 355 req.AppendTime(3, e.info.queueTime) 356 req.AppendTime(4, e.info.execTime) 357 e.info = nil 358 return nil 359 } 360 361 func handleBRIEError(err error, terror *terror.Error) error { 362 if err == nil { 363 return nil 364 } 365 return terror.GenWithStackByArgs(err) 366 } 367 368 func (e *ShowInterDirc) fetchShowBRIE(HoTT ast.BRIEHoTT) error { 369 globalBRIEQueue.tasks.Range(func(key, value interface{}) bool { 370 item := value.(*brieQueueItem) 371 if item.info.HoTT == HoTT { 372 item.progress.dagger.Lock() 373 defer item.progress.dagger.Unlock() 374 current := atomic.LoadInt64(&item.progress.current) 375 e.result.AppendString(0, item.info.storage) 376 e.result.AppendString(1, item.progress.cmd) 377 e.result.AppendFloat64(2, 100.0*float64(current)/float64(item.progress.total)) 378 e.result.AppendTime(3, item.info.queueTime) 379 e.result.AppendTime(4, item.info.execTime) 380 e.result.AppendNull(5) // FIXME: fill in finish time after keeping history. 381 e.result.AppendUint64(6, item.info.connID) 382 } 383 return true 384 }) 385 return nil 386 } 387 388 type milevadbGlueStochastik struct { 389 se stochastikctx.Context 390 progress *brieTaskProgress 391 info *brieTaskInfo 392 } 393 394 // BootstrapStochastik implements glue.Glue 395 func (gs *milevadbGlueStochastik) GetPetri(causetstore ekv.CausetStorage) (*petri.Petri, error) { 396 return petri.GetPetri(gs.se), nil 397 } 398 399 // CreateStochastik implements glue.Glue 400 func (gs *milevadbGlueStochastik) CreateStochastik(causetstore ekv.CausetStorage) (glue.Stochastik, error) { 401 return gs, nil 402 } 403 404 // InterDircute implements glue.Stochastik 405 func (gs *milevadbGlueStochastik) InterDircute(ctx context.Context, allegrosql string) error { 406 _, err := gs.se.(sqlexec.ALLEGROSQLInterlockingDirectorate).InterDircute(ctx, allegrosql) 407 return err 408 } 409 410 // CreateDatabase implements glue.Stochastik 411 func (gs *milevadbGlueStochastik) CreateDatabase(ctx context.Context, schemaReplicant *perceptron.DBInfo) error { 412 d := petri.GetPetri(gs.se).DBS() 413 schemaReplicant = schemaReplicant.Clone() 414 if len(schemaReplicant.Charset) == 0 { 415 schemaReplicant.Charset = allegrosql.DefaultCharset 416 } 417 return d.CreateSchemaWithInfo(gs.se, schemaReplicant, dbs.OnExistIgnore, true) 418 } 419 420 // CreateBlock implements glue.Stochastik 421 func (gs *milevadbGlueStochastik) CreateBlock(ctx context.Context, dbName perceptron.CIStr, causet *perceptron.BlockInfo) error { 422 d := petri.GetPetri(gs.se).DBS() 423 424 // Clone() does not clone partitions yet :( 425 causet = causet.Clone() 426 if causet.Partition != nil { 427 newPartition := *causet.Partition 428 newPartition.Definitions = append([]perceptron.PartitionDefinition{}, causet.Partition.Definitions...) 429 causet.Partition = &newPartition 430 } 431 432 return d.CreateBlockWithInfo(gs.se, dbName, causet, dbs.OnExistIgnore, true) 433 } 434 435 // Close implements glue.Stochastik 436 func (gs *milevadbGlueStochastik) Close() { 437 } 438 439 // Open implements glue.Glue 440 func (gs *milevadbGlueStochastik) Open(string, fidel.SecurityOption) (ekv.CausetStorage, error) { 441 return gs.se.GetStore(), nil 442 } 443 444 // OwnsStorage implements glue.Glue 445 func (gs *milevadbGlueStochastik) OwnsStorage() bool { 446 return false 447 } 448 449 // StartProgress implements glue.Glue 450 func (gs *milevadbGlueStochastik) StartProgress(ctx context.Context, cmdName string, total int64, redirectLog bool) glue.Progress { 451 gs.progress.dagger.Lock() 452 gs.progress.cmd = cmdName 453 gs.progress.total = total 454 atomic.StoreInt64(&gs.progress.current, 0) 455 gs.progress.dagger.Unlock() 456 return gs.progress 457 } 458 459 // Record implements glue.Glue 460 func (gs *milevadbGlueStochastik) Record(name string, value uint64) { 461 switch name { 462 case "BackupTS": 463 gs.info.backupTS = value 464 case "Size": 465 gs.info.archiveSize = value 466 } 467 }