Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SAPBTPCFS-7876] Optimize handling of non-transient errors #375

Merged
merged 43 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1469afe
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 4, 2023
d1e9565
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 4, 2023
b5b904d
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 4, 2023
4baf2a2
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 4, 2023
dd373f0
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 4, 2023
1519b54
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 4, 2023
e882c72
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 5, 2023
a4714f8
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 5, 2023
8069aa1
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 5, 2023
5ac9c7b
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 5, 2023
7c9ae23
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 6, 2023
672a596
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 6, 2023
c17ca72
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 6, 2023
3dba10f
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 6, 2023
0843bff
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 7, 2023
752ae8f
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 7, 2023
561a977
Merge remote-tracking branch 'origin/main' into transient_error
kerenlahav Dec 10, 2023
635aa5f
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 11, 2023
2753508
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 11, 2023
2a17b4a
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 11, 2023
6adea66
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 11, 2023
cb31706
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 11, 2023
e5d8601
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 11, 2023
140e4a6
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 13, 2023
4ab647e
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 14, 2023
ac865dd
Merge remote-tracking branch 'origin/transient_error' into transient_…
kerenlahav Dec 17, 2023
3a0001c
Merge branch 'main' into transient_error
kerenlahav Dec 17, 2023
a2a4865
Merge remote-tracking branch 'origin/transient_error' into transient_…
kerenlahav Dec 18, 2023
c7b6f66
review
kerenlahav Dec 18, 2023
e8c737b
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 18, 2023
4e3dfad
review
kerenlahav Dec 18, 2023
bd3eb96
go import
kerenlahav Dec 19, 2023
6708dc9
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 19, 2023
2c813f0
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 19, 2023
6f3fc96
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 19, 2023
2a90781
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 19, 2023
d54080b
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 19, 2023
2555545
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 19, 2023
911ac91
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 19, 2023
ca9e9f9
review
evyaffe Dec 19, 2023
997f0fc
remove test focus
evyaffe Dec 19, 2023
9576626
[SAPBTPCFS-7876] Optimize handling of non-transient errors
I065450 Dec 20, 2023
9e7269d
go import
kerenlahav Dec 20, 2023
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
26 changes: 15 additions & 11 deletions api/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import (
type ControllerName string

const (
ServiceInstanceController ControllerName = "ServiceInstance"
ServiceBindingController ControllerName = "ServiceBinding"
FinalizerName string = "services.cloud.sap.com/sap-btp-finalizer"
StaleBindingIDLabel string = "services.cloud.sap.com/stale"
StaleBindingRotationOfLabel string = "services.cloud.sap.com/rotationOf"
ForceRotateAnnotation string = "services.cloud.sap.com/forceRotate"
PreventDeletion string = "services.cloud.sap.com/preventDeletion"
UseInstanceMetadataNameInSecret string = "services.cloud.sap.com/useInstanceMetadataName"
ServiceInstanceController ControllerName = "ServiceInstance"
ServiceBindingController ControllerName = "ServiceBinding"
FinalizerName string = "services.cloud.sap.com/sap-btp-finalizer"
StaleBindingIDLabel string = "services.cloud.sap.com/stale"
StaleBindingRotationOfLabel string = "services.cloud.sap.com/rotationOf"
ForceRotateAnnotation string = "services.cloud.sap.com/forceRotate"
PreventDeletion string = "services.cloud.sap.com/preventDeletion"
UseInstanceMetadataNameInSecret string = "services.cloud.sap.com/useInstanceMetadataName"
IgnoreNonTransientErrorAnnotation string = "services.cloud.sap.com/ignoreNonTransientError"
IgnoreNonTransientErrorTimestampAnnotation string = "services.cloud.sap.com/ignoreNonTransientErrorTimestamp"
)

type HTTPStatusCodeError struct {
Expand All @@ -33,16 +35,16 @@ type HTTPStatusCodeError struct {
}

func (e HTTPStatusCodeError) Error() string {
errorMessage := "<nil>"
description := "<nil>"
errorMessage := ""
description := ""

if e.ErrorMessage != nil {
errorMessage = *e.ErrorMessage
}
if e.Description != nil {
description = *e.Description
}
return fmt.Sprintf("Status: %v; ErrorMessage: %v; Description: %v; ResponseError: %v", e.StatusCode, errorMessage, description, e.ResponseError)
return fmt.Sprintf("BrokerError:%s, Status: %d, Description: %s", errorMessage, e.StatusCode, description)
}

const (
Expand Down Expand Up @@ -79,4 +81,6 @@ type SAPBTPResource interface {
DeepClone() SAPBTPResource
SetReady(metav1.ConditionStatus)
GetReady() metav1.ConditionStatus
GetAnnotations() map[string]string
SetAnnotations(map[string]string)
}
8 changes: 8 additions & 0 deletions api/v1/servicebinding_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ func (sb *ServiceBinding) SetReady(ready metav1.ConditionStatus) {
sb.Status.Ready = ready
}

func (sb *ServiceBinding) GetAnnotations() map[string]string {
return sb.Annotations
}

func (sb *ServiceBinding) SetAnnotations(annotations map[string]string) {
sb.Annotations = annotations
}

// +kubebuilder:object:root=true

// ServiceBindingList contains a list of ServiceBinding
Expand Down
8 changes: 8 additions & 0 deletions api/v1/servicebinding_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,12 @@ var _ = Describe("Service Binding Type Test", func() {
binding.SetStatus(status)
Expect(binding.GetStatus()).To(Equal(status))
})

It("should update annotation", func() {
annotation := map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
}
binding.SetAnnotations(annotation)
Expect(binding.GetAnnotations()).To(Equal(annotation))
})
})
16 changes: 16 additions & 0 deletions api/v1/servicebinding_validating_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

// log is for logging in this package.
var servicebindinglog = logf.Log.WithName("servicebinding-resource")
var AnnotationNotSupportedError = "The specified annotation '%s' is not supported within the service binding object."

func (sb *ServiceBinding) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
Expand All @@ -49,6 +50,9 @@ var _ webhook.Validator = &ServiceBinding{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (sb *ServiceBinding) ValidateCreate() (admission.Warnings, error) {
servicebindinglog.Info("validate create", "name", sb.Name)
if err := sb.validateAnnotations(); err != nil {
return nil, err
}
if sb.Spec.CredRotationPolicy != nil {
if err := sb.validateCredRotatingConfig(); err != nil {
return nil, err
Expand All @@ -60,6 +64,9 @@ func (sb *ServiceBinding) ValidateCreate() (admission.Warnings, error) {
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (sb *ServiceBinding) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
servicebindinglog.Info("validate update", "name", sb.Name)
if err := sb.validateAnnotations(); err != nil {
return nil, err
}
if sb.Spec.CredRotationPolicy != nil {
if err := sb.validateCredRotatingConfig(); err != nil {
return nil, err
Expand Down Expand Up @@ -124,3 +131,12 @@ func (sb *ServiceBinding) validateCredRotatingConfig() error {

return nil
}

func (sb *ServiceBinding) validateAnnotations() error {
if sb.Annotations != nil {
if _, ok := sb.Annotations[api.IgnoreNonTransientErrorAnnotation]; ok {
return fmt.Errorf(AnnotationNotSupportedError, api.IgnoreNonTransientErrorAnnotation)
}
}
return nil
}
22 changes: 22 additions & 0 deletions api/v1/servicebinding_validating_webhook_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v1

import (
"fmt"
"github.com/SAP/sap-btp-service-operator/api"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -19,6 +20,15 @@ var _ = Describe("Service Binding Webhook Test", func() {
_, err := binding.ValidateCreate()
Expect(err).ToNot(HaveOccurred())
})
It("should failed", func() {
binding.Annotations = map[string]string{
api.IgnoreNonTransientErrorAnnotation: "false",
}
_, err := binding.ValidateCreate()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(AnnotationNotSupportedError, api.IgnoreNonTransientErrorAnnotation)))

})
})

Context("Validate update of spec before binding is created (failure recovery)", func() {
Expand Down Expand Up @@ -109,6 +119,18 @@ var _ = Describe("Service Binding Webhook Test", func() {
Expect(err).ToNot(HaveOccurred())
})
})
When("Annotation changed", func() {
It("should fail", func() {
newBinding.Annotations = map[string]string{
api.IgnoreNonTransientErrorAnnotation: "false",
}
_, err := newBinding.ValidateUpdate(binding)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(AnnotationNotSupportedError, api.IgnoreNonTransientErrorAnnotation)))

})
})

})

Context("Validate update of spec after binding is created", func() {
Expand Down
7 changes: 7 additions & 0 deletions api/v1/serviceinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ func (si *ServiceInstance) GetReady() metav1.ConditionStatus {
func (si *ServiceInstance) SetReady(ready metav1.ConditionStatus) {
si.Status.Ready = ready
}
func (si *ServiceInstance) GetAnnotations() map[string]string {
return si.Annotations
}

func (si *ServiceInstance) SetAnnotations(annotations map[string]string) {
si.Annotations = annotations
}

// +kubebuilder:object:root=true

Expand Down
8 changes: 8 additions & 0 deletions api/v1/serviceinstance_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,12 @@ var _ = Describe("Service Instance Type Test", func() {
instance.SetStatus(status)
Expect(instance.GetStatus()).To(Equal(status))
})

It("should update annotation", func() {
annotation := map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
}
instance.SetAnnotations(annotation)
Expect(instance.GetAnnotations()).To(Equal(annotation))
})
})
27 changes: 25 additions & 2 deletions api/v1/serviceinstance_validating_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package v1
import (
"fmt"
"strings"
"time"

"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -29,6 +30,11 @@ import (
"github.com/SAP/sap-btp-service-operator/api"
)

const (
annotationInFutureError = "Annotation %s cannot be a date in the future."
annotationNotValidTimestampError = "Annotation %s is not a valid timestamp"
)

func (si *ServiceInstance) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(si).
Expand All @@ -43,7 +49,7 @@ var _ webhook.Validator = &ServiceInstance{}
var serviceinstancelog = logf.Log.WithName("serviceinstance-resource")

func (si *ServiceInstance) ValidateCreate() (warnings admission.Warnings, err error) {
return nil, nil
return nil, si.validateNonTransientTimestampAnnotation()
}

func (si *ServiceInstance) ValidateUpdate(old runtime.Object) (warnings admission.Warnings, err error) {
Expand All @@ -53,7 +59,7 @@ func (si *ServiceInstance) ValidateUpdate(old runtime.Object) (warnings admissio
if oldInstance.Spec.BTPAccessCredentialsSecret != si.Spec.BTPAccessCredentialsSecret {
return nil, fmt.Errorf("changing the btpAccessCredentialsSecret for an existing instance is not allowed")
}
return nil, nil
return nil, si.validateNonTransientTimestampAnnotation()
}

func (si *ServiceInstance) ValidateDelete() (warnings admission.Warnings, err error) {
Expand All @@ -66,3 +72,20 @@ func (si *ServiceInstance) ValidateDelete() (warnings admission.Warnings, err er
}
return nil, nil
}

func (si *ServiceInstance) validateNonTransientTimestampAnnotation() error {
if len(si.Annotations) > 0 && len(si.Annotations[api.IgnoreNonTransientErrorAnnotation]) > 0 {
serviceinstancelog.Info(fmt.Sprintf("%s annotation exist, validating %s annotation", api.IgnoreNonTransientErrorAnnotation, api.IgnoreNonTransientErrorTimestampAnnotation))
annotationTime, err := time.Parse(time.RFC3339, si.Annotations[api.IgnoreNonTransientErrorTimestampAnnotation])
if err != nil {
serviceinstancelog.Error(err, fmt.Sprintf("failed to parse %s", api.IgnoreNonTransientErrorTimestampAnnotation))
return fmt.Errorf(annotationNotValidTimestampError, api.IgnoreNonTransientErrorTimestampAnnotation)
}

if time.Since(annotationTime) < 0 {
return fmt.Errorf(annotationInFutureError, api.IgnoreNonTransientErrorTimestampAnnotation)
}
}

return nil
}
112 changes: 112 additions & 0 deletions api/v1/serviceinstance_validating_webhook_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package v1

import (
"fmt"
"github.com/SAP/sap-btp-service-operator/api"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"time"
)

var _ = Describe("Service Instance Webhook Test", func() {
Expand All @@ -24,6 +26,27 @@ var _ = Describe("Service Instance Webhook Test", func() {
Expect(err.Error()).To(ContainSubstring("changing the btpAccessCredentialsSecret for an existing instance is not allowed"))
})
})
When("service instance IgnoreNonTransientErrorTimestampAnnotation annotation is not set with not a date", func() {
It("should return error from webhook", func() {
instance.Annotations = map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
api.IgnoreNonTransientErrorTimestampAnnotation: "true",
}
_, err := instance.ValidateUpdate(instance)
Expect(err).To(HaveOccurred())

})
})
When("service instance IgnoreNonTransientErrorTimestampAnnotation annotation is not set with future date", func() {
It("should return error from webhook", func() {
instance.Annotations = map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
api.IgnoreNonTransientErrorTimestampAnnotation: time.Now().Add(48 * time.Hour).Format(time.RFC3339),
}
_, err := instance.ValidateUpdate(instance)
Expect(err).To(HaveOccurred())
})
})
})

Context("Validate Delete", func() {
Expand Down Expand Up @@ -53,5 +76,94 @@ var _ = Describe("Service Instance Webhook Test", func() {
Expect(err).ToNot(HaveOccurred())
})
})
Context("Validate Create", func() {
When("service instance IgnoreNonTransientErrorTimestampAnnotation annotation is not set with not a date", func() {
It("should not return error from webhook", func() {
instance.Annotations = map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
api.IgnoreNonTransientErrorTimestampAnnotation: "true",
}
_, err := instance.ValidateCreate()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(annotationNotValidTimestampError, api.IgnoreNonTransientErrorTimestampAnnotation)))

})
})
When("service instance IgnoreNonTransientErrorTimestampAnnotation annotation is not set with future date", func() {
It("should not return error from webhook", func() {
instance.Annotations = map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
api.IgnoreNonTransientErrorTimestampAnnotation: time.Now().Add(48 * time.Hour).Format(time.RFC3339),
}
_, err := instance.ValidateCreate()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(annotationInFutureError, api.IgnoreNonTransientErrorTimestampAnnotation)))

})
})
})
})

Context("validateNonTransientTimestampAnnotation", func() {
It("fails when not a valid date", func() {
annotation := map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
api.IgnoreNonTransientErrorTimestampAnnotation: "true",
}
instance.SetAnnotations(annotation)
err := instance.validateNonTransientTimestampAnnotation()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("is not a valid timestamp"))
})

It("fails when a future date", func() {
annotation := map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
api.IgnoreNonTransientErrorTimestampAnnotation: time.Now().Add(48 * time.Hour).Format(time.RFC3339),
}
instance.SetAnnotations(annotation)
err := instance.validateNonTransientTimestampAnnotation()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(annotationInFutureError, api.IgnoreNonTransientErrorTimestampAnnotation)))
})

It("succeeds when timestamp is valid and in the past", func() {
annotation := map[string]string{
api.IgnoreNonTransientErrorAnnotation: "true",
api.IgnoreNonTransientErrorTimestampAnnotation: time.Now().Format(time.RFC3339),
}
instance.SetAnnotations(annotation)
err := instance.validateNonTransientTimestampAnnotation()
Expect(err).NotTo(HaveOccurred())
})
//
//It("validate annotation exist and valid", func() {
// annotation := map[string]string{
// api.IgnoreNonTransientErrorAnnotation: "true",
// api.IgnoreNonTransientErrorTimestampAnnotation: time.Now().Format(time.RFC3339),
// }
// instance.SetAnnotations(annotation)
// exist := utils.IsIgnoreNonTransientAnnotationExistAndValid(serviceinstancelog, instance, time.Hour)
// Expect(exist).To(BeTrue())
//})
//
//It("validate timeout for Ignore Non Transient Error Annotation", func() {
// annotation := map[string]string{
// api.IgnoreNonTransientErrorAnnotation: "true",
// api.IgnoreNonTransientErrorTimestampAnnotation: time.Now().Truncate(48 * time.Hour).Format(time.RFC3339),
// }
// instance.SetAnnotations(annotation)
// exist := utils.IsIgnoreNonTransientAnnotationExistAndValid(serviceinstancelog, instance, time.Hour)
// Expect(exist).To(BeFalse())
//})
//
//It("validate annotation not exist", func() {
// annotation := map[string]string{
// api.IgnoreNonTransientErrorTimestampAnnotation: time.Now().Format(time.RFC3339),
// }
// instance.SetAnnotations(annotation)
// exist := utils.IsIgnoreNonTransientAnnotationExistAndValid(serviceinstancelog, instance, time.Hour)
// Expect(exist).To(BeFalse())
//})
})
})
Loading
Loading