github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/transferfiles/state/statemanager.go (about)

     1  package state
     2  
     3  import (
     4  	"path/filepath"
     5  	"time"
     6  
     7  	"github.com/jfrog/gofrog/datastructures"
     8  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/transferfiles/api"
     9  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
    10  	"github.com/jfrog/jfrog-cli-core/v2/utils/lock"
    11  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    12  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    13  	"github.com/jfrog/jfrog-client-go/utils/log"
    14  )
    15  
    16  // The interval in which to save the state and run transfer files to the file system.
    17  // Every change made will be held in memory till the saving time comes.
    18  const stateAndStatusSaveIntervalSecsDefault = 10
    19  
    20  var stateAndStatusSaveIntervalSecs = stateAndStatusSaveIntervalSecsDefault
    21  
    22  type ProgressState struct {
    23  	TotalSizeBytes       int64 `json:"total_size_bytes,omitempty"`
    24  	TransferredSizeBytes int64 `json:"transferred_size_bytes,omitempty"`
    25  	ProgressStateUnits
    26  }
    27  
    28  type ProgressStateUnits struct {
    29  	TotalUnits       int64 `json:"total_units,omitempty"`
    30  	TransferredUnits int64 `json:"transferred_units,omitempty"`
    31  }
    32  
    33  type TransferStateManager struct {
    34  	TransferState
    35  	TransferRunStatus
    36  	repoTransferSnapshot *RepoTransferSnapshot
    37  	// This function unlocks the state manager after the transfer-files command is finished
    38  	unlockStateManager func() error
    39  }
    40  
    41  func NewTransferStateManager(loadRunStatus bool) (*TransferStateManager, error) {
    42  	stateManager := TransferStateManager{}
    43  	if loadRunStatus {
    44  		transferRunStatus, _, err := loadTransferRunStatus()
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  		stateManager.TransferRunStatus = transferRunStatus
    49  	}
    50  	stateManager.TimeEstimationManager.stateManager = &stateManager
    51  	return &stateManager, nil
    52  }
    53  
    54  // Try to lock the transfer state manager.
    55  // If file-transfer is already running, return "Already locked" error.
    56  func (ts *TransferStateManager) TryLockTransferStateManager() error {
    57  	return ts.tryLockStateManager()
    58  }
    59  
    60  func (ts *TransferStateManager) UnlockTransferStateManager() error {
    61  	return ts.unlockStateManager()
    62  }
    63  
    64  func (ts *TransferStateManager) SetStartTimestamp(startTimestamp time.Time) {
    65  	ts.startTimestamp = startTimestamp
    66  }
    67  
    68  func (ts *TransferStateManager) GetStartTimestamp() time.Time {
    69  	return ts.startTimestamp
    70  }
    71  
    72  // Set the repository state.
    73  // repoKey        - Repository key
    74  // totalSizeBytes - Repository size in bytes
    75  // totalFiles     - Total files in the repository
    76  // buildInfoRepo  - True if build info repository
    77  // reset          - Delete the repository's previous transfer info
    78  func (ts *TransferStateManager) SetRepoState(repoKey string, totalSizeBytes, totalFiles int64, buildInfoRepo, reset bool) error {
    79  	var transferredFiles uint32 = 0
    80  	var transferredSizeBytes uint64 = 0
    81  	err := ts.Action(func(*TransferState) error {
    82  		transferState, repoTransferSnapshot, err := getTransferStateAndSnapshot(repoKey, reset)
    83  		if err != nil {
    84  			return err
    85  		}
    86  		transferState.CurrentRepo.Phase1Info.TotalSizeBytes = totalSizeBytes
    87  		transferState.CurrentRepo.Phase1Info.TotalUnits = totalFiles
    88  
    89  		if repoTransferSnapshot != nil && repoTransferSnapshot.loadedFromSnapshot {
    90  			transferredFiles, transferredSizeBytes, err = repoTransferSnapshot.snapshotManager.CalculateTransferredFilesAndSize()
    91  			if err != nil {
    92  				return err
    93  			}
    94  			log.Info("Calculated transferred files from previous run:", transferredFiles)
    95  			log.Info("Calculated transferred bytes from previous run:", transferredSizeBytes)
    96  			transferState.CurrentRepo.Phase1Info.TransferredUnits = int64(transferredFiles)
    97  			transferState.CurrentRepo.Phase1Info.TransferredSizeBytes = int64(transferredSizeBytes)
    98  		}
    99  
   100  		ts.TransferState = transferState
   101  		ts.repoTransferSnapshot = repoTransferSnapshot
   102  		return nil
   103  	})
   104  	if err != nil {
   105  		return err
   106  	}
   107  	return ts.action(func(transferRunStatus *TransferRunStatus) error {
   108  		transferRunStatus.CurrentRepoKey = repoKey
   109  		transferRunStatus.BuildInfoRepo = buildInfoRepo
   110  		transferRunStatus.VisitedFolders = 0
   111  
   112  		transferRunStatus.OverallTransfer.TransferredUnits += int64(transferredFiles)
   113  		transferRunStatus.OverallTransfer.TransferredSizeBytes += int64(transferredSizeBytes)
   114  		return nil
   115  	})
   116  }
   117  
   118  func (ts *TransferStateManager) SetRepoFullTransferStarted(startTime time.Time) error {
   119  	// We do not want to change the start time if it already exists, because it means we continue transferring from a snapshot.
   120  	// Some dirs may not be searched again (if done exploring or completed), so handling their diffs from the original time is required.
   121  	return ts.TransferState.Action(func(state *TransferState) error {
   122  		if state.CurrentRepo.FullTransfer.Started == "" {
   123  			state.CurrentRepo.FullTransfer.Started = ConvertTimeToRFC3339(startTime)
   124  		}
   125  		return nil
   126  	})
   127  }
   128  
   129  func (ts *TransferStateManager) SetRepoFullTransferCompleted() error {
   130  	return ts.TransferState.Action(func(state *TransferState) error {
   131  		state.CurrentRepo.FullTransfer.Ended = ConvertTimeToRFC3339(time.Now())
   132  		return nil
   133  	})
   134  }
   135  
   136  // Increasing Transferred Diff files (modified files) and SizeByBytes value in suitable repository progress state
   137  func (ts *TransferStateManager) IncTransferredSizeAndFilesPhase1(chunkTotalFiles, chunkTotalSizeInBytes int64) error {
   138  	err := ts.TransferState.Action(func(state *TransferState) error {
   139  		atomicallyAddInt64(&state.CurrentRepo.Phase1Info.TransferredSizeBytes, chunkTotalSizeInBytes)
   140  		atomicallyAddInt64(&state.CurrentRepo.Phase1Info.TransferredUnits, chunkTotalFiles)
   141  		return nil
   142  	})
   143  	if err != nil {
   144  		return err
   145  	}
   146  	return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   147  		atomicallyAddInt64(&transferRunStatus.OverallTransfer.TransferredSizeBytes, chunkTotalSizeInBytes)
   148  		atomicallyAddInt64(&transferRunStatus.OverallTransfer.TransferredUnits, chunkTotalFiles)
   149  
   150  		if transferRunStatus.BuildInfoRepo {
   151  			atomicallyAddInt64(&transferRunStatus.OverallBiFiles.TransferredUnits, chunkTotalFiles)
   152  		}
   153  		return nil
   154  	})
   155  }
   156  
   157  func (ts *TransferStateManager) IncTransferredSizeAndFilesPhase2(chunkTotalFiles, chunkTotalSizeInBytes int64) error {
   158  	return ts.TransferState.Action(func(state *TransferState) error {
   159  		atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TransferredSizeBytes, chunkTotalSizeInBytes)
   160  		atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TransferredUnits, chunkTotalFiles)
   161  		return nil
   162  	})
   163  }
   164  
   165  func (ts *TransferStateManager) IncTotalSizeAndFilesPhase2(filesNumber, totalSize int64) error {
   166  	return ts.TransferState.Action(func(state *TransferState) error {
   167  		atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TotalSizeBytes, totalSize)
   168  		atomicallyAddInt64(&state.CurrentRepo.Phase2Info.TotalUnits, filesNumber)
   169  		return nil
   170  	})
   171  }
   172  
   173  // Set relevant information of files and storage we need to transfer in phase3
   174  func (ts *TransferStateManager) SetTotalSizeAndFilesPhase3(filesNumber, totalSize int64) error {
   175  	return ts.TransferState.Action(func(state *TransferState) error {
   176  		state.CurrentRepo.Phase3Info.TransferredUnits = 0
   177  		state.CurrentRepo.Phase3Info.TransferredSizeBytes = 0
   178  		atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TotalSizeBytes, totalSize)
   179  		atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TotalUnits, filesNumber)
   180  		return nil
   181  	})
   182  }
   183  
   184  // Increase transferred storage and files in phase 3
   185  func (ts *TransferStateManager) IncTransferredSizeAndFilesPhase3(chunkTotalFiles, chunkTotalSizeInBytes int64) error {
   186  	return ts.TransferState.Action(func(state *TransferState) error {
   187  		atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TransferredSizeBytes, chunkTotalSizeInBytes)
   188  		atomicallyAddInt64(&state.CurrentRepo.Phase3Info.TransferredUnits, chunkTotalFiles)
   189  		return nil
   190  	})
   191  }
   192  
   193  // Returns pointers to TotalStorage, TotalFiles, TransferredFiles and TransferredStorage from progressState of a specific Repository.
   194  func (ts *TransferStateManager) GetStorageAndFilesRepoPointers(phase int) (totalFailedStorage, totalUploadedFailedStorage, totalFailedFiles, totalUploadedFailedFiles *int64, err error) {
   195  	err = ts.TransferState.Action(func(state *TransferState) error {
   196  		switch phase {
   197  		case api.Phase1:
   198  			totalFailedStorage = &ts.CurrentRepo.Phase1Info.TotalSizeBytes
   199  			totalUploadedFailedStorage = &ts.CurrentRepo.Phase1Info.TransferredSizeBytes
   200  			totalFailedFiles = &ts.CurrentRepo.Phase1Info.TotalUnits
   201  			totalUploadedFailedFiles = &ts.CurrentRepo.Phase1Info.TransferredUnits
   202  		case api.Phase2:
   203  			totalFailedStorage = &ts.CurrentRepo.Phase2Info.TotalSizeBytes
   204  			totalUploadedFailedStorage = &ts.CurrentRepo.Phase2Info.TransferredSizeBytes
   205  			totalFailedFiles = &ts.CurrentRepo.Phase2Info.TotalUnits
   206  			totalUploadedFailedFiles = &ts.CurrentRepo.Phase2Info.TransferredUnits
   207  		case api.Phase3:
   208  			totalFailedStorage = &ts.CurrentRepo.Phase3Info.TotalSizeBytes
   209  			totalUploadedFailedStorage = &ts.CurrentRepo.Phase3Info.TransferredSizeBytes
   210  			totalFailedFiles = &ts.CurrentRepo.Phase3Info.TotalUnits
   211  			totalUploadedFailedFiles = &ts.CurrentRepo.Phase3Info.TransferredUnits
   212  		}
   213  		return nil
   214  	})
   215  	return
   216  }
   217  
   218  // Adds new diff details to the repo's diff array in state.
   219  // Marks files handling as started, and sets the handling range.
   220  func (ts *TransferStateManager) AddNewDiffToState(startTime time.Time) error {
   221  	return ts.TransferState.Action(func(state *TransferState) error {
   222  
   223  		newDiff := DiffDetails{}
   224  
   225  		// Set Files Diff Handling started.
   226  		newDiff.FilesDiffRunTime.Started = ConvertTimeToRFC3339(startTime)
   227  
   228  		// Determines the range on which files diffs should be handled.
   229  		// If the repo previously completed files diff phases, we will continue handling diffs from where the last phase finished handling.
   230  		// Otherwise, we will start handling diffs from the start time of the full transfer.
   231  		for i := len(state.CurrentRepo.Diffs) - 1; i >= 0; i-- {
   232  			if state.CurrentRepo.Diffs[i].Completed {
   233  				newDiff.HandledRange.Started = state.CurrentRepo.Diffs[i].HandledRange.Ended
   234  				break
   235  			}
   236  		}
   237  		if newDiff.HandledRange.Started == "" {
   238  			newDiff.HandledRange.Started = state.CurrentRepo.FullTransfer.Started
   239  		}
   240  		newDiff.HandledRange.Ended = ConvertTimeToRFC3339(startTime)
   241  		state.CurrentRepo.Diffs = append(state.CurrentRepo.Diffs, newDiff)
   242  		return nil
   243  	})
   244  }
   245  
   246  func (ts *TransferStateManager) SetFilesDiffHandlingCompleted() error {
   247  	return ts.TransferState.Action(func(state *TransferState) error {
   248  		state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].FilesDiffRunTime.Ended = ConvertTimeToRFC3339(time.Now())
   249  		state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].Completed = true
   250  		return nil
   251  	})
   252  }
   253  
   254  func (ts *TransferStateManager) GetReposTransferredSizeBytes(repoKeys ...string) (transferredSizeBytes int64, err error) {
   255  	reposSet := datastructures.MakeSet[string]()
   256  	for _, repoKey := range repoKeys {
   257  		if reposSet.Exists(repoKey) {
   258  			continue
   259  		}
   260  		reposSet.Add(repoKey)
   261  		transferState, exists, err := LoadTransferState(repoKey, false)
   262  		if err != nil {
   263  			return transferredSizeBytes, err
   264  		}
   265  		if !exists {
   266  			continue
   267  		}
   268  		transferredSizeBytes += transferState.CurrentRepo.Phase1Info.TransferredSizeBytes
   269  	}
   270  	return
   271  }
   272  
   273  func (ts *TransferStateManager) GetTransferredSizeBytes() (transferredSizeBytes int64, err error) {
   274  	return transferredSizeBytes, ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   275  		transferredSizeBytes = transferRunStatus.OverallTransfer.TransferredSizeBytes
   276  		return nil
   277  	})
   278  }
   279  
   280  func (ts *TransferStateManager) GetDiffHandlingRange() (start, end time.Time, err error) {
   281  	return start, end, ts.TransferState.Action(func(state *TransferState) error {
   282  		var inErr error
   283  		start, inErr = ConvertRFC3339ToTime(state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].HandledRange.Started)
   284  		if inErr != nil {
   285  			return inErr
   286  		}
   287  		end, inErr = ConvertRFC3339ToTime(state.CurrentRepo.Diffs[len(state.CurrentRepo.Diffs)-1].HandledRange.Ended)
   288  		return inErr
   289  	})
   290  }
   291  
   292  func (ts *TransferStateManager) IncVisitedFolders() error {
   293  	return ts.action(func(transferRunStatus *TransferRunStatus) error {
   294  		atomicallyAddUint64(&transferRunStatus.VisitedFolders, 1, true)
   295  		return nil
   296  	})
   297  }
   298  
   299  func (ts *TransferStateManager) ChangeDelayedFilesCountBy(count uint64, increase bool) error {
   300  	return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   301  		atomicallyAddUint64(&transferRunStatus.DelayedFiles, count, increase)
   302  		return nil
   303  	})
   304  }
   305  
   306  func (ts *TransferStateManager) ChangeTransferFailureCountBy(count uint64, increase bool) error {
   307  	return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   308  		atomicallyAddUint64(&transferRunStatus.TransferFailures, count, increase)
   309  		return nil
   310  	})
   311  }
   312  
   313  func (ts *TransferStateManager) IncRepositoriesTransferred() error {
   314  	return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   315  		transferRunStatus.TotalRepositories.TransferredUnits++
   316  		return nil
   317  	})
   318  }
   319  
   320  func (ts *TransferStateManager) SetRepoPhase(phaseId int) error {
   321  	return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   322  		transferRunStatus.CurrentRepoPhase = phaseId
   323  		return nil
   324  	})
   325  }
   326  
   327  func (ts *TransferStateManager) SetWorkingThreads(workingThreads int) error {
   328  	return ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   329  		transferRunStatus.WorkingThreads = workingThreads
   330  		return nil
   331  	})
   332  }
   333  
   334  func (ts *TransferStateManager) GetWorkingThreads() (workingThreads int, err error) {
   335  	return workingThreads, ts.TransferRunStatus.action(func(transferRunStatus *TransferRunStatus) error {
   336  		workingThreads = transferRunStatus.WorkingThreads
   337  		return nil
   338  	})
   339  }
   340  
   341  func (ts *TransferStateManager) SetStaleChunks(staleChunks []StaleChunks) error {
   342  	return ts.action(func(transferRunStatus *TransferRunStatus) error {
   343  		transferRunStatus.StaleChunks = staleChunks
   344  		return nil
   345  	})
   346  }
   347  
   348  func (ts *TransferStateManager) GetStaleChunks() (staleChunks []StaleChunks, err error) {
   349  	return staleChunks, ts.action(func(transferRunStatus *TransferRunStatus) error {
   350  		staleChunks = transferRunStatus.StaleChunks
   351  		return nil
   352  	})
   353  }
   354  
   355  func (ts *TransferStateManager) SaveStateAndSnapshots() error {
   356  	ts.TransferState.lastSaveTimestamp = time.Now()
   357  	if err := ts.persistTransferState(false); err != nil {
   358  		return err
   359  	}
   360  	// Save snapshots if needed.
   361  	if ts.repoTransferSnapshot == nil {
   362  		return nil
   363  	}
   364  	ts.repoTransferSnapshot.lastSaveTimestamp = time.Now()
   365  	if err := ts.repoTransferSnapshot.snapshotManager.PersistRepoSnapshot(); err != nil {
   366  		return err
   367  	}
   368  	return ts.persistTransferState(true)
   369  }
   370  
   371  type AlreadyLockedError struct{}
   372  
   373  func (m *AlreadyLockedError) Error() string {
   374  	return "Files transfer is already running"
   375  }
   376  
   377  // Lock the state manager. We use the File Lock to acquire two purposes:
   378  // 1. Make sure that only one transfer-files process is running
   379  // 2. Check whether there is an active transfer-file process when providing the --status flag. We also extract the start timestamp (See 'GetStartTimestamp').
   380  func (ts *TransferStateManager) tryLockStateManager() error {
   381  	lockDirPath, err := coreutils.GetJfrogTransferLockDir()
   382  	if err != nil {
   383  		return err
   384  	}
   385  	startTimestamp, err := lock.GetLastLockTimestamp(lockDirPath)
   386  	if err != nil {
   387  		return err
   388  	}
   389  	if startTimestamp != 0 {
   390  		return errorutils.CheckError(new(AlreadyLockedError))
   391  	}
   392  	unlockFunc, err := lock.CreateLock(lockDirPath)
   393  	if err != nil {
   394  		return err
   395  	}
   396  	ts.unlockStateManager = unlockFunc
   397  	return nil
   398  }
   399  
   400  func (ts *TransferStateManager) Running() (running bool, err error) {
   401  	lockDirPath, err := coreutils.GetJfrogTransferLockDir()
   402  	if err != nil {
   403  		return false, err
   404  	}
   405  	var startTimestamp int64
   406  	startTimestamp, err = lock.GetLastLockTimestamp(lockDirPath)
   407  	return err == nil && startTimestamp != 0, err
   408  }
   409  
   410  func (ts *TransferStateManager) InitStartTimestamp() (running bool, err error) {
   411  	if !ts.startTimestamp.IsZero() {
   412  		return true, nil
   413  	}
   414  	lockDirPath, err := coreutils.GetJfrogTransferLockDir()
   415  	if err != nil {
   416  		return false, err
   417  	}
   418  	var startTimestamp int64
   419  	startTimestamp, err = lock.GetLastLockTimestamp(lockDirPath)
   420  	if err != nil || startTimestamp == 0 {
   421  		return false, err
   422  	}
   423  	ts.startTimestamp = time.Unix(0, startTimestamp)
   424  	return true, nil
   425  }
   426  
   427  func (ts *TransferStateManager) GetRunningTimeString() (runningTime string) {
   428  	if ts.startTimestamp.IsZero() {
   429  		return ""
   430  	}
   431  	runningSecs := int64(time.Since(ts.startTimestamp).Seconds())
   432  	return SecondsToLiteralTime(runningSecs, "")
   433  }
   434  
   435  func UpdateChunkInState(stateManager *TransferStateManager, chunk *api.ChunkStatus) (err error) {
   436  	var chunkTotalSizeInBytes int64 = 0
   437  	var chunkTotalFiles int64 = 0
   438  	for _, file := range chunk.Files {
   439  		if file.Status != api.Fail && file.Name != "" {
   440  			// Count only successfully transferred files
   441  			chunkTotalSizeInBytes += file.SizeBytes
   442  			chunkTotalFiles++
   443  		}
   444  	}
   445  	switch stateManager.CurrentRepoPhase {
   446  	case api.Phase1:
   447  		err = stateManager.IncTransferredSizeAndFilesPhase1(chunkTotalFiles, chunkTotalSizeInBytes)
   448  	case api.Phase2:
   449  		err = stateManager.IncTransferredSizeAndFilesPhase2(chunkTotalFiles, chunkTotalSizeInBytes)
   450  	case api.Phase3:
   451  		err = stateManager.IncTransferredSizeAndFilesPhase3(chunkTotalFiles, chunkTotalSizeInBytes)
   452  	}
   453  	return err
   454  }
   455  
   456  func GetJfrogTransferRepoSnapshotDir(repoKey string) (string, error) {
   457  	return GetJfrogTransferRepoSubDir(repoKey, coreutils.JfrogTransferSnapshotDirName)
   458  }
   459  
   460  func GetRepoSnapshotFilePath(repoKey string) (string, error) {
   461  	snapshotDir, err := GetJfrogTransferRepoSnapshotDir(repoKey)
   462  	if err != nil {
   463  		return "", err
   464  	}
   465  	err = fileutils.CreateDirIfNotExist(snapshotDir)
   466  	if err != nil {
   467  		return "", err
   468  	}
   469  	return filepath.Join(snapshotDir, coreutils.JfrogTransferRepoSnapshotFileName), nil
   470  }