Skip to content

Commit b0554dd

Browse files
authored
Async operation failure retry (#599)
1 parent 905fcbf commit b0554dd

File tree

13 files changed

+224
-234
lines changed

13 files changed

+224
-234
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ SED = sed -i ''
1818
endif
1919
TEST_PACKAGES=$(shell go list ./... | egrep "controllers|internal/utils|api|client" | egrep -v "client/sm/smfakes" | paste -sd " " -)
2020

21-
GO_TEST = go test $(TEST_PACKAGES) -coverprofile=$(TEST_PROFILE) -ginkgo.flakeAttempts=2
21+
GO_TEST = go test $(TEST_PACKAGES) -coverprofile=$(TEST_PROFILE) -ginkgo.flakeAttempts=3
2222

2323
all: manager
2424

api/common/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const (
5353
ConditionSucceeded = "Succeeded"
5454

5555
// ConditionFailed represents information about a final failure that should not be retried.
56+
// Deprecated: use succeeded with proper message
5657
ConditionFailed = "Failed"
5758

5859
// ConditionReady represents if the resource ready for usage.

client/sm/client.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -362,12 +362,16 @@ func (client *serviceManagerClient) delete(url string, q *Parameters, user strin
362362
switch response.StatusCode {
363363
case http.StatusGone:
364364
fallthrough
365-
case http.StatusNotFound:
366-
fallthrough
367365
case http.StatusOK:
368366
return "", nil
369367
case http.StatusAccepted:
370368
return response.Header.Get("Location"), nil
369+
case http.StatusNotFound:
370+
if response.Header.Get("X-Cf-RouterError") != "unknown_route" {
371+
return "", nil
372+
}
373+
response.StatusCode = http.StatusServiceUnavailable
374+
fallthrough
371375
default:
372376
return "", handleResponseError(response)
373377
}
@@ -592,12 +596,8 @@ func handleResponseError(response *http.Response) error {
592596
body = []byte(fmt.Sprintf("error reading response body: %s", err))
593597
}
594598

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

controllers/servicebinding_controller.go

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,21 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
107107
if !apierrors.IsNotFound(instanceErr) {
108108
log.Error(instanceErr, "failed to get service instance for binding")
109109
return ctrl.Result{}, instanceErr
110-
} else if !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
110+
}
111+
if !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
111112
//instance is not found and binding is not marked for deletion
112-
instanceNamespace := serviceBinding.Namespace
113-
if len(serviceBinding.Spec.ServiceInstanceNamespace) > 0 {
114-
instanceNamespace = serviceBinding.Spec.ServiceInstanceNamespace
115-
}
116-
errMsg := fmt.Sprintf("couldn't find the service instance '%s' in namespace '%s'", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
117-
utils.SetBlockedCondition(ctx, errMsg, serviceBinding)
118-
if updateErr := utils.UpdateStatus(ctx, r.Client, serviceBinding); updateErr != nil {
119-
return ctrl.Result{}, updateErr
113+
return r.handleInstanceForBindingNotFound(ctx, serviceBinding)
114+
}
115+
if len(serviceBinding.Status.BindingID) == 0 {
116+
log.Info("service instance not found, binding is marked for deletion and has no binding id, removing finalizer if exists")
117+
if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
118+
return ctrl.Result{}, err
120119
}
121-
return ctrl.Result{}, instanceErr
120+
return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
122121
}
123122
}
124123

125-
if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
124+
if shouldBindingBeDeleted(serviceBinding) {
126125
return r.delete(ctx, serviceBinding, serviceInstance)
127126
}
128127

@@ -171,7 +170,10 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
171170
if !serviceInstanceReady(serviceInstance) {
172171
log.Info(fmt.Sprintf("service instance name: %s namespace: %s is not ready, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
173172
utils.SetBlockedCondition(ctx, "service instance is not ready", serviceBinding)
174-
return ctrl.Result{Requeue: true}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
173+
if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
174+
return ctrl.Result{}, err
175+
}
176+
return ctrl.Result{}, errors.New("ServiceInstance is not ready")
175177
}
176178

177179
// should rotate creds
@@ -199,7 +201,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
199201
}
200202

201203
log.Info("binding in final state, maintaining secret")
202-
return r.maintain(ctx, serviceBinding, serviceInstance)
204+
return r.maintain(ctx, smClient, serviceBinding)
203205
}
204206

205207
if serviceBinding.Status.BindingID == "" {
@@ -221,7 +223,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
221223
return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
222224
}
223225

224-
log.Error(fmt.Errorf("update binding is not allowed, this line should not be reached"), "")
226+
log.Info("nothing to do for this binding")
225227
return ctrl.Result{}, nil
226228
}
227229

@@ -264,6 +266,7 @@ func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient s
264266
if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
265267
return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
266268
}
269+
log.Info(fmt.Sprintf("binding is being created async, bindingID=%s", bindingID))
267270
serviceBinding.Status.BindingID = bindingID
268271

269272
log.Info("Create smBinding request is async")
@@ -299,6 +302,7 @@ func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient s
299302

300303
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
301304
log := logutils.GetLogger(ctx)
305+
log.Info(fmt.Sprintf("binding in delete phase, marked for deletion=%v, bindingID=%s, ready=%s", utils.IsMarkedForDeletion(serviceBinding.ObjectMeta), serviceBinding.Status.BindingID, serviceBinding.Status.Ready))
302306
if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
303307
smClient, err := r.GetSMClient(ctx, serviceInstance)
304308
if err != nil {
@@ -335,7 +339,7 @@ func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v
335339
return r.poll(ctx, serviceBinding, serviceInstance)
336340
}
337341

338-
log.Info(fmt.Sprintf("Deleting binding with id %v from SM", serviceBinding.Status.BindingID))
342+
log.Info(fmt.Sprintf("Deleting binding with id %v from SM, resourceMarkedForDeletions=%v", serviceBinding.Status.BindingID, utils.IsMarkedForDeletion(serviceBinding.ObjectMeta)))
339343
operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
340344
if unbindErr != nil {
341345
return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
@@ -352,6 +356,12 @@ func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v
352356
return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
353357
}
354358

359+
log.Info("reset binding id after successful sync delete operation")
360+
serviceBinding.Status.BindingID = ""
361+
if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
362+
log.Error(err, "unable to update ServiceBinding status after deletion")
363+
return ctrl.Result{}, err
364+
}
355365
log.Info("Binding was deleted successfully")
356366
return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
357367
}
@@ -360,7 +370,7 @@ func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v
360370

361371
func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
362372
log := logutils.GetLogger(ctx)
363-
log.Info(fmt.Sprintf("resource is in progress, found operation url %s", serviceBinding.Status.OperationURL))
373+
log.Info(fmt.Sprintf("binding resource is in progress, found operation url %s", serviceBinding.Status.OperationURL))
364374

365375
smClient, err := r.GetSMClient(ctx, serviceInstance)
366376
if err != nil {
@@ -391,6 +401,7 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1.
391401
case smClientTypes.INPROGRESS:
392402
fallthrough
393403
case smClientTypes.PENDING:
404+
log.Info(fmt.Sprintf("%s is still in progress", serviceBinding.Status.OperationURL))
394405
if len(status.Description) != 0 {
395406
utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding, true)
396407
if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
@@ -400,23 +411,21 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1.
400411
}
401412
return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
402413
case smClientTypes.FAILED:
403-
// if async operation failed we should not retry
414+
log.Info(fmt.Sprintf("%s ended with failure", serviceBinding.Status.OperationURL))
404415
utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true)
405-
if serviceBinding.Status.OperationType == smClientTypes.DELETE {
406-
serviceBinding.Status.OperationURL = ""
407-
serviceBinding.Status.OperationType = ""
408-
if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
409-
log.Error(err, "unable to update ServiceBinding status")
410-
return ctrl.Result{}, err
411-
}
412-
errMsg := "Async unbind operation failed"
413-
if status.Errors != nil {
414-
errMsg = fmt.Sprintf("Async unbind operation failed, errors: %s", string(status.Errors))
415-
}
416-
return ctrl.Result{}, errors.New(errMsg)
416+
serviceBinding.Status.OperationURL = ""
417+
serviceBinding.Status.OperationType = ""
418+
if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
419+
log.Error(err, "unable to update ServiceBinding status")
420+
return ctrl.Result{}, err
421+
}
422+
errMsg := fmt.Sprintf("Async binding %s operation failed", serviceBinding.Status.OperationType)
423+
if status.Errors != nil {
424+
errMsg = fmt.Sprintf("Async unbind operation failed, errors: %s", string(status.Errors))
417425
}
426+
return ctrl.Result{}, errors.New(errMsg)
418427
case smClientTypes.SUCCEEDED:
419-
utils.SetSuccessConditions(status.Type, serviceBinding, true)
428+
log.Info(fmt.Sprintf("%s completed successfully", serviceBinding.Status.OperationURL))
420429
switch serviceBinding.Status.OperationType {
421430
case smClientTypes.CREATE:
422431
smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
@@ -433,7 +442,15 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1.
433442
}
434443
utils.SetSuccessConditions(status.Type, serviceBinding, false)
435444
case smClientTypes.DELETE:
436-
return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
445+
_, err := r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
446+
if err != nil {
447+
log.Error(err, "failed to delete binding secret and remove finalizer after delete operation completed")
448+
return ctrl.Result{}, err
449+
}
450+
451+
log.Info("reset binding id after successful async delete operation")
452+
serviceBinding.Status.BindingID = ""
453+
return ctrl.Result{RequeueAfter: time.Second}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
437454
}
438455
}
439456

@@ -471,9 +488,9 @@ func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, sm
471488
return nil, nil
472489
}
473490

474-
func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.ServiceBinding, instance *v1.ServiceInstance) (ctrl.Result, error) {
491+
func (r *ServiceBindingReconciler) maintain(ctx context.Context, smClient sm.Client, binding *v1.ServiceBinding) (ctrl.Result, error) {
475492
log := logutils.GetLogger(ctx)
476-
if err := r.maintainSecret(ctx, binding, instance); err != nil {
493+
if err := r.maintainSecret(ctx, smClient, binding); err != nil {
477494
log.Error(err, "failed to maintain secret")
478495
return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
479496
}
@@ -482,7 +499,7 @@ func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.Ser
482499
return ctrl.Result{}, nil
483500
}
484501

485-
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) error {
502+
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) error {
486503
log := logutils.GetLogger(ctx)
487504
if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
488505
log.Info("observed generation is up to date, checking if secret exists")
@@ -496,10 +513,6 @@ func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, serviceBi
496513
}
497514

498515
log.Info("maintaining binding's secret")
499-
smClient, err := r.GetSMClient(ctx, serviceInstance)
500-
if err != nil {
501-
return err
502-
}
503516
smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
504517
if err != nil {
505518
log.Error(err, "failed to get binding for update secret")
@@ -904,9 +917,6 @@ func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, bindin
904917
now := metav1.NewTime(time.Now())
905918
binding.Status.LastCredentialsRotationTime = &now
906919
return false, r.stopRotation(ctx, binding)
907-
} else if utils.IsFailed(binding) {
908-
log.Info("Credentials rotation - binding failed stopping rotation")
909-
return false, r.stopRotation(ctx, binding)
910920
}
911921
log.Info("Credentials rotation - waiting to finish")
912922
return false, nil
@@ -945,6 +955,7 @@ func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, bindin
945955
}
946956
}
947957

958+
log.Info("reset binding id after successful rotation")
948959
binding.Status.BindingID = ""
949960
binding.Status.Ready = metav1.ConditionFalse
950961
utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
@@ -1047,6 +1058,21 @@ func (r *ServiceBindingReconciler) recover(ctx context.Context, serviceBinding *
10471058
return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
10481059
}
10491060

1061+
func (r *ServiceBindingReconciler) handleInstanceForBindingNotFound(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1062+
log := logutils.GetLogger(ctx)
1063+
instanceNamespace := serviceBinding.Namespace
1064+
if len(serviceBinding.Spec.ServiceInstanceNamespace) > 0 {
1065+
instanceNamespace = serviceBinding.Spec.ServiceInstanceNamespace
1066+
}
1067+
errMsg := fmt.Sprintf("couldn't find the service instance '%s' in namespace '%s'", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1068+
log.Info(errMsg)
1069+
utils.SetBlockedCondition(ctx, errMsg, serviceBinding)
1070+
if updateErr := utils.UpdateStatus(ctx, r.Client, serviceBinding); updateErr != nil {
1071+
return ctrl.Result{}, updateErr
1072+
}
1073+
return ctrl.Result{}, fmt.Errorf("instance %s not found in namespace %s", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1074+
}
1075+
10501076
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
10511077
if utils.IsMarkedForDeletion(binding.ObjectMeta) {
10521078
return false
@@ -1170,3 +1196,8 @@ func isBindingExistInSM(smClient sm.Client, instance *v1.ServiceInstance, bindin
11701196
log.Info("binding found in SM")
11711197
return true, nil
11721198
}
1199+
1200+
func shouldBindingBeDeleted(serviceBinding *v1.ServiceBinding) bool {
1201+
return utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) ||
1202+
(len(serviceBinding.Status.OperationURL) == 0 && len(serviceBinding.Status.BindingID) > 0 && serviceBinding.Status.Ready == metav1.ConditionFalse)
1203+
}

0 commit comments

Comments
 (0)