managed reconciler: add retry attempt for updating the resource whose external name is assigned during the Create call
Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>
This commit is contained in:
		
							parent
							
								
									31eef9b01e
								
							
						
					
					
						commit
						6b88ef0288
					
				|  | @ -21,6 +21,9 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"k8s.io/apimachinery/pkg/types" | ||||||
|  | 	"k8s.io/client-go/util/retry" | ||||||
|  | 
 | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
|  | @ -45,25 +48,27 @@ const ( | ||||||
| 
 | 
 | ||||||
| // Error strings.
 | // Error strings.
 | ||||||
| const ( | const ( | ||||||
| 	errGetManaged       = "cannot get managed resource" | 	errGetManaged               = "cannot get managed resource" | ||||||
| 	errReconcileConnect = "connect failed" | 	errUpdateManagedAfterCreate = "cannot update managed resource. this may have resulted in a leaked external resource" | ||||||
| 	errReconcileObserve = "observe failed" | 	errReconcileConnect         = "connect failed" | ||||||
| 	errReconcileCreate  = "create failed" | 	errReconcileObserve         = "observe failed" | ||||||
| 	errReconcileUpdate  = "update failed" | 	errReconcileCreate          = "create failed" | ||||||
| 	errReconcileDelete  = "delete failed" | 	errReconcileUpdate          = "update failed" | ||||||
|  | 	errReconcileDelete          = "delete failed" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Event reasons.
 | // Event reasons.
 | ||||||
| const ( | const ( | ||||||
| 	reasonCannotConnect     event.Reason = "CannotConnectToProvider" | 	reasonCannotConnect       event.Reason = "CannotConnectToProvider" | ||||||
| 	reasonCannotInitialize  event.Reason = "CannotInitializeManagedResource" | 	reasonCannotInitialize    event.Reason = "CannotInitializeManagedResource" | ||||||
| 	reasonCannotResolveRefs event.Reason = "CannotResolveResourceReferences" | 	reasonCannotResolveRefs   event.Reason = "CannotResolveResourceReferences" | ||||||
| 	reasonCannotObserve     event.Reason = "CannotObserveExternalResource" | 	reasonCannotObserve       event.Reason = "CannotObserveExternalResource" | ||||||
| 	reasonCannotCreate      event.Reason = "CannotCreateExternalResource" | 	reasonCannotCreate        event.Reason = "CannotCreateExternalResource" | ||||||
| 	reasonCannotDelete      event.Reason = "CannotDeleteExternalResource" | 	reasonCannotDelete        event.Reason = "CannotDeleteExternalResource" | ||||||
| 	reasonCannotPublish     event.Reason = "CannotPublishConnectionDetails" | 	reasonCannotPublish       event.Reason = "CannotPublishConnectionDetails" | ||||||
| 	reasonCannotUnpublish   event.Reason = "CannotUnpublishConnectionDetails" | 	reasonCannotUnpublish     event.Reason = "CannotUnpublishConnectionDetails" | ||||||
| 	reasonCannotUpdate      event.Reason = "CannotUpdateExternalResource" | 	reasonCannotUpdate        event.Reason = "CannotUpdateExternalResource" | ||||||
|  | 	reasonCannotUpdateManaged event.Reason = "CannotUpdateManagedResource" | ||||||
| 
 | 
 | ||||||
| 	reasonDeleted event.Reason = "DeletedExternalResource" | 	reasonDeleted event.Reason = "DeletedExternalResource" | ||||||
| 	reasonCreated event.Reason = "CreatedExternalResource" | 	reasonCreated event.Reason = "CreatedExternalResource" | ||||||
|  | @ -306,6 +311,12 @@ type ExternalObservation struct { | ||||||
| 
 | 
 | ||||||
| // An ExternalCreation is the result of the creation of an external resource.
 | // An ExternalCreation is the result of the creation of an external resource.
 | ||||||
| type ExternalCreation struct { | type ExternalCreation struct { | ||||||
|  | 	// ExternalNameAssigned is true if the Create operation resulted in a change
 | ||||||
|  | 	// in the external name annotation. If that's the case, we need to issue a
 | ||||||
|  | 	// spec update and make sure it goes through so that we don't lose the identifier
 | ||||||
|  | 	// of the resource we just created.
 | ||||||
|  | 	ExternalNameAssigned bool | ||||||
|  | 
 | ||||||
| 	// ConnectionDetails required to connect to this resource. These details
 | 	// ConnectionDetails required to connect to this resource. These details
 | ||||||
| 	// are a set that is collated throughout the managed resource's lifecycle -
 | 	// are a set that is collated throughout the managed resource's lifecycle -
 | ||||||
| 	// i.e. returning new connection details will have no affect on old details
 | 	// i.e. returning new connection details will have no affect on old details
 | ||||||
|  | @ -676,6 +687,28 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) | ||||||
| 			return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | 			return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if creation.ExternalNameAssigned { | ||||||
|  | 			en := meta.GetExternalName(managed) | ||||||
|  | 			// We will retry in all cases where the error comes from the api-server.
 | ||||||
|  | 			// At one point, context deadline will be exceeded and we'll get out
 | ||||||
|  | 			// of the loop. In that case, we warn the user that the external resource
 | ||||||
|  | 			// might be leaked.
 | ||||||
|  | 			err := retry.OnError(retry.DefaultRetry, resource.IsAPIError, func() error { | ||||||
|  | 				nn := types.NamespacedName{Name: managed.GetName()} | ||||||
|  | 				if err := r.client.Get(ctx, nn, managed); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				meta.SetExternalName(managed, en) | ||||||
|  | 				return r.client.Update(ctx, managed) | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Debug("Cannot update managed resource", "error", err, "requeue-after", time.Now().Add(r.shortWait)) | ||||||
|  | 				record.Event(managed, event.Warning(reasonCannotUpdateManaged, errors.Wrap(err, errUpdateManagedAfterCreate))) | ||||||
|  | 				managed.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errUpdateManagedAfterCreate))) | ||||||
|  | 				return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if err := r.managed.PublishConnection(ctx, managed, creation.ConnectionDetails); err != nil { | 		if err := r.managed.PublishConnection(ctx, managed, creation.ConnectionDetails); err != nil { | ||||||
| 			// If this is the first time we encounter this issue we'll be
 | 			// If this is the first time we encounter this issue we'll be
 | ||||||
| 			// requeued implicitly when we update our status with the new error
 | 			// requeued implicitly when we update our status with the new error
 | ||||||
|  | @ -706,7 +739,7 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) | ||||||
| 		// and persist its status.
 | 		// and persist its status.
 | ||||||
| 		if err := r.client.Update(ctx, managed); err != nil { | 		if err := r.client.Update(ctx, managed); err != nil { | ||||||
| 			log.Debug(errUpdateManaged, "error", err, "requeue-after", time.Now().Add(r.shortWait)) | 			log.Debug(errUpdateManaged, "error", err, "requeue-after", time.Now().Add(r.shortWait)) | ||||||
| 			record.Event(managed, event.Warning(reasonCannotUpdate, err)) | 			record.Event(managed, event.Warning(reasonCannotUpdateManaged, err)) | ||||||
| 			managed.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errUpdateManaged))) | 			managed.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errUpdateManaged))) | ||||||
| 			return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | 			return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -194,6 +194,12 @@ func IgnoreNotFound(err error) error { | ||||||
| 	return Ignore(kerrors.IsNotFound, err) | 	return Ignore(kerrors.IsNotFound, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // IsAPIError returns true if the given error's type is of Kubernetes API error.
 | ||||||
|  | func IsAPIError(err error) bool { | ||||||
|  | 	_, ok := err.(kerrors.APIStatus) | ||||||
|  | 	return ok | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // IsConditionTrue returns if condition status is true
 | // IsConditionTrue returns if condition status is true
 | ||||||
| func IsConditionTrue(c v1alpha1.Condition) bool { | func IsConditionTrue(c v1alpha1.Condition) bool { | ||||||
| 	return c.Status == corev1.ConditionTrue | 	return c.Status == corev1.ConditionTrue | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue