Skip to content

Commit

Permalink
feat: upgrade frisbii for bifrost-gateway support, add full e2e+bifro…
Browse files Browse the repository at this point in the history
…st-gateway test
  • Loading branch information
rvagg committed Oct 6, 2023
1 parent 026b6e4 commit ed3814b
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 27 deletions.
207 changes: 207 additions & 0 deletions cmd/booster-http/e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package main

import (
"context"
"fmt"
"io"
"math/rand"
"net/http"
"os"
"path/filepath"
"testing"
"time"

"github.com/filecoin-project/boost/itests/framework"
"github.com/filecoin-project/boost/testutil"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/itests/kit"
"github.com/ipfs/go-cid"
"github.com/ipld/go-car/v2"
"github.com/ipld/go-car/v2/storage"
unixfsgen "github.com/ipld/go-fixtureplate/generator"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipni/storetheindex/test"
"github.com/stretchr/testify/require"
)

// Test a full deal -> booster-http serve via lassie trustless and fronted with
// bifrost-gateway with curl-style trusted file fetch

func TestE2E(t *testing.T) {
req := require.New(t)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tr := test.NewTestIpniRunner(t, ctx, t.TempDir())

t.Log("Running in test directory:", tr.Dir)

kit.QuietMiningLogs()
framework.SetLogLevel()

t.Log("Starting boost and miner")
boostAndMiner := framework.NewTestFramework(ctx, t, framework.EnableLegacyDeals(true))
req.NoError(boostAndMiner.Start(framework.WithMaxStagingDealsBytes(40000000)))
defer boostAndMiner.Stop()

req.NoError(boostAndMiner.AddClientProviderBalance(abi.NewTokenAmount(1e15)))

// Get the listen address of the miner
minerApiInfo, err := boostAndMiner.LotusMinerApiInfo()
req.NoError(err)
fullNodeApiInfo, err := boostAndMiner.LotusFullNodeApiInfo()
req.NoError(err)

boosterHttpPort, err := testutil.FreePort()
require.NoError(t, err)

t.Log("Starting booster-http")
runAndWaitForBoosterHttp(ctx, t, []string{minerApiInfo}, fullNodeApiInfo, boosterHttpPort, "--serve-pieces=false", "--serve-cars=true")

cwd, err := os.Getwd()
req.NoError(err)
err = os.Chdir(tr.Dir)
req.NoError(err)

rndSeed := time.Now().UTC().UnixNano()
t.Logf("Random seed: %d", rndSeed)
var rndReader io.Reader = rand.New(rand.NewSource(rndSeed))

t.Log("Generate a CAR file with some content")
carFilepath := filepath.Join(tr.Dir, "test.car")
carFile, err := os.Create(carFilepath)
req.NoError(err)
store, err := storage.NewReadableWritable(carFile, nil, car.WriteAsCarV1(true))
req.NoError(err)
lsys := cidlink.DefaultLinkSystem()
lsys.TrustedStorage = true
lsys.SetReadStorage(store)
lsys.SetWriteStorage(store)
entity, err := unixfsgen.Parse(`dir(dir{name:"foo"}(dir{name:"bar"}(file:6MiB{name:"baz.txt"},file:1KiB{name:"boom.png"},file:1{name:"fizz.mov"})),file:1KiB{name:"qux.html"})`)
req.NoError(err)
t.Logf("Generating: %s", entity.Describe(""))
rootEnt, err := entity.Generate(lsys, rndReader)
req.NoError(err)
req.NoError(carFile.Close())

dealTestCarInParts(ctx, t, boostAndMiner, carFilepath, rootEnt.Root)

bifrostGateway := filepath.Join(tr.Dir, "bifrost-gateway")
tr.Run("go", "install", "github.com/ipfs/bifrost-gateway@latest")

t.Log("Install lassie to perform a fetch of our content")
lassie := filepath.Join(tr.Dir, "lassie")
tr.Run("go", "install", "github.com/filecoin-project/lassie/cmd/lassie@latest")

t.Log("Start bifrost-gateway")
bifrostPort, err := testutil.FreePort()
req.NoError(err)
bifrostMetricsPort, err := testutil.FreePort()
req.NoError(err)
bifrostReady := test.NewStdoutWatcher("Path gateway listening on ")
tr.Env = append(tr.Env, fmt.Sprintf("PROXY_GATEWAY_URL=http://0.0.0.0:%d", boosterHttpPort))

cmdBifrost := tr.Start(test.NewExecution(bifrostGateway,
"--gateway-port", fmt.Sprintf("%d", bifrostPort),
"--metrics-port", fmt.Sprintf("%d", bifrostMetricsPort),
).WithWatcher(bifrostReady))

select {
case <-bifrostReady.Signal:
case <-ctx.Done():
t.Fatal("timed out waiting for bifrost-gateway to start")
}

// we don't have a clear stdout signal for bifrost-gateway, so we need to
// probe for it
t.Logf("Waiting for bifrost-gateway server to fully come up on port %d...", bifrostPort)
start := time.Now()
waitForHttp(ctx, t, bifrostPort, http.StatusNotFound, 20*time.Second)
t.Logf("bifrost-gateway is up after %s", time.Since(start))

t.Log("Perform some curl requests to bifrost-gateway")
for _, fetch := range []struct {
path string
expect []byte
expectType string
}{
{"/foo/bar/baz.txt", rootEnt.Children[0].Children[0].Children[0].Content, "text/plain; charset=utf-8"},
{"/foo/bar/boom.png", rootEnt.Children[0].Children[0].Children[1].Content, "image/png"},
{"/foo/bar/fizz.mov", rootEnt.Children[0].Children[0].Children[2].Content, "video/quicktime"},
{"/qux.html", rootEnt.Children[1].Content, "text/html"},
} {
byts, ct, code, err := curl(fmt.Sprintf("http://0.0.0.0:%d/ipfs/%s%s", bifrostPort, rootEnt.Root.String(), fetch.path))
req.NoError(err)
req.Equal(http.StatusOK, code)
req.Equal(fetch.expect, byts)
req.Equal(fetch.expectType, ct)
}

t.Log("Perform some curl requests to bifrost-gateway that should fail")

byts, ct, code, err := curl(fmt.Sprintf("http://0.0.0.0:%d/ipfs/%s/nope", bifrostPort, rootEnt.Root.String()))
req.NoError(err)
req.Equal(http.StatusNotFound, code)
req.Contains(string(byts), "failed to resolve")
req.Equal("text/plain; charset=utf-8", ct)

byts, ct, code, err = curl(fmt.Sprintf("http://0.0.0.0:%d/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", bifrostPort))
req.NoError(err)
req.Equal(http.StatusBadGateway, code)
req.Contains(string(byts), "failed to resolve")
req.Equal("text/plain; charset=utf-8", ct)

t.Log("Perform a direct CAR fetch with lassie")
tr.Run(lassie,
"fetch",
"--provider", fmt.Sprintf("http://0.0.0.0:%d", boosterHttpPort),
"--output", "lassie.car",
rootEnt.Link().String()+"/foo/bar/baz.txt",
)

t.Log("Verify that the Lassie CAR contains the expected CIDs")
verifyCarContains(t, "lassie.car",
append(append([]cid.Cid{}, rootEnt.Root, rootEnt.Children[0].Root, rootEnt.Children[0].Children[0].Root), rootEnt.Children[0].Children[0].Children[0].SelfCids...),
)

t.Log("Cleanup ...")

tr.Stop(cmdBifrost, time.Second)

err = os.Chdir(cwd)
req.NoError(err)
}

func verifyCarContains(t *testing.T, carFilepath string, wantCids []cid.Cid) {
req := require.New(t)

carReader, err := os.Open(carFilepath)
req.NoError(err)
cr, err := car.NewBlockReader(carReader)
req.NoError(err)

gotCids := make([]cid.Cid, 0)
for {
blk, err := cr.Next()
if err != nil {
req.Equal(io.EOF, err)
break
}
gotCids = append(gotCids, blk.Cid())
}
req.ElementsMatch(wantCids, gotCids)
}

// simulate a curl to the url and return the body bytes, content type and status code
func curl(to string) ([]byte, string, int, error) {
req, err := http.Get(to)
if err != nil {
return nil, "", 0, err
}
defer req.Body.Close()
byts, err := io.ReadAll(req.Body)
if err != nil {
return nil, "", 0, err
}
return byts, req.Header.Get("Content-Type"), req.StatusCode, nil
}
6 changes: 3 additions & 3 deletions cmd/booster-http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const testFile = "test/test_file"

func TestNewHttpServer(t *testing.T) {
// Create a new mock Http server
port, err := testutil.OpenPort()
port, err := testutil.FreePort()
require.NoError(t, err)
ctrl := gomock.NewController(t)
httpServer := NewHttpServer("", "0.0.0.0", port, mocks_booster_http.NewMockHttpServerApi(ctrl), nil)
Expand Down Expand Up @@ -53,7 +53,7 @@ func TestNewHttpServer(t *testing.T) {

func TestHttpGzipResponse(t *testing.T) {
// Create a new mock Http server with custom functions
port, err := testutil.OpenPort()
port, err := testutil.FreePort()
require.NoError(t, err)
ctrl := gomock.NewController(t)
mockHttpServer := mocks_booster_http.NewMockHttpServerApi(ctrl)
Expand Down Expand Up @@ -114,7 +114,7 @@ func TestHttpGzipResponse(t *testing.T) {
func TestHttpInfo(t *testing.T) {
var v apiVersion

port, err := testutil.OpenPort()
port, err := testutil.FreePort()
require.NoError(t, err)
// Create a new mock Http server
ctrl := gomock.NewController(t)
Expand Down
26 changes: 15 additions & 11 deletions cmd/booster-http/multiminer_retrieval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"testing"
"time"

Expand All @@ -27,7 +28,7 @@ func TestMultiMinerHttpRetrieval(t *testing.T) {
fullNode2ApiInfo, err := rt.BoostAndMiner2.LotusFullNodeApiInfo()
require.NoError(t, err)

port, err := testutil.OpenPort()
port, err := testutil.FreePort()
require.NoError(t, err)

runAndWaitForBoosterHttp(ctx, t, []string{miner1ApiInfo, miner2ApiInfo}, fullNode2ApiInfo, port)
Expand Down Expand Up @@ -64,20 +65,23 @@ func TestMultiMinerHttpRetrieval(t *testing.T) {
}

func runAndWaitForBoosterHttp(ctx context.Context, t *testing.T, minerApiInfo []string, fullNodeApiInfo string, port int, args ...string) {
req := require.New(t)

args = append(args, fmt.Sprintf("--port=%d", port))

go func() {
_ = runBoosterHttp(ctx, t.TempDir(), minerApiInfo, fullNodeApiInfo, "ws://localhost:8042", args...)
_ = runBoosterHttp(ctx, t, minerApiInfo, fullNodeApiInfo, "ws://localhost:8042", args...)
}()

t.Logf("waiting for booster-http server to come up on port %d...", port)
start := time.Now()
req.Eventually(func() bool {
waitForHttp(ctx, t, port, http.StatusOK, 1*time.Minute)
t.Logf("booster-http is up after %s", time.Since(start))
}

func waitForHttp(ctx context.Context, t *testing.T, port int, waitForCode int, waitFor time.Duration) {
require.Eventually(t, func() bool {
// Wait for server to come up
resp, err := http.Get(fmt.Sprintf("http://localhost:%d", port))
if err == nil && resp != nil && resp.StatusCode == 200 {
if err == nil && resp != nil && resp.StatusCode == waitForCode {
return true
}
msg := "Waiting for http server to come up: "
Expand All @@ -86,18 +90,17 @@ func runAndWaitForBoosterHttp(ctx context.Context, t *testing.T, minerApiInfo []
}
if resp != nil {
respBody, err := io.ReadAll(resp.Body)
req.NoError(err)
require.NoError(t, err)
msg += " / Resp: " + resp.Status + "\n" + string(respBody)
}
t.Log(msg)
return false
}, 30*time.Second, 1*time.Second)
t.Logf("booster-http is up after %s", time.Since(start))
}, waitFor, 5*time.Second)
}

func runBoosterHttp(ctx context.Context, repo string, minerApiInfo []string, fullNodeApiInfo string, lidApiInfo string, args ...string) error {
func runBoosterHttp(ctx context.Context, t *testing.T, minerApiInfo []string, fullNodeApiInfo string, lidApiInfo string, args ...string) error {
args = append([]string{"booster-http",
"--repo=" + repo,
"--repo=" + t.TempDir(),
"--vv",
"run",
"--api-fullnode=" + fullNodeApiInfo,
Expand All @@ -107,6 +110,7 @@ func runBoosterHttp(ctx context.Context, repo string, minerApiInfo []string, ful
for _, apiInfo := range minerApiInfo {
args = append(args, "--api-storage="+apiInfo)
}
t.Logf("Running: booster-http %s", strings.Join(args, " "))
// new app per call
app := cli.NewApp()
app.Name = "booster-http"
Expand Down
8 changes: 2 additions & 6 deletions cmd/booster-http/trustless_gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestTrustlessGateway(t *testing.T) {

dealTestCarInParts(ctx, t, boostAndMiner, carFilepath, rootCid)

port, err := testutil.OpenPort()
port, err := testutil.FreePort()
require.NoError(t, err)

runAndWaitForBoosterHttp(ctx, t, []string{minerApiInfo}, fullNodeApiInfo, port, "--serve-pieces=false", "--serve-cars=true")
Expand All @@ -70,10 +70,6 @@ func TestTrustlessGateway(t *testing.T) {
req.NoError(err)
req.Equal(uint64(1), carReader.Version)
req.Equal([]cid.Cid{tc.Root}, carReader.Roots)
// log all headers
for k, v := range res.Header {
t.Logf(" header %s: %s", k, v)
}

for ii, expectedCid := range tc.ExpectedCids {
blk, err := carReader.Next()
Expand Down Expand Up @@ -137,8 +133,8 @@ func dealTestCarInParts(ctx context.Context, t *testing.T, boostAndMiner *framew
// LID and store the data on the first miner
res, err := boostAndMiner.MakeDummyDeal(dealUuid, file.Name(), rootCid, server.URL+"/"+filepath.Base(file.Name()), false)
req.NoError(err)
req.True(res.Result.Accepted)
t.Logf("created MarketDummyDeal %s", spew.Sdump(res))
req.True(res.Result.Accepted)
req.NoError(boostAndMiner.WaitForDealAddedToSector(dealUuid))
}
}
Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,12 @@ require (
github.com/filecoin-project/lotus v1.23.4-rc1
github.com/ipfs/boxo v0.12.0
github.com/ipfs/kubo v0.22.0
github.com/ipld/frisbii v0.2.1
github.com/ipld/frisbii v0.2.2-0.20231006085121-7d48d11641db
github.com/ipld/go-fixtureplate v0.0.2
github.com/ipld/ipld/specs v0.0.0-20230927010225-ef4dbd703269
github.com/ipni/go-libipni v0.5.2
github.com/ipni/ipni-cli v0.1.1
github.com/ipni/storetheindex v0.8.1
github.com/schollz/progressbar/v3 v3.13.1
)

Expand All @@ -361,7 +363,7 @@ require (
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/ipfs/go-ipfs-blockstore v1.3.0 // indirect
github.com/ipld/go-trustless-utils v0.3.1 // indirect
github.com/ipld/go-trustless-utils v0.3.2-0.20231006083052-c8beabd68ce4 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.11.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
Expand Down
Loading

0 comments on commit ed3814b

Please sign in to comment.