diff --git a/local/.env.local b/local/.env.local index 72a04d90..7029e77d 100644 --- a/local/.env.local +++ b/local/.env.local @@ -10,7 +10,7 @@ export SQS_AWS_REGION="sqs-aws-region" export SQS_QUEUE_NAME="cluster-registry-local" export SQS_BATCH_SIZE="10" export SQS_WAIT_SECONDS="5" -export SQS_RUN_INTERVAL="30" +export SQS_RUN_INTERVAL="10" export SET_CA_CERT="true" export CONTAINER_DB="dynamodb" export IMAGE_DB="amazon/dynamodb-local:2.2.1" diff --git a/pkg/apiserver/web/cache.go b/pkg/apiserver/web/cache.go index b4fc570c..80fe1a68 100644 --- a/pkg/apiserver/web/cache.go +++ b/pkg/apiserver/web/cache.go @@ -15,6 +15,7 @@ package web import ( "bufio" "bytes" + "encoding/gob" "github.com/adobe/cluster-registry/pkg/config" "github.com/eko/gocache/lib/v4/cache" "github.com/eko/gocache/lib/v4/store" @@ -26,8 +27,33 @@ import ( "net/url" "sort" "strconv" + "strings" ) +type Response struct { + // Value is the cached response value. + Value []byte + + // Header is the cached response header. + Header http.Header +} + +func StringToResponse(s string) Response { + var r Response + dec := gob.NewDecoder(strings.NewReader(s)) + _ = dec.Decode(&r) + + return r +} + +func (r Response) String() string { + var b bytes.Buffer + enc := gob.NewEncoder(&b) + _ = enc.Encode(r) + + return b.String() +} + func HTTPCache(client *cache.Cache[string], appConfig *config.AppConfig, tags []string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { @@ -35,16 +61,22 @@ func HTTPCache(client *cache.Cache[string], appConfig *config.AppConfig, tags [] sortURLParams(c.Request().URL) key := GenerateKey(c.Request().URL.String()) - value, err := client.Get(c.Request().Context(), key) + cachedResponse, err := client.Get(c.Request().Context(), key) + response := StringToResponse(cachedResponse) if err != nil { c.Logger().Errorf("Error getting key from cache: %s", err.Error()) c.Error(err) } // if key in cache - if value != "" { + if cachedResponse != "" { // return body from cache - _, _ = c.Response().Write([]byte(value)) + for k, v := range response.Header { + c.Response().Header().Set(k, strings.Join(v, ",")) + } + c.Response().WriteHeader(http.StatusOK) + _, _ = c.Response().Write(response.Value) + return nil } @@ -57,7 +89,11 @@ func HTTPCache(client *cache.Cache[string], appConfig *config.AppConfig, tags [] c.Error(err) } if writer.statusCode < 400 { - err := client.Set(c.Request().Context(), key, resBody.String(), store.WithExpiration(appConfig.ApiCacheTTL), store.WithTags(tags)) + newResponse := Response{ + Value: resBody.Bytes(), + Header: writer.Header(), + } + err := client.Set(c.Request().Context(), key, newResponse.String(), store.WithExpiration(appConfig.ApiCacheTTL), store.WithTags(tags)) if err != nil { c.Logger().Errorf("Error setting cache key: %s", err.Error()) c.Error(err) diff --git a/pkg/apiserver/web/handler/v1/handler_test.go b/pkg/apiserver/web/handler/v1/handler_test.go index 1978a5e0..c1f87e6f 100644 --- a/pkg/apiserver/web/handler/v1/handler_test.go +++ b/pkg/apiserver/web/handler/v1/handler_test.go @@ -309,15 +309,22 @@ func TestListClustersWithEmptyCache(t *testing.T) { rec := httptest.NewRecorder() ctx := r.NewContext(req, rec) - expectedResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) - expectedBody, err := json.Marshal(expectedResponse) + expectedClusterListResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) + expectedBody, err := json.Marshal(expectedClusterListResponse) test.NoError(err) + expectedBody = append(expectedBody, "\n"...) + + expectedResponse := web.Response{ + Value: expectedBody, + Header: http.Header{"Content-Type": []string{echo.MIMEApplicationJSON}}, + } + key := web.GenerateKey(ctx.Request().URL.String()) redisMock.ExpectGet(key).SetVal("") - redisMock.ExpectSet(key, string(expectedBody)+"\n", appConfig.ApiCacheTTL).SetVal("OK") + redisMock.ExpectSet(key, expectedResponse.String(), appConfig.ApiCacheTTL).SetVal("OK") err = web.HTTPCache(cacheManager, appConfig, []string{"clusters"})(h.ListClusters)(ctx) test.NoError(err) @@ -365,14 +372,21 @@ func TestListClustersWithCache(t *testing.T) { rec := httptest.NewRecorder() ctx := r.NewContext(req, rec) - expectedResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) - expectedBody, err := json.Marshal(expectedResponse) + expectedClusterListResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) + expectedBody, err := json.Marshal(expectedClusterListResponse) test.NoError(err) + expectedBody = append(expectedBody, "\n"...) + + expectedResponse := web.Response{ + Value: expectedBody, + Header: http.Header{"Content-Type": []string{echo.MIMEApplicationJSON}}, + } + key := web.GenerateKey(ctx.Request().URL.String()) - redisMock.ExpectSet(key, string(expectedBody), appConfig.ApiCacheTTL).SetVal("") - err = redisStore.Set(context.Background(), key, string(expectedBody), store.WithExpiration(appConfig.ApiCacheTTL)) + redisMock.ExpectSet(key, expectedResponse.String(), appConfig.ApiCacheTTL).SetVal("") + err = redisStore.Set(context.Background(), key, expectedResponse.String(), store.WithExpiration(appConfig.ApiCacheTTL)) test.NoError(err) redisMock.ExpectGet(key).SetVal(string(expectedBody)) diff --git a/pkg/apiserver/web/handler/v2/handler_test.go b/pkg/apiserver/web/handler/v2/handler_test.go index b67d3f03..69da9432 100644 --- a/pkg/apiserver/web/handler/v2/handler_test.go +++ b/pkg/apiserver/web/handler/v2/handler_test.go @@ -478,15 +478,21 @@ func TestListClustersWithEmptyCache(t *testing.T) { rec := httptest.NewRecorder() ctx := r.NewContext(req, rec) - expectedResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) - expectedBody, err := json.Marshal(expectedResponse) - test.NoError(err) + expectedClusterListResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) + expectedBody, err := json.Marshal(expectedClusterListResponse) + + expectedBody = append(expectedBody, "\n"...) + + expectedResponse := web.Response{ + Value: expectedBody, + Header: http.Header{"Content-Type": []string{echo.MIMEApplicationJSON}}, + } key := web.GenerateKey(ctx.Request().URL.String()) redisMock.ExpectGet(key).SetVal("") - redisMock.ExpectSet(key, string(expectedBody)+"\n", appConfig.ApiCacheTTL).SetVal("OK") + redisMock.ExpectSet(key, expectedResponse.String(), appConfig.ApiCacheTTL).SetVal("OK") err = web.HTTPCache(cacheManager, appConfig, []string{"clusters"})(h.ListClusters)(ctx) test.NoError(err) @@ -534,17 +540,24 @@ func TestListClustersWithCache(t *testing.T) { rec := httptest.NewRecorder() ctx := r.NewContext(req, rec) - expectedResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) - expectedBody, err := json.Marshal(expectedResponse) + expectedClusterListResponse := newClusterListResponse(expectedClusters, len(expectedClusters), 0, 200, false) + expectedBody, err := json.Marshal(expectedClusterListResponse) test.NoError(err) + expectedBody = append(expectedBody, "\n"...) + + expectedResponse := web.Response{ + Value: expectedBody, + Header: http.Header{"Content-Type": []string{echo.MIMEApplicationJSON}}, + } + key := web.GenerateKey(ctx.Request().URL.String()) - redisMock.ExpectSet(key, string(expectedBody), appConfig.ApiCacheTTL).SetVal("") - err = redisStore.Set(context.Background(), key, string(expectedBody), store.WithExpiration(appConfig.ApiCacheTTL)) + redisMock.ExpectSet(key, expectedResponse.String(), appConfig.ApiCacheTTL).SetVal("") + err = redisStore.Set(context.Background(), key, expectedResponse.String(), store.WithExpiration(appConfig.ApiCacheTTL)) test.NoError(err) - redisMock.ExpectGet(key).SetVal(string(expectedBody)) + redisMock.ExpectGet(key).SetVal(expectedResponse.String()) err = web.HTTPCache(cacheManager, appConfig, []string{"clusters"})(h.ListClusters)(ctx) test.NoError(err) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index fbdd2e1d..19045b41 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -74,7 +74,7 @@ func (s *e2eTestSuite) Test_EndToEnd_GetClusters() { jwtToken := jwt.GenerateDefaultSignedToken(appConfig) bearer := "Bearer " + jwtToken - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/api/v1/clusters", s.apiPort), nil) + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/api/v2/clusters", s.apiPort), nil) if err != nil { s.T().Fatalf("Cannot build http request: %v", err.Error()) } @@ -96,7 +96,7 @@ func (s *e2eTestSuite) Test_EndToEnd_GetClusters() { s.T().Fatalf("Cannot read response body: %v", err.Error()) } - err = json.Unmarshal([]byte(body), &clusters) + err = json.Unmarshal(body, &clusters) if err != nil { s.T().Fatalf("Failed to unmarshal data: %v", err.Error()) } @@ -115,7 +115,7 @@ func (s *e2eTestSuite) Test_EndToEnd_CreateCluster() { s.T().Fatalf("Failed to read data from file %s.", input_file) } - err = json.Unmarshal([]byte(data), &inputCluster) + err = json.Unmarshal(data, &inputCluster) if err != nil { s.T().Fatalf("Failed to unmarshal data: %v", err.Error()) } @@ -176,7 +176,7 @@ func (s *e2eTestSuite) Test_EndToEnd_CreateCluster() { s.T().Fatalf("Failed read response body: %v", err.Error()) } - err = json.Unmarshal([]byte(body), &outputCluster) + err = json.Unmarshal(body, &outputCluster) s.Assert().Equal(http.StatusOK, resp.StatusCode) @@ -227,7 +227,7 @@ func (s *e2eTestSuite) TBD_Test_EndToEnd_UpdateCluster() { log.Fatal(err.Error()) } - err = json.Unmarshal([]byte(data), &inputCluster) + err = json.Unmarshal(data, &inputCluster) if err != nil { log.Fatal(err.Error()) } @@ -281,7 +281,7 @@ func (s *e2eTestSuite) TBD_Test_EndToEnd_UpdateCluster() { log.Fatal(err) } - err = json.Unmarshal([]byte(body), &outputCluster) + err = json.Unmarshal(body, &outputCluster) if err != nil { log.Fatal(err) } @@ -318,7 +318,6 @@ func (s *e2eTestSuite) Test_EndToEnd_RateLimiter() { if err != nil { s.T().Fatalf("Cannot make http request: %v", err.Error()) } - defer resp.Body.Close() s.NoError(err) @@ -329,6 +328,7 @@ func (s *e2eTestSuite) Test_EndToEnd_RateLimiter() { } else { s.T().Errorf("Unexpected status code: %d", resp.StatusCode) } + _ = resp.Body.Close() } s.Assert().LessOrEqual(statusOK, expectedMaxStatusOK)