github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/test/devstack/url_test.go (about)

     1  //go:build integration
     2  
     3  package devstack
     4  
     5  import (
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"path"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/filecoin-project/bacalhau/pkg/job"
    15  	"github.com/filecoin-project/bacalhau/pkg/model"
    16  	"github.com/filecoin-project/bacalhau/pkg/node"
    17  	"github.com/filecoin-project/bacalhau/pkg/test/scenario"
    18  	"github.com/stretchr/testify/require"
    19  	"github.com/stretchr/testify/suite"
    20  )
    21  
    22  type URLTestSuite struct {
    23  	scenario.ScenarioRunner
    24  }
    25  
    26  func TestURLTests(t *testing.T) {
    27  	suite.Run(t, new(URLTestSuite))
    28  }
    29  
    30  type URLBasedTestCase struct {
    31  	file1  string
    32  	file2  string
    33  	mount1 string
    34  	mount2 string
    35  	files  map[string]string
    36  }
    37  
    38  func runURLTest(
    39  	suite *URLTestSuite,
    40  	handler func(w http.ResponseWriter, r *http.Request),
    41  	testCase URLBasedTestCase,
    42  ) {
    43  	svr := httptest.NewServer(http.HandlerFunc(handler))
    44  	defer svr.Close()
    45  
    46  	allContent := testCase.files[fmt.Sprintf("/%s", testCase.file1)] + testCase.files[fmt.Sprintf("/%s", testCase.file2)]
    47  
    48  	testScenario := scenario.Scenario{
    49  		Stack: &scenario.StackConfig{
    50  			ComputeConfig: node.NewComputeConfigWith(node.ComputeConfigParams{
    51  				JobSelectionPolicy: model.JobSelectionPolicy{
    52  					Locality: model.Anywhere,
    53  				},
    54  			}),
    55  		},
    56  		Inputs: scenario.ManyStores(
    57  			scenario.URLDownload(svr, testCase.file1, testCase.mount1),
    58  			scenario.URLDownload(svr, testCase.file2, testCase.mount2),
    59  		),
    60  		ResultsChecker: scenario.ManyChecks(
    61  			scenario.FileEquals(model.DownloadFilenameStderr, ""),
    62  			scenario.FileEquals(model.DownloadFilenameStdout, allContent),
    63  		),
    64  		JobCheckers: []job.CheckStatesFunction{
    65  			job.WaitForSuccessfulCompletion(),
    66  		},
    67  		Spec: model.Spec{
    68  			Engine:    model.EngineWasm,
    69  			Verifier:  model.VerifierNoop,
    70  			Publisher: model.PublisherIpfs,
    71  			Wasm: model.JobSpecWasm{
    72  				EntryPoint:  scenario.CatFileToStdout.Spec.Wasm.EntryPoint,
    73  				EntryModule: scenario.CatFileToStdout.Spec.Wasm.EntryModule,
    74  				Parameters: []string{
    75  					testCase.mount1,
    76  					testCase.mount2,
    77  				},
    78  			},
    79  		},
    80  	}
    81  
    82  	suite.RunScenario(testScenario)
    83  }
    84  
    85  func getSimpleTestCase() URLBasedTestCase {
    86  	file1 := "hello-cid-1.txt"
    87  	file2 := "hello-cid-2.txt"
    88  	return URLBasedTestCase{
    89  		file1:  file1,
    90  		file2:  file2,
    91  		mount1: "/inputs-1",
    92  		mount2: "/inputs-2",
    93  		files: map[string]string{
    94  			fmt.Sprintf("/%s", file1): "Before you marry a person, you should first make them use a computer with slow Internet to see who they really are.\n",
    95  			fmt.Sprintf("/%s", file2): "I walk around like everything's fine, but deep down, inside my shoe, my sock is sliding off.\n",
    96  		},
    97  	}
    98  }
    99  
   100  func (s *URLTestSuite) TestMultipleURLs() {
   101  	testCase := getSimpleTestCase()
   102  	handler := func(w http.ResponseWriter, r *http.Request) {
   103  		content, ok := testCase.files[r.URL.Path]
   104  		if !ok {
   105  			w.WriteHeader(http.StatusNotFound)
   106  			w.Write([]byte("not found"))
   107  		} else {
   108  			w.WriteHeader(http.StatusOK)
   109  			w.Write([]byte(content))
   110  		}
   111  	}
   112  	runURLTest(s, handler, testCase)
   113  }
   114  
   115  // both starts should be before both ends if we are downloading in parallel
   116  func (s *URLTestSuite) TestURLsInParallel() {
   117  	mutex := sync.Mutex{}
   118  	testCase := getSimpleTestCase()
   119  
   120  	accessTimes := map[string]int64{}
   121  	getAccessTime := func() int64 {
   122  		return time.Now().UnixNano() / int64(time.Millisecond)
   123  	}
   124  	getAccessKey := func(filename, append string) string {
   125  		return fmt.Sprintf("%s_%s", filename, append)
   126  	}
   127  	setAccessTime := func(key string) {
   128  		mutex.Lock()
   129  		defer mutex.Unlock()
   130  		accessTimes[key] = getAccessTime()
   131  	}
   132  
   133  	handler := func(w http.ResponseWriter, r *http.Request) {
   134  		setAccessTime(getAccessKey(r.URL.Path, "start"))
   135  		time.Sleep(time.Second * 1)
   136  		setAccessTime(getAccessKey(r.URL.Path, "end"))
   137  		content, ok := testCase.files[r.URL.Path]
   138  		if !ok {
   139  			w.WriteHeader(http.StatusNotFound)
   140  			w.Write([]byte("not found"))
   141  		} else {
   142  			w.WriteHeader(http.StatusOK)
   143  			w.Write([]byte(content))
   144  		}
   145  
   146  	}
   147  	runURLTest(s, handler, testCase)
   148  
   149  	start1, ok := accessTimes["/"+getAccessKey(testCase.file1, "start")]
   150  	require.True(s.T(), ok)
   151  	start2, ok := accessTimes["/"+getAccessKey(testCase.file2, "start")]
   152  	require.True(s.T(), ok)
   153  	end1, ok := accessTimes["/"+getAccessKey(testCase.file1, "end")]
   154  	require.True(s.T(), ok)
   155  	end2, ok := accessTimes["/"+getAccessKey(testCase.file2, "end")]
   156  	require.True(s.T(), ok)
   157  
   158  	require.True(s.T(), start2 < end1, "start 2 should be before end 1")
   159  	require.True(s.T(), start1 < end2, "start 1 should be before end 2")
   160  }
   161  
   162  func (s *URLTestSuite) TestFlakyURLs() {
   163  	mutex := sync.Mutex{}
   164  	testCase := getSimpleTestCase()
   165  	accessCounter := map[string]int{}
   166  	increaseCounter := func(key string) int {
   167  		mutex.Lock()
   168  		defer mutex.Unlock()
   169  		accessCount, ok := accessCounter[key]
   170  		if !ok {
   171  			accessCount = 0
   172  		}
   173  		accessCount++
   174  		accessCounter[key] = accessCount
   175  		return accessCount
   176  	}
   177  
   178  	handler := func(w http.ResponseWriter, r *http.Request) {
   179  		accessCounts := increaseCounter(r.URL.Path)
   180  		if accessCounts < 3 {
   181  			w.WriteHeader(http.StatusUnauthorized)
   182  			w.Write([]byte("not found"))
   183  			return
   184  		}
   185  		content, ok := testCase.files[r.URL.Path]
   186  		if !ok {
   187  			w.WriteHeader(http.StatusNotFound)
   188  			w.Write([]byte("not found"))
   189  		} else {
   190  			w.WriteHeader(http.StatusOK)
   191  			w.Write([]byte(content))
   192  		}
   193  
   194  	}
   195  	runURLTest(s, handler, testCase)
   196  }
   197  
   198  func (s *URLTestSuite) TestIPFSURLCombo() {
   199  	ipfsfile := "hello-ipfs.txt"
   200  	urlfile := "hello-url.txt"
   201  	ipfsmount := "/inputs-1"
   202  	urlmount := "/inputs-2"
   203  
   204  	URLContent := "Common sense is like deodorant. The people who need it most never use it.\n"
   205  	IPFSContent := "Truth hurts. Maybe not as much as jumping on a bicycle with a seat missing, but it hurts.\n"
   206  
   207  	svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   208  		w.WriteHeader(http.StatusOK)
   209  		w.Write([]byte(URLContent))
   210  	}))
   211  	defer svr.Close()
   212  
   213  	testScenario := scenario.Scenario{
   214  		Stack: &scenario.StackConfig{
   215  			ComputeConfig: node.NewComputeConfigWith(node.ComputeConfigParams{
   216  				JobSelectionPolicy: model.JobSelectionPolicy{
   217  					Locality: model.Anywhere,
   218  				},
   219  			}),
   220  		},
   221  		Inputs: scenario.ManyStores(
   222  			scenario.StoredText(IPFSContent, path.Join(ipfsmount, ipfsfile)),
   223  			scenario.URLDownload(svr, urlfile, urlmount),
   224  		),
   225  		Spec: model.Spec{
   226  			Engine:    model.EngineWasm,
   227  			Verifier:  model.VerifierNoop,
   228  			Publisher: model.PublisherIpfs,
   229  			Wasm: model.JobSpecWasm{
   230  				EntryPoint:  scenario.CatFileToStdout.Spec.Wasm.EntryPoint,
   231  				EntryModule: scenario.CatFileToStdout.Spec.Wasm.EntryModule,
   232  				Parameters: []string{
   233  					urlmount,
   234  					path.Join(ipfsmount, ipfsfile),
   235  				},
   236  			},
   237  		},
   238  		ResultsChecker: scenario.FileEquals(model.DownloadFilenameStdout, URLContent+IPFSContent),
   239  		JobCheckers:    scenario.WaitUntilSuccessful(1),
   240  	}
   241  
   242  	s.RunScenario(testScenario)
   243  }