Skip to content

Commit 262f48e

Browse files
authored
resources sync - validate resource exists (#593)
1 parent 16498c9 commit 262f48e

File tree

7 files changed

+148
-21
lines changed

7 files changed

+148
-21
lines changed

api/common/common.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,15 @@ type SAPBTPResource interface {
8686
}
8787

8888
func GetObservedGeneration(obj SAPBTPResource) int64 {
89-
cond := meta.FindStatusCondition(obj.GetConditions(), ConditionSucceeded)
9089
observedGen := int64(0)
91-
if cond != nil {
92-
observedGen = cond.ObservedGeneration
90+
if len(obj.GetConditions()) == 0 {
91+
return observedGen
92+
}
93+
94+
cond := meta.FindStatusCondition(obj.GetConditions(), ConditionSucceeded)
95+
if cond == nil {
96+
cond = &obj.GetConditions()[0]
9397
}
98+
observedGen = cond.ObservedGeneration
9499
return observedGen
95100
}

api/common/consts.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131
ShareNotSupported = "ShareNotSupported"
3232
UnShareFailed = "UnShareFailed"
3333
UnShareSucceeded = "UnShareSucceeded"
34+
ResourceNotFound = "NotFound"
3435

3536
Blocked = "Blocked"
3637
Unknown = "Unknown"
@@ -42,4 +43,7 @@ const (
4243
// Constance for seceret template
4344
InstanceKey = "instance"
4445
CredentialsKey = "credentials"
46+
47+
//messages
48+
ResourceNotFoundMessageFormat = "%s %s not found for this cluster or namespace; or it is not managed by this operator-access instance."
4549
)

client/sm/client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,8 +592,12 @@ func handleResponseError(response *http.Response) error {
592592
body = []byte(fmt.Sprintf("error reading response body: %s", err))
593593
}
594594

595+
statusCode := response.StatusCode
596+
if response.Header.Get("X-Cf-RouterError") == "unknown_route" {
597+
statusCode = http.StatusServiceUnavailable
598+
}
595599
smError := &ServiceManagerError{
596-
StatusCode: response.StatusCode,
600+
StatusCode: statusCode,
597601
ResponseHeaders: response.Header,
598602
}
599603
_ = json.Unmarshal(body, &smError)

controllers/servicebinding_controller.go

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controllers
1919
import (
2020
"context"
2121
"encoding/json"
22+
"net/http"
2223
"strings"
2324
"time"
2425

@@ -125,6 +126,30 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
125126
return r.delete(ctx, serviceBinding, serviceInstance)
126127
}
127128

129+
smClient, err := r.GetSMClient(ctx, serviceInstance)
130+
if err != nil {
131+
return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
132+
}
133+
if len(serviceBinding.Status.BindingID) > 0 {
134+
if bindingExist, err := isBindingExistInSM(smClient, serviceInstance, serviceBinding.Status.BindingID, log); err != nil {
135+
log.Error(err, "failed to check if binding exist in sm due to unknown error")
136+
return ctrl.Result{}, err
137+
} else if !bindingExist {
138+
log.Info("binding not found in SM, updating status")
139+
condition := metav1.Condition{
140+
Type: common.ConditionReady,
141+
Status: metav1.ConditionFalse,
142+
ObservedGeneration: serviceBinding.Generation,
143+
LastTransitionTime: metav1.NewTime(time.Now()),
144+
Reason: common.ResourceNotFound,
145+
Message: fmt.Sprintf(common.ResourceNotFoundMessageFormat, "binding", serviceBinding.Status.BindingID),
146+
}
147+
serviceBinding.Status.Conditions = []metav1.Condition{condition}
148+
serviceBinding.Status.Ready = metav1.ConditionFalse
149+
return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
150+
}
151+
}
152+
128153
if controllerutil.AddFinalizer(serviceBinding, common.FinalizerName) {
129154
log.Info(fmt.Sprintf("added finalizer '%s' to service binding", common.FinalizerName))
130155
if err := r.Client.Update(ctx, serviceBinding); err != nil {
@@ -184,11 +209,6 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
184209
return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
185210
}
186211

187-
smClient, err := r.GetSMClient(ctx, serviceInstance)
188-
if err != nil {
189-
return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
190-
}
191-
192212
smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
193213
if err != nil {
194214
log.Error(err, "failed to check binding recovery")
@@ -1127,3 +1147,26 @@ func truncateString(str string, length int) string {
11271147
}
11281148
return str
11291149
}
1150+
1151+
func isBindingExistInSM(smClient sm.Client, instance *v1.ServiceInstance, bindingID string, log logr.Logger) (bool, error) {
1152+
log.Info("checking if k8s instance status is NotFound")
1153+
instanceReadyCond := meta.FindStatusCondition(instance.GetConditions(), common.ConditionReady)
1154+
if instanceReadyCond != nil && instanceReadyCond.Reason == common.ResourceNotFound {
1155+
log.Info("k8s instance is in NotFound state -> invalid binding")
1156+
return false, nil
1157+
}
1158+
1159+
log.Info(fmt.Sprintf("trying to get from SM binding with id %s", bindingID))
1160+
if _, err := smClient.GetBindingByID(bindingID, nil); err != nil {
1161+
var smError *sm.ServiceManagerError
1162+
if ok := errors.As(err, &smError); ok {
1163+
log.Error(smError, fmt.Sprintf("SM returned status code %d", smError.StatusCode))
1164+
if smError.StatusCode == http.StatusNotFound {
1165+
return false, nil
1166+
}
1167+
}
1168+
return false, err
1169+
}
1170+
log.Info("binding found in SM")
1171+
return true, nil
1172+
}

controllers/servicebinding_controller_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"net/http"
88
"strings"
9+
"time"
910

1011
"github.com/SAP/sap-btp-service-operator/internal/utils/logutils"
1112
"github.com/lithammer/dedent"
@@ -1547,6 +1548,30 @@ stringData:
15471548
})
15481549
})
15491550
})
1551+
1552+
When("binding id exist on resource but instance is in state NotFound", func() {
1553+
BeforeEach(func() {
1554+
createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", fakeBindingID)
1555+
createdInstance.Status.Conditions = []metav1.Condition{{
1556+
Type: common.ConditionReady,
1557+
Status: metav1.ConditionFalse,
1558+
ObservedGeneration: createdInstance.Generation,
1559+
LastTransitionTime: metav1.NewTime(time.Now()),
1560+
Reason: common.ResourceNotFound,
1561+
}}
1562+
Expect(k8sClient.Status().Update(ctx, createdInstance)).To(Succeed())
1563+
})
1564+
When("reconcile", func() {
1565+
It("should update status to ready=false", func() {
1566+
createdBinding.Labels = map[string]string{"tickle": "1"}
1567+
updateBinding(ctx, getResourceNamespacedName(createdBinding), createdBinding)
1568+
waitForResourceCondition(ctx, createdBinding, common.ConditionReady, metav1.ConditionFalse, common.ResourceNotFound, fmt.Sprintf(common.ResourceNotFoundMessageFormat, "binding", createdBinding.Status.BindingID))
1569+
1570+
By("deleting the binding should succeed")
1571+
deleteAndWait(ctx, createdBinding)
1572+
})
1573+
})
1574+
})
15501575
})
15511576

15521577
func generateBasicBindingTemplate(name, namespace, instanceName, instanceNamespace, externalName string, secretTemplate string) *v1.ServiceBinding {

controllers/serviceinstance_controller.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"net/http"
2324
"strings"
25+
"time"
2426

2527
"github.com/SAP/sap-btp-service-operator/internal/utils/logutils"
2628
"github.com/pkg/errors"
@@ -88,12 +90,6 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ
8890
if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
8991
return r.deleteInstance(ctx, serviceInstance)
9092
}
91-
if len(serviceInstance.GetConditions()) == 0 {
92-
err := utils.InitConditions(ctx, r.Client, serviceInstance)
93-
if err != nil {
94-
return ctrl.Result{}, err
95-
}
96-
}
9793

9894
// If stored hash is MD5 (32 chars) and we're now using SHA256 (64 chars),
9995
// perform one-time migration by updating the stored hash without triggering update
@@ -105,6 +101,42 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ
105101
return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
106102
}
107103

104+
smClient, err := r.GetSMClient(ctx, serviceInstance)
105+
if err != nil {
106+
log.Error(err, "failed to get sm client")
107+
return utils.HandleOperationFailure(ctx, r.Client, serviceInstance, common.Unknown, err)
108+
}
109+
if len(serviceInstance.Status.InstanceID) > 0 {
110+
if _, err := smClient.GetInstanceByID(serviceInstance.Status.InstanceID, nil); err != nil {
111+
var smError *sm.ServiceManagerError
112+
if ok := errors.As(err, &smError); ok {
113+
if smError.StatusCode == http.StatusNotFound {
114+
log.Info(fmt.Sprintf("instance %s not found in SM", serviceInstance.Status.InstanceID))
115+
condition := metav1.Condition{
116+
Type: common.ConditionReady,
117+
Status: metav1.ConditionFalse,
118+
ObservedGeneration: serviceInstance.Generation,
119+
LastTransitionTime: metav1.NewTime(time.Now()),
120+
Reason: common.ResourceNotFound,
121+
Message: fmt.Sprintf(common.ResourceNotFoundMessageFormat, "instance", serviceInstance.Status.InstanceID),
122+
}
123+
serviceInstance.Status.Conditions = []metav1.Condition{condition}
124+
serviceInstance.Status.Ready = metav1.ConditionFalse
125+
return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
126+
}
127+
}
128+
log.Error(err, fmt.Sprintf("failed to get instance %s from SM", serviceInstance.Status.InstanceID))
129+
return ctrl.Result{}, err
130+
}
131+
}
132+
133+
if len(serviceInstance.GetConditions()) == 0 {
134+
err := utils.InitConditions(ctx, r.Client, serviceInstance)
135+
if err != nil {
136+
return ctrl.Result{}, err
137+
}
138+
}
139+
108140
if len(serviceInstance.Status.OperationURL) > 0 {
109141
// ongoing operation - poll status from SM
110142
return r.poll(ctx, serviceInstance)
@@ -126,12 +158,6 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ
126158
}
127159
}
128160

129-
smClient, err := r.GetSMClient(ctx, serviceInstance)
130-
if err != nil {
131-
log.Error(err, "failed to get sm client")
132-
return utils.HandleOperationFailure(ctx, r.Client, serviceInstance, common.Unknown, err)
133-
}
134-
135161
if serviceInstance.Status.InstanceID == "" {
136162
log.Info("Instance ID is empty, checking if instance exist in SM")
137163
smInstance, err := r.getInstanceForRecovery(ctx, smClient, serviceInstance)

controllers/serviceinstance_controller_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,6 +1549,26 @@ var _ = Describe("ServiceInstance controller", func() {
15491549
})
15501550

15511551
})
1552+
1553+
When("instance id exist on resource but not found in sm", func() {
1554+
BeforeEach(func() {
1555+
serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true)
1556+
Expect(len(serviceInstance.Status.InstanceID) > 0).To(Equal(true))
1557+
fakeClient.GetInstanceByIDReturns(nil, &sm.ServiceManagerError{
1558+
StatusCode: http.StatusNotFound,
1559+
})
1560+
})
1561+
When("updating instance", func() {
1562+
It("should update status to ready=false", func() {
1563+
serviceInstance.Spec.ExternalName = "new-name"
1564+
Expect(k8sClient.Update(ctx, serviceInstance)).To(Succeed())
1565+
waitForResourceCondition(ctx, serviceInstance, common.ConditionReady, metav1.ConditionFalse, common.ResourceNotFound, fmt.Sprintf(common.ResourceNotFoundMessageFormat, "instance", serviceInstance.Status.InstanceID))
1566+
1567+
By("deleting the instance should succeed")
1568+
deleteInstance(ctx, serviceInstance, true)
1569+
})
1570+
})
1571+
})
15521572
})
15531573

15541574
func waitForInstanceConditionAndMessage(ctx context.Context, key types.NamespacedName, conditionType, msg string) {

0 commit comments

Comments
 (0)