diff --git a/src/autoscaler/Makefile b/src/autoscaler/Makefile index 34d73e2d47..9e6c750e6b 100644 --- a/src/autoscaler/Makefile +++ b/src/autoscaler/Makefile @@ -121,11 +121,12 @@ build_test-%: generate-fakes check: fmt lint build test +.PHONY: generate-fakes test: generate-fakes @echo "Running tests" APP_AUTOSCALER_TEST_RUN='true' go run 'github.com/onsi/ginkgo/v2/ginkgo@${GINKGO_VERSION}' -p ${GINKGO_OPTS} ${TEST} --skip-package='integration' -testsuite: generate-fakes +testsuite: @echo " - using DBURL=${DBURL} TEST=${TEST}" APP_AUTOSCALER_TEST_RUN='true' go run 'github.com/onsi/ginkgo/v2/ginkgo@${GINKGO_VERSION}' -p ${GINKGO_OPTS} ${TEST} diff --git a/src/autoscaler/api/cmd/api/api_suite_test.go b/src/autoscaler/api/cmd/api/api_suite_test.go index b545e2f0ff..1abbfcb369 100644 --- a/src/autoscaler/api/cmd/api/api_suite_test.go +++ b/src/autoscaler/api/cmd/api/api_suite_test.go @@ -251,6 +251,7 @@ func NewApiRunner() *ApiRunner { } func (ap *ApiRunner) Start() { + GinkgoHelper() // #nosec G204 apSession, err := gexec.Start(exec.Command( apPath, diff --git a/src/autoscaler/api/cmd/api/api_test.go b/src/autoscaler/api/cmd/api/api_test.go index df170132ff..c8c349a740 100644 --- a/src/autoscaler/api/cmd/api/api_test.go +++ b/src/autoscaler/api/cmd/api/api_test.go @@ -1,6 +1,8 @@ package main_test import ( + "crypto/rand" + "crypto/rsa" "fmt" "io" "net/http" @@ -9,9 +11,9 @@ import ( "strings" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/config" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" - - . "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -44,9 +46,9 @@ var _ = Describe("Api", func() { vcapPort = 8080 + GinkgoParallelProcess() - brokerHttpClient = NewServiceBrokerClient() + brokerHttpClient = testhelpers.NewServiceBrokerClient() healthHttpClient = &http.Client{} - apiHttpClient = NewPublicApiClient() + apiHttpClient = testhelpers.NewPublicApiClient() cfServerHttpClient = &http.Client{} serverURL, err = url.Parse(fmt.Sprintf("https://127.0.0.1:%d", cfg.Server.Port)) @@ -166,7 +168,7 @@ var _ = Describe("Api", func() { bodyBytes, err := io.ReadAll(rsp.Body) - FailOnError("Read failed", err) + testhelpers.FailOnError("Read failed", err) if len(bodyBytes) == 0 { Fail("body empty") } @@ -297,7 +299,29 @@ var _ = Describe("Api", func() { }) When("running CF server", func() { + var ( + cfInstanceKeyFile string + cfInstanceCertFile string + ) + BeforeEach(func() { + rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) + Expect(err).NotTo(HaveOccurred()) + + cfInstanceCert, err := testhelpers.GenerateClientCertWithPrivateKey("org-guid", "space-guid", rsaPrivateKey) + Expect(err).NotTo(HaveOccurred()) + + certTmpDir := os.TempDir() + + cfInstanceCertFile, err := configutil.MaterializeContentInFile(certTmpDir, "eventgenerator.crt", string(cfInstanceCert)) + Expect(err).NotTo(HaveOccurred()) + os.Setenv("CF_INSTANCE_CERT", string(cfInstanceCertFile)) + + cfInstanceKey := testhelpers.GenerateClientKeyWithPrivateKey(rsaPrivateKey) + cfInstanceKeyFile, err = configutil.MaterializeContentInFile(certTmpDir, "eventgenerator.key", string(cfInstanceKey)) + Expect(err).NotTo(HaveOccurred()) + os.Setenv("CF_INSTANCE_KEY", string(cfInstanceKeyFile)) + os.Setenv("VCAP_APPLICATION", "{}") os.Setenv("VCAP_SERVICES", getVcapServices()) os.Setenv("PORT", fmt.Sprintf("%d", vcapPort)) @@ -306,6 +330,12 @@ var _ = Describe("Api", func() { AfterEach(func() { runner.Interrupt() Eventually(runner.Session, 5).Should(Exit(0)) + + os.Remove(cfInstanceKeyFile) + os.Remove(cfInstanceCertFile) + + os.Unsetenv("CF_INSTANCE_KEY") + os.Unsetenv("CF_INSTANCE_CERT") os.Unsetenv("VCAP_APPLICATION") os.Unsetenv("VCAP_SERVICES") os.Unsetenv("PORT") diff --git a/src/autoscaler/api/config/config.go b/src/autoscaler/api/config/config.go index 71915a25d3..6bc856b39e 100644 --- a/src/autoscaler/api/config/config.go +++ b/src/autoscaler/api/config/config.go @@ -209,9 +209,17 @@ func loadVcapConfig(conf *Config, vcapReader configutil.VCAPConfigurationReader) if err := configureBindingDb(conf, vcapReader); err != nil { return err } + + configureEventGenerator(conf) + return nil } +func configureEventGenerator(conf *Config) { + conf.EventGenerator.TLSClientCerts.CertFile = os.Getenv("CF_INSTANCE_CERT") + conf.EventGenerator.TLSClientCerts.KeyFile = os.Getenv("CF_INSTANCE_KEY") +} + func configurePolicyDb(conf *Config, vcapReader configutil.VCAPConfigurationReader) error { currentPolicyDb, ok := conf.Db[db.PolicyDb] if !ok { diff --git a/src/autoscaler/api/config/config_test.go b/src/autoscaler/api/config/config_test.go index 74b840cc89..fa0ba67b7e 100644 --- a/src/autoscaler/api/config/config_test.go +++ b/src/autoscaler/api/config/config_test.go @@ -2,6 +2,7 @@ package config_test import ( "fmt" + "os" "time" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/fakes" @@ -41,6 +42,25 @@ var _ = Describe("Config", func() { conf, err = LoadConfig("", mockVCAPConfigurationReader) }) + When("vcap CF_INSTANCE_CERT is set", func() { + BeforeEach(func() { + os.Setenv("CF_INSTANCE_KEY", "some/path/in/container/eventgenerator.key") + os.Setenv("CF_INSTANCE_CERT", "some/path/in/container/eventgenerator.crt") + }) + + AfterEach(func() { + os.Unsetenv("CF_INSTANCE_KEY") + os.Unsetenv("CF_INSTANCE_CERT") + + }) + + It("sets EventGenerator TlSClientCert", func() { + Expect(conf.EventGenerator.TLSClientCerts.KeyFile).To(Equal("some/path/in/container/eventgenerator.key")) + Expect(conf.EventGenerator.TLSClientCerts.CertFile).To(Equal("some/path/in/container/eventgenerator.crt")) + + }) + }) + When("vcap PORT is set to a number", func() { BeforeEach(func() { mockVCAPConfigurationReader.GetPortReturns(3333) diff --git a/src/autoscaler/api/publicapiserver/middleware.go b/src/autoscaler/api/publicapiserver/middleware.go index e64af3f281..53542d01e9 100644 --- a/src/autoscaler/api/publicapiserver/middleware.go +++ b/src/autoscaler/api/publicapiserver/middleware.go @@ -61,7 +61,7 @@ func (mw *Middleware) Oauth(next http.Handler) http.Handler { if err != nil { mw.logger.Error("failed to check if user is admin", err, nil) handlers.WriteJSONResponse(w, http.StatusInternalServerError, models.ErrorResponse{ - Code: "Internal-Server-Error", + Code: http.StatusText(http.StatusInternalServerError), Message: "Failed to check if user is admin"}) return } diff --git a/src/autoscaler/api/publicapiserver/middleware_test.go b/src/autoscaler/api/publicapiserver/middleware_test.go index 92ad777145..31aa6fff08 100644 --- a/src/autoscaler/api/publicapiserver/middleware_test.go +++ b/src/autoscaler/api/publicapiserver/middleware_test.go @@ -131,7 +131,7 @@ var _ = Describe("Middleware", func() { }) It("should fail with 500", func() { CheckResponse(resp, http.StatusInternalServerError, models.ErrorResponse{ - Code: "Internal-Server-Error", + Code: http.StatusText(http.StatusInternalServerError), Message: "Failed to check if user is admin", }) }) diff --git a/src/autoscaler/api/publicapiserver/public_api_handler.go b/src/autoscaler/api/publicapiserver/public_api_handler.go index 06aa056e7a..c52c4dd234 100644 --- a/src/autoscaler/api/publicapiserver/public_api_handler.go +++ b/src/autoscaler/api/publicapiserver/public_api_handler.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" "os" - "reflect" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/config" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/policyvalidator" @@ -16,12 +15,11 @@ import ( "code.cloudfoundry.org/app-autoscaler/src/autoscaler/cred_helper" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers/handlers" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/routes" - "github.com/google/uuid" - - "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers/handlers" "code.cloudfoundry.org/lager/v3" + "github.com/google/uuid" ) type PublicApiHandler struct { @@ -56,22 +54,26 @@ func NewPublicApiHandler(logger lager.Logger, conf *config.Config, policydb db.P policydb: policydb, bindingdb: bindingdb, eventGeneratorClient: egClient, - policyValidator: policyvalidator.NewPolicyValidator( - conf.PolicySchemaPath, - conf.ScalingRules.CPU.LowerThreshold, - conf.ScalingRules.CPU.UpperThreshold, - conf.ScalingRules.CPUUtil.LowerThreshold, - conf.ScalingRules.CPUUtil.UpperThreshold, - conf.ScalingRules.DiskUtil.LowerThreshold, - conf.ScalingRules.DiskUtil.UpperThreshold, - conf.ScalingRules.Disk.LowerThreshold, - conf.ScalingRules.Disk.UpperThreshold, - ), - schedulerUtil: schedulerclient.New(conf, logger), - credentials: credentials, + policyValidator: createPolicyValidator(conf), + schedulerUtil: schedulerclient.New(conf, logger), + credentials: credentials, } } +func createPolicyValidator(conf *config.Config) *policyvalidator.PolicyValidator { + return policyvalidator.NewPolicyValidator( + conf.PolicySchemaPath, + conf.ScalingRules.CPU.LowerThreshold, + conf.ScalingRules.CPU.UpperThreshold, + conf.ScalingRules.CPUUtil.LowerThreshold, + conf.ScalingRules.CPUUtil.UpperThreshold, + conf.ScalingRules.DiskUtil.LowerThreshold, + conf.ScalingRules.DiskUtil.UpperThreshold, + conf.ScalingRules.Disk.LowerThreshold, + conf.ScalingRules.Disk.UpperThreshold, + ) +} + func writeErrorResponse(w http.ResponseWriter, statusCode int, message string) { handlers.WriteJSONResponse(w, statusCode, models.ErrorResponse{ Code: http.StatusText(statusCode), @@ -85,6 +87,7 @@ func (h *PublicApiHandler) GetScalingPolicy(w http.ResponseWriter, r *http.Reque writeErrorResponse(w, http.StatusBadRequest, ErrorMessageAppidIsRequired) return } + logger := h.logger.Session("GetScalingPolicy", lager.Data{"appId": appId}) logger.Info("Get Scaling Policy") @@ -148,16 +151,15 @@ func (h *PublicApiHandler) AttachScalingPolicy(w http.ResponseWriter, r *http.Re } policyGuid := uuid.NewString() - err = h.policydb.SaveAppPolicy(r.Context(), appId, policy, policyGuid) - if err != nil { + if err := h.policydb.SaveAppPolicy(r.Context(), appId, policy, policyGuid); err != nil { logger.Error("Failed to save policy", err) writeErrorResponse(w, http.StatusInternalServerError, "Error saving policy") return } h.logger.Info("creating/updating schedules", lager.Data{"policy": policy}) - err = h.schedulerUtil.CreateOrUpdateSchedule(r.Context(), appId, policy, policyGuid) - if err != nil { + + if err := h.schedulerUtil.CreateOrUpdateSchedule(r.Context(), appId, policy, policyGuid); err != nil { logger.Error("Failed to create/update schedule", err) writeErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -186,7 +188,7 @@ func (h *PublicApiHandler) AttachScalingPolicy(w http.ResponseWriter, r *http.Re } _, err = w.Write(responseJson) if err != nil { - logger.Error("Failed to write body", err) + h.logger.Error("Failed to write body", err) } } @@ -197,62 +199,28 @@ func (h *PublicApiHandler) DetachScalingPolicy(w http.ResponseWriter, r *http.Re writeErrorResponse(w, http.StatusBadRequest, ErrorMessageAppidIsRequired) return } + logger := h.logger.Session("DetachScalingPolicy", lager.Data{"appId": appId}) logger.Info("Deleting policy json", lager.Data{"appId": appId}) - err := h.policydb.DeletePolicy(r.Context(), appId) - if err != nil { + + if err := h.policydb.DeletePolicy(r.Context(), appId); err != nil { logger.Error("Failed to delete policy from database", err) writeErrorResponse(w, http.StatusInternalServerError, "Error deleting policy") return } + logger.Info("Deleting schedules") - err = h.schedulerUtil.DeleteSchedule(r.Context(), appId) - if err != nil { + if err := h.schedulerUtil.DeleteSchedule(r.Context(), appId); err != nil { logger.Error("Failed to delete schedule", err) writeErrorResponse(w, http.StatusInternalServerError, "Error deleting schedules") return } - if h.bindingdb != nil && !reflect.ValueOf(h.bindingdb).IsNil() { - //TODO this is a copy of part of the attach ... this should use a common function. - // brokered offering: check if there's a default policy that could apply - serviceInstance, err := h.bindingdb.GetServiceInstanceByAppId(appId) - if err != nil { - logger.Error("Failed to find service instance for app", err) - writeErrorResponse(w, http.StatusInternalServerError, "Error retrieving service instance") - return - } - if serviceInstance != nil && serviceInstance.DefaultPolicy != "" { - policyStr := serviceInstance.DefaultPolicy - policyGuidStr := serviceInstance.DefaultPolicyGuid - logger.Info("saving default policy json for app", lager.Data{"policy": policyStr}) - var policy *models.ScalingPolicy - err := json.Unmarshal([]byte(policyStr), &policy) - if err != nil { - h.logger.Error("default policy invalid", err, lager.Data{"appId": appId, "policy": policyStr}) - writeErrorResponse(w, http.StatusInternalServerError, "Default policy not valid") - return - } - - err = h.policydb.SaveAppPolicy(r.Context(), appId, policy, policyGuidStr) - if err != nil { - logger.Error("failed to save policy", err, lager.Data{"policy": policyStr}) - writeErrorResponse(w, http.StatusInternalServerError, "Error attaching the default policy") - return - } - - logger.Info("creating/updating schedules", lager.Data{"policy": policyStr}) - err = h.schedulerUtil.CreateOrUpdateSchedule(r.Context(), appId, policy, policyGuidStr) - //while there is synchronization between policy and schedule, so creating schedule error does not break - //the whole creating binding process - if err != nil { - logger.Error("failed to create/update schedules", err, lager.Data{"policy": policyStr}) - writeErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to update schedule:%s", err)) - } - } + if err := h.resetDefaultPolicy(w, r, logger, appId); err != nil { + return } - err = h.bindingdb.SetOrUpdateCustomMetricStrategy(r.Context(), appId, "", "delete") - if err != nil { + + if err := h.bindingdb.SetOrUpdateCustomMetricStrategy(r.Context(), appId, "", "delete"); err != nil { actionName := "failed to delete custom metric submission strategy in the database" logger.Error(actionName, err) writeErrorResponse(w, http.StatusInternalServerError, actionName) @@ -261,17 +229,75 @@ func (h *PublicApiHandler) DetachScalingPolicy(w http.ResponseWriter, r *http.Re // find via the app id the binding -> service instance // default policy? then apply that - w.WriteHeader(http.StatusOK) - _, err = w.Write([]byte("{}")) + _, err := w.Write([]byte("{}")) if err != nil { logger.Error(ActionWriteBody, err) } } -func proxyRequest(pathFn func() string, call func(url string) (*http.Response, error), w http.ResponseWriter, reqUrl *url.URL, parameters *url.Values, requestDescription string, logger lager.Logger) { - aUrl := pathFn() - resp, err := call(aUrl) +func (h *PublicApiHandler) resetDefaultPolicy(w http.ResponseWriter, r *http.Request, logger lager.Logger, appId string) error { + serviceInstance, err := h.bindingdb.GetServiceInstanceByAppId(appId) + if err != nil { + logger.Error("Failed to find service instance for app", err) + writeErrorResponse(w, http.StatusInternalServerError, "Error retrieving service instance") + return errors.New("error retrieving service instance") + } + + if serviceInstance != nil && serviceInstance.DefaultPolicy != "" { + return h.saveDefaultPolicy(w, r, logger, appId, serviceInstance) + } + + return nil +} + +func (h *PublicApiHandler) saveDefaultPolicy(w http.ResponseWriter, r *http.Request, logger lager.Logger, appId string, serviceInstance *models.ServiceInstance) error { + policyStr := serviceInstance.DefaultPolicy + policyGuidStr := serviceInstance.DefaultPolicyGuid + logger.Info("saving default policy json for app", lager.Data{"policy": policyStr}) + + var policy *models.ScalingPolicy + if err := json.Unmarshal([]byte(policyStr), &policy); err != nil { + h.logger.Error("default policy invalid", err, lager.Data{"appId": appId, "policy": policyStr}) + writeErrorResponse(w, http.StatusInternalServerError, "Default policy not valid") + return errors.New("default policy not valid") + } + + if err := h.policydb.SaveAppPolicy(r.Context(), appId, policy, policyGuidStr); err != nil { + logger.Error("failed to save policy", err, lager.Data{"policy": policyStr}) + writeErrorResponse(w, http.StatusInternalServerError, "Error attaching the default policy") + return errors.New("error attaching the default policy") + } + + logger.Info("creating/updating schedules", lager.Data{"policy": policyStr}) + if err := h.schedulerUtil.CreateOrUpdateSchedule(r.Context(), appId, policy, policyGuidStr); err != nil { + logger.Error("failed to create/update schedules", err, lager.Data{"policy": policyStr}) + writeErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to update schedule:%s", err)) + return errors.New("failed to update schedule") + } + + return nil +} + +func (h *PublicApiHandler) proxyRequest(logger lager.Logger, appId string, metricType string, w http.ResponseWriter, req *http.Request, parameters *url.Values, requestDescription string) { + reqUrl := req.URL + r := routes.NewRouter() + router := r.CreateEventGeneratorRoutes() + if router == nil { + panic("Failed to create event generator routes") + } + + route := router.Get(routes.GetAggregatedMetricHistoriesRouteName) + path, err := route.URLPath("appid", appId, "metrictype", metricType) + if err != nil { + logger.Error("Failed to create path", err) + panic(err) + } + + aUrl := h.conf.EventGenerator.EventGeneratorUrl + path.RequestURI() + "?" + parameters.Encode() + req, _ = http.NewRequest("GET", aUrl, nil) + + resp, err := h.eventGeneratorClient.Do(req) if err != nil { logger.Error("Failed to retrieve "+requestDescription, err, lager.Data{"url": aUrl}) writeErrorResponse(w, http.StatusInternalServerError, "Error retrieving "+requestDescription) @@ -291,6 +317,7 @@ func proxyRequest(pathFn func() string, call func(url string) (*http.Response, e writeErrorResponse(w, resp.StatusCode, string(responseData)) return } + paginatedResponse, err := paginateResource(responseData, parameters, reqUrl) if err != nil { handlers.WriteJSONResponse(w, http.StatusInternalServerError, err.Error()) @@ -318,22 +345,7 @@ func (h *PublicApiHandler) GetAggregatedMetricsHistories(w http.ResponseWriter, return } - pathFn := func() string { - r := routes.NewRouter() - router := r.CreateEventGeneratorRoutes() - if router == nil { - panic("Failed to create event generator routes") - } - - route := router.Get(routes.GetAggregatedMetricHistoriesRouteName) - path, err := route.URLPath("appid", appId, "metrictype", metricType) - if err != nil { - logger.Error("Failed to create path", err) - panic(err) - } - return h.conf.EventGenerator.EventGeneratorUrl + path.RequestURI() + "?" + parameters.Encode() - } - proxyRequest(pathFn, h.eventGeneratorClient.Get, w, req.URL, parameters, "metrics history from eventgenerator", logger) + h.proxyRequest(logger, appId, metricType, w, req, parameters, "metrics history from eventgenerator") } func (h *PublicApiHandler) GetApiInfo(w http.ResponseWriter, _ *http.Request, _ map[string]string) { diff --git a/src/autoscaler/api/publicapiserver/public_api_handler_test.go b/src/autoscaler/api/publicapiserver/public_api_handler_test.go index 1a57d088cf..38ed015987 100644 --- a/src/autoscaler/api/publicapiserver/public_api_handler_test.go +++ b/src/autoscaler/api/publicapiserver/public_api_handler_test.go @@ -7,6 +7,8 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" + "regexp" "strings" . "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/publicapiserver" @@ -17,9 +19,23 @@ import ( "code.cloudfoundry.org/lager/v3/lagertest" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/ghttp" ) var _ = Describe("PublicApiHandler", func() { + + var eventGeneratorHandler http.HandlerFunc + + JustBeforeEach(func() { + eventGeneratorPathMatcher, err := regexp.Compile(`/v1/apps/[A-Za-z0-9\-]+/aggregated_metric_histories/[a-zA-Z0-9_]+`) + Expect(err).NotTo(HaveOccurred()) + eventGeneratorServer.RouteToHandler(http.MethodGet, eventGeneratorPathMatcher, eventGeneratorHandler) + }) + + BeforeEach(func() { + eventGeneratorHandler = ghttp.RespondWithJSONEncodedPtr(&eventGeneratorStatus, &eventGeneratorResponse) + }) + const ( InvalidPolicyStr = `{ "instance_max_count":4, @@ -149,6 +165,7 @@ var _ = Describe("PublicApiHandler", func() { req *http.Request pathVariables map[string]string ) + BeforeEach(func() { policydb = &fakes.FakePolicyDB{} credentials = &fakes.FakeCredentials{} @@ -157,6 +174,7 @@ var _ = Describe("PublicApiHandler", func() { req = httptest.NewRequest("GET", "/v1/info", nil) pathVariables = map[string]string{} }) + JustBeforeEach(func() { handler = NewPublicApiHandler(lagertest.NewTestLogger("public_api_handler"), conf, policydb, bindingdb, credentials) }) @@ -165,7 +183,7 @@ var _ = Describe("PublicApiHandler", func() { JustBeforeEach(func() { handler.GetApiInfo(resp, req, map[string]string{}) }) - Context("When GetApiInfo is called", func() { + When("GetApiInfo is called", func() { It("gets the info json", func() { Expect(resp.Code).To(Equal(http.StatusOK)) Expect(resp.Body.Bytes()).To(Equal(infoBytes)) @@ -177,7 +195,7 @@ var _ = Describe("PublicApiHandler", func() { JustBeforeEach(func() { handler.GetHealth(resp, req, map[string]string{}) }) - Context("When GetHealth is called", func() { + When("GetHealth is called", func() { It("succeeds with 200", func() { Expect(resp.Code).To(Equal(http.StatusOK)) Expect(resp.Body.String()).To(Equal(`{"alive":"true"}`)) @@ -190,13 +208,13 @@ var _ = Describe("PublicApiHandler", func() { handler.GetScalingPolicy(resp, req, pathVariables) }) - Context("When appId is not present", func() { + When("appId is not present", func() { It("should fail with 400", func() { Expect(resp.Code).To(Equal(http.StatusBadRequest)) Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"AppId is required"}`)) }) }) - Context("When database gives error", func() { + When("database gives error", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID policydb.GetAppPolicyReturns(nil, fmt.Errorf("Failed to retrieve policy")) @@ -207,7 +225,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When policy doesn't exist", func() { + When("policy doesn't exist", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID policydb.GetAppPolicyReturns(nil, nil) @@ -218,7 +236,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When policy exist", func() { + When("policy exist", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID policydb.GetAppPolicyReturns(&models.ScalingPolicy{ @@ -299,13 +317,13 @@ var _ = Describe("PublicApiHandler", func() { handler.AttachScalingPolicy(resp, req, pathVariables) }) - Context("When appId is not present", func() { + When("appId is not present", func() { It("should fail with 400", func() { Expect(resp.Code).To(Equal(http.StatusBadRequest)) Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"AppId is required"}`)) }) }) - Context("When the policy is invalid", func() { + When("the policy is invalid", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID req, _ = http.NewRequest(http.MethodPut, "", bytes.NewBufferString(InvalidPolicyStr)) @@ -316,7 +334,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When save policy errors", func() { + When("save policy errors", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID req, _ = http.NewRequest(http.MethodPut, "", bytes.NewBufferString(ValidPolicyStr)) @@ -328,7 +346,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When scheduler returns non 200 and non 204 status code", func() { + When("scheduler returns non 200 and non 204 status code", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID req, _ = http.NewRequest(http.MethodPut, "", bytes.NewBufferString(ValidPolicyStr)) @@ -344,7 +362,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When scheduler returns 200 status code", func() { + When("scheduler returns 200 status code", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID req, _ = http.NewRequest(http.MethodPut, "", bytes.NewBufferString(ValidPolicyStr)) @@ -356,7 +374,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When providing extra fields", func() { + When("providing extra fields", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID req, _ = http.NewRequest(http.MethodPut, "", bytes.NewBufferString(ValidPolicyStrWithExtraFields)) @@ -368,7 +386,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When scheduler returns 204 status code", func() { + When("scheduler returns 204 status code", func() { BeforeEach(func() { pathVariables["appId"] = TEST_APP_ID req, _ = http.NewRequest(http.MethodPut, "", bytes.NewBufferString(ValidPolicyStr)) @@ -445,7 +463,7 @@ var _ = Describe("PublicApiHandler", func() { handler.DetachScalingPolicy(resp, req, pathVariables) }) - Context("When appId is not present", func() { + When("appId is not present", func() { BeforeEach(func() { delete(pathVariables, "appId") }) @@ -455,7 +473,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When delete policy errors", func() { + When("delete policy errors", func() { BeforeEach(func() { policydb.DeletePolicyReturns(fmt.Errorf("Failed to save policy")) }) @@ -465,7 +483,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When scheduler returns non 200 and non 204 status code", func() { + When("scheduler returns non 200 and non 204 status code", func() { BeforeEach(func() { schedulerStatus = 500 }) @@ -475,7 +493,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When scheduler returns 200 status code", func() { + When("scheduler returns 200 status code", func() { BeforeEach(func() { schedulerStatus = 200 }) @@ -483,7 +501,7 @@ var _ = Describe("PublicApiHandler", func() { Expect(resp.Code).To(Equal(http.StatusOK)) }) - Context("when the service is offered in brokered mode", func() { + When("the service is offered in brokered mode", func() { BeforeEach(func() { bindingdb = &fakes.FakeBindingDB{} }) @@ -535,7 +553,7 @@ var _ = Describe("PublicApiHandler", func() { }) }) - Context("When scheduler returns 204 status code", func() { + When("scheduler returns 204 status code", func() { BeforeEach(func() { schedulerStatus = 204 bindingdb.GetServiceInstanceByAppIdReturns(&models.ServiceInstance{}, nil) @@ -611,402 +629,408 @@ var _ = Describe("PublicApiHandler", func() { handler.GetAggregatedMetricsHistories(resp, req, pathVariables) }) - Context("When appId is not present", func() { + When("CF_INSTANCE_CERT is not set", func() { BeforeEach(func() { - pathVariables["metricType"] = TEST_METRIC_TYPE + os.Unsetenv("CF_INSTANCE_CERT") + }) - eventGeneratorStatus = http.StatusOK - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("order-direction", "desc") - params.Add("page", "1") - params.Add("results-per-page", "2") + When("appId is not present", func() { + BeforeEach(func() { + pathVariables["metricType"] = TEST_METRIC_TYPE - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should fail with 400", func() { - Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"appId is required"}`)) + eventGeneratorStatus = http.StatusOK + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("order-direction", "desc") + params.Add("page", "1") + params.Add("results-per-page", "2") + + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should fail with 400", func() { + Expect(resp.Code).To(Equal(http.StatusBadRequest)) + Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"appId is required"}`)) + }) }) - }) - Context("When metricType is not present", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID + When("metricType is not present", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("order-direction", "desc") - params.Add("page", "1") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("order-direction", "desc") + params.Add("page", "1") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should fail with 400", func() { - Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"Metrictype is required"}`)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should fail with 400", func() { + Expect(resp.Code).To(Equal(http.StatusBadRequest)) + Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"Metrictype is required"}`)) + }) }) - }) - Context("When start-time is not integer", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("start-time is not integer", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "not-integer") - params.Add("end-time", "300") - params.Add("order-direction", "desc") - params.Add("page", "1") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "not-integer") + params.Add("end-time", "300") + params.Add("order-direction", "desc") + params.Add("page", "1") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should fail with 400", func() { - Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"start-time must be an integer"}`)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should fail with 400", func() { + Expect(resp.Code).To(Equal(http.StatusBadRequest)) + Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"start-time must be an integer"}`)) + }) }) - }) - Context("When start-time is not provided", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("start-time is not provided", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("end-time", "300") - params.Add("order-direction", "desc") - params.Add("page", "1") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("end-time", "300") + params.Add("order-direction", "desc") + params.Add("page", "1") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should succeed with 200", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should succeed with 200", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + }) }) - }) - Context("When end-time is not integer", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("end-time is not integer", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "not-integer") - params.Add("order-direction", "desc") - params.Add("page", "1") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "not-integer") + params.Add("order-direction", "desc") + params.Add("page", "1") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should fail with 400", func() { - Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"end-time must be an integer"}`)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should fail with 400", func() { + Expect(resp.Code).To(Equal(http.StatusBadRequest)) + Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"end-time must be an integer"}`)) + }) }) - }) - Context("When end-time is not provided", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("end-time is not provided", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("order-direction", "desc") - params.Add("page", "1") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("order-direction", "desc") + params.Add("page", "1") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should succeed with 200", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should succeed with 200", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + }) }) - }) - Context("When order-direction is not provided", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("order-direction is not provided", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "1") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "1") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should succeed with 200", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should succeed with 200", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + }) }) - }) - Context("When order-direction is not desc or asc", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("order-direction is not desc or asc", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("order-direction", "not-asc-desc") - params.Add("page", "1") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("order-direction", "not-asc-desc") + params.Add("page", "1") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should fail with 400", func() { - Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"order-direction must be DESC or ASC"}`)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should fail with 400", func() { + Expect(resp.Code).To(Equal(http.StatusBadRequest)) + Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"order-direction must be DESC or ASC"}`)) + }) }) - }) - Context("When page is not integer", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("page is not integer", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("order-direction", "desc") - params.Add("page", "not-integer") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("order-direction", "desc") + params.Add("page", "not-integer") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should fail with 400", func() { - Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"page must be an integer"}`)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should fail with 400", func() { + Expect(resp.Code).To(Equal(http.StatusBadRequest)) + Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"page must be an integer"}`)) + }) }) - }) - Context("When page is not provided", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("page is not provided", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("order-direction", "desc") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("order-direction", "desc") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should succeed with 200", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should succeed with 200", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + }) }) - }) - Context("when results-per-page is not integer", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("results-per-page is not integer", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "1") - params.Add("order-direction", "desc") - params.Add("results-per-page", "not-integer") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "1") + params.Add("order-direction", "desc") + params.Add("results-per-page", "not-integer") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should fail with 400", func() { - Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"results-per-page must be an integer"}`)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should fail with 400", func() { + Expect(resp.Code).To(Equal(http.StatusBadRequest)) + Expect(resp.Body.String()).To(Equal(`{"code":"Bad Request","message":"results-per-page must be an integer"}`)) + }) }) - }) - Context("when results-per-page is not provided", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("results-per-page is not provided", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "1") - params.Add("order-direction", "desc") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "1") + params.Add("order-direction", "desc") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should succeed with 200", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should succeed with 200", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + }) }) - }) - Context("when scaling engine returns 500", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusInternalServerError - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("scaling engine returns 500", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusInternalServerError + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "1") - params.Add("order-direction", "desc") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "1") + params.Add("order-direction", "desc") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should succeed with 200", func() { - Expect(resp.Code).To(Equal(http.StatusInternalServerError)) + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should succeed with 200", func() { + Expect(resp.Code).To(Equal(http.StatusInternalServerError)) + }) }) - }) - Context("when getting 1st page", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("getting 1st page", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "1") - params.Add("order-direction", "desc") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "1") + params.Add("order-direction", "desc") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should get full page", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) - var result models.AppMetricResponse - err := json.Unmarshal(resp.Body.Bytes(), &result) - Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal( - models.AppMetricResponse{ - PublicApiResponseBase: models.PublicApiResponseBase{ - TotalResults: 5, - TotalPages: 3, - Page: 1, - PrevUrl: "", - NextUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=2\u0026results-per-page=2\u0026start-time=100", + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should get full page", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + var result models.AppMetricResponse + err := json.Unmarshal(resp.Body.Bytes(), &result) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal( + models.AppMetricResponse{ + PublicApiResponseBase: models.PublicApiResponseBase{ + TotalResults: 5, + TotalPages: 3, + Page: 1, + PrevUrl: "", + NextUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=2\u0026results-per-page=2\u0026start-time=100", + }, + Resources: eventGeneratorResponse[0:2], }, - Resources: eventGeneratorResponse[0:2], - }, - )) + )) + }) }) - }) - Context("when getting 2nd page", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("getting 2nd page", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "2") - params.Add("order-direction", "desc") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "2") + params.Add("order-direction", "desc") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should get full page", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) - var result models.AppMetricResponse - err := json.Unmarshal(resp.Body.Bytes(), &result) - Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal( - models.AppMetricResponse{ - PublicApiResponseBase: models.PublicApiResponseBase{ - TotalResults: 5, - TotalPages: 3, - Page: 2, - PrevUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=1\u0026results-per-page=2\u0026start-time=100", - NextUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=3\u0026results-per-page=2\u0026start-time=100", + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should get full page", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + var result models.AppMetricResponse + err := json.Unmarshal(resp.Body.Bytes(), &result) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal( + models.AppMetricResponse{ + PublicApiResponseBase: models.PublicApiResponseBase{ + TotalResults: 5, + TotalPages: 3, + Page: 2, + PrevUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=1\u0026results-per-page=2\u0026start-time=100", + NextUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=3\u0026results-per-page=2\u0026start-time=100", + }, + Resources: eventGeneratorResponse[2:4], }, - Resources: eventGeneratorResponse[2:4], - }, - )) + )) + }) }) - }) - Context("when getting 3rd page", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("getting 3rd page", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "3") - params.Add("order-direction", "desc") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "3") + params.Add("order-direction", "desc") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should get only one record", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) - var result models.AppMetricResponse - err := json.Unmarshal(resp.Body.Bytes(), &result) - Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal( - models.AppMetricResponse{ - PublicApiResponseBase: models.PublicApiResponseBase{ - TotalResults: 5, - TotalPages: 3, - Page: 3, - PrevUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=2\u0026results-per-page=2\u0026start-time=100", - NextUrl: "", + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should get only one record", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + var result models.AppMetricResponse + err := json.Unmarshal(resp.Body.Bytes(), &result) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal( + models.AppMetricResponse{ + PublicApiResponseBase: models.PublicApiResponseBase{ + TotalResults: 5, + TotalPages: 3, + Page: 3, + PrevUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=2\u0026results-per-page=2\u0026start-time=100", + NextUrl: "", + }, + Resources: eventGeneratorResponse[4:5], }, - Resources: eventGeneratorResponse[4:5], - }, - )) + )) + }) }) - }) - Context("when getting 4th page", func() { - BeforeEach(func() { - eventGeneratorStatus = http.StatusOK - pathVariables["appId"] = TEST_APP_ID - pathVariables["metricType"] = TEST_METRIC_TYPE + When("getting 4th page", func() { + BeforeEach(func() { + eventGeneratorStatus = http.StatusOK + pathVariables["appId"] = TEST_APP_ID + pathVariables["metricType"] = TEST_METRIC_TYPE - params := url.Values{} - params.Add("start-time", "100") - params.Add("end-time", "300") - params.Add("page", "4") - params.Add("order-direction", "desc") - params.Add("results-per-page", "2") + params := url.Values{} + params.Add("start-time", "100") + params.Add("end-time", "300") + params.Add("page", "4") + params.Add("order-direction", "desc") + params.Add("results-per-page", "2") - req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) - }) - It("should get no records", func() { - Expect(resp.Code).To(Equal(http.StatusOK)) - var result models.AppMetricResponse - err := json.Unmarshal(resp.Body.Bytes(), &result) - Expect(err).NotTo(HaveOccurred()) - Expect(result).To(Equal( - models.AppMetricResponse{ - PublicApiResponseBase: models.PublicApiResponseBase{ - TotalResults: 5, - TotalPages: 3, - Page: 4, - PrevUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=3\u0026results-per-page=2\u0026start-time=100", - NextUrl: "", + req = httptest.NewRequest(http.MethodGet, "/v1/apps/"+TEST_APP_ID+"/aggregated_metric_histories/"+TEST_METRIC_TYPE+"?"+params.Encode(), nil) + }) + It("should get no records", func() { + Expect(resp.Code).To(Equal(http.StatusOK)) + var result models.AppMetricResponse + err := json.Unmarshal(resp.Body.Bytes(), &result) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal( + models.AppMetricResponse{ + PublicApiResponseBase: models.PublicApiResponseBase{ + TotalResults: 5, + TotalPages: 3, + Page: 4, + PrevUrl: "/v1/apps/" + TEST_APP_ID + "/aggregated_metric_histories/test_metric?end-time=300\u0026order-direction=desc\u0026page=3\u0026results-per-page=2\u0026start-time=100", + NextUrl: "", + }, + Resources: []models.AppMetric{}, }, - Resources: []models.AppMetric{}, - }, - )) + )) + }) }) }) diff --git a/src/autoscaler/api/publicapiserver/public_api_server.go b/src/autoscaler/api/publicapiserver/public_api_server.go index 7bae65880e..71f268ec3b 100644 --- a/src/autoscaler/api/publicapiserver/public_api_server.go +++ b/src/autoscaler/api/publicapiserver/public_api_server.go @@ -75,7 +75,7 @@ func (s *PublicApiServer) CreateHealthServer() (ifrit.Runner, error) { return nil, err } - return helpers.NewHTTPServer(s.logger, s.conf.Health.ServerConfig, s.healthRouter) + return helpers.NewHTTPServer(s.logger.Session("HealthServer"), s.conf.Health.ServerConfig, s.healthRouter) } func (s *PublicApiServer) setupBrokerRouter() error { @@ -104,7 +104,7 @@ func (s *PublicApiServer) CreateCFServer() (ifrit.Runner, error) { r := s.autoscalerRouter.GetRouter() - return helpers.NewHTTPServer(s.logger, s.conf.VCAPServer, r) + return helpers.NewHTTPServer(s.logger.Session("CfServer"), s.conf.VCAPServer, r) } func (s *PublicApiServer) CreateMtlsServer() (ifrit.Runner, error) { @@ -112,7 +112,7 @@ func (s *PublicApiServer) CreateMtlsServer() (ifrit.Runner, error) { return nil, err } - return helpers.NewHTTPServer(s.logger, s.conf.Server, s.autoscalerRouter.GetRouter()) + return helpers.NewHTTPServer(s.logger.Session("MtlsServer"), s.conf.Server, s.autoscalerRouter.GetRouter()) } func (s *PublicApiServer) setupApiProtectedRoutes(pah *PublicApiHandler, scalingHistoryHandler http.Handler) { diff --git a/src/autoscaler/api/publicapiserver/publicapiserver_suite_test.go b/src/autoscaler/api/publicapiserver/publicapiserver_suite_test.go index ed0b419ecf..08f4c59c41 100644 --- a/src/autoscaler/api/publicapiserver/publicapiserver_suite_test.go +++ b/src/autoscaler/api/publicapiserver/publicapiserver_suite_test.go @@ -191,10 +191,6 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) metricsCollectorServer.RouteToHandler(http.MethodGet, metricsCollectorPathMatcher, ghttp.RespondWithJSONEncodedPtr(&metricsCollectorStatus, &metricsCollectorResponse)) - eventGeneratorPathMatcher, err := regexp.Compile(`/v1/apps/[A-Za-z0-9\-]+/aggregated_metric_histories/[a-zA-Z0-9_]+`) - Expect(err).NotTo(HaveOccurred()) - eventGeneratorServer.RouteToHandler(http.MethodGet, eventGeneratorPathMatcher, ghttp.RespondWithJSONEncodedPtr(&eventGeneratorStatus, &eventGeneratorResponse)) - schedulerPathMatcher, err := regexp.Compile(`/v1/apps/[A-Za-z0-9\-]+/schedules`) Expect(err).NotTo(HaveOccurred()) schedulerErrJson = "{}" @@ -216,6 +212,7 @@ func GetTestHandler() http.HandlerFunc { } func CheckResponse(resp *httptest.ResponseRecorder, statusCode int, errResponse models.ErrorResponse) { + GinkgoHelper() Expect(resp.Code).To(Equal(statusCode)) var errResp models.ErrorResponse err := json.NewDecoder(resp.Body).Decode(&errResp) diff --git a/src/autoscaler/build-extension-file.sh b/src/autoscaler/build-extension-file.sh index fbbf35755b..a6979851ab 100755 --- a/src/autoscaler/build-extension-file.sh +++ b/src/autoscaler/build-extension-file.sh @@ -20,10 +20,8 @@ export POSTGRES_EXTERNAL_PORT="${PR_NUMBER:-5432}" export METRICSFORWARDER_HOST="${METRICSFORWARDER_HOST:-"${DEPLOYMENT_NAME}-metricsforwarder"}" export METRICSFORWARDER_MTLS_HOST="${METRICSFORWARDER_MTLS_HOST:-"${DEPLOYMENT_NAME}-metricsforwarder-mtls"}" - export SCALINGENGINE_HOST="${SCALINGENGINE_HOST:-"${DEPLOYMENT_NAME}-cf-scalingengine"}" export EVENTGENERATOR_HOST="${EVENTGENERATOR_HOST:-"${DEPLOYMENT_NAME}-cf-eventgenerator"}" - export PUBLICAPISERVER_HOST="${PUBLICAPISERVER_HOST:-"${DEPLOYMENT_NAME}"}" export SERVICEBROKER_HOST="${SERVICEBROKER_HOST:-"${DEPLOYMENT_NAME}servicebroker"}" diff --git a/src/autoscaler/configutil/cf.go b/src/autoscaler/configutil/cf.go index 89b5ad6cdc..108f53a941 100644 --- a/src/autoscaler/configutil/cf.go +++ b/src/autoscaler/configutil/cf.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/url" - "os" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" "github.com/cloud-gov/go-cfenv" @@ -39,6 +38,7 @@ func NewVCAPConfigurationReader() (*VCAPConfiguration, error) { func (vc *VCAPConfiguration) GetPort() int { return vc.appEnv.Port } + func (vc *VCAPConfiguration) IsRunningOnCF() bool { return cfenv.IsRunningOnCF() } @@ -122,7 +122,7 @@ func (vc *VCAPConfiguration) createCertFile(service *cfenv.Service, credentialKe return fmt.Errorf("%w: %s", ErrMissingCredential, credentialKey) } fileName := fmt.Sprintf("%s.%s", credentialKey, fileSuffix) - createdFile, err := materializeServiceProperty(serviceTag, fileName, content) + createdFile, err := MaterializeContentInFile(serviceTag, fileName, content) if err != nil { return err } @@ -195,7 +195,7 @@ func (vc *VCAPConfiguration) addConnectionParam(service *cfenv.Service, dbName, content, ok := service.CredentialString(bindingKey) if ok { fileName := fmt.Sprintf("%s.%s", bindingKey, connectionParam) - createdFile, err := materializeServiceProperty(dbName, fileName, content) + createdFile, err := MaterializeContentInFile(dbName, fileName, content) if err != nil { return err } @@ -203,17 +203,3 @@ func (vc *VCAPConfiguration) addConnectionParam(service *cfenv.Service, dbName, } return nil } - -func materializeServiceProperty(serviceTag, fileName, content string) (string, error) { - dirPath := fmt.Sprintf("/tmp/%s", serviceTag) - if err := os.MkdirAll(dirPath, 0700); err != nil { - return "", err - } - - filePath := fmt.Sprintf("%s/%s", dirPath, fileName) - if err := os.WriteFile(filePath, []byte(content), 0600); err != nil { - return "", err - } - - return filePath, nil -} diff --git a/src/autoscaler/configutil/file.go b/src/autoscaler/configutil/file.go new file mode 100644 index 0000000000..5b41ab0d7d --- /dev/null +++ b/src/autoscaler/configutil/file.go @@ -0,0 +1,20 @@ +package configutil + +import ( + "fmt" + "os" +) + +func MaterializeContentInFile(folderName, fileName, content string) (string, error) { + dirPath := fmt.Sprintf("/tmp/%s", folderName) + if err := os.MkdirAll(dirPath, 0700); err != nil { + return "", err + } + + filePath := fmt.Sprintf("%s/%s", dirPath, fileName) + if err := os.WriteFile(filePath, []byte(content), 0600); err != nil { + return "", err + } + + return filePath, nil +} diff --git a/src/autoscaler/eventgenerator/generator/evaluator.go b/src/autoscaler/eventgenerator/generator/evaluator.go index 8e7ae4015b..1aaa443fa0 100644 --- a/src/autoscaler/eventgenerator/generator/evaluator.go +++ b/src/autoscaler/eventgenerator/generator/evaluator.go @@ -188,7 +188,10 @@ func (e *Evaluator) sendTriggerAlarm(trigger *models.Trigger) error { return nil } - path, err := routes.ScalingEngineRoutes().Get(routes.ScaleRouteName).URLPath("appid", trigger.AppId) + r := routes.NewRouter() + scalingEngineRouter := r.CreateScalingEngineRoutes() + + path, err := scalingEngineRouter.Get(routes.ScaleRouteName).URLPath("appid", trigger.AppId) if err != nil { return fmt.Errorf("failed to create url ScaleRouteName, %s: %w", trigger.AppId, err) } diff --git a/src/autoscaler/eventgenerator/generator/evaluator_test.go b/src/autoscaler/eventgenerator/generator/evaluator_test.go index 82354b7a6b..a9545d8809 100644 --- a/src/autoscaler/eventgenerator/generator/evaluator_test.go +++ b/src/autoscaler/eventgenerator/generator/evaluator_test.go @@ -104,7 +104,8 @@ var _ = Describe("Evaluator", func() { httpClient = cfhttp.NewClient() triggerChan = make(chan []*models.Trigger, 1) - path, err := routes.ScalingEngineRoutes().Get(routes.ScaleRouteName).URLPath("appid", testAppId) + r := routes.NewRouter() + path, err := r.CreateScalingEngineRoutes().Get(routes.ScaleRouteName).URLPath("appid", testAppId) Expect(err).NotTo(HaveOccurred()) urlPath = path.Path diff --git a/src/autoscaler/helpers/auth/xfcc_auth.go b/src/autoscaler/helpers/auth/xfcc_auth.go index 6eaaa7735a..f40055794e 100644 --- a/src/autoscaler/helpers/auth/xfcc_auth.go +++ b/src/autoscaler/helpers/auth/xfcc_auth.go @@ -1,8 +1,10 @@ package auth import ( + "crypto/sha256" "crypto/x509" "encoding/base64" + "encoding/pem" "errors" "fmt" "net/http" @@ -21,10 +23,31 @@ type XFCCAuthMiddleware interface { XFCCAuthenticationMiddleware(next http.Handler) http.Handler } +type Cert struct { + FullChainPem string + Sha256 [32]byte + Base64 string +} + +func NewCert(fullChainPem string) *Cert { + block, _ := pem.Decode([]byte(fullChainPem)) + if block == nil { + return nil + } + return &Cert{ + FullChainPem: fullChainPem, + Sha256: sha256.Sum256(block.Bytes), + Base64: base64.StdEncoding.EncodeToString(block.Bytes), + } +} + +func (c *Cert) GetXFCCHeader() string { + return fmt.Sprintf("Hash=%x;Cert=%s", c.Sha256, c.Base64) +} + type xfccAuthMiddleware struct { - logger lager.Logger - spaceGuid string - orgGuid string + logger lager.Logger + xfccAuth *models.XFCCAuth } func (m *xfccAuthMiddleware) checkAuth(r *http.Request) error { @@ -33,7 +56,13 @@ func (m *xfccAuthMiddleware) checkAuth(r *http.Request) error { return ErrXFCCHeaderNotFound } - data, err := base64.StdEncoding.DecodeString(removeQuotes(xfccHeader)) + attrs := make(map[string]string) + for _, v := range strings.Split(xfccHeader, ";") { + attr := strings.SplitN(v, "=", 2) + attrs[attr[0]] = attr[1] + } + + data, err := base64.StdEncoding.DecodeString(attrs["Cert"]) if err != nil { return fmt.Errorf("base64 parsing failed: %w", err) } @@ -43,11 +72,11 @@ func (m *xfccAuthMiddleware) checkAuth(r *http.Request) error { return fmt.Errorf("failed to parse certificate: %w", err) } - if getSpaceGuid(cert) != m.spaceGuid { + if m.getSpaceGuid(cert) != m.xfccAuth.ValidSpaceGuid { return ErrorWrongSpace } - if getOrgGuid(cert) != m.orgGuid { + if m.getOrgGuid(cert) != m.xfccAuth.ValidOrgGuid { return ErrorWrongOrg } @@ -70,17 +99,16 @@ func (m *xfccAuthMiddleware) XFCCAuthenticationMiddleware(next http.Handler) htt func NewXfccAuthMiddleware(logger lager.Logger, xfccAuth models.XFCCAuth) XFCCAuthMiddleware { return &xfccAuthMiddleware{ - logger: logger, - orgGuid: xfccAuth.ValidOrgGuid, - spaceGuid: xfccAuth.ValidSpaceGuid, + logger: logger, + xfccAuth: &xfccAuth, } } -func getSpaceGuid(cert *x509.Certificate) string { +func (m *xfccAuthMiddleware) getSpaceGuid(cert *x509.Certificate) string { var certSpaceGuid string for _, ou := range cert.Subject.OrganizationalUnit { if strings.Contains(ou, "space:") { - kv := mapFrom(ou) + kv := m.mapFrom(ou) certSpaceGuid = kv["space"] break } @@ -88,34 +116,28 @@ func getSpaceGuid(cert *x509.Certificate) string { return certSpaceGuid } -func mapFrom(input string) map[string]string { +func (m *xfccAuthMiddleware) mapFrom(input string) map[string]string { result := make(map[string]string) - r := regexp.MustCompile(`(\w+):(\w+-\w+)`) + r := regexp.MustCompile(`(\w+):((\w+-)*\w+)`) matches := r.FindAllStringSubmatch(input, -1) for _, match := range matches { result[match[1]] = match[2] } + + m.logger.Debug("parseCertOrganizationalUnit", lager.Data{"input": input, "result": result}) return result } -func getOrgGuid(cert *x509.Certificate) string { +func (m *xfccAuthMiddleware) getOrgGuid(cert *x509.Certificate) string { var certOrgGuid string for _, ou := range cert.Subject.OrganizationalUnit { - // capture from string k:v with regex if strings.Contains(ou, "org:") { - kv := mapFrom(ou) + kv := m.mapFrom(ou) certOrgGuid = kv["org"] break } } return certOrgGuid } - -func removeQuotes(xfccHeader string) string { - if xfccHeader[0] == '"' { - xfccHeader = xfccHeader[1 : len(xfccHeader)-1] - } - return xfccHeader -} diff --git a/src/autoscaler/helpers/auth/xfcc_auth_test.go b/src/autoscaler/helpers/auth/xfcc_auth_test.go index ef8bb4e756..f743f8182f 100644 --- a/src/autoscaler/helpers/auth/xfcc_auth_test.go +++ b/src/autoscaler/helpers/auth/xfcc_auth_test.go @@ -1,8 +1,6 @@ package auth_test import ( - "encoding/base64" - "encoding/pem" "net/http" "net/http/httptest" @@ -22,53 +20,44 @@ var handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var _ = Describe("XfccAuthMiddleware", func() { var ( - server *httptest.Server - resp *http.Response + //server *httptest.Server + resp *http.Response buffer *gbytes.Buffer err error xfccClientCert []byte - orgGuid string - spaceGuid string - ) + xm auth.XFCCAuthMiddleware - AfterEach(func() { - server.Close() - }) + expectedOrgGuid = "validorg" + expectedSpaceGuid = "validspace" + server *httptest.Server + ) - JustBeforeEach(func() { + BeforeEach(func() { logger := lagertest.NewTestLogger("xfcc-auth-test") buffer = logger.Buffer() - xfccAuth := models.XFCCAuth{ - ValidOrgGuid: orgGuid, - ValidSpaceGuid: spaceGuid, - } - xm := auth.NewXfccAuthMiddleware(logger, xfccAuth) + xm = auth.NewXfccAuthMiddleware(logger, models.XFCCAuth{expectedOrgGuid, expectedSpaceGuid}) - server = httptest.NewServer(xm.XFCCAuthenticationMiddleware(handler)) + server = httptest.NewUnstartedServer(xm.XFCCAuthenticationMiddleware(handler)) + + }) + JustBeforeEach(func() { + server.Start() req, err := http.NewRequest("GET", server.URL+"/some-protected-endpoint", nil) + Expect(err).NotTo(HaveOccurred()) if len(xfccClientCert) > 0 { - block, _ := pem.Decode(xfccClientCert) - Expect(err).NotTo(HaveOccurred()) - Expect(block).ShouldNot(BeNil()) - - req.Header.Add("X-Forwarded-Client-Cert", base64.StdEncoding.EncodeToString(block.Bytes)) + cert := auth.NewCert(string(xfccClientCert)) + req.Header.Add("X-Forwarded-Client-Cert", cert.GetXFCCHeader()) } - Expect(err).NotTo(HaveOccurred()) - resp, err = http.DefaultClient.Do(req) + resp, err = server.Client().Do(req) Expect(err).NotTo(HaveOccurred()) }) - BeforeEach(func() { - orgGuid = "org-guid" - spaceGuid = "space-guid" - }) - When("xfcc header is not set", func() { BeforeEach(func() { xfccClientCert = []byte{} @@ -82,7 +71,7 @@ var _ = Describe("XfccAuthMiddleware", func() { When("xfcc cert matches org and space guids", func() { BeforeEach(func() { - xfccClientCert, err = testhelpers.GenerateClientCert(orgGuid, spaceGuid) + xfccClientCert, err = testhelpers.GenerateClientCert(expectedOrgGuid, expectedSpaceGuid) Expect(err).NotTo(HaveOccurred()) }) @@ -93,7 +82,7 @@ var _ = Describe("XfccAuthMiddleware", func() { When("xfcc cert does not match org guid", func() { BeforeEach(func() { - xfccClientCert, err = testhelpers.GenerateClientCert("wrong-org-guid", spaceGuid) + xfccClientCert, err = testhelpers.GenerateClientCert("wrong-org-guid", expectedSpaceGuid) Expect(err).NotTo(HaveOccurred()) }) @@ -106,7 +95,7 @@ var _ = Describe("XfccAuthMiddleware", func() { When("xfcc cert does not match space guid", func() { BeforeEach(func() { - xfccClientCert, err = testhelpers.GenerateClientCert(orgGuid, "wrong-space-guid") + xfccClientCert, err = testhelpers.GenerateClientCert(expectedOrgGuid, "wrong-space-guid") Expect(err).NotTo(HaveOccurred()) }) diff --git a/src/autoscaler/helpers/handlers/handlers.go b/src/autoscaler/helpers/handlers/handlers.go index ae16351446..1e01f9462c 100644 --- a/src/autoscaler/helpers/handlers/handlers.go +++ b/src/autoscaler/helpers/handlers/handlers.go @@ -26,7 +26,7 @@ func WriteJSONResponse(w http.ResponseWriter, statusCode int, jsonObj interface{ if err != nil { logger.Error("marshall-json-response", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } w.Header().Set("Content-Length", strconv.Itoa(len(result))) diff --git a/src/autoscaler/helpers/httpclient.go b/src/autoscaler/helpers/httpclient.go index ea80dc1f08..7a68d1e717 100644 --- a/src/autoscaler/helpers/httpclient.go +++ b/src/autoscaler/helpers/httpclient.go @@ -1,60 +1,79 @@ package helpers import ( - "encoding/base64" + "crypto/tls" + "crypto/x509" "fmt" "net/http" "time" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/cf" "code.cloudfoundry.org/lager/v3" + "github.com/hashicorp/go-retryablehttp" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" - "code.cloudfoundry.org/cfhttp/v2" ) -type TransportWithBasicAuth struct { - Username string - Password string - Base http.RoundTripper +type TLSReloadTransport struct { + Base http.RoundTripper + logger lager.Logger + tlsCerts *models.TLSCerts + certExpiration time.Time + + HTTPClient *http.Client // Internal HTTP client. + } -func (t *TransportWithBasicAuth) base() http.RoundTripper { - if t.Base != nil { - return t.Base +func (t *TLSReloadTransport) GetCertExpiration() time.Time { + if t.certExpiration.IsZero() { + x509Cert, _ := x509.ParseCertificate(t.tlsClientConfig().Certificates[0].Certificate[0]) + t.certExpiration = x509Cert.NotAfter } - return http.DefaultTransport + return t.certExpiration } -func (t *TransportWithBasicAuth) RoundTrip(req *http.Request) (*http.Response, error) { - credentials := t.Username + ":" + t.Password - basicAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(credentials)) - req.Header.Add("Authorization", basicAuth) - return t.base().RoundTrip(req) +func (t *TLSReloadTransport) tlsClientConfig() *tls.Config { + return t.HTTPClient.Transport.(*http.Transport).TLSClientConfig } -func DefaultClientConfig() cf.ClientConfig { - return cf.ClientConfig{ - MaxIdleConnsPerHost: 200, - IdleConnectionTimeoutMs: 5 * 1000, - } +func (t *TLSReloadTransport) setTLSClientConfig(tlsConfig *tls.Config) { + t.HTTPClient.Transport.(*http.Transport).TLSClientConfig = tlsConfig } -func CreateHTTPClient(ba *models.BasicAuth, config cf.ClientConfig, logger lager.Logger) (*http.Client, error) { - client := cfhttp.NewClient( - cfhttp.WithDialTimeout(30*time.Second), - cfhttp.WithIdleConnTimeout(time.Duration(config.IdleConnectionTimeoutMs)*time.Millisecond), - cfhttp.WithMaxIdleConnsPerHost(config.MaxIdleConnsPerHost), - ) +func (t *TLSReloadTransport) reloadCert() { + tlsConfig, _ := t.tlsCerts.CreateClientConfig() + t.setTLSClientConfig(tlsConfig) + x509Cert, _ := x509.ParseCertificate(t.tlsClientConfig().Certificates[0].Certificate[0]) + t.certExpiration = x509Cert.NotAfter +} - client = cf.RetryClient(config, client, logger) - client.Transport = &TransportWithBasicAuth{ - Username: ba.Username, - Password: ba.Password, +func (t *TLSReloadTransport) certificateExpiringWithin(dur time.Duration) bool { + return time.Until(t.GetCertExpiration()) < dur +} + +func (t *TLSReloadTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // skips if no tls config to reload + if t.tlsClientConfig() == nil { + return t.Base.RoundTrip(req) + } + + // Checks for cert validity within 5m timeframe. See https://docs.cloudfoundry.org/devguide/deploy-apps/instance-identity.html + if t.certificateExpiringWithin(5 * time.Minute) { + t.logger.Debug("reloading-cert", lager.Data{"request": req}) + t.reloadCert() + } else { + t.logger.Debug("cert-not-expiring", lager.Data{"request": req}) } - return client, nil + return t.Base.RoundTrip(req) +} + +func DefaultClientConfig() cf.ClientConfig { + return cf.ClientConfig{ + MaxIdleConnsPerHost: 200, + IdleConnectionTimeoutMs: 5 * 1000, + } } func CreateHTTPSClient(tlsCerts *models.TLSCerts, config cf.ClientConfig, logger lager.Logger) (*http.Client, error) { @@ -70,5 +89,17 @@ func CreateHTTPSClient(tlsCerts *models.TLSCerts, config cf.ClientConfig, logger cfhttp.WithMaxIdleConnsPerHost(config.MaxIdleConnsPerHost), ) - return cf.RetryClient(config, client, logger), nil + retryClient := cf.RetryClient(config, client, logger) + + retryClient.Transport = &TLSReloadTransport{ + Base: retryClient.Transport, + logger: logger, + tlsCerts: tlsCerts, + + // Send wrapped HTTPClient referente to access tls configuration inside RoundTrip + // and to abract the TLSReloadTransport from the retryablehttp + HTTPClient: retryClient.Transport.(*retryablehttp.RoundTripper).Client.HTTPClient, + } + + return retryClient, nil } diff --git a/src/autoscaler/helpers/httpclient_test.go b/src/autoscaler/helpers/httpclient_test.go new file mode 100644 index 0000000000..fdac3f1e57 --- /dev/null +++ b/src/autoscaler/helpers/httpclient_test.go @@ -0,0 +1,169 @@ +package helpers_test + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "log" + "net/http" + "os" + "time" + + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" + "code.cloudfoundry.org/lager/v3/lagertest" + "github.com/hashicorp/go-retryablehttp" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/ghttp" +) + +var _ = Describe("HTTPClient", func() { + var ( + fakeServer *ghttp.Server + client *http.Client + logger *lagertest.TestLogger + err error + ) + + BeforeEach(func() { + fakeServer = ghttp.NewServer() + fakeServer.RouteToHandler("GET", "/", ghttp.RespondWith(http.StatusOK, "successful")) + }) + + Describe("CreateHTTPSClient", func() { + var ( + cfInstanceCertFile string + cfInstanceKeyFile string + cfInstanceCertContent []byte + cfInstanceKeyContent []byte + notAfter time.Time + certTmpDir string + privateKey *rsa.PrivateKey + ) + + JustBeforeEach(func() { + privateKey, err = rsa.GenerateKey(rand.Reader, 2048) + Expect(err).ToNot(HaveOccurred()) + + cfInstanceCertContent, err = testhelpers.GenerateClientCertWithPrivateKeyExpiring("org", "space", privateKey, notAfter) + certTmpDir = os.TempDir() + cfInstanceKeyContent = testhelpers.GenerateClientKeyWithPrivateKey(privateKey) + + cfInstanceCertFile, err = configutil.MaterializeContentInFile(certTmpDir, "eventgenerator.crt", string(cfInstanceCertContent)) + Expect(err).NotTo(HaveOccurred()) + + cfInstanceKeyFile, err = configutil.MaterializeContentInFile(certTmpDir, "eventgenerator.key", string(cfInstanceKeyContent)) + Expect(err).NotTo(HaveOccurred()) + + logger = lagertest.NewTestLogger("http-client-test") + + tlsCerts := &models.TLSCerts{ + KeyFile: cfInstanceKeyFile, + CertFile: cfInstanceCertFile, + CACertFile: cfInstanceCertFile, + } + + client, err = helpers.CreateHTTPSClient(tlsCerts, helpers.DefaultClientConfig(), logger) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + os.Remove(cfInstanceCertFile) + os.Remove(cfInstanceKeyFile) + }) + + When("No cert is provided", func() { + BeforeEach(func() { + notAfter = time.Now().Add(7 * time.Minute) + }) + + It("should process request ok", func() { + client.Transport.(*helpers.TLSReloadTransport).Base.(*retryablehttp.RoundTripper).Client.HTTPClient.Transport.(*http.Transport).TLSClientConfig = nil + resp, err := client.Get(fakeServer.URL()) + Expect(err).ToNot(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + }) + }) + + When("Cert cert is not within 5m of expiration", func() { + BeforeEach(func() { + notAfter = time.Now().Add(7 * time.Minute) + }) + + It("should reload the cert", func() { + Expect(client).ToNot(BeNil()) + resp, err := client.Get(fakeServer.URL()) + Expect(err).ToNot(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(logger).To(gbytes.Say("cert-not-expiring")) + }) + }) + + When("Cert cert is within 5m of expiration", func() { + var cfInstanceCertFileToRotateContent []byte + + BeforeEach(func() { + notAfter = time.Now().Add(3 * time.Minute) + }) + + It("should reload the cert", func() { + cfInstanceCertFileToRotateContent, err = testhelpers.GenerateClientCertWithPrivateKey("org", "space", privateKey) + Expect(err).ToNot(HaveOccurred()) + + By("Rotating with unexpired one") + _, err = configutil.MaterializeContentInFile(certTmpDir, "eventgenerator.crt", string(cfInstanceCertFileToRotateContent)) + Expect(err).NotTo(HaveOccurred()) + + oldCertExpiration := getCertExpirationFromClient(client) + fmt.Println(oldCertExpiration) + Expect(getCertFromClient(client)).To(Equal(string(cfInstanceCertContent))) + resp, err := client.Get(fakeServer.URL()) + Expect(err).ToNot(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(logger).To(gbytes.Say("reloading-cert")) + newCertExpiration := getCertExpirationFromClient(client) + Expect(newCertExpiration).To(BeTemporally(">", oldCertExpiration)) + Expect(getCertFromClient(client)).To(Equal(string(cfInstanceCertFileToRotateContent))) + }) + }) + }) +}) + +func getCertExpirationFromClient(client *http.Client) time.Time { + GinkgoHelper() + return client.Transport.(*helpers.TLSReloadTransport).GetCertExpiration() +} + +func getCertFromClient(client *http.Client) string { + GinkgoHelper() + cert := client.Transport.(*helpers.TLSReloadTransport).Base.(*retryablehttp.RoundTripper).Client.HTTPClient.Transport.(*http.Transport).TLSClientConfig.Certificates[0] + return getPEM(cert) +} + +func getPEM(cert tls.Certificate) string { + result := "" + + for _, certBytes := range cert.Certificate { + parsedCert, err := x509.ParseCertificate(certBytes) + if err != nil { + log.Printf("Failed to parse certificate: %v", err) + continue + } + + // Encode to PEM format + pemBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: parsedCert.Raw, + } + result += string(pem.EncodeToMemory(pemBlock)) + } + + return result +} diff --git a/src/autoscaler/integration/components_test.go b/src/autoscaler/integration/components_test.go index 760fd9fca1..85c31afe0d 100644 --- a/src/autoscaler/integration/components_test.go +++ b/src/autoscaler/integration/components_test.go @@ -33,6 +33,7 @@ const ( Scheduler = "scheduler" MetricsCollector = "metricsCollector" EventGenerator = "eventGenerator" + CfEventGenerator = "cfEventGenerator" ScalingEngine = "scalingEngine" Operator = "operator" ) @@ -326,6 +327,9 @@ func (components *Components) PrepareEventGeneratorConfig(dbUri string, port int Logging: helpers.LoggingConfig{ Level: LOGLEVEL, }, + CFServer: helpers.ServerConfig{ + Port: components.Ports[CfEventGenerator], + }, Server: egConfig.ServerConfig{ ServerConfig: helpers.ServerConfig{ Port: port, diff --git a/src/autoscaler/integration/helpers_test.go b/src/autoscaler/integration/helpers_test.go index d2a88d2174..c31aab9365 100644 --- a/src/autoscaler/integration/helpers_test.go +++ b/src/autoscaler/integration/helpers_test.go @@ -7,10 +7,9 @@ import ( "net/http" "net/url" - . "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" - "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" + . "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -33,11 +32,8 @@ type ScalingHistoryResult struct { Resources []models.AppScalingHistory `json:"resources"` } -func getAppAggregatedMetricUrl(appId string, metricType string, parameteters map[string]string, pageNo int) string { - return fmt.Sprintf("/v1/apps/%s/aggregated_metric_histories/%s?any=any&start-time=%s&end-time=%s&order-direction=%s&page=%d&results-per-page=%s", appId, metricType, parameteters["start-time"], parameteters["end-time"], parameteters["order-direction"], pageNo, parameteters["results-per-page"]) -} - func compareAppAggregatedMetricResult(o1, o2 AppAggregatedMetricResult) { + GinkgoHelper() compareUrlValues(o1.NextUrl, o2.NextUrl) compareUrlValues(o1.PrevUrl, o2.PrevUrl) o1.PrevUrl = "" @@ -58,18 +54,6 @@ func compareUrlValues(actual string, expected string) { Expect(actualQuery).To(Equal(expectedQuery)) } -func checkAggregatedMetricResult(apiServerPort int, pathVariables []string, parameters map[string]string, result AppAggregatedMetricResult) { - var actual AppAggregatedMetricResult - resp, err := getAppAggregatedMetrics(apiServerPort, pathVariables, parameters) - body := MustReadAll(resp.Body) - FailOnError(fmt.Sprintf("getAppAggregatedMetrics failed: %d-%s", resp.StatusCode, body), err) - defer func() { _ = resp.Body.Close() }() - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - err = json.Unmarshal([]byte(body), &actual) - Expect(err).NotTo(HaveOccurred()) - compareAppAggregatedMetricResult(actual, result) -} - func getScalingHistoriesUrl(appId string, parameteters map[string]string, pageNo int) string { return fmt.Sprintf("/v1/apps/%s/scaling_histories?start-time=%s&end-time=%s&order-direction=%s&page=%d&results-per-page=%s", appId, parameteters["start-time"], parameteters["end-time"], parameteters["order-direction"], pageNo, parameteters["results-per-page"]) } diff --git a/src/autoscaler/integration/integration_golangapi_eventgenerator_test.go b/src/autoscaler/integration/integration_golangapi_eventgenerator_test.go index 2c8330be41..e477363d90 100644 --- a/src/autoscaler/integration/integration_golangapi_eventgenerator_test.go +++ b/src/autoscaler/integration/integration_golangapi_eventgenerator_test.go @@ -2,345 +2,391 @@ package integration_test import ( "encoding/base64" + "encoding/json" "fmt" "net/http" + "strconv" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" - "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" + . "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) +const metricType = "memoryused" +const initInstanceCount = 2 + +type testMetrics struct { + AppId string + BindingId string + ServiceInstanceId string + OrgId string + SpaceId string + PathVariables []string +} + +func (t *testMetrics) InitializeIdentifiers() { + t.ServiceInstanceId = getRandomIdRef("serviceInstId") + t.OrgId = getRandomIdRef("orgId") + t.SpaceId = getRandomIdRef("spaceId") + t.BindingId = getRandomIdRef("bindingId") + t.AppId = getRandomIdRef("appId") + t.PathVariables = []string{t.AppId, metricType} +} + var _ = Describe("Integration_GolangApi_EventGenerator", func() { - var ( - appId string - pathVariables []string - parameters map[string]string - metric *models.AppMetric - metricType = "memoryused" - initInstanceCount = 2 - serviceInstanceId string - bindingId string - orgId string - spaceId string - ) + var t *testMetrics + var eventGeneratorConfPath string + var golangApiServerConfPath string BeforeEach(func() { - startFakeCCNOAAUAA(initInstanceCount) - httpClient = testhelpers.NewApiClient() - httpClientForPublicApi = testhelpers.NewPublicApiClient() - - eventGeneratorConfPath = components.PrepareEventGeneratorConfig(dbUrl, components.Ports[EventGenerator], fmt.Sprintf("https://127.0.0.1:%d", components.Ports[MetricsCollector]), fmt.Sprintf("https://127.0.0.1:%d", components.Ports[ScalingEngine]), aggregatorExecuteInterval, policyPollerInterval, saveInterval, evaluationManagerInterval, defaultHttpClientTimeout, tmpDir) - startEventGenerator() - golangApiServerConfPath = components.PrepareGolangApiServerConfig( - dbUrl, - components.Ports[GolangAPIServer], - components.Ports[GolangServiceBroker], - fakeCCNOAAUAA.URL(), - fmt.Sprintf("https://127.0.0.1:%d", components.Ports[Scheduler]), - fmt.Sprintf("https://127.0.0.1:%d", components.Ports[ScalingEngine]), - fmt.Sprintf("https://127.0.0.1:%d", components.Ports[EventGenerator]), - "https://127.0.0.1:8888", - tmpDir) - brokerAuth = base64.StdEncoding.EncodeToString([]byte("broker_username:broker_password")) - startGolangApiServer() - serviceInstanceId = getRandomIdRef("serviceInstId") - orgId = getRandomIdRef("orgId") - spaceId = getRandomIdRef("spaceId") - bindingId = getRandomIdRef("bindingId") - appId = getRandomIdRef("appId") - pathVariables = []string{appId, metricType} + t = &testMetrics{} + setupTestEnvironment(t) + }) + JustBeforeEach(func() { + startEventGenerator(eventGeneratorConfPath) + startGolangApiServer(golangApiServerConfPath) }) AfterEach(func() { - stopGolangApiServer() - stopEventGenerator() + tearDownTestEnvironment() }) - Describe("Get App Metrics", func() { - Context("Cloud Controller api is not available", func() { - BeforeEach(func() { - fakeCCNOAAUAA.Reset() - fakeCCNOAAUAA.AllowUnhandledRequests = true - }) - It("should error with status code 500", func() { - checkPublicAPIResponseContentWithParameters(getAppAggregatedMetrics, components.Ports[GolangAPIServer], pathVariables, parameters, http.StatusInternalServerError, map[string]interface{}{ - "code": "Internal-Server-Error", - "message": "Failed to check if user is admin", - }) - }) + When("using eventgenerator unified CF Server", func() { + JustBeforeEach(func() { + bindServiceInstance(t) + }) + BeforeEach(func() { + eventGeneratorConfPath = prepareEventGeneratorConfig() + golangApiServerConfPath = prepareGolangApiServerConfig() }) - Context("UAA api is not available", func() { + Context("Get aggregated metrics", func() { + var timestamps []int64 + BeforeEach(func() { - fakeCCNOAAUAA.Reset() - fakeCCNOAAUAA.AllowUnhandledRequests = true - fakeCCNOAAUAA.Add().Info(fakeCCNOAAUAA.URL()) - }) - It("should error with status code 500", func() { - checkPublicAPIResponseContentWithParameters(getAppAggregatedMetrics, components.Ports[GolangAPIServer], pathVariables, parameters, http.StatusInternalServerError, map[string]interface{}{ - "code": "Internal-Server-Error", - "message": "Failed to check if user is admin", - }) + timestamps = []int64{333333, 444444, 555555, 555556, 666666} + insertTestMetrics(t, timestamps...) }) - }) - Context("UAA api returns 401", func() { - BeforeEach(func() { - fakeCCNOAAUAA.Reset() - fakeCCNOAAUAA.AllowUnhandledRequests = true - fakeCCNOAAUAA.Add().Info(fakeCCNOAAUAA.URL()).Introspect(testUserScope).UserInfo(http.StatusUnauthorized, "ERR") + + It("should get the metrics", func() { + expectedResources := generateResources(t, timestamps...) + + verifyAggregatedMetrics(t, "111111", "999999", "asc", "1", "2", 5, 3, 1, 2, expectedResources[0:2]) + verifyAggregatedMetrics(t, "111111", "999999", "asc", "2", "2", 5, 3, 2, 2, expectedResources[2:4]) + verifyAggregatedMetrics(t, "111111", "999999", "asc", "3", "2", 5, 3, 3, 1, expectedResources[4:5]) + + verifyEmptyAggregatedMetrics(t, "111111", "999999", "asc", "4", "2", 5, 3, 4) }) - It("should error with status code 401", func() { - checkPublicAPIResponseContentWithParameters(getAppAggregatedMetrics, components.Ports[GolangAPIServer], pathVariables, - parameters, http.StatusUnauthorized, map[string]interface{}{ - "code": "Unauthorized", - "message": "You are not authorized to perform the requested action"}) + + It("should get the metrics in specified time scope", func() { + expectedResources := generateResources(t, timestamps...) + + verifyMetricsInTimeScope(t, "555555", "10", 3, 1, expectedResources[2:5]) + verifyMetricsInTimeScope(t, "444444", "10", 4, 1, expectedResources[1:5]) + verifyMetricsInTimeScopeWithRange(t, "444444", "555556", "10", 3, 1, expectedResources[1:4]) }) }) + }) - Context("Check permission not passed", func() { - BeforeEach(func() { - fakeCCNOAAUAA.Add().Roles(http.StatusOK) - }) - It("should error with status code 401", func() { - checkPublicAPIResponseContentWithParameters(getAppAggregatedMetrics, components.Ports[GolangAPIServer], - pathVariables, parameters, http.StatusUnauthorized, map[string]interface{}{ - "code": "Unauthorized", - "message": "You are not authorized to perform the requested action", - }) - }) + When("the using eventgenerator legacy Server", func() { + BeforeEach(func() { + eventGeneratorConfPath = prepareEventGeneratorConfig() + golangApiServerConfPath = prepareGolangApiServerConfig() }) - When("the app is bound to the service instance", func() { - BeforeEach(func() { - provisionAndBind(serviceInstanceId, orgId, spaceId, bindingId, appId, components.Ports[GolangServiceBroker], httpClientForPublicApi) + Describe("Get App Metrics", func() { + Context("Cloud Controller API is not available", func() { + JustBeforeEach(func() { + prepareFakeCCNOAAUAA() + }) + It("should return status code 500", func() { + verifyErrorResponse(t, http.StatusInternalServerError, "Failed to check if user is admin") + }) }) - Context("EventGenerator is down", func() { + Context("UAA API is not available", func() { JustBeforeEach(func() { - stopEventGenerator() + prepareFakeCCNOAAUAA() }) - It("should error with status code 500", func() { - checkPublicAPIResponseContentWithParameters(getAppAggregatedMetrics, components.Ports[GolangAPIServer], pathVariables, parameters, http.StatusInternalServerError, map[string]interface{}{ - "code": "Internal Server Error", - "message": "Error retrieving metrics history from eventgenerator", - }) + It("should return status code 500", func() { + verifyErrorResponse(t, http.StatusInternalServerError, "Failed to check if user is admin") + }) + }) + + Context("UAA API returns 401", func() { + JustBeforeEach(func() { + prepareFakeCCNOAAUAAWithUnauthorized() + }) + + XIt("should return status code 401", func() { + verifyErrorResponse(t, http.StatusUnauthorized, "You are not authorized to perform the requested action") }) }) - Context("Get aggregated metrics", func() { + Context("Check permission not passed", func() { BeforeEach(func() { - metric = &models.AppMetric{ - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - } - - metric.Timestamp = 666666 - insertAppMetric(metric) - - metric.Timestamp = 555555 - insertAppMetric(metric) - - metric.Timestamp = 555556 - insertAppMetric(metric) - - metric.Timestamp = 333333 - insertAppMetric(metric) - - metric.Timestamp = 444444 - insertAppMetric(metric) - - //add some other metric-type - metric.MetricType = models.MetricNameThroughput - metric.Unit = models.UnitNum - metric.Timestamp = 444444 - insertAppMetric(metric) - //add some other appId - metric.AppId = getRandomIdRef("metric.appId") - metric.MetricType = models.MetricNameMemoryUsed - metric.Unit = models.UnitMegaBytes - metric.Timestamp = 444444 - insertAppMetric(metric) + fakeCCNOAAUAA.Add().Roles(http.StatusOK) + }) + It("should return status code 401", func() { + verifyErrorResponse(t, http.StatusUnauthorized, "You are not authorized to perform the requested action") }) - It("should get the metrics ", func() { - By("get the 1st page") - parameters = map[string]string{"start-time": "111111", "end-time": "999999", "order-direction": "asc", "page": "1", "results-per-page": "2"} - result := AppAggregatedMetricResult{ - TotalResults: 5, - TotalPages: 3, - Page: 1, - NextUrl: getAppAggregatedMetricUrl(appId, metricType, parameters, 2), - Resources: []models.AppMetric{ - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 333333, - }, - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 444444, - }, - }, - } - checkAggregatedMetricResult(components.Ports[GolangAPIServer], pathVariables, parameters, result) - - By("get the 2nd page") - parameters = map[string]string{"start-time": "111111", "end-time": "999999", "order-direction": "asc", "page": "2", "results-per-page": "2"} - result = AppAggregatedMetricResult{ - TotalResults: 5, - TotalPages: 3, - Page: 2, - PrevUrl: getAppAggregatedMetricUrl(appId, metricType, parameters, 1), - NextUrl: getAppAggregatedMetricUrl(appId, metricType, parameters, 3), - Resources: []models.AppMetric{ - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 555555, - }, - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 555556, - }, - }, - } - checkAggregatedMetricResult(components.Ports[GolangAPIServer], pathVariables, parameters, result) - - By("get the 3rd page") - parameters = map[string]string{"start-time": "111111", "end-time": "999999", "order-direction": "asc", "page": "3", "results-per-page": "2"} - result = AppAggregatedMetricResult{ - TotalResults: 5, - TotalPages: 3, - Page: 3, - PrevUrl: getAppAggregatedMetricUrl(appId, metricType, parameters, 2), - Resources: []models.AppMetric{ - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 666666, - }, - }, - } - checkAggregatedMetricResult(components.Ports[GolangAPIServer], pathVariables, parameters, result) - - By("the 4th page should be empty") - parameters = map[string]string{"start-time": "111111", "end-time": "999999", "order-direction": "asc", "page": "4", "results-per-page": "2"} - result = AppAggregatedMetricResult{ - TotalResults: 5, - TotalPages: 3, - Page: 4, - PrevUrl: getAppAggregatedMetricUrl(appId, metricType, parameters, 3), - Resources: []models.AppMetric{}, - } - checkAggregatedMetricResult(components.Ports[GolangAPIServer], pathVariables, parameters, result) + }) + + When("the app is bound to the service instance", func() { + JustBeforeEach(func() { + bindServiceInstance(t) }) - It("should get the metrics in specified time scope", func() { - By("get the results from 555555") - parameters = map[string]string{"start-time": "555555", "order-direction": "asc", "page": "1", "results-per-page": "10"} - result := AppAggregatedMetricResult{ - TotalResults: 3, - TotalPages: 1, - Page: 1, - Resources: []models.AppMetric{ - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 555555, - }, - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 555556, - }, - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 666666, - }, - }, - } - checkAggregatedMetricResult(components.Ports[GolangAPIServer], pathVariables, parameters, result) - - By("get the results to 444444") - parameters = map[string]string{"end-time": "444444", "order-direction": "asc", "page": "1", "results-per-page": "10"} - result = AppAggregatedMetricResult{ - TotalResults: 2, - TotalPages: 1, - Page: 1, - Resources: []models.AppMetric{ - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 333333, - }, - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 444444, - }, - }, - } - checkAggregatedMetricResult(components.Ports[GolangAPIServer], pathVariables, parameters, result) - - By("get the results from 444444 to 555556") - parameters = map[string]string{"start-time": "444444", "end-time": "555556", "order-direction": "asc", "page": "1", "results-per-page": "10"} - result = AppAggregatedMetricResult{ - TotalResults: 3, - TotalPages: 1, - Page: 1, - Resources: []models.AppMetric{ - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 444444, - }, - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 555555, - }, - { - AppId: appId, - MetricType: models.MetricNameMemoryUsed, - Unit: models.UnitMegaBytes, - Value: "123456", - Timestamp: 555556, - }, - }, - } - checkAggregatedMetricResult(components.Ports[GolangAPIServer], pathVariables, parameters, result) + + Context("EventGenerator is down", func() { + JustBeforeEach(func() { + stopEventGenerator() + }) + + It("should return status code 500", func() { + verifyErrorResponse(t, http.StatusInternalServerError, "Error retrieving metrics history from eventgenerator") + }) + }) + + Context("Get aggregated metrics", func() { + var timestamps []int64 + + BeforeEach(func() { + timestamps = []int64{333333, 444444, 555555, 555556, 666666} + insertTestMetrics(t, timestamps...) + }) + + It("should get the metrics", func() { + expectedResources := generateResources(t, timestamps...) + + verifyAggregatedMetrics(t, "111111", "999999", "asc", "1", "2", 5, 3, 1, 2, expectedResources[0:2]) + verifyAggregatedMetrics(t, "111111", "999999", "asc", "2", "2", 5, 3, 2, 2, expectedResources[2:4]) + verifyAggregatedMetrics(t, "111111", "999999", "asc", "3", "2", 5, 3, 3, 1, expectedResources[4:5]) + + verifyEmptyAggregatedMetrics(t, "111111", "999999", "asc", "4", "2", 5, 3, 4) + }) + + It("should get the metrics in specified time scope", func() { + expectedResources := generateResources(t, timestamps...) + + verifyMetricsInTimeScope(t, "555555", "10", 3, 1, expectedResources[2:5]) + verifyMetricsInTimeScope(t, "444444", "10", 4, 1, expectedResources[1:5]) + verifyMetricsInTimeScopeWithRange(t, "444444", "555556", "10", 3, 1, expectedResources[1:4]) + }) }) }) }) + }) }) + +func checkAggregatedMetricResult(apiServerPort int, pathVariables []string, parameters map[string]string, result AppAggregatedMetricResult) { + //GinkgoHelper() + var actual AppAggregatedMetricResult + resp, err := getAppAggregatedMetrics(apiServerPort, pathVariables, parameters) + body := MustReadAll(resp.Body) + + FailOnError(fmt.Sprintf("getAppAggregatedMetrics failed: %d-%s", resp.StatusCode, body), err) + defer func() { _ = resp.Body.Close() }() + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + err = json.Unmarshal([]byte(body), &actual) + Expect(err).NotTo(HaveOccurred()) + compareAppAggregatedMetricResult(actual, result) +} + +func setupTestEnvironment(t *testMetrics) { + GinkgoHelper() + startFakeCCNOAAUAA(initInstanceCount) + httpClient = NewApiClient() + httpClientForPublicApi = NewPublicApiClient() + t.InitializeIdentifiers() +} + +func tearDownTestEnvironment() { + stopGolangApiServer() + stopEventGenerator() +} + +func prepareFakeCCNOAAUAA() { + fakeCCNOAAUAA.Reset() + fakeCCNOAAUAA.AllowUnhandledRequests = true +} + +func prepareFakeCCNOAAUAAWithUnauthorized() { + fakeCCNOAAUAA.Reset() + fakeCCNOAAUAA.AllowUnhandledRequests = true +} + +func bindServiceInstance(t *testMetrics) { + GinkgoHelper() + provisionAndBind(t.ServiceInstanceId, t.OrgId, t.SpaceId, t.BindingId, t.AppId, components.Ports[GolangServiceBroker], httpClientForPublicApi) +} + +func insertTestMetrics(t *testMetrics, timestamps ...int64) { + metric := &models.AppMetric{ + AppId: t.AppId, + MetricType: models.MetricNameMemoryUsed, + Unit: models.UnitMegaBytes, + Value: "123456", + } + for _, timestamp := range timestamps { + metric.Timestamp = timestamp + insertAppMetric(metric) + } + metric.MetricType = models.MetricNameThroughput + metric.Unit = models.UnitNum + metric.Timestamp = 444444 + insertAppMetric(metric) + metric.AppId = getRandomIdRef("metric.appId") + metric.MetricType = models.MetricNameMemoryUsed + metric.Unit = models.UnitMegaBytes + metric.Timestamp = 444444 + insertAppMetric(metric) +} + +func verifyErrorResponse(t *testMetrics, expectedStatus int, expectedMessage string) { + GinkgoHelper() + var expectedCodeMessage string + + switch expectedStatus { + case http.StatusUnauthorized: + expectedCodeMessage = "Unauthorized" + + case http.StatusInternalServerError: + expectedCodeMessage = http.StatusText(expectedStatus) + } + + parameters := map[string]string{} + checkPublicAPIResponseContentWithParameters(getAppAggregatedMetrics, components.Ports[GolangAPIServer], t.PathVariables, parameters, expectedStatus, map[string]interface{}{ + "code": expectedCodeMessage, + "message": expectedMessage, + }) +} + +func verifyAggregatedMetrics(t *testMetrics, startTime, endTime, orderDirection, page, resultsPerPage string, totalResults, totalPages, pageNum, resourcesCount int, expectedResources []models.AppMetric) { + //GinkgoHelper() + + parameters := map[string]string{"start-time": startTime, "end-time": endTime, "order-direction": orderDirection, "page": page, "results-per-page": resultsPerPage} + result := AppAggregatedMetricResult{ + TotalResults: totalResults, + TotalPages: totalPages, + Page: pageNum, + Resources: expectedResources, + } + if pageNum > 1 { + result.PrevUrl = getAppAggregatedMetricPrevUrl(t.AppId, metricType, parameters) + } + + if pageNum != totalPages { + result.NextUrl = getAppAggregatedMetricNextUrl(t.AppId, metricType, parameters) + } + + checkAggregatedMetricResult(components.Ports[GolangAPIServer], t.PathVariables, parameters, result) +} + +func verifyEmptyAggregatedMetrics(t *testMetrics, startTime, endTime, orderDirection, page, resultsPerPage string, totalResults, totalPages, pageNum int) { + GinkgoHelper() + + parameters := map[string]string{"start-time": startTime, "end-time": endTime, "order-direction": orderDirection, "page": page, "results-per-page": resultsPerPage} + result := AppAggregatedMetricResult{ + TotalResults: totalResults, + TotalPages: totalPages, + Page: pageNum, + PrevUrl: getAppAggregatedMetricPrevUrl(t.AppId, metricType, parameters), + Resources: []models.AppMetric{}, + } + + checkAggregatedMetricResult(components.Ports[GolangAPIServer], t.PathVariables, parameters, result) +} + +func verifyMetricsInTimeScope(t *testMetrics, startTime, resultsPerPage string, totalResults, totalPages int, expectedResources []models.AppMetric) { + GinkgoHelper() + + parameters := map[string]string{"start-time": startTime, "order-direction": "asc", "page": "1", "results-per-page": resultsPerPage} + result := AppAggregatedMetricResult{ + + TotalResults: totalResults, + TotalPages: totalPages, + Page: 1, + Resources: expectedResources, + } + checkAggregatedMetricResult(components.Ports[GolangAPIServer], t.PathVariables, parameters, result) +} + +func verifyMetricsInTimeScopeWithRange(t *testMetrics, startTime, endTime, resultsPerPage string, totalResults, totalPages int, expectedResources []models.AppMetric) { + GinkgoHelper() + parameters := map[string]string{"start-time": startTime, "end-time": endTime, "order-direction": "asc", "page": "1", "results-per-page": resultsPerPage} + result := AppAggregatedMetricResult{ + TotalResults: totalResults, + TotalPages: totalPages, + Page: 1, + Resources: expectedResources, + } + checkAggregatedMetricResult(components.Ports[GolangAPIServer], t.PathVariables, parameters, result) +} + +func generateResources(t *testMetrics, timestamps ...int64) []models.AppMetric { + count := len(timestamps) + resources := make([]models.AppMetric, count) + for i, timestamp := range timestamps { + resources[i] = models.AppMetric{ + AppId: t.AppId, + MetricType: models.MetricNameMemoryUsed, + Unit: models.UnitMegaBytes, + Value: "123456", + Timestamp: timestamp, + } + } + + return resources +} + +func prepareEventGeneratorConfig() string { + return components.PrepareEventGeneratorConfig(dbUrl, + components.Ports[EventGenerator], + fmt.Sprintf("https://127.0.0.1:%d", components.Ports[MetricsCollector]), + fmt.Sprintf("https://127.0.0.1:%d", components.Ports[ScalingEngine]), + aggregatorExecuteInterval, policyPollerInterval, + saveInterval, evaluationManagerInterval, defaultHttpClientTimeout, + tmpDir) +} + +func prepareGolangApiServerConfig() string { + golangApiServerConfPath := components.PrepareGolangApiServerConfig( + dbUrl, + components.Ports[GolangAPIServer], + components.Ports[GolangServiceBroker], + fakeCCNOAAUAA.URL(), + fmt.Sprintf("https://127.0.0.1:%d", components.Ports[Scheduler]), + fmt.Sprintf("https://127.0.0.1:%d", components.Ports[ScalingEngine]), + fmt.Sprintf("https://127.0.0.1:%d", components.Ports[EventGenerator]), + "https://127.0.0.1:8888", + tmpDir) + + brokerAuth = base64.StdEncoding.EncodeToString([]byte("broker_username:broker_password")) + + return golangApiServerConfPath +} + +func getAppAggregatedMetricNextUrl(appId string, metricType string, params map[string]string) string { + currentPage, err := strconv.Atoi(params["page"]) + Expect(err).NotTo(HaveOccurred()) + page := strconv.Itoa(currentPage + 1) + + return getAppAggregatedMetricUrl(appId, metricType, params, page) +} + +func getAppAggregatedMetricPrevUrl(appId string, metricType string, params map[string]string) string { + currentPage, err := strconv.Atoi(params["page"]) + Expect(err).NotTo(HaveOccurred()) + page := strconv.Itoa(currentPage - 1) + + return getAppAggregatedMetricUrl(appId, metricType, params, page) +} + +func getAppAggregatedMetricUrl(appId string, metricType string, params map[string]string, page string) string { + return fmt.Sprintf("/v1/apps/%s/aggregated_metric_histories/%s?any=any&end-time=%s&order-direction=%s&page=%s&results-per-page=%s&start-time=%s", appId, metricType, params["end-time"], params["order-direction"], page, params["results-per-page"], params["start-time"]) +} diff --git a/src/autoscaler/integration/integration_golangapi_scalingengine_test.go b/src/autoscaler/integration/integration_golangapi_scalingengine_test.go index 1d0371d0ba..859a44a020 100644 --- a/src/autoscaler/integration/integration_golangapi_scalingengine_test.go +++ b/src/autoscaler/integration/integration_golangapi_scalingengine_test.go @@ -31,7 +31,7 @@ var _ = Describe("Integration_GolangApi_ScalingEngine", func() { scalingEngineConfPath = components.PrepareScalingEngineConfig(dbUrl, components.Ports[ScalingEngine], fakeCCNOAAUAA.URL(), defaultHttpClientTimeout, tmpDir) startScalingEngine() - golangApiServerConfPath = components.PrepareGolangApiServerConfig( + golangApiServerConfPath := components.PrepareGolangApiServerConfig( dbUrl, components.Ports[GolangAPIServer], components.Ports[GolangServiceBroker], @@ -42,7 +42,7 @@ var _ = Describe("Integration_GolangApi_ScalingEngine", func() { "https://127.0.0.1:8888", tmpDir) brokerAuth = base64.StdEncoding.EncodeToString([]byte("broker_username:broker_password")) - startGolangApiServer() + startGolangApiServer(golangApiServerConfPath) serviceInstanceId = getRandomIdRef("serviceInstId") orgId = getRandomIdRef("orgId") spaceId = getRandomIdRef("spaceId") @@ -66,7 +66,7 @@ var _ = Describe("Integration_GolangApi_ScalingEngine", func() { }) It("should error with status code 500", func() { checkPublicAPIResponseContentWithParameters(getScalingHistories, components.Ports[GolangAPIServer], pathVariables, parameters, http.StatusInternalServerError, map[string]interface{}{ - "code": "Internal-Server-Error", + "code": http.StatusText(http.StatusInternalServerError), "message": "Failed to check if user is admin", }) }) @@ -81,7 +81,7 @@ var _ = Describe("Integration_GolangApi_ScalingEngine", func() { }) It("should error with status code 500", func() { checkPublicAPIResponseContentWithParameters(getScalingHistories, components.Ports[GolangAPIServer], pathVariables, parameters, http.StatusInternalServerError, map[string]interface{}{ - "code": "Internal-Server-Error", + "code": http.StatusText(http.StatusInternalServerError), "message": "Failed to check if user is admin", }) }) diff --git a/src/autoscaler/integration/integration_golangapi_scheduler_test.go b/src/autoscaler/integration/integration_golangapi_scheduler_test.go index 6ad46b6d9d..c8856638f2 100644 --- a/src/autoscaler/integration/integration_golangapi_scheduler_test.go +++ b/src/autoscaler/integration/integration_golangapi_scheduler_test.go @@ -54,7 +54,7 @@ var _ = Describe("Integration_GolangApi_Scheduler", func() { Describe("When offered as a service", func() { BeforeEach(func() { - golangApiServerConfPath = components.PrepareGolangApiServerConfig( + golangApiServerConfPath := components.PrepareGolangApiServerConfig( dbUrl, components.Ports[GolangAPIServer], components.Ports[GolangServiceBroker], @@ -64,7 +64,7 @@ var _ = Describe("Integration_GolangApi_Scheduler", func() { fmt.Sprintf("https://127.0.0.1:%d", components.Ports[EventGenerator]), "https://127.0.0.1:8888", tmpDir) - startGolangApiServer() + startGolangApiServer(golangApiServerConfPath) resp, err := detachPolicy(appId, components.Ports[GolangAPIServer], httpClientForPublicApi) Expect(err).NotTo(HaveOccurred()) diff --git a/src/autoscaler/integration/integration_logcache_eventgenerator_scalingengine_test.go b/src/autoscaler/integration/integration_logcache_eventgenerator_scalingengine_test.go index 19dd87cbdc..10cb7a609d 100644 --- a/src/autoscaler/integration/integration_logcache_eventgenerator_scalingengine_test.go +++ b/src/autoscaler/integration/integration_logcache_eventgenerator_scalingengine_test.go @@ -25,10 +25,10 @@ var _ = Describe("Integration_Eventgenerator_Scalingengine", func() { }) JustBeforeEach(func() { - eventGeneratorConfPath = components.PrepareEventGeneratorConfig(dbUrl, components.Ports[EventGenerator], mockLogCache.URL(), fmt.Sprintf("https://127.0.0.1:%d", components.Ports[ScalingEngine]), aggregatorExecuteInterval, policyPollerInterval, saveInterval, evaluationManagerInterval, defaultHttpClientTimeout, tmpDir) + eventGeneratorConfPath := components.PrepareEventGeneratorConfig(dbUrl, components.Ports[EventGenerator], mockLogCache.URL(), fmt.Sprintf("https://127.0.0.1:%d", components.Ports[ScalingEngine]), aggregatorExecuteInterval, policyPollerInterval, saveInterval, evaluationManagerInterval, defaultHttpClientTimeout, tmpDir) scalingEngineConfPath = components.PrepareScalingEngineConfig(dbUrl, components.Ports[ScalingEngine], fakeCCNOAAUAA.URL(), defaultHttpClientTimeout, tmpDir) - startEventGenerator() + startEventGenerator(eventGeneratorConfPath) startScalingEngine() }) diff --git a/src/autoscaler/integration/integration_operator_others_test.go b/src/autoscaler/integration/integration_operator_others_test.go index 0c9f825a22..3da1bb38ad 100644 --- a/src/autoscaler/integration/integration_operator_others_test.go +++ b/src/autoscaler/integration/integration_operator_others_test.go @@ -39,7 +39,7 @@ var _ = Describe("Integration_Operator_Others", func() { startFakeCCNOAAUAA(initInstanceCount) - golangApiServerConfPath = components.PrepareGolangApiServerConfig( + golangApiServerConfPath := components.PrepareGolangApiServerConfig( dbUrl, components.Ports[GolangAPIServer], components.Ports[GolangServiceBroker], @@ -49,7 +49,7 @@ var _ = Describe("Integration_Operator_Others", func() { fmt.Sprintf("https://127.0.0.1:%d", components.Ports[EventGenerator]), "https://127.0.0.1:8888", tmpDir) - startGolangApiServer() + startGolangApiServer(golangApiServerConfPath) brokerAuth = base64.StdEncoding.EncodeToString([]byte("broker_username:broker_password")) provisionAndBind(serviceInstanceId, orgId, spaceId, bindingId, testAppId, components.Ports[GolangServiceBroker], httpClientForPublicApi) diff --git a/src/autoscaler/integration/integration_suite_test.go b/src/autoscaler/integration/integration_suite_test.go index 88f62bfaff..cea3922c38 100644 --- a/src/autoscaler/integration/integration_suite_test.go +++ b/src/autoscaler/integration/integration_suite_test.go @@ -40,21 +40,19 @@ const ( ) var ( - components Components - tmpDir string - golangApiServerConfPath string - schedulerConfPath string - eventGeneratorConfPath string - scalingEngineConfPath string - operatorConfPath string - brokerAuth string - dbUrl string - LOGLEVEL string - dbHelper *sqlx.DB - fakeCCNOAAUAA *mocks.Server - testUserScope = []string{"cloud_controller.read", "cloud_controller.write", "password.write", "openid", "network.admin", "network.write", "uaa.user"} - processMap = map[string]ifrit.Process{} - mockLogCache = &MockLogCache{} + components Components + tmpDir string + schedulerConfPath string + scalingEngineConfPath string + operatorConfPath string + brokerAuth string + dbUrl string + LOGLEVEL string + dbHelper *sqlx.DB + fakeCCNOAAUAA *mocks.Server + testUserScope = []string{"cloud_controller.read", "cloud_controller.write", "password.write", "openid", "network.admin", "network.write", "uaa.user"} + processMap = map[string]ifrit.Process{} + mockLogCache = &MockLogCache{} defaultHttpClientTimeout = 10 * time.Second @@ -157,11 +155,12 @@ func PreparePorts() Ports { Scheduler: 15000 + GinkgoParallelProcess(), MetricsCollector: 16000 + GinkgoParallelProcess(), EventGenerator: 17000 + GinkgoParallelProcess(), + CfEventGenerator: 17500 + GinkgoParallelProcess(), ScalingEngine: 18000 + GinkgoParallelProcess(), } } -func startGolangApiServer() { +func startGolangApiServer(golangApiServerConfPath string) { processMap[GolangAPIServer] = ginkgomon_v2.Invoke(grouper.NewOrdered(os.Interrupt, grouper.Members{ {GolangAPIServer, components.GolangAPIServer(golangApiServerConfPath)}, })) @@ -173,7 +172,7 @@ func startScheduler() { })) } -func startEventGenerator() { +func startEventGenerator(eventGeneratorConfPath string) { processMap[EventGenerator] = ginkgomon_v2.Invoke(grouper.NewOrdered(os.Interrupt, grouper.Members{ {EventGenerator, components.EventGenerator(eventGeneratorConfPath)}, })) diff --git a/src/autoscaler/routes/routes.go b/src/autoscaler/routes/routes.go index d193a3c50f..0b871d69a0 100644 --- a/src/autoscaler/routes/routes.go +++ b/src/autoscaler/routes/routes.go @@ -151,10 +151,6 @@ func (r *Router) CreateEventGeneratorRoutes() *mux.Router { return r.router } -func ScalingEngineRoutes() *mux.Router { - return autoScalerRouteInstance.GetRouter() -} - func MetricsForwarderRoutes() *mux.Router { return autoScalerRouteInstance.GetRouter() } diff --git a/src/autoscaler/routes/routes_test.go b/src/autoscaler/routes/routes_test.go index f5dc418da2..8661e4a051 100644 --- a/src/autoscaler/routes/routes_test.go +++ b/src/autoscaler/routes/routes_test.go @@ -24,6 +24,7 @@ var _ = Describe("Routes", func() { JustBeforeEach(func() { router = autoscalerRouter.GetRouter() }) + Describe("MetricsCollectorRoutes", func() { Context("GetMetricHistoriesRoute", func() { Context("when provide correct route variable", func() { @@ -210,7 +211,7 @@ var _ = Describe("Routes", func() { }) }) - Describe("EventGeneratorRoutes", func() { + Describe("CreateEventGeneratorRoutes", func() { JustBeforeEach(func() { autoscalerRouter.CreateEventGeneratorRoutes() }) @@ -243,11 +244,14 @@ var _ = Describe("Routes", func() { }) - Describe("ScalingEngineRoutes", func() { + Describe("CreateScalingEngineRoutes", func() { + JustBeforeEach(func() { + autoscalerRouter.CreateScalingEngineRoutes() + }) Context("ScaleRoute", func() { Context("when provide correct route variable", func() { It("should return the correct path", func() { - path, err := routes.ScalingEngineRoutes().Get(routes.ScaleRouteName).URLPath("appid", testAppId) + path, err := router.Get(routes.ScaleRouteName).URLPath("appid", testAppId) Expect(err).NotTo(HaveOccurred()) Expect(path.Path).To(Equal("/v1/apps/" + testAppId + "/scale")) }) @@ -255,7 +259,7 @@ var _ = Describe("Routes", func() { Context("when provide wrong route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.ScaleRouteName).URLPath("wrongVariable", testAppId) + _, err := router.Get(routes.ScaleRouteName).URLPath("wrongVariable", testAppId) Expect(err).To(HaveOccurred()) }) @@ -263,7 +267,7 @@ var _ = Describe("Routes", func() { Context("when provide not enough route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.ScaleRouteName).URLPath() + _, err := router.Get(routes.ScaleRouteName).URLPath() Expect(err).To(HaveOccurred()) }) @@ -273,7 +277,7 @@ var _ = Describe("Routes", func() { Context("GetScalingHistoriesRoute", func() { Context("when provide correct route variable", func() { It("should return the correct path", func() { - path, err := routes.ScalingEngineRoutes().Get(routes.GetScalingHistoriesRouteName).URLPath("guid", testAppId) + path, err := router.Get(routes.GetScalingHistoriesRouteName).URLPath("guid", testAppId) Expect(err).NotTo(HaveOccurred()) Expect(path.Path).To(Equal("/v1/apps/" + testAppId + "/scaling_histories")) }) @@ -281,7 +285,7 @@ var _ = Describe("Routes", func() { Context("when provide wrong route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.GetScalingHistoriesRouteName).URLPath("wrongVariable", testAppId) + _, err := router.Get(routes.GetScalingHistoriesRouteName).URLPath("wrongVariable", testAppId) Expect(err).To(HaveOccurred()) }) @@ -289,7 +293,7 @@ var _ = Describe("Routes", func() { Context("when provide not enough route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.GetScalingHistoriesRouteName).URLPath() + _, err := router.Get(routes.GetScalingHistoriesRouteName).URLPath() Expect(err).To(HaveOccurred()) }) @@ -299,7 +303,7 @@ var _ = Describe("Routes", func() { Context("SetActiveScheduleRoute", func() { Context("when provide correct route variable", func() { It("should return the correct path", func() { - path, err := routes.ScalingEngineRoutes().Get(routes.SetActiveScheduleRouteName).URLPath("appid", testAppId, "scheduleid", testScheduleId) + path, err := router.Get(routes.SetActiveScheduleRouteName).URLPath("appid", testAppId, "scheduleid", testScheduleId) Expect(err).NotTo(HaveOccurred()) Expect(path.Path).To(Equal("/v1/apps/" + testAppId + "/active_schedules/" + testScheduleId)) }) @@ -307,7 +311,7 @@ var _ = Describe("Routes", func() { Context("when provide wrong route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.SetActiveScheduleRouteName).URLPath("wrongVariable", testAppId) + _, err := router.Get(routes.SetActiveScheduleRouteName).URLPath("wrongVariable", testAppId) Expect(err).To(HaveOccurred()) }) @@ -315,7 +319,7 @@ var _ = Describe("Routes", func() { Context("when provide not enough route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.SetActiveScheduleRouteName).URLPath("appid", testAppId) + _, err := router.Get(routes.SetActiveScheduleRouteName).URLPath("appid", testAppId) Expect(err).To(HaveOccurred()) }) @@ -325,7 +329,7 @@ var _ = Describe("Routes", func() { Context("DeleteActiveScheduleRoute", func() { Context("when provide correct route variable", func() { It("should return the correct path", func() { - path, err := routes.ScalingEngineRoutes().Get(routes.DeleteActiveScheduleRouteName).URLPath("appid", testAppId, "scheduleid", testScheduleId) + path, err := router.Get(routes.DeleteActiveScheduleRouteName).URLPath("appid", testAppId, "scheduleid", testScheduleId) Expect(err).NotTo(HaveOccurred()) Expect(path.Path).To(Equal("/v1/apps/" + testAppId + "/active_schedules/" + testScheduleId)) }) @@ -333,7 +337,7 @@ var _ = Describe("Routes", func() { Context("when provide wrong route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.DeleteActiveScheduleRouteName).URLPath("wrongVariable", testAppId) + _, err := router.Get(routes.DeleteActiveScheduleRouteName).URLPath("wrongVariable", testAppId) Expect(err).To(HaveOccurred()) }) @@ -341,7 +345,7 @@ var _ = Describe("Routes", func() { Context("when provide not enough route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.DeleteActiveScheduleRouteName).URLPath("appid", testAppId) + _, err := router.Get(routes.DeleteActiveScheduleRouteName).URLPath("appid", testAppId) Expect(err).To(HaveOccurred()) }) @@ -351,7 +355,7 @@ var _ = Describe("Routes", func() { Context("GetActiveSchedulesRoute", func() { Context("when provide correct route variable", func() { It("should return the correct path", func() { - path, err := routes.ScalingEngineRoutes().Get(routes.GetActiveSchedulesRouteName).URLPath("appid", testAppId) + path, err := router.Get(routes.GetActiveSchedulesRouteName).URLPath("appid", testAppId) Expect(err).NotTo(HaveOccurred()) Expect(path.Path).To(Equal("/v1/apps/" + testAppId + "/active_schedules")) }) @@ -359,7 +363,7 @@ var _ = Describe("Routes", func() { Context("when provide wrong route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.GetActiveSchedulesRouteName).URLPath("wrongVariable", testAppId) + _, err := router.Get(routes.GetActiveSchedulesRouteName).URLPath("wrongVariable", testAppId) Expect(err).To(HaveOccurred()) }) @@ -367,9 +371,8 @@ var _ = Describe("Routes", func() { Context("when provide not enough route variable", func() { It("should return error", func() { - _, err := routes.ScalingEngineRoutes().Get(routes.GetActiveSchedulesRouteName).URLPath() + _, err := router.Get(routes.GetActiveSchedulesRouteName).URLPath() Expect(err).To(HaveOccurred()) - }) }) }) diff --git a/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go b/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go index 0f8f264fdc..b160d8e98f 100644 --- a/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go +++ b/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go @@ -54,7 +54,6 @@ var _ = Describe("Main", func() { }) Describe("With incorrect config", func() { - Context("with a missing config file", func() { BeforeEach(func() { runner.startCheck = "" @@ -259,9 +258,10 @@ var _ = Describe("Main", func() { }) }) }) + When("running CF server", func() { - When("running outside cf", func() { - It("/v1/liveness should return 200", func() { + Describe("GET /v1/liveness", func() { + It("should return 200", func() { cfServerURL.Path = "/v1/liveness" req, err := http.NewRequest(http.MethodGet, cfServerURL.String(), nil) diff --git a/src/autoscaler/scalingengine/server/server_test.go b/src/autoscaler/scalingengine/server/server_test.go index 70cf0fb82a..28a8cc8fb7 100644 --- a/src/autoscaler/scalingengine/server/server_test.go +++ b/src/autoscaler/scalingengine/server/server_test.go @@ -37,7 +37,7 @@ var _ = Describe("Server", func() { err error method string bodyReader io.Reader - route = routes.ScalingEngineRoutes() + route = routes.NewRouter().CreateScalingEngineRoutes() scalingEngineDB *fakes.FakeScalingEngineDB sychronizer *fakes.FakeActiveScheduleSychronizer diff --git a/src/autoscaler/testhelpers/certs.go b/src/autoscaler/testhelpers/certs.go index a1c93ac40c..f8aeaec0bf 100644 --- a/src/autoscaler/testhelpers/certs.go +++ b/src/autoscaler/testhelpers/certs.go @@ -5,56 +5,77 @@ import ( "crypto/rsa" "crypto/x509" "crypto/x509/pkix" - "encoding/base64" "encoding/pem" "fmt" "math/big" "net/http" + "time" + + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers/auth" ) -// generateClientCert generates a client certificate with the specified spaceGUID and orgGUID -// included in the organizational unit string. -func GenerateClientCert(orgGUID, spaceGUID string) ([]byte, error) { - // Generate a random serial number for the certificate - // +func GenerateClientCertWithPrivateKeyExpiring(orgGUID, spaceGUID string, privateKey *rsa.PrivateKey, notAfter time.Time) ([]byte, error) { serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, err } - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - - // Create a new X.509 certificate template template := x509.Certificate{ SerialNumber: serialNumber, + NotBefore: time.Now(), + NotAfter: notAfter, Subject: pkix.Name{ Organization: []string{"My Organization"}, OrganizationalUnit: []string{fmt.Sprintf("space:%s org:%s", spaceGUID, orgGUID)}, }, } - // Generate the certificate + + if privateKey == nil { + return nil, fmt.Errorf("private key is nil") + } + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { return nil, err } - // Encode the certificate to PEM format certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) return certPEM, nil } +func GenerateClientCertWithPrivateKey(orgGUID, spaceGUID string, privateKey *rsa.PrivateKey) ([]byte, error) { + notAfter := time.Now().AddDate(1, 0, 0) + return GenerateClientCertWithPrivateKeyExpiring(orgGUID, spaceGUID, privateKey, notAfter) +} + +func GenerateClientCert(orgGUID, spaceGUID string) ([]byte, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + return GenerateClientCertWithPrivateKey(orgGUID, spaceGUID, privateKey) +} + +func GenerateClientKeyWithPrivateKey(privateKey *rsa.PrivateKey) []byte { + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + pemBlock := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privateKeyBytes, + } + + return pem.EncodeToMemory(pemBlock) +} + func SetXFCCCertHeader(req *http.Request, orgGuid, spaceGuid string) error { xfccClientCert, err := GenerateClientCert(orgGuid, spaceGuid) if err != nil { return err } - block, _ := pem.Decode(xfccClientCert) + cert := auth.NewCert(string(xfccClientCert)) - req.Header.Add("X-Forwarded-Client-Cert", base64.StdEncoding.EncodeToString(block.Bytes)) + req.Header.Add("X-Forwarded-Client-Cert", cert.GetXFCCHeader()) return nil }