Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions api/broker/binding_request_parser/binding-request-parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package binding_request_parser

import (
"encoding/json"
"fmt"

"code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/broker/binding_request_parser/legacy"
brp "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/broker/binding_request_parser/types"
"code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/broker/binding_request_parser/v0_1"
"code.cloudfoundry.org/app-autoscaler/src/autoscaler/models"
)

// The parser for bind-requests needs to deal with a multi-version-schema. It therefore is a
// aggregation of multiple dedicated parsers where each one is specialised for precisely one
// particular schema-version. The top-level-parser then just extracts the schema that is referenced
// within a binding-request and dispatches the parsing to the responsible one of the specialised
// parsers.
//
// All the specialised parsers as well as the top-level one share the same interface `Parser` (as
// well as some other dedicated types) within the sub-package `types`.

// ================================================================================
// To-level parser
// ================================================================================

type BindRequestParser struct {
legacyParser legacy.BindingRequestParser
v0_1Parser v0_1.BindingRequestParser
}

var _ brp.Parser = BindRequestParser{}

func NewFromParsers(
legacyParser legacy.BindingRequestParser, v0_1Parser v0_1.BindingRequestParser,
) BindRequestParser {
return BindRequestParser{
legacyParser: legacyParser,
v0_1Parser: v0_1Parser,
}
}

func New(
legacySchemaPath, v0_1SchemaPath string,
defaultCustomMetricsCredentialType models.CustomMetricsBindingAuthScheme,
) (BindRequestParser, error) {
legacyParser, err := legacy.New(legacySchemaPath, defaultCustomMetricsCredentialType)
if err != nil {
return BindRequestParser{}, fmt.Errorf("failed to create legacy binding-request-parser: %w", err)
}

v0_1Parser, err := v0_1.NewFromFile(v0_1SchemaPath, defaultCustomMetricsCredentialType)
if err != nil {
return BindRequestParser{}, fmt.Errorf("failed to create v0.1 binding-request-parser: %w", err)
}

return NewFromParsers(legacyParser, v0_1Parser), nil
}

func (p BindRequestParser) Parse(
bindingReqParams string, ccAppGuid models.GUID,
) (models.AppScalingConfig, error) {
schemaVersion, err := extractSchemaVersion(bindingReqParams)
if err != nil {
return models.AppScalingConfig{}, err
}

switch schemaVersion {
case "0.1":
return p.v0_1Parser.Parse(bindingReqParams, ccAppGuid)
case "": // The legacy-schema is the only one without a version-identifier;
return p.legacyParser.Parse(bindingReqParams, ccAppGuid)
default:
return models.AppScalingConfig{}, fmt.Errorf(
"unsupported schema-version '%s' referenced in binding-request", schemaVersion)
}
}

func extractSchemaVersion(bindingReqParams string) (string, error) {
type schemaHolder struct {
Schema string `json:"schema-version,omitempty"`
}

var holder schemaHolder
err := json.Unmarshal([]byte(bindingReqParams), &holder)
if err != nil {
return "", fmt.Errorf("failed to extract schema-version from binding-request: %w", err)
}

return holder.Schema, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package binding_request_parser_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"testing"
)

func TestServer(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "BindingRequestParser test suite")
}
164 changes: 164 additions & 0 deletions api/broker/binding_request_parser/binding-request-parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package binding_request_parser_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

br "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/broker/binding_request_parser"
lp "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/broker/binding_request_parser/legacy"
brp_v1 "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/broker/binding_request_parser/v0_1"
"code.cloudfoundry.org/app-autoscaler/src/autoscaler/models"
)

var _ = Describe("BindingRequestParsers", func() {
const v0_1SchemaFilePath string = "file://./v0_1/meta.schema.json"
const legacySchemaFilePath string = "file://./legacy/schema.json"

const validModernBindingRequestRaw string = `
{
"schema-version": "0.1",
"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"
}
]
}`
const validLegacyBindingRequestRaw string = `
{
"configuration": {
"custom_metrics": {
"metric_submission_strategy": {
"allow_from": "bound_app"
}
}
},
"instance_min_count": 1,
"instance_max_count": 4,
"scaling_rules": [
{
"metric_type": "memoryutil",
"breach_duration_secs": 600,
"threshold": 30,
"operator": "<",
"cool_down_secs": 300,
"adjustment": "-1"
},
{
"metric_type": "memoryutil",
"breach_duration_secs": 600,
"threshold": 90,
"operator": ">=",
"cool_down_secs": 300,
"adjustment": "+1"
}
]
}`

Describe("v0.1_BindingRequestParser", func() {
var (
v0_1Parser brp_v1.BindingRequestParser
err error
)
var _ = BeforeEach(func() {
v0_1Parser, err = brp_v1.NewFromFile(v0_1SchemaFilePath, models.X509Certificate)
Expect(err).NotTo(HaveOccurred())
})
Context("When using the new format for binding-requests", func() {
Context("and parsing a valid and complete one", func() {
It("should return a correctly populated BindingRequestParameters", func() {
bindingRequestRaw := validModernBindingRequestRaw
ccAppGuid := models.GUID("") // Raw request is about creating a service-key;

bindingRequest, err := v0_1Parser.Parse(bindingRequestRaw, ccAppGuid)

Expect(err).NotTo(HaveOccurred())
Expect(bindingRequest.GetConfiguration().GetAppGUID()).To(
Equal(models.GUID("8d0cee08-23ad-4813-a779-ad8118ea0b91")))
})
})
})
})

Describe("LegacyBindingRequestParser", func() {
var (
legacyParser lp.BindingRequestParser
err error
)
var _ = BeforeEach(func() {
Expect(err).NotTo(HaveOccurred())
legacyParser, err = lp.New(legacySchemaFilePath, models.X509Certificate)
Expect(err).NotTo(HaveOccurred())
})

Context("When using the legacy format for binding-requests", func() {
It("should return a correctly populated BindingRequestParameters", func() {
bindingRequestRaw := validLegacyBindingRequestRaw
appGuidFromCC := models.GUID("8d0cee08-23ad-4813-a779-ad8118ea0b91")

appScalingConfig, err := legacyParser.Parse(bindingRequestRaw, appGuidFromCC)

Expect(err).NotTo(HaveOccurred())
Expect(appScalingConfig.GetConfiguration().GetAppGUID()).
To(Equal(models.GUID("8d0cee08-23ad-4813-a779-ad8118ea0b91")))
// 🚧 To-do: Add a few more field-comparisons;
})
})
})

Describe("CombinedBindingRequestParser", func() {
var (
parser br.BindRequestParser
err error
)
var _ = BeforeEach(func() {
parser, err = br.New(
legacySchemaFilePath,
v0_1SchemaFilePath,
models.X509Certificate,
)
Expect(err).NotTo(HaveOccurred())
})

Context("When using the v0.1 schema for binding-requests", func() {
Context("and parsing a valid and complete one", func() {
It("should return a correctly populated BindingRequestParameters", func() {
bindingRequestRaw := validModernBindingRequestRaw
appGuidFromCC := models.GUID("")

bindingRequest, err := parser.Parse(bindingRequestRaw, appGuidFromCC)

Expect(err).NotTo(HaveOccurred())
Expect(bindingRequest.GetConfiguration().GetAppGUID()).To(
Equal(models.GUID("8d0cee08-23ad-4813-a779-ad8118ea0b91")))
})
})
})

Context("When using the legacy schema for binding-requests", func() {
It("should return a correctly populated BindingRequestParameters", func() {
bindingRequestRaw := validLegacyBindingRequestRaw
appGuidFromCC := models.GUID("8d0cee08-23ad-4813-a779-ad8118ea0b91")

bindingRequest, err := parser.Parse(bindingRequestRaw, appGuidFromCC)

Expect(err).NotTo(HaveOccurred())
Expect(bindingRequest.GetConfiguration().GetAppGUID()).
To(Equal(models.GUID("8d0cee08-23ad-4813-a779-ad8118ea0b91")))
// 🚧 To-do: Add a few more field-comparisons;
})
})
})
})
Loading
Loading