From e49e699c4b9f2368f9e0cd883b351a8fd043c80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Fri, 24 Oct 2025 14:03:01 +0200 Subject: [PATCH 01/19] =?UTF-8?q?=F0=9F=9A=A7=20Work=20in=20progress:=20Fa?= =?UTF-8?q?ctor=20out=20binding-request-parsing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../broker/binding_request_parser/parser.go | 139 ++++++++++++++++++ src/autoscaler/api/broker/broker.go | 12 +- 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/autoscaler/api/broker/binding_request_parser/parser.go diff --git a/src/autoscaler/api/broker/binding_request_parser/parser.go b/src/autoscaler/api/broker/binding_request_parser/parser.go new file mode 100644 index 0000000000..4de15c875a --- /dev/null +++ b/src/autoscaler/api/broker/binding_request_parser/parser.go @@ -0,0 +1,139 @@ +package bindingrequestparser + +import ( + "encoding/json" + "fmt" + "net/http" + + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/policyvalidator" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" + "code.cloudfoundry.org/brokerapi/v13/domain" + "code.cloudfoundry.org/brokerapi/v13/domain/apiresponses" + "code.cloudfoundry.org/lager/v3" +) + +type BindRequestParser = interface { + Parse(instanceID string, details domain.BindDetails) (models.AppScalingConfig, error) +} + +type bindRequestParser struct { + policyValidator *policyvalidator.PolicyValidator +} + +var _ BindRequestParser = &bindRequestParser{ + policyValidator: policyvalidator.NewPolicyValidator( + "🚸 This is a dummy and never executed!", 0, 0, 0, 0, 0, 0, 0, 0, + )} + +func (brp *bindRequestParser) Parse(instanceID string, details domain.BindDetails) (models.AppScalingConfig, error) { + var scalingPolicyRaw json.RawMessage + if details.RawParameters != nil { + scalingPolicyRaw = details.RawParameters + } + + // This just gets used for legacy-reasons. The actually parsing happens in the step + // afterwards. But it still does not validate against the schema, which is done here. + _, err := brp.getPolicyFromJsonRawMessage(scalingPolicyRaw, instanceID, details.PlanID) + if err != nil { + err := fmt.Errorf("validation-error against the json-schema:\n\t%w", err) + return models.AppScalingConfig{}, err + } + + scalingPolicy, err := models.ScalingPolicyFromRawJSON(scalingPolicyRaw) + if err != nil { + err := fmt.Errorf("could not parse scaling policy from request:\n\t%w", err) + return models.AppScalingConfig{}, err + // // ⚠️ I need to be run on the receiver-side. + // return nil, apiresponses.NewFailureResponseBuilder( + // ErrInvalidConfigurations, http.StatusBadRequest, actionReadScalingPolicy). + // WithErrorKey(actionReadScalingPolicy). + // Build() + } + + // // 🚧 To-do: Check if exactly one is provided. We don't want to accept both to be present. + // requestAppGuid := details.BindResource.AppGuid + // paramsAppGuid := bindingConfig.Configuration.AppGUID + var appGUID string + if details.BindResource != nil && details.BindResource.AppGuid != "" { + appGUID = details.BindResource.AppGuid + } else if details.AppGUID != "" { + // 👎 Access to `details.AppGUID` has been deprecated, see: + // + appGUID = details.AppGUID + } else { + // 🚧 To-do: Implement feature: service-key-creation; Use appID from `bindingConfig`! + } + + if appGUID == "" { + err := errors.New("error: service must be bound to an application - service key creation is not supported") + logger.Error("check-required-app-guid", err) + return result, apiresponses.NewFailureResponseBuilder( + err, http.StatusUnprocessableEntity, "check-required-app-guid"). + WithErrorKey("RequiresApp").Build() + } + + // 💡🚧 To-do: We should fail during startup if this does not work. Because then the + // configuration of the service is corrupted. + var defaultCustomMetricsCredentialType *models.CustomMetricsBindingAuthScheme + defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme( + b.conf.DefaultCustomMetricsCredentialType) + if err != nil { + programmingError := &models.InvalidArgumentError{ + Param: "default-credential-type", + Value: b.conf.DefaultCustomMetricsCredentialType, + Msg: "error parsing default credential type", + } + logger.Error("parse-default-credential-type", programmingError, + lager.Data{ + "default-credential-type": b.conf.DefaultCustomMetricsCredentialType, + }) + return result, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, + "parse-default-credential-type") + } + // 🏚️ Subsequently we assume that this credential-type-configuration is part of the + // scaling-policy and check it accordingly. However this is legacy and not in line with the + // current terminology of “PolicyDefinition”, “ScalingPolicy”, “BindingConfig” and + // “AppScalingConfig”. + customMetricsBindingAuthScheme, err := getOrDefaultCredentialType(scalingPolicyRaw, + defaultCustomMetricsCredentialType, logger) + if err != nil { + return result, err + } + + // To-do: 🚧 Factor everything that is involved in this creation out into an own + // helper-function. Consider a function analogous to `getScalingPolicyFromRequest` that is + // defined within this file. + appScalingConfig := models.NewAppScalingConfig( + *models.NewBindingConfig(models.GUID(appGUID), customMetricsBindingAuthScheme), + *scalingPolicy) + + return models.AppScalingConfig{}, models.ErrUnimplemented +} + +func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMessage, instanceID string, planID string) (*models.ScalingPolicy, error) { + if isEmptyPolicy := len(policyJson) <= 0; isEmptyPolicy { // no nil-check needed: `len(nil) == 0` + return nil, nil + } + + policy, errResults := brp.policyValidator.ParseAndValidatePolicy(policyJson) + if errResults != nil { + // 🚫 The subsequent log-message is a strong assumption about the context of the caller. But + // how can we actually know here that we operate on a default-policy? In fact, when we are + // in the call-stack of `Bind` then we are *not* called with a default-policy. + resultsJson, err := json.Marshal(errResults) + if err != nil { + return nil, &models.InvalidArgumentError{ + Param: "errResults", + Value: errResults, + Msg: "Failed to json-marshal validation results; This should never happen."} + } + return policy, apiresponses.NewFailureResponse(fmt.Errorf("invalid policy provided: %s", string(resultsJson)), http.StatusBadRequest, "failed-to-validate-policy") + } + + return policy, nil +} + +func (brp *bindRequestParser) getScalingPolicyFromRequest( + scalingPolicyRaw json.RawMessage, logger lager.Logger, +) (*models.ScalingPolicy, error) { +} diff --git a/src/autoscaler/api/broker/broker.go b/src/autoscaler/api/broker/broker.go index bc0013978c..01f630e212 100644 --- a/src/autoscaler/api/broker/broker.go +++ b/src/autoscaler/api/broker/broker.go @@ -205,9 +205,7 @@ func (b *Broker) validateAndCheckPolicy(rawJson json.RawMessage, instanceID stri } return policy, apiresponses.NewFailureResponse(fmt.Errorf("invalid policy provided: %s", string(resultsJson)), http.StatusBadRequest, "failed-to-validate-policy") } - if err := b.planDefinitionExceeded(policy.GetPolicyDefinition(), planID, instanceID); err != nil { - return policy, err - } + return policy, nil } @@ -533,7 +531,13 @@ func (b *Broker) Bind( logger.Error("get-scaling-policy-configuration-from-request", err) return result, err } - policyGuidStr := uuid.NewString() + + // ⚠️ I need to stay here after factorising out the request-parsing! + if err := b.planDefinitionExceeded(scalingPolicy.GetPolicyDefinition(), details.PlanID, instanceID); err != nil { + return result, err + } + + policyGuidStr := uuid.NewString() // ⚠️ I need to stay here after factorising out the request-parsing! // // 🚧 To-do: Check if exactly one is provided. We don't want to accept both to be present. // requestAppGuid := details.BindResource.AppGuid From 9c4490828d3a8f6268b1be4e54347ddba9d52256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Mon, 27 Oct 2025 16:54:17 +0100 Subject: [PATCH 02/19] =?UTF-8?q?=F0=9F=93=9D=20Initial=20draft=20of=20the?= =?UTF-8?q?=20binding-request-parser.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../broker/binding_request_parser/parser.go | 176 ++++++++++++------ 1 file changed, 123 insertions(+), 53 deletions(-) diff --git a/src/autoscaler/api/broker/binding_request_parser/parser.go b/src/autoscaler/api/broker/binding_request_parser/parser.go index 4de15c875a..2cf16e7d32 100644 --- a/src/autoscaler/api/broker/binding_request_parser/parser.go +++ b/src/autoscaler/api/broker/binding_request_parser/parser.go @@ -2,6 +2,7 @@ package bindingrequestparser import ( "encoding/json" + "errors" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" "code.cloudfoundry.org/brokerapi/v13/domain" "code.cloudfoundry.org/brokerapi/v13/domain/apiresponses" - "code.cloudfoundry.org/lager/v3" ) type BindRequestParser = interface { @@ -17,7 +17,8 @@ type BindRequestParser = interface { } type bindRequestParser struct { - policyValidator *policyvalidator.PolicyValidator + policyValidator *policyvalidator.PolicyValidator + defaultCustomMetricsCredentialType models.CustomMetricsBindingAuthScheme } var _ BindRequestParser = &bindRequestParser{ @@ -25,6 +26,13 @@ var _ BindRequestParser = &bindRequestParser{ "🚸 This is a dummy and never executed!", 0, 0, 0, 0, 0, 0, 0, 0, )} +func NewBindRequestParser(policyValidator *policyvalidator.PolicyValidator, defaultCredentialType models.CustomMetricsBindingAuthScheme) BindRequestParser { + return &bindRequestParser{ + policyValidator: policyValidator, + defaultCustomMetricsCredentialType: defaultCredentialType, + } +} + func (brp *bindRequestParser) Parse(instanceID string, details domain.BindDetails) (models.AppScalingConfig, error) { var scalingPolicyRaw json.RawMessage if details.RawParameters != nil { @@ -43,71 +51,86 @@ func (brp *bindRequestParser) Parse(instanceID string, details domain.BindDetail if err != nil { err := fmt.Errorf("could not parse scaling policy from request:\n\t%w", err) return models.AppScalingConfig{}, err - // // ⚠️ I need to be run on the receiver-side. + // // 🚧 ⚠️ I need to be run on the receiver-side. // return nil, apiresponses.NewFailureResponseBuilder( // ErrInvalidConfigurations, http.StatusBadRequest, actionReadScalingPolicy). // WithErrorKey(actionReadScalingPolicy). // Build() } - // // 🚧 To-do: Check if exactly one is provided. We don't want to accept both to be present. - // requestAppGuid := details.BindResource.AppGuid - // paramsAppGuid := bindingConfig.Configuration.AppGUID - var appGUID string - if details.BindResource != nil && details.BindResource.AppGuid != "" { - appGUID = details.BindResource.AppGuid - } else if details.AppGUID != "" { - // 👎 Access to `details.AppGUID` has been deprecated, see: - // - appGUID = details.AppGUID - } else { - // 🚧 To-do: Implement feature: service-key-creation; Use appID from `bindingConfig`! - } - - if appGUID == "" { - err := errors.New("error: service must be bound to an application - service key creation is not supported") - logger.Error("check-required-app-guid", err) - return result, apiresponses.NewFailureResponseBuilder( - err, http.StatusUnprocessableEntity, "check-required-app-guid"). - WithErrorKey("RequiresApp").Build() - } - - // 💡🚧 To-do: We should fail during startup if this does not work. Because then the - // configuration of the service is corrupted. - var defaultCustomMetricsCredentialType *models.CustomMetricsBindingAuthScheme - defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme( - b.conf.DefaultCustomMetricsCredentialType) + // 🏚️ Subsequently we assume that this credential-type-configuration is part of the + // scaling-policy and check it accordingly. However this is legacy and not in line with the + // current terminology of “PolicyDefinition”, “ScalingPolicy”, “BindingConfig” and + // “AppScalingConfig”. + customMetricsBindingAuthScheme, err := brp.getOrDefaultCredentialType(scalingPolicyRaw) if err != nil { - programmingError := &models.InvalidArgumentError{ - Param: "default-credential-type", - Value: b.conf.DefaultCustomMetricsCredentialType, - Msg: "error parsing default credential type", - } - logger.Error("parse-default-credential-type", programmingError, - lager.Data{ - "default-credential-type": b.conf.DefaultCustomMetricsCredentialType, - }) - return result, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, - "parse-default-credential-type") + return models.AppScalingConfig{}, err } - // 🏚️ Subsequently we assume that this credential-type-configuration is part of the + + // 🏚️ Subsequently we assume that this app-guid is part of the // scaling-policy and check it accordingly. However this is legacy and not in line with the // current terminology of “PolicyDefinition”, “ScalingPolicy”, “BindingConfig” and // “AppScalingConfig”. - customMetricsBindingAuthScheme, err := getOrDefaultCredentialType(scalingPolicyRaw, - defaultCustomMetricsCredentialType, logger) + appGuidFromBindingConfig, err := brp.getAppGuidFromBindingConfig(scalingPolicyRaw) if err != nil { - return result, err + return models.AppScalingConfig{}, err + } + + var appGuid models.GUID + appGuidIsFromCC := details.BindResource != nil && details.BindResource.AppGuid != "" + appGuidIsFromCCDeprField := details.AppGUID != "" + appGuidIsFromBindingConfig := appGuidFromBindingConfig == "" + switch { + case (appGuidIsFromCC || appGuidIsFromCCDeprField) && appGuidIsFromBindingConfig: + msg := "error: app GUID provided in both, binding resource and binding configuration" + err := fmt.Errorf("%s:\n\tfrom binding-request: %s", msg, appGuidFromBindingConfig) + return models.AppScalingConfig{}, err + case appGuidIsFromCC: + appGuid = models.GUID(details.BindResource.AppGuid) + case appGuidIsFromCCDeprField: + // 👎 Access to `details.AppGUID` has been deprecated, see: + // + appGuid = models.GUID(details.AppGUID) + case appGuidIsFromBindingConfig: + appGuid = appGuidFromBindingConfig + default: + err := errors.New("error: service must be bound to an application; Please provide a GUID of an app!") + return models.AppScalingConfig{}, err } - // To-do: 🚧 Factor everything that is involved in this creation out into an own - // helper-function. Consider a function analogous to `getScalingPolicyFromRequest` that is - // defined within this file. + // 🚧 To-do: This should go to the service-broker. + // if appGUID == "" { + // err := errors.New("error: service must be bound to an application - service key creation is not supported") + // logger.Error("check-required-app-guid", err) + // return result, apiresponses.NewFailureResponseBuilder( + // err, http.StatusUnprocessableEntity, "check-required-app-guid"). + // WithErrorKey("RequiresApp").Build() + // } + + // // 💡🚧 To-do: We should fail during startup if this does not work. Because then the + // // configuration of the service is corrupted. + // var defaultCustomMetricsCredentialType *models.CustomMetricsBindingAuthScheme + // defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme( + // b.conf.DefaultCustomMetricsCredentialType) + // if err != nil { + // programmingError := &models.InvalidArgumentError{ + // Param: "default-credential-type", + // Value: b.conf.DefaultCustomMetricsCredentialType, + // Msg: "error parsing default credential type", + // } + // logger.Error("parse-default-credential-type", programmingError, + // lager.Data{ + // "default-credential-type": b.conf.DefaultCustomMetricsCredentialType, + // }) + // return result, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, + // "parse-default-credential-type") + // } + appScalingConfig := models.NewAppScalingConfig( - *models.NewBindingConfig(models.GUID(appGUID), customMetricsBindingAuthScheme), + *models.NewBindingConfig(appGuid, customMetricsBindingAuthScheme), *scalingPolicy) - return models.AppScalingConfig{}, models.ErrUnimplemented + return *appScalingConfig, models.ErrUnimplemented } func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMessage, instanceID string, planID string) (*models.ScalingPolicy, error) { @@ -133,7 +156,54 @@ func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMes return policy, nil } -func (brp *bindRequestParser) getScalingPolicyFromRequest( - scalingPolicyRaw json.RawMessage, logger lager.Logger, -) (*models.ScalingPolicy, error) { +func (brp *bindRequestParser) getOrDefaultCredentialType( + policyJson json.RawMessage, +) (*models.CustomMetricsBindingAuthScheme, error) { + credentialType := &brp.defaultCustomMetricsCredentialType + + if len(policyJson) > 0 { + var policy struct { + CredentialType string `json:"credential-type,omitempty"` + } + err := json.Unmarshal(policyJson, &policy) + if err != nil { + // 🚸 This can not happen because the input at this point has already been checked + // against the json-schema. + return nil, fmt.Errorf("could not parse scaling policy to get credential type: %w", err) + } + + if policy.CredentialType != "" { + parsedCredentialType, err := models.ParseCustomMetricsBindingAuthScheme(policy.CredentialType) + if err != nil { + // 🚸 This can not happen because the input at this point has already been checked + // against the json-schema. + return nil, fmt.Errorf("could not parse credential type from scaling policy: %w", err) + } + credentialType = parsedCredentialType + } + } + + return credentialType, nil +} + +func (brp *bindRequestParser) getAppGuidFromBindingConfig(policyJson json.RawMessage) (models.GUID, error) { + if len(policyJson) <= 0 { + return "", nil + } + + var policy struct { + BindingConfig struct { + AppGUID string `json:"app_guid,omitempty"` + } `json:"binding-configuration,omitempty"` + } + err := json.Unmarshal(policyJson, &policy) + if err != nil { + // 🚸 This can not happen because the input at this point has already been checked + // against the json-schema. + return "", fmt.Errorf("could not parse scaling policy to get app-guid from binding-configuration: %w", err) + } + + appGuid := models.GUID(policy.BindingConfig.AppGUID) + + return appGuid, nil } From 686143eb4fcc9891752b38dd98c481073ba5d7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 29 Oct 2025 10:14:55 +0100 Subject: [PATCH 03/19] Use the bind-request-parser in the broker; --- .../broker/binding_request_parser/parser.go | 8 +- src/autoscaler/api/broker/broker.go | 222 +++++------------- 2 files changed, 63 insertions(+), 167 deletions(-) diff --git a/src/autoscaler/api/broker/binding_request_parser/parser.go b/src/autoscaler/api/broker/binding_request_parser/parser.go index 2cf16e7d32..015faced7b 100644 --- a/src/autoscaler/api/broker/binding_request_parser/parser.go +++ b/src/autoscaler/api/broker/binding_request_parser/parser.go @@ -13,7 +13,7 @@ import ( ) type BindRequestParser = interface { - Parse(instanceID string, details domain.BindDetails) (models.AppScalingConfig, error) + Parse(details domain.BindDetails) (models.AppScalingConfig, error) } type bindRequestParser struct { @@ -33,7 +33,7 @@ func NewBindRequestParser(policyValidator *policyvalidator.PolicyValidator, defa } } -func (brp *bindRequestParser) Parse(instanceID string, details domain.BindDetails) (models.AppScalingConfig, error) { +func (brp *bindRequestParser) Parse(details domain.BindDetails) (models.AppScalingConfig, error) { var scalingPolicyRaw json.RawMessage if details.RawParameters != nil { scalingPolicyRaw = details.RawParameters @@ -41,7 +41,7 @@ func (brp *bindRequestParser) Parse(instanceID string, details domain.BindDetail // This just gets used for legacy-reasons. The actually parsing happens in the step // afterwards. But it still does not validate against the schema, which is done here. - _, err := brp.getPolicyFromJsonRawMessage(scalingPolicyRaw, instanceID, details.PlanID) + _, err := brp.getPolicyFromJsonRawMessage(scalingPolicyRaw) if err != nil { err := fmt.Errorf("validation-error against the json-schema:\n\t%w", err) return models.AppScalingConfig{}, err @@ -133,7 +133,7 @@ func (brp *bindRequestParser) Parse(instanceID string, details domain.BindDetail return *appScalingConfig, models.ErrUnimplemented } -func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMessage, instanceID string, planID string) (*models.ScalingPolicy, error) { +func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMessage) (*models.ScalingPolicy, error) { if isEmptyPolicy := len(policyJson) <= 0; isEmptyPolicy { // no nil-check needed: `len(nil) == 0` return nil, nil } diff --git a/src/autoscaler/api/broker/broker.go b/src/autoscaler/api/broker/broker.go index 01f630e212..5b9dd1678f 100644 --- a/src/autoscaler/api/broker/broker.go +++ b/src/autoscaler/api/broker/broker.go @@ -10,6 +10,7 @@ import ( "regexp" "strings" + brParser "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/broker/binding_request_parser" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/config" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/plancheck" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/policyvalidator" @@ -27,15 +28,16 @@ import ( var _ domain.ServiceBroker = &Broker{} type Broker struct { - logger lager.Logger - conf *config.Config - bindingdb db.BindingDB - policydb db.PolicyDB - policyValidator *policyvalidator.PolicyValidator - schedulerUtil *schedulerclient.Client - catalog []domain.Service - PlanChecker plancheck.PlanChecker - credentials cred_helper.Credentials + logger lager.Logger + conf *config.Config + bindingdb db.BindingDB + policydb db.PolicyDB + policyValidator *policyvalidator.PolicyValidator // 🚧 To-do: Probably not needed anymore! + bindingReqParser brParser.BindRequestParser + schedulerUtil *schedulerclient.Client + catalog []domain.Service + PlanChecker plancheck.PlanChecker + credentials cred_helper.Credentials } var ( @@ -65,26 +67,40 @@ func (e Errors) Error() string { var _ error = Errors{} func New(logger lager.Logger, conf *config.Config, bindingDb db.BindingDB, policyDb db.PolicyDB, catalog []domain.Service, credentials cred_helper.Credentials) *Broker { + 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, + ) + + defaultCustomMetricsCredentialType, err := models.ParseCustomMetricsBindingAuthScheme( + conf.DefaultCustomMetricsCredentialType) + if err != nil { + logger.Fatal("parse-default-credential-type", err, lager.Data{ + "default-credential-type": conf.DefaultCustomMetricsCredentialType, + }) + } + + bindingReqParser := brParser.NewBindRequestParser( + policyValidator, *defaultCustomMetricsCredentialType) + broker := &Broker{ - logger: logger, - conf: conf, - bindingdb: bindingDb, - policydb: policyDb, - catalog: catalog, - 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), - PlanChecker: plancheck.NewPlanChecker(conf.PlanCheck, logger), - credentials: credentials, + logger: logger, + conf: conf, + bindingdb: bindingDb, + policydb: policyDb, + catalog: catalog, + policyValidator: policyValidator, + bindingReqParser: bindingReqParser, + schedulerUtil: schedulerclient.New(conf, logger), + PlanChecker: plancheck.NewPlanChecker(conf.PlanCheck, logger), + credentials: credentials, } return broker } @@ -513,94 +529,23 @@ func (b *Broker) Bind( result := domain.Binding{} - var scalingPolicyRaw json.RawMessage - if details.RawParameters != nil { - scalingPolicyRaw = details.RawParameters - } - - // This just gets used for legacy-reasons. The actually parsing happens in the step - // afterwards. But it still does not validate against the schema, which is done here. - _, err := b.getPolicyFromJsonRawMessage(scalingPolicyRaw, instanceID, details.PlanID) + appScalingConfig, err := b.bindingReqParser.Parse(details) if err != nil { - logger.Error("get-default-policy", err) - return result, err + logger.Error("parse-binding-request", err) + return result, apiresponses.NewFailureResponse( + err, http.StatusBadRequest, "parse-binding-request") } - scalingPolicy, err := b.getScalingPolicyFromRequest(scalingPolicyRaw, logger) - if err != nil { - logger.Error("get-scaling-policy-configuration-from-request", err) + if err := b.planDefinitionExceeded(appScalingConfig.GetScalingPolicy().GetPolicyDefinition(), details.PlanID, instanceID); err != nil { return result, err } - // ⚠️ I need to stay here after factorising out the request-parsing! - if err := b.planDefinitionExceeded(scalingPolicy.GetPolicyDefinition(), details.PlanID, instanceID); err != nil { - return result, err - } - - policyGuidStr := uuid.NewString() // ⚠️ I need to stay here after factorising out the request-parsing! - - // // 🚧 To-do: Check if exactly one is provided. We don't want to accept both to be present. - // requestAppGuid := details.BindResource.AppGuid - // paramsAppGuid := bindingConfig.Configuration.AppGUID - var appGUID string - if details.BindResource != nil { - appGUID = details.BindResource.AppGuid - } else { - // 👎 Access to `details.AppGUID` has been deprecated, see: - // - appGUID = details.AppGUID - } - - // 🚧 To-do: Implement feature: service-key-creation; Use appID from `bindingConfig`! - if appGUID == "" { - err := errors.New("error: service must be bound to an application - service key creation is not supported") - logger.Error("check-required-app-guid", err) - return result, apiresponses.NewFailureResponseBuilder( - err, http.StatusUnprocessableEntity, "check-required-app-guid"). - WithErrorKey("RequiresApp").Build() - } - - // 💡🚧 To-do: We should fail during startup if this does not work. Because then the - // configuration of the service is corrupted. - var defaultCustomMetricsCredentialType *models.CustomMetricsBindingAuthScheme - defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme( - b.conf.DefaultCustomMetricsCredentialType) - if err != nil { - programmingError := &models.InvalidArgumentError{ - Param: "default-credential-type", - Value: b.conf.DefaultCustomMetricsCredentialType, - Msg: "error parsing default credential type", - } - logger.Error("parse-default-credential-type", programmingError, - lager.Data{ - "default-credential-type": b.conf.DefaultCustomMetricsCredentialType, - }) - return result, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, - "parse-default-credential-type") - } - // 🏚️ Subsequently we assume that this credential-type-configuration is part of the - // scaling-policy and check it accordingly. However this is legacy and not in line with the - // current terminology of “PolicyDefinition”, “ScalingPolicy”, “BindingConfig” and - // “AppScalingConfig”. - customMetricsBindingAuthScheme, err := getOrDefaultCredentialType(scalingPolicyRaw, - defaultCustomMetricsCredentialType, logger) - if err != nil { - return result, err - } - - // To-do: 🚧 Factor everything that is involved in this creation out into an own - // helper-function. Consider a function analogous to `getScalingPolicyFromRequest` that is - // defined within this file. - appScalingConfig := models.NewAppScalingConfig( - *models.NewBindingConfig(models.GUID(appGUID), customMetricsBindingAuthScheme), - *scalingPolicy) - - if err := b.handleExistingBindingsResiliently(ctx, instanceID, appGUID, logger); err != nil { + appGUID := appScalingConfig.GetConfiguration().GetAppGUID() + if err := b.handleExistingBindingsResiliently(ctx, instanceID, + appGUID, logger); err != nil { return result, err } err = createServiceBinding( - // First time in this function and its recursions, that `instanceID` is used for something - // different but logging: ctx, b.bindingdb, bindingID, instanceID, appScalingConfig.GetConfiguration().GetAppGUID(), appScalingConfig.GetScalingPolicy().GetCustomMetricsStrategy()) @@ -613,7 +558,7 @@ func (b *Broker) Bind( errors.New("error: an autoscaler service instance is already bound to the application and multiple bindings are not supported"), http.StatusConflict, actionCreateServiceBinding) } - if errors.Is(err, ErrInvalidCustomMetricsStrategy) { + if errors.Is(err, ErrInvalidCustomMetricsStrategy) { // 🚧 To-do: Can not happen anymore! return result, apiresponses.NewFailureResponse( err, http.StatusBadRequest, actionCreateServiceBinding) } @@ -625,14 +570,14 @@ func (b *Broker) Bind( MtlsUrl: b.conf.MetricsForwarder.MetricsForwarderMtlsUrl, } - if customMetricsBindingAuthScheme == &models.BindingSecret { + if appScalingConfig.GetConfiguration().GetCustomMetricsBindingAuth() == &models.BindingSecret { // create credentials - cred, err := b.credentials.Create(ctx, appGUID, nil) + cred, err := b.credentials.Create(ctx, string(appGUID), nil) if err != nil { //revert binding creating logger.Error("create-credentials", err) - err = b.bindingdb.DeleteServiceBindingByAppId(ctx, appGUID) + err = b.bindingdb.DeleteServiceBindingByAppId(ctx, string(appGUID)) if err != nil { logger.Error("revert-binding-creation-due-to-credentials-creation-failure", err) } @@ -642,6 +587,7 @@ func (b *Broker) Bind( customMetricsCredentials.Credential = cred } + policyGuidStr := uuid.NewString() if err := b.attachPolicyOrDefaultPolicyToApp(ctx, instanceID, appScalingConfig.GetConfiguration().GetAppGUID(), appScalingConfig.GetScalingPolicy().GetPolicyDefinition(), policyGuidStr, @@ -655,56 +601,6 @@ func (b *Broker) Bind( return result, nil } -func (b *Broker) getScalingPolicyFromRequest( - scalingPolicyRaw json.RawMessage, logger lager.Logger, -) (*models.ScalingPolicy, error) { - scalingPolicy, err := models.ScalingPolicyFromRawJSON(scalingPolicyRaw) - if err != nil { - actionReadScalingPolicy := "read-scaling-policy" - logger.Error("unmarshal-scaling-policy", err) - return nil, apiresponses.NewFailureResponseBuilder( - ErrInvalidConfigurations, http.StatusBadRequest, actionReadScalingPolicy). - WithErrorKey(actionReadScalingPolicy). - Build() - } - logger.Debug("getScalingPolicyFromRequest", lager.Data{"scalingPolicy": scalingPolicy}) - return scalingPolicy, nil -} - -func getOrDefaultCredentialType( - policyJson json.RawMessage, defaultCredentialType *models.CustomMetricsBindingAuthScheme, - logger lager.Logger, -) (*models.CustomMetricsBindingAuthScheme, error) { - credentialType := defaultCredentialType - - if len(policyJson) > 0 { - var policy struct { - CredentialType string `json:"credential-type,omitempty"` - } - err := json.Unmarshal(policyJson, &policy) - if err != nil { - logger.Error("error: unmarshal-credential-type", err) - return nil, apiresponses.NewFailureResponse(ErrCreatingServiceBinding, - http.StatusInternalServerError, "error-unmarshal-credential-type") - } - - if policy.CredentialType != "" { - parsedCredentialType, err := models.ParseCustomMetricsBindingAuthScheme(policy.CredentialType) - if err != nil { - logger.Error("error: parse-credential-type", err) - return nil, apiresponses.NewFailureResponseBuilder( - ErrInvalidCredentialType, http.StatusBadRequest, "error-parse-credential-type"). - WithErrorKey("validate-credential-type").Build() - // For backwards-compatibility we use "validate-credential-type" here. - } - credentialType = parsedCredentialType - } - } - - logger.Debug("getOrDefaultCredentialType", lager.Data{"credential-type": credentialType}) - return credentialType, nil -} - func (b *Broker) attachPolicyOrDefaultPolicyToApp( ctx context.Context, instanceID string, appGUID models.GUID, @@ -790,7 +686,7 @@ func attachPolicyToApp( return nil } -func (b *Broker) handleExistingBindingsResiliently(ctx context.Context, instanceID string, appGUID string, logger lager.Logger) error { +func (b *Broker) handleExistingBindingsResiliently(ctx context.Context, instanceID string, appGUID models.GUID, logger lager.Logger) error { // fetch and all service bindings for the service instance logger = logger.Session("handleExistingBindingsResiliently", lager.Data{"app_id": appGUID, "instance_id": instanceID}) bindingIds, err := b.bindingdb.GetBindingIdsByInstanceId(ctx, instanceID) @@ -808,7 +704,7 @@ func (b *Broker) handleExistingBindingsResiliently(ctx context.Context, instance } //select the binding-id for the appGUID - if fetchedAppID == appGUID { + if models.GUID(fetchedAppID) == appGUID { err = b.deleteBinding(ctx, existingBindingId, instanceID) if err != nil { From 72859eaf16375ce052bc74c92a4d33ef3dedb787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 29 Oct 2025 10:52:27 +0100 Subject: [PATCH 04/19] =?UTF-8?q?=F0=9F=A7=B9=F0=9F=94=A7=20Clean-up=20and?= =?UTF-8?q?=20fix=20test-case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed test-case: "[It] Supports provision of an Autoscaler Policy as RawParameters" --- .../broker/binding_request_parser/parser.go | 32 +---- src/autoscaler/api/broker/broker_test.go | 118 +++++++++--------- 2 files changed, 61 insertions(+), 89 deletions(-) diff --git a/src/autoscaler/api/broker/binding_request_parser/parser.go b/src/autoscaler/api/broker/binding_request_parser/parser.go index 015faced7b..58032ab114 100644 --- a/src/autoscaler/api/broker/binding_request_parser/parser.go +++ b/src/autoscaler/api/broker/binding_request_parser/parser.go @@ -79,7 +79,7 @@ func (brp *bindRequestParser) Parse(details domain.BindDetails) (models.AppScali var appGuid models.GUID appGuidIsFromCC := details.BindResource != nil && details.BindResource.AppGuid != "" appGuidIsFromCCDeprField := details.AppGUID != "" - appGuidIsFromBindingConfig := appGuidFromBindingConfig == "" + appGuidIsFromBindingConfig := appGuidFromBindingConfig != "" switch { case (appGuidIsFromCC || appGuidIsFromCCDeprField) && appGuidIsFromBindingConfig: msg := "error: app GUID provided in both, binding resource and binding configuration" @@ -98,39 +98,11 @@ func (brp *bindRequestParser) Parse(details domain.BindDetails) (models.AppScali return models.AppScalingConfig{}, err } - // 🚧 To-do: This should go to the service-broker. - // if appGUID == "" { - // err := errors.New("error: service must be bound to an application - service key creation is not supported") - // logger.Error("check-required-app-guid", err) - // return result, apiresponses.NewFailureResponseBuilder( - // err, http.StatusUnprocessableEntity, "check-required-app-guid"). - // WithErrorKey("RequiresApp").Build() - // } - - // // 💡🚧 To-do: We should fail during startup if this does not work. Because then the - // // configuration of the service is corrupted. - // var defaultCustomMetricsCredentialType *models.CustomMetricsBindingAuthScheme - // defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme( - // b.conf.DefaultCustomMetricsCredentialType) - // if err != nil { - // programmingError := &models.InvalidArgumentError{ - // Param: "default-credential-type", - // Value: b.conf.DefaultCustomMetricsCredentialType, - // Msg: "error parsing default credential type", - // } - // logger.Error("parse-default-credential-type", programmingError, - // lager.Data{ - // "default-credential-type": b.conf.DefaultCustomMetricsCredentialType, - // }) - // return result, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, - // "parse-default-credential-type") - // } - appScalingConfig := models.NewAppScalingConfig( *models.NewBindingConfig(appGuid, customMetricsBindingAuthScheme), *scalingPolicy) - return *appScalingConfig, models.ErrUnimplemented + return *appScalingConfig, nil } func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMessage) (*models.ScalingPolicy, error) { diff --git a/src/autoscaler/api/broker/broker_test.go b/src/autoscaler/api/broker/broker_test.go index 2aa553a785..8925ffca65 100644 --- a/src/autoscaler/api/broker/broker_test.go +++ b/src/autoscaler/api/broker/broker_test.go @@ -278,67 +278,67 @@ var _ = Describe("Broker", func() { bindingID = "some_binding-id" }) Context("Create a binding", func() { - // // 🚧 To-do: Integrate and activate this test, when finishing the service-key-feature (PR #3652). - // It("Fails when the additional config-parameter “app-guid” is provided", func() { - // // As we don't see any case where it makes sense to provide metrics by a different - // // app without using custom-metrics, we can assume that basic policy-definitions are - // // present. - // var bindingParams = []byte(` - // { - // "configuration": { - // "app-guid": "8d0cee08-23ad-4813-a779-ad8118ea0b91", - // "custom_metrics": { - // "metric_submission_strategy": { - // "allow_from": "bound_app" - // } - // } - // }, - // "instance_min_count": 1, - // "instance_max_count": 5, - // "scaling_rules": [ - // { - // "metric_type": "memoryused", - // "threshold": 30, - // "operator": "<", - // "adjustment": "-1" - // } - // ] - // }`) - // details = domain.BindDetails{ - // AppGUID: "", // Deprecated field! - // PlanID: "some_plan-id", - // ServiceID: "some_service-id", - // BindResource: &domain.BindResource{ - // AppGuid: "AppGUID_for_bindings", - // // SpaceGuid string `json:"space_guid,omitempty"` - // // Route string `json:"route,omitempty"` - // // CredentialClientID string `json:"credential_client_id,omitempty"` - // // BackupAgent bool `json:"backup_agent,omitempty"` - // }, // *BindResource - - // // RawContext: json.RawMessage // `json:"context,omitempty"` - // RawParameters: bindingParams, // `json:"parameters,omitempty"` - // } - - // _, err := aBroker.Bind(ctx, instanceID, bindingID, details, false) - - // Expect(err).NotTo(BeNil()) - // Expect(err).To(MatchError(ContainSubstring("app-guid is not supported in configuration"))) - // }) + // 🚧 To-do: Integrate and activate this test, when finishing the service-key-feature (PR #3652). + It("Fails when the additional config-parameter “app-guid” is provided", func() { + // As we don't see any case where it makes sense to provide metrics by a different + // app without using custom-metrics, we can assume that basic policy-definitions are + // present. + var bindingParams = []byte(` + { + "configuration": { + "app-guid": "8d0cee08-23ad-4813-a779-ad8118ea0b91", + "custom_metrics": { + "metric_submission_strategy": { + "allow_from": "bound_app" + } + } + }, + "instance_min_count": 1, + "instance_max_count": 5, + "scaling_rules": [ + { + "metric_type": "memoryused", + "threshold": 30, + "operator": "<", + "adjustment": "-1" + } + ] + }`) + details = domain.BindDetails{ + AppGUID: "", // Deprecated field! + PlanID: "some_plan-id", + ServiceID: "some_service-id", + BindResource: &domain.BindResource{ + AppGuid: "AppGUID_for_bindings", + // SpaceGuid string `json:"space_guid,omitempty"` + // Route string `json:"route,omitempty"` + // CredentialClientID string `json:"credential_client_id,omitempty"` + // BackupAgent bool `json:"backup_agent,omitempty"` + }, // *BindResource + + // RawContext: json.RawMessage // `json:"context,omitempty"` + RawParameters: bindingParams, // `json:"parameters,omitempty"` + } + + _, err := aBroker.Bind(ctx, instanceID, bindingID, details, false) + + Expect(err).NotTo(BeNil()) + Expect(err).To(MatchError(ContainSubstring("app-guid is not supported in configuration"))) + }) It("Supports provision of an Autoscaler Policy as RawParameters", func() { var bindingParams = []byte(` -{ - "instance_min_count": 1, - "instance_max_count": 5, - "scaling_rules": [ - { - "metric_type": "memoryused", - "threshold": 30, - "operator": "<", - "adjustment": "-1" - } - ] -}`) + { + "instance_min_count": 1, + "instance_max_count": 5, + "scaling_rules": [ + { + "metric_type": "memoryused", + "threshold": 30, + "operator": "<", + "adjustment": "-1" + } + ] + }`) details = domain.BindDetails{ AppGUID: "", PlanID: "some_plan-id", From 0e06ae6b2d351f48585a2e3f0112f109066b078f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 29 Oct 2025 15:57:01 +0100 Subject: [PATCH 05/19] =?UTF-8?q?=F0=9F=94=A7=20Fix=20reading=20of=20app-g?= =?UTF-8?q?uid;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/broker/binding_request_parser/parser.go | 8 ++++---- src/autoscaler/api/broker/broker_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/autoscaler/api/broker/binding_request_parser/parser.go b/src/autoscaler/api/broker/binding_request_parser/parser.go index 58032ab114..d1ada83b73 100644 --- a/src/autoscaler/api/broker/binding_request_parser/parser.go +++ b/src/autoscaler/api/broker/binding_request_parser/parser.go @@ -163,19 +163,19 @@ func (brp *bindRequestParser) getAppGuidFromBindingConfig(policyJson json.RawMes return "", nil } - var policy struct { + var appGuidRawStruct struct { BindingConfig struct { AppGUID string `json:"app_guid,omitempty"` - } `json:"binding-configuration,omitempty"` + } `json:"configuration,omitempty"` } - err := json.Unmarshal(policyJson, &policy) + err := json.Unmarshal(policyJson, &appGuidRawStruct) if err != nil { // 🚸 This can not happen because the input at this point has already been checked // against the json-schema. return "", fmt.Errorf("could not parse scaling policy to get app-guid from binding-configuration: %w", err) } - appGuid := models.GUID(policy.BindingConfig.AppGUID) + appGuid := models.GUID(appGuidRawStruct.BindingConfig.AppGUID) return appGuid, nil } diff --git a/src/autoscaler/api/broker/broker_test.go b/src/autoscaler/api/broker/broker_test.go index 8925ffca65..be1eb4c6fc 100644 --- a/src/autoscaler/api/broker/broker_test.go +++ b/src/autoscaler/api/broker/broker_test.go @@ -286,7 +286,7 @@ var _ = Describe("Broker", func() { var bindingParams = []byte(` { "configuration": { - "app-guid": "8d0cee08-23ad-4813-a779-ad8118ea0b91", + "app_guid": "8d0cee08-23ad-4813-a779-ad8118ea0b91", "custom_metrics": { "metric_submission_strategy": { "allow_from": "bound_app" @@ -323,7 +323,7 @@ var _ = Describe("Broker", func() { _, err := aBroker.Bind(ctx, instanceID, bindingID, details, false) Expect(err).NotTo(BeNil()) - Expect(err).To(MatchError(ContainSubstring("app-guid is not supported in configuration"))) + Expect(err).To(MatchError(ContainSubstring("app GUID provided in both, binding resource and binding configuration"))) }) It("Supports provision of an Autoscaler Policy as RawParameters", func() { var bindingParams = []byte(` From eacdfa8c344aebc15196c59401c4fa8607f9befd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Thu, 30 Oct 2025 17:20:23 +0100 Subject: [PATCH 06/19] Introduce branching on proper error-types; --- .../broker/binding_request_parser/parser.go | 48 ++++++++++++------- src/autoscaler/api/broker/broker.go | 31 +++++++++++- .../api/brokerserver/broker_handler_test.go | 26 +--------- 3 files changed, 61 insertions(+), 44 deletions(-) diff --git a/src/autoscaler/api/broker/binding_request_parser/parser.go b/src/autoscaler/api/broker/binding_request_parser/parser.go index d1ada83b73..612756ca3f 100644 --- a/src/autoscaler/api/broker/binding_request_parser/parser.go +++ b/src/autoscaler/api/broker/binding_request_parser/parser.go @@ -2,20 +2,38 @@ package bindingrequestparser import ( "encoding/json" - "errors" "fmt" - "net/http" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/policyvalidator" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" "code.cloudfoundry.org/brokerapi/v13/domain" - "code.cloudfoundry.org/brokerapi/v13/domain/apiresponses" ) + + +// ================================================================================ +// Parser-interface +// ================================================================================ + type BindRequestParser = interface { Parse(details domain.BindDetails) (models.AppScalingConfig, error) } +// This error type is used, when the passed binding-request fails to validate against the schema. +type BindReqNoAppGuid struct {} // 🚧 To-do: Generalise for every appGuidError (as well if two are provided) + +func (e BindReqNoAppGuid) Error() string { + return "error: service must be bound to an application; Please provide a GUID of an app!" +} + +var _ error = BindReqNoAppGuid{} + + + +// ================================================================================ +// Parser-implementation +// ================================================================================ + type bindRequestParser struct { policyValidator *policyvalidator.PolicyValidator defaultCustomMetricsCredentialType models.CustomMetricsBindingAuthScheme @@ -33,15 +51,20 @@ func NewBindRequestParser(policyValidator *policyvalidator.PolicyValidator, defa } } + + + func (brp *bindRequestParser) Parse(details domain.BindDetails) (models.AppScalingConfig, error) { var scalingPolicyRaw json.RawMessage if details.RawParameters != nil { scalingPolicyRaw = details.RawParameters } + var err error + // This just gets used for legacy-reasons. The actually parsing happens in the step // afterwards. But it still does not validate against the schema, which is done here. - _, err := brp.getPolicyFromJsonRawMessage(scalingPolicyRaw) + _, err = brp.getPolicyFromJsonRawMessage(scalingPolicyRaw) if err != nil { err := fmt.Errorf("validation-error against the json-schema:\n\t%w", err) return models.AppScalingConfig{}, err @@ -94,8 +117,7 @@ func (brp *bindRequestParser) Parse(details domain.BindDetails) (models.AppScali case appGuidIsFromBindingConfig: appGuid = appGuidFromBindingConfig default: - err := errors.New("error: service must be bound to an application; Please provide a GUID of an app!") - return models.AppScalingConfig{}, err + return models.AppScalingConfig{}, &BindReqNoAppGuid{} } appScalingConfig := models.NewAppScalingConfig( @@ -111,18 +133,8 @@ func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMes } policy, errResults := brp.policyValidator.ParseAndValidatePolicy(policyJson) - if errResults != nil { - // 🚫 The subsequent log-message is a strong assumption about the context of the caller. But - // how can we actually know here that we operate on a default-policy? In fact, when we are - // in the call-stack of `Bind` then we are *not* called with a default-policy. - resultsJson, err := json.Marshal(errResults) - if err != nil { - return nil, &models.InvalidArgumentError{ - Param: "errResults", - Value: errResults, - Msg: "Failed to json-marshal validation results; This should never happen."} - } - return policy, apiresponses.NewFailureResponse(fmt.Errorf("invalid policy provided: %s", string(resultsJson)), http.StatusBadRequest, "failed-to-validate-policy") + if errResults != nil && len(errResults) > 0 { + return policy, &errResults } return policy, nil diff --git a/src/autoscaler/api/broker/broker.go b/src/autoscaler/api/broker/broker.go index 5b9dd1678f..9a59535734 100644 --- a/src/autoscaler/api/broker/broker.go +++ b/src/autoscaler/api/broker/broker.go @@ -153,6 +153,10 @@ func (b *Broker) Provision(ctx context.Context, instanceID string, details domai return result, err } + if err := b.planDefinitionExceeded(policy.GetPolicyDefinition(), details.PlanID, instanceID); err != nil { + return result, err + } + var policyStr, policyGuidStr string if policy != nil { policyGuidStr = uuid.NewString() @@ -325,6 +329,10 @@ func (b *Broker) Update(ctx context.Context, instanceID string, details domain.U return result, err } + if err := b.planDefinitionExceeded(defaultPolicy, servicePlan, instanceID); err != nil { + return result, err + } + if !servicePlanIsNew && !defaultPolicyIsNew { logger.Info("no-changes-requested") return result, nil @@ -530,7 +538,28 @@ func (b *Broker) Bind( result := domain.Binding{} appScalingConfig, err := b.bindingReqParser.Parse(details) - if err != nil { + var schemaErr policyvalidator.ValidationErrors + var appGuidErr *brParser.BindReqNoAppGuid + switch { + case errors.As(err, &schemaErr): + resultsJson, err := json.Marshal(schemaErr) + if err != nil { + return result, &models.InvalidArgumentError{ + Param: "errResults", + Value: schemaErr, + Msg: "Failed to json-marshal validation results; This should never happen."} + } + apiErr := apiresponses.NewFailureResponse( + fmt.Errorf("invalid policy provided: %s", resultsJson), + http.StatusUnprocessableEntity, "failed-to-validate-policy") + + return result, apiErr + case errors.As(err, &appGuidErr): + logger.Error("bind-no-app-guid-provided", err) + return result, apiresponses.NewFailureResponseBuilder( + err, http.StatusUnprocessableEntity, "bind-no-app-guid-provided"). + WithErrorKey("RequiresApp").Build() + case err != nil: logger.Error("parse-binding-request", err) return result, apiresponses.NewFailureResponse( err, http.StatusBadRequest, "parse-binding-request") diff --git a/src/autoscaler/api/brokerserver/broker_handler_test.go b/src/autoscaler/api/brokerserver/broker_handler_test.go index e7fbed380b..836fd97c8f 100644 --- a/src/autoscaler/api/brokerserver/broker_handler_test.go +++ b/src/autoscaler/api/brokerserver/broker_handler_test.go @@ -146,7 +146,6 @@ var _ = Describe("BrokerHandler", func() { }) }) - Context("When all parameters are present", func() { Context("When database CreateServiceInstance call returns ErrAlreadyExists", func() { BeforeEach(func() { @@ -160,7 +159,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Body.String()).To(MatchJSON(`{}`)) }) }) - Context("When dashboard redirect uri is present in config and database CreateServiceInstance call returns ErrAlreadyExists", func() { BeforeEach(func() { body, err = json.Marshal(instanceCreationReqBody) @@ -173,7 +171,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Body.Bytes()).To(MatchJSON("{\"dashboard_url\":\"https://service-dashboard-url.com/manage/an-instance-id\"}")) }) }) - Context("When database CreateServiceInstance call returns ErrConflict", func() { BeforeEach(func() { body, err = json.Marshal(instanceCreationReqBody) @@ -184,7 +181,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Code).To(Equal(http.StatusConflict)) }) }) - Context("When database CreateServiceInstance call returns error other than ErrAlreadyExists", func() { BeforeEach(func() { body, err = json.Marshal(instanceCreationReqBody) @@ -196,7 +192,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Body.String()).To(MatchJSON(`{"description":"error creating service instance"}`)) }) }) - Context("When dashboard redirect uri is present in config", func() { BeforeEach(func() { body, err = json.Marshal(instanceCreationReqBody) @@ -335,7 +330,6 @@ var _ = Describe("BrokerHandler", func() { } JustBeforeEach(callUpdateServiceInstance) - Context("When request body is not a valid json", func() { BeforeEach(func() { body = []byte("") @@ -416,7 +410,6 @@ var _ = Describe("BrokerHandler", func() { }) }) Context("When a default policy is present and there was previously not a default policy", func() { - BeforeEach(func() { d := json.RawMessage(testDefaultPolicy) updateDefaultPolicy(d) @@ -549,7 +542,6 @@ var _ = Describe("BrokerHandler", func() { Expect(schedulerServer.ReceivedRequests()).To(HaveLen(1)) }) }) - Context("When a default policy with too many rules is present", func() { BeforeEach(func() { invalidDefaultPolicy := ` @@ -583,7 +575,6 @@ var _ = Describe("BrokerHandler", func() { Expect(string(bodyBytes)).To(MatchJSON(`{"description":"error: policy did not adhere to plan: Too many scaling rules: Found 2 scaling rules, but a maximum of 1 scaling rules are allowed for this service plan. "}`)) }) }) - Context("When the service plan is updatable", func() { Context("and the target plan is available", func() { @@ -611,7 +602,6 @@ var _ = Describe("BrokerHandler", func() { }) }) }) - Context("The service plan is updated and a default policy was present previously", func() { BeforeEach(func() { updatePlan("autoscaler-free-plan-id", "a-plan-id-not-updatable") @@ -635,7 +625,6 @@ var _ = Describe("BrokerHandler", func() { }) }) }) - Context("When the service plan is not updatable", func() { BeforeEach(func() { updatePlan("a-plan-id-not-updatable", "autoscaler-free-plan-id") @@ -695,7 +684,6 @@ var _ = Describe("BrokerHandler", func() { }) }) }) - Describe("DeleteServiceInstance", func() { JustBeforeEach(func() { req, _ = http.NewRequest(http.MethodDelete, "", nil) @@ -782,7 +770,6 @@ var _ = Describe("BrokerHandler", func() { }) }) - Describe("BindServiceInstance", func() { var ( err error @@ -848,10 +835,9 @@ var _ = Describe("BrokerHandler", func() { }) It("fails with 422", func() { Expect(resp.Code).To(Equal(http.StatusUnprocessableEntity)) - Expect(resp.Body.String()).To(MatchJSON(`{"error": "RequiresApp", "description": "error: service must be bound to an application - service key creation is not supported"}`)) + Expect(resp.Body.String()).To(MatchJSON(`{"error": "RequiresApp", "description": "error: service must be bound to an application; Please provide a GUID of an app!"}`)) }) }) - Context("When ServiceID is not provided", func() { BeforeEach(func() { bindingRequestBody.ServiceID = "" @@ -862,7 +848,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Code).To(Equal(http.StatusBadRequest)) }) }) - Context("When PlanID is not provided", func() { BeforeEach(func() { bindingRequestBody.PlanID = "" @@ -873,7 +858,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Code).To(Equal(http.StatusBadRequest)) }) }) - }) Context("When a policy with too many rules is provided", func() { BeforeEach(func() { @@ -1122,7 +1106,6 @@ var _ = Describe("BrokerHandler", func() { }) }) }) - Context("credential-type is provided while binding", func() { BeforeEach(func() { schedulerExpectedJSON = `{ @@ -1382,7 +1365,6 @@ var _ = Describe("BrokerHandler", func() { Expect(creds.Credentials.CustomMetrics.MtlsUrl).To(Equal("Mtls-someURL")) }) }) - Context("When a default policy was provided when creating the service instance", func() { BeforeEach(func() { bindingdb.GetServiceInstanceReturns(&models.ServiceInstance{testInstanceId, testOrgId, testSpaceId, testDefaultPolicy, testDefaultGuid}, nil) @@ -1469,7 +1451,6 @@ var _ = Describe("BrokerHandler", func() { }) }) }) - Context("When database CreateServiceBinding call returns ErrAlreadyExists", func() { BeforeEach(func() { body, err = json.Marshal(bindingRequestBody) @@ -1481,7 +1462,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Body.String()).To(MatchJSON(`{"description":"error: an autoscaler service instance is already bound to the application and multiple bindings are not supported"}`)) }) }) - Context("When database CreateServiceBinding call returns error other than ErrAlreadyExists", func() { BeforeEach(func() { body, err = json.Marshal(bindingRequestBody) @@ -1507,7 +1487,6 @@ var _ = Describe("BrokerHandler", func() { Expect(resp.Body.String()).To(MatchJSON(`{"description":"error creating service binding"}`)) }) }) - Context("When called with invalid policy json", func() { BeforeEach(func() { bindingRequestBody.Policy = json.RawMessage(`{ @@ -1530,7 +1509,6 @@ var _ = Describe("BrokerHandler", func() { Expect(string(bodyBytes)).To(ContainSubstring(`instance_min_count is required`)) }) }) - Context("When service bindings are present", func() { bindingIds := []string{testBindingId} BeforeEach(func() { @@ -1553,7 +1531,6 @@ var _ = Describe("BrokerHandler", func() { }) }) }) - Describe("UnBindServiceInstance", func() { BeforeEach(func() { req, _ = http.NewRequest(http.MethodDelete, "", nil) @@ -1624,7 +1601,6 @@ var _ = Describe("BrokerHandler", func() { }) }) }) - Describe("GetBinding", func() { var ( err error From d324dc163951badc2536b61c9d38efc6a2f3ac08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Fri, 31 Oct 2025 15:42:24 +0100 Subject: [PATCH 07/19] =?UTF-8?q?=F0=9F=94=A7=20Fix=20brokerserver-tests?= =?UTF-8?q?=20and=20broker-tests;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autoscaler/api/broker/broker.go | 8 +++++--- src/autoscaler/api/brokerserver/broker_handler_test.go | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/autoscaler/api/broker/broker.go b/src/autoscaler/api/broker/broker.go index 9a59535734..556ae6892d 100644 --- a/src/autoscaler/api/broker/broker.go +++ b/src/autoscaler/api/broker/broker.go @@ -153,8 +153,10 @@ func (b *Broker) Provision(ctx context.Context, instanceID string, details domai return result, err } - if err := b.planDefinitionExceeded(policy.GetPolicyDefinition(), details.PlanID, instanceID); err != nil { - return result, err + if policy != nil { + if err := b.planDefinitionExceeded(policy.GetPolicyDefinition(), details.PlanID, instanceID); err != nil { + return result, err + } } var policyStr, policyGuidStr string @@ -599,7 +601,7 @@ func (b *Broker) Bind( MtlsUrl: b.conf.MetricsForwarder.MetricsForwarderMtlsUrl, } - if appScalingConfig.GetConfiguration().GetCustomMetricsBindingAuth() == &models.BindingSecret { + if *appScalingConfig.GetConfiguration().GetCustomMetricsBindingAuth() == models.BindingSecret { // create credentials cred, err := b.credentials.Create(ctx, string(appGUID), nil) if err != nil { diff --git a/src/autoscaler/api/brokerserver/broker_handler_test.go b/src/autoscaler/api/brokerserver/broker_handler_test.go index 836fd97c8f..4ad65cdc53 100644 --- a/src/autoscaler/api/brokerserver/broker_handler_test.go +++ b/src/autoscaler/api/brokerserver/broker_handler_test.go @@ -979,7 +979,7 @@ var _ = Describe("BrokerHandler", func() { verifyScheduleIsUpdatedInScheduler(testAppId, bindingPolicy) }) It("should fail with 400", func() { - Expect(resp.Body.String()).To(ContainSubstring("{\"description\":\"invalid policy provided: [{\\\"context\\\":\\\"(root).configuration.custom_metrics.metric_submission_strategy.allow_from\\\",\\\"description\\\":\\\"configuration.custom_metrics.metric_submission_strategy.allow_from must be one of the following: \\\\\\\"bound_app\\\\\\\", \\\\\\\"same_app\\\\\\\"\\\"}]\"}")) + Expect(resp.Body.String()).To(ContainSubstring(`(root).configuration.custom_metrics.metric_submission_strategy.allow_from-configuration.custom_metrics.metric_submission_strategy.allow_from must be one of the following: \"bound_app\", \"same_app\"`)) Expect(resp.Code).To(Equal(http.StatusBadRequest)) }) }) @@ -1179,7 +1179,7 @@ var _ = Describe("BrokerHandler", func() { }) It("fails with 400", func() { Expect(resp.Code).To(Equal(http.StatusBadRequest)) - Expect(resp.Body.String()).To(MatchJSON(`{"description": "invalid policy provided: [{\"context\":\"(root).credential-type\",\"description\":\"credential-type must be one of the following: \\\"x509\\\", \\\"binding-secret\\\"\"}]"}`)) + Expect(resp.Body.String()).To(MatchJSON(`{"description": "validation-error against the json-schema:\n\t(root).credential-type-credential-type must be one of the following: \"x509\", \"binding-secret\""}`)) }) }) From 68ab508979d37ecb2f45ab673997e55a669e7d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Fri, 31 Oct 2025 15:46:38 +0100 Subject: [PATCH 08/19] Make golangci-lint happy; --- .../broker/binding_request_parser/parser.go | 11 +-- src/autoscaler/api/broker/broker_test.go | 93 +++++++++---------- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/src/autoscaler/api/broker/binding_request_parser/parser.go b/src/autoscaler/api/broker/binding_request_parser/parser.go index 612756ca3f..9acc1a3257 100644 --- a/src/autoscaler/api/broker/binding_request_parser/parser.go +++ b/src/autoscaler/api/broker/binding_request_parser/parser.go @@ -9,8 +9,6 @@ import ( "code.cloudfoundry.org/brokerapi/v13/domain" ) - - // ================================================================================ // Parser-interface // ================================================================================ @@ -20,7 +18,7 @@ type BindRequestParser = interface { } // This error type is used, when the passed binding-request fails to validate against the schema. -type BindReqNoAppGuid struct {} // 🚧 To-do: Generalise for every appGuidError (as well if two are provided) +type BindReqNoAppGuid struct{} // 🚧 To-do: Generalise for every appGuidError (as well if two are provided) func (e BindReqNoAppGuid) Error() string { return "error: service must be bound to an application; Please provide a GUID of an app!" @@ -28,8 +26,6 @@ func (e BindReqNoAppGuid) Error() string { var _ error = BindReqNoAppGuid{} - - // ================================================================================ // Parser-implementation // ================================================================================ @@ -51,9 +47,6 @@ func NewBindRequestParser(policyValidator *policyvalidator.PolicyValidator, defa } } - - - func (brp *bindRequestParser) Parse(details domain.BindDetails) (models.AppScalingConfig, error) { var scalingPolicyRaw json.RawMessage if details.RawParameters != nil { @@ -133,7 +126,7 @@ func (brp *bindRequestParser) getPolicyFromJsonRawMessage(policyJson json.RawMes } policy, errResults := brp.policyValidator.ParseAndValidatePolicy(policyJson) - if errResults != nil && len(errResults) > 0 { + if len(errResults) > 0 { return policy, &errResults } diff --git a/src/autoscaler/api/broker/broker_test.go b/src/autoscaler/api/broker/broker_test.go index be1eb4c6fc..af28ae34ed 100644 --- a/src/autoscaler/api/broker/broker_test.go +++ b/src/autoscaler/api/broker/broker_test.go @@ -278,53 +278,52 @@ var _ = Describe("Broker", func() { bindingID = "some_binding-id" }) Context("Create a binding", func() { - // 🚧 To-do: Integrate and activate this test, when finishing the service-key-feature (PR #3652). - It("Fails when the additional config-parameter “app-guid” is provided", func() { - // As we don't see any case where it makes sense to provide metrics by a different - // app without using custom-metrics, we can assume that basic policy-definitions are - // present. - var bindingParams = []byte(` - { - "configuration": { - "app_guid": "8d0cee08-23ad-4813-a779-ad8118ea0b91", - "custom_metrics": { - "metric_submission_strategy": { - "allow_from": "bound_app" - } - } - }, - "instance_min_count": 1, - "instance_max_count": 5, - "scaling_rules": [ - { - "metric_type": "memoryused", - "threshold": 30, - "operator": "<", - "adjustment": "-1" - } - ] - }`) - details = domain.BindDetails{ - AppGUID: "", // Deprecated field! - PlanID: "some_plan-id", - ServiceID: "some_service-id", - BindResource: &domain.BindResource{ - AppGuid: "AppGUID_for_bindings", - // SpaceGuid string `json:"space_guid,omitempty"` - // Route string `json:"route,omitempty"` - // CredentialClientID string `json:"credential_client_id,omitempty"` - // BackupAgent bool `json:"backup_agent,omitempty"` - }, // *BindResource - - // RawContext: json.RawMessage // `json:"context,omitempty"` - RawParameters: bindingParams, // `json:"parameters,omitempty"` - } - - _, err := aBroker.Bind(ctx, instanceID, bindingID, details, false) - - Expect(err).NotTo(BeNil()) - Expect(err).To(MatchError(ContainSubstring("app GUID provided in both, binding resource and binding configuration"))) - }) + It("Fails when the additional config-parameter “app-guid” is provided", func() { + // As we don't see any case where it makes sense to provide metrics by a different + // app without using custom-metrics, we can assume that basic policy-definitions are + // present. + var bindingParams = []byte(` + { + "configuration": { + "app_guid": "8d0cee08-23ad-4813-a779-ad8118ea0b91", + "custom_metrics": { + "metric_submission_strategy": { + "allow_from": "bound_app" + } + } + }, + "instance_min_count": 1, + "instance_max_count": 5, + "scaling_rules": [ + { + "metric_type": "memoryused", + "threshold": 30, + "operator": "<", + "adjustment": "-1" + } + ] + }`) + details = domain.BindDetails{ + AppGUID: "", // Deprecated field! + PlanID: "some_plan-id", + ServiceID: "some_service-id", + BindResource: &domain.BindResource{ + AppGuid: "AppGUID_for_bindings", + // SpaceGuid string `json:"space_guid,omitempty"` + // Route string `json:"route,omitempty"` + // CredentialClientID string `json:"credential_client_id,omitempty"` + // BackupAgent bool `json:"backup_agent,omitempty"` + }, // *BindResource + + // RawContext: json.RawMessage // `json:"context,omitempty"` + RawParameters: bindingParams, // `json:"parameters,omitempty"` + } + + _, err := aBroker.Bind(ctx, instanceID, bindingID, details, false) + + Expect(err).NotTo(BeNil()) + Expect(err).To(MatchError(ContainSubstring("app GUID provided in both, binding resource and binding configuration"))) + }) It("Supports provision of an Autoscaler Policy as RawParameters", func() { var bindingParams = []byte(` { From 9ca68503e2f08bd8123df39b5722decb0fcde1bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Sat, 1 Nov 2025 15:47:40 +0100 Subject: [PATCH 09/19] =?UTF-8?q?=F0=9F=94=A7=20Fix:=20Set=20hard-coded=20?= =?UTF-8?q?default-value=20for=20custom-metrics-binding-auth-config;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autoscaler/api/broker/broker.go | 16 ++++++++++------ src/autoscaler/api/cmd/api/api_test.go | 11 +---------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/autoscaler/api/broker/broker.go b/src/autoscaler/api/broker/broker.go index 556ae6892d..b97787e0c9 100644 --- a/src/autoscaler/api/broker/broker.go +++ b/src/autoscaler/api/broker/broker.go @@ -79,12 +79,16 @@ func New(logger lager.Logger, conf *config.Config, bindingDb db.BindingDB, polic conf.ScalingRules.Disk.UpperThreshold, ) - defaultCustomMetricsCredentialType, err := models.ParseCustomMetricsBindingAuthScheme( - conf.DefaultCustomMetricsCredentialType) - if err != nil { - logger.Fatal("parse-default-credential-type", err, lager.Data{ - "default-credential-type": conf.DefaultCustomMetricsCredentialType, - }) + defaultCustomMetricsCredentialType := &models.X509Certificate + if len(conf.DefaultCustomMetricsCredentialType) > 0 { + var err error + defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme( + conf.DefaultCustomMetricsCredentialType) + if err != nil { + logger.Fatal("parse-default-credential-type", err, lager.Data{ + "default-credential-type": conf.DefaultCustomMetricsCredentialType, + }) + } } bindingReqParser := brParser.NewBindRequestParser( diff --git a/src/autoscaler/api/cmd/api/api_test.go b/src/autoscaler/api/cmd/api/api_test.go index 7c8acaf0a8..2905db5b96 100644 --- a/src/autoscaler/api/cmd/api/api_test.go +++ b/src/autoscaler/api/cmd/api/api_test.go @@ -130,7 +130,6 @@ var _ = Describe("Api", func() { }) }) - Describe("when interrupt is sent", func() { It("should stop", func() { runner.Session.Interrupt() @@ -138,7 +137,6 @@ var _ = Describe("Api", func() { }) }) - Describe("Broker Rest API", func() { AfterEach(func() { runner.Interrupt() @@ -173,7 +171,6 @@ var _ = Describe("Api", func() { }) }) }) - Describe("Pubic API", func() { AfterEach(func() { runner.Interrupt() @@ -195,7 +192,6 @@ var _ = Describe("Api", func() { }) }) }) - Describe("when Health server is ready to serve RESTful API", func() { BeforeEach(func() { basicAuthConfig := conf @@ -217,20 +213,17 @@ var _ = Describe("Api", func() { }) }) }) - Describe("when Health server is ready to serve RESTful API with basic Auth", func() { AfterEach(func() { runner.Interrupt() Eventually(runner.Session, 5).Should(Exit(0)) }) - When("Health server is ready to serve RESTful API with basic Auth", func() { When("username and password are incorrect for basic authentication during health check", func() { It("should return 401", func() { testhelpers.CheckHealthAuth(GinkgoT(), healthHttpClient, healthURL.String(), "wrongusername", "wrongpassword", http.StatusUnauthorized) }) }) - When("username and password are correct for basic authentication during health check", func() { It("should return 200", func() { testhelpers.CheckHealthAuth(GinkgoT(), healthHttpClient, healthURL.String(), conf.Health.BasicAuth.Username, conf.Health.BasicAuth.Password, http.StatusOK) @@ -238,7 +231,6 @@ var _ = Describe("Api", func() { }) }) }) - Describe("can start with default plugin", func() { BeforeEach(func() { pluginPathConfig := conf @@ -265,7 +257,6 @@ var _ = Describe("Api", func() { }) }) }) - When("running CF server", func() { var ( cfInstanceKeyFile string @@ -362,7 +353,7 @@ func getVcapServices() (result string) { "user-provided": [ { "name": "apiserver-config", "tags": ["apiserver-config"], "credentials": { "apiserver-config": { } }}, { "name": "broker-catalog", "tags": ["broker-catalog"], "credentials": { "broker-catalog": ` + string(catalogBytes) + ` }} - ], + ], "autoscaler": [ { "name": "some-service", "credentials": { From eb83713695b7c8118060b0412371c9a594ba0e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Mon, 10 Nov 2025 11:02:52 +0100 Subject: [PATCH 10/19] Update submodule; --- src/autoscaler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autoscaler b/src/autoscaler index e9e1f8763f..7292d3fa6a 160000 --- a/src/autoscaler +++ b/src/autoscaler @@ -1 +1 @@ -Subproject commit e9e1f8763f2e8005255d4741abff061ad5490b39 +Subproject commit 7292d3fa6a97fa1e866f9b8999177fc15e3c5df0 From 1c6e5cc47a5c1a78846d83fbcc0e6ac6591a2551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Mon, 10 Nov 2025 13:44:45 +0100 Subject: [PATCH 11/19] Adapt bosh-package-specs; --- packages/golangapiserver/spec | 7 +------ packages/metricsforwarder/spec | 1 - packages/scalingengine/spec | 6 ------ 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/golangapiserver/spec b/packages/golangapiserver/spec index ee25694862..d931ef5419 100644 --- a/packages/golangapiserver/spec +++ b/packages/golangapiserver/spec @@ -14,6 +14,7 @@ files: - autoscaler/api/apis/* # gosub - autoscaler/api/apis/scalinghistory/* # gosub - autoscaler/api/broker/* # gosub +- autoscaler/api/broker/binding_request_parser/* # gosub - autoscaler/api/brokerserver/* # gosub - autoscaler/api/cmd/api/* # gosub - autoscaler/api/config/* # gosub @@ -128,11 +129,6 @@ files: - autoscaler/vendor/github.com/prometheus/procfs/internal/fs/* # gosub - autoscaler/vendor/github.com/prometheus/procfs/internal/util/* # gosub - autoscaler/vendor/github.com/segmentio/asm/base64/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/arm/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/arm64/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/cpuid/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/x86/* # gosub - autoscaler/vendor/github.com/segmentio/asm/internal/unsafebytes/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/grouper/* # gosub @@ -194,7 +190,6 @@ files: - autoscaler/vendor/golang.org/x/net/idna/* # gosub - autoscaler/vendor/golang.org/x/sync/errgroup/* # gosub - autoscaler/vendor/golang.org/x/sync/semaphore/* # gosub -- autoscaler/vendor/golang.org/x/sys/cpu/* # gosub - autoscaler/vendor/golang.org/x/sys/unix/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/registry/* # gosub diff --git a/packages/metricsforwarder/spec b/packages/metricsforwarder/spec index 50093ce76e..b8d6a732df 100644 --- a/packages/metricsforwarder/spec +++ b/packages/metricsforwarder/spec @@ -79,7 +79,6 @@ files: - autoscaler/vendor/github.com/klauspost/compress/fse/* # gosub - autoscaler/vendor/github.com/klauspost/compress/gzip/* # gosub - autoscaler/vendor/github.com/klauspost/compress/huff0/* # gosub -- autoscaler/vendor/github.com/klauspost/compress/internal/cpuinfo/* # gosub - autoscaler/vendor/github.com/klauspost/compress/internal/le/* # gosub - autoscaler/vendor/github.com/klauspost/compress/internal/snapref/* # gosub - autoscaler/vendor/github.com/klauspost/compress/zlib/* # gosub diff --git a/packages/scalingengine/spec b/packages/scalingengine/spec index 4ef9632868..2fe7b57e78 100644 --- a/packages/scalingengine/spec +++ b/packages/scalingengine/spec @@ -110,11 +110,6 @@ files: - autoscaler/vendor/github.com/prometheus/procfs/internal/fs/* # gosub - autoscaler/vendor/github.com/prometheus/procfs/internal/util/* # gosub - autoscaler/vendor/github.com/segmentio/asm/base64/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/arm/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/arm64/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/cpuid/* # gosub -- autoscaler/vendor/github.com/segmentio/asm/cpu/x86/* # gosub - autoscaler/vendor/github.com/segmentio/asm/internal/unsafebytes/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/grouper/* # gosub @@ -172,7 +167,6 @@ files: - autoscaler/vendor/golang.org/x/net/idna/* # gosub - autoscaler/vendor/golang.org/x/sync/errgroup/* # gosub - autoscaler/vendor/golang.org/x/sync/semaphore/* # gosub -- autoscaler/vendor/golang.org/x/sys/cpu/* # gosub - autoscaler/vendor/golang.org/x/sys/unix/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/registry/* # gosub From 1bade5d443fe40a62593a5bc263444daa7e284dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Mon, 10 Nov 2025 15:42:03 +0100 Subject: [PATCH 12/19] Update submodule; --- src/autoscaler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autoscaler b/src/autoscaler index 7292d3fa6a..1aecd22847 160000 --- a/src/autoscaler +++ b/src/autoscaler @@ -1 +1 @@ -Subproject commit 7292d3fa6a97fa1e866f9b8999177fc15e3c5df0 +Subproject commit 1aecd2284755083889064ea061cb072b2220bfec From 2fc40d4fa88d0873988c8eca7a9cba0fc10ed2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 12 Nov 2025 11:41:20 +0100 Subject: [PATCH 13/19] Make terminal-style work; --- src/acceptance/assets/app/go_app/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/acceptance/assets/app/go_app/Makefile b/src/acceptance/assets/app/go_app/Makefile index 3a1a5c1204..39d9c82c1c 100644 --- a/src/acceptance/assets/app/go_app/Makefile +++ b/src/acceptance/assets/app/go_app/Makefile @@ -1,8 +1,8 @@ .ONESHELL: SHELL := /bin/bash .SHELLFLAGS := -eu -o pipefail -c ${SHELLFLAGS} -aes_terminal_font_yellow := \e[38;2;255;255;0m -aes_terminal_reset := \e[0m +aes_terminal_font_yellow := \033[38;2;255;255;0m +aes_terminal_reset := \033[0m # TODO: Do we need the next line? MAKEFLAGS = From 5fd05dc869804eada1b55669c1ec8b1eb15aa2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Thu, 13 Nov 2025 13:46:29 +0100 Subject: [PATCH 14/19] Update package-specs; --- packages/golangapiserver/spec | 6 ++++++ packages/metricsforwarder/spec | 1 + packages/scalingengine/spec | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/packages/golangapiserver/spec b/packages/golangapiserver/spec index d931ef5419..c086e5d062 100644 --- a/packages/golangapiserver/spec +++ b/packages/golangapiserver/spec @@ -129,6 +129,11 @@ files: - autoscaler/vendor/github.com/prometheus/procfs/internal/fs/* # gosub - autoscaler/vendor/github.com/prometheus/procfs/internal/util/* # gosub - autoscaler/vendor/github.com/segmentio/asm/base64/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/arm/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/arm64/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/cpuid/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/x86/* # gosub - autoscaler/vendor/github.com/segmentio/asm/internal/unsafebytes/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/grouper/* # gosub @@ -190,6 +195,7 @@ files: - autoscaler/vendor/golang.org/x/net/idna/* # gosub - autoscaler/vendor/golang.org/x/sync/errgroup/* # gosub - autoscaler/vendor/golang.org/x/sync/semaphore/* # gosub +- autoscaler/vendor/golang.org/x/sys/cpu/* # gosub - autoscaler/vendor/golang.org/x/sys/unix/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/registry/* # gosub diff --git a/packages/metricsforwarder/spec b/packages/metricsforwarder/spec index b8d6a732df..50093ce76e 100644 --- a/packages/metricsforwarder/spec +++ b/packages/metricsforwarder/spec @@ -79,6 +79,7 @@ files: - autoscaler/vendor/github.com/klauspost/compress/fse/* # gosub - autoscaler/vendor/github.com/klauspost/compress/gzip/* # gosub - autoscaler/vendor/github.com/klauspost/compress/huff0/* # gosub +- autoscaler/vendor/github.com/klauspost/compress/internal/cpuinfo/* # gosub - autoscaler/vendor/github.com/klauspost/compress/internal/le/* # gosub - autoscaler/vendor/github.com/klauspost/compress/internal/snapref/* # gosub - autoscaler/vendor/github.com/klauspost/compress/zlib/* # gosub diff --git a/packages/scalingengine/spec b/packages/scalingengine/spec index 2fe7b57e78..4ef9632868 100644 --- a/packages/scalingengine/spec +++ b/packages/scalingengine/spec @@ -110,6 +110,11 @@ files: - autoscaler/vendor/github.com/prometheus/procfs/internal/fs/* # gosub - autoscaler/vendor/github.com/prometheus/procfs/internal/util/* # gosub - autoscaler/vendor/github.com/segmentio/asm/base64/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/arm/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/arm64/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/cpuid/* # gosub +- autoscaler/vendor/github.com/segmentio/asm/cpu/x86/* # gosub - autoscaler/vendor/github.com/segmentio/asm/internal/unsafebytes/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/* # gosub - autoscaler/vendor/github.com/tedsuo/ifrit/grouper/* # gosub @@ -167,6 +172,7 @@ files: - autoscaler/vendor/golang.org/x/net/idna/* # gosub - autoscaler/vendor/golang.org/x/sync/errgroup/* # gosub - autoscaler/vendor/golang.org/x/sync/semaphore/* # gosub +- autoscaler/vendor/golang.org/x/sys/cpu/* # gosub - autoscaler/vendor/golang.org/x/sys/unix/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/* # gosub - autoscaler/vendor/golang.org/x/sys/windows/registry/* # gosub From 37578bc4cabb5759cf3aafa85704b96ef3ad663a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 19 Nov 2025 18:12:55 +0100 Subject: [PATCH 15/19] Update submodule; --- src/autoscaler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autoscaler b/src/autoscaler index 1aecd22847..829e565c83 160000 --- a/src/autoscaler +++ b/src/autoscaler @@ -1 +1 @@ -Subproject commit 1aecd2284755083889064ea061cb072b2220bfec +Subproject commit 829e565c834166bc798ef09a17168af90b1c9765 From 781ae0df96e84d54652e0ae679d3db5499c006b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Thu, 27 Nov 2025 10:37:58 +0100 Subject: [PATCH 16/19] Link to schemas within the submodule; --- schema/json/legacy.schema.json | 1 + schema/json/meta.schema.json | 1 + schema/json/policy-configuration.schema.json | 41 +- schema/json/scaling-policy.schema.json | 930 ------------------ schema/json/scaling-policy.v0_9.schema.json | 1 + schema/json/service-key_only.v0_9.schema.json | 1 + schema/json/shared_definitions.json | 11 +- src/autoscaler | 2 +- 8 files changed, 7 insertions(+), 981 deletions(-) create mode 120000 schema/json/legacy.schema.json create mode 120000 schema/json/meta.schema.json mode change 100644 => 120000 schema/json/policy-configuration.schema.json delete mode 100644 schema/json/scaling-policy.schema.json create mode 120000 schema/json/scaling-policy.v0_9.schema.json create mode 120000 schema/json/service-key_only.v0_9.schema.json mode change 100644 => 120000 schema/json/shared_definitions.json diff --git a/schema/json/legacy.schema.json b/schema/json/legacy.schema.json new file mode 120000 index 0000000000..645e3923a9 --- /dev/null +++ b/schema/json/legacy.schema.json @@ -0,0 +1 @@ +../../src/autoscaler/api/policyvalidator/legacy.schema.json \ No newline at end of file diff --git a/schema/json/meta.schema.json b/schema/json/meta.schema.json new file mode 120000 index 0000000000..e05716cffd --- /dev/null +++ b/schema/json/meta.schema.json @@ -0,0 +1 @@ +../../src/autoscaler/api/policyvalidator/meta.schema.json \ No newline at end of file diff --git a/schema/json/policy-configuration.schema.json b/schema/json/policy-configuration.schema.json deleted file mode 100644 index 7362d63d4e..0000000000 --- a/schema/json/policy-configuration.schema.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-05/schema#", - "title": "Autoscaler Scaling Policy Configuration Schema", - "description": "Schema for the configuration options of scaling policy.", - "type": "object", - - "properties": { - "custom_metrics": { - "type": "object", - "properties": { - "metric_submission_strategy": { - "type": "object", - "properties": { - "allow_from": { - "type": "string", - "enum": [ - "bound_app", - "same_app" - ] - } - }, - "required": [ - "allow_from" - ] - } - }, - "required": [ - "metric_submission_strategy" - ], - "additionalProperties": false - }, - "app_guid": { - "$ref": "./shared_definitions.json#/schemas/guid" - } - }, - "required": [ - "custom_metrics" - ], - "additionalProperties": false -} diff --git a/schema/json/policy-configuration.schema.json b/schema/json/policy-configuration.schema.json new file mode 120000 index 0000000000..b6d92a07a6 --- /dev/null +++ b/schema/json/policy-configuration.schema.json @@ -0,0 +1 @@ +../../src/autoscaler/api/policyvalidator/policy-configuration.schema.json \ No newline at end of file diff --git a/schema/json/scaling-policy.schema.json b/schema/json/scaling-policy.schema.json deleted file mode 100644 index e8573ca326..0000000000 --- a/schema/json/scaling-policy.schema.json +++ /dev/null @@ -1,930 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-05/schema#", - "title": "Autoscaler Policy JSON Schema", - "description": "Schema for scaling-policies for Autoscaler", - "type": "object", - - "properties": { - "schema-version": { - "description": "Version-identifier for the used schema of this parameters-object.", - "type": "string", - "pattern": "^[0-9]+(\\.[0-9]+)?$" - }, - "credential-type": { - "type": "string", - "enum": [ - "x509", - "binding-secret" - ] - }, - "configuration": { - "$ref": "./policy-configuration.schema.json" - }, - "instance_min_count": { - "$id": "#/properties/instance_min_count", - "type": "integer", - "minimum": 1, - "title": "Minimum number of application instance always runs" - }, - "instance_max_count": { - "$id": "#/properties/instance_max_count", - "type": "integer", - "title": "Maximum how many instances of application can be provisioned as part of application scaling" - }, - "scaling_rules": { - "$id": "#/properties/scaling_rules", - "type": "array", - "title": "Dynamic Scaling_rules Schema", - "items": { - "$id": "#/properties/scaling_rules/items", - "type": "object", - "title": "Scaling_rules Items Schema", - "required": [ - "metric_type", - "threshold", - "operator", - "adjustment" - ], - "properties": { - "metric_type": { - "$id": "#/properties/scaling_rules/items/properties/metric_type", - "type": "string", - "title": "The Metric_type Schema", - "pattern": "^[a-zA-Z0-9_]+$", - "maxLength": 100 - }, - "breach_duration_secs": { - "$id": "#/properties/scaling_rules/items/properties/breach_duration_secs", - "type": "integer", - "title": "The Breach_duration_secs Schema", - "description": "The length of the past period when a scaling action might be triggered based on metric usage", - "maximum": 3600, - "minimum": 60 - }, - "stats_window_secs": { - "$id": "#/properties/scaling_rules/items/properties/stats_window_secs", - "type": "integer", - "deprecated": true, - "title": "The stats_window_secs schema", - "description": "This is a legacy-parameter. 🏚️ It is silently ignored." - }, - "threshold": { - "$id": "#/properties/scaling_rules/items/properties/threshold", - "type": "integer", - "title": "The Threshold Schema" - }, - "operator": { - "$id": "#/properties/scaling_rules/items/properties/operator", - "type": "string", - "title": "The Operator Schema", - "description": "Operator is used in combination with the threshold value to compare the current metric value", - "enum": [ - "<", - ">", - "<=", - ">=" - ] - }, - "cool_down_secs": { - "$id": "#/properties/scaling_rules/items/properties/cool_down_secs", - "type": "integer", - "title": "The Cool_down_secs Schema", - "description": "The interval between two successive scaling activity", - "maximum": 3600, - "minimum": 60 - }, - "adjustment": { - "$id": "#/properties/scaling_rules/items/properties/adjustment", - "type": "string", - "title": "The Adjustment Schema", - "description": "Magnitude of scaling in each step, +1 means scale up 1 Instance -2 means scale down 2 instances", - "pattern": "^[-+][1-9]+[0-9]*%?$" - } - } - } - }, - "schedules": { - "$id": "#/properties/schedules", - "type": "object", - "title": "The Scaling Schedules Schema", - "required": [ - "timezone" - ], - "anyOf": [ - { - "required": [ - "recurring_schedule" - ] - }, - { - "required": [ - "specific_date" - ] - } - ], - "properties": { - "timezone": { - "$id": "#/properties/schedules/properties/timezone", - "type": "string", - "title": "The Timezone Schema", - "pattern": "^(.*)$", - "enum": [ - "Etc/GMT+12", - "Etc/GMT+11", - "Pacific/Midway", - "Pacific/Niue", - "Pacific/Pago_Pago", - "Pacific/Samoa", - "US/Samoa", - "Etc/GMT+10", - "HST", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Rarotonga", - "Pacific/Tahiti", - "US/Hawaii", - "Pacific/Marquesas", - "America/Adak", - "America/Atka", - "Etc/GMT+9", - "Pacific/Gambier", - "US/Aleutian", - "America/Anchorage", - "America/Juneau", - "America/Metlakatla", - "America/Nome", - "America/Sitka", - "America/Yakutat", - "Etc/GMT+8", - "Pacific/Pitcairn", - "US/Alaska", - "America/Creston", - "America/Dawson", - "America/Dawson_Creek", - "America/Ensenada", - "America/Hermosillo", - "America/Los_Angeles", - "America/Phoenix", - "America/Santa_Isabel", - "America/Tijuana", - "America/Vancouver", - "America/Whitehorse", - "Canada/Pacific", - "Canada/Yukon", - "Etc/GMT+7", - "MST", - "Mexico/BajaNorte", - "PST8PDT", - "US/Arizona", - "US/Pacific", - "US/Pacific-New", - "America/Belize", - "America/Boise", - "America/Cambridge_Bay", - "America/Chihuahua", - "America/Costa_Rica", - "America/Denver", - "America/Edmonton", - "America/El_Salvador", - "America/Guatemala", - "America/Inuvik", - "America/Managua", - "America/Mazatlan", - "America/Ojinaga", - "America/Regina", - "America/Shiprock", - "America/Swift_Current", - "America/Tegucigalpa", - "America/Yellowknife", - "Canada/East-Saskatchewan", - "Canada/Mountain", - "Canada/Saskatchewan", - "Etc/GMT+6", - "MST7MDT", - "Mexico/BajaSur", - "Navajo", - "Pacific/Galapagos", - "US/Mountain", - "America/Atikokan", - "America/Bahia_Banderas", - "America/Bogota", - "America/Cancun", - "America/Cayman", - "America/Chicago", - "America/Coral_Harbour", - "America/Eirunepe", - "America/Guayaquil", - "America/Indiana/Knox", - "America/Indiana/Tell_City", - "America/Jamaica", - "America/Knox_IN", - "America/Lima", - "America/Matamoros", - "America/Menominee", - "America/Merida", - "America/Mexico_City", - "America/Monterrey", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Panama", - "America/Porto_Acre", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Resolute", - "America/Rio_Branco", - "America/Winnipeg", - "Brazil/Acre", - "CST6CDT", - "Canada/Central", - "Chile/EasterIsland", - "EST", - "Etc/GMT+5", - "Jamaica", - "Mexico/General", - "Pacific/Easter", - "US/Central", - "US/Indiana-Starke", - "America/Caracas", - "America/Anguilla", - "America/Antigua", - "America/Aruba", - "America/Asuncion", - "America/Barbados", - "America/Blanc-Sablon", - "America/Boa_Vista", - "America/Campo_Grande", - "America/Cuiaba", - "America/Curacao", - "America/Detroit", - "America/Dominica", - "America/Fort_Wayne", - "America/Grand_Turk", - "America/Grenada", - "America/Guadeloupe", - "America/Guyana", - "America/Havana", - "America/Indiana/Indianapolis", - "America/Indiana/Marengo", - "America/Indiana/Petersburg", - "America/Indiana/Vevay", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Indianapolis", - "America/Iqaluit ", - "America/Kentucky/Louisville ", - "America/Kentucky/Monticello", - "America/Kralendijk", - "America/La_Paz", - "America/Louisville ", - "America/Lower_Princes", - "America/Manaus", - "America/Marigot", - "America/Martinique", - "America/Montreal", - "America/Montserrat", - "America/Nassau", - "America/New_York", - "America/Nipigon", - "America/Pangnirtung ", - "America/Port-au-Prince ", - "America/Port_of_Spain", - "America/Porto_Velho", - "America/Puerto_Rico ", - "America/Santo_Domingo ", - "America/St_Barthelemy", - "America/St_Kitts", - "America/St_Lucia", - "America/St_Thomas", - "America/St_Vincent", - "America/Thunder_Bay", - "America/Toronto", - "America/Tortola", - "America/Virgin", - "Brazil/West", - "Canada/Eastern", - "Cuba", - "EST5EDT", - "Etc/GMT+4", - "US/East-Indiana", - "US/Eastern", - "US/Michigan", - "America/Araguaina ", - "America/Argentina/Buenos_Aires ", - "America/Argentina/Catamarca ", - "America/Argentina/ComodRivadavia ", - "America/Argentina/Cordoba ", - "America/Argentina/Jujuy ", - "America/Argentina/La_Rioja ", - "America/Argentina/Mendoza ", - "America/Argentina/Rio_Gallegos ", - "America/Argentina/Salta ", - "America/Argentina/San_Juan ", - "America/Argentina/San_Luis ", - "America/Argentina/Tucuman ", - "America/Argentina/Ushuaia", - "America/Bahia", - "America/Belem", - "America/Buenos_Aires", - "America/Catamarca", - "America/Cayenne", - "America/Cordoba", - "America/Fortaleza", - "America/Glace_Bay", - "America/Goose_Bay", - "America/Halifax", - "America/Jujuy", - "America/Maceio", - "America/Mendoza", - "America/Moncton", - "America/Montevideo", - "America/Paramaribo", - "America/Recife", - "America/Rosario", - "America/Santarem", - "America/Santiago", - "America/Sao_Paulo", - "America/Thule", - "Antarctica/Palmer", - "Antarctica/Rothera", - "Atlantic/Bermuda", - "Atlantic/Stanley", - "Brazil/East", - "Canada/Atlantic", - "Chile/Continental", - "Etc/GMT+3", - "America/St_Johns", - "Canada/Newfoundland", - "America/Godthab", - "America/Miquelon", - "America/Noronha ", - "Atlantic/South_Georgia", - "Brazil/DeNoronha", - "Etc/GMT+2", - "Atlantic/Cape_Verde", - "Etc/GMT+1", - "Africa/Abidjan", - "Africa/Accra", - "Africa/Bamako", - "Africa/Banjul", - "Africa/Bissau", - "Africa/Conakry", - "Africa/Dakar", - "Africa/Freetown", - "Africa/Lome", - "Africa/Monrovia", - "Africa/Nouakchott", - "Africa/Ouagadougou", - "Africa/Sao_Tome", - "Africa/Timbuktu", - "America/Danmarkshavn", - "America/Scoresbysund", - "Atlantic/Azores", - "Atlantic/Reykjavik", - "Atlantic/St_Helena", - "Etc/GMT", - "Etc/GMT+0", - "Etc/GMT-0", - "Etc/GMT0", - "Etc/Greenwich", - "Etc/UCT", - "Etc/UTC", - "Etc/Universal", - "Etc/Zulu", - "GMT", - "GMT+0", - "GMT-0", - "GMT0", - "Greenwich", - "Iceland", - "UCT", - "UTC", - "Universal", - "Zulu", - "Africa/Algiers", - "Africa/Bangui", - "Africa/Brazzaville", - "Africa/Casablanca", - "Africa/Douala", - "Africa/El_Aaiun", - "Africa/Kinshasa", - "Africa/Lagos", - "Africa/Libreville", - "Africa/Luanda", - "Africa/Malabo", - "Africa/Ndjamena", - "Africa/Niamey", - "Africa/Porto-Novo", - "Africa/Tunis", - "Africa/Windhoek", - "Atlantic/Canary", - "Atlantic/Faeroe", - "Atlantic/Faroe", - "Atlantic/Madeira", - "Eire", - "Etc/GMT-1", - "Europe/Belfast", - "Europe/Dublin", - "Europe/Guernsey", - "Europe/Isle_of_Man", - "Europe/Jersey", - "Europe/Lisbon", - "Europe/London", - "GB", - "GB-Eire", - "Portugal", - "WET", - "Africa/Blantyre", - "Africa/Bujumbura", - "Africa/Cairo", - "Africa/Ceuta", - "Africa/Gaborone", - "Africa/Harare", - "Africa/Johannesburg", - "Africa/Kigali", - "Africa/Lubumbashi", - "Africa/Lusaka", - "Africa/Maputo", - "Africa/Maseru", - "Africa/Mbabane", - "Africa/Tripoli", - "Antarctica/Troll", - "Arctic/Longyearbyen", - "Atlantic/Jan_Mayen", - "CET", - "Egypt", - "Etc/GMT-2", - "Europe/Amsterdam", - "Europe/Andorra", - "Europe/Belgrade", - "Europe/Berlin", - "Europe/Bratislava", - "Europe/Brussels", - "Europe/Budapest", - "Europe/Busingen", - "Europe/Copenhagen", - "Europe/Gibraltar", - "Europe/Kaliningrad", - "Europe/Ljubljana", - "Europe/Luxembourg", - "Europe/Madrid", - "Europe/Malta", - "Europe/Monaco", - "Europe/Oslo", - "Europe/Paris", - "Europe/Podgorica", - "Europe/Prague", - "Europe/Rome", - "Europe/San_Marino", - "Europe/Sarajevo", - "Europe/Skopje", - "Europe/Stockholm", - "Europe/Tirane", - "Europe/Vaduz", - "Europe/Vatican", - "Europe/Vienna", - "Europe/Warsaw", - "Europe/Zagreb", - "Europe/Zurich", - "Libya", - "MET", - "Poland", - "Africa/Addis_Ababa", - "Africa/Asmara", - "Africa/Asmera", - "Africa/Dar_es_Salaam", - "Africa/Djibouti", - "Africa/Juba", - "Africa/Kampala", - "Africa/Khartoum", - "Africa/Mogadishu", - "Africa/Nairobi", - "Antarctica/Syowa", - "Asia/Aden", - "Asia/Amman", - "Asia/Baghdad", - "Asia/Bahrain", - "Asia/Beirut", - "Asia/Damascus", - "Asia/Gaza", - "Asia/Hebron", - "Asia/Istanbul", - "Asia/Jerusalem", - "Asia/Kuwait", - "Asia/Nicosia", - "Asia/Qatar", - "Asia/Riyadh", - "Asia/Tel_Aviv", - "EET", - "Etc/GMT-3", - "Europe/Athens", - "Europe/Bucharest", - "Europe/Chisinau", - "Europe/Helsinki", - "Europe/Istanbul", - "Europe/Kiev", - "Europe/Mariehamn", - "Europe/Minsk", - "Europe/Moscow", - "Europe/Nicosia", - "Europe/Riga", - "Europe/Simferopol", - "Europe/Sofia", - "Europe/Tallinn", - "Europe/Tiraspol", - "Europe/Uzhgorod", - "Europe/Vilnius", - "Europe/Volgograd", - "Europe/Zaporozhye", - "Indian/Antananarivo", - "Indian/Comoro", - "Indian/Mayotte", - "Israel", - "Turkey", - "W-SU", - "Asia/Dubai", - "Asia/Muscat", - "Asia/Tbilisi", - "Asia/Yerevan", - "Etc/GMT-4", - "Europe/Samara", - "Indian/Mahe", - "Indian/Mauritius", - "Indian/Reunion", - "Asia/Kabul", - "Asia/Tehran", - "Iran", - "Antarctica/Mawson", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Ashgabat", - "Asia/Ashkhabad", - "Asia/Baku", - "Asia/Dushanbe", - "Asia/Karachi", - "Asia/Oral", - "Asia/Samarkand", - "Asia/Tashkent", - "Asia/Yekaterinburg", - "Etc/GMT-5", - "Indian/Kerguelen", - "Indian/Maldives", - "Asia/Calcutta", - "Asia/Colombo", - "Asia/Kolkata", - "Asia/Kathmandu", - "Asia/Katmandu", - "Antarctica/Vostok", - "Asia/Almaty", - "Asia/Bishkek", - "Asia/Dacca", - "Asia/Dhaka", - "Asia/Kashgar", - "Asia/Novosibirsk", - "Asia/Omsk", - "Asia/Qyzylorda", - "Asia/Thimbu", - "Asia/Thimphu", - "Asia/Urumqi", - "Etc/GMT-6", - "Indian/Chagos", - "Asia/Rangoon", - "Indian/Cocos", - "Antarctica/Davis", - "Asia/Bangkok", - "Asia/Ho_Chi_Minh", - "Asia/Hovd", - "Asia/Jakarta", - "Asia/Krasnoyarsk", - "Asia/Novokuznetsk", - "Asia/Phnom_Penh", - "Asia/Pontianak", - "Asia/Saigon", - "Asia/Vientiane", - "Etc/GMT-7", - "Indian/Christmas", - "Antarctica/Casey", - "Asia/Brunei", - "Asia/Chita", - "Asia/Choibalsan", - "Asia/Chongqing", - "Asia/Chungking", - "Asia/Harbin", - "Asia/Hong_Kong", - "Asia/Irkutsk", - "Asia/Kuala_Lumpur", - "Asia/Kuching", - "Asia/Macao", - "Asia/Macau", - "Asia/Makassar", - "Asia/Manila", - "Asia/Shanghai", - "Asia/Singapore", - "Asia/Taipei", - "Asia/Ujung_Pandang", - "Asia/Ulaanbaatar", - "Asia/Ulan_Bator", - "Australia/Perth", - "Australia/West", - "Etc/GMT-8", - "Hongkong", - "PRC", - "ROC", - "Singapore", - "Australia/Eucla", - "Asia/Dili", - "Asia/Jayapura", - "Asia/Khandyga", - "Asia/Pyongyang", - "Asia/Seoul", - "Asia/Tokyo", - "Asia/Yakutsk", - "Etc/GMT-9", - "Japan", - "Pacific/Palau", - "ROK", - "Australia/Adelaide ", - "Australia/Broken_Hill", - "Australia/Darwin", - "Australia/North", - "Australia/South", - "Australia/Yancowinna ", - "Antarctica/DumontDUrville", - "Asia/Magadan", - "Asia/Sakhalin", - "Asia/Ust-Nera", - "Asia/Vladivostok", - "Australia/ACT", - "Australia/Brisbane", - "Australia/Canberra", - "Australia/Currie", - "Australia/Hobart", - "Australia/Lindeman", - "Australia/Melbourne", - "Australia/NSW", - "Australia/Queensland", - "Australia/Sydney", - "Australia/Tasmania", - "Australia/Victoria", - "Etc/GMT-10", - "Pacific/Chuuk", - "Pacific/Guam", - "Pacific/Port_Moresby", - "Pacific/Saipan", - "Pacific/Truk", - "Pacific/Yap", - "Australia/LHI", - "Australia/Lord_Howe", - "Antarctica/Macquarie", - "Asia/Srednekolymsk", - "Etc/GMT-11", - "Pacific/Bougainville", - "Pacific/Efate", - "Pacific/Guadalcanal", - "Pacific/Kosrae", - "Pacific/Noumea", - "Pacific/Pohnpei", - "Pacific/Ponape", - "Pacific/Norfolk", - "Antarctica/McMurdo", - "Antarctica/South_Pole", - "Asia/Anadyr", - "Asia/Kamchatka", - "Etc/GMT-12", - "Kwajalein", - "NZ", - "Pacific/Auckland", - "Pacific/Fiji", - "Pacific/Funafuti", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Nauru", - "Pacific/Tarawa", - "Pacific/Wake", - "Pacific/Wallis", - "NZ-CHAT", - "Pacific/Chatham", - "Etc/GMT-13", - "Pacific/Apia", - "Pacific/Enderbury", - "Pacific/Fakaofo", - "Pacific/Tongatapu", - "Etc/GMT-14", - "Pacific/Kiritimati" - ] - }, - "recurring_schedule": { - "$id": "#/properties/schedules/properties/recurring_schedule", - "type": "array", - "title": "The Recurring_schedule Schema", - "items": { - "$id": "#/properties/schedules/properties/recurring_schedule/items", - "type": "object", - "title": "The Recurring_schedule Items Schema", - "required": [ - "start_time", - "end_time", - "instance_min_count", - "instance_max_count" - ], - "oneOf": [ - { - "required": [ - "days_of_week" - ] - }, - { - "required": [ - "days_of_month" - ] - } - ], - "properties": { - "start_time": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/start_time", - "type": "string", - "title": "The Start_time Schema", - "pattern": "^(2[0-3]|1[0-9]|0[0-9]):([0-5][0-9])$" - }, - "end_time": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/end_time", - "type": "string", - "title": "The End_time Schema", - "pattern": "^(2[0-3]|1[0-9]|0[0-9]):([0-5][0-9])$" - }, - "days_of_week": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/days_of_week", - "type": "array", - "title": "The Days_of_week Schema", - "items": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/days_of_week/items", - "type": "integer", - "enum": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7 - ] - } - }, - "days_of_month": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/days_of_month", - "type": "array", - "title": "The Days_of_month Schema", - "items": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/days_of_month/items", - "type": "integer", - "enum": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31 - ] - } - }, - "instance_min_count": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/instance_min_count", - "type": "integer", - "title": "The Instance_min_count Schema" - }, - "instance_max_count": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/instance_max_count", - "type": "integer", - "title": "The Instance_max_count Schema" - }, - "initial_min_instance_count": { - "$id": "#/properties/schedules/properties/recurring_schedule/items/properties/initial_min_instance_count", - "type": "integer", - "title": "The Initial_min_instance_count Schema" - }, - "start_date": { - "oneOf": [ - { - "pattern": "^2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$", - "type": "string" - }, - { - "enum": [ - "" - ], - "type": "string" - } - ], - "description": "Start date of the recurrence in YYYY-MM-DD format" - }, - "end_date": { - "oneOf": [ - { - "pattern": "^2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$", - "type": "string" - }, - { - "enum": [ - "" - ], - "type": "string" - } - ], - "description": "End date of the recurrence in YYYY-MM-DD format" - } - } - } - }, - "specific_date": { - "$id": "#/properties/schedules/properties/specific_date", - "type": "array", - "title": "The Specific_date Schema", - "items": { - "$id": "#/properties/schedules/properties/specific_date/items", - "type": "object", - "title": "The Items Schema", - "required": [ - "start_date_time", - "end_date_time", - "instance_min_count", - "instance_max_count" - ], - "properties": { - "start_date_time": { - "$id": "#/properties/schedules/properties/specific_date/items/properties/start_date_time", - "type": "string", - "title": "The Start_date_time Schema", - "pattern": "^2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T(2[0-3]|1[0-9]|0[0-9]):([0-5][0-9])$" - }, - "end_date_time": { - "$id": "#/properties/schedules/properties/specific_date/items/properties/end_date_time", - "type": "string", - "title": "The End_date_time Schema", - "default": "", - "pattern": "^2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T(2[0-3]|1[0-9]|0[0-9]):([0-5][0-9])$" - }, - "instance_min_count": { - "$id": "#/properties/schedules/properties/specific_date/items/properties/instance_min_count", - "type": "integer", - "title": "The Instance_min_count Schema" - }, - "instance_max_count": { - "$id": "#/properties/schedules/properties/specific_date/items/properties/instance_max_count", - "type": "integer", - "title": "The Instance_max_count Schema" - }, - "initial_min_instance_count": { - "$id": "#/properties/schedules/properties/specific_date/items/properties/initial_min_instance_count", - "type": "integer", - "title": "The Initial_min_instance_count Schema" - } - } - } - } - } - } - }, - - "required": [ - "instance_min_count", - "instance_max_count" - ], - "anyOf": [ - { - "required": [ - "scaling_rules" - ] - }, - { - "required": [ - "schedules" - ] - } - ], - "additionalProperties": true -} diff --git a/schema/json/scaling-policy.v0_9.schema.json b/schema/json/scaling-policy.v0_9.schema.json new file mode 120000 index 0000000000..583ce9934d --- /dev/null +++ b/schema/json/scaling-policy.v0_9.schema.json @@ -0,0 +1 @@ +../../src/autoscaler/api/policyvalidator/scaling-policy.v0_9.schema.json \ No newline at end of file diff --git a/schema/json/service-key_only.v0_9.schema.json b/schema/json/service-key_only.v0_9.schema.json new file mode 120000 index 0000000000..0f5e1dabb4 --- /dev/null +++ b/schema/json/service-key_only.v0_9.schema.json @@ -0,0 +1 @@ +../../src/autoscaler/api/policyvalidator/service-key_only.v0_9.schema.json \ No newline at end of file diff --git a/schema/json/shared_definitions.json b/schema/json/shared_definitions.json deleted file mode 100644 index 46a6ae9315..0000000000 --- a/schema/json/shared_definitions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "schemas": { - "guid": { - "type": "string", - "description": "Unique identificator for a CF-resource, e.g. app, space, service-binding, …", - "pattern": "(\\d|[a-f]){8}-(\\d|[a-f]){4}-(\\d|[a-f]){4}-(\\d|[a-f]){4}-(\\d|[a-f]){12}", - "example": "8d0cee08-23ad-4813-a779-ad8118ea0b91" - } - } -} diff --git a/schema/json/shared_definitions.json b/schema/json/shared_definitions.json new file mode 120000 index 0000000000..01a37df394 --- /dev/null +++ b/schema/json/shared_definitions.json @@ -0,0 +1 @@ +../../src/autoscaler/api/policyvalidator/shared_definitions.json \ No newline at end of file diff --git a/src/autoscaler b/src/autoscaler index 829e565c83..5f7945b49d 160000 --- a/src/autoscaler +++ b/src/autoscaler @@ -1 +1 @@ -Subproject commit 829e565c834166bc798ef09a17168af90b1c9765 +Subproject commit 5f7945b49d75577d420f366b4a0a05e526925f90 From 9874a96a3dba30c57a429ad03377102b123edda9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 3 Dec 2025 11:20:50 +0100 Subject: [PATCH 17/19] Adapt spec-filenaming; --- jobs/golangapiserver/templates/apiserver.yml.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/golangapiserver/templates/apiserver.yml.erb b/jobs/golangapiserver/templates/apiserver.yml.erb index b7cc80e4c0..5420205b01 100644 --- a/jobs/golangapiserver/templates/apiserver.yml.erb +++ b/jobs/golangapiserver/templates/apiserver.yml.erb @@ -86,7 +86,7 @@ broker_credentials: catalog_path: /var/vcap/jobs/golangapiserver/config/catalog.json catalog_schema_path: /var/vcap/packages/golangapiserver/catalog.schema.json info_file_path: /var/vcap/jobs/golangapiserver/config/info.json -policy_schema_path: /var/vcap/packages/golangapiserver/scaling-policy.schema.json +policy_schema_path: /var/vcap/packages/golangapiserver/scaling-policy.v0_9.schema.json dashboard_redirect_uri: <%= p("autoscaler.apiserver.broker.server.dashboard_redirect_uri") %> default_credential_type: <%= p("autoscaler.apiserver.broker.default_credential_type") %> From a4ca9e37b2f17d8cebff1dfcd5576151af3985ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 3 Dec 2025 14:23:02 +0100 Subject: [PATCH 18/19] Fix wrong file-naming; --- jobs/golangapiserver/templates/apiserver.yml.erb | 2 +- src/autoscaler | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jobs/golangapiserver/templates/apiserver.yml.erb b/jobs/golangapiserver/templates/apiserver.yml.erb index 5420205b01..80d560fcf8 100644 --- a/jobs/golangapiserver/templates/apiserver.yml.erb +++ b/jobs/golangapiserver/templates/apiserver.yml.erb @@ -86,7 +86,7 @@ broker_credentials: catalog_path: /var/vcap/jobs/golangapiserver/config/catalog.json catalog_schema_path: /var/vcap/packages/golangapiserver/catalog.schema.json info_file_path: /var/vcap/jobs/golangapiserver/config/info.json -policy_schema_path: /var/vcap/packages/golangapiserver/scaling-policy.v0_9.schema.json +policy_schema_path: /var/vcap/packages/golangapiserver/scaling-policy.v0_1.schema.json dashboard_redirect_uri: <%= p("autoscaler.apiserver.broker.server.dashboard_redirect_uri") %> default_credential_type: <%= p("autoscaler.apiserver.broker.default_credential_type") %> diff --git a/src/autoscaler b/src/autoscaler index 4855d4aa80..70c136212a 160000 --- a/src/autoscaler +++ b/src/autoscaler @@ -1 +1 @@ -Subproject commit 4855d4aa800c9afdd5bfabfed42e46071d5ec2a3 +Subproject commit 70c136212ae1b2f8c947809dabe3b2d0e89661e5 From 2ae6d9e8f0ae8839191b741dc890c4a40e5262c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Weisbarth?= Date: Wed, 3 Dec 2025 15:01:39 +0100 Subject: [PATCH 19/19] =?UTF-8?q?=F0=9F=94=A7=20Fix=20schema-path;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jobs/golangapiserver/templates/apiserver.yml.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/golangapiserver/templates/apiserver.yml.erb b/jobs/golangapiserver/templates/apiserver.yml.erb index 80d560fcf8..4dd34c3312 100644 --- a/jobs/golangapiserver/templates/apiserver.yml.erb +++ b/jobs/golangapiserver/templates/apiserver.yml.erb @@ -86,7 +86,7 @@ broker_credentials: catalog_path: /var/vcap/jobs/golangapiserver/config/catalog.json catalog_schema_path: /var/vcap/packages/golangapiserver/catalog.schema.json info_file_path: /var/vcap/jobs/golangapiserver/config/info.json -policy_schema_path: /var/vcap/packages/golangapiserver/scaling-policy.v0_1.schema.json +policy_schema_path: /var/vcap/packages/golangapiserver/meta.schema.json dashboard_redirect_uri: <%= p("autoscaler.apiserver.broker.server.dashboard_redirect_uri") %> default_credential_type: <%= p("autoscaler.apiserver.broker.default_credential_type") %>