gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/persist.go (about)

     1  package renter
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	"gitlab.com/NebulousLabs/errors"
    10  	"gitlab.com/NebulousLabs/writeaheadlog"
    11  
    12  	"gitlab.com/SkynetLabs/skyd/build"
    13  	"gitlab.com/SkynetLabs/skyd/skymodules"
    14  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem"
    15  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siafile"
    16  	"go.sia.tech/siad/persist"
    17  	"go.sia.tech/siad/types"
    18  )
    19  
    20  // PersistedStats contains the information about the renter's stats which is
    21  // persisted to disk.
    22  type PersistedStats struct {
    23  	RegistryReadStats     skymodules.PersistedDistributionTracker `json:"registryreadstats"`
    24  	RegistryWriteStats    skymodules.PersistedDistributionTracker `json:"registrywritestats"`
    25  	BaseSectorUploadStats skymodules.PersistedDistributionTracker `json:"basesectoruploadstats"`
    26  	ChunkUploadStats      skymodules.PersistedDistributionTracker `json:"chunkuploadstats"`
    27  	StreamBufferStats     skymodules.PersistedDistributionTracker `json:"streambufferstats"`
    28  }
    29  
    30  const (
    31  	logFile       = skymodules.RenterDir + ".log"
    32  	repairLogFile = "repair.log"
    33  	// PersistFilename is the filename to be used when persisting renter
    34  	// information to a JSON file
    35  	PersistFilename = "renter.json"
    36  	// SiaDirMetadata is the name of the metadata file for the sia directory
    37  	SiaDirMetadata = ".siadir"
    38  	// StatsFilename is the name of the file persisting the stats of the
    39  	// renter.
    40  	StatsFilename = "stats.json"
    41  	// walFile is the filename of the renter's writeaheadlog's file.
    42  	walFile = skymodules.RenterDir + ".wal"
    43  )
    44  
    45  var (
    46  	// ErrBadFile is an error when a file does not qualify as .sia file
    47  	ErrBadFile = errors.New("not a .sia file")
    48  	// ErrIncompatible is an error when file is not compatible with current
    49  	// version
    50  	ErrIncompatible = errors.New("file is not compatible with current version")
    51  	// ErrNoNicknames is an error when no nickname is given
    52  	ErrNoNicknames = errors.New("at least one nickname must be supplied")
    53  	// ErrNonShareSuffix is an error when the suffix of a file does not match
    54  	// the defined share extension
    55  	ErrNonShareSuffix = errors.New("suffix of file must be " + skymodules.SiaFileExtension)
    56  
    57  	settingsMetadata = persist.Metadata{
    58  		Header:  "Renter Persistence",
    59  		Version: persistVersion,
    60  	}
    61  
    62  	shareHeader  = [15]byte{'S', 'i', 'a', ' ', 'S', 'h', 'a', 'r', 'e', 'd', ' ', 'F', 'i', 'l', 'e'}
    63  	shareVersion = "0.4"
    64  
    65  	// statsPersistInterval defines the interval the renter uses for
    66  	// persisting its stats.
    67  	statsPersistInterval = build.Select(build.Var{
    68  		Dev:      time.Minute,
    69  		Standard: 30 * time.Minute,
    70  		Testing:  2 * time.Second,
    71  	}).(time.Duration)
    72  
    73  	// statsMetadata is the metadata used when persisting the renter stats.
    74  	statsMetadata = persist.Metadata{
    75  		Header:  "Stats",
    76  		Version: "1.5.7",
    77  	}
    78  
    79  	// Persist Version Numbers
    80  	persistVersion040 = "0.4"
    81  	persistVersion133 = "1.3.3"
    82  	persistVersion140 = "1.4.0"
    83  	persistVersion142 = "1.4.2"
    84  )
    85  
    86  type (
    87  	// persist contains all of the persistent renter data.
    88  	persistence struct {
    89  		MaxDownloadSpeed int64
    90  		MaxUploadSpeed   int64
    91  		UploadedBackups  []skymodules.UploadedBackup
    92  		SyncedContracts  []types.FileContractID
    93  	}
    94  )
    95  
    96  // saveSync stores the current renter data to disk and then syncs to disk.
    97  func (r *Renter) saveSync() error {
    98  	return persist.SaveJSON(settingsMetadata, r.persist, filepath.Join(r.persistDir, PersistFilename))
    99  }
   100  
   101  // threadedStatsPersister periodically persists the renter's collected stats.
   102  func (r *Renter) threadedStatsPersister() {
   103  	if err := r.tg.Add(); err != nil {
   104  		return
   105  	}
   106  	defer r.tg.Done()
   107  
   108  	ticker := time.NewTicker(statsPersistInterval)
   109  	for {
   110  		statsPath := filepath.Join(r.persistDir, StatsFilename)
   111  		err := persist.SaveJSON(statsMetadata, PersistedStats{
   112  			RegistryReadStats:     r.staticRegistryReadStats.Persist(),
   113  			RegistryWriteStats:    r.staticRegWriteStats.Persist(),
   114  			BaseSectorUploadStats: r.staticBaseSectorUploadStats.Persist(),
   115  			ChunkUploadStats:      r.staticChunkUploadStats.Persist(),
   116  			StreamBufferStats:     r.staticStreamBufferStats.Persist(),
   117  		}, statsPath)
   118  		if err != nil {
   119  			r.staticLog.Print("Failed to persist stats object:", err)
   120  		}
   121  
   122  		// Sleep
   123  		select {
   124  		case <-r.tg.StopCtx().Done():
   125  			return // shutdown
   126  		case <-ticker.C:
   127  		}
   128  	}
   129  }
   130  
   131  // managedLoadSettings fetches the saved renter data from disk.
   132  func (r *Renter) managedLoadSettings() error {
   133  	r.persist = persistence{}
   134  	err := persist.LoadJSON(settingsMetadata, &r.persist, filepath.Join(r.persistDir, PersistFilename))
   135  	if os.IsNotExist(err) {
   136  		// No persistence yet, set the defaults and continue.
   137  		r.persist.MaxDownloadSpeed = DefaultMaxDownloadSpeed
   138  		r.persist.MaxUploadSpeed = DefaultMaxUploadSpeed
   139  		id := r.mu.Lock()
   140  		err = r.saveSync()
   141  		r.mu.Unlock(id)
   142  		if err != nil {
   143  			return err
   144  		}
   145  	} else if errors.Contains(err, persist.ErrBadVersion) {
   146  		// Outdated version, try the 040 to 133 upgrade.
   147  		err = convertPersistVersionFrom040To133(filepath.Join(r.persistDir, PersistFilename))
   148  		if err != nil {
   149  			r.staticLog.Println("WARNING: 040 to 133 renter upgrade failed, trying 133 to 140 next", err)
   150  		}
   151  		// Then upgrade from 133 to 140.
   152  		oldContracts := r.staticHostContractor.OldContracts()
   153  		err = r.convertPersistVersionFrom133To140(filepath.Join(r.persistDir, PersistFilename), oldContracts)
   154  		if err != nil {
   155  			r.staticLog.Println("WARNING: 133 to 140 renter upgrade failed", err)
   156  		}
   157  		// Then upgrade from 140 to 142.
   158  		err = r.convertPersistVersionFrom140To142(filepath.Join(r.persistDir, PersistFilename))
   159  		if err != nil {
   160  			r.staticLog.Println("WARNING: 140 to 142 renter upgrade failed", err)
   161  			// Nothing left to try.
   162  			return err
   163  		}
   164  		r.staticLog.Println("Renter upgrade successful")
   165  		// Re-load the settings now that the file has been upgraded.
   166  		return r.managedLoadSettings()
   167  	} else if err != nil {
   168  		return err
   169  	}
   170  
   171  	// Set the bandwidth limits on the contractor, which was already initialized
   172  	// without bandwidth limits.
   173  	return r.staticSetBandwidthLimits(r.persist.MaxDownloadSpeed, r.persist.MaxUploadSpeed)
   174  }
   175  
   176  // managedInitPersist handles all of the persistence initialization, such as creating
   177  // the persistence directory and starting the logger.
   178  func (r *Renter) managedInitPersist() error {
   179  	// Create the persist and filesystem directories if they do not yet exist.
   180  	//
   181  	// Note: the os package needs to be used here instead of the renter's
   182  	// CreateDir method because the staticDirSet has not been initialized yet.
   183  	// The directory is needed before the staticDirSet can be initialized
   184  	// because the wal needs the directory to be created and the staticDirSet
   185  	// needs the wal.
   186  	fsRoot := filepath.Join(r.persistDir, skymodules.FileSystemRoot)
   187  	err := os.MkdirAll(fsRoot, skymodules.DefaultDirPerm)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	// Initialize the writeaheadlog.
   193  	options := writeaheadlog.Options{
   194  		StaticLog: r.staticLog.Logger,
   195  		Path:      filepath.Join(r.persistDir, walFile),
   196  	}
   197  	txns, wal, err := writeaheadlog.NewWithOptions(options)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	if err := r.tg.AfterStop(wal.Close); err != nil {
   202  		return err
   203  	}
   204  
   205  	// Apply unapplied wal txns before loading the persistence structure to
   206  	// avoid loading potentially corrupted files.
   207  	if len(txns) > 0 {
   208  		r.staticLog.Println("Wal initialized", len(txns), "transactions to apply")
   209  	}
   210  	for _, txn := range txns {
   211  		applyTxn := true
   212  		r.staticLog.Println("applying transaction with", len(txn.Updates), "updates")
   213  		for _, update := range txn.Updates {
   214  			if siafile.IsSiaFileUpdate(update) {
   215  				r.staticLog.Println("Applying a siafile update:", update.Name)
   216  				if err := siafile.ApplyUpdates(update); err != nil {
   217  					return errors.AddContext(err, "failed to apply SiaFile update")
   218  				}
   219  			} else {
   220  				r.staticLog.Println("wal update not applied, marking transaction as not applied")
   221  				applyTxn = false
   222  			}
   223  		}
   224  		if applyTxn {
   225  			if err := txn.SignalUpdatesApplied(); err != nil {
   226  				return err
   227  			}
   228  		}
   229  	}
   230  
   231  	// Create the filesystem.
   232  	fs, err := filesystem.New(fsRoot, r.staticLog, wal)
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	// Initialize the wal, staticFileSet and the staticDirSet. With the
   238  	// staticDirSet finish the initialization of the files directory
   239  	r.staticWAL = wal
   240  	r.staticFileSystem = fs
   241  
   242  	// Load the prior persistence structures.
   243  	if err := r.managedLoadSettings(); err != nil {
   244  		return errors.AddContext(err, "failed to load renter's persistence structrue")
   245  	}
   246  
   247  	// Load the stats.
   248  	if err := r.managedInitStats(); err != nil {
   249  		return errors.AddContext(err, "failed to initialize the renter's distribution trackers")
   250  	}
   251  
   252  	// Create the essential dirs in the filesystem.
   253  	err = fs.NewSiaDir(skymodules.HomeFolder, skymodules.DefaultDirPerm)
   254  	if err != nil && !errors.Contains(err, filesystem.ErrExists) {
   255  		return err
   256  	}
   257  	err = fs.NewSiaDir(skymodules.UserFolder, skymodules.DefaultDirPerm)
   258  	if err != nil && !errors.Contains(err, filesystem.ErrExists) {
   259  		return err
   260  	}
   261  	err = fs.NewSiaDir(skymodules.BackupFolder, skymodules.DefaultDirPerm)
   262  	if err != nil && !errors.Contains(err, filesystem.ErrExists) {
   263  		return err
   264  	}
   265  	err = fs.NewSiaDir(skymodules.SkynetFolder, skymodules.DefaultDirPerm)
   266  	if err != nil && !errors.Contains(err, filesystem.ErrExists) {
   267  		return err
   268  	}
   269  	return nil
   270  }
   271  
   272  // managedInitStats initializes the distribution trackers of the renter.
   273  func (r *Renter) managedInitStats() error {
   274  	// Init the trackers.
   275  	r.staticRegistryReadStats = skymodules.NewDistributionTrackerStandard()
   276  	r.staticRegWriteStats = skymodules.NewDistributionTrackerStandard()
   277  	r.staticBaseSectorUploadStats = skymodules.NewDistributionTrackerStandard()
   278  	r.staticChunkUploadStats = skymodules.NewDistributionTrackerStandard()
   279  	r.staticStreamBufferStats = skymodules.NewDistributionTrackerStandard()
   280  
   281  	// Load the existing stats.
   282  	statsPath := filepath.Join(r.persistDir, StatsFilename)
   283  	var stats PersistedStats
   284  	err := persist.LoadJSON(statsMetadata, &stats, statsPath)
   285  	if os.IsNotExist(err) {
   286  		// No persistence yet. Seed the trackers.
   287  		r.staticRegistryReadStats.AddDataPoint(readRegistryStatsSeed) // Seed the stats so that startup doesn't say 0.
   288  		r.staticRegWriteStats.AddDataPoint(5 * time.Second)           // Seed the stats so that startup doesn't say 0.
   289  		r.staticBaseSectorUploadStats.AddDataPoint(15 * time.Second)  // Seed the stats so that startup doesn't say 0.
   290  		r.staticChunkUploadStats.AddDataPoint(15 * time.Second)       // Seed the stats so that startup doesn't say 0.
   291  		r.staticStreamBufferStats.AddDataPoint(5 * time.Second)       // Seed the stats so that startup doesn't say 0.
   292  		return nil
   293  	} else if err != nil {
   294  		if build.Release == "testing" {
   295  			build.Critical(err)
   296  		}
   297  		fmt.Println("WARN: reset stats after failing to load them", err)
   298  		return nil // ignore and overwrite
   299  	}
   300  
   301  	// Found stats. Seed with existing values.
   302  	err1 := r.staticRegistryReadStats.Load(stats.RegistryReadStats)
   303  	err2 := r.staticRegWriteStats.Load(stats.RegistryWriteStats)
   304  	err3 := r.staticBaseSectorUploadStats.Load(stats.BaseSectorUploadStats)
   305  	err4 := r.staticChunkUploadStats.Load(stats.ChunkUploadStats)
   306  	err5 := r.staticStreamBufferStats.Load(stats.StreamBufferStats)
   307  	if err := errors.Compose(err1, err2, err3, err4, err5); err != nil {
   308  		if build.Release == "testing" {
   309  			build.Critical(err)
   310  		}
   311  		fmt.Println("WARN: failed to load one or more distribution trackers")
   312  		return nil // ignore and overwrite
   313  	}
   314  	return nil
   315  }