diff --git a/.github/scripts/generate-crd-docs/generate-crd-docs.sh b/.github/scripts/generate-crd-docs/generate-crd-docs.sh index bb00b948d4..a582a23c0e 100755 --- a/.github/scripts/generate-crd-docs/generate-crd-docs.sh +++ b/.github/scripts/generate-crd-docs/generate-crd-docs.sh @@ -58,11 +58,13 @@ for api_group in "$OPERATOR_API_ROOT"*; do mkdir -p "$OUTPUT_PATH" echo "Generating CRD docs for $sanitized_api_group.$API_DOMAIN/$sanitized_api_version..." + # max-depth should be bumped when the number of nested structures of CRDs will exceed 10 crd-ref-docs \ --templates-dir "$TEMPLATE_DIR" \ --source-path="./$api_version" \ --renderer="$RENDERER" \ --config "$RENDERER_CONFIG_FILE" \ + --max-depth 10 \ --output-path "$OUTPUT_PATH/_index.md" echo "---------------------" done @@ -100,11 +102,13 @@ for api_version in "$METRICS_API_ROOT"*; do mkdir -p "$OUTPUT_PATH" echo "Generating CRD docs for $sanitized_api_group.$API_DOMAIN/$sanitized_api_version..." + # max-depth should be bumped when the number of nested structures of CRDs will exceed 10 crd-ref-docs \ --templates-dir "$TEMPLATE_DIR" \ --source-path="./$api_version" \ --renderer="$RENDERER" \ --config "$RENDERER_CONFIG_FILE" \ + --max-depth 10 \ --output-path "$OUTPUT_PATH/_index.md" echo "---------------------" done diff --git a/docs/content/en/docs/crd-ref/metrics/v1alpha3/_index.md b/docs/content/en/docs/crd-ref/metrics/v1alpha3/_index.md index 1732420d81..42fabb0d24 100644 --- a/docs/content/en/docs/crd-ref/metrics/v1alpha3/_index.md +++ b/docs/content/en/docs/crd-ref/metrics/v1alpha3/_index.md @@ -13,6 +13,8 @@ description: Reference information for metrics.keptn.sh/v1alpha3 Package v1alpha3 contains API Schema definitions for the metrics v1alpha3 API group ### Resource Types +- [AnalysisDefinition](#analysisdefinition) +- [AnalysisDefinitionList](#analysisdefinitionlist) - [KeptnMetric](#keptnmetric) - [KeptnMetricList](#keptnmetriclist) - [KeptnMetricsProvider](#keptnmetricsprovider) @@ -20,6 +22,55 @@ Package v1alpha3 contains API Schema definitions for the metrics v1alpha3 API gr +#### AnalysisDefinition + + + +AnalysisDefinition is the Schema for the analysisdefinitions APIs + +_Appears in:_ +- [AnalysisDefinitionList](#analysisdefinitionlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `metrics.keptn.sh/v1alpha3` +| `kind` _string_ | `AnalysisDefinition` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[AnalysisDefinitionSpec](#analysisdefinitionspec)_ | | +| `status` _string_ | unused field | + + +#### AnalysisDefinitionList + + + +AnalysisDefinitionList contains a list of AnalysisDefinition + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `metrics.keptn.sh/v1alpha3` +| `kind` _string_ | `AnalysisDefinitionList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[AnalysisDefinition](#analysisdefinition) array_ | | + + +#### AnalysisDefinitionSpec + + + +AnalysisDefinitionSpec defines the desired state of AnalysisDefinition + +_Appears in:_ +- [AnalysisDefinition](#analysisdefinition) + +| Field | Description | +| --- | --- | +| `objectives` _[Objective](#objective) array_ | Objectives defines a list of objectives to evaluate for an analysis | +| `totalScore` _[TotalScore](#totalscore)_ | TotalScore defines the required score for an analysis to be successful | + + #### KeptnMetric @@ -138,6 +189,70 @@ _Appears in:_ | `secretKeyRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#secretkeyselector-v1-core)_ | SecretKeyRef defines an optional secret for access credentials to the metrics provider. | +#### ObjectReference + + + + + +_Appears in:_ +- [Objective](#objective) + +| Field | Description | +| --- | --- | +| `name` _string_ | Name defines the name of the referenced object | +| `namespace` _string_ | Namespace defines the namespace of the referenced object | + + +#### Objective + + + +Objective defines an objective for analysis + +_Appears in:_ +- [AnalysisDefinitionSpec](#analysisdefinitionspec) + +| Field | Description | +| --- | --- | +| `analysisValueTemplateRef` _[ObjectReference](#objectreference)_ | AnalysisValueTemplateRef refers to the appropriate AnalysisValueTemplate | +| `target` _[Target](#target)_ | Target defines failure or warning criteria | +| `weight` _integer_ | Weight can be used to emphasize the importance of one Objective over the others | +| `keyObjective` _boolean_ | KeyObjective defines whether the whole analysis fails when this objective's target is not met | + + +#### Operator + + + +Operator specifies the supported operators for value comparisons + +_Appears in:_ +- [Target](#target) + +| Field | Description | +| --- | --- | +| `lessThanOrEqual` _[OperatorValue](#operatorvalue)_ | LessThanOrEqual represents '<=' operator | +| `lessThan` _[OperatorValue](#operatorvalue)_ | LessThan represents '<' operator | +| `greaterThan` _[OperatorValue](#operatorvalue)_ | GreaterThan represents '>' operator | +| `greaterThanOrEqual` _[OperatorValue](#operatorvalue)_ | GreaterThanOrEqual represents '>=' operator | +| `equalTo` _[OperatorValue](#operatorvalue)_ | EqualTo represents '==' operator | + + +#### OperatorValue + + + +OperatorValue represents the value to which the result is compared + +_Appears in:_ +- [Operator](#operator) + +| Field | Description | +| --- | --- | +| `fixedValue` _Quantity_ | FixedValue defines the value for comparison | + + #### ProviderRef @@ -165,6 +280,36 @@ _Appears in:_ | --- | --- | | `interval` _string_ | Interval specifies the duration of the time interval for the data query | | `step` _string_ | Step represents the query resolution step width for the data query | -| `aggregation` _string_ | Aggregation defines as the type of aggregation function to be applied on the data. Accepted values: p90, p95, p99, max, min, avg, median | +| `aggregation` _string_ | Aggregation defines the type of aggregation function to be applied on the data. Accepted values: p90, p95, p99, max, min, avg, median | + + +#### Target + + + +Target defines the failure and warning criteria + +_Appears in:_ +- [Objective](#objective) + +| Field | Description | +| --- | --- | +| `failure` _[Operator](#operator)_ | Failure defines limits up to which an analysis fails | +| `warning` _[Operator](#operator)_ | Warning defines limits where the result does not pass or fail | + + +#### TotalScore + + + +TotalScore defines the required score for an analysis to be successful + +_Appears in:_ +- [AnalysisDefinitionSpec](#analysisdefinitionspec) + +| Field | Description | +| --- | --- | +| `passPercentage` _integer_ | PassPercentage defines the threshold to reach for an analysis to pass | +| `warningPercentage` _integer_ | WarningPercentage defines the threshold to reach for an analysis to pass with a 'warning' status | diff --git a/helm/chart/templates/analysisdefinition-crd.yaml b/helm/chart/templates/analysisdefinition-crd.yaml new file mode 100644 index 0000000000..e0121622b4 --- /dev/null +++ b/helm/chart/templates/analysisdefinition-crd.yaml @@ -0,0 +1,253 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: analysisdefinitions.metrics.keptn.sh + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app.kubernetes.io/part-of: keptn-lifecycle-toolkit + crdGroup: metrics.keptn.sh + keptn.sh/inject-cert: "true" + {{- include "chart.labels" . | nindent 4 }} +spec: + group: metrics.keptn.sh + names: + kind: AnalysisDefinition + listKind: AnalysisDefinitionList + plural: analysisdefinitions + singular: analysisdefinition + scope: Namespaced + versions: + - name: v1alpha3 + schema: + openAPIV3Schema: + description: AnalysisDefinition is the Schema for the analysisdefinitions APIs + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AnalysisDefinitionSpec defines the desired state of AnalysisDefinition + properties: + objectives: + description: Objectives defines a list of objectives to evaluate for + an analysis + items: + description: Objective defines an objective for analysis + properties: + analysisValueTemplateRef: + description: AnalysisValueTemplateRef refers to the appropriate + AnalysisValueTemplate + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + keyObjective: + default: false + description: KeyObjective defines if the objective fails when + the target is not met + type: boolean + target: + description: Target defines failure or warning criteria + properties: + failure: + description: Failure defines limits up to which an analysis + fails. + properties: + equalTo: + description: EqualTo represents '==' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThan: + description: GreaterThan represents '>' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThanOrEqual: + description: GreaterThanOrEqual represents '>=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThan: + description: LessThan represents '<' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThanOrEqual: + description: LessThanOrEqual represents '<=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + type: object + warning: + description: Warning defines limits where the result does + not pass or fail + properties: + equalTo: + description: EqualTo represents '==' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThan: + description: GreaterThan represents '>' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThanOrEqual: + description: GreaterThanOrEqual represents '>=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThan: + description: LessThan represents '<' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThanOrEqual: + description: LessThanOrEqual represents '<=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + type: object + type: object + weight: + default: 1 + description: Weight can be used to emphasize the importance of + one Objective over the others + type: integer + required: + - analysisValueTemplateRef + type: object + type: array + totalScore: + description: TotalScore defines the required score for an analysis to + be successful + properties: + passPercentage: + description: PassPercentage defines the threshold to reach for an + analysis to pass. + maximum: 100 + minimum: 0 + type: integer + warningPercentage: + description: WarningPercentage defines the threshold to reach for + an analysis to pass with a 'warning' status. + maximum: 100 + minimum: 0 + type: integer + required: + - passPercentage + - warningPercentage + type: object + required: + - totalScore + type: object + status: + description: unused field + type: string + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/metrics-operator/PROJECT b/metrics-operator/PROJECT index 35b682db16..12af78e4fd 100644 --- a/metrics-operator/PROJECT +++ b/metrics-operator/PROJECT @@ -53,4 +53,15 @@ resources: kind: KeptnMetricsProvider path: github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3 version: v1alpha3 +- api: + crdVersion: v1 + namespaced: true + domain: keptn.sh + group: metrics + kind: AnalysisDefinition + path: github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3 + version: v1alpha3 + webhooks: + validation: true + webhookVersion: v1 version: "3" diff --git a/metrics-operator/api/v1alpha3/analysisdefinition_types.go b/metrics-operator/api/v1alpha3/analysisdefinition_types.go new file mode 100644 index 0000000000..206907802c --- /dev/null +++ b/metrics-operator/api/v1alpha3/analysisdefinition_types.go @@ -0,0 +1,110 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha3 + +import ( + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AnalysisDefinitionSpec defines the desired state of AnalysisDefinition +type AnalysisDefinitionSpec struct { + // Objectives defines a list of objectives to evaluate for an analysis + Objectives []Objective `json:"objectives,omitempty"` + // TotalScore defines the required score for an analysis to be successful + TotalScore TotalScore `json:"totalScore"` +} + +// TotalScore defines the required score for an analysis to be successful +type TotalScore struct { + // PassPercentage defines the threshold to reach for an analysis to pass + // +kubebuilder:validation:Minimum:=0 + // +kubebuilder:validation:Maximum:=100 + PassPercentage int `json:"passPercentage"` + // WarningPercentage defines the threshold to reach for an analysis to pass with a 'warning' status + // +kubebuilder:validation:Minimum:=0 + // +kubebuilder:validation:Maximum:=100 + WarningPercentage int `json:"warningPercentage"` +} + +// Objective defines an objective for analysis +type Objective struct { + // AnalysisValueTemplateRef refers to the appropriate AnalysisValueTemplate + AnalysisValueTemplateRef ObjectReference `json:"analysisValueTemplateRef"` + // Target defines failure or warning criteria + Target Target `json:"target,omitempty"` + // Weight can be used to emphasize the importance of one Objective over the others + // +kubebuilder:default:=1 + Weight int `json:"weight,omitempty"` + // KeyObjective defines whether the whole analysis fails when this objective's target is not met + // +kubebuilder:default:=false + KeyObjective bool `json:"keyObjective,omitempty"` +} + +// Target defines the failure and warning criteria +type Target struct { + // Failure defines limits up to which an analysis fails + Failure *Operator `json:"failure,omitempty"` + // Warning defines limits where the result does not pass or fail + Warning *Operator `json:"warning,omitempty"` +} + +// OperatorValue represents the value to which the result is compared +type OperatorValue struct { + // FixedValue defines the value for comparison + FixedValue resource.Quantity `json:"fixedValue"` +} + +// Operator specifies the supported operators for value comparisons +type Operator struct { + // LessThanOrEqual represents '<=' operator + LessThanOrEqual *OperatorValue `json:"lessThanOrEqual,omitempty"` + // LessThan represents '<' operator + LessThan *OperatorValue `json:"lessThan,omitempty"` + // GreaterThan represents '>' operator + GreaterThan *OperatorValue `json:"greaterThan,omitempty"` + // GreaterThanOrEqual represents '>=' operator + GreaterThanOrEqual *OperatorValue `json:"greaterThanOrEqual,omitempty"` + // EqualTo represents '==' operator + EqualTo *OperatorValue `json:"equalTo,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// AnalysisDefinition is the Schema for the analysisdefinitions APIs +type AnalysisDefinition struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AnalysisDefinitionSpec `json:"spec,omitempty"` + // unused field + Status string `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// AnalysisDefinitionList contains a list of AnalysisDefinition +type AnalysisDefinitionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AnalysisDefinition `json:"items"` +} + +func init() { + SchemeBuilder.Register(&AnalysisDefinition{}, &AnalysisDefinitionList{}) +} diff --git a/metrics-operator/api/v1alpha3/analysisdefinition_webhook.go b/metrics-operator/api/v1alpha3/analysisdefinition_webhook.go new file mode 100644 index 0000000000..5ae251358f --- /dev/null +++ b/metrics-operator/api/v1alpha3/analysisdefinition_webhook.go @@ -0,0 +1,130 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha3 + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// log is for logging in this package. +var analysisdefinitionlog = logf.Log.WithName("analysisdefinition-webhook") + +func (r *AnalysisDefinition) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/validate-metrics-keptn-sh-v1alpha3-analysisdefinition,mutating=false,failurePolicy=fail,sideEffects=None,groups=metrics.keptn.sh,resources=analysisdefinitions,verbs=create;update,versions=v1alpha3,name=vanalysisdefinition.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &AnalysisDefinition{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *AnalysisDefinition) ValidateCreate() error { + analysisdefinitionlog.Info("validate create", "name", r.Name) + + for _, o := range r.Spec.Objectives { + if err := o.validate(); err != nil { + return err + } + } + + return r.Spec.TotalScore.validate() +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *AnalysisDefinition) ValidateUpdate(old runtime.Object) error { + analysisdefinitionlog.Info("validate update", "name", r.Name) + + for _, o := range r.Spec.Objectives { + if err := o.validate(); err != nil { + return err + } + } + + return r.Spec.TotalScore.validate() +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *AnalysisDefinition) ValidateDelete() error { + analysisdefinitionlog.Info("validate delete", "name", r.Name) + + return nil +} + +func (s *TotalScore) validate() error { + if s.WarningPercentage >= s.PassPercentage { + return fmt.Errorf("Warn percentage score cannot be higher or equal than Pass percentage score") + } + return nil +} + +func (o *Objective) validate() error { + if err := o.Target.validate(); err != nil { + return err + } + return nil +} + +func (t *Target) validate() error { + if t.Failure == nil && t.Warning != nil { + return fmt.Errorf("Warning criteria cannot be set without Failure criteria") + } + if t.Failure != nil { + if err := t.Failure.validate(); err != nil { + return err + } + } + + if t.Warning != nil { + if err := t.Warning.validate(); err != nil { + return err + } + } + return nil +} + +func (o *Operator) validate() error { + counter := 0 + if o.LessThan != nil { + counter++ + } + if o.LessThanOrEqual != nil { + counter++ + } + if o.GreaterThan != nil { + counter++ + } + if o.GreaterThanOrEqual != nil { + counter++ + } + if o.EqualTo != nil { + counter++ + } + if counter > 1 { + return fmt.Errorf("Operator: multiple operators can not be set") + } + if counter == 0 { + return fmt.Errorf("Operator: no operator set") + } + return nil +} diff --git a/metrics-operator/api/v1alpha3/analysisdefinition_webhook_test.go b/metrics-operator/api/v1alpha3/analysisdefinition_webhook_test.go new file mode 100644 index 0000000000..7f93955e6c --- /dev/null +++ b/metrics-operator/api/v1alpha3/analysisdefinition_webhook_test.go @@ -0,0 +1,314 @@ +package v1alpha3 + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/resource" +) + +func TestOperator_validate(t *testing.T) { + tests := []struct { + name string + operator Operator + wantErr error + }{ + { + name: "no operator set", + operator: Operator{}, + wantErr: fmt.Errorf("Operator: no operator set"), + }, + { + name: "multiple operators set", + operator: Operator{ + LessThanOrEqual: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + LessThan: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + wantErr: fmt.Errorf("Operator: multiple operators can not be set"), + }, + { + name: "happy path", + operator: Operator{ + LessThanOrEqual: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.wantErr, tt.operator.validate()) + }) + } +} + +func TestScore_validate(t *testing.T) { + tests := []struct { + name string + score TotalScore + wantErr error + }{ + { + name: "happy path", + score: TotalScore{ + PassPercentage: 90, + WarningPercentage: 80, + }, + wantErr: nil, + }, + { + name: "warn and pass equal", + score: TotalScore{ + PassPercentage: 90, + WarningPercentage: 90, + }, + wantErr: fmt.Errorf("Warn percentage score cannot be higher or equal than Pass percentage score"), + }, + { + name: "warn higher than pass", + score: TotalScore{ + PassPercentage: 90, + WarningPercentage: 95, + }, + wantErr: fmt.Errorf("Warn percentage score cannot be higher or equal than Pass percentage score"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.wantErr, tt.score.validate()) + }) + } +} + +func TestObjective_validate(t *testing.T) { + tests := []struct { + name string + obj Objective + wantErr error + }{ + { + name: "no Target set", + obj: Objective{}, + wantErr: nil, + }, + { + name: "only warning set", + obj: Objective{ + Target: Target{ + Warning: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + }, + wantErr: fmt.Errorf("Warning criteria cannot be set without Failure criteria"), + }, + { + name: "warning and failure set properly", + obj: Objective{ + Target: Target{ + Warning: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + Failure: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.wantErr, tt.obj.validate()) + }) + } +} + +func TestAnalysisDefinition_validateCreateUpdate(t *testing.T) { + tests := []struct { + name string + obj AnalysisDefinition + wantErr error + }{ + { + name: "failure path - objective", + obj: AnalysisDefinition{ + Spec: AnalysisDefinitionSpec{ + Objectives: []Objective{ + { + Target: Target{ + Failure: &Operator{}, + }, + }, + }, + }, + }, + wantErr: fmt.Errorf("Operator: no operator set"), + }, + { + name: "failure path - score", + obj: AnalysisDefinition{ + Spec: AnalysisDefinitionSpec{ + Objectives: []Objective{ + { + Target: Target{ + Failure: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + }, + }, + TotalScore: TotalScore{ + PassPercentage: 80, + WarningPercentage: 90, + }, + }, + }, + wantErr: fmt.Errorf("Warn percentage score cannot be higher or equal than Pass percentage score"), + }, + { + name: "happy path", + obj: AnalysisDefinition{ + Spec: AnalysisDefinitionSpec{ + Objectives: []Objective{ + { + Target: Target{ + Failure: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + }, + }, + TotalScore: TotalScore{ + PassPercentage: 80, + WarningPercentage: 70, + }, + }, + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.wantErr, tt.obj.ValidateCreate()) + require.Equal(t, tt.wantErr, tt.obj.ValidateUpdate(&AnalysisDefinition{})) + }) + } +} + +func TestTarget_validate(t *testing.T) { + tests := []struct { + name string + target Target + wantErr error + }{ + { + name: "neither Failure and Warning set", + target: Target{}, + wantErr: nil, + }, + { + name: "Failure set", + target: Target{ + Failure: &Operator{ + LessThanOrEqual: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + wantErr: nil, + }, + { + name: "only warning set", + target: Target{ + Warning: &Operator{ + LessThanOrEqual: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + wantErr: fmt.Errorf("Warning criteria cannot be set without Failure criteria"), + }, + { + name: "neither failure nor warning set", + target: Target{}, + wantErr: nil, + }, + { + name: "only failure set", + target: Target{ + Failure: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + wantErr: nil, + }, + { + name: "only warning set", + target: Target{ + Warning: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + wantErr: fmt.Errorf("Warning criteria cannot be set without Failure criteria"), + }, + { + name: "warning not set properly", + target: Target{ + Warning: &Operator{}, + Failure: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + wantErr: fmt.Errorf("Operator: no operator set"), + }, + { + name: "warning and failure set properly", + target: Target{ + Warning: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + Failure: &Operator{ + EqualTo: &OperatorValue{ + FixedValue: *resource.NewQuantity(5, resource.DecimalSI), + }, + }, + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.wantErr, tt.target.validate()) + }) + } +} diff --git a/metrics-operator/api/v1alpha3/common.go b/metrics-operator/api/v1alpha3/common.go new file mode 100644 index 0000000000..149c8f4d3c --- /dev/null +++ b/metrics-operator/api/v1alpha3/common.go @@ -0,0 +1,8 @@ +package v1alpha3 + +type ObjectReference struct { + // Name defines the name of the referenced object + Name string `json:"name"` + // Namespace defines the namespace of the referenced object + Namespace string `json:"namespace,omitempty"` +} diff --git a/metrics-operator/api/v1alpha3/keptnmetric_types.go b/metrics-operator/api/v1alpha3/keptnmetric_types.go index 823773a6f9..5d65858a11 100644 --- a/metrics-operator/api/v1alpha3/keptnmetric_types.go +++ b/metrics-operator/api/v1alpha3/keptnmetric_types.go @@ -60,7 +60,7 @@ type RangeSpec struct { Interval string `json:"interval,omitempty"` // Step represents the query resolution step width for the data query Step string `json:"step,omitempty"` - // Aggregation defines as the type of aggregation function to be applied on the data. Accepted values: p90, p95, p99, max, min, avg, median + // Aggregation defines the type of aggregation function to be applied on the data. Accepted values: p90, p95, p99, max, min, avg, median // +kubebuilder:validation:Enum:=p90;p95;p99;max;min;avg;median Aggregation string `json:"aggregation,omitempty"` } diff --git a/metrics-operator/api/v1alpha3/zz_generated.deepcopy.go b/metrics-operator/api/v1alpha3/zz_generated.deepcopy.go index e52006c8e0..ea277610c9 100644 --- a/metrics-operator/api/v1alpha3/zz_generated.deepcopy.go +++ b/metrics-operator/api/v1alpha3/zz_generated.deepcopy.go @@ -25,6 +25,87 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AnalysisDefinition) DeepCopyInto(out *AnalysisDefinition) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnalysisDefinition. +func (in *AnalysisDefinition) DeepCopy() *AnalysisDefinition { + if in == nil { + return nil + } + out := new(AnalysisDefinition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AnalysisDefinition) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AnalysisDefinitionList) DeepCopyInto(out *AnalysisDefinitionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AnalysisDefinition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnalysisDefinitionList. +func (in *AnalysisDefinitionList) DeepCopy() *AnalysisDefinitionList { + if in == nil { + return nil + } + out := new(AnalysisDefinitionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AnalysisDefinitionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AnalysisDefinitionSpec) DeepCopyInto(out *AnalysisDefinitionSpec) { + *out = *in + if in.Objectives != nil { + in, out := &in.Objectives, &out.Objectives + *out = make([]Objective, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.TotalScore = in.TotalScore +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnalysisDefinitionSpec. +func (in *AnalysisDefinitionSpec) DeepCopy() *AnalysisDefinitionSpec { + if in == nil { + return nil + } + out := new(AnalysisDefinitionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KeptnMetric) DeepCopyInto(out *KeptnMetric) { *out = *in @@ -200,6 +281,94 @@ func (in *KeptnMetricsProviderSpec) DeepCopy() *KeptnMetricsProviderSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. +func (in *ObjectReference) DeepCopy() *ObjectReference { + if in == nil { + return nil + } + out := new(ObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Objective) DeepCopyInto(out *Objective) { + *out = *in + out.AnalysisValueTemplateRef = in.AnalysisValueTemplateRef + in.Target.DeepCopyInto(&out.Target) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Objective. +func (in *Objective) DeepCopy() *Objective { + if in == nil { + return nil + } + out := new(Objective) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Operator) DeepCopyInto(out *Operator) { + *out = *in + if in.LessThanOrEqual != nil { + in, out := &in.LessThanOrEqual, &out.LessThanOrEqual + *out = new(OperatorValue) + (*in).DeepCopyInto(*out) + } + if in.LessThan != nil { + in, out := &in.LessThan, &out.LessThan + *out = new(OperatorValue) + (*in).DeepCopyInto(*out) + } + if in.GreaterThan != nil { + in, out := &in.GreaterThan, &out.GreaterThan + *out = new(OperatorValue) + (*in).DeepCopyInto(*out) + } + if in.GreaterThanOrEqual != nil { + in, out := &in.GreaterThanOrEqual, &out.GreaterThanOrEqual + *out = new(OperatorValue) + (*in).DeepCopyInto(*out) + } + if in.EqualTo != nil { + in, out := &in.EqualTo, &out.EqualTo + *out = new(OperatorValue) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Operator. +func (in *Operator) DeepCopy() *Operator { + if in == nil { + return nil + } + out := new(Operator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorValue) DeepCopyInto(out *OperatorValue) { + *out = *in + out.FixedValue = in.FixedValue.DeepCopy() +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorValue. +func (in *OperatorValue) DeepCopy() *OperatorValue { + if in == nil { + return nil + } + out := new(OperatorValue) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProviderRef) DeepCopyInto(out *ProviderRef) { *out = *in @@ -229,3 +398,43 @@ func (in *RangeSpec) DeepCopy() *RangeSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Target) DeepCopyInto(out *Target) { + *out = *in + if in.Failure != nil { + in, out := &in.Failure, &out.Failure + *out = new(Operator) + (*in).DeepCopyInto(*out) + } + if in.Warning != nil { + in, out := &in.Warning, &out.Warning + *out = new(Operator) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. +func (in *Target) DeepCopy() *Target { + if in == nil { + return nil + } + out := new(Target) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TotalScore) DeepCopyInto(out *TotalScore) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TotalScore. +func (in *TotalScore) DeepCopy() *TotalScore { + if in == nil { + return nil + } + out := new(TotalScore) + in.DeepCopyInto(out) + return out +} diff --git a/metrics-operator/config/crd/bases/metrics.keptn.sh_analysisdefinitions.yaml b/metrics-operator/config/crd/bases/metrics.keptn.sh_analysisdefinitions.yaml new file mode 100644 index 0000000000..6d26b70ba5 --- /dev/null +++ b/metrics-operator/config/crd/bases/metrics.keptn.sh_analysisdefinitions.yaml @@ -0,0 +1,246 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: analysisdefinitions.metrics.keptn.sh +spec: + group: metrics.keptn.sh + names: + kind: AnalysisDefinition + listKind: AnalysisDefinitionList + plural: analysisdefinitions + singular: analysisdefinition + scope: Namespaced + versions: + - name: v1alpha3 + schema: + openAPIV3Schema: + description: AnalysisDefinition is the Schema for the analysisdefinitions + APIs + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AnalysisDefinitionSpec defines the desired state of AnalysisDefinition + properties: + objectives: + description: Objectives defines a list of objectives to evaluate for + an analysis + items: + description: Objective defines an objective for analysis + properties: + analysisValueTemplateRef: + description: AnalysisValueTemplateRef refers to the appropriate + AnalysisValueTemplate + properties: + name: + description: Name defines the name of the referenced object + type: string + namespace: + description: Namespace defines the namespace of the referenced + object + type: string + required: + - name + type: object + keyObjective: + default: false + description: KeyObjective defines whether the whole analysis + fails when this objective's target is not met + type: boolean + target: + description: Target defines failure or warning criteria + properties: + failure: + description: Failure defines limits up to which an analysis + fails + properties: + equalTo: + description: EqualTo represents '==' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThan: + description: GreaterThan represents '>' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThanOrEqual: + description: GreaterThanOrEqual represents '>=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThan: + description: LessThan represents '<' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThanOrEqual: + description: LessThanOrEqual represents '<=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + type: object + warning: + description: Warning defines limits where the result does + not pass or fail + properties: + equalTo: + description: EqualTo represents '==' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThan: + description: GreaterThan represents '>' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + greaterThanOrEqual: + description: GreaterThanOrEqual represents '>=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThan: + description: LessThan represents '<' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + lessThanOrEqual: + description: LessThanOrEqual represents '<=' operator + properties: + fixedValue: + anyOf: + - type: integer + - type: string + description: FixedValue defines the value for comparison + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - fixedValue + type: object + type: object + type: object + weight: + default: 1 + description: Weight can be used to emphasize the importance + of one Objective over the others + type: integer + required: + - analysisValueTemplateRef + type: object + type: array + totalScore: + description: TotalScore defines the required score for an analysis + to be successful + properties: + passPercentage: + description: PassPercentage defines the threshold to reach for + an analysis to pass + maximum: 100 + minimum: 0 + type: integer + warningPercentage: + description: WarningPercentage defines the threshold to reach + for an analysis to pass with a 'warning' status + maximum: 100 + minimum: 0 + type: integer + required: + - passPercentage + - warningPercentage + type: object + required: + - totalScore + type: object + status: + description: unused field + type: string + type: object + served: true + storage: true + subresources: + status: {} diff --git a/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml b/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml index cca55c3374..80af18b0c3 100644 --- a/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml +++ b/metrics-operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml @@ -220,7 +220,7 @@ spec: be queried properties: aggregation: - description: 'Aggregation defines as the type of aggregation function + description: 'Aggregation defines the type of aggregation function to be applied on the data. Accepted values: p90, p95, p99, max, min, avg, median' enum: diff --git a/metrics-operator/config/crd/kustomization.yaml b/metrics-operator/config/crd/kustomization.yaml index 9af3c8a8b6..e32b655345 100644 --- a/metrics-operator/config/crd/kustomization.yaml +++ b/metrics-operator/config/crd/kustomization.yaml @@ -4,6 +4,7 @@ resources: - bases/metrics.keptn.sh_keptnmetricsproviders.yaml - bases/metrics.keptn.sh_keptnmetrics.yaml + - bases/metrics.keptn.sh_analysisdefinitions.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -11,12 +12,14 @@ patchesStrategicMerge: # patches here are for enabling the conversion webhook for each CRD # - patches/webhook_in_keptnmetricsproviders.yaml - patches/webhook_in_keptnmetrics.yaml + # - patches/webhook_in_analysisdefinitions.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD # - patches/cainjection_in_keptnmetricsproviders.yaml # - patches/cainjection_in_keptnmetrics.yaml +# - patches/cainjection_in_analysisdefinitions.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/metrics-operator/config/rbac/analysisdefinition_editor_role.yaml b/metrics-operator/config/rbac/analysisdefinition_editor_role.yaml new file mode 100644 index 0000000000..5794272980 --- /dev/null +++ b/metrics-operator/config/rbac/analysisdefinition_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit analysisdefinitions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: analysisdefinition-editor-role +rules: + - apiGroups: + - metrics.keptn.sh + resources: + - analysisdefinitions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - metrics.keptn.sh + resources: + - analysisdefinitions/status + verbs: + - get diff --git a/metrics-operator/config/rbac/analysisdefinition_viewer_role.yaml b/metrics-operator/config/rbac/analysisdefinition_viewer_role.yaml new file mode 100644 index 0000000000..4d6226eff0 --- /dev/null +++ b/metrics-operator/config/rbac/analysisdefinition_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view analysisdefinitions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: analysisdefinition-viewer-role +rules: + - apiGroups: + - metrics.keptn.sh + resources: + - analysisdefinitions + verbs: + - get + - list + - watch + - apiGroups: + - metrics.keptn.sh + resources: + - analysisdefinitions/status + verbs: + - get diff --git a/metrics-operator/config/samples/metrics_v1alpha3_analysisdefinition.yaml b/metrics-operator/config/samples/metrics_v1alpha3_analysisdefinition.yaml new file mode 100644 index 0000000000..9898bb5892 --- /dev/null +++ b/metrics-operator/config/samples/metrics_v1alpha3_analysisdefinition.yaml @@ -0,0 +1,21 @@ +apiVersion: metrics.keptn.sh/v1alpha3 +kind: AnalysisDefinition +metadata: + name: analysisdefinition-sample +spec: + objectives: + - analysisValueTemplateRef: + name: response_time_p95 + namespace: keptn-lifecycle-toolkit-system + target: + failure: + lessThan: + fixedValue: 600 + warning: + greaterThanOrEqual: + fixedValue: 800 + weight: 1 + keyObjective: false + totalScore: + passPercentage: 90 + warningPercentage: 75 diff --git a/metrics-operator/config/webhook/manifests.yaml b/metrics-operator/config/webhook/manifests.yaml index f7444c6444..d855e38641 100644 --- a/metrics-operator/config/webhook/manifests.yaml +++ b/metrics-operator/config/webhook/manifests.yaml @@ -27,3 +27,23 @@ webhooks: resources: - keptnmetrics sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: metrics-webhook-service + namespace: system + path: /validate-metrics-keptn-sh-v1alpha3-analysisdefinition + failurePolicy: Fail + name: vanalysisdefinition.kb.io + rules: + - apiGroups: + - metrics.keptn.sh + apiVersions: + - v1alpha3 + operations: + - CREATE + - UPDATE + resources: + - analysisdefinitions + sideEffects: None diff --git a/metrics-operator/main.go b/metrics-operator/main.go index bc99c602b7..3d15767bd4 100644 --- a/metrics-operator/main.go +++ b/metrics-operator/main.go @@ -69,6 +69,7 @@ type envConfig struct { ExposeKeptnMetrics bool `envconfig:"EXPOSE_KEPTN_METRICS" default:"true"` } +//nolint:gocyclo func main() { var env envConfig if err := envconfig.Process("", &env); err != nil { @@ -140,6 +141,10 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "KeptnMetric") os.Exit(1) } + if err = (&metricsv1alpha3.AnalysisDefinition{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "AnalysisDefinition") + os.Exit(1) + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/test/integration/validate-analysisdefinition/00-teststep-install.yaml b/test/integration/validate-analysisdefinition/00-teststep-install.yaml new file mode 100644 index 0000000000..a75e0d1c47 --- /dev/null +++ b/test/integration/validate-analysisdefinition/00-teststep-install.yaml @@ -0,0 +1,8 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +apply: + - analysisdef_good.yaml + - analysisdef_good_empty_fail.yaml +commands: + - command: kubectl apply -f analysisdef_bad.yaml + ignoreFailure: true # we must install ignoring the validating webhook error to proceed with the test diff --git a/test/integration/validate-analysisdefinition/01-teststep-assert.yaml b/test/integration/validate-analysisdefinition/01-teststep-assert.yaml new file mode 100644 index 0000000000..6619f039a7 --- /dev/null +++ b/test/integration/validate-analysisdefinition/01-teststep-assert.yaml @@ -0,0 +1,7 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +error: # this checks that kubectl get resource fails, AKA bad CRD not added + - analysisdef_bad.yaml +assert: # this checks that kubectl get resource succeeds + - analysisdef_good.yaml + - analysisdef_good_empty_fail.yaml diff --git a/test/integration/validate-analysisdefinition/analysisdef_bad.yaml b/test/integration/validate-analysisdefinition/analysisdef_bad.yaml new file mode 100644 index 0000000000..579fe2fa68 --- /dev/null +++ b/test/integration/validate-analysisdefinition/analysisdef_bad.yaml @@ -0,0 +1,24 @@ +apiVersion: metrics.keptn.sh/v1alpha3 +kind: AnalysisDefinition +metadata: + name: analysisdefinition-bad +spec: + objectives: + - analysisValueTemplateRef: + name: response_time_p95 + namespace: keptn-lifecycle-toolkit-system + target: + failure: + # multiple operators should not be defined as part of one item, but as 2 separate list items + lessThan: + fixedValue: 600 + greaterThan: + fixedValue: 600 + warning: + greaterThanOrEqual: + fixedValue: 800 + weight: 1 + keyObjective: false + totalScore: + passPercentage: 90 + warningPercentage: 75 diff --git a/test/integration/validate-analysisdefinition/analysisdef_good.yaml b/test/integration/validate-analysisdefinition/analysisdef_good.yaml new file mode 100644 index 0000000000..0f25a6bc75 --- /dev/null +++ b/test/integration/validate-analysisdefinition/analysisdef_good.yaml @@ -0,0 +1,21 @@ +apiVersion: metrics.keptn.sh/v1alpha3 +kind: AnalysisDefinition +metadata: + name: analysisdefinition +spec: + objectives: + - analysisValueTemplateRef: + name: response_time_p95 + namespace: keptn-lifecycle-toolkit-system + target: + failure: + lessThan: + fixedValue: 600 + warning: + greaterThanOrEqual: + fixedValue: 800 + weight: 1 + keyObjective: false + totalScore: + passPercentage: 90 + warningPercentage: 75 diff --git a/test/integration/validate-analysisdefinition/analysisdef_good_empty_fail.yaml b/test/integration/validate-analysisdefinition/analysisdef_good_empty_fail.yaml new file mode 100644 index 0000000000..7ed516b029 --- /dev/null +++ b/test/integration/validate-analysisdefinition/analysisdef_good_empty_fail.yaml @@ -0,0 +1,14 @@ +apiVersion: metrics.keptn.sh/v1alpha3 +kind: AnalysisDefinition +metadata: + name: analysisdefinition-empty +spec: + objectives: + - analysisValueTemplateRef: + name: response_time_p95 + namespace: keptn-lifecycle-toolkit-system + weight: 1 + keyObjective: false + totalScore: + passPercentage: 90 + warningPercentage: 75