github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/repairworker.go (about)

     1  package sdk
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  
     9  	"github.com/0chain/gosdk/constants"
    10  	"github.com/0chain/gosdk/core/sys"
    11  	"github.com/0chain/gosdk/zboxcore/fileref"
    12  	l "github.com/0chain/gosdk/zboxcore/logger"
    13  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    14  	"go.uber.org/zap"
    15  )
    16  
    17  type RepairRequest struct {
    18  	listDir           *ListResult
    19  	isRepairCanceled  bool
    20  	localRootPath     string
    21  	statusCB          StatusCallback
    22  	completedCallback func()
    23  	filesRepaired     int
    24  	wg                *sync.WaitGroup
    25  	allocation        *Allocation
    26  }
    27  
    28  type RepairStatusCB struct {
    29  	wg       *sync.WaitGroup
    30  	success  bool
    31  	err      error
    32  	statusCB StatusCallback
    33  }
    34  
    35  var RepairBlocks = 100
    36  
    37  func (cb *RepairStatusCB) Started(allocationId, filePath string, op int, totalBytes int) {
    38  	cb.statusCB.Started(allocationId, filePath, op, totalBytes)
    39  }
    40  
    41  func (cb *RepairStatusCB) InProgress(allocationId, filePath string, op int, completedBytes int, data []byte) {
    42  	cb.statusCB.InProgress(allocationId, filePath, op, completedBytes, data)
    43  }
    44  
    45  func (cb *RepairStatusCB) RepairCompleted(filesRepaired int) {
    46  	cb.statusCB.RepairCompleted(filesRepaired)
    47  }
    48  
    49  func (cb *RepairStatusCB) Completed(allocationId, filePath string, filename string, mimetype string, size int, op int) {
    50  	cb.statusCB.Completed(allocationId, filePath, filename, mimetype, size, op)
    51  	cb.success = true
    52  	cb.wg.Done()
    53  }
    54  
    55  func (cb *RepairStatusCB) Error(allocationID string, filePath string, op int, err error) {
    56  	cb.statusCB.Error(allocationID, filePath, op, err)
    57  	cb.success = false
    58  	cb.err = err
    59  	cb.wg.Done()
    60  }
    61  
    62  func (r *RepairRequest) processRepair(ctx context.Context, a *Allocation) {
    63  	if r.completedCallback != nil {
    64  		defer r.completedCallback()
    65  	}
    66  
    67  	if r.checkForCancel(a) {
    68  		return
    69  	}
    70  	SetNumBlockDownloads(RepairBlocks)
    71  	currBatchSize := BatchSize
    72  	BatchSize = BatchSize / 2
    73  	defer func() {
    74  		BatchSize = currBatchSize
    75  	}()
    76  	if !singleClientMode {
    77  		SetSingleClietnMode(true)
    78  		defer SetSingleClietnMode(false)
    79  	}
    80  	r.iterateDir(a, r.listDir)
    81  	if r.statusCB != nil {
    82  		r.statusCB.RepairCompleted(r.filesRepaired)
    83  	}
    84  }
    85  
    86  // holds result of repair size
    87  type RepairSize struct {
    88  	// upload size in bytes
    89  	UploadSize uint64 `json:"upload_size"`
    90  	// download size in bytes
    91  	DownloadSize uint64 `json:"download_size"`
    92  }
    93  
    94  // gets size to repair for remote dir.
    95  func (r *RepairRequest) Size(ctx context.Context, dir *ListResult) (RepairSize, error) {
    96  	var rs RepairSize
    97  	var err error
    98  	switch dir.Type {
    99  	case fileref.DIRECTORY:
   100  		if len(dir.Children) == 0 {
   101  			// fetch dir
   102  			dir, err = r.allocation.ListDir(dir.Path, WithListRequestForRepair(true), WithListRequestPageLimit(-1))
   103  			if err != nil {
   104  				return rs, err
   105  			}
   106  		}
   107  		for _, subDir := range dir.Children {
   108  			subDirSz, err := r.Size(ctx, subDir)
   109  			if err != nil {
   110  				return rs, err
   111  			}
   112  			rs.UploadSize += subDirSz.UploadSize
   113  			rs.DownloadSize += subDirSz.DownloadSize
   114  		}
   115  	case fileref.FILE:
   116  		// this returns repair operations required
   117  		repairOps := r.repairFile(r.allocation, dir)
   118  		if repairOps == nil {
   119  			err = fmt.Errorf("fetch repairOps failed")
   120  			return rs, err
   121  		}
   122  		for _, repairOp := range repairOps {
   123  			if repairOp.OperationType == constants.FileOperationInsert {
   124  				rs.UploadSize += uint64(repairOp.Mask.CountOnes()) * uint64(getShardSize(repairOp.FileMeta.ActualSize, r.allocation.DataShards, repairOp.EncryptedKey != ""))
   125  				rs.DownloadSize += uint64(repairOp.FileMeta.ActualSize)
   126  			}
   127  		}
   128  	}
   129  	return rs, err
   130  }
   131  
   132  func (r *RepairRequest) iterateDir(a *Allocation, dir *ListResult) []OperationRequest {
   133  	ops := make([]OperationRequest, 0)
   134  	switch dir.Type {
   135  	case fileref.DIRECTORY:
   136  		if len(dir.Children) == 0 {
   137  			var err error
   138  			dir, err = a.ListDir(dir.Path, WithListRequestForRepair(true), WithListRequestPageLimit(-1))
   139  			if err != nil {
   140  				l.Logger.Error("Failed to get listDir for path ", zap.Any("path", dir.Path), zap.Error(err))
   141  				return nil
   142  			}
   143  		}
   144  		if len(dir.Children) == 0 {
   145  			if dir.deleteMask.CountOnes() > 0 {
   146  				l.Logger.Info("Deleting minority shards for the path :", zap.Any("path", dir.Path))
   147  				consensus := dir.deleteMask.CountOnes()
   148  				if consensus < a.DataShards {
   149  
   150  					err := a.deleteFile(dir.Path, 0, consensus, dir.deleteMask)
   151  					if err != nil {
   152  						l.Logger.Error("repair_file_failed", zap.Error(err))
   153  						if r.statusCB != nil {
   154  							r.statusCB.Error(a.ID, dir.Path, OpRepair, err)
   155  						}
   156  						return nil
   157  					}
   158  					r.filesRepaired++
   159  				} else if consensus < len(a.Blobbers) {
   160  					createMask := dir.deleteMask.Not().And(zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1))
   161  					err := a.createDir(dir.Path, 0, createMask.CountOnes(), createMask)
   162  					if err != nil {
   163  						l.Logger.Error("repair_file_failed", zap.Error(err))
   164  						if r.statusCB != nil {
   165  							r.statusCB.Error(a.ID, dir.Path, OpRepair, err)
   166  						}
   167  						return nil
   168  					}
   169  					r.filesRepaired++
   170  				}
   171  			}
   172  		}
   173  		for _, childDir := range dir.Children {
   174  			if r.checkForCancel(a) {
   175  				return nil
   176  			}
   177  			ops = append(ops, r.iterateDir(a, childDir)...)
   178  			if len(ops) >= RepairBatchSize/2 {
   179  				r.repairOperation(a, ops)
   180  				ops = nil
   181  			}
   182  		}
   183  		if len(ops) > 0 {
   184  			r.repairOperation(a, ops)
   185  			ops = nil
   186  		}
   187  	case fileref.FILE:
   188  		// this returns op object and mask
   189  		repairOps := r.repairFile(a, dir)
   190  		if repairOps != nil {
   191  			ops = append(ops, repairOps...)
   192  		}
   193  
   194  	default:
   195  		l.Logger.Info("Invalid directory type", zap.Any("type", dir.Type))
   196  	}
   197  	return ops
   198  }
   199  
   200  func (r *RepairRequest) repairFile(a *Allocation, file *ListResult) []OperationRequest {
   201  	ops := make([]OperationRequest, 0)
   202  	if r.checkForCancel(a) {
   203  		return nil
   204  	}
   205  	l.Logger.Info("Checking file for the path :", zap.Any("path", file.Path))
   206  	found, deleteMask, repairRequired, ref, err := a.RepairRequired(file.Path)
   207  	if err != nil {
   208  		l.Logger.Error("repair_required_failed", zap.Error(err))
   209  		return nil
   210  	}
   211  	if repairRequired {
   212  		l.Logger.Info("Repair required for the path :", zap.Any("path", file.Path))
   213  		if found.CountOnes() >= a.DataShards {
   214  			l.Logger.Info("Repair by upload", zap.Any("path", file.Path))
   215  			var wg sync.WaitGroup
   216  			statusCB := &RepairStatusCB{
   217  				wg:       &wg,
   218  				statusCB: r.statusCB,
   219  			}
   220  
   221  			if deleteMask.CountOnes() > 0 {
   222  				l.Logger.Info("Deleting minority shards for the path :", zap.Any("path", file.Path))
   223  				op := OperationRequest{
   224  					OperationType: constants.FileOperationDelete,
   225  					RemotePath:    file.Path,
   226  					Mask:          &deleteMask,
   227  				}
   228  				ops = append(ops, op)
   229  			}
   230  			wg.Add(1)
   231  			localPath := r.getLocalPath(file)
   232  			var op *OperationRequest
   233  			if !checkFileExists(localPath) {
   234  				if r.checkForCancel(a) {
   235  					return nil
   236  				}
   237  				memFile := &sys.MemChanFile{
   238  					Buffer:         make(chan []byte, 100),
   239  					ChunkWriteSize: int(a.GetChunkReadSize(ref.EncryptedKey != "")),
   240  				}
   241  				op = a.RepairFile(memFile, file.Path, statusCB, found, ref)
   242  				if op.FileMeta.ActualSize > 0 {
   243  					op.DownloadFile = true
   244  				}
   245  			} else {
   246  				f, err := sys.Files.Open(localPath)
   247  				if err != nil {
   248  					l.Logger.Error("repair_file_failed", zap.Error(err))
   249  					return nil
   250  				}
   251  				op = a.RepairFile(f, file.Path, statusCB, found, ref)
   252  			}
   253  			ops = append(ops, *op)
   254  			if r.checkForCancel(a) {
   255  				return nil
   256  			}
   257  		} else {
   258  			l.Logger.Info("Repair by delete", zap.Any("path", file.Path))
   259  			op := OperationRequest{
   260  				OperationType: constants.FileOperationDelete,
   261  				RemotePath:    file.Path,
   262  				Mask:          &found,
   263  			}
   264  			ops = append(ops, op)
   265  		}
   266  	} else if deleteMask.CountOnes() > 0 {
   267  		l.Logger.Info("Deleting minority shards for the path :", zap.Any("path", file.Path))
   268  		op := OperationRequest{
   269  			OperationType: constants.FileOperationDelete,
   270  			RemotePath:    file.Path,
   271  			Mask:          &deleteMask,
   272  		}
   273  		ops = append(ops, op)
   274  	}
   275  	return ops
   276  }
   277  
   278  func (r *RepairRequest) repairOperation(a *Allocation, ops []OperationRequest) {
   279  	err := a.DoMultiOperation(ops, WithRepair())
   280  	if err != nil {
   281  		l.Logger.Error("repair_file_failed", zap.Error(err))
   282  		status := r.statusCB != nil
   283  		for _, op := range ops {
   284  			if op.DownloadFile {
   285  				_ = a.CancelDownload(op.RemotePath)
   286  			}
   287  			if status {
   288  				r.statusCB.Error(a.ID, op.RemotePath, OpRepair, err)
   289  			}
   290  		}
   291  	} else {
   292  		r.filesRepaired += len(ops)
   293  	}
   294  	for _, op := range ops {
   295  		if op.FileReader != nil && !op.DownloadFile {
   296  			if f, ok := op.FileReader.(io.Closer); ok {
   297  				f.Close()
   298  			}
   299  		}
   300  	}
   301  }
   302  
   303  func (r *RepairRequest) getLocalPath(file *ListResult) string {
   304  	return r.localRootPath + file.Path
   305  }
   306  
   307  func checkFileExists(localPath string) bool {
   308  	if IsWasm {
   309  		return false
   310  	}
   311  	info, err := sys.Files.Stat(localPath)
   312  	if err != nil {
   313  		return false
   314  	}
   315  	return !info.IsDir()
   316  }
   317  
   318  func (r *RepairRequest) checkForCancel(a *Allocation) bool {
   319  	if r.isRepairCanceled {
   320  		l.Logger.Info("Repair Cancelled by the user")
   321  		if r.statusCB != nil {
   322  			r.statusCB.RepairCompleted(r.filesRepaired)
   323  		}
   324  		return true
   325  	}
   326  	return false
   327  }