github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/go/datas/http_chunk_store_test.go (about)

     1  // Copyright 2016 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  package datas
     6  
     7  import (
     8  	"encoding/binary"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"testing"
    14  
    15  	"github.com/attic-labs/noms/go/chunks"
    16  	"github.com/attic-labs/noms/go/constants"
    17  	"github.com/attic-labs/noms/go/hash"
    18  	"github.com/attic-labs/noms/go/types"
    19  	"github.com/julienschmidt/httprouter"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/suite"
    22  )
    23  
    24  const testAuthToken = "aToken123"
    25  
    26  func TestHTTPChunkStore(t *testing.T) {
    27  	suite.Run(t, &HTTPChunkStoreSuite{})
    28  }
    29  
    30  type HTTPChunkStoreSuite struct {
    31  	suite.Suite
    32  	serverCS *chunks.TestStoreView
    33  	http     *httpChunkStore
    34  }
    35  
    36  type inlineServer struct {
    37  	*httprouter.Router
    38  }
    39  
    40  func (serv inlineServer) Do(req *http.Request) (resp *http.Response, err error) {
    41  	w := httptest.NewRecorder()
    42  	serv.ServeHTTP(w, req)
    43  	return &http.Response{
    44  			StatusCode: w.Code,
    45  			Status:     http.StatusText(w.Code),
    46  			Header:     w.HeaderMap,
    47  			Body:       ioutil.NopCloser(w.Body),
    48  		},
    49  		nil
    50  }
    51  
    52  func (suite *HTTPChunkStoreSuite) SetupTest() {
    53  	storage := &chunks.TestStorage{}
    54  	suite.serverCS = storage.NewView()
    55  	suite.http = newHTTPChunkStoreForTest(suite.serverCS)
    56  }
    57  
    58  func newHTTPChunkStoreForTest(cs chunks.ChunkStore) *httpChunkStore {
    59  	// Ideally, this function (and its bretheren below) would take a *TestStorage and mint a fresh TestStoreView in each handler call below. That'd break a bunch of tests in pull_test.go that want to pass in a single TestStoreView and then inspect it after doing a bunch of work. The cs.Rebase() calls here are a good compromise for now, but BUG 3415 tracks Making This Right.
    60  	serv := inlineServer{httprouter.New()}
    61  	serv.POST(
    62  		constants.WriteValuePath,
    63  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
    64  			cs.Rebase()
    65  			HandleWriteValue(w, req, ps, cs)
    66  		},
    67  	)
    68  	serv.POST(
    69  		constants.GetRefsPath,
    70  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
    71  			cs.Rebase()
    72  			HandleGetRefs(w, req, ps, cs)
    73  		},
    74  	)
    75  	serv.POST(
    76  		constants.HasRefsPath,
    77  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
    78  			cs.Rebase()
    79  			HandleHasRefs(w, req, ps, cs)
    80  		},
    81  	)
    82  	serv.POST(
    83  		constants.RootPath,
    84  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
    85  			cs.Rebase()
    86  			HandleRootPost(w, req, ps, cs)
    87  		},
    88  	)
    89  	serv.GET(
    90  		constants.RootPath,
    91  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
    92  			cs.Rebase()
    93  			HandleRootGet(w, req, ps, cs)
    94  		},
    95  	)
    96  	serv.GET(
    97  		constants.StatsPath,
    98  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
    99  			cs.Rebase()
   100  			HandleStats(w, req, ps, cs)
   101  		},
   102  	)
   103  	return newHTTPChunkStoreWithClient("http://localhost:9000", "", serv)
   104  }
   105  
   106  func newAuthenticatingHTTPChunkStoreForTest(assert *assert.Assertions, cs chunks.ChunkStore, hostUrl string) *httpChunkStore {
   107  	authenticate := func(req *http.Request) {
   108  		assert.Equal(testAuthToken, req.URL.Query().Get("access_token"))
   109  	}
   110  
   111  	serv := inlineServer{httprouter.New()}
   112  	serv.POST(
   113  		constants.RootPath,
   114  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   115  			cs.Rebase()
   116  			authenticate(req)
   117  			HandleRootPost(w, req, ps, cs)
   118  		},
   119  	)
   120  	serv.GET(
   121  		constants.RootPath,
   122  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   123  			cs.Rebase()
   124  			HandleRootGet(w, req, ps, cs)
   125  		},
   126  	)
   127  	return newHTTPChunkStoreWithClient(hostUrl, "", serv)
   128  }
   129  
   130  func newBadVersionHTTPChunkStoreForTest(cs chunks.ChunkStore) *httpChunkStore {
   131  	serv := inlineServer{httprouter.New()}
   132  	serv.POST(
   133  		constants.RootPath,
   134  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   135  			cs.Rebase()
   136  			HandleRootPost(w, req, ps, cs)
   137  			w.Header().Set(NomsVersionHeader, "BAD")
   138  		},
   139  	)
   140  	serv.GET(
   141  		constants.RootPath,
   142  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   143  			cs.Rebase()
   144  			HandleRootGet(w, req, ps, cs)
   145  		},
   146  	)
   147  	return newHTTPChunkStoreWithClient("http://localhost", "", serv)
   148  }
   149  
   150  func (suite *HTTPChunkStoreSuite) TearDownTest() {
   151  	suite.http.Close()
   152  	suite.serverCS.Close()
   153  }
   154  
   155  func (suite *HTTPChunkStoreSuite) TestPutChunk() {
   156  	c := types.EncodeValue(types.String("abc"))
   157  	suite.http.Put(c)
   158  	suite.True(suite.http.Has(c.Hash()))
   159  
   160  	suite.True(suite.http.Commit(hash.Hash{}, hash.Hash{}))
   161  	suite.Equal(1, suite.serverCS.Writes)
   162  }
   163  
   164  func (suite *HTTPChunkStoreSuite) TestPutChunksInOrder() {
   165  	vals := []types.Value{
   166  		types.String("abc"),
   167  		types.String("def"),
   168  	}
   169  	vs := types.NewValueStore(suite.serverCS)
   170  	defer vs.Close()
   171  	le := types.NewList(vs).Edit()
   172  	for _, val := range vals {
   173  		suite.http.Put(types.EncodeValue(val))
   174  		le.Append(types.NewRef(val))
   175  	}
   176  	suite.http.Put(types.EncodeValue(le.List()))
   177  	suite.True(suite.http.Commit(hash.Hash{}, hash.Hash{}))
   178  
   179  	suite.Equal(3, suite.serverCS.Writes)
   180  }
   181  
   182  func (suite *HTTPChunkStoreSuite) TestStats() {
   183  	suite.http.Put(types.EncodeValue(types.String("abc")))
   184  	suite.http.Put(types.EncodeValue(types.String("def")))
   185  
   186  	suite.True(suite.http.Commit(hash.Hash{}, hash.Hash{}))
   187  
   188  	suite.NotEmpty(suite.http.StatsSummary())
   189  }
   190  
   191  func (suite *HTTPChunkStoreSuite) TestRebase() {
   192  	suite.Equal(hash.Hash{}, suite.http.Root())
   193  	db := NewDatabase(suite.serverCS)
   194  	defer db.Close()
   195  	c := types.EncodeValue(types.NewMap(db))
   196  	suite.serverCS.Put(c)
   197  	suite.True(suite.serverCS.Commit(c.Hash(), hash.Hash{})) // change happens behind our backs
   198  	suite.Equal(hash.Hash{}, suite.http.Root())              // shouldn't be visible yet
   199  
   200  	suite.http.Rebase()
   201  	suite.Equal(c.Hash(), suite.serverCS.Root())
   202  }
   203  
   204  func (suite *HTTPChunkStoreSuite) TestRoot() {
   205  	db := NewDatabase(suite.serverCS)
   206  	defer db.Close()
   207  	c := types.EncodeValue(types.NewMap(db))
   208  	suite.serverCS.Put(c)
   209  	suite.True(suite.http.Commit(c.Hash(), hash.Hash{}))
   210  	suite.Equal(c.Hash(), suite.serverCS.Root())
   211  }
   212  
   213  func (suite *HTTPChunkStoreSuite) TestVersionMismatch() {
   214  	store := newBadVersionHTTPChunkStoreForTest(suite.serverCS)
   215  	vs := types.NewValueStore(store)
   216  	defer vs.Close()
   217  	c := types.EncodeValue(types.NewMap(vs))
   218  	suite.serverCS.Put(c)
   219  	suite.Panics(func() { store.Commit(c.Hash(), hash.Hash{}) })
   220  }
   221  
   222  func (suite *HTTPChunkStoreSuite) TestCommit() {
   223  	db := NewDatabase(suite.serverCS)
   224  	defer db.Close()
   225  	c := types.EncodeValue(types.NewMap(db))
   226  	suite.serverCS.Put(c)
   227  	suite.True(suite.http.Commit(c.Hash(), hash.Hash{}))
   228  	suite.Equal(c.Hash(), suite.serverCS.Root())
   229  }
   230  
   231  func (suite *HTTPChunkStoreSuite) TestEmptyHashCommit() {
   232  	suite.True(suite.http.Commit(hash.Hash{}, hash.Hash{}))
   233  	suite.Equal(hash.Hash{}, suite.serverCS.Root())
   234  }
   235  
   236  func (suite *HTTPChunkStoreSuite) TestCommitWithParams() {
   237  	u := fmt.Sprintf("http://localhost:9000?access_token=%s&other=19", testAuthToken)
   238  	store := newAuthenticatingHTTPChunkStoreForTest(suite.Assert(), suite.serverCS, u)
   239  	vs := types.NewValueStore(store)
   240  	defer vs.Close()
   241  	c := types.EncodeValue(types.NewMap(vs))
   242  	suite.serverCS.Put(c)
   243  	suite.True(store.Commit(c.Hash(), hash.Hash{}))
   244  	suite.Equal(c.Hash(), suite.serverCS.Root())
   245  }
   246  
   247  func (suite *HTTPChunkStoreSuite) TestGet() {
   248  	chnx := []chunks.Chunk{
   249  		chunks.NewChunk([]byte("abc")),
   250  		chunks.NewChunk([]byte("def")),
   251  	}
   252  	for _, c := range chnx {
   253  		suite.serverCS.Put(c)
   254  	}
   255  	got := suite.http.Get(chnx[0].Hash())
   256  	suite.Equal(chnx[0].Hash(), got.Hash())
   257  	got = suite.http.Get(chnx[1].Hash())
   258  	suite.Equal(chnx[1].Hash(), got.Hash())
   259  }
   260  
   261  func (suite *HTTPChunkStoreSuite) TestGetMany() {
   262  	chnx := []chunks.Chunk{
   263  		chunks.NewChunk([]byte("abc")),
   264  		chunks.NewChunk([]byte("def")),
   265  	}
   266  	notPresent := chunks.NewChunk([]byte("ghi")).Hash()
   267  	for _, c := range chnx {
   268  		suite.serverCS.Put(c)
   269  	}
   270  	persistChunks(suite.serverCS)
   271  
   272  	hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash(), notPresent)
   273  	foundChunks := make(chan *chunks.Chunk)
   274  	go func() { suite.http.GetMany(hashes, foundChunks); close(foundChunks) }()
   275  
   276  	for c := range foundChunks {
   277  		hashes.Remove(c.Hash())
   278  	}
   279  	suite.Len(hashes, 1)
   280  	suite.True(hashes.Has(notPresent))
   281  }
   282  
   283  func (suite *HTTPChunkStoreSuite) TestOverGetThreshold_Issue3589() {
   284  	if testing.Short() {
   285  		suite.T().Skip("Skipping test in short mode.")
   286  	}
   287  	// BUG 3589 happened because we requested enough hashes that the body was over 10MB. The new way of encoding getRefs request bodies means that 10MB will no longer be a limitation. This test will generate a request larger than 10MB.
   288  	count := ((10 * (1 << 20)) / hash.ByteLen) + 1
   289  	hashes := make(hash.HashSet, count)
   290  	for i := 0; i < count-1; i++ {
   291  		h := hash.Hash{}
   292  		binary.BigEndian.PutUint64(h[hash.ByteLen-8:], uint64(i))
   293  		hashes.Insert(h)
   294  	}
   295  
   296  	present := chunks.NewChunk([]byte("ghi"))
   297  	suite.serverCS.Put(present)
   298  	persistChunks(suite.serverCS)
   299  	hashes.Insert(present.Hash())
   300  
   301  	foundChunks := make(chan *chunks.Chunk)
   302  	go func() { suite.http.GetMany(hashes, foundChunks); close(foundChunks) }()
   303  
   304  	found := hash.HashSet{}
   305  	for c := range foundChunks {
   306  		found.Insert(c.Hash())
   307  	}
   308  	suite.Len(found, 1)
   309  	suite.True(found.Has(present.Hash()))
   310  }
   311  
   312  func (suite *HTTPChunkStoreSuite) TestGetManyAllCached() {
   313  	chnx := []chunks.Chunk{
   314  		chunks.NewChunk([]byte("abc")),
   315  		chunks.NewChunk([]byte("def")),
   316  	}
   317  	for _, c := range chnx {
   318  		suite.http.Put(c)
   319  	}
   320  
   321  	hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash())
   322  	foundChunks := make(chan *chunks.Chunk)
   323  	go func() { suite.http.GetMany(hashes, foundChunks); close(foundChunks) }()
   324  
   325  	for c := range foundChunks {
   326  		hashes.Remove(c.Hash())
   327  	}
   328  	suite.Len(hashes, 0)
   329  }
   330  
   331  func (suite *HTTPChunkStoreSuite) TestGetManySomeCached() {
   332  	chnx := []chunks.Chunk{
   333  		chunks.NewChunk([]byte("abc")),
   334  		chunks.NewChunk([]byte("def")),
   335  	}
   336  	cached := chunks.NewChunk([]byte("ghi"))
   337  	for _, c := range chnx {
   338  		suite.serverCS.Put(c)
   339  	}
   340  	persistChunks(suite.serverCS)
   341  	suite.http.Put(cached)
   342  
   343  	hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash(), cached.Hash())
   344  	foundChunks := make(chan *chunks.Chunk)
   345  	go func() { suite.http.GetMany(hashes, foundChunks); close(foundChunks) }()
   346  
   347  	for c := range foundChunks {
   348  		hashes.Remove(c.Hash())
   349  	}
   350  	suite.Len(hashes, 0)
   351  }
   352  
   353  func (suite *HTTPChunkStoreSuite) TestGetSame() {
   354  	chnx := []chunks.Chunk{
   355  		chunks.NewChunk([]byte("def")),
   356  		chunks.NewChunk([]byte("def")),
   357  	}
   358  	for _, c := range chnx {
   359  		suite.serverCS.Put(c)
   360  	}
   361  	got := suite.http.Get(chnx[0].Hash())
   362  	suite.Equal(chnx[0].Hash(), got.Hash())
   363  	got = suite.http.Get(chnx[1].Hash())
   364  	suite.Equal(chnx[1].Hash(), got.Hash())
   365  }
   366  
   367  func (suite *HTTPChunkStoreSuite) TestGetWithRoot() {
   368  	chnx := []chunks.Chunk{
   369  		chunks.NewChunk([]byte("abc")),
   370  		chunks.NewChunk([]byte("def")),
   371  	}
   372  	for _, c := range chnx {
   373  		suite.serverCS.Put(c)
   374  	}
   375  	suite.serverCS.Commit(chnx[0].Hash(), hash.Hash{})
   376  
   377  	serv := inlineServer{httprouter.New()}
   378  	serv.GET(
   379  		constants.RootPath,
   380  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   381  			suite.serverCS.Rebase()
   382  			HandleRootGet(w, req, ps, suite.serverCS)
   383  		},
   384  	)
   385  	serv.POST(
   386  		constants.GetRefsPath,
   387  		func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   388  			r := req.URL.Query().Get("root")
   389  			suite.Equal(chnx[0].Hash().String(), r)
   390  			suite.serverCS.Rebase()
   391  			HandleGetRefs(w, req, ps, suite.serverCS)
   392  		},
   393  	)
   394  	store := newHTTPChunkStoreWithClient("http://localhost:9000", "", serv)
   395  
   396  	got := store.Get(chnx[1].Hash())
   397  	suite.Equal(chnx[1].Hash(), got.Hash())
   398  }
   399  
   400  func (suite *HTTPChunkStoreSuite) TestHas() {
   401  	chnx := []chunks.Chunk{
   402  		chunks.NewChunk([]byte("abc")),
   403  		chunks.NewChunk([]byte("def")),
   404  	}
   405  	for _, c := range chnx {
   406  		suite.serverCS.Put(c)
   407  	}
   408  	suite.True(suite.http.Has(chnx[0].Hash()))
   409  	suite.True(suite.http.Has(chnx[1].Hash()))
   410  }
   411  
   412  func (suite *HTTPChunkStoreSuite) TestHasMany() {
   413  	chnx := []chunks.Chunk{
   414  		chunks.NewChunk([]byte("abc")),
   415  		chunks.NewChunk([]byte("def")),
   416  	}
   417  	for _, c := range chnx {
   418  		suite.serverCS.Put(c)
   419  	}
   420  	persistChunks(suite.serverCS)
   421  	notPresent := chunks.NewChunk([]byte("ghi")).Hash()
   422  
   423  	hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash(), notPresent)
   424  	absent := suite.http.HasMany(hashes)
   425  
   426  	suite.Len(absent, 1)
   427  	for _, c := range chnx {
   428  		suite.False(absent.Has(c.Hash()), "%s present in %v", c.Hash(), absent)
   429  	}
   430  	suite.True(absent.Has(notPresent))
   431  }
   432  
   433  func (suite *HTTPChunkStoreSuite) TestHasManyAllCached() {
   434  	chnx := []chunks.Chunk{
   435  		chunks.NewChunk([]byte("abc")),
   436  		chunks.NewChunk([]byte("def")),
   437  	}
   438  	for _, c := range chnx {
   439  		suite.http.Put(c)
   440  	}
   441  	persistChunks(suite.serverCS)
   442  
   443  	hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash())
   444  	absent := suite.http.HasMany(hashes)
   445  
   446  	suite.Len(absent, 0)
   447  	for _, c := range chnx {
   448  		suite.False(absent.Has(c.Hash()), "%s present in %v", c.Hash(), absent)
   449  	}
   450  }
   451  
   452  func (suite *HTTPChunkStoreSuite) TestHasManySomeCached() {
   453  	chnx := []chunks.Chunk{
   454  		chunks.NewChunk([]byte("abc")),
   455  		chunks.NewChunk([]byte("def")),
   456  	}
   457  	cached := chunks.NewChunk([]byte("ghi"))
   458  	for _, c := range chnx {
   459  		suite.serverCS.Put(c)
   460  	}
   461  	persistChunks(suite.serverCS)
   462  	suite.http.Put(cached)
   463  
   464  	hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash(), cached.Hash())
   465  	absent := suite.http.HasMany(hashes)
   466  
   467  	suite.Len(absent, 0)
   468  	for _, c := range chnx {
   469  		suite.False(absent.Has(c.Hash()), "%s present in %v", c.Hash(), absent)
   470  	}
   471  	suite.False(absent.Has(cached.Hash()), "%s present in %v", cached.Hash(), absent)
   472  }