Remove support for resource claims and classes
See https://github.com/crossplane/crossplane/issues/1670 for context. Signed-off-by: Nic Cope <negz@rk0n.org>
This commit is contained in:
parent
e7742464e4
commit
95d71dbd91
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 v1alpha1
|
||||
|
||||
// BindingPhase represents the current binding phase of a resource or claim.
|
||||
type BindingPhase string
|
||||
|
||||
// Binding phases.
|
||||
const (
|
||||
// BindingPhaseUnset resources cannot be bound to another resource because
|
||||
// they are in an unset binding phase, presumed to be functionally
|
||||
// equivalent to BindingPhaseUnbindable.
|
||||
BindingPhaseUnset BindingPhase = ""
|
||||
|
||||
// BindingPhaseUnbindable resources cannot be bound to another resource, for
|
||||
// example because they are currently unavailable, or being created.
|
||||
BindingPhaseUnbindable BindingPhase = "Unbindable"
|
||||
|
||||
// BindingPhaseUnbound resources are available for binding to another
|
||||
// resource.
|
||||
BindingPhaseUnbound BindingPhase = "Unbound"
|
||||
|
||||
// BindingPhaseBound resources are bound to another resource.
|
||||
BindingPhaseBound BindingPhase = "Bound"
|
||||
|
||||
// BindingPhaseReleased managed resources were bound to a resource claim
|
||||
// that has since been deleted. Released managed resources cannot be
|
||||
// reclaimed; they are retained to allow manual clean-up and deletion.
|
||||
BindingPhaseReleased BindingPhase = "Released"
|
||||
)
|
||||
|
||||
// A BindingStatus represents the bindability and binding status of a resource.
|
||||
type BindingStatus struct {
|
||||
// Phase represents the binding phase of a managed resource or claim.
|
||||
// Unbindable resources cannot be bound, typically because they are
|
||||
// currently unavailable, or still being created. Unbound resource are
|
||||
// available for binding, and Bound resources have successfully bound to
|
||||
// another resource.
|
||||
// Deprecated. See https://github.com/crossplane/crossplane/issues/1670
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=Unbindable;Unbound;Bound;Released
|
||||
Phase BindingPhase `json:"bindingPhase,omitempty"`
|
||||
}
|
||||
|
||||
// SetBindingPhase sets the binding phase of the resource.
|
||||
func (s *BindingStatus) SetBindingPhase(p BindingPhase) {
|
||||
s.Phase = p
|
||||
}
|
||||
|
||||
// GetBindingPhase gets the binding phase of the resource.
|
||||
func (s *BindingStatus) GetBindingPhase() BindingPhase {
|
||||
return s.Phase
|
||||
}
|
||||
|
|
@ -16,21 +16,6 @@ limitations under the License.
|
|||
|
||||
package v1alpha1
|
||||
|
||||
// A ReclaimPolicy determines what should happen to managed resources when their
|
||||
// bound resource claims are deleted.
|
||||
type ReclaimPolicy string
|
||||
|
||||
const (
|
||||
// ReclaimDelete means both the managed resource and its underlying external
|
||||
// resource will be deleted when its bound resource claim is deleted.
|
||||
ReclaimDelete ReclaimPolicy = "Delete"
|
||||
|
||||
// ReclaimRetain means the managed resource will retained when its bound
|
||||
// resource claim is deleted. Furthermore, its underlying external resource
|
||||
// will be retained when the managed resource is deleted.
|
||||
ReclaimRetain ReclaimPolicy = "Retain"
|
||||
)
|
||||
|
||||
// A DeletionPolicy determines what should happen to the underlying external
|
||||
// resource when a managed resource is deleted.
|
||||
type DeletionPolicy string
|
||||
|
|
|
|||
|
|
@ -19,20 +19,10 @@ package v1alpha1
|
|||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
// NOTE(negz): Importing this as metav1 appears to break controller-gen's
|
||||
// deepcopy generation logic. It generates a deepcopy file that omits this
|
||||
// import and thus does not compile. Importing as v1 fixes this. ¯\_(ツ)_/¯
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// The annotation used to make a resource class the default.
|
||||
const (
|
||||
AnnotationDefaultClassKey = "resourceclass.crossplane.io/is-default-class"
|
||||
AnnotationDefaultClassValue = "true"
|
||||
)
|
||||
|
||||
const (
|
||||
// ResourceCredentialsSecretEndpointKey is the key inside a connection secret for the connection endpoint
|
||||
ResourceCredentialsSecretEndpointKey = "endpoint"
|
||||
|
|
@ -130,47 +120,6 @@ func (obj *TypedReference) GroupVersionKind() schema.GroupVersionKind {
|
|||
// GetObjectKind get the ObjectKind of a TypedReference.
|
||||
func (obj *TypedReference) GetObjectKind() schema.ObjectKind { return obj }
|
||||
|
||||
// A ResourceClaimSpec defines the desired state of a resource claim.
|
||||
// Deprecated. See https://github.com/crossplane/crossplane/issues/1670
|
||||
type ResourceClaimSpec struct {
|
||||
// WriteConnectionSecretToReference specifies the name of a Secret, in the
|
||||
// same namespace as this resource claim, to which any connection details
|
||||
// for this resource claim should be written. Connection details frequently
|
||||
// include the endpoint, username, and password required to connect to the
|
||||
// managed resource bound to this resource claim.
|
||||
// +optional
|
||||
WriteConnectionSecretToReference *LocalSecretReference `json:"writeConnectionSecretToRef,omitempty"`
|
||||
|
||||
// TODO(negz): Make the below references immutable once set? Doing so means
|
||||
// we don't have to track what provisioner was used to create a resource.
|
||||
|
||||
// A ClassSelector specifies labels that will be used to select a resource
|
||||
// class for this claim. If multiple classes match the labels one will be
|
||||
// chosen at random.
|
||||
// +optional
|
||||
ClassSelector *v1.LabelSelector `json:"classSelector,omitempty"`
|
||||
|
||||
// A ClassReference specifies a resource class that will be used to
|
||||
// dynamically provision a managed resource when the resource claim is
|
||||
// created.
|
||||
// +optional
|
||||
ClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
|
||||
|
||||
// A ResourceReference specifies an existing managed resource, in any
|
||||
// namespace, to which this resource claim should attempt to bind. Omit the
|
||||
// resource reference to enable dynamic provisioning using a resource class;
|
||||
// the resource reference will be automatically populated by Crossplane.
|
||||
// +optional
|
||||
ResourceReference *corev1.ObjectReference `json:"resourceRef,omitempty"`
|
||||
}
|
||||
|
||||
// A ResourceClaimStatus represents the observed status of a resource claim.
|
||||
// Deprecated. See https://github.com/crossplane/crossplane/issues/1670
|
||||
type ResourceClaimStatus struct {
|
||||
ConditionedStatus `json:",inline"`
|
||||
BindingStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// TODO(negz): Rename Resource* to Managed* to clarify that they enable the
|
||||
// resource.Managed interface.
|
||||
|
||||
|
|
@ -183,20 +132,6 @@ type ResourceSpec struct {
|
|||
// +optional
|
||||
WriteConnectionSecretToReference *SecretReference `json:"writeConnectionSecretToRef,omitempty"`
|
||||
|
||||
// ClaimReference specifies the resource claim to which this managed
|
||||
// resource will be bound. ClaimReference is set automatically during
|
||||
// dynamic provisioning.
|
||||
// Deprecated. See https://github.com/crossplane/crossplane/issues/1670
|
||||
//
|
||||
// +optional
|
||||
ClaimReference *corev1.ObjectReference `json:"claimRef,omitempty"`
|
||||
|
||||
// ClassReference specifies the resource class that was used to dynamically
|
||||
// provision this managed resource, if any.
|
||||
// Deprecated. See https://github.com/crossplane/crossplane/issues/1670
|
||||
// +optional
|
||||
ClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
|
||||
|
||||
// ProviderConfigReference specifies how the provider that will be used to
|
||||
// create, observe, update, and delete this managed resource should be
|
||||
// configured.
|
||||
|
|
@ -215,59 +150,11 @@ type ResourceSpec struct {
|
|||
// +optional
|
||||
// +kubebuilder:validation:Enum=Orphan;Delete
|
||||
DeletionPolicy DeletionPolicy `json:"deletionPolicy,omitempty"`
|
||||
|
||||
// ReclaimPolicy specifies what will happen to this managed resource when
|
||||
// its resource claim is deleted, and what will happen to the underlying
|
||||
// external resource when the managed resource is deleted. The "Delete"
|
||||
// policy causes the managed resource to be deleted when its bound resource
|
||||
// claim is deleted, and in turn causes the external resource to be deleted
|
||||
// when its managed resource is deleted. The "Retain" policy causes the
|
||||
// managed resource to be retained, in binding phase "Released", when its
|
||||
// resource claim is deleted, and in turn causes the external resource to be
|
||||
// retained when its managed resource is deleted. The "Delete" policy is
|
||||
// used when no policy is specified.
|
||||
//
|
||||
// Deprecated. DeletionPolicy takes precedence when both are set.
|
||||
// See https://github.com/crossplane/crossplane-runtime/issues/179.
|
||||
//
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=Retain;Delete
|
||||
ReclaimPolicy ReclaimPolicy `json:"reclaimPolicy,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceStatus represents the observed state of a managed resource.
|
||||
type ResourceStatus struct {
|
||||
ConditionedStatus `json:",inline"`
|
||||
BindingStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// A ClassSpecTemplate defines a template that will be used to create the
|
||||
// specifications of managed resources dynamically provisioned using a resource
|
||||
// class.
|
||||
type ClassSpecTemplate struct {
|
||||
// WriteConnectionSecretsToNamespace specifies the namespace in which the
|
||||
// connection secrets of managed resources dynamically provisioned using
|
||||
// this claim will be created.
|
||||
WriteConnectionSecretsToNamespace string `json:"writeConnectionSecretsToNamespace"`
|
||||
|
||||
// ProviderReference specifies the provider that will be used to create,
|
||||
// observe, update, and delete managed resources that are dynamically
|
||||
// provisioned using this resource class.
|
||||
ProviderReference Reference `json:"providerRef"`
|
||||
|
||||
// ReclaimPolicy specifies what will happen to managed resources dynamically
|
||||
// provisioned using this class when their resource claims are deleted, and
|
||||
// what will happen to their underlying external resource when they are
|
||||
// deleted. The "Delete" policy causes the managed resource to be deleted
|
||||
// when its bound resource claim is deleted, and in turn causes the external
|
||||
// resource to be deleted when its managed resource is deleted. The "Retain"
|
||||
// policy causes the managed resource to be retained, in binding phase
|
||||
// "Released", when its resource claim is deleted, and in turn causes the
|
||||
// external resource to be retained when its managed resource is deleted.
|
||||
// The "Delete" policy is used when no policy is specified.
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=Retain;Delete
|
||||
ReclaimPolicy ReclaimPolicy `json:"reclaimPolicy,omitempty"`
|
||||
}
|
||||
|
||||
// A ProviderSpec defines the common way to get to the necessary objects to connect
|
||||
|
|
|
|||
|
|
@ -21,41 +21,9 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BindingStatus) DeepCopyInto(out *BindingStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindingStatus.
|
||||
func (in *BindingStatus) DeepCopy() *BindingStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BindingStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClassSpecTemplate) DeepCopyInto(out *ClassSpecTemplate) {
|
||||
*out = *in
|
||||
out.ProviderReference = in.ProviderReference
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClassSpecTemplate.
|
||||
func (in *ClassSpecTemplate) DeepCopy() *ClassSpecTemplate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClassSpecTemplate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Condition) DeepCopyInto(out *Condition) {
|
||||
*out = *in
|
||||
|
|
@ -164,58 +132,6 @@ func (in *Reference) DeepCopy() *Reference {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceClaimSpec) DeepCopyInto(out *ResourceClaimSpec) {
|
||||
*out = *in
|
||||
if in.WriteConnectionSecretToReference != nil {
|
||||
in, out := &in.WriteConnectionSecretToReference, &out.WriteConnectionSecretToReference
|
||||
*out = new(LocalSecretReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.ClassSelector != nil {
|
||||
in, out := &in.ClassSelector, &out.ClassSelector
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ClassReference != nil {
|
||||
in, out := &in.ClassReference, &out.ClassReference
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.ResourceReference != nil {
|
||||
in, out := &in.ResourceReference, &out.ResourceReference
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceClaimSpec.
|
||||
func (in *ResourceClaimSpec) DeepCopy() *ResourceClaimSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResourceClaimSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceClaimStatus) DeepCopyInto(out *ResourceClaimStatus) {
|
||||
*out = *in
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
out.BindingStatus = in.BindingStatus
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceClaimStatus.
|
||||
func (in *ResourceClaimStatus) DeepCopy() *ResourceClaimStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResourceClaimStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) {
|
||||
*out = *in
|
||||
|
|
@ -224,16 +140,6 @@ func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) {
|
|||
*out = new(SecretReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.ClaimReference != nil {
|
||||
in, out := &in.ClaimReference, &out.ClaimReference
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.ClassReference != nil {
|
||||
in, out := &in.ClassReference, &out.ClassReference
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.ProviderConfigReference != nil {
|
||||
in, out := &in.ProviderConfigReference, &out.ProviderConfigReference
|
||||
*out = new(Reference)
|
||||
|
|
@ -260,7 +166,6 @@ func (in *ResourceSpec) DeepCopy() *ResourceSpec {
|
|||
func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) {
|
||||
*out = *in
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
out.BindingStatus = in.BindingStatus
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus.
|
||||
|
|
@ -341,7 +246,7 @@ func (in *TargetSpec) DeepCopyInto(out *TargetSpec) {
|
|||
}
|
||||
if in.ResourceReference != nil {
|
||||
in, out := &in.ResourceReference, &out.ResourceReference
|
||||
*out = new(corev1.ObjectReference)
|
||||
*out = new(v1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,259 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimbinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
)
|
||||
|
||||
// Error strings.
|
||||
const (
|
||||
errCreateManaged = "cannot create managed resource"
|
||||
errUpdateClaim = "cannot update resource claim"
|
||||
errUpdateManaged = "cannot update managed resource"
|
||||
errUpdateManagedStatus = "cannot update managed resource status"
|
||||
errDeleteManaged = "cannot delete managed resource"
|
||||
errBindMismatch = "refusing to bind to managed resource that does not reference resource claim"
|
||||
errUnbindMismatch = "refusing to 'unbind' from managed resource that does not reference resource claim"
|
||||
errBindControlled = "refusing to bind to managed resource that is controlled by another resource"
|
||||
)
|
||||
|
||||
// An APIManagedCreator creates resources by submitting them to a Kubernetes
|
||||
// API server.
|
||||
type APIManagedCreator struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
}
|
||||
|
||||
// NewAPIManagedCreator returns a new APIManagedCreator.
|
||||
func NewAPIManagedCreator(c client.Client, t runtime.ObjectTyper) *APIManagedCreator {
|
||||
return &APIManagedCreator{client: c, typer: t}
|
||||
}
|
||||
|
||||
// Create the supplied resource using the supplied class and claim.
|
||||
func (a *APIManagedCreator) Create(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error {
|
||||
cmr := meta.ReferenceTo(cm, resource.MustGetKind(cm, a.typer))
|
||||
csr := meta.ReferenceTo(cs, resource.MustGetKind(cs, a.typer))
|
||||
|
||||
mg.SetClaimReference(cmr)
|
||||
mg.SetClassReference(csr)
|
||||
if err := a.client.Create(ctx, mg); err != nil {
|
||||
return errors.Wrap(err, errCreateManaged)
|
||||
}
|
||||
// Since we use GenerateName feature of ObjectMeta, final name of the
|
||||
// resource is calculated during the creation of the resource. So, we
|
||||
// can generate a complete reference only after the creation.
|
||||
mgr := meta.ReferenceTo(mg, resource.MustGetKind(mg, a.typer))
|
||||
cm.SetResourceReference(mgr)
|
||||
|
||||
return errors.Wrap(a.client.Update(ctx, cm), errUpdateClaim)
|
||||
}
|
||||
|
||||
// An APIBinder binds resources to claims by updating them in a Kubernetes API
|
||||
// server. Note that APIBinder does not support objects using the status
|
||||
// subresource; such objects should use APIStatusBinder.
|
||||
type APIBinder struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
}
|
||||
|
||||
// NewAPIBinder returns a new APIBinder.
|
||||
func NewAPIBinder(c client.Client, t runtime.ObjectTyper) *APIBinder {
|
||||
return &APIBinder{client: c, typer: t}
|
||||
}
|
||||
|
||||
// Bind the supplied resource to the supplied claim.
|
||||
func (a *APIBinder) Bind(ctx context.Context, cm resource.Claim, mg resource.Managed) error {
|
||||
// A managed resource that was statically provisioned by an infrastructure
|
||||
// operator should not have a controller reference. We assume a managed
|
||||
// resource with a controller reference is part of a composite resource or a
|
||||
// stack, and therefore not available to be claimed.
|
||||
if metav1.GetControllerOf(mg) != nil {
|
||||
return errors.New(errBindControlled)
|
||||
}
|
||||
|
||||
// Note that we allow a claim to bind to a managed resource with a nil claim
|
||||
// reference in order to enable the static provisioning case in which a
|
||||
// managed resource is provisioned ahead of time and is not associated with
|
||||
// any claim.
|
||||
if r := mg.GetClaimReference(); r != nil && !equal(meta.ReferenceTo(cm, resource.MustGetKind(cm, a.typer)), r) {
|
||||
return errors.New(errBindMismatch)
|
||||
}
|
||||
|
||||
cm.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
|
||||
// This claim reference will already be set for dynamically provisioned
|
||||
// managed resources, but we need to make sure it's set for statically
|
||||
// provisioned resources too.
|
||||
cmr := meta.ReferenceTo(cm, resource.MustGetKind(cm, a.typer))
|
||||
mg.SetClaimReference(cmr)
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
if err := a.client.Update(ctx, mg); err != nil {
|
||||
return errors.Wrap(err, errUpdateManaged)
|
||||
}
|
||||
|
||||
if meta.GetExternalName(mg) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Propagate back the final name of the external resource to the claim.
|
||||
meta.SetExternalName(cm, meta.GetExternalName(mg))
|
||||
return errors.Wrap(a.client.Update(ctx, cm), errUpdateClaim)
|
||||
}
|
||||
|
||||
// Unbind the supplied Claim from the supplied Managed resource by removing the
|
||||
// managed resource's claim reference, transitioning it to binding phase
|
||||
// "Released", and if the managed resource's reclaim policy is "Delete",
|
||||
// deleting it.
|
||||
func (a *APIBinder) Unbind(ctx context.Context, cm resource.Claim, mg resource.Managed) error {
|
||||
if !equal(meta.ReferenceTo(cm, resource.MustGetKind(cm, a.typer)), mg.GetClaimReference()) {
|
||||
return errors.New(errUnbindMismatch)
|
||||
}
|
||||
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseReleased)
|
||||
mg.SetClaimReference(nil)
|
||||
if err := a.client.Update(ctx, mg); err != nil {
|
||||
return errors.Wrap(resource.IgnoreNotFound(err), errUpdateManaged)
|
||||
}
|
||||
|
||||
// We go to the trouble of unbinding the managed resource before deleting it
|
||||
// because we want it to show up as "released" (not "bound") if its managed
|
||||
// resource reconciler is wedged or delayed trying to delete it.
|
||||
if mg.GetReclaimPolicy() != v1alpha1.ReclaimDelete {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(resource.IgnoreNotFound(a.client.Delete(ctx, mg)), errDeleteManaged)
|
||||
}
|
||||
|
||||
// An APIStatusBinder binds resources to claims by updating them in a
|
||||
// Kubernetes API server. Note that APIStatusBinder does not support
|
||||
// objects that do not use the status subresource; such objects should use
|
||||
// APIBinder.
|
||||
type APIStatusBinder struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
}
|
||||
|
||||
// NewAPIStatusBinder returns a new APIStatusBinder.
|
||||
func NewAPIStatusBinder(c client.Client, t runtime.ObjectTyper) *APIStatusBinder {
|
||||
return &APIStatusBinder{client: c, typer: t}
|
||||
}
|
||||
|
||||
// Bind the supplied resource to the supplied claim.
|
||||
func (a *APIStatusBinder) Bind(ctx context.Context, cm resource.Claim, mg resource.Managed) error {
|
||||
// A managed resource that was statically provisioned by an infrastructure
|
||||
// operator should not have a controller reference. We assume a managed
|
||||
// resource with a controller reference is part of a composite resource or a
|
||||
// stack, and therefore not available to be claimed.
|
||||
if metav1.GetControllerOf(mg) != nil {
|
||||
return errors.New(errBindControlled)
|
||||
}
|
||||
|
||||
// Note that we allow a claim to bind to a managed resource with a nil claim
|
||||
// reference in order to enable the static provisioning case in which a
|
||||
// managed resource is provisioned ahead of time and is not associated with
|
||||
// any claim.
|
||||
if r := mg.GetClaimReference(); r != nil && !equal(meta.ReferenceTo(cm, resource.MustGetKind(cm, a.typer)), r) {
|
||||
return errors.New(errBindMismatch)
|
||||
}
|
||||
|
||||
cm.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
|
||||
// This claim reference will already be set for dynamically provisioned
|
||||
// managed resources, but we need to make sure it's set for statically
|
||||
// provisioned resources too.
|
||||
cmr := meta.ReferenceTo(cm, resource.MustGetKind(cm, a.typer))
|
||||
mg.SetClaimReference(cmr)
|
||||
if err := a.client.Update(ctx, mg); err != nil {
|
||||
return errors.Wrap(err, errUpdateManaged)
|
||||
}
|
||||
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
if err := a.client.Status().Update(ctx, mg); err != nil {
|
||||
return errors.Wrap(err, errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
if meta.GetExternalName(mg) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Propagate back the final name of the external resource to the claim.
|
||||
meta.SetExternalName(cm, meta.GetExternalName(mg))
|
||||
return errors.Wrap(a.client.Update(ctx, cm), errUpdateClaim)
|
||||
}
|
||||
|
||||
// Unbind the supplied Claim from the supplied Managed resource by removing the
|
||||
// managed resource's claim reference, transitioning it to binding phase
|
||||
// "Released", and if the managed resource's reclaim policy is "Delete",
|
||||
// deleting it.
|
||||
func (a *APIStatusBinder) Unbind(ctx context.Context, cm resource.Claim, mg resource.Managed) error {
|
||||
if !equal(meta.ReferenceTo(cm, resource.MustGetKind(cm, a.typer)), mg.GetClaimReference()) {
|
||||
return errors.New(errUnbindMismatch)
|
||||
}
|
||||
|
||||
mg.SetClaimReference(nil)
|
||||
if err := a.client.Update(ctx, mg); err != nil {
|
||||
return errors.Wrap(resource.IgnoreNotFound(err), errUpdateManaged)
|
||||
}
|
||||
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseReleased)
|
||||
if err := a.client.Status().Update(ctx, mg); err != nil {
|
||||
return errors.Wrap(resource.IgnoreNotFound(err), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// We go to the trouble of unbinding the managed resource before deleting it
|
||||
// because we want it to show up as "released" (not "bound") if its managed
|
||||
// resource reconciler is wedged or delayed trying to delete it.
|
||||
if mg.GetReclaimPolicy() != v1alpha1.ReclaimDelete {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(resource.IgnoreNotFound(a.client.Delete(ctx, mg)), errDeleteManaged)
|
||||
}
|
||||
|
||||
// equal returns true if the supplied object references are considered equal. We
|
||||
// consider two otherwise matching references with different UIDs to be equal,
|
||||
// presuming that they are both references to a particular object that has been
|
||||
// deleted and recreated, e.g. due to being restored from a backup.
|
||||
//
|
||||
// TODO(negz): If we switch to a reference that only has the fields we care
|
||||
// about (GVK, namespace, and name) we can just use struct equality.
|
||||
// https://github.com/crossplane/crossplane-runtime/issues/49
|
||||
func equal(a, b *corev1.ObjectReference) bool {
|
||||
switch {
|
||||
case a == nil || b == nil:
|
||||
return a == b
|
||||
case a.APIVersion != b.APIVersion:
|
||||
return false
|
||||
case a.Kind != b.Kind:
|
||||
return false
|
||||
case a.Namespace != b.Namespace:
|
||||
return false
|
||||
}
|
||||
return a.Name == b.Name
|
||||
}
|
||||
|
|
@ -1,919 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimbinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ManagedCreator = &APIManagedCreator{}
|
||||
_ Binder = &APIBinder{}
|
||||
_ Binder = &APIStatusBinder{}
|
||||
)
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
type fields struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
}
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
cs resource.Class
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
cmname := "coolclaim"
|
||||
csname := "coolclass"
|
||||
mgname := "coolmanaged"
|
||||
errBoom := errors.New("boom")
|
||||
|
||||
cases := map[string]struct {
|
||||
fields fields
|
||||
args args
|
||||
want error
|
||||
}{
|
||||
"CreateManagedError": {
|
||||
fields: fields{
|
||||
client: &test.MockClient{
|
||||
MockCreate: test.NewMockCreateFn(errBoom),
|
||||
},
|
||||
typer: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
cs: &fake.Class{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: errors.Wrap(errBoom, errCreateManaged),
|
||||
},
|
||||
"UpdateClaimError": {
|
||||
fields: fields{
|
||||
client: &test.MockClient{
|
||||
MockCreate: test.NewMockCreateFn(nil),
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
},
|
||||
typer: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
cs: &fake.Class{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: errors.Wrap(errBoom, errUpdateClaim),
|
||||
},
|
||||
"Successful": {
|
||||
fields: fields{
|
||||
client: &test.MockClient{
|
||||
MockCreate: test.NewMockCreateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetName(mgname)
|
||||
want.SetClaimReference(&corev1.ObjectReference{
|
||||
Name: cmname,
|
||||
APIVersion: fake.GVK(&fake.Claim{}).GroupVersion().String(),
|
||||
Kind: fake.GVK(&fake.Claim{}).Kind,
|
||||
})
|
||||
want.SetClassReference(&corev1.ObjectReference{
|
||||
Name: csname,
|
||||
APIVersion: fake.GVK(&fake.Class{}).GroupVersion().String(),
|
||||
Kind: fake.GVK(&fake.Class{}).Kind,
|
||||
})
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetName(cmname)
|
||||
want.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
APIVersion: fake.GVK(&fake.Managed{}).GroupVersion().String(),
|
||||
Kind: fake.GVK(&fake.Managed{}).Kind,
|
||||
})
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
typer: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{ObjectMeta: metav1.ObjectMeta{Name: cmname}},
|
||||
cs: &fake.Class{ObjectMeta: metav1.ObjectMeta{Name: csname}},
|
||||
mg: &fake.Managed{ObjectMeta: metav1.ObjectMeta{Name: mgname}},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
api := NewAPIManagedCreator(tc.fields.client, tc.fields.typer)
|
||||
err := api.Create(tc.args.ctx, tc.args.cm, tc.args.cs, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("api.Create(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBind(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
cm resource.Claim
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
externalName := "very-cool-external-resource"
|
||||
controller := true
|
||||
|
||||
cases := map[string]struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"ControlledError": {
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{{Controller: &controller}},
|
||||
}},
|
||||
},
|
||||
want: want{
|
||||
err: errors.New(errBindControlled),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{{Controller: &controller}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
"RefMismatchError": {
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{ObjectMeta: metav1.ObjectMeta{Name: "I'm different!"}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.New(errBindMismatch),
|
||||
cm: &fake.Claim{ObjectMeta: metav1.ObjectMeta{Name: "I'm different!"}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateManagedError": {
|
||||
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil, func(obj runtime.Object) error {
|
||||
switch obj.(type) {
|
||||
case *fake.Managed:
|
||||
return errBoom
|
||||
default:
|
||||
return errors.New("unexpected object kind")
|
||||
}
|
||||
})},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateManaged),
|
||||
cm: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateClaimError": {
|
||||
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil, func(obj runtime.Object) error {
|
||||
switch obj.(type) {
|
||||
case *fake.Managed:
|
||||
return nil
|
||||
case *fake.Claim:
|
||||
return errBoom
|
||||
default:
|
||||
return errors.New("unexpected object kind")
|
||||
}
|
||||
})},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateClaim),
|
||||
cm: &fake.Claim{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
"SuccessfulWithoutExternalName": {
|
||||
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil)},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
cm: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
"SuccessfulWithExternalName": {
|
||||
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil)},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
cm: &fake.Claim{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound}},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
api := NewAPIBinder(tc.client, tc.typer)
|
||||
err := api.Bind(tc.args.ctx, tc.args.cm, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("api.Bind(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.cm, tc.args.cm, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("api.Bind(...) Claim: -want, +got:\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.mg, tc.args.mg, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("api.Bind(...) Managed: -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusBind(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
cm resource.Claim
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
externalName := "very-cool-external-resource"
|
||||
controller := true
|
||||
|
||||
cases := map[string]struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"ControlledError": {
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{{Controller: &controller}},
|
||||
}},
|
||||
},
|
||||
want: want{
|
||||
err: errors.New(errBindControlled),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{{Controller: &controller}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
"RefMismatchError": {
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{ObjectMeta: metav1.ObjectMeta{Name: "I'm different!"}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.New(errBindMismatch),
|
||||
cm: &fake.Claim{ObjectMeta: metav1.ObjectMeta{Name: "I'm different!"}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateManagedError": {
|
||||
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil, func(obj runtime.Object) error {
|
||||
switch obj.(type) {
|
||||
case *fake.Managed:
|
||||
return errBoom
|
||||
default:
|
||||
return errors.New("unexpected object kind")
|
||||
}
|
||||
})},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateManaged),
|
||||
cm: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateManagedStatusError": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(errBoom),
|
||||
},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateManagedStatus),
|
||||
cm: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateClaimError": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(obj runtime.Object) error {
|
||||
switch obj.(type) {
|
||||
case *fake.Managed:
|
||||
return nil
|
||||
case *fake.Claim:
|
||||
return errBoom
|
||||
default:
|
||||
return errors.New("unexpected object kind")
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateClaim),
|
||||
cm: &fake.Claim{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
"SuccessfulWithoutExternalName": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
cm: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound}},
|
||||
mg: &fake.Managed{
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
"SuccessfulWithExternalName": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
},
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
cm: &fake.Claim{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName}},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: meta.ReferenceTo(&fake.Claim{}, fake.GVK(&fake.Claim{}))},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
api := NewAPIStatusBinder(tc.client, tc.typer)
|
||||
err := api.Bind(tc.args.ctx, tc.args.cm, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("api.Bind(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.cm, tc.args.cm, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("api.Bind(...) Claim: -want, +got:\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.mg, tc.args.mg, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("api.Bind(...) Managed: -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnbind(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
|
||||
typer := fake.SchemeWith(&fake.Claim{})
|
||||
ref := meta.ReferenceTo(&fake.Claim{}, resource.MustGetKind(&fake.Claim{}, typer))
|
||||
|
||||
cases := map[string]struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"RefMismatchError": {
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{ObjectMeta: metav1.ObjectMeta{Name: "I'm different!"}},
|
||||
mg: &fake.Managed{ClaimReferencer: fake.ClaimReferencer{Ref: ref}},
|
||||
},
|
||||
want: want{
|
||||
err: errors.New(errUnbindMismatch),
|
||||
mg: &fake.Managed{ClaimReferencer: fake.ClaimReferencer{Ref: ref}},
|
||||
},
|
||||
},
|
||||
"SuccessfulRetain": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
"SuccessfulDelete": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockDelete: test.NewMockDeleteFn(nil),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateError": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateManaged),
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
"DeleteError": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockDelete: test.NewMockDeleteFn(errBoom),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errDeleteManaged),
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
api := NewAPIBinder(tc.client, tc.typer)
|
||||
err := api.Unbind(tc.args.ctx, tc.args.cm, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("api.Unbind(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.mg, tc.args.mg, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("api.Unbind(...) Managed: -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusUnbind(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
typer := fake.SchemeWith(&fake.Claim{})
|
||||
ref := meta.ReferenceTo(&fake.Claim{}, resource.MustGetKind(&fake.Claim{}, typer))
|
||||
|
||||
cases := map[string]struct {
|
||||
client client.Client
|
||||
typer runtime.ObjectTyper
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"RefMismatchError": {
|
||||
typer: fake.SchemeWith(&fake.Claim{}),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{ObjectMeta: metav1.ObjectMeta{Name: "I'm different!"}},
|
||||
mg: &fake.Managed{ClaimReferencer: fake.ClaimReferencer{Ref: ref}},
|
||||
},
|
||||
want: want{
|
||||
err: errors.New(errUnbindMismatch),
|
||||
mg: &fake.Managed{ClaimReferencer: fake.ClaimReferencer{Ref: ref}},
|
||||
},
|
||||
},
|
||||
"SuccessfulRetain": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
mg: &fake.Managed{
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
"SuccessfulDelete": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
MockDelete: test.NewMockDeleteFn(nil),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateError": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateManaged),
|
||||
mg: &fake.Managed{
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateStatusError": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(errBoom),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errUpdateManagedStatus),
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
"DeleteError": {
|
||||
client: &test.MockClient{
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil),
|
||||
MockDelete: test.NewMockDeleteFn(errBoom),
|
||||
},
|
||||
typer: typer,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: ref},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errDeleteManaged),
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased},
|
||||
ClaimReferencer: fake.ClaimReferencer{Ref: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
api := NewAPIStatusBinder(tc.client, tc.typer)
|
||||
err := api.Unbind(tc.args.ctx, tc.args.cm, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("api.Unbind(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.mg, tc.args.mg, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("api.Unbind(...) Managed: -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectReferenceEqual(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
a *corev1.ObjectReference
|
||||
b *corev1.ObjectReference
|
||||
want bool
|
||||
}{
|
||||
"BothNil": {
|
||||
want: true,
|
||||
},
|
||||
"OneIsNil": {
|
||||
a: &corev1.ObjectReference{},
|
||||
want: false,
|
||||
},
|
||||
"MismatchedAPIVersion": {
|
||||
a: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
},
|
||||
b: &corev1.ObjectReference{},
|
||||
want: false,
|
||||
},
|
||||
"MismatchedKind": {
|
||||
a: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
Kind: "k",
|
||||
},
|
||||
b: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"MismatchedNamespace": {
|
||||
a: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
Kind: "k",
|
||||
Namespace: "ns",
|
||||
},
|
||||
b: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
Kind: "k",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"MismatchedName": {
|
||||
a: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
Kind: "k",
|
||||
Namespace: "ns",
|
||||
Name: "cool",
|
||||
},
|
||||
b: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
Kind: "k",
|
||||
Namespace: "ns",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"Match": {
|
||||
a: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
Kind: "k",
|
||||
Namespace: "ns",
|
||||
Name: "cool",
|
||||
},
|
||||
b: &corev1.ObjectReference{
|
||||
APIVersion: "v",
|
||||
Kind: "k",
|
||||
Namespace: "ns",
|
||||
Name: "cool",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := equal(tc.a, tc.b)
|
||||
if got != tc.want {
|
||||
t.Errorf("equal(...): want %t, got %t", tc.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimbinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
)
|
||||
|
||||
// A ConfiguratorChain chains multiple configurators.
|
||||
type ConfiguratorChain []ManagedConfigurator
|
||||
|
||||
// Configure calls each ManagedConfigurator serially. It returns the first
|
||||
// error it encounters, if any.
|
||||
func (cc ConfiguratorChain) Configure(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error {
|
||||
for _, c := range cc {
|
||||
if err := c.Configure(ctx, cm, cs, mg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// An ObjectMetaConfigurator sets standard object metadata for a dynamically
|
||||
// provisioned resource, deriving it from a class and claim. It is deprecated;
|
||||
// use ConfigureNames instead.
|
||||
type ObjectMetaConfigurator struct{}
|
||||
|
||||
// NewObjectMetaConfigurator returns a new ObjectMetaConfigurator.
|
||||
func NewObjectMetaConfigurator(_ runtime.ObjectTyper) *ObjectMetaConfigurator {
|
||||
return &ObjectMetaConfigurator{}
|
||||
}
|
||||
|
||||
// Configure the supplied Managed resource's object metadata.
|
||||
func (c *ObjectMetaConfigurator) Configure(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error {
|
||||
return ConfigureNames(ctx, cm, cs, mg)
|
||||
}
|
||||
|
||||
// ConfigureNames configures the name and external name of the supplied managed
|
||||
// resource. The managed resource name is derived from the supplied resource
|
||||
// claim, in the form {claim-namespace}-{claim-name}-{random-string}. The
|
||||
// resource claim's external name annotation, if any, is propagated to the
|
||||
// managed resource.
|
||||
func ConfigureNames(_ context.Context, cm resource.Claim, _ resource.Class, mg resource.Managed) error {
|
||||
mg.SetGenerateName(fmt.Sprintf("%s-%s-", cm.GetNamespace(), cm.GetName()))
|
||||
if meta.GetExternalName(cm) != "" {
|
||||
meta.SetExternalName(mg, meta.GetExternalName(cm))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureReclaimPolicy configures the reclaim policy of the supplied managed
|
||||
// resource. If the managed resource _already has_ a reclaim policy (for example
|
||||
// because one was set by another configurator) it is respected. Otherwise the
|
||||
// reclaim policy is copied from the resource class. If the resource class does
|
||||
// not specify a reclaim policy, the managed resource's policy is set to
|
||||
// "Delete".
|
||||
func ConfigureReclaimPolicy(_ context.Context, _ resource.Claim, cs resource.Class, mg resource.Managed) error {
|
||||
if mg.GetReclaimPolicy() != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mg.SetReclaimPolicy(cs.GetReclaimPolicy())
|
||||
|
||||
if mg.GetReclaimPolicy() == "" {
|
||||
mg.SetReclaimPolicy(v1alpha1.ReclaimDelete)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimbinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ManagedConfiguratorFn = ConfigureNames
|
||||
_ ManagedConfiguratorFn = ConfigureReclaimPolicy
|
||||
_ ManagedConfigurator = ConfiguratorChain{}
|
||||
)
|
||||
|
||||
func TestConfiguratorChain(t *testing.T) {
|
||||
errBoom := errors.New("boom")
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
cs resource.Class
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
cc ConfiguratorChain
|
||||
args args
|
||||
want error
|
||||
}{
|
||||
"EmptyChain": {
|
||||
cc: ConfiguratorChain{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
cs: &fake.Class{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"SuccessulConfigurator": {
|
||||
cc: ConfiguratorChain{
|
||||
ManagedConfiguratorFn(func(_ context.Context, _ resource.Claim, _ resource.Class, _ resource.Managed) error {
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
cs: &fake.Class{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"ConfiguratorReturnsError": {
|
||||
cc: ConfiguratorChain{
|
||||
ManagedConfiguratorFn(func(_ context.Context, _ resource.Claim, _ resource.Class, _ resource.Managed) error {
|
||||
return errBoom
|
||||
}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{},
|
||||
cs: &fake.Class{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: errBoom,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := tc.cc.Configure(tc.args.ctx, tc.args.cm, tc.args.cs, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("tc.cc.Configure(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameConfigurators(t *testing.T) {
|
||||
claimName := "myclaim"
|
||||
claimNS := "myclaimns"
|
||||
externalName := "wayout"
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
cs resource.Class
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"Successful": {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &fake.Claim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: claimNS,
|
||||
Name: claimName,
|
||||
Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName},
|
||||
}},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
mg: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: claimNS + "-" + claimName + "-",
|
||||
Annotations: map[string]string{meta.AnnotationKeyExternalName: externalName},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("TestConfigureNames", func(t *testing.T) {
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := ConfigureNames(tc.args.ctx, tc.args.cm, tc.args.cs, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want.err, got, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("ConfigureNames(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.mg, tc.args.mg); diff != "" {
|
||||
t.Errorf("ConfigureNames(...) Managed: -want, +got error:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// NOTE(negz): This deprecated API wraps ConfigureNames; they should behave
|
||||
// identically.
|
||||
t.Run("TestObjectMetaConfigurator", func(t *testing.T) {
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
om := NewObjectMetaConfigurator(nil)
|
||||
got := om.Configure(tc.args.ctx, tc.args.cm, tc.args.cs, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want.err, got, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("om.Configure(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.mg, tc.args.mg); diff != "" {
|
||||
t.Errorf("om.Configure(...) Managed: -want, +got error:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigureReclaimPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
cm resource.Claim
|
||||
cs resource.Class
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
mg resource.Managed
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"AlreadySet": {
|
||||
reason: "Existing managed resource reclaim policies should be respected.",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cs: &fake.Class{Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete}},
|
||||
mg: &fake.Managed{Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain}},
|
||||
},
|
||||
want: want{
|
||||
mg: &fake.Managed{Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain}},
|
||||
},
|
||||
},
|
||||
"SetByClass": {
|
||||
reason: "The class's reclaim policy should be propagated to the managed resource.",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cs: &fake.Class{Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain}},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
mg: &fake.Managed{Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain}},
|
||||
},
|
||||
},
|
||||
"DefaultToDelete": {
|
||||
reason: "If neither the class nor managed resource set a reclaim policy, it should default to Delete.",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cs: &fake.Class{},
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: want{
|
||||
mg: &fake.Managed{Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := ConfigureReclaimPolicy(tc.args.ctx, tc.args.cm, tc.args.cs, tc.args.mg)
|
||||
if diff := cmp.Diff(tc.want.err, got, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nConfigureReclaimPolicy(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.mg, tc.args.mg); diff != "" {
|
||||
t.Errorf("\nReason: %s\nConfigureReclaimPolicy(...) Managed: -want, +got error:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Crossplane Authors.
|
||||
|
||||
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 claimbinding provides a resource claim binding reconciler.
|
||||
// Deprecated: See https://github.com/crossplane/crossplane/issues/1670
|
||||
package claimbinding
|
||||
|
|
@ -1,514 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimbinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
)
|
||||
|
||||
const (
|
||||
claimFinalizerName = "finalizer.resourceclaim.crossplane.io"
|
||||
claimReconcileTimeout = 1 * time.Minute
|
||||
|
||||
aShortWait = 30 * time.Second
|
||||
)
|
||||
|
||||
// Reasons a resource claim is or is not ready.
|
||||
const (
|
||||
ReasonBinding = "Managed claim is waiting for managed resource to become bindable"
|
||||
)
|
||||
|
||||
// Error strings.
|
||||
const (
|
||||
errGetClaim = "cannot get resource claim"
|
||||
errUpdateClaimStatus = "cannot update resource claim status"
|
||||
)
|
||||
|
||||
// Event reasons.
|
||||
const (
|
||||
reasonCannotGetResource event.Reason = "CannotGetManagedResource"
|
||||
reasonCannotGetClass event.Reason = "CannotGetResourceClass"
|
||||
reasonCannotConfigureResource event.Reason = "CannotConfigureManagedResource"
|
||||
reasonCannotCreateResource event.Reason = "CannotCreateManagedResource"
|
||||
reasonCannotPropagate event.Reason = "CannotPropagateConnectionDetails"
|
||||
reasonCannotBind event.Reason = "CannotBindManagedResource"
|
||||
reasonCannotUnbind event.Reason = "CannotUnbindManagedResource"
|
||||
|
||||
reasonResourceNotFound event.Reason = "ManagedResourceNotFound"
|
||||
reasonCreatedResource event.Reason = "CreatedManagedResource"
|
||||
reasonWaitingToBind event.Reason = "WaitingToBind"
|
||||
reasonBound event.Reason = "BoundManagedResource"
|
||||
reasonUnbound event.Reason = "UnboundManagedResource"
|
||||
)
|
||||
|
||||
// ControllerName returns the recommended name for controllers that use this
|
||||
// package to reconcile a particular kind of resource claim.
|
||||
func ControllerName(kind string) string {
|
||||
return "claimbinding/" + strings.ToLower(kind)
|
||||
}
|
||||
|
||||
// A ManagedConfigurator configures a resource, typically by converting it to
|
||||
// a known type and populating its spec.
|
||||
type ManagedConfigurator interface {
|
||||
Configure(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error
|
||||
}
|
||||
|
||||
// A ManagedConfiguratorFn is a function that satisfies the
|
||||
// ManagedConfigurator interface.
|
||||
type ManagedConfiguratorFn func(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error
|
||||
|
||||
// Configure the supplied resource using the supplied claim and class.
|
||||
func (fn ManagedConfiguratorFn) Configure(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error {
|
||||
return fn(ctx, cm, cs, mg)
|
||||
}
|
||||
|
||||
// A ManagedCreator creates a resource, typically by submitting it to an API
|
||||
// server. ManagedCreators must not modify the supplied resource class, but are
|
||||
// responsible for final modifications to the claim and resource, for example
|
||||
// ensuring resource, class, claim, and owner references are set.
|
||||
type ManagedCreator interface {
|
||||
Create(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error
|
||||
}
|
||||
|
||||
// A ManagedCreatorFn is a function that satisfies the ManagedCreator interface.
|
||||
type ManagedCreatorFn func(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error
|
||||
|
||||
// Create the supplied resource.
|
||||
func (fn ManagedCreatorFn) Create(ctx context.Context, cm resource.Claim, cs resource.Class, mg resource.Managed) error {
|
||||
return fn(ctx, cm, cs, mg)
|
||||
}
|
||||
|
||||
// A Binder binds a resource claim to a managed resource.
|
||||
type Binder interface {
|
||||
// Bind the supplied Claim to the supplied Managed resource.
|
||||
Bind(ctx context.Context, cm resource.Claim, mg resource.Managed) error
|
||||
|
||||
// Unbind the supplied Claim from the supplied Managed resource.
|
||||
Unbind(ctx context.Context, cm resource.Claim, mg resource.Managed) error
|
||||
}
|
||||
|
||||
// BinderFns satisfy the Binder interface.
|
||||
type BinderFns struct {
|
||||
BindFn func(ctx context.Context, cm resource.Claim, mg resource.Managed) error
|
||||
UnbindFn func(ctx context.Context, cm resource.Claim, mg resource.Managed) error
|
||||
}
|
||||
|
||||
// Bind the supplied Claim to the supplied Managed resource.
|
||||
func (b BinderFns) Bind(ctx context.Context, cm resource.Claim, mg resource.Managed) error {
|
||||
return b.BindFn(ctx, cm, mg)
|
||||
}
|
||||
|
||||
// Unbind the supplied Claim from the supplied Managed resource.
|
||||
func (b BinderFns) Unbind(ctx context.Context, cm resource.Claim, mg resource.Managed) error {
|
||||
return b.UnbindFn(ctx, cm, mg)
|
||||
}
|
||||
|
||||
// A Reconciler reconciles resource claims by creating exactly one kind of
|
||||
// concrete managed resource. Each resource claim kind should create an instance
|
||||
// of this controller for each managed resource kind they can bind to, using
|
||||
// watch predicates to ensure each controller is responsible for exactly one
|
||||
// type of resource class provisioner. Each controller must watch its subset of
|
||||
// resource claims and any managed resources they control.
|
||||
type Reconciler struct {
|
||||
client client.Client
|
||||
newClaim func() resource.Claim
|
||||
newClass func() resource.Class
|
||||
newManaged func() resource.Managed
|
||||
|
||||
// The below structs embed the set of interfaces used to implement the
|
||||
// resource claim reconciler. We do this primarily for readability, so that
|
||||
// the reconciler logic reads r.managed.Create(), r.claim.Finalize(), etc.
|
||||
managed crManaged
|
||||
claim crClaim
|
||||
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
}
|
||||
|
||||
type crManaged struct {
|
||||
ManagedConfigurator
|
||||
ManagedCreator
|
||||
resource.ManagedConnectionPropagator
|
||||
}
|
||||
|
||||
func defaultCRManaged(m manager.Manager) crManaged {
|
||||
return crManaged{
|
||||
ManagedConfigurator: ConfiguratorChain{
|
||||
ManagedConfiguratorFn(ConfigureNames),
|
||||
ManagedConfiguratorFn(ConfigureReclaimPolicy),
|
||||
},
|
||||
ManagedCreator: NewAPIManagedCreator(m.GetClient(), m.GetScheme()),
|
||||
|
||||
// TODO(negz): Switch to ConnectionPropagator once this has been
|
||||
// deprecated for a release or two.
|
||||
//nolint:staticcheck
|
||||
ManagedConnectionPropagator: resource.NewAPIManagedConnectionPropagator(m.GetClient(), m.GetScheme()),
|
||||
}
|
||||
}
|
||||
|
||||
type crClaim struct {
|
||||
resource.Finalizer
|
||||
Binder
|
||||
}
|
||||
|
||||
func defaultCRClaim(m manager.Manager) crClaim {
|
||||
return crClaim{
|
||||
Finalizer: resource.NewAPIFinalizer(m.GetClient(), claimFinalizerName),
|
||||
Binder: NewAPIStatusBinder(m.GetClient(), m.GetScheme()),
|
||||
}
|
||||
}
|
||||
|
||||
// A ReconcilerOption configures a Reconciler.
|
||||
type ReconcilerOption func(*Reconciler)
|
||||
|
||||
// WithManagedConfigurators specifies which configurators should be used to
|
||||
// configure each managed resource. Configurators will be applied in the order
|
||||
// they are specified.
|
||||
func WithManagedConfigurators(c ...ManagedConfigurator) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.managed.ManagedConfigurator = ConfiguratorChain(c)
|
||||
}
|
||||
}
|
||||
|
||||
// WithManagedCreator specifies which ManagedCreator should be used to create
|
||||
// managed resources.
|
||||
func WithManagedCreator(c ManagedCreator) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.managed.ManagedCreator = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithManagedConnectionPropagator specifies which ManagedConnectionPropagator
|
||||
// should be used to propagate resource connection details to their claim.
|
||||
//
|
||||
// Deprecated: Use WithConnectionPropagator.
|
||||
func WithManagedConnectionPropagator(p resource.ManagedConnectionPropagator) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.managed.ManagedConnectionPropagator = p
|
||||
}
|
||||
}
|
||||
|
||||
// WithConnectionPropagator specifies which ConnectionPropagator
|
||||
// should be used to propagate resource connection details to their claim.
|
||||
func WithConnectionPropagator(p resource.ConnectionPropagator) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.managed.ManagedConnectionPropagator = &resource.APIManagedConnectionPropagator{Propagator: p}
|
||||
}
|
||||
}
|
||||
|
||||
// WithBinder specifies which Binder should be used to bind
|
||||
// resources to their claim.
|
||||
func WithBinder(b Binder) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.claim.Binder = b
|
||||
}
|
||||
}
|
||||
|
||||
// WithClaimFinalizer specifies which ClaimFinalizer should be used to finalize
|
||||
// claims when they are deleted.
|
||||
func WithClaimFinalizer(f resource.Finalizer) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.claim.Finalizer = f
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger specifies how the Reconciler should log messages.
|
||||
func WithLogger(l logging.Logger) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.log = l
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecorder specifies how the Reconciler should record events.
|
||||
func WithRecorder(er event.Recorder) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.record = er
|
||||
}
|
||||
}
|
||||
|
||||
// NewReconciler returns a Reconciler that reconciles resource claims
|
||||
// of the supplied ClaimKind with resources of the supplied ManagedKind. It
|
||||
// panics if asked to reconcile a claim or resource kind that is not registered
|
||||
// with the supplied manager's runtime.Scheme. The returned Reconciler will
|
||||
// apply only the ObjectMetaConfigurator by default; most callers should supply
|
||||
// one or more ManagedConfigurators to configure their managed resources.
|
||||
func NewReconciler(m manager.Manager, of resource.ClaimKind, using resource.ClassKind, with resource.ManagedKind, o ...ReconcilerOption) *Reconciler {
|
||||
nc := func() resource.Claim {
|
||||
return resource.MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(resource.Claim)
|
||||
}
|
||||
ns := func() resource.Class {
|
||||
return resource.MustCreateObject(schema.GroupVersionKind(using), m.GetScheme()).(resource.Class)
|
||||
}
|
||||
nr := func() resource.Managed {
|
||||
return resource.MustCreateObject(schema.GroupVersionKind(with), m.GetScheme()).(resource.Managed)
|
||||
}
|
||||
|
||||
// Panic early if we've been asked to reconcile a claim or resource kind
|
||||
// that has not been registered with our controller manager's scheme.
|
||||
_, _, _ = nc(), ns(), nr()
|
||||
|
||||
r := &Reconciler{
|
||||
client: m.GetClient(),
|
||||
newClaim: nc,
|
||||
newClass: ns,
|
||||
newManaged: nr,
|
||||
managed: defaultCRManaged(m),
|
||||
claim: defaultCRClaim(m),
|
||||
log: logging.NewNopLogger(),
|
||||
record: event.NewNopRecorder(),
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
ro(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Reconcile a resource claim with a concrete managed resource.
|
||||
func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) { // nolint:gocyclo
|
||||
// NOTE(negz): This method is well over our cyclomatic complexity goal.
|
||||
// Be wary of adding additional complexity.
|
||||
|
||||
log := r.log.WithValues("request", req)
|
||||
log.Debug("Reconciling")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), claimReconcileTimeout)
|
||||
defer cancel()
|
||||
|
||||
claim := r.newClaim()
|
||||
if err := r.client.Get(ctx, req.NamespacedName, claim); err != nil {
|
||||
// There's no need to requeue if we no longer exist. Otherwise we'll be
|
||||
// requeued implicitly because we return an error.
|
||||
log.Debug("Cannot get resource claim", "error", err)
|
||||
return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetClaim)
|
||||
}
|
||||
|
||||
record := r.record.WithAnnotations("external-name", meta.GetExternalName(claim))
|
||||
log = log.WithValues(
|
||||
"uid", claim.GetUID(),
|
||||
"version", claim.GetResourceVersion(),
|
||||
"external-name", meta.GetExternalName(claim),
|
||||
)
|
||||
|
||||
managed := r.newManaged()
|
||||
if ref := claim.GetResourceReference(); ref != nil {
|
||||
record = record.WithAnnotations("managed-name", claim.GetResourceReference().Name)
|
||||
log = log.WithValues("managed-name", claim.GetResourceReference().Name)
|
||||
|
||||
err := r.client.Get(ctx, meta.NamespacedNameOf(ref), managed)
|
||||
if kerrors.IsNotFound(err) {
|
||||
// If the managed resource we explicitly reference doesn't exist yet
|
||||
// we want to retry after a brief wait, in case it is created. We
|
||||
// must explicitly requeue because our EnqueueRequestForClaim
|
||||
// handler can only enqueue reconciles for managed resources that
|
||||
// have their claim reference set, so we can't expect to be queued
|
||||
// implicitly when the managed resource we want to bind to appears.
|
||||
log.Debug("Referenced managed resource not found", "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Normal(reasonResourceNotFound, "Referenced managed resource not found"))
|
||||
claim.SetConditions(Binding(), v1alpha1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
if err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued
|
||||
// implicitly due to the status update. Otherwise we want to retry
|
||||
// after a brief wait, in case this was a transient error.
|
||||
log.Debug("Cannot get referenced managed resource", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Warning(reasonCannotGetResource, err))
|
||||
claim.SetConditions(v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
}
|
||||
|
||||
if meta.WasDeleted(claim) {
|
||||
log = log.WithValues("deletion-timestamp", claim.GetDeletionTimestamp())
|
||||
|
||||
if err := r.claim.Unbind(ctx, claim, managed); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued
|
||||
// implicitly due to the status update. Otherwise we want to retry
|
||||
// after a brief wait, in case this was a transient error.
|
||||
log.Debug("Cannot unbind claim", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Warning(reasonCannotUnbind, err))
|
||||
claim.SetConditions(v1alpha1.Deleting(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
log.Debug("Successfully unbound managed resource")
|
||||
record.Event(claim, event.Normal(reasonUnbound, "Successfully unbound managed resource"))
|
||||
|
||||
if err := r.claim.RemoveFinalizer(ctx, claim); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued
|
||||
// implicitly due to the status update. Otherwise we want to retry
|
||||
// after a brief wait, in case this was a transient error.
|
||||
log.Debug("Cannot remove finalizer", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
claim.SetConditions(v1alpha1.Deleting(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
// We've successfully deleted our claim and removed our finalizer. If we
|
||||
// assume we were the only controller that added a finalizer to this
|
||||
// claim then it should no longer exist and thus there is no point
|
||||
// trying to update its status.
|
||||
log.Debug("Successfully deleted resource claim")
|
||||
return reconcile.Result{Requeue: false}, nil
|
||||
}
|
||||
|
||||
if err := r.claim.AddFinalizer(ctx, claim); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued
|
||||
// implicitly due to the status update. Otherwise we want to retry
|
||||
// after a brief wait, in case this was a transient error.
|
||||
log.Debug("Cannot add resource claim finalizer", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
claim.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
// Claim reconcilers (should) watch for either claims with a resource ref,
|
||||
// claims with a class ref, or managed resources with a claim ref. In the
|
||||
// first case the managed resource always exists by the time we get here. In
|
||||
// the second case the class reference is set. The third case exposes us to
|
||||
// a pathological scenario in which a managed resource references a claim
|
||||
// that has no resource ref or class ref, so we can't assume the class ref
|
||||
// is always set at this point.
|
||||
if !meta.WasCreated(managed) && claim.GetClassReference() != nil {
|
||||
record = record.WithAnnotations("class-name", claim.GetClassReference().Name)
|
||||
log = log.WithValues("class-name", claim.GetClassReference().Name)
|
||||
|
||||
class := r.newClass()
|
||||
// Class reference should always be set by the time we get this far; we
|
||||
// set it on last reconciliation.
|
||||
if err := r.client.Get(ctx, meta.NamespacedNameOf(claim.GetClassReference()), class); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued
|
||||
// implicitly due to the status update. Otherwise we want to retry
|
||||
// after a brief wait, in case this was a transient error or the
|
||||
// class is (re)created.
|
||||
log.Debug("Cannot get referenced resource class", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Warning(reasonCannotGetClass, err))
|
||||
claim.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
if err := r.managed.Configure(ctx, claim, class, managed); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued
|
||||
// implicitly due to the status update. Otherwise we want to retry
|
||||
// after a brief wait, in case this was a transient error or some
|
||||
// issue with the resource class was resolved.
|
||||
log.Debug("Cannot configure managed resource", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Warning(reasonCannotConfigureResource, err))
|
||||
claim.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
// We'll know our managed resource's name at this point because it was
|
||||
// set by the above configure step.
|
||||
record = record.WithAnnotations("managed-name", managed.GetName())
|
||||
log = log.WithValues("managed-name", managed.GetName())
|
||||
|
||||
if err := r.managed.Create(ctx, claim, class, managed); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued
|
||||
// implicitly due to the status update. Otherwise we want to retry
|
||||
// after a brief wait, in case this was a transient error.
|
||||
log.Debug("Cannot create managed resource", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Warning(reasonCannotCreateResource, err))
|
||||
claim.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
log.Debug("Successfully created managed resource")
|
||||
record.Event(claim, event.Normal(reasonCreatedResource, "Successfully created managed resource"))
|
||||
}
|
||||
|
||||
if !resource.IsBindable(managed) && !resource.IsBound(managed) {
|
||||
log.Debug("Managed resource is not yet bindable")
|
||||
record.Event(claim, event.Normal(reasonWaitingToBind, "Managed resource is not yet bindable"))
|
||||
|
||||
if managed.GetClaimReference() == nil {
|
||||
// We're waiting to bind to a statically provisioned managed
|
||||
// resource. We must requeue because our EnqueueRequestForClaim
|
||||
// handler can only enqueue reconciles for managed resource updates
|
||||
// when they have their claim reference set, and that doesn't happen
|
||||
// until we bind to the managed resource we're waiting for.
|
||||
claim.SetConditions(Binding(), v1alpha1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
// If this claim was not already binding we'll be requeued due to the
|
||||
// status update. Otherwise there's no need to requeue. We should be
|
||||
// watching both the resource claims and the resources we own, so we'll
|
||||
// be queued if anything changes.
|
||||
claim.SetConditions(Binding(), v1alpha1.ReconcileSuccess())
|
||||
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
if resource.IsBindable(managed) {
|
||||
if err := r.managed.PropagateConnection(ctx, claim, managed); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued implicitly
|
||||
// due to the status update. Otherwise we want to retry after a brief
|
||||
// wait in case this was a transient error, or the resource connection
|
||||
// secret is created.
|
||||
log.Debug("Cannot propagate connection details from managed resource to claim", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Warning(reasonCannotPropagate, err))
|
||||
claim.SetConditions(Binding(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
if err := r.claim.Bind(ctx, claim, managed); err != nil {
|
||||
// If we didn't hit this error last time we'll be requeued implicitly
|
||||
// due to the status update. Otherwise we want to retry after a brief
|
||||
// wait, in case this was a transient error.
|
||||
log.Debug("Cannot bind to managed resource", "error", err, "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(claim, event.Warning(reasonCannotBind, err))
|
||||
claim.SetConditions(Binding(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
log.Debug("Successfully bound managed resource")
|
||||
record.Event(claim, event.Normal(reasonBound, "Successfully bound managed resource"))
|
||||
}
|
||||
|
||||
// No need to requeue. We should be watching both the resource claims and
|
||||
// the resources we own, so we'll be queued if anything changes.
|
||||
claim.SetConditions(v1alpha1.Available(), v1alpha1.ReconcileSuccess())
|
||||
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
// Binding returns a condition that indicates the resource claim is currently
|
||||
// waiting for its managed resource to become bindable.
|
||||
func Binding() v1alpha1.Condition {
|
||||
return v1alpha1.Condition{
|
||||
Type: v1alpha1.TypeReady,
|
||||
Status: corev1.ConditionFalse,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: ReasonBinding,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,735 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimbinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var _ reconcile.Reconciler = &Reconciler{}
|
||||
|
||||
func TestReconciler(t *testing.T) {
|
||||
type args struct {
|
||||
m manager.Manager
|
||||
of resource.ClaimKind
|
||||
use resource.ClassKind
|
||||
with resource.ManagedKind
|
||||
o []ReconcilerOption
|
||||
}
|
||||
|
||||
type want struct {
|
||||
result reconcile.Result
|
||||
err error
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
errUnexpected := errors.New("unexpected object type")
|
||||
now := metav1.Now()
|
||||
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"GetClaimError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errGetClaim)},
|
||||
},
|
||||
"GetManagedError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
return errBoom
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"ManagedNotFound": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
return kerrors.NewNotFound(schema.GroupResource{}, "")
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(Binding(), v1alpha1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"UnbindError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetDeletionTimestamp(&now)
|
||||
*o = *cm
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetDeletionTimestamp(&now)
|
||||
want.SetConditions(v1alpha1.Deleting(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ resource.Claim, _ resource.Managed) error { return errBoom }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"UnbindSuccess": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetDeletionTimestamp(&now)
|
||||
*o = *cm
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetDeletionTimestamp(&now)
|
||||
want.SetConditions(v1alpha1.Deleting(), v1alpha1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ resource.Claim, _ resource.Managed) error { return nil }}),
|
||||
WithClaimFinalizer(resource.FinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"RemoveClaimFinalizerError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetDeletionTimestamp(&now)
|
||||
*o = *cm
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetDeletionTimestamp(&now)
|
||||
want.SetConditions(v1alpha1.Deleting(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ resource.Claim, _ resource.Managed) error { return nil }}),
|
||||
WithClaimFinalizer(resource.FinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return errBoom }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"SuccessfulDelete": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetDeletionTimestamp(&now)
|
||||
*o = *cm
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ resource.Claim, _ resource.Managed) error { return nil }}),
|
||||
WithClaimFinalizer(resource.FinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"ClassReferenceNotSet": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
*o = fake.Claim{}
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetConditions(Binding(), v1alpha1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithClaimFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"GetResourceClassError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetClassReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Class:
|
||||
return errBoom
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetClassReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithClaimFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"ConfigureManagedError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetClassReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Class:
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetClassReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithManagedConfigurators(ManagedConfiguratorFn(
|
||||
func(_ context.Context, _ resource.Claim, _ resource.Class, _ resource.Managed) error { return errBoom },
|
||||
)),
|
||||
WithClaimFinalizer(resource.FinalizerFns{
|
||||
AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }},
|
||||
),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"CreateManagedError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetClassReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Class:
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetClassReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithManagedConfigurators(ManagedConfiguratorFn(
|
||||
func(_ context.Context, _ resource.Claim, _ resource.Class, _ resource.Managed) error { return nil },
|
||||
)),
|
||||
WithClaimFinalizer(resource.FinalizerFns{
|
||||
AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }},
|
||||
),
|
||||
WithManagedCreator(ManagedCreatorFn(
|
||||
func(_ context.Context, _ resource.Claim, _ resource.Class, _ resource.Managed) error { return errBoom },
|
||||
)),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"ManagedIsInUnknownBindingPhase": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
// We do not explicitly set a BindingPhase here
|
||||
// because the zero value of BindingPhase is
|
||||
// BindingPhaseUnset.
|
||||
mg := &fake.Managed{}
|
||||
mg.SetClaimReference(&corev1.ObjectReference{})
|
||||
mg.SetCreationTimestamp(now)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(Binding(), v1alpha1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithClaimFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"ManagedIsInUnbindableBindingPhase": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{}
|
||||
mg.SetCreationTimestamp(now)
|
||||
mg.SetClaimReference(&corev1.ObjectReference{})
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbindable)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(Binding(), v1alpha1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithClaimFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"PropagateConnectionError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{}
|
||||
mg.SetCreationTimestamp(now)
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(Binding(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithClaimFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
WithManagedConnectionPropagator(resource.ManagedConnectionPropagatorFn(
|
||||
func(_ context.Context, _ resource.LocalConnectionSecretOwner, _ resource.Managed) error {
|
||||
return errBoom
|
||||
},
|
||||
)),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"AddFinalizerError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{}
|
||||
mg.SetCreationTimestamp(now)
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithManagedConnectionPropagator(resource.ManagedConnectionPropagatorFn(
|
||||
func(_ context.Context, _ resource.LocalConnectionSecretOwner, _ resource.Managed) error { return nil },
|
||||
)),
|
||||
WithClaimFinalizer(resource.FinalizerFns{
|
||||
AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return errBoom }},
|
||||
),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"BindError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{}
|
||||
mg.SetCreationTimestamp(now)
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(Binding(), v1alpha1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithManagedConnectionPropagator(resource.ManagedConnectionPropagatorFn(
|
||||
func(_ context.Context, _ resource.LocalConnectionSecretOwner, _ resource.Managed) error { return nil },
|
||||
)),
|
||||
WithClaimFinalizer(resource.FinalizerFns{
|
||||
AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }},
|
||||
),
|
||||
WithBinder(BinderFns{
|
||||
BindFn: func(_ context.Context, _ resource.Claim, _ resource.Managed) error { return errBoom },
|
||||
}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"Successful": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Claim:
|
||||
cm := &fake.Claim{}
|
||||
cm.SetResourceReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{}
|
||||
mg.SetCreationTimestamp(now)
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetResourceReference(&corev1.ObjectReference{})
|
||||
want.SetConditions(v1alpha1.Available(), v1alpha1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}, &fake.Class{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
use: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ReconcilerOption{
|
||||
WithClaimFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewReconciler(tc.args.m, tc.args.of, tc.args.use, tc.args.with, tc.args.o...)
|
||||
got, err := r.Reconcile(reconcile.Request{})
|
||||
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.result, got); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Crossplane Authors.
|
||||
|
||||
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 claimdefaulting provides a reconciler that sets the default resource
|
||||
// class for a resource claim.
|
||||
// Deprecated: See https://github.com/crossplane/crossplane/issues/1670
|
||||
package claimdefaulting
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimdefaulting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
)
|
||||
|
||||
const (
|
||||
claimDefaultingReconcileTimeout = 1 * time.Minute
|
||||
claimDefaultingReconcileMaxJitterMs = 1500
|
||||
|
||||
aShortWait = 30 * time.Second
|
||||
)
|
||||
|
||||
// Error strings.
|
||||
const (
|
||||
errGetClaim = "cannot get resource claim"
|
||||
errUpdateClaim = "cannot update resource claim"
|
||||
errListClasses = "cannot list resource classes"
|
||||
)
|
||||
|
||||
// Event reasons.
|
||||
const reasonClassFound = "DefaultResourceClass"
|
||||
|
||||
// ControllerName returns the recommended name for controllers that use this
|
||||
// package to reconcile a particular kind of resource claim.
|
||||
func ControllerName(kind string) string {
|
||||
return "claimdefaulting/" + strings.ToLower(kind)
|
||||
}
|
||||
|
||||
// A Jitterer sleeps for a random amount of time in order to decrease the chance
|
||||
// of any one controller predictably winning the race to schedule claims to a
|
||||
// class, for example because it has fewer classes to list and select from than
|
||||
// its competitors.
|
||||
type Jitterer func()
|
||||
|
||||
// A Reconciler reconciles resource claims by setting their class reference to
|
||||
// the resource class annotated as the default. If multiple classes are
|
||||
// annotated as the default one of the annotated classes will be set, but which
|
||||
// one is undefined.
|
||||
type Reconciler struct {
|
||||
client client.Client
|
||||
newClaim func() resource.Claim
|
||||
classKind resource.ClassKind
|
||||
jitter Jitterer
|
||||
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
}
|
||||
|
||||
// A ReconcilerOption configures a Reconciler.
|
||||
type ReconcilerOption func(*Reconciler)
|
||||
|
||||
// WithDefaultingJitterer specifies the Jitterer a Reconciler should use.
|
||||
func WithDefaultingJitterer(j Jitterer) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.jitter = j
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger specifies how the Reconciler should log messages.
|
||||
func WithLogger(l logging.Logger) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.log = l
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecorder specifies how the Reconciler should record events.
|
||||
func WithRecorder(er event.Recorder) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.record = er
|
||||
}
|
||||
}
|
||||
|
||||
// NewReconciler returns a Reconciler that sets the class reference of a
|
||||
// resource claim to the resource class annotated as the default.
|
||||
func NewReconciler(m manager.Manager, of resource.ClaimKind, to resource.ClassKind, o ...ReconcilerOption) *Reconciler {
|
||||
nc := func() resource.Claim {
|
||||
return resource.MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(resource.Claim)
|
||||
}
|
||||
|
||||
// Panic early if we've been asked to reconcile a claim or resource kind
|
||||
// that has not been registered with our controller manager's scheme.
|
||||
_ = nc()
|
||||
|
||||
r := &Reconciler{
|
||||
client: m.GetClient(),
|
||||
newClaim: nc,
|
||||
classKind: to,
|
||||
jitter: func() {
|
||||
random := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
time.Sleep(time.Duration(random.Intn(claimDefaultingReconcileMaxJitterMs)) * time.Millisecond)
|
||||
},
|
||||
log: logging.NewNopLogger(),
|
||||
record: event.NewNopRecorder(),
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
ro(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Reconcile a resource claim by using its class selector to select and allocate
|
||||
// it a resource class.
|
||||
func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
|
||||
log := r.log.WithValues("request", req)
|
||||
log.Debug("Reconciling")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), claimDefaultingReconcileTimeout)
|
||||
defer cancel()
|
||||
|
||||
claim := r.newClaim()
|
||||
if err := r.client.Get(ctx, req.NamespacedName, claim); err != nil {
|
||||
// There's no need to requeue if we no longer exist. Otherwise we'll be
|
||||
// requeued implicitly because we return an error.
|
||||
log.Debug("Cannot get resource claim", "error", err)
|
||||
return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetClaim)
|
||||
}
|
||||
|
||||
record := r.record.WithAnnotations(
|
||||
"external-name", meta.GetExternalName(claim),
|
||||
"class-kind", r.classKind.Kind,
|
||||
)
|
||||
log = log.WithValues(
|
||||
"uid", claim.GetUID(),
|
||||
"version", claim.GetResourceVersion(),
|
||||
"external-name", meta.GetExternalName(claim),
|
||||
"class-kind", r.classKind.Kind,
|
||||
)
|
||||
|
||||
// There could be several controllers racing to schedule this claim. If it
|
||||
// was scheduled since we were queued then another controller won and we
|
||||
// should abort.
|
||||
if claim.GetClassReference() != nil {
|
||||
log.Debug("Resource class is already set")
|
||||
return reconcile.Result{Requeue: false}, nil
|
||||
}
|
||||
|
||||
classes := &unstructured.UnstructuredList{}
|
||||
classes.SetGroupVersionKind(r.classKind.List())
|
||||
|
||||
if err := r.client.List(ctx, classes); err != nil {
|
||||
// Claim defaulting controllers don't update the synced status because
|
||||
// no one scheduler has the full view of whether the process failed or
|
||||
// succeeded. It's possible another controller can successfully set a
|
||||
// class even though we can't, so it would be confusing to mark this
|
||||
// claim as failing to be reconciled. Instead we return an error - we'll
|
||||
// be requeued but abort immediately if the claim was defaulted.
|
||||
log.Debug("Cannot list resource classes", "error", err)
|
||||
return reconcile.Result{}, errors.Wrap(err, errListClasses)
|
||||
}
|
||||
|
||||
defaults := []unstructured.Unstructured{}
|
||||
for _, c := range classes.Items {
|
||||
if c.GetAnnotations()[v1alpha1.AnnotationDefaultClassKey] == v1alpha1.AnnotationDefaultClassValue {
|
||||
defaults = append(defaults, c)
|
||||
}
|
||||
}
|
||||
|
||||
if len(defaults) == 0 {
|
||||
// None of our classes are annotated as the default. We can't be sure
|
||||
// whether another controller owns the default class, or whether there
|
||||
// is no default class, so we requeue after a short wait. We'll abort
|
||||
// the next reconcile immediately if another controller defaulted the
|
||||
// claim.
|
||||
log.Debug("No default resource classes found", "requeue-after", time.Now().Add(aShortWait))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, nil
|
||||
}
|
||||
|
||||
random := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
selected := defaults[random.Intn(len(defaults))]
|
||||
claim.SetClassReference(meta.ReferenceTo(&selected, schema.GroupVersionKind(r.classKind)))
|
||||
|
||||
// There could be several controllers racing to default this claim to a
|
||||
// class. We sleep for a randomly jittered amount of time before trying to
|
||||
// update the class reference to decrease the chance of any one controller
|
||||
// predictably winning the race, for example because it has fewer classes to
|
||||
// list and select from than its competitors.
|
||||
r.jitter()
|
||||
|
||||
// Attempt to set the class reference. If a competing controller beat us
|
||||
// we'll fail the write because the claim's resource version has changed
|
||||
// since we read it. We'll be requeued, but will abort immediately if the
|
||||
// claim was defaulted.
|
||||
log.Debug("Attempting to set resource class", "class-name", selected.GetName())
|
||||
record.Event(claim, event.Normal(reasonClassFound, "Selected default resource class", "class-name", selected.GetName()))
|
||||
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Update(ctx, claim), errUpdateClaim)
|
||||
}
|
||||
|
|
@ -1,271 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimdefaulting
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var _ reconcile.Reconciler = &Reconciler{}
|
||||
|
||||
func TestReconciler(t *testing.T) {
|
||||
name := "coolName"
|
||||
uid := types.UID("definitely-a-uuid")
|
||||
|
||||
type args struct {
|
||||
m manager.Manager
|
||||
of resource.ClaimKind
|
||||
to resource.ClassKind
|
||||
}
|
||||
|
||||
type want struct {
|
||||
result reconcile.Result
|
||||
err error
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"GetClaimError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errGetClaim)},
|
||||
},
|
||||
"ClaimNotFound": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ClaimHasClassRef": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassReferencer: fake.ClassReferencer{Ref: &corev1.ObjectReference{}}}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"ListClassesError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(errBoom),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errListClasses)},
|
||||
},
|
||||
"NoClassesAnnotatedDefault": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(fake.GVK(&fake.Class{}))
|
||||
u.SetName(name)
|
||||
u.SetUID(uid)
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
l.Items = []unstructured.Unstructured{*u}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"UpdateClaimError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(fake.GVK(&fake.Class{}))
|
||||
u.SetName(name)
|
||||
u.SetUID(uid)
|
||||
u.SetAnnotations(map[string]string{v1alpha1.AnnotationDefaultClassKey: v1alpha1.AnnotationDefaultClassValue})
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
l.Items = []unstructured.Unstructured{*u}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errUpdateClaim)},
|
||||
},
|
||||
"Successful": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(fake.GVK(&fake.Class{}))
|
||||
u.SetName(name)
|
||||
u.SetUID(uid)
|
||||
u.SetAnnotations(map[string]string{v1alpha1.AnnotationDefaultClassKey: v1alpha1.AnnotationDefaultClassValue})
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
l.Items = []unstructured.Unstructured{*u}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetClassReference(&corev1.ObjectReference{
|
||||
APIVersion: fake.GVK(&fake.Class{}).GroupVersion().String(),
|
||||
Kind: fake.GVK(&fake.Class{}).Kind,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
})
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewReconciler(tc.args.m, tc.args.of, tc.args.to, WithDefaultingJitterer(func() {}))
|
||||
got, err := r.Reconcile(reconcile.Request{})
|
||||
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.result, got); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilerRandomness(t *testing.T) {
|
||||
classes := 10
|
||||
reconciles := 100
|
||||
refs := make([]*corev1.ObjectReference, 0)
|
||||
|
||||
newClass := func(i int) unstructured.Unstructured {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetUID(types.UID(strconv.Itoa(i)))
|
||||
u.SetAnnotations(map[string]string{v1alpha1.AnnotationDefaultClassKey: v1alpha1.AnnotationDefaultClassValue})
|
||||
return *u
|
||||
}
|
||||
|
||||
m := &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassSelector: fake.ClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
for i := 0; i < classes; i++ {
|
||||
l.Items = append(l.Items, newClass(i))
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(obj runtime.Object) error {
|
||||
ls := obj.(resource.ClassReferencer)
|
||||
refs = append(refs, ls.GetClassReference())
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
}
|
||||
|
||||
r := NewReconciler(m,
|
||||
resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
WithDefaultingJitterer(func() {}))
|
||||
|
||||
for i := 0; i < reconciles; i++ {
|
||||
r.Reconcile(reconcile.Request{})
|
||||
}
|
||||
|
||||
distribution := map[types.UID]int{}
|
||||
for _, ref := range refs {
|
||||
distribution[ref.UID]++
|
||||
}
|
||||
|
||||
// The goal here is to test whether we're random-ish, i.e. that we're not
|
||||
// picking the same class every time.
|
||||
if len(distribution) < 2 {
|
||||
t.Errorf("want > 1 resource classes selected, got %d", len(distribution))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Crossplane Authors.
|
||||
|
||||
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 claimscheduling provides a reconciler that selects a resource class
|
||||
// for a resource claim.
|
||||
// Deprecated: See https://github.com/crossplane/crossplane/issues/1670
|
||||
package claimscheduling
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimscheduling
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
)
|
||||
|
||||
const (
|
||||
claimSchedulingReconcileTimeout = 1 * time.Minute
|
||||
claimSchedulingReconcileMaxJitterMs = 1500
|
||||
|
||||
aShortWait = 30 * time.Second
|
||||
)
|
||||
|
||||
// Error strings.
|
||||
const (
|
||||
errGetClaim = "cannot get resource claim"
|
||||
errUpdateClaim = "cannot update resource claim"
|
||||
errListClasses = "cannot list resource classes"
|
||||
)
|
||||
|
||||
// Event reasons.
|
||||
const reasonClassFound = "SelectedResourceClass"
|
||||
|
||||
// ControllerName returns the recommended name for controllers that use this
|
||||
// package to reconcile a particular kind of resource claim.
|
||||
func ControllerName(kind string) string {
|
||||
return "claimscheduling/" + strings.ToLower(kind)
|
||||
}
|
||||
|
||||
// A Jitterer sleeps for a random amount of time in order to decrease the chance
|
||||
// of any one controller predictably winning the race to schedule claims to a
|
||||
// class, for example because it has fewer classes to list and select from than
|
||||
// its competitors.
|
||||
type Jitterer func()
|
||||
|
||||
// A Reconciler schedules resource claims to a resource class that matches their
|
||||
// class selector. Claims are reconciled by randomly selecting a matching
|
||||
// resource class and attempting to set it as the claim's class reference. The
|
||||
// Reconciler is designed for use in claim scheduling controllers that race
|
||||
// several others to schedule a claim.
|
||||
type Reconciler struct {
|
||||
client client.Client
|
||||
newClaim func() resource.Claim
|
||||
classKind resource.ClassKind
|
||||
jitter Jitterer
|
||||
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
}
|
||||
|
||||
// A ReconcilerOption configures a Reconciler.
|
||||
type ReconcilerOption func(*Reconciler)
|
||||
|
||||
// WithSchedulingJitterer specifies the Jitterer a Reconciler should use.
|
||||
func WithSchedulingJitterer(j Jitterer) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.jitter = j
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger specifies how the Reconciler should log messages.
|
||||
func WithLogger(l logging.Logger) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.log = l
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecorder specifies how the Reconciler should record events.
|
||||
func WithRecorder(er event.Recorder) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.record = er
|
||||
}
|
||||
}
|
||||
|
||||
// NewReconciler returns a Reconciler that schedules resource claims to a
|
||||
// resource class that matches their class selector.
|
||||
func NewReconciler(m manager.Manager, of resource.ClaimKind, to resource.ClassKind, o ...ReconcilerOption) *Reconciler {
|
||||
nc := func() resource.Claim {
|
||||
return resource.MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(resource.Claim)
|
||||
}
|
||||
|
||||
// Panic early if we've been asked to reconcile a claim or resource kind
|
||||
// that has not been registered with our controller manager's scheme.
|
||||
_ = nc()
|
||||
|
||||
r := &Reconciler{
|
||||
client: m.GetClient(),
|
||||
newClaim: nc,
|
||||
classKind: to,
|
||||
jitter: func() {
|
||||
random := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
time.Sleep(time.Duration(random.Intn(claimSchedulingReconcileMaxJitterMs)) * time.Millisecond)
|
||||
},
|
||||
log: logging.NewNopLogger(),
|
||||
record: event.NewNopRecorder(),
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
ro(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Reconcile a resource claim by using its class selector to select and allocate
|
||||
// it a resource class.
|
||||
func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
|
||||
log := r.log.WithValues("request", req)
|
||||
log.Debug("Reconciling")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), claimSchedulingReconcileTimeout)
|
||||
defer cancel()
|
||||
|
||||
claim := r.newClaim()
|
||||
if err := r.client.Get(ctx, req.NamespacedName, claim); err != nil {
|
||||
// There's no need to requeue if we no longer exist. Otherwise we'll be
|
||||
// requeued implicitly because we return an error.
|
||||
log.Debug("Cannot get resource claim", "error", err)
|
||||
return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetClaim)
|
||||
}
|
||||
|
||||
record := r.record.WithAnnotations(
|
||||
"external-name", meta.GetExternalName(claim),
|
||||
"class-kind", r.classKind.Kind,
|
||||
)
|
||||
log = log.WithValues(
|
||||
"uid", claim.GetUID(),
|
||||
"version", claim.GetResourceVersion(),
|
||||
"external-name", meta.GetExternalName(claim),
|
||||
"class-kind", r.classKind.Kind,
|
||||
)
|
||||
|
||||
// There could be several controllers racing to schedule this claim. If it
|
||||
// was scheduled since we were queued then another controller won and we
|
||||
// should abort.
|
||||
if claim.GetClassReference() != nil {
|
||||
log.Debug("Resource class is already set")
|
||||
return reconcile.Result{Requeue: false}, nil
|
||||
}
|
||||
|
||||
classes := &unstructured.UnstructuredList{}
|
||||
classes.SetGroupVersionKind(r.classKind.List())
|
||||
|
||||
if err := r.client.List(ctx, classes, client.MatchingLabels(claim.GetClassSelector().MatchLabels)); err != nil {
|
||||
// Claim scheduler controllers don't update the synced status because
|
||||
// no one scheduler has the full view of whether the process failed or
|
||||
// succeeded. It's possible another controller can successfully set a
|
||||
// class even though we can't, so it would be confusing to mark this
|
||||
// claim as failing to be reconciled. Instead we return an error - we'll
|
||||
// be requeued but abort immediately if the claim was scheduled.
|
||||
log.Debug("Cannot list resource classes", "error", err)
|
||||
return reconcile.Result{}, errors.Wrap(err, errListClasses)
|
||||
}
|
||||
|
||||
if len(classes.Items) == 0 {
|
||||
// None of our classes matched the selector. We can't be sure whether
|
||||
// another controller owns classes that matched the selector, or whether
|
||||
// no classes match, so we requeue after a short wait. We'll abort the
|
||||
// next reconcile immediately if another controller scheduled the claim.
|
||||
log.Debug("No matching resource classes found", "requeue-after", time.Now().Add(aShortWait))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, nil
|
||||
}
|
||||
|
||||
random := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
selected := classes.Items[random.Intn(len(classes.Items))]
|
||||
claim.SetClassReference(meta.ReferenceTo(&selected, schema.GroupVersionKind(r.classKind)))
|
||||
|
||||
// There could be several controllers racing to schedule this claim to a
|
||||
// class. We sleep for a randomly jittered amount of time before trying to
|
||||
// update the class reference to decrease the chance of any one controller
|
||||
// predictably winning the race, for example because it has fewer classes to
|
||||
// list and select from than its competitors.
|
||||
r.jitter()
|
||||
|
||||
// Attempt to set the class reference. If a competing controller beat us
|
||||
// we'll fail the write because the claim's resource version has changed
|
||||
// since we read it. We'll be requeued, but will abort immediately if the
|
||||
// claim was scheduled.
|
||||
log.Debug("Attempting to set resource class", "class-name", selected.GetName())
|
||||
record.Event(claim, event.Normal(reasonClassFound, "Selected matching resource class", "class-name", selected.GetName()))
|
||||
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Update(ctx, claim), errUpdateClaim)
|
||||
}
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
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 claimscheduling
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var _ reconcile.Reconciler = &Reconciler{}
|
||||
|
||||
func TestReconciler(t *testing.T) {
|
||||
name := "coolName"
|
||||
uid := types.UID("definitely-a-uuid")
|
||||
|
||||
type args struct {
|
||||
m manager.Manager
|
||||
of resource.ClaimKind
|
||||
to resource.ClassKind
|
||||
}
|
||||
|
||||
type want struct {
|
||||
result reconcile.Result
|
||||
err error
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"GetClaimError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errGetClaim)},
|
||||
},
|
||||
"ClaimNotFound": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ClaimHasClassRef": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassReferencer: fake.ClassReferencer{Ref: &corev1.ObjectReference{}}}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"ListClassesError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassSelector: fake.ClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(errBoom),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errListClasses)},
|
||||
},
|
||||
"NoClassesMatchLabels": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassSelector: fake.ClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"UpdateClaimError": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassSelector: fake.ClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(fake.GVK(&fake.Class{}))
|
||||
u.SetName(name)
|
||||
u.SetUID(uid)
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
l.Items = []unstructured.Unstructured{*u}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errUpdateClaim)},
|
||||
},
|
||||
"Successful": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassSelector: fake.ClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(fake.GVK(&fake.Class{}))
|
||||
u.SetName(name)
|
||||
u.SetUID(uid)
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
l.Items = []unstructured.Unstructured{*u}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Claim{}
|
||||
want.SetClassSelector(&metav1.LabelSelector{})
|
||||
want.SetClassReference(&corev1.ObjectReference{
|
||||
APIVersion: fake.GVK(&fake.Class{}).GroupVersion().String(),
|
||||
Kind: fake.GVK(&fake.Class{}).Kind,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
})
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
},
|
||||
of: resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
to: resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewReconciler(tc.args.m, tc.args.of, tc.args.to, WithSchedulingJitterer(func() {}))
|
||||
got, err := r.Reconcile(reconcile.Request{})
|
||||
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.result, got); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcilerRandomness(t *testing.T) {
|
||||
classes := 10
|
||||
reconciles := 100
|
||||
refs := make([]*corev1.ObjectReference, 0)
|
||||
|
||||
newClass := func(i int) unstructured.Unstructured {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetUID(types.UID(strconv.Itoa(i)))
|
||||
return *u
|
||||
}
|
||||
|
||||
m := &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*fake.Claim)
|
||||
*c = fake.Claim{ClassSelector: fake.ClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
for i := 0; i < classes; i++ {
|
||||
l.Items = append(l.Items, newClass(i))
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(obj runtime.Object) error {
|
||||
ls := obj.(resource.ClassReferencer)
|
||||
refs = append(refs, ls.GetClassReference())
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Claim{}),
|
||||
}
|
||||
|
||||
r := NewReconciler(m,
|
||||
resource.ClaimKind(fake.GVK(&fake.Claim{})),
|
||||
resource.ClassKind(fake.GVK(&fake.Class{})),
|
||||
WithSchedulingJitterer(func() {}))
|
||||
|
||||
for i := 0; i < reconciles; i++ {
|
||||
r.Reconcile(reconcile.Request{})
|
||||
}
|
||||
|
||||
distribution := map[types.UID]int{}
|
||||
for _, ref := range refs {
|
||||
distribution[ref.UID]++
|
||||
}
|
||||
|
||||
// The goal here is to test whether we're random-ish, i.e. that we're not
|
||||
// picking the same class every time.
|
||||
if len(distribution) < 2 {
|
||||
t.Errorf("want > 1 resource classes selected, got %d", len(distribution))
|
||||
}
|
||||
}
|
||||
|
|
@ -545,7 +545,7 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error)
|
|||
if meta.WasDeleted(managed) {
|
||||
log = log.WithValues("deletion-timestamp", managed.GetDeletionTimestamp())
|
||||
|
||||
if observation.ResourceExists && shouldDelete(managed) {
|
||||
if observation.ResourceExists && managed.GetDeletionPolicy() != v1alpha1.DeletionOrphan {
|
||||
if err := external.Delete(externalCtx, managed); err != nil {
|
||||
// We'll hit this condition if we can't delete our external
|
||||
// resource, for example if our provider credentials don't have
|
||||
|
|
@ -695,23 +695,3 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error)
|
|||
managed.SetConditions(v1alpha1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: r.longWait}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// TODO(negz): ReclaimPolicy will be deprecated alongside resource claims; this
|
||||
// should be inlined when claims (and thus reclaim policies) are removed.
|
||||
func shouldDelete(mg resource.Managed) bool {
|
||||
switch {
|
||||
// The deletion policy should take precedence over the reclaim policy.
|
||||
case mg.GetDeletionPolicy() == v1alpha1.DeletionOrphan:
|
||||
return false
|
||||
case mg.GetDeletionPolicy() == v1alpha1.DeletionDelete:
|
||||
return true
|
||||
|
||||
case mg.GetReclaimPolicy() == v1alpha1.ReclaimRetain:
|
||||
return false
|
||||
case mg.GetReclaimPolicy() == v1alpha1.ReclaimDelete:
|
||||
return true
|
||||
}
|
||||
|
||||
// If no policy is set, we default to deleting the resource.
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,13 +207,13 @@ func TestReconciler(t *testing.T) {
|
|||
MockGet: test.NewMockGetFn(nil, func(obj runtime.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetDeletionTimestamp(&now)
|
||||
mg.SetReclaimPolicy(v1alpha1.ReclaimDelete)
|
||||
mg.SetDeletionPolicy(v1alpha1.DeletionDelete)
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetDeletionTimestamp(&now)
|
||||
want.SetReclaimPolicy(v1alpha1.ReclaimDelete)
|
||||
want.SetDeletionPolicy(v1alpha1.DeletionDelete)
|
||||
want.SetConditions(v1alpha1.ReconcileError(errors.Wrap(errBoom, errReconcileDelete)))
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "An error deleting an external resource should be reported as a conditioned status."
|
||||
|
|
@ -251,13 +251,13 @@ func TestReconciler(t *testing.T) {
|
|||
MockGet: test.NewMockGetFn(nil, func(obj runtime.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetDeletionTimestamp(&now)
|
||||
mg.SetReclaimPolicy(v1alpha1.ReclaimDelete)
|
||||
mg.SetDeletionPolicy(v1alpha1.DeletionDelete)
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetDeletionTimestamp(&now)
|
||||
want.SetReclaimPolicy(v1alpha1.ReclaimDelete)
|
||||
want.SetDeletionPolicy(v1alpha1.DeletionDelete)
|
||||
want.SetConditions(v1alpha1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "A deleted external resource should be reported as a conditioned status."
|
||||
|
|
@ -759,58 +759,3 @@ func TestReconciler(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldDelete(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
mg resource.Managed
|
||||
want bool
|
||||
}{
|
||||
"DeletionPolicyDelete": {
|
||||
reason: "The delete deletion policy should take precedence over the reclaim policy.",
|
||||
mg: &fake.Managed{
|
||||
Orphanable: fake.Orphanable{Policy: v1alpha1.DeletionDelete},
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"DeletionPolicyOrphan": {
|
||||
reason: "The orphan deletion policy should take precedence over the reclaim policy.",
|
||||
mg: &fake.Managed{
|
||||
Orphanable: fake.Orphanable{Policy: v1alpha1.DeletionOrphan},
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"ReclaimPolicyDelete": {
|
||||
reason: "The delete reclaim policy should take effect when no deletion policy exists.",
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimDelete},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ReclaimPolicyRetain": {
|
||||
reason: "The retain reclaim policy should take effect when no deletion policy exists.",
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{Policy: v1alpha1.ReclaimRetain},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"NoPolicy": {
|
||||
reason: "Resources should be deleted when no deletion or reclaim policy is specified.",
|
||||
mg: &fake.Managed{
|
||||
Reclaimer: fake.Reclaimer{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := shouldDelete(tc.mg)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("\nReason: %s\nshouldDelete(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,14 +55,14 @@ func TestReconciler(t *testing.T) {
|
|||
Name: "coolmanagedsecret",
|
||||
}},
|
||||
}
|
||||
cm := &fake.Claim{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "coolns", Name: "coolclaim"},
|
||||
tg := &fake.Target{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "coolns", Name: "cooltarget"},
|
||||
LocalConnectionSecretWriterTo: fake.LocalConnectionSecretWriterTo{Ref: &v1alpha1.LocalSecretReference{
|
||||
Name: "coolclaimsecret",
|
||||
Name: "cooltargetsecret",
|
||||
}},
|
||||
}
|
||||
from := resource.ConnectionSecretFor(mg, fake.GVK(mg))
|
||||
to := resource.LocalConnectionSecretFor(cm, fake.GVK(cm))
|
||||
to := resource.LocalConnectionSecretFor(tg, fake.GVK(tg))
|
||||
|
||||
fromData := map[string][]byte{"cool": {1}}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,15 +43,13 @@ const (
|
|||
|
||||
// Error strings
|
||||
const (
|
||||
errGetTarget = "unable to get Target"
|
||||
errManagedResourceIsNotBound = "managed resource in Target clusterRef is unbound"
|
||||
errUpdateTarget = "unable to update Target"
|
||||
errGetTarget = "unable to get Target"
|
||||
errUpdateTarget = "unable to update Target"
|
||||
)
|
||||
|
||||
// Event reasons.
|
||||
const (
|
||||
reasonSetSecretRef event.Reason = "SetSecretRef"
|
||||
reasonWaitingUntilBound event.Reason = "WaitingUntilBound"
|
||||
reasonCannotGetManaged event.Reason = "CannotGetManaged"
|
||||
reasonCannotPropagateSecret event.Reason = "CannotPropagateSecret"
|
||||
reasonPropagatedSecret event.Reason = "PropagatedSecret"
|
||||
|
|
@ -201,14 +199,6 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error)
|
|||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, target), errUpdateTarget)
|
||||
}
|
||||
|
||||
if !resource.IsBound(managed) {
|
||||
// TODO(negz): Should we really consider this an error?
|
||||
log.Debug("Managed resource is not yet bound to a resource claim", "requeue-after", time.Now().Add(aShortWait))
|
||||
record.Event(target, event.Normal(reasonWaitingUntilBound, "Managed resource is not yet bound to a resource claim"))
|
||||
target.SetConditions(SecretPropagationError(errors.New(errManagedResourceIsNotBound)))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, target), errUpdateTarget)
|
||||
}
|
||||
|
||||
if err := r.propagator.PropagateConnection(ctx, target, managed); err != nil {
|
||||
// If we fail to propagate the connection secret of a bound managed
|
||||
// resource, we try again after a short wait.
|
||||
|
|
|
|||
|
|
@ -248,67 +248,6 @@ func TestReconciler(t *testing.T) {
|
|||
result: reconcile.Result{RequeueAfter: aShortWait},
|
||||
},
|
||||
},
|
||||
"ErrorManagedNotBound": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
tg := &fake.Target{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: tguid,
|
||||
Name: tgname,
|
||||
Namespace: ns,
|
||||
}}
|
||||
tg.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
tg.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{
|
||||
Name: tgcsname,
|
||||
})
|
||||
*o = *tg
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: mguid,
|
||||
Name: mgname,
|
||||
}}
|
||||
mg.SetWriteConnectionSecretToReference(&v1alpha1.SecretReference{
|
||||
Name: mgcsname,
|
||||
Namespace: mgcsnamespace,
|
||||
})
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Target{}
|
||||
want.SetName(tgname)
|
||||
want.SetNamespace(ns)
|
||||
want.SetUID(tguid)
|
||||
want.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
want.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{Name: tgcsname})
|
||||
want.SetConditions(SecretPropagationError(errors.New(errManagedResourceIsNotBound)))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: resource.TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: resource.ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{RequeueAfter: aShortWait},
|
||||
},
|
||||
},
|
||||
"ErrorSecretPropagationFailed": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
|
|
@ -338,7 +277,6 @@ func TestReconciler(t *testing.T) {
|
|||
Name: mgcsname,
|
||||
Namespace: mgcsnamespace,
|
||||
})
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
|
|
@ -406,7 +344,6 @@ func TestReconciler(t *testing.T) {
|
|||
Name: mgcsname,
|
||||
Namespace: mgcsnamespace,
|
||||
})
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ func TestPropagateConnection(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
cm := &fake.Claim{
|
||||
cm := &fake.CompositeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: cmcsns},
|
||||
LocalConnectionSecretWriterTo: fake.LocalConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: cmcsname},
|
||||
|
|
@ -83,7 +83,7 @@ func TestPropagateConnection(t *testing.T) {
|
|||
"ClaimDoesNotWantConnectionSecret": {
|
||||
reason: "The managed resource's secret should not be propagated if the claim does not want to write one",
|
||||
args: args{
|
||||
o: &fake.Claim{},
|
||||
o: &fake.CompositeClaim{},
|
||||
mg: mg,
|
||||
},
|
||||
want: nil,
|
||||
|
|
|
|||
|
|
@ -30,41 +30,6 @@ type adder interface {
|
|||
Add(item interface{})
|
||||
}
|
||||
|
||||
// EnqueueRequestForClaim enqueues a reconcile.Request for the NamespacedName
|
||||
// of a ClaimReferencer's ClaimReference.
|
||||
type EnqueueRequestForClaim struct{}
|
||||
|
||||
// Create adds a NamespacedName for the supplied CreateEvent if its Object is a
|
||||
// ClaimReferencer.
|
||||
func (e *EnqueueRequestForClaim) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) {
|
||||
addClaim(evt.Object, q)
|
||||
}
|
||||
|
||||
// Update adds a NamespacedName for the supplied UpdateEvent if its Objects are
|
||||
// ClaimReferencers.
|
||||
func (e *EnqueueRequestForClaim) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) {
|
||||
addClaim(evt.ObjectOld, q)
|
||||
addClaim(evt.ObjectNew, q)
|
||||
}
|
||||
|
||||
// Delete adds a NamespacedName for the supplied DeleteEvent if its Object is a
|
||||
// ClaimReferencer.
|
||||
func (e *EnqueueRequestForClaim) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
|
||||
addClaim(evt.Object, q)
|
||||
}
|
||||
|
||||
// Generic adds a NamespacedName for the supplied GenericEvent if its Object is a
|
||||
// ClaimReferencer.
|
||||
func (e *EnqueueRequestForClaim) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) {
|
||||
addClaim(evt.Object, q)
|
||||
}
|
||||
|
||||
func addClaim(obj runtime.Object, queue adder) {
|
||||
if cr, ok := obj.(ClaimReferencer); ok && cr.GetClaimReference() != nil {
|
||||
queue.Add(reconcile.Request{NamespacedName: meta.NamespacedNameOf(cr.GetClaimReference())})
|
||||
}
|
||||
}
|
||||
|
||||
// EnqueueRequestForPropagated enqueues a reconcile.Request for the
|
||||
// NamespacedName of a propagated object, i.e. an object with propagation
|
||||
// metadata annotations.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
|
@ -32,7 +31,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
_ handler.EventHandler = &EnqueueRequestForClaim{}
|
||||
_ handler.EventHandler = &EnqueueRequestForPropagated{}
|
||||
)
|
||||
|
||||
type addFn func(item interface{})
|
||||
|
|
@ -41,40 +40,6 @@ func (fn addFn) Add(item interface{}) {
|
|||
fn(item)
|
||||
}
|
||||
|
||||
func TestAddClaim(t *testing.T) {
|
||||
ns := "coolns"
|
||||
name := "coolname"
|
||||
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
queue adder
|
||||
}{
|
||||
"ObjectIsNotAClaimReferencer": {
|
||||
queue: addFn(func(_ interface{}) { t.Errorf("queue.Add() called unexpectedly") }),
|
||||
},
|
||||
"ObjectHasNilClaimReference": {
|
||||
obj: &fake.Managed{},
|
||||
queue: addFn(func(_ interface{}) { t.Errorf("queue.Add() called unexpectedly") }),
|
||||
},
|
||||
"ObjectHasClaimReference": {
|
||||
obj: &fake.Managed{ClaimReferencer: fake.ClaimReferencer{Ref: &corev1.ObjectReference{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
}}},
|
||||
queue: addFn(func(got interface{}) {
|
||||
want := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: ns, Name: name}}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
addClaim(tc.obj, tc.queue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddPropagated(t *testing.T) {
|
||||
ns := "coolns"
|
||||
name := "coolname"
|
||||
|
|
@ -94,11 +59,11 @@ func TestAddPropagated(t *testing.T) {
|
|||
},
|
||||
"IsPropagator": {
|
||||
obj: func() runtime.Object {
|
||||
cm := &fake.Claim{}
|
||||
cm.SetNamespace(ns)
|
||||
cm.SetName(name)
|
||||
tg := &fake.Target{}
|
||||
tg.SetNamespace(ns)
|
||||
tg.SetName(name)
|
||||
mg := &fake.Managed{}
|
||||
meta.AllowPropagation(mg, cm)
|
||||
meta.AllowPropagation(mg, tg)
|
||||
return mg
|
||||
}(),
|
||||
queue: addFn(func(got interface{}) {
|
||||
|
|
|
|||
|
|
@ -33,15 +33,6 @@ import (
|
|||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
)
|
||||
|
||||
// Bindable is a mock that implements Bindable interface.
|
||||
type Bindable struct{ Phase v1alpha1.BindingPhase }
|
||||
|
||||
// SetBindingPhase sets the BindingPhase.
|
||||
func (m *Bindable) SetBindingPhase(p v1alpha1.BindingPhase) { m.Phase = p }
|
||||
|
||||
// GetBindingPhase sets the BindingPhase.
|
||||
func (m *Bindable) GetBindingPhase() v1alpha1.BindingPhase { return m.Phase }
|
||||
|
||||
// Conditioned is a mock that implements Conditioned interface.
|
||||
type Conditioned struct{ Conditions []v1alpha1.Condition }
|
||||
|
||||
|
|
@ -62,24 +53,6 @@ func (m *ClaimReferencer) SetClaimReference(r *corev1.ObjectReference) { m.Ref =
|
|||
// GetClaimReference gets the ClaimReference.
|
||||
func (m *ClaimReferencer) GetClaimReference() *corev1.ObjectReference { return m.Ref }
|
||||
|
||||
// ClassSelector is a mock that implements ClassSelector interface.
|
||||
type ClassSelector struct{ Sel *metav1.LabelSelector }
|
||||
|
||||
// SetClassSelector sets the ClassSelector.
|
||||
func (m *ClassSelector) SetClassSelector(s *metav1.LabelSelector) { m.Sel = s }
|
||||
|
||||
// GetClassSelector gets the ClassSelector.
|
||||
func (m *ClassSelector) GetClassSelector() *metav1.LabelSelector { return m.Sel }
|
||||
|
||||
// ClassReferencer is a mock that implements ClassReferencer interface.
|
||||
type ClassReferencer struct{ Ref *corev1.ObjectReference }
|
||||
|
||||
// SetClassReference sets the ClassReference.
|
||||
func (m *ClassReferencer) SetClassReference(r *corev1.ObjectReference) { m.Ref = r }
|
||||
|
||||
// GetClassReference gets the ClassReference.
|
||||
func (m *ClassReferencer) GetClassReference() *corev1.ObjectReference { return m.Ref }
|
||||
|
||||
// ManagedResourceReferencer is a mock that implements ManagedResourceReferencer interface.
|
||||
type ManagedResourceReferencer struct{ Ref *corev1.ObjectReference }
|
||||
|
||||
|
|
@ -135,15 +108,6 @@ func (m *ConnectionSecretWriterTo) GetWriteConnectionSecretToReference() *v1alph
|
|||
return m.Ref
|
||||
}
|
||||
|
||||
// Reclaimer is a mock that implements Reclaimer interface.
|
||||
type Reclaimer struct{ Policy v1alpha1.ReclaimPolicy }
|
||||
|
||||
// SetReclaimPolicy sets the ReclaimPolicy.
|
||||
func (m *Reclaimer) SetReclaimPolicy(p v1alpha1.ReclaimPolicy) { m.Policy = p }
|
||||
|
||||
// GetReclaimPolicy gets the ReclaimPolicy.
|
||||
func (m *Reclaimer) GetReclaimPolicy() v1alpha1.ReclaimPolicy { return m.Policy }
|
||||
|
||||
// Orphanable implements the Orphanable interface.
|
||||
type Orphanable struct{ Policy v1alpha1.DeletionPolicy }
|
||||
|
||||
|
|
@ -225,67 +189,14 @@ func (o *Object) DeepCopyObject() runtime.Object {
|
|||
return out
|
||||
}
|
||||
|
||||
// Claim is a mock that implements Claim interface.
|
||||
type Claim struct {
|
||||
metav1.ObjectMeta
|
||||
ClassSelector
|
||||
ClassReferencer
|
||||
ManagedResourceReferencer
|
||||
LocalConnectionSecretWriterTo
|
||||
v1alpha1.ConditionedStatus
|
||||
v1alpha1.BindingStatus
|
||||
}
|
||||
|
||||
// GetObjectKind returns schema.ObjectKind.
|
||||
func (m *Claim) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a copy of the object as runtime.Object
|
||||
func (m *Claim) DeepCopyObject() runtime.Object {
|
||||
out := &Claim{}
|
||||
j, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = json.Unmarshal(j, out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Class is a mock that implements Class interface.
|
||||
type Class struct {
|
||||
metav1.ObjectMeta
|
||||
Reclaimer
|
||||
}
|
||||
|
||||
// GetObjectKind returns schema.ObjectKind.
|
||||
func (m *Class) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a copy of the object as runtime.Object
|
||||
func (m *Class) DeepCopyObject() runtime.Object {
|
||||
out := &Class{}
|
||||
j, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = json.Unmarshal(j, out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Managed is a mock that implements Managed interface.
|
||||
type Managed struct {
|
||||
metav1.ObjectMeta
|
||||
ClassReferencer
|
||||
ClaimReferencer
|
||||
ProviderReferencer
|
||||
ProviderConfigReferencer
|
||||
ConnectionSecretWriterTo
|
||||
Orphanable
|
||||
Reclaimer
|
||||
v1alpha1.ConditionedStatus
|
||||
v1alpha1.BindingStatus
|
||||
}
|
||||
|
||||
// GetObjectKind returns schema.ObjectKind.
|
||||
|
|
@ -357,7 +268,6 @@ type Composite struct {
|
|||
CompositionReferencer
|
||||
ComposedResourcesReferencer
|
||||
ClaimReferencer
|
||||
Reclaimer
|
||||
ConnectionSecretWriterTo
|
||||
v1alpha1.ConditionedStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,6 @@ import (
|
|||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
)
|
||||
|
||||
// A Bindable resource may be bound to another resource. Resources are bindable
|
||||
// when they available for use.
|
||||
type Bindable interface {
|
||||
SetBindingPhase(p v1alpha1.BindingPhase)
|
||||
GetBindingPhase() v1alpha1.BindingPhase
|
||||
}
|
||||
|
||||
// A Conditioned may have conditions set or retrieved. Conditions are typically
|
||||
// indicate the status of both a resource and its reconciliation process.
|
||||
type Conditioned interface {
|
||||
|
|
@ -46,18 +39,6 @@ type ClaimReferencer interface {
|
|||
GetClaimReference() *corev1.ObjectReference
|
||||
}
|
||||
|
||||
// A ClassSelector may reference a resource class.
|
||||
type ClassSelector interface {
|
||||
SetClassSelector(s *metav1.LabelSelector)
|
||||
GetClassSelector() *metav1.LabelSelector
|
||||
}
|
||||
|
||||
// A ClassReferencer may reference a resource class.
|
||||
type ClassReferencer interface {
|
||||
SetClassReference(r *corev1.ObjectReference)
|
||||
GetClassReference() *corev1.ObjectReference
|
||||
}
|
||||
|
||||
// A ManagedResourceReferencer may reference a concrete managed resource.
|
||||
type ManagedResourceReferencer interface {
|
||||
SetResourceReference(r *corev1.ObjectReference)
|
||||
|
|
@ -78,12 +59,6 @@ type ConnectionSecretWriterTo interface {
|
|||
GetWriteConnectionSecretToReference() *v1alpha1.SecretReference
|
||||
}
|
||||
|
||||
// A Reclaimer may specify a ReclaimPolicy.
|
||||
type Reclaimer interface {
|
||||
SetReclaimPolicy(p v1alpha1.ReclaimPolicy)
|
||||
GetReclaimPolicy() v1alpha1.ReclaimPolicy
|
||||
}
|
||||
|
||||
// An Orphanable resource may specify a DeletionPolicy.
|
||||
type Orphanable interface {
|
||||
SetDeletionPolicy(p v1alpha1.DeletionPolicy)
|
||||
|
|
@ -145,60 +120,17 @@ type Object interface {
|
|||
runtime.Object
|
||||
}
|
||||
|
||||
// A Claim is a Kubernetes object representing an abstract resource claim (e.g.
|
||||
// an SQL database) that may be bound to a concrete managed resource (e.g. a
|
||||
// CloudSQL instance).
|
||||
type Claim interface {
|
||||
Object
|
||||
|
||||
ClassSelector
|
||||
ClassReferencer
|
||||
ManagedResourceReferencer
|
||||
LocalConnectionSecretWriterTo
|
||||
|
||||
Conditioned
|
||||
Bindable
|
||||
}
|
||||
|
||||
// A ClaimList is a list of resource claims.
|
||||
type ClaimList interface {
|
||||
runtime.Object
|
||||
|
||||
// GetItems returns the list of resource claims.
|
||||
GetItems() []Claim
|
||||
}
|
||||
|
||||
// A Class is a Kubernetes object representing configuration specifications for
|
||||
// a managed resource.
|
||||
type Class interface {
|
||||
Object
|
||||
|
||||
Reclaimer
|
||||
}
|
||||
|
||||
// A ClassList is a list of resource classes.
|
||||
type ClassList interface {
|
||||
runtime.Object
|
||||
|
||||
// GetItems returns the list of resource classes.
|
||||
GetItems() []Class
|
||||
}
|
||||
|
||||
// A Managed is a Kubernetes object representing a concrete managed
|
||||
// resource (e.g. a CloudSQL instance).
|
||||
type Managed interface {
|
||||
Object
|
||||
|
||||
ClassReferencer
|
||||
ClaimReferencer
|
||||
ProviderReferencer
|
||||
ProviderConfigReferencer
|
||||
ConnectionSecretWriterTo
|
||||
Orphanable
|
||||
Reclaimer
|
||||
|
||||
Conditioned
|
||||
Bindable
|
||||
}
|
||||
|
||||
// A ManagedList is a list of managed resources.
|
||||
|
|
|
|||
|
|
@ -81,23 +81,6 @@ func HasManagedResourceReferenceKind(k ManagedKind) PredicateFn {
|
|||
}
|
||||
}
|
||||
|
||||
// HasClassReferenceKind accepts objects that reference the supplied resource
|
||||
// class kind.
|
||||
func HasClassReferenceKind(k ClassKind) PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
r, ok := obj.(ClassReferencer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if r.GetClassReference() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return r.GetClassReference().GroupVersionKind() == schema.GroupVersionKind(k)
|
||||
}
|
||||
}
|
||||
|
||||
// IsManagedKind accepts objects that are of the supplied managed resource kind.
|
||||
func IsManagedKind(k ManagedKind, ot runtime.ObjectTyper) PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
|
|
@ -152,51 +135,3 @@ func IsPropagated() PredicateFn {
|
|||
return nn.Namespace != "" && nn.Name != ""
|
||||
}
|
||||
}
|
||||
|
||||
// HasClassSelector accepts resource claims that do not specify a resource
|
||||
// class selector.
|
||||
func HasClassSelector() PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
cs, ok := obj.(ClassSelector)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return cs.GetClassSelector() != nil
|
||||
}
|
||||
}
|
||||
|
||||
// HasNoClassSelector accepts resource claims that do not specify a resource
|
||||
// class selector.
|
||||
func HasNoClassSelector() PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
cs, ok := obj.(ClassSelector)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return cs.GetClassSelector() == nil
|
||||
}
|
||||
}
|
||||
|
||||
// HasNoClassReference accepts resource claims that do not reference a specific
|
||||
// resource class.
|
||||
func HasNoClassReference() PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
cr, ok := obj.(ClassReferencer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return cr.GetClassReference() == nil
|
||||
}
|
||||
}
|
||||
|
||||
// HasNoManagedResourceReference accepts resource claims that do not reference a
|
||||
// specific managed resource.
|
||||
func HasNoManagedResourceReference() PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
cr, ok := obj.(ManagedResourceReferencer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return cr.GetResourceReference() == nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,9 @@ import (
|
|||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
func TestAnyOf(t *testing.T) {
|
||||
|
|
@ -102,88 +100,6 @@ func TestAllOf(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasManagedResourceReferenceKind(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
c client.Client
|
||||
kind ManagedKind
|
||||
want bool
|
||||
}{
|
||||
"NotAClassReferencer": {
|
||||
c: &test.MockClient{},
|
||||
kind: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
want: false,
|
||||
},
|
||||
"HasNoResourceReference": {
|
||||
obj: &fake.Claim{},
|
||||
kind: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
want: false,
|
||||
},
|
||||
"HasCorrectResourceReference": {
|
||||
obj: &fake.Claim{
|
||||
ManagedResourceReferencer: fake.ManagedResourceReferencer{
|
||||
Ref: &corev1.ObjectReference{
|
||||
APIVersion: fake.GVK(&fake.Managed{}).GroupVersion().String(),
|
||||
Kind: fake.GVK(&fake.Managed{}).Kind,
|
||||
},
|
||||
},
|
||||
},
|
||||
kind: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasManagedResourceReferenceKind(tc.kind)(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("HasManagedResourceReferenceKind(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClassReferenceKind(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
c client.Client
|
||||
kind ClassKind
|
||||
want bool
|
||||
}{
|
||||
"NotAClassReferencer": {
|
||||
c: &test.MockClient{},
|
||||
kind: ClassKind(fake.GVK(&fake.Class{})),
|
||||
want: false,
|
||||
},
|
||||
"HasNoClassReference": {
|
||||
obj: &fake.Claim{},
|
||||
kind: ClassKind(fake.GVK(&fake.Class{})),
|
||||
want: false,
|
||||
},
|
||||
"HasCorrectClassReference": {
|
||||
obj: &fake.Claim{
|
||||
ClassReferencer: fake.ClassReferencer{
|
||||
Ref: &corev1.ObjectReference{
|
||||
APIVersion: fake.GVK(&fake.Class{}).GroupVersion().String(),
|
||||
Kind: fake.GVK(&fake.Class{}).Kind,
|
||||
},
|
||||
},
|
||||
},
|
||||
kind: ClassKind(fake.GVK(&fake.Class{})),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasClassReferenceKind(tc.kind)(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("HasClassReferenceKind(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsManagedKind(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
kind ManagedKind
|
||||
|
|
@ -198,7 +114,7 @@ func TestIsManagedKind(t *testing.T) {
|
|||
},
|
||||
"IsNotKind": {
|
||||
kind: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
ot: MockTyper{GVKs: []schema.GroupVersionKind{fake.GVK(&fake.Claim{})}},
|
||||
ot: MockTyper{GVKs: []schema.GroupVersionKind{fake.GVK(&fake.Object{})}},
|
||||
want: false,
|
||||
},
|
||||
"ErrorDeterminingKind": {
|
||||
|
|
@ -292,11 +208,11 @@ func TestIsPropagator(t *testing.T) {
|
|||
},
|
||||
"IsPropagator": {
|
||||
obj: func() runtime.Object {
|
||||
cm := &fake.Claim{}
|
||||
cm.SetNamespace("somenamespace")
|
||||
cm.SetName("somename")
|
||||
o := &fake.Object{}
|
||||
o.SetNamespace("somenamespace")
|
||||
o.SetName("somename")
|
||||
mg := &fake.Managed{}
|
||||
meta.AllowPropagation(mg, cm)
|
||||
meta.AllowPropagation(mg, o)
|
||||
return mg
|
||||
}(),
|
||||
want: true,
|
||||
|
|
@ -329,12 +245,12 @@ func TestIsPropagated(t *testing.T) {
|
|||
},
|
||||
"IsPropagated": {
|
||||
obj: func() runtime.Object {
|
||||
cm := &fake.Claim{}
|
||||
o := &fake.Object{}
|
||||
mg := &fake.Managed{}
|
||||
mg.SetNamespace("somenamespace")
|
||||
mg.SetName("somename")
|
||||
meta.AllowPropagation(mg, cm)
|
||||
return cm
|
||||
meta.AllowPropagation(mg, o)
|
||||
return o
|
||||
}(),
|
||||
want: true,
|
||||
},
|
||||
|
|
@ -349,115 +265,3 @@ func TestIsPropagated(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClassSelector(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
want bool
|
||||
}{
|
||||
"NotAClassSelector": {
|
||||
want: false,
|
||||
},
|
||||
"NoClassSelector": {
|
||||
obj: &fake.Claim{},
|
||||
want: false,
|
||||
},
|
||||
"HasClassSelector": {
|
||||
obj: &fake.Claim{ClassSelector: fake.ClassSelector{Sel: &v1.LabelSelector{}}},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasClassSelector()(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("HasClassSelector(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNoClassSelector(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
want bool
|
||||
}{
|
||||
"NotAClassSelector": {
|
||||
want: false,
|
||||
},
|
||||
"NoClassSelector": {
|
||||
obj: &fake.Claim{},
|
||||
want: true,
|
||||
},
|
||||
"HasClassSelector": {
|
||||
obj: &fake.Claim{ClassSelector: fake.ClassSelector{Sel: &v1.LabelSelector{}}},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasNoClassSelector()(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("HasNoClassSelector(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNoClassReference(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
want bool
|
||||
}{
|
||||
"NotAClassReferencer": {
|
||||
want: false,
|
||||
},
|
||||
"NoClassReference": {
|
||||
obj: &fake.Claim{},
|
||||
want: true,
|
||||
},
|
||||
"HasClassReference": {
|
||||
obj: &fake.Claim{ClassReferencer: fake.ClassReferencer{Ref: &corev1.ObjectReference{}}},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasNoClassReference()(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("HasNoClassReference(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNoMangedResourceReference(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
want bool
|
||||
}{
|
||||
"NotAManagedResourceReferencer": {
|
||||
want: false,
|
||||
},
|
||||
"NoManagedResourceReference": {
|
||||
obj: &fake.Claim{},
|
||||
want: true,
|
||||
},
|
||||
"HasClassReference": {
|
||||
obj: &fake.Claim{ManagedResourceReferencer: fake.ManagedResourceReferencer{Ref: &corev1.ObjectReference{}}},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasNoManagedResourceReference()(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("HasNoManagedResourecReference(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,25 +41,9 @@ const SecretTypeConnection corev1.SecretType = "connection.crossplane.io/v1alpha
|
|||
const (
|
||||
ExternalResourceTagKeyKind = "crossplane-kind"
|
||||
ExternalResourceTagKeyName = "crossplane-name"
|
||||
ExternalResourceTagKeyClass = "crossplane-class"
|
||||
ExternalResourceTagKeyProvider = "crossplane-providerconfig"
|
||||
)
|
||||
|
||||
// A ClaimKind contains the type metadata for a kind of resource claim.
|
||||
type ClaimKind schema.GroupVersionKind
|
||||
|
||||
// A ClassKind contains the type metadata for a kind of resource class.
|
||||
type ClassKind schema.GroupVersionKind
|
||||
|
||||
// List returns the list kind associated with a ClassKind.
|
||||
func (k ClassKind) List() schema.GroupVersionKind {
|
||||
return schema.GroupVersionKind{
|
||||
Group: k.Group,
|
||||
Version: k.Version,
|
||||
Kind: k.Kind + "List",
|
||||
}
|
||||
}
|
||||
|
||||
// A ManagedKind contains the type metadata for a kind of managed resource.
|
||||
type ManagedKind schema.GroupVersionKind
|
||||
|
||||
|
|
@ -94,7 +78,7 @@ type ConnectionPropagatorFn func(ctx context.Context, to LocalConnectionSecretOw
|
|||
|
||||
// A ManagedConnectionPropagator is responsible for propagating information
|
||||
// required to connect to a managed resource (for example the connection secret)
|
||||
// from the managed resource to its resource claim.
|
||||
// from the managed resource to a target.
|
||||
type ManagedConnectionPropagator interface {
|
||||
PropagateConnection(ctx context.Context, o LocalConnectionSecretOwner, mg Managed) error
|
||||
}
|
||||
|
|
@ -204,46 +188,6 @@ func IgnoreNotFound(err error) error {
|
|||
return Ignore(kerrors.IsNotFound, err)
|
||||
}
|
||||
|
||||
// ResolveClassClaimValues validates the supplied claim value against the
|
||||
// supplied resource class value. If both are non-zero they must match.
|
||||
func ResolveClassClaimValues(classValue, claimValue string) (string, error) {
|
||||
if classValue == "" {
|
||||
return claimValue, nil
|
||||
}
|
||||
if claimValue == "" {
|
||||
return classValue, nil
|
||||
}
|
||||
if classValue != claimValue {
|
||||
return "", errors.Errorf("claim value [%s] does not match class value [%s]", claimValue, classValue)
|
||||
}
|
||||
return claimValue, nil
|
||||
}
|
||||
|
||||
// SetBindable indicates that the supplied Bindable is ready for binding to
|
||||
// another Bindable, such as a resource claim or managed resource by setting its
|
||||
// binding phase to "Unbound". It is a no-op for Bindables in phases "Bound" or
|
||||
// "Released", because these phases may not transition back to "Unbound".
|
||||
func SetBindable(b Bindable) {
|
||||
switch b.GetBindingPhase() {
|
||||
case v1alpha1.BindingPhaseBound, v1alpha1.BindingPhaseReleased:
|
||||
return
|
||||
default:
|
||||
b.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
|
||||
}
|
||||
}
|
||||
|
||||
// IsBindable returns true if the supplied Bindable is ready for binding to
|
||||
// another Bindable, such as a resource claim or managed
|
||||
func IsBindable(b Bindable) bool {
|
||||
return b.GetBindingPhase() == v1alpha1.BindingPhaseUnbound
|
||||
}
|
||||
|
||||
// IsBound returns true if the supplied Bindable is bound to another Bindable,
|
||||
// such as a resource claim or managed
|
||||
func IsBound(b Bindable) bool {
|
||||
return b.GetBindingPhase() == v1alpha1.BindingPhaseBound
|
||||
}
|
||||
|
||||
// IsConditionTrue returns if condition status is true
|
||||
func IsConditionTrue(c v1alpha1.Condition) bool {
|
||||
return c.Status == corev1.ConditionTrue
|
||||
|
|
@ -373,9 +317,6 @@ func GetExternalTags(mg Managed) map[string]string {
|
|||
ExternalResourceTagKeyKind: strings.ToLower(mg.GetObjectKind().GroupVersionKind().GroupKind().String()),
|
||||
ExternalResourceTagKeyName: mg.GetName(),
|
||||
}
|
||||
if mg.GetClassReference() != nil {
|
||||
tags[ExternalResourceTagKeyClass] = mg.GetClassReference().Name
|
||||
}
|
||||
|
||||
switch {
|
||||
case mg.GetProviderConfigReference() != nil && mg.GetProviderConfigReference().Name != "":
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ func TestGetKind(t *testing.T) {
|
|||
"TooManyKinds": {
|
||||
args: args{
|
||||
ot: MockTyper{GVKs: []schema.GroupVersionKind{
|
||||
fake.GVK(&fake.Claim{}),
|
||||
fake.GVK(&fake.Object{}),
|
||||
fake.GVK(&fake.Managed{}),
|
||||
}},
|
||||
},
|
||||
|
|
@ -248,10 +248,10 @@ func TestMustCreateObject(t *testing.T) {
|
|||
}{
|
||||
"KindRegistered": {
|
||||
args: args{
|
||||
kind: fake.GVK(&fake.Claim{}),
|
||||
oc: fake.SchemeWith(&fake.Claim{}),
|
||||
kind: fake.GVK(&fake.Managed{}),
|
||||
oc: fake.SchemeWith(&fake.Managed{}),
|
||||
},
|
||||
want: &fake.Claim{},
|
||||
want: &fake.Managed{},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -302,86 +302,6 @@ func TestIgnore(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveClassClaimValues(t *testing.T) {
|
||||
type args struct {
|
||||
classValue string
|
||||
claimValue string
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
value string
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
args
|
||||
want
|
||||
}{
|
||||
"ClassValueUnset": {
|
||||
args: args{claimValue: "cool"},
|
||||
want: want{value: "cool"},
|
||||
},
|
||||
"ClaimValueUnset": {
|
||||
args: args{classValue: "cool"},
|
||||
want: want{value: "cool"},
|
||||
},
|
||||
"IdenticalValues": {
|
||||
args: args{classValue: "cool", claimValue: "cool"},
|
||||
want: want{value: "cool"},
|
||||
},
|
||||
"ConflictingValues": {
|
||||
args: args{classValue: "lame", claimValue: "cool"},
|
||||
want: want{err: errors.New("claim value [cool] does not match class value [lame]")},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, err := ResolveClassClaimValues(tc.args.classValue, tc.args.claimValue)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("ResolveClassClaimValues(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.value, got); diff != "" {
|
||||
t.Errorf("ResolveClassClaimValues(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetBindable(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
b Bindable
|
||||
want v1alpha1.BindingPhase
|
||||
}{
|
||||
"BindableIsUnbindable": {
|
||||
b: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseUnbindable}},
|
||||
want: v1alpha1.BindingPhaseUnbound,
|
||||
},
|
||||
"BindableIsUnbound": {
|
||||
b: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseUnbound}},
|
||||
want: v1alpha1.BindingPhaseUnbound,
|
||||
},
|
||||
"BindableIsBound": {
|
||||
b: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseBound}},
|
||||
want: v1alpha1.BindingPhaseBound,
|
||||
},
|
||||
"BindableIsReleased": {
|
||||
b: &fake.Claim{BindingStatus: v1alpha1.BindingStatus{Phase: v1alpha1.BindingPhaseReleased}},
|
||||
want: v1alpha1.BindingPhaseReleased,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
SetBindable(tc.b)
|
||||
if diff := cmp.Diff(tc.want, tc.b.GetBindingPhase()); diff != "" {
|
||||
t.Errorf("SetBindable(...): -got, +want:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConditionTrue(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
c v1alpha1.Condition
|
||||
|
|
@ -653,7 +573,6 @@ func TestConnectionSecretMustBeControllableBy(t *testing.T) {
|
|||
|
||||
func TestGetExternalTags(t *testing.T) {
|
||||
provName := "prov"
|
||||
className := "classy"
|
||||
cases := map[string]struct {
|
||||
o Managed
|
||||
want map[string]string
|
||||
|
|
@ -663,13 +582,11 @@ func TestGetExternalTags(t *testing.T) {
|
|||
Name: name,
|
||||
},
|
||||
ProviderReferencer: fake.ProviderReferencer{Ref: &v1alpha1.Reference{Name: provName}},
|
||||
ClassReferencer: fake.ClassReferencer{Ref: &corev1.ObjectReference{Name: className}},
|
||||
},
|
||||
want: map[string]string{
|
||||
ExternalResourceTagKeyKind: strings.ToLower((&fake.Managed{}).GetObjectKind().GroupVersionKind().GroupKind().String()),
|
||||
ExternalResourceTagKeyName: name,
|
||||
ExternalResourceTagKeyProvider: provName,
|
||||
ExternalResourceTagKeyClass: className,
|
||||
},
|
||||
},
|
||||
"SuccessfulWithProviderConfig": {
|
||||
|
|
@ -677,13 +594,11 @@ func TestGetExternalTags(t *testing.T) {
|
|||
Name: name,
|
||||
},
|
||||
ProviderConfigReferencer: fake.ProviderConfigReferencer{Ref: &v1alpha1.Reference{Name: provName}},
|
||||
ClassReferencer: fake.ClassReferencer{Ref: &corev1.ObjectReference{Name: className}},
|
||||
},
|
||||
want: map[string]string{
|
||||
ExternalResourceTagKeyKind: strings.ToLower((&fake.Managed{}).GetObjectKind().GroupVersionKind().GroupKind().String()),
|
||||
ExternalResourceTagKeyName: name,
|
||||
ExternalResourceTagKeyProvider: provName,
|
||||
ExternalResourceTagKeyClass: className,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue