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 }