github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/siatest/renter/hostdb_test.go (about)

     1  package renter
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"path/filepath"
     7  	"sort"
     8  	"testing"
     9  	"time"
    10  
    11  	"SiaPrime/build"
    12  	"SiaPrime/modules"
    13  	"SiaPrime/node"
    14  	"SiaPrime/node/api/client"
    15  	"SiaPrime/siatest"
    16  	"gitlab.com/NebulousLabs/errors"
    17  )
    18  
    19  // TestInitialScanComplete tests if the initialScanComplete field is set
    20  // correctly.
    21  func TestInitialScanComplete(t *testing.T) {
    22  	if testing.Short() {
    23  		t.SkipNow()
    24  	}
    25  
    26  	// Get a directory for testing.
    27  	testDir := renterTestDir(t.Name())
    28  
    29  	// Create a group. The renter should block the scanning thread using a
    30  	// dependency.
    31  	deps := &dependencyBlockScan{}
    32  	renterTemplate := node.Renter(filepath.Join(testDir, "renter"))
    33  	renterTemplate.SkipSetAllowance = true
    34  	renterTemplate.SkipHostDiscovery = true
    35  	renterTemplate.HostDBDeps = deps
    36  
    37  	tg, err := siatest.NewGroup(testDir, renterTemplate, node.Host(filepath.Join(testDir, "host")),
    38  		siatest.Miner(filepath.Join(testDir, "miner")))
    39  	if err != nil {
    40  		t.Fatal("Failed to create group: ", err)
    41  	}
    42  	defer func() {
    43  		deps.Scan()
    44  		if err := tg.Close(); err != nil {
    45  			t.Fatal(err)
    46  		}
    47  	}()
    48  
    49  	// The renter should have 1 offline host in its database and
    50  	// initialScanComplete should be false.
    51  	renter := tg.Renters()[0]
    52  	hdag, err := renter.HostDbAllGet()
    53  	if err != nil {
    54  		t.Fatal(err)
    55  	}
    56  	hdg, err := renter.HostDbGet()
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	if len(hdag.Hosts) != 1 {
    61  		t.Fatalf("HostDB should have 1 host but had %v", len(hdag.Hosts))
    62  	}
    63  	if hdag.Hosts[0].ScanHistory.Len() > 0 {
    64  		t.Fatalf("Host should have 0 scans but had %v", hdag.Hosts[0].ScanHistory.Len())
    65  	}
    66  	if hdg.InitialScanComplete {
    67  		t.Fatal("Initial scan is complete even though it shouldn't")
    68  	}
    69  
    70  	deps.Scan()
    71  	err = build.Retry(600, 100*time.Millisecond, func() error {
    72  		hdag, err := renter.HostDbAllGet()
    73  		if err != nil {
    74  			t.Fatal(err)
    75  		}
    76  		hdg, err := renter.HostDbGet()
    77  		if err != nil {
    78  			t.Fatal(err)
    79  		}
    80  		if !hdg.InitialScanComplete {
    81  			return fmt.Errorf("Initial scan is not complete even though it should be")
    82  		}
    83  		if len(hdag.Hosts) != 1 {
    84  			return fmt.Errorf("HostDB should have 1 host but had %v", len(hdag.Hosts))
    85  		}
    86  		if hdag.Hosts[0].ScanHistory.Len() == 0 {
    87  			return fmt.Errorf("Host should have >0 scans but had %v", hdag.Hosts[0].ScanHistory.Len())
    88  		}
    89  		return nil
    90  	})
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  }
    95  
    96  // TestPruneRedundantAddressRange checks if the contractor correctly cancels
    97  // contracts with redundant IP ranges.
    98  func TestPruneRedundantAddressRange(t *testing.T) {
    99  	if testing.Short() {
   100  		t.SkipNow()
   101  	}
   102  	t.Parallel()
   103  
   104  	// Get the testDir for this test.
   105  	testDir := renterTestDir(t.Name())
   106  
   107  	// Create a group with a few hosts.
   108  	groupParams := siatest.GroupParams{
   109  		Hosts:  3,
   110  		Miners: 1,
   111  	}
   112  	tg, err := siatest.NewGroupFromTemplate(testDir, groupParams)
   113  	if err != nil {
   114  		t.Fatal("Failed to create group: ", err)
   115  	}
   116  	defer func() {
   117  		if err := tg.Close(); err != nil {
   118  			t.Fatal(err)
   119  		}
   120  	}()
   121  	// Get the ports of the hosts.
   122  	allHosts := tg.Hosts()
   123  	hg1, err1 := allHosts[0].HostGet()
   124  	hg2, err2 := allHosts[1].HostGet()
   125  	hg3, err3 := allHosts[2].HostGet()
   126  	err = errors.Compose(err1, err2, err3)
   127  	if err != nil {
   128  		t.Fatal("Failed to get ports from at least one host", err)
   129  	}
   130  	host1Port := hg1.ExternalSettings.NetAddress.Port()
   131  	host2Port := hg2.ExternalSettings.NetAddress.Port()
   132  	host3Port := hg3.ExternalSettings.NetAddress.Port()
   133  
   134  	// Reannounce the hosts with custom hostnames which match the hostnames
   135  	// from the custom resolver method. We announce host1 first and host3 last
   136  	// to make sure host1 is the 'oldest' and host3 the 'youngest'.
   137  	err1 = allHosts[0].HostAnnounceAddrPost(modules.NetAddress(fmt.Sprintf("host1.com:%s", host1Port)))
   138  	err2 = tg.Miners()[0].MineBlock()
   139  	if err := errors.Compose(err1, err2); err != nil {
   140  		t.Fatal("failed to announce host1")
   141  	}
   142  	err1 = allHosts[1].HostAnnounceAddrPost(modules.NetAddress(fmt.Sprintf("host2.com:%s", host2Port)))
   143  	err2 = tg.Miners()[0].MineBlock()
   144  	if err := errors.Compose(err1, err2); err != nil {
   145  		t.Fatal("failed to announce host2")
   146  	}
   147  	err1 = allHosts[2].HostAnnounceAddrPost(modules.NetAddress(fmt.Sprintf("host3.com:%s", host3Port)))
   148  	err2 = tg.Miners()[0].MineBlock()
   149  	if err := errors.Compose(err1, err2); err != nil {
   150  		t.Fatal("failed to announce host3")
   151  	}
   152  
   153  	// Mine announcements.
   154  	if tg.Miners()[0].MineBlock() != nil {
   155  		t.Fatal(err)
   156  	}
   157  
   158  	// Add a renter with a custom resolver to the group.
   159  	renterTemplate := node.Renter(testDir + "/renter")
   160  	renterTemplate.HostDBDeps = siatest.NewDependencyCustomResolver(func(host string) ([]net.IP, error) {
   161  		switch host {
   162  		case "host1.com":
   163  			return []net.IP{{128, 0, 0, 1}}, nil
   164  		case "host2.com":
   165  			return []net.IP{{129, 0, 0, 1}}, nil
   166  		case "host3.com":
   167  			return []net.IP{{130, 0, 0, 1}}, nil
   168  		case "host4.com":
   169  			return []net.IP{{130, 0, 0, 2}}, nil
   170  		case "localhost":
   171  			return []net.IP{{127, 0, 0, 1}}, nil
   172  		default:
   173  			panic("shouldn't happen")
   174  		}
   175  	})
   176  	renterTemplate.ContractorDeps = renterTemplate.HostDBDeps
   177  
   178  	// Adding a custom RenewWindow will make a contract renewal during the test
   179  	// unlikely.
   180  	renterTemplate.Allowance = siatest.DefaultAllowance
   181  	renterTemplate.Allowance.Period *= 2
   182  	renterTemplate.Allowance.RenewWindow = 1
   183  	renterTemplate.Allowance.Hosts = uint64(len(allHosts))
   184  	_, err = tg.AddNodes(renterTemplate)
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  
   189  	// We expect the renter to have 3 active contracts.
   190  	renter := tg.Renters()[0]
   191  	contracts, err := renter.RenterContractsGet()
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  	if len(contracts.ActiveContracts) != len(allHosts) {
   196  		t.Fatalf("Expected %v active contracts but got %v", len(allHosts), len(contracts.ActiveContracts))
   197  	}
   198  
   199  	// Check that all the hosts have been scanned.
   200  	hdag, err := renter.HostDbAllGet()
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	for _, host := range hdag.Hosts {
   205  		if host.LastIPNetChange.IsZero() {
   206  			t.Fatal("host's LastIPNetChange is still zero", host.NetAddress.Host())
   207  		}
   208  		if len(host.IPNets) == 0 {
   209  			t.Fatal("host doesn't have any IPNets associated with it")
   210  		}
   211  	}
   212  
   213  	// Reannounce host1 as host4 which creates a violation with host3 and
   214  	// causes host4 to be the 'youngest'.
   215  	err = allHosts[0].HostAnnounceAddrPost(modules.NetAddress(fmt.Sprintf("host4.com:%s", host1Port)))
   216  	if err != nil {
   217  		t.Fatal("Failed to reannonce host 1")
   218  	}
   219  
   220  	// Mine the announcement.
   221  	if err := tg.Miners()[0].MineBlock(); err != nil {
   222  		t.Fatal("Failed to mine block", err)
   223  	}
   224  
   225  	// The 'youngest' host should be host4.com.
   226  	retry := 0
   227  	err = build.Retry(600, 100*time.Millisecond, func() error {
   228  		// Mine new blocks periodically.
   229  		if retry%60 == 0 {
   230  			if tg.Miners()[0].MineBlock() != nil {
   231  				return err
   232  			}
   233  		}
   234  		retry++
   235  		hdag, err := renter.HostDbAllGet()
   236  		if err != nil {
   237  			return (err)
   238  		}
   239  		sort.Slice(hdag.Hosts, func(i, j int) bool {
   240  			return hdag.Hosts[i].LastIPNetChange.Before(hdag.Hosts[j].LastIPNetChange)
   241  		})
   242  		if hdag.Hosts[len(hdag.Hosts)-1].NetAddress.Host() != "host4.com" {
   243  			return fmt.Errorf("Youngest host should be host4.com but was %v", hdag.Hosts[len(hdag.Hosts)-1].NetAddress.Host())
   244  		}
   245  		return nil
   246  	})
   247  	if err != nil {
   248  		renter.PrintDebugInfo(t, true, true, false)
   249  		t.Fatal(err)
   250  	}
   251  
   252  	// host4.com has the most recent change time. It should be canceled and
   253  	// show up as inactive.
   254  	retry = 0
   255  	err = build.Retry(600, 100*time.Millisecond, func() error {
   256  		// Mine new blocks periodically.
   257  		if retry%60 == 0 {
   258  			if tg.Miners()[0].MineBlock() != nil {
   259  				return err
   260  			}
   261  		}
   262  		retry++
   263  		// The renter should now have 2 active contracts and 1 inactive one.
   264  		// The inactive one should be host4 since it's the 'youngest'.
   265  		contracts, err = renter.RenterInactiveContractsGet()
   266  		if err != nil {
   267  			return err
   268  		}
   269  		if len(contracts.InactiveContracts) != 1 {
   270  			return fmt.Errorf("Expected 1 inactive contract but got %v", len(contracts.InactiveContracts))
   271  		}
   272  		if len(contracts.ActiveContracts) != len(allHosts)-1 {
   273  			return fmt.Errorf("Expected %v active contracts but got %v", len(allHosts)-1, len(contracts.ActiveContracts))
   274  		}
   275  		canceledHost := contracts.InactiveContracts[0].NetAddress.Host()
   276  		if canceledHost != "host4.com" {
   277  			return fmt.Errorf("Expected canceled contract to be host4.com but was %v", canceledHost)
   278  		}
   279  		return nil
   280  	})
   281  	if err != nil {
   282  		renter.PrintDebugInfo(t, true, true, false)
   283  		t.Fatal(err)
   284  	}
   285  }
   286  
   287  // TestSelectRandomCanceledHost makes sure that we can form a contract with a
   288  // hostB even if it has a conflict with a hostA iff hostA is canceled.
   289  func TestSelectRandomCanceledHost(t *testing.T) {
   290  	if testing.Short() {
   291  		t.SkipNow()
   292  	}
   293  	t.Parallel()
   294  
   295  	// Get the testDir for this test.
   296  	testDir := renterTestDir(t.Name())
   297  
   298  	// Create a group with a single host.
   299  	groupParams := siatest.GroupParams{
   300  		Hosts:  1,
   301  		Miners: 1,
   302  	}
   303  	tg, err := siatest.NewGroupFromTemplate(testDir, groupParams)
   304  	if err != nil {
   305  		t.Fatal("Failed to create group: ", err)
   306  	}
   307  	defer func() {
   308  		if err := tg.Close(); err != nil {
   309  			t.Fatal(err)
   310  		}
   311  	}()
   312  	// Get the host's port.
   313  	hg, err := tg.Hosts()[0].HostGet()
   314  	if err != nil {
   315  		t.Fatal("Failed to get port from host", err)
   316  	}
   317  	hostPort := hg.ExternalSettings.NetAddress.Port()
   318  
   319  	// Reannounce the hosts with custom hostnames which match the hostnames from the custom resolver method.
   320  	err = tg.Hosts()[0].HostAnnounceAddrPost(modules.NetAddress(fmt.Sprintf("host1.com:%s", hostPort)))
   321  	if err != nil {
   322  		t.Fatal("Failed to reannounce at least one of the hosts", err)
   323  	}
   324  
   325  	// Mine the announcements.
   326  	if err := tg.Miners()[0].MineBlock(); err != nil {
   327  		t.Fatal("Failed to mine block", err)
   328  	}
   329  
   330  	// Add a renter with a custom resolver to the group.
   331  	renterTemplate := node.Renter(testDir + "/renter")
   332  	renterTemplate.HostDBDeps = siatest.NewDependencyCustomResolver(func(host string) ([]net.IP, error) {
   333  		switch host {
   334  		case "host1.com":
   335  			return []net.IP{{128, 0, 0, 1}}, nil
   336  		case "host2.com":
   337  			return []net.IP{{128, 1, 0, 1}}, nil
   338  		case "localhost":
   339  			return []net.IP{{127, 0, 0, 1}}, nil
   340  		default:
   341  			panic("shouldn't happen")
   342  		}
   343  	})
   344  	renterTemplate.ContractorDeps = renterTemplate.HostDBDeps
   345  
   346  	// Create renter.
   347  	_, err = tg.AddNodes(renterTemplate)
   348  	if err != nil {
   349  		t.Fatal(err)
   350  	}
   351  
   352  	// We expect the renter to have 1 active contract.
   353  	renter := tg.Renters()[0]
   354  	contracts, err := renter.RenterContractsGet()
   355  	if err != nil {
   356  		t.Fatal(err)
   357  	}
   358  	if len(contracts.ActiveContracts) != 1 {
   359  		t.Fatalf("Expected 1 active contract but got %v", len(contracts.Contracts))
   360  	}
   361  
   362  	// Cancel the active contract.
   363  	err = renter.RenterContractCancelPost(contracts.ActiveContracts[0].ID)
   364  	if err != nil {
   365  		t.Fatal("Failed to cancel contract", err)
   366  	}
   367  
   368  	// We expect the renter to have 1 inactive contract.
   369  	contracts, err = renter.RenterInactiveContractsGet()
   370  	if err != nil {
   371  		t.Fatal(err)
   372  	}
   373  	if len(contracts.InactiveContracts) != 1 {
   374  		t.Fatalf("Expected 1 inactive contract but got %v", len(contracts.InactiveContracts))
   375  	}
   376  
   377  	// Create a new host which doesn't announce itself right away.
   378  	newHostTemplate := node.Host(testDir + "/host")
   379  	newHostTemplate.SkipHostAnnouncement = true
   380  	newHost, err := tg.AddNodes(newHostTemplate)
   381  	if err != nil {
   382  		t.Fatal(err)
   383  	}
   384  
   385  	// Announce the new host as host2.com. That should cause a conflict between
   386  	// the hosts. That shouldn't be an issue though since one of the hosts has
   387  	// a canceled contract.
   388  	hg, err = newHost[0].HostGet()
   389  	if err != nil {
   390  		t.Fatal("Failed to get port from host", err)
   391  	}
   392  	hostPort = hg.ExternalSettings.NetAddress.Port()
   393  	err1 := newHost[0].HostModifySettingPost(client.HostParamAcceptingContracts, true)
   394  	err2 := newHost[0].HostAnnounceAddrPost(modules.NetAddress(fmt.Sprintf("host2.com:%s", hostPort)))
   395  	err = errors.Compose(err1, err2)
   396  	if err != nil {
   397  		t.Fatal("Failed to announce the new host", err)
   398  	}
   399  
   400  	// Mine the announcement.
   401  	if err := tg.Miners()[0].MineBlock(); err != nil {
   402  		t.Fatal("Failed to mine block", err)
   403  	}
   404  
   405  	// The renter should have an active contract with the new host and an
   406  	// inactive contract with the old host now.
   407  	numRetries := 0
   408  	err = build.Retry(100, 100*time.Millisecond, func() error {
   409  		if numRetries%10 == 0 {
   410  			err := tg.Miners()[0].MineBlock()
   411  			if err != nil {
   412  				return err
   413  			}
   414  		}
   415  		numRetries++
   416  		// Get the active and inactive contracts.
   417  		contracts, err := renter.RenterInactiveContractsGet()
   418  		if err != nil {
   419  			return err
   420  		}
   421  		// Should have 1 active contract and 1 inactive contract.
   422  		if len(contracts.ActiveContracts) != 1 || len(contracts.InactiveContracts) != 1 {
   423  			return fmt.Errorf("Expected 1 active contract and 1 inactive contract. (%v/%v)",
   424  				len(contracts.ActiveContracts), len(contracts.InactiveContracts))
   425  		}
   426  		// The active contract should be with host2.
   427  		if contracts.ActiveContracts[0].NetAddress.Host() != "host2.com" {
   428  			return fmt.Errorf("active contract should be with host2.com")
   429  		}
   430  		// The inactive contract should be with host1.
   431  		if contracts.InactiveContracts[0].NetAddress.Host() != "host1.com" {
   432  			return fmt.Errorf("active contract should be with host1.com")
   433  		}
   434  		return nil
   435  	})
   436  	if err != nil {
   437  		t.Fatal(err)
   438  	}
   439  }