diff --git a/src/autoscaler/api/config/config.go b/src/autoscaler/api/config/config.go index fcf699f7c3..ebad2e2ea5 100644 --- a/src/autoscaler/api/config/config.go +++ b/src/autoscaler/api/config/config.go @@ -210,9 +210,16 @@ func loadVcapConfig(conf *Config, vcapReader configutil.VCAPConfigurationReader) if err := configureBindingDb(conf, vcapReader); err != nil { return err } + + configureCfInstanceCert(conf) + return nil } +func configureCfInstanceCert(conf *Config) { + conf.CfInstanceCert = os.Getenv("CF_INSTANCE_CERT") +} + 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 ccd5c67dd8..7961c74d4f 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" @@ -43,13 +44,24 @@ var _ = Describe("Config", func() { When("vcap CF_INSTANCE_CERT is set", func() { BeforeEach(func() { - mockVCAPConfigurationReader + os.Setenv("CF_INSTANCE_CERT", "cert") }) + It("sets env variable over config file", func() { Expect(err).NotTo(HaveOccurred()) - Expect(conf.CFInstanceCert).To(Equal("cert")) - } + Expect(conf.CfInstanceCert).To(Equal("cert")) + }) + }) + + When("vcap CF_INSTANCE_CERT is not set", func() { + BeforeEach(func() { + os.Unsetenv("CF_INSTANCE_CERT") + }) + It("sets env variable over config file", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(conf.CfInstanceCert).To(Equal("")) + }) }) When("vcap PORT is set to a number", func() { diff --git a/src/autoscaler/api/publicapiserver/public_api_handler.go b/src/autoscaler/api/publicapiserver/public_api_handler.go index 1b35cdb2da..f396d24361 100644 --- a/src/autoscaler/api/publicapiserver/public_api_handler.go +++ b/src/autoscaler/api/publicapiserver/public_api_handler.go @@ -17,12 +17,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 { @@ -55,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), @@ -84,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") @@ -129,15 +133,17 @@ 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 { + + //while there is synchronization between policy and schedule, so creating schedule error does not break + //the whole creating binding process + 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 @@ -151,7 +157,7 @@ func (h *PublicApiHandler) AttachScalingPolicy(w http.ResponseWriter, r *http.Re } _, err = w.Write(response) if err != nil { - logger.Error("Failed to write body", err) + h.logger.Error("Failed to write body", err) } } @@ -162,70 +168,84 @@ 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") + if err := h.handleDefaultPolicy(w, r, logger, appId); err != nil { return } - if 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)) - } - } } + // 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) } } +// 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 +func (h *PublicApiHandler) handleDefaultPolicy(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.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() @@ -242,26 +262,13 @@ func (h *PublicApiHandler) proxyRequest(logger lager.Logger, appId string, metri } aUrl := h.conf.EventGenerator.EventGeneratorUrl + path.RequestURI() + "?" + parameters.Encode() - req, err = http.NewRequest("GET", aUrl, nil) if h.conf.CfInstanceCert != "" { - certPEM := []byte(h.conf.CfInstanceCert) - - // Calculate SHA-256 hash of the certificate - hash := sha256.Sum256(certPEM) - - // URL encode the PEM certificate - encodedCert := url.QueryEscape(string(certPEM)) - - // Construct the XFCC header value - xfccHeader := fmt.Sprintf("Hash=%x;Cert=\"%s\"", hash, encodedCert) - - req.Header.Set("X-Forwarded-Client-Cert", xfccHeader) + h.setXForwardedClientCertHeader(req) } 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) @@ -281,6 +288,7 @@ func (h *PublicApiHandler) proxyRequest(logger lager.Logger, appId string, metri writeErrorResponse(w, resp.StatusCode, string(responseData)) return } + paginatedResponse, err := paginateResource(responseData, parameters, reqUrl) if err != nil { handlers.WriteJSONResponse(w, http.StatusInternalServerError, err.Error()) @@ -290,6 +298,14 @@ func (h *PublicApiHandler) proxyRequest(logger lager.Logger, appId string, metri handlers.WriteJSONResponse(w, resp.StatusCode, paginatedResponse) } +func (h *PublicApiHandler) setXForwardedClientCertHeader(req *http.Request) { + certPEM := []byte(h.conf.CfInstanceCert) + hash := sha256.Sum256(certPEM) + encodedCert := url.QueryEscape(string(certPEM)) + xfccHeader := fmt.Sprintf("Hash=%x;Cert=\"%s\"", hash, encodedCert) + req.Header.Set("X-Forwarded-Client-Cert", xfccHeader) +} + func (h *PublicApiHandler) GetAggregatedMetricsHistories(w http.ResponseWriter, req *http.Request, vars map[string]string) { appId := vars["appId"] metricType := vars["metricType"] @@ -309,7 +325,6 @@ func (h *PublicApiHandler) GetAggregatedMetricsHistories(w http.ResponseWriter, } h.proxyRequest(logger, appId, metricType, w, req, parameters, "metrics history from eventgenerator") - //proxyRequest(pathFn, h.eventGeneratorClient, w, req.URL, parameters, "metrics history from eventgenerator", logger) } func (h *PublicApiHandler) GetApiInfo(w http.ResponseWriter, _ *http.Request, _ map[string]string) {