Skip to content

Commit d9b915b

Browse files
committed
(catalogd) add more unit tests for localdir storage.Instance
Also rename `query` to `metas` in places that were missed by #1703 Signed-off-by: Anik Bhattacharjee <[email protected]>
1 parent dcf50b8 commit d9b915b

File tree

4 files changed

+106
-33
lines changed

4 files changed

+106
-33
lines changed

Diff for: catalogd/cmd/catalogd/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ func main() {
306306
localStorage = &storage.LocalDirV1{
307307
RootDir: storeDir,
308308
RootURL: baseStorageURL,
309-
EnableQueryHandler: features.CatalogdFeatureGate.Enabled(features.APIV1QueryHandler),
309+
EnableMetasHandler: features.CatalogdFeatureGate.Enabled(features.APIV1MetasHandler),
310310
}
311311

312312
// Config for the catalogd web server

Diff for: catalogd/internal/features/features.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import (
66
)
77

88
const (
9-
APIV1QueryHandler = featuregate.Feature("APIV1QueryHandler")
9+
APIV1MetasHandler = featuregate.Feature("APIV1MetasHandler")
1010
)
1111

1212
var catalogdFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
13-
APIV1QueryHandler: {Default: false, PreRelease: featuregate.Alpha},
13+
APIV1MetasHandler: {Default: false, PreRelease: featuregate.Alpha},
1414
}
1515

1616
var CatalogdFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()

Diff for: catalogd/internal/storage/localdir.go

+21-7
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ import (
2828
type LocalDirV1 struct {
2929
RootDir string
3030
RootURL *url.URL
31-
EnableQueryHandler bool
31+
EnableMetasHandler bool
3232

3333
m sync.RWMutex
3434
// this singleflight Group is used in `getIndex()`` to handle concurrent HTTP requests
3535
// optimally. With the use of this slightflight group, the index is loaded from disk
36-
// once per concurrent group of HTTP requests being handled by the query handler.
36+
// once per concurrent group of HTTP requests being handled by the metas handler.
3737
// The single flight instance gives us a way to load the index from disk exactly once
3838
// per concurrent group of callers, and then let every concurrent caller have access to
3939
// the loaded index. This avoids lots of unnecessary open/decode/close cycles when concurrent
@@ -60,7 +60,7 @@ func (s *LocalDirV1) Store(ctx context.Context, catalog string, fsys fs.FS) erro
6060
defer os.RemoveAll(tmpCatalogDir)
6161

6262
storeMetaFuncs := []storeMetasFunc{storeCatalogData}
63-
if s.EnableQueryHandler {
63+
if s.EnableMetasHandler {
6464
storeMetaFuncs = append(storeMetaFuncs, storeIndexData)
6565
}
6666

@@ -126,7 +126,7 @@ func (s *LocalDirV1) ContentExists(catalog string) bool {
126126
return false
127127
}
128128

129-
if s.EnableQueryHandler {
129+
if s.EnableMetasHandler {
130130
indexFileStat, err := os.Stat(catalogIndexFilePath(s.catalogDir(catalog)))
131131
if err != nil {
132132
return false
@@ -189,8 +189,8 @@ func (s *LocalDirV1) StorageServerHandler() http.Handler {
189189
mux := http.NewServeMux()
190190

191191
mux.HandleFunc(s.RootURL.JoinPath("{catalog}", "api", "v1", "all").Path, s.handleV1All)
192-
if s.EnableQueryHandler {
193-
mux.HandleFunc(s.RootURL.JoinPath("{catalog}", "api", "v1", "metas").Path, s.handleV1Query)
192+
if s.EnableMetasHandler {
193+
mux.HandleFunc(s.RootURL.JoinPath("{catalog}", "api", "v1", "metas").Path, s.handleV1Metas)
194194
}
195195
allowedMethodsHandler := func(next http.Handler, allowedMethods ...string) http.Handler {
196196
allowedMethodSet := sets.New[string](allowedMethods...)
@@ -219,10 +219,24 @@ func (s *LocalDirV1) handleV1All(w http.ResponseWriter, r *http.Request) {
219219
http.ServeContent(w, r, "", catalogStat.ModTime(), catalogFile)
220220
}
221221

222-
func (s *LocalDirV1) handleV1Query(w http.ResponseWriter, r *http.Request) {
222+
func (s *LocalDirV1) handleV1Metas(w http.ResponseWriter, r *http.Request) {
223223
s.m.RLock()
224224
defer s.m.RUnlock()
225225

226+
// Check for unexpected query parameters
227+
expectedParams := map[string]bool{
228+
"schema": true,
229+
"package": true,
230+
"name": true,
231+
}
232+
233+
for param := range r.URL.Query() {
234+
if !expectedParams[param] {
235+
httpError(w, errInvalidParams)
236+
return
237+
}
238+
}
239+
226240
catalog := r.PathValue("catalog")
227241
catalogFile, catalogStat, err := s.catalogData(catalog)
228242
if err != nil {

Diff for: catalogd/internal/storage/localdir_test.go

+82-23
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ func TestLocalDirStoraget(t *testing.T) {
6666
},
6767
},
6868
{
69-
name: "storing with query handler enabled should create indexes",
69+
name: "storing with metas handler enabled should create indexes",
7070
setup: func(t *testing.T) (*LocalDirV1, fs.FS) {
7171
s := &LocalDirV1{
7272
RootDir: t.TempDir(),
73-
EnableQueryHandler: true,
73+
EnableMetasHandler: true,
7474
}
7575
return s, createTestFS(t)
7676
},
@@ -106,7 +106,7 @@ func TestLocalDirStoraget(t *testing.T) {
106106
for i := 0; i < 10; i++ {
107107
wg.Add(1)
108108
go func() {
109-
defer wg.Add(-1)
109+
defer wg.Done()
110110
for j := 0; j < 100; j++ {
111111
s.ContentExists(catalog)
112112
}
@@ -266,13 +266,13 @@ func TestLocalDirServerHandler(t *testing.T) {
266266
}
267267
}
268268

269-
// Tests to verify the behavior of the query endpoint, as described in
269+
// Tests to verify the behavior of the metas endpoint, as described in
270270
// https://docs.google.com/document/d/1s6_9IFEKGQLNh3ueH7SF4Yrx4PW9NSiNFqFIJx0pU-8/edit?usp=sharing
271-
func TestQueryEndpoint(t *testing.T) {
271+
func TestMetasEndpoint(t *testing.T) {
272272
store := &LocalDirV1{
273273
RootDir: t.TempDir(),
274274
RootURL: &url.URL{Path: urlPrefix},
275-
EnableQueryHandler: true,
275+
EnableMetasHandler: true,
276276
}
277277
if store.Store(context.Background(), "test-catalog", createTestFS(t)) != nil {
278278
t.Fatal("failed to store test catalog")
@@ -281,7 +281,7 @@ func TestQueryEndpoint(t *testing.T) {
281281

282282
testCases := []struct {
283283
name string
284-
setupStore func() (*httptest.Server, error)
284+
initRequest func(req *http.Request) error
285285
queryParams string
286286
expectedStatusCode int
287287
expectedContent string
@@ -329,27 +329,50 @@ func TestQueryEndpoint(t *testing.T) {
329329
expectedContent: "",
330330
},
331331
{
332-
name: "cached response with If-Modified-Since",
333-
queryParams: "?schema=olm.package",
332+
name: "valid query with packageName that returns multiple bolbs",
333+
queryParams: "?package=webhook_operator_test",
334+
expectedStatusCode: http.StatusOK,
335+
expectedContent: `{"image":"quaydock.io/namespace/bundle:0.0.3","name":"bundle.v0.0.1","package":"webhook_operator_test","properties":[{"type":"olm.bundle.object","value":{"data":"dW5pbXBvcnRhbnQK"}},{"type":"some.other","value":{"data":"arbitrary-info"}}],"relatedImages":[{"image":"testimage:latest","name":"test"}],"schema":"olm.bundle"}
336+
{"entries":[{"name":"bundle.v0.0.1"}],"name":"preview_test","package":"webhook_operator_test","schema":"olm.channel"}`,
337+
},
338+
{
339+
name: "cached response with If-Modified-Since",
340+
queryParams: "?schema=olm.package",
341+
initRequest: func(req *http.Request) error {
342+
resp, err := http.DefaultClient.Do(req)
343+
if err != nil {
344+
return err
345+
}
346+
resp.Body.Close()
347+
req.Header.Set("If-Modified-Since", resp.Header.Get("Last-Modified"))
348+
return nil
349+
},
334350
expectedStatusCode: http.StatusNotModified,
335351
expectedContent: "",
336352
},
353+
{
354+
name: "request with unknown parameters",
355+
queryParams: "?non-existent=foo",
356+
expectedStatusCode: http.StatusBadRequest,
357+
expectedContent: "400 Bad Request",
358+
},
359+
{
360+
name: "request with duplicate parameters",
361+
queryParams: "?schema=olm.bundle&&schema=olm.bundle",
362+
expectedStatusCode: http.StatusOK,
363+
expectedContent: `{"image":"quaydock.io/namespace/bundle:0.0.3","name":"bundle.v0.0.1","package":"webhook_operator_test","properties":[{"type":"olm.bundle.object","value":{"data":"dW5pbXBvcnRhbnQK"}},{"type":"some.other","value":{"data":"arbitrary-info"}}],"relatedImages":[{"image":"testimage:latest","name":"test"}],"schema":"olm.bundle"}`,
364+
},
337365
}
338366

339367
for _, tc := range testCases {
340368
t.Run(tc.name, func(t *testing.T) {
341-
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil)
369+
reqGet, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil)
342370
require.NoError(t, err)
343371

344-
if strings.Contains(tc.name, "If-Modified-Since") {
345-
// Do an initial request to get a Last-Modified timestamp
346-
// for the actual request
347-
resp, err := http.DefaultClient.Do(req)
348-
require.NoError(t, err)
349-
resp.Body.Close()
350-
req.Header.Set("If-Modified-Since", resp.Header.Get("Last-Modified"))
372+
if tc.initRequest != nil {
373+
require.NoError(t, tc.initRequest(reqGet))
351374
}
352-
resp, err := http.DefaultClient.Do(req)
375+
resp, err := http.DefaultClient.Do(reqGet)
353376
require.NoError(t, err)
354377
defer resp.Body.Close()
355378

@@ -358,6 +381,42 @@ func TestQueryEndpoint(t *testing.T) {
358381
actualContent, err := io.ReadAll(resp.Body)
359382
require.NoError(t, err)
360383
require.Equal(t, tc.expectedContent, strings.TrimSpace(string(actualContent)))
384+
385+
// Also do a HEAD request
386+
reqHead, err := http.NewRequest(http.MethodHead, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil)
387+
require.NoError(t, err)
388+
if tc.initRequest != nil {
389+
require.NoError(t, tc.initRequest(reqHead))
390+
}
391+
resp, err = http.DefaultClient.Do(reqHead)
392+
require.NoError(t, err)
393+
require.Equal(t, tc.expectedStatusCode, resp.StatusCode)
394+
actualContent, err = io.ReadAll(resp.Body)
395+
require.NoError(t, err)
396+
require.Equal(t, "", string(actualContent)) // HEAD should not return a body
397+
resp.Body.Close()
398+
399+
// And make sure any other method is not allowed
400+
reqPost, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil)
401+
require.NoError(t, err)
402+
resp, err = http.DefaultClient.Do(reqPost)
403+
require.NoError(t, err)
404+
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
405+
resp.Body.Close()
406+
407+
reqDelete, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil)
408+
require.NoError(t, err)
409+
resp, err = http.DefaultClient.Do(reqDelete)
410+
require.NoError(t, err)
411+
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
412+
resp.Body.Close()
413+
414+
reqPut, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil)
415+
require.NoError(t, err)
416+
resp, err = http.DefaultClient.Do(reqPut)
417+
require.NoError(t, err)
418+
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
419+
resp.Body.Close()
361420
})
362421
}
363422
}
@@ -366,7 +425,7 @@ func TestServerLoadHandling(t *testing.T) {
366425
store := &LocalDirV1{
367426
RootDir: t.TempDir(),
368427
RootURL: &url.URL{Path: urlPrefix},
369-
EnableQueryHandler: true,
428+
EnableMetasHandler: true,
370429
}
371430

372431
// Create large test data
@@ -443,20 +502,20 @@ func TestServerLoadHandling(t *testing.T) {
443502
},
444503
},
445504
{
446-
name: "mixed all and query endpoints",
505+
name: "mixed all and metas endpoints",
447506
concurrent: 40,
448507
requests: func(baseURL string) []*http.Request {
449508
var reqs []*http.Request
450509
for i := 0; i < 20; i++ {
451510
allReq, _ := http.NewRequest(http.MethodGet,
452511
fmt.Sprintf("%s/catalogs/test-catalog/api/v1/all", baseURL),
453512
nil)
454-
queryReq, _ := http.NewRequest(http.MethodGet,
513+
metasReq, _ := http.NewRequest(http.MethodGet,
455514
fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas?schema=olm.bundle", baseURL),
456515
nil)
457516
allReq.Header.Set("Accept", "application/jsonl")
458-
queryReq.Header.Set("Accept", "application/jsonl")
459-
reqs = append(reqs, allReq, queryReq)
517+
metasReq.Header.Set("Accept", "application/jsonl")
518+
reqs = append(reqs, allReq, metasReq)
460519
}
461520
return reqs
462521
},

0 commit comments

Comments
 (0)