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  }