gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/dependencies.go (about)

     1  package modules
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/ioutil"
     7  	"net"
     8  	"os"
     9  	"sync"
    10  	"time"
    11  
    12  	"gitlab.com/NebulousLabs/fastrand"
    13  	"gitlab.com/SiaPrime/SiaPrime/build"
    14  	"gitlab.com/SiaPrime/SiaPrime/persist"
    15  )
    16  
    17  // ProdDependencies act as a global instance of the production dependencies to
    18  // avoid having to instantiate new dependencies every time we want to pass
    19  // production dependencies.
    20  var ProdDependencies = new(ProductionDependencies)
    21  
    22  // Dependencies defines dependencies used by all of Sia's modules. Custom
    23  // dependencies can be created to inject certain behavior during testing.
    24  type (
    25  	Dependencies interface {
    26  		// AtLeastOne will return a value that is at least one. In production,
    27  		// the value should always be one. This function is used to test the
    28  		// idempotency of actions, so during testing sometimes the value
    29  		// returned will be higher, causing an idempotent action to be
    30  		// committed multiple times. If the action is truly idempotent,
    31  		// committing it multiple times should not cause any problems or
    32  		// changes.
    33  		AtLeastOne() uint64
    34  
    35  		// CreateFile gives the host the ability to create files on the
    36  		// operating system.
    37  		CreateFile(string) (File, error)
    38  
    39  		// Destruct will clean up the dependencies, panicking if there are
    40  		// unclosed resources.
    41  		Destruct()
    42  
    43  		// DialTimeout tries to create a tcp connection to the specified
    44  		// address with a certain timeout.
    45  		DialTimeout(NetAddress, time.Duration) (net.Conn, error)
    46  
    47  		// Disrupt can be inserted in the code as a way to inject problems,
    48  		// such as a network call that take 10 minutes or a disk write that
    49  		// never completes. disrupt will return true if the disruption is
    50  		// forcibly triggered. In production, disrupt will always return false.
    51  		Disrupt(string) bool
    52  
    53  		// Listen gives the host the ability to receive incoming connections.
    54  		Listen(string, string) (net.Listener, error)
    55  
    56  		// LoadFile allows the host to load a persistence structure form disk.
    57  		LoadFile(persist.Metadata, interface{}, string) error
    58  
    59  		// LookupIP resolves a hostname to a number of IP addresses. If an IP
    60  		// address is provided as an argument it will just return that IP.
    61  		LookupIP(string) ([]net.IP, error)
    62  
    63  		// MkdirAll gives the host the ability to create chains of folders
    64  		// within the filesystem.
    65  		MkdirAll(string, os.FileMode) error
    66  
    67  		// NewLogger creates a logger that the host can use to log messages and
    68  		// write critical statements.
    69  		NewLogger(string) (*persist.Logger, error)
    70  
    71  		// OpenDatabase creates a database that the host can use to interact
    72  		// with large volumes of persistent data.
    73  		OpenDatabase(persist.Metadata, string) (*persist.BoltDatabase, error)
    74  
    75  		// Open opens a file readonly.
    76  		Open(string) (File, error)
    77  
    78  		// OpenFile opens a file with the specified mode.
    79  		OpenFile(string, int, os.FileMode) (File, error)
    80  
    81  		// Resolver returns a Resolver which can resolve hostnames to IPs.
    82  		Resolver() Resolver
    83  
    84  		// RandRead fills the input bytes with random data.
    85  		RandRead([]byte) (int, error)
    86  
    87  		// ReadFile reads a file in full from the filesystem.
    88  		ReadFile(string) ([]byte, error)
    89  
    90  		// RemoveFile removes a file from file filesystem.
    91  		RemoveFile(string) error
    92  
    93  		// RenameFile renames a file on disk to another name.
    94  		RenameFile(string, string) error
    95  
    96  		// SaveFileSync writes JSON encoded data to disk and syncs the file
    97  		// afterwards.
    98  		SaveFileSync(persist.Metadata, interface{}, string) error
    99  
   100  		// Sleep blocks the calling thread for at least the specified duration.
   101  		Sleep(time.Duration)
   102  
   103  		// Symlink creates a sym link between a source and a destination.
   104  		Symlink(s1, s2 string) error
   105  
   106  		// WriteFile writes data to the filesystem using the provided filename.
   107  		WriteFile(string, []byte, os.FileMode) error
   108  	}
   109  
   110  	// File implements all of the methods that can be called on an os.File.
   111  	File interface {
   112  		io.ReadWriteCloser
   113  		Name() string
   114  		ReadAt([]byte, int64) (int, error)
   115  		Seek(int64, int) (int64, error)
   116  		Sync() error
   117  		Truncate(int64) error
   118  		WriteAt([]byte, int64) (int, error)
   119  	}
   120  )
   121  
   122  type (
   123  	// ProductionDependencies are the dependencies used in a Release or Debug
   124  	// production build.
   125  	ProductionDependencies struct {
   126  		shouldInit bool
   127  		openFiles  map[string]int
   128  		mu         sync.Mutex
   129  	}
   130  
   131  	// ProductionFile is the implementation of the File interface that is used
   132  	// in a Release or Debug production build.
   133  	ProductionFile struct {
   134  		pd *ProductionDependencies
   135  		*os.File
   136  	}
   137  )
   138  
   139  // Close will close a file, checking whether the file handle is open somewhere
   140  // else before closing completely. This check is performed on Windows but not
   141  // Linux, therefore a mock is used to ensure that linux testing picks up
   142  // potential problems that would be seen on Windows.
   143  func (pf *ProductionFile) Close() error {
   144  	if !build.DEBUG {
   145  		return pf.File.Close()
   146  	}
   147  
   148  	pf.pd.mu.Lock()
   149  	if pf.pd.openFiles == nil {
   150  		pf.pd.openFiles = make(map[string]int)
   151  	}
   152  	v, exists := pf.pd.openFiles[pf.File.Name()]
   153  	if !exists {
   154  		panic("file not registered")
   155  	}
   156  	if v == 1 {
   157  		delete(pf.pd.openFiles, pf.File.Name())
   158  	} else if v > 1 {
   159  		pf.pd.openFiles[pf.File.Name()] = v - 1
   160  	} else {
   161  		panic("inconsistent state")
   162  	}
   163  	pf.pd.mu.Unlock()
   164  	return pf.File.Close()
   165  }
   166  
   167  // AtLeastOne will return a value that is equal to 1 if debugging is disabled.
   168  // If debugging is enabled, a higher value may be returned.
   169  func (*ProductionDependencies) AtLeastOne() uint64 {
   170  	if !build.DEBUG {
   171  		return 1
   172  	}
   173  
   174  	// Probabilistically return a number greater than one.
   175  	val := uint64(1)
   176  	for fastrand.Intn(2) != 0 {
   177  		val++
   178  	}
   179  	return val
   180  }
   181  
   182  // CreateFile gives the host the ability to create files on the operating
   183  // system.
   184  func (pd *ProductionDependencies) CreateFile(s string) (File, error) {
   185  	if !build.DEBUG {
   186  		return os.Create(s)
   187  	}
   188  
   189  	f, err := os.Create(s)
   190  	if err != nil {
   191  		return f, err
   192  	}
   193  
   194  	pd.mu.Lock()
   195  	if pd.openFiles == nil {
   196  		pd.openFiles = make(map[string]int)
   197  	}
   198  	v := pd.openFiles[s]
   199  	pd.openFiles[s] = v + 1
   200  	pd.mu.Unlock()
   201  	return &ProductionFile{
   202  		pd:   pd,
   203  		File: f,
   204  	}, nil
   205  }
   206  
   207  // Destruct checks that all resources have been cleaned up correctly.
   208  func (pd *ProductionDependencies) Destruct() {
   209  	if !build.DEBUG {
   210  		return
   211  	}
   212  
   213  	pd.mu.Lock()
   214  	l := len(pd.openFiles)
   215  	pd.mu.Unlock()
   216  	if l != 0 {
   217  		panic("unclosed resources - most likely file handles")
   218  	}
   219  }
   220  
   221  // DialTimeout creates a tcp connection to a certain address with the specified
   222  // timeout.
   223  func (*ProductionDependencies) DialTimeout(addr NetAddress, timeout time.Duration) (net.Conn, error) {
   224  	return net.DialTimeout("tcp", string(addr), timeout)
   225  }
   226  
   227  // Disrupt can be used to inject specific behavior into a module by overwriting
   228  // it using a custom dependency.
   229  func (*ProductionDependencies) Disrupt(string) bool {
   230  	return false
   231  }
   232  
   233  // Listen gives the host the ability to receive incoming connections.
   234  func (*ProductionDependencies) Listen(s1, s2 string) (net.Listener, error) {
   235  	return net.Listen(s1, s2)
   236  }
   237  
   238  // LoadFile loads JSON encoded data from a file.
   239  func (*ProductionDependencies) LoadFile(meta persist.Metadata, data interface{}, filename string) error {
   240  	return persist.LoadJSON(meta, data, filename)
   241  }
   242  
   243  // LookupIP resolves a hostname to a number of IP addresses. If an IP address
   244  // is provided as an argument it will just return that IP.
   245  func (*ProductionDependencies) LookupIP(host string) ([]net.IP, error) {
   246  	return net.LookupIP(host)
   247  }
   248  
   249  // SaveFileSync writes JSON encoded data to a file and syncs the file to disk
   250  // afterwards.
   251  func (*ProductionDependencies) SaveFileSync(meta persist.Metadata, data interface{}, filename string) error {
   252  	return persist.SaveJSON(meta, data, filename)
   253  }
   254  
   255  // MkdirAll gives the host the ability to create chains of folders within the
   256  // filesystem.
   257  func (*ProductionDependencies) MkdirAll(s string, fm os.FileMode) error {
   258  	return os.MkdirAll(s, fm)
   259  }
   260  
   261  // NewLogger creates a logger that the host can use to log messages and write
   262  // critical statements.
   263  func (*ProductionDependencies) NewLogger(s string) (*persist.Logger, error) {
   264  	return persist.NewFileLogger(s)
   265  }
   266  
   267  // OpenDatabase creates a database that the host can use to interact with large
   268  // volumes of persistent data.
   269  func (*ProductionDependencies) OpenDatabase(m persist.Metadata, s string) (*persist.BoltDatabase, error) {
   270  	return persist.OpenDatabase(m, s)
   271  }
   272  
   273  // Open opens a file readonly.
   274  func (pd *ProductionDependencies) Open(s string) (File, error) {
   275  	return pd.OpenFile(s, os.O_RDONLY, 0)
   276  }
   277  
   278  // OpenFile opens a file with the specified mode and permissions.
   279  func (pd *ProductionDependencies) OpenFile(s string, i int, fm os.FileMode) (File, error) {
   280  	if !build.DEBUG {
   281  		return os.OpenFile(s, i, fm)
   282  	}
   283  
   284  	f, err := os.OpenFile(s, i, fm)
   285  	if err != nil {
   286  		return f, err
   287  	}
   288  
   289  	pd.mu.Lock()
   290  	if pd.openFiles == nil {
   291  		pd.openFiles = make(map[string]int)
   292  	}
   293  	v := pd.openFiles[s]
   294  	pd.openFiles[s] = v + 1
   295  	pd.mu.Unlock()
   296  	return &ProductionFile{
   297  		pd:   pd,
   298  		File: f,
   299  	}, nil
   300  }
   301  
   302  // RandRead fills the input bytes with random data.
   303  func (*ProductionDependencies) RandRead(b []byte) (int, error) {
   304  	return fastrand.Reader.Read(b)
   305  }
   306  
   307  // ReadFile reads a file from the filesystem.
   308  func (*ProductionDependencies) ReadFile(s string) ([]byte, error) {
   309  	return ioutil.ReadFile(s)
   310  }
   311  
   312  // RemoveFile will remove a file from disk.
   313  func (pd *ProductionDependencies) RemoveFile(s string) error {
   314  	if !build.DEBUG {
   315  		return os.Remove(s)
   316  	}
   317  
   318  	pd.mu.Lock()
   319  	if pd.openFiles == nil {
   320  		pd.openFiles = make(map[string]int)
   321  	}
   322  	v, exists := pd.openFiles[s]
   323  	pd.mu.Unlock()
   324  	if exists && v > 0 {
   325  		return errors.New("cannot remove the file, it's open somewhere else right now")
   326  	}
   327  	return os.Remove(s)
   328  }
   329  
   330  // RenameFile renames a file on disk.
   331  func (pd *ProductionDependencies) RenameFile(s1 string, s2 string) error {
   332  	if !build.DEBUG {
   333  		return os.Rename(s1, s2)
   334  	}
   335  
   336  	pd.mu.Lock()
   337  	if pd.openFiles == nil {
   338  		pd.openFiles = make(map[string]int)
   339  	}
   340  	v1, exists1 := pd.openFiles[s1]
   341  	v2, exists2 := pd.openFiles[s2]
   342  	pd.mu.Unlock()
   343  	if exists1 && v1 > 0 {
   344  		return errors.New("cannot remove the file, it's open somewhere else right now")
   345  	}
   346  	if exists2 && v2 > 0 {
   347  		return errors.New("cannot remove the file, it's open somewhere else right now")
   348  	}
   349  	return os.Rename(s1, s2)
   350  }
   351  
   352  // Sleep blocks the calling thread for a certain duration.
   353  func (*ProductionDependencies) Sleep(d time.Duration) {
   354  	time.Sleep(d)
   355  }
   356  
   357  // Symlink creates a symlink between a source and a destination file.
   358  func (*ProductionDependencies) Symlink(s1, s2 string) error {
   359  	return os.Symlink(s1, s2)
   360  }
   361  
   362  // WriteFile writes a file to the filesystem.
   363  func (*ProductionDependencies) WriteFile(s string, b []byte, fm os.FileMode) error {
   364  	return ioutil.WriteFile(s, b, fm)
   365  }
   366  
   367  // Resolver is an interface that allows resolving a hostname into IP
   368  // addresses.
   369  type Resolver interface {
   370  	LookupIP(string) ([]net.IP, error)
   371  }
   372  
   373  // ProductionResolver is the hostname resolver used in production builds.
   374  type ProductionResolver struct{}
   375  
   376  // LookupIP is a passthrough function to net.LookupIP. In testing builds it
   377  // returns a random IP.
   378  func (ProductionResolver) LookupIP(host string) ([]net.IP, error) {
   379  	if build.Release == "testing" {
   380  		rawIP := make([]byte, 16)
   381  		fastrand.Read(rawIP)
   382  		return []net.IP{net.IP(rawIP)}, nil
   383  	}
   384  	return net.LookupIP(host)
   385  }
   386  
   387  // Resolver returns the ProductionResolver.
   388  func (*ProductionDependencies) Resolver() Resolver {
   389  	return ProductionResolver{}
   390  }