Merge pull request #48 from negz/notsoclassy
Introduce label selector based class scheduling
This commit is contained in:
commit
0f37bea549
|
|
@ -18,6 +18,17 @@ 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"
|
||||
)
|
||||
|
||||
// The annotation used to make a resource class the default.
|
||||
const (
|
||||
AnnotationDefaultClassKey = "resourceclass.crossplane.io/is-default-class"
|
||||
AnnotationDefaultClassValue = "true"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -39,6 +50,35 @@ const (
|
|||
ResourceCredentialsTokenKey = "token"
|
||||
)
|
||||
|
||||
// NOTE(negz): The below secret references differ from ObjectReference and
|
||||
// LocalObjectReference in that they include only the fields Crossplane needs to
|
||||
// reference a secret, and make those fields required. This reduces ambiguity in
|
||||
// the API for resource authors.
|
||||
|
||||
// A LocalSecretReference is a reference to a secret in the same namespace as
|
||||
// the referencer.
|
||||
type LocalSecretReference struct {
|
||||
// Name of the secret.
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// A SecretReference is a reference to a secret in an arbitrary namespace.
|
||||
type SecretReference struct {
|
||||
// Name of the secret.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Namespace of the secret.
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// A SecretKeySelector is a reference to a secret key in an arbitrary namespace.
|
||||
type SecretKeySelector struct {
|
||||
SecretReference `json:",inline"`
|
||||
|
||||
// The key to select.
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// A ResourceClaimSpec defines the desired state of a resource claim.
|
||||
type ResourceClaimSpec struct {
|
||||
// WriteConnectionSecretToReference specifies the name of a Secret, in the
|
||||
|
|
@ -47,28 +87,27 @@ type ResourceClaimSpec struct {
|
|||
// include the endpoint, username, and password required to connect to the
|
||||
// managed resource bound to this resource claim.
|
||||
// +optional
|
||||
WriteConnectionSecretToReference corev1.LocalObjectReference `json:"writeConnectionSecretToRef,omitempty"`
|
||||
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 PortableClassReference specifies the name of a portable resource class,
|
||||
// in the same namespace as this resource claim, that will be used to
|
||||
// dynamically provision a managed resource when the resource claim is
|
||||
// created. The specified class kind must be aligned with the resource
|
||||
// claim; e.g. a MySQLInstance kind resource claim always refers to a
|
||||
// MySQLInstanceClass kind portable resource class. Omit the portable class
|
||||
// reference if you wish to bind to a specific managed resource (known as
|
||||
// static binding), or to use the default portable class for the resource
|
||||
// claim's namespace (if any).
|
||||
// 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
|
||||
PortableClassReference *corev1.LocalObjectReference `json:"classRef,omitempty"`
|
||||
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 portable
|
||||
// resource class; the resource reference will be automatically populated by
|
||||
// Crossplane.
|
||||
// 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"`
|
||||
}
|
||||
|
|
@ -84,13 +123,12 @@ type ResourceClaimStatus struct {
|
|||
|
||||
// A ResourceSpec defines the desired state of a managed resource.
|
||||
type ResourceSpec struct {
|
||||
// WriteConnectionSecretToReference specifies the name of a Secret, in the
|
||||
// same namespace as this managed resource, to which any connection details
|
||||
// for this managed resource should be written. Connection details
|
||||
// frequently include the endpoint, username, and password required to
|
||||
// connect to the managed resource.
|
||||
// WriteConnectionSecretToReference specifies the namespace and name of a
|
||||
// Secret to which any connection details for this managed resource should
|
||||
// be written. Connection details frequently include the endpoint, username,
|
||||
// and password required to connect to the managed resource.
|
||||
// +optional
|
||||
WriteConnectionSecretToReference corev1.LocalObjectReference `json:"writeConnectionSecretToRef,omitempty"`
|
||||
WriteConnectionSecretToReference *SecretReference `json:"writeConnectionSecretToRef,omitempty"`
|
||||
|
||||
// ClaimReference specifies the resource claim to which this managed
|
||||
// resource will be bound. ClaimReference is set automatically during
|
||||
|
|
@ -99,12 +137,12 @@ type ResourceSpec struct {
|
|||
// +optional
|
||||
ClaimReference *corev1.ObjectReference `json:"claimRef,omitempty"`
|
||||
|
||||
// NonPortableClassReference specifies the non-portable resource class that
|
||||
// was used to dynamically provision this managed resource, if any.
|
||||
// Crossplane does not currently support setting this field manually, per
|
||||
// ClassReference specifies the resource class that was used to dynamically
|
||||
// provision this managed resource, if any. Crossplane does not currently
|
||||
// support setting this field manually, per
|
||||
// https://github.com/crossplaneio/crossplane-runtime/issues/20
|
||||
// +optional
|
||||
NonPortableClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
|
||||
ClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
|
||||
|
||||
// ProviderReference specifies the provider that will be used to create,
|
||||
// observe, update, and delete this managed resource.
|
||||
|
|
@ -126,10 +164,15 @@ type ResourceStatus struct {
|
|||
BindingStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// NonPortableClassSpecTemplate defines a template that will be used to create
|
||||
// the specifications of managed resources dynamically provisioned using a
|
||||
// resource class.
|
||||
type NonPortableClassSpecTemplate struct {
|
||||
// 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.
|
||||
|
|
@ -144,25 +187,3 @@ type NonPortableClassSpecTemplate struct {
|
|||
// +optional
|
||||
ReclaimPolicy ReclaimPolicy `json:"reclaimPolicy,omitempty"`
|
||||
}
|
||||
|
||||
// PortableClass contains standard fields that all portable classes should include. Class
|
||||
// should typically be embedded in a specific portable class.
|
||||
|
||||
// A PortableClass connects a portable resource claim to a non-portable resource
|
||||
// class used for dynamic provisioning.
|
||||
type PortableClass struct {
|
||||
|
||||
// NonPortableClassReference is a reference to a resource class kind that is
|
||||
// not portable across cloud providers, such as an RDSInstanceClass.
|
||||
NonPortableClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
|
||||
}
|
||||
|
||||
// SetNonPortableClassReference of this Class
|
||||
func (c *PortableClass) SetNonPortableClassReference(r *corev1.ObjectReference) {
|
||||
c.NonPortableClassReference = r
|
||||
}
|
||||
|
||||
// GetNonPortableClassReference of this Class
|
||||
func (c *PortableClass) GetNonPortableClassReference() *corev1.ObjectReference {
|
||||
return c.NonPortableClassReference
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
|
|
@ -39,6 +40,26 @@ func (in *BindingStatus) DeepCopy() *BindingStatus {
|
|||
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
|
||||
if in.ProviderReference != nil {
|
||||
in, out := &in.ProviderReference, &out.ProviderReference
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -78,41 +99,16 @@ func (in *ConditionedStatus) DeepCopy() *ConditionedStatus {
|
|||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NonPortableClassSpecTemplate) DeepCopyInto(out *NonPortableClassSpecTemplate) {
|
||||
func (in *LocalSecretReference) DeepCopyInto(out *LocalSecretReference) {
|
||||
*out = *in
|
||||
if in.ProviderReference != nil {
|
||||
in, out := &in.ProviderReference, &out.ProviderReference
|
||||
*out = new(v1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NonPortableClassSpecTemplate.
|
||||
func (in *NonPortableClassSpecTemplate) DeepCopy() *NonPortableClassSpecTemplate {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalSecretReference.
|
||||
func (in *LocalSecretReference) DeepCopy() *LocalSecretReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NonPortableClassSpecTemplate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PortableClass) DeepCopyInto(out *PortableClass) {
|
||||
*out = *in
|
||||
if in.NonPortableClassReference != nil {
|
||||
in, out := &in.NonPortableClassReference, &out.NonPortableClassReference
|
||||
*out = new(v1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortableClass.
|
||||
func (in *PortableClass) DeepCopy() *PortableClass {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PortableClass)
|
||||
out := new(LocalSecretReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
@ -120,15 +116,24 @@ func (in *PortableClass) DeepCopy() *PortableClass {
|
|||
// 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
|
||||
out.WriteConnectionSecretToReference = in.WriteConnectionSecretToReference
|
||||
if in.PortableClassReference != nil {
|
||||
in, out := &in.PortableClassReference, &out.PortableClassReference
|
||||
*out = new(v1.LocalObjectReference)
|
||||
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(v1.ObjectReference)
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
|
@ -163,20 +168,24 @@ func (in *ResourceClaimStatus) DeepCopy() *ResourceClaimStatus {
|
|||
// 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
|
||||
out.WriteConnectionSecretToReference = in.WriteConnectionSecretToReference
|
||||
if in.ClaimReference != nil {
|
||||
in, out := &in.ClaimReference, &out.ClaimReference
|
||||
*out = new(v1.ObjectReference)
|
||||
if in.WriteConnectionSecretToReference != nil {
|
||||
in, out := &in.WriteConnectionSecretToReference, &out.WriteConnectionSecretToReference
|
||||
*out = new(SecretReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.NonPortableClassReference != nil {
|
||||
in, out := &in.NonPortableClassReference, &out.NonPortableClassReference
|
||||
*out = new(v1.ObjectReference)
|
||||
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.ProviderReference != nil {
|
||||
in, out := &in.ProviderReference, &out.ProviderReference
|
||||
*out = new(v1.ObjectReference)
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
|
@ -207,3 +216,34 @@ func (in *ResourceStatus) DeepCopy() *ResourceStatus {
|
|||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) {
|
||||
*out = *in
|
||||
out.SecretReference = in.SecretReference
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector.
|
||||
func (in *SecretKeySelector) DeepCopy() *SecretKeySelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecretKeySelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretReference) DeepCopyInto(out *SecretReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference.
|
||||
func (in *SecretReference) DeepCopy() *SecretReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecretReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,12 +58,12 @@ func NewAPIManagedCreator(c client.Client, t runtime.ObjectTyper) *APIManagedCre
|
|||
}
|
||||
|
||||
// Create the supplied resource using the supplied class and claim.
|
||||
func (a *APIManagedCreator) Create(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error {
|
||||
func (a *APIManagedCreator) Create(ctx context.Context, cm Claim, cs Class, mg Managed) error {
|
||||
cmr := meta.ReferenceTo(cm, MustGetKind(cm, a.typer))
|
||||
csr := meta.ReferenceTo(cs, MustGetKind(cs, a.typer))
|
||||
|
||||
mg.SetClaimReference(cmr)
|
||||
mg.SetNonPortableClassReference(csr)
|
||||
mg.SetClassReference(csr)
|
||||
if err := a.client.Create(ctx, mg); err != nil {
|
||||
return errors.Wrap(err, errCreateManaged)
|
||||
}
|
||||
|
|
@ -93,11 +93,14 @@ func NewAPIManagedConnectionPropagator(c client.Client, t runtime.ObjectTyper) *
|
|||
func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context, cm Claim, mg Managed) error {
|
||||
// Either this resource does not expose a connection secret, or this claim
|
||||
// does not want one.
|
||||
if mg.GetWriteConnectionSecretToReference().Name == "" || cm.GetWriteConnectionSecretToReference().Name == "" {
|
||||
if mg.GetWriteConnectionSecretToReference() == nil || cm.GetWriteConnectionSecretToReference() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := types.NamespacedName{Namespace: mg.GetNamespace(), Name: mg.GetWriteConnectionSecretToReference().Name}
|
||||
n := types.NamespacedName{
|
||||
Namespace: mg.GetWriteConnectionSecretToReference().Namespace,
|
||||
Name: mg.GetWriteConnectionSecretToReference().Name,
|
||||
}
|
||||
mgcs := &corev1.Secret{}
|
||||
if err := a.client.Get(ctx, n, mgcs); err != nil {
|
||||
return errors.Wrap(err, errGetSecret)
|
||||
|
|
@ -111,7 +114,7 @@ func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context
|
|||
return errors.New(errSecretConflict)
|
||||
}
|
||||
|
||||
cmcs := ConnectionSecretFor(cm, MustGetKind(cm, a.typer))
|
||||
cmcs := LocalConnectionSecretFor(cm, MustGetKind(cm, a.typer))
|
||||
if _, err := util.CreateOrUpdate(ctx, a.client, cmcs, func() error {
|
||||
// Inside this anonymous function cmcs could either be unchanged (if
|
||||
// it does not exist in the API server) or updated to reflect its
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ func TestCreate(t *testing.T) {
|
|||
type args struct {
|
||||
ctx context.Context
|
||||
cm Claim
|
||||
cs NonPortableClass
|
||||
cs Class
|
||||
mg Managed
|
||||
}
|
||||
|
||||
|
|
@ -72,12 +72,12 @@ func TestCreate(t *testing.T) {
|
|||
client: &test.MockClient{
|
||||
MockCreate: test.NewMockCreateFn(errBoom),
|
||||
},
|
||||
typer: MockSchemeWith(&MockClaim{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
typer: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{},
|
||||
cs: &MockNonPortableClass{},
|
||||
cs: &MockClass{},
|
||||
mg: &MockManaged{},
|
||||
},
|
||||
want: errors.Wrap(errBoom, errCreateManaged),
|
||||
|
|
@ -88,12 +88,12 @@ func TestCreate(t *testing.T) {
|
|||
MockCreate: test.NewMockCreateFn(nil),
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
},
|
||||
typer: MockSchemeWith(&MockClaim{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
typer: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{},
|
||||
cs: &MockNonPortableClass{},
|
||||
cs: &MockClass{},
|
||||
mg: &MockManaged{},
|
||||
},
|
||||
want: errors.Wrap(errBoom, errUpdateClaim),
|
||||
|
|
@ -109,10 +109,10 @@ func TestCreate(t *testing.T) {
|
|||
APIVersion: MockGVK(&MockClaim{}).GroupVersion().String(),
|
||||
Kind: MockGVK(&MockClaim{}).Kind,
|
||||
})
|
||||
want.SetNonPortableClassReference(&corev1.ObjectReference{
|
||||
want.SetClassReference(&corev1.ObjectReference{
|
||||
Name: csname,
|
||||
APIVersion: MockGVK(&MockNonPortableClass{}).GroupVersion().String(),
|
||||
Kind: MockGVK(&MockNonPortableClass{}).Kind,
|
||||
APIVersion: MockGVK(&MockClass{}).GroupVersion().String(),
|
||||
Kind: MockGVK(&MockClass{}).Kind,
|
||||
})
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
|
|
@ -134,12 +134,12 @@ func TestCreate(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
typer: MockSchemeWith(&MockClaim{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
typer: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Name: cmname}},
|
||||
cs: &MockNonPortableClass{ObjectMeta: metav1.ObjectMeta{Name: csname}},
|
||||
cs: &MockClass{ObjectMeta: metav1.ObjectMeta{Name: csname}},
|
||||
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Name: mgname}},
|
||||
},
|
||||
want: nil,
|
||||
|
|
@ -173,6 +173,7 @@ func TestPropagateConnection(t *testing.T) {
|
|||
uid := types.UID("definitely-a-uuid")
|
||||
cmcsname := "coolclaimsecret"
|
||||
mgcsname := "coolmanagedsecret"
|
||||
mgcsnamespace := "coolns"
|
||||
mgcsdata := map[string][]byte{"cool": []byte("data")}
|
||||
controller := true
|
||||
errBoom := errors.New("boom")
|
||||
|
|
@ -187,7 +188,9 @@ func TestPropagateConnection(t *testing.T) {
|
|||
ctx: context.Background(),
|
||||
cm: &MockClaim{},
|
||||
mg: &MockManaged{
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.SecretReference{Namespace: mgcsnamespace, Name: mgcsname},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
|
|
@ -196,7 +199,9 @@ func TestPropagateConnection(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
MockLocalConnectionSecretWriterTo: MockLocalConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: mgcsname},
|
||||
},
|
||||
},
|
||||
mg: &MockManaged{},
|
||||
},
|
||||
|
|
@ -209,10 +214,14 @@ func TestPropagateConnection(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: cmcsname}},
|
||||
MockLocalConnectionSecretWriterTo: MockLocalConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: cmcsname},
|
||||
},
|
||||
},
|
||||
mg: &MockManaged{
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.SecretReference{Namespace: mgcsnamespace, Name: mgcsname},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: errors.Wrap(errBoom, errGetSecret),
|
||||
|
|
@ -248,12 +257,16 @@ func TestPropagateConnection(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: cmname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: cmcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: cmname},
|
||||
MockLocalConnectionSecretWriterTo: MockLocalConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: cmcsname},
|
||||
},
|
||||
},
|
||||
mg: &MockManaged{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.SecretReference{Namespace: mgcsnamespace, Name: mgcsname},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: errors.Wrap(errors.New(errSecretConflict), errCreateOrUpdateSecret),
|
||||
|
|
@ -285,12 +298,16 @@ func TestPropagateConnection(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: cmname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: cmcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: cmname},
|
||||
MockLocalConnectionSecretWriterTo: MockLocalConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: cmcsname},
|
||||
},
|
||||
},
|
||||
mg: &MockManaged{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.SecretReference{Namespace: mgcsnamespace, Name: mgcsname},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: errors.Wrap(errors.New(errSecretConflict), errCreateOrUpdateSecret),
|
||||
|
|
@ -331,12 +348,16 @@ func TestPropagateConnection(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: cmname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: cmcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: cmname},
|
||||
MockLocalConnectionSecretWriterTo: MockLocalConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: cmcsname},
|
||||
},
|
||||
},
|
||||
mg: &MockManaged{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.SecretReference{Namespace: mgcsnamespace, Name: mgcsname},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: errors.Wrap(errBoom, errUpdateSecret),
|
||||
|
|
@ -402,12 +423,16 @@ func TestPropagateConnection(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: cmname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: cmcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: cmname, UID: uid},
|
||||
MockLocalConnectionSecretWriterTo: MockLocalConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: cmcsname},
|
||||
},
|
||||
},
|
||||
mg: &MockManaged{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname, UID: uid},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{
|
||||
Ref: &v1alpha1.SecretReference{Namespace: mgcsnamespace, Name: mgcsname},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
|
@ -57,14 +56,16 @@ var log = logging.Logger.WithName("controller")
|
|||
// A ClaimKind contains the type metadata for a kind of resource claim.
|
||||
type ClaimKind schema.GroupVersionKind
|
||||
|
||||
// A NonPortableClassKind contains the type metadata for a kind of
|
||||
// non-portable resource class.
|
||||
type NonPortableClassKind schema.GroupVersionKind
|
||||
// A ClassKind contains the type metadata for a kind of resource class.
|
||||
type ClassKind schema.GroupVersionKind
|
||||
|
||||
// A ClassKinds contains the type metadata for a kind of resource class.
|
||||
type ClassKinds struct {
|
||||
Portable schema.GroupVersionKind
|
||||
NonPortable 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.
|
||||
|
|
@ -73,15 +74,15 @@ type ManagedKind schema.GroupVersionKind
|
|||
// 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 Claim, cs NonPortableClass, mg Managed) error
|
||||
Configure(ctx context.Context, cm Claim, cs Class, mg Managed) error
|
||||
}
|
||||
|
||||
// A ManagedConfiguratorFn is a function that satisfies the
|
||||
// ManagedConfigurator interface.
|
||||
type ManagedConfiguratorFn func(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error
|
||||
type ManagedConfiguratorFn func(ctx context.Context, cm Claim, cs Class, mg Managed) error
|
||||
|
||||
// Configure the supplied resource using the supplied claim and class.
|
||||
func (fn ManagedConfiguratorFn) Configure(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error {
|
||||
func (fn ManagedConfiguratorFn) Configure(ctx context.Context, cm Claim, cs Class, mg Managed) error {
|
||||
return fn(ctx, cm, cs, mg)
|
||||
}
|
||||
|
||||
|
|
@ -90,14 +91,14 @@ func (fn ManagedConfiguratorFn) Configure(ctx context.Context, cm Claim, cs NonP
|
|||
// 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 Claim, cs NonPortableClass, mg Managed) error
|
||||
Create(ctx context.Context, cm Claim, cs Class, mg Managed) error
|
||||
}
|
||||
|
||||
// A ManagedCreatorFn is a function that satisfies the ManagedCreator interface.
|
||||
type ManagedCreatorFn func(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error
|
||||
type ManagedCreatorFn func(ctx context.Context, cm Claim, cs Class, mg Managed) error
|
||||
|
||||
// Create the supplied resource.
|
||||
func (fn ManagedCreatorFn) Create(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error {
|
||||
func (fn ManagedCreatorFn) Create(ctx context.Context, cm Claim, cs Class, mg Managed) error {
|
||||
return fn(ctx, cm, cs, mg)
|
||||
}
|
||||
|
||||
|
|
@ -164,11 +165,10 @@ func (fn ClaimFinalizerFn) Finalize(ctx context.Context, cm Claim) error {
|
|||
// type of resource class provisioner. Each controller must watch its subset of
|
||||
// resource claims and any managed resources they control.
|
||||
type ClaimReconciler struct {
|
||||
client client.Client
|
||||
newClaim func() Claim
|
||||
newNonPortableClass func() NonPortableClass
|
||||
newPortableClass func() PortableClass
|
||||
newManaged func() Managed
|
||||
client client.Client
|
||||
newClaim func() Claim
|
||||
newClass func() Class
|
||||
newManaged func() Managed
|
||||
|
||||
// The below structs embed the set of interfaces used to implement the
|
||||
// resource claim reconciler. We do this primarily for readability, so that
|
||||
|
|
@ -203,7 +203,7 @@ func defaultCRClaim(m manager.Manager) crClaim {
|
|||
return crClaim{ClaimFinalizer: NewAPIClaimFinalizerRemover(m.GetClient())}
|
||||
}
|
||||
|
||||
// A ClaimReconcilerOption configures a Reconciler.
|
||||
// A ClaimReconcilerOption configures a ClaimReconciler.
|
||||
type ClaimReconcilerOption func(*ClaimReconciler)
|
||||
|
||||
// WithManagedConfigurators specifies which configurators should be used to
|
||||
|
|
@ -261,24 +261,22 @@ func WithClaimFinalizer(f ClaimFinalizer) ClaimReconcilerOption {
|
|||
// with the supplied manager's runtime.Scheme. The returned ClaimReconciler will
|
||||
// apply only the ObjectMetaConfigurator by default; most callers should supply
|
||||
// one or more ManagedConfigurators to configure their managed resources.
|
||||
func NewClaimReconciler(m manager.Manager, of ClaimKind, using ClassKinds, with ManagedKind, o ...ClaimReconcilerOption) *ClaimReconciler {
|
||||
func NewClaimReconciler(m manager.Manager, of ClaimKind, using ClassKind, with ManagedKind, o ...ClaimReconcilerOption) *ClaimReconciler {
|
||||
nc := func() Claim { return MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(Claim) }
|
||||
ns := func() NonPortableClass { return MustCreateObject(using.NonPortable, m.GetScheme()).(NonPortableClass) }
|
||||
np := func() PortableClass { return MustCreateObject(using.Portable, m.GetScheme()).(PortableClass) }
|
||||
ns := func() Class { return MustCreateObject(schema.GroupVersionKind(using), m.GetScheme()).(Class) }
|
||||
nr := func() Managed { return MustCreateObject(schema.GroupVersionKind(with), m.GetScheme()).(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(), np(), nr()
|
||||
_, _, _ = nc(), ns(), nr()
|
||||
|
||||
r := &ClaimReconciler{
|
||||
client: m.GetClient(),
|
||||
newClaim: nc,
|
||||
newNonPortableClass: ns,
|
||||
newPortableClass: np,
|
||||
newManaged: nr,
|
||||
managed: defaultCRManaged(m),
|
||||
claim: defaultCRClaim(m),
|
||||
client: m.GetClient(),
|
||||
newClaim: nc,
|
||||
newClass: ns,
|
||||
newManaged: nr,
|
||||
managed: defaultCRManaged(m),
|
||||
claim: defaultCRClaim(m),
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
|
|
@ -342,26 +340,12 @@ func (r *ClaimReconciler) Reconcile(req reconcile.Request) (reconcile.Result, er
|
|||
}
|
||||
|
||||
if !meta.WasCreated(managed) {
|
||||
portable := r.newPortableClass()
|
||||
// Portable class reference should always be set by the time we get this far;
|
||||
// Class reference should always be set by the time we get this far;
|
||||
// Our watch predicates require it.
|
||||
p := types.NamespacedName{
|
||||
Namespace: claim.GetNamespace(),
|
||||
Name: claim.GetPortableClassReference().Name,
|
||||
}
|
||||
if err := r.client.Get(ctx, p, portable); 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
|
||||
// portable class is (re)created.
|
||||
claim.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
class := r.newNonPortableClass()
|
||||
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(portable.GetNonPortableClassReference()), class); err != nil {
|
||||
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
|
||||
|
|
@ -63,7 +63,7 @@ func TestClaimReconciler(t *testing.T) {
|
|||
type args struct {
|
||||
m manager.Manager
|
||||
of ClaimKind
|
||||
use ClassKinds
|
||||
use ClassKind
|
||||
with ManagedKind
|
||||
o []ClaimReconcilerOption
|
||||
}
|
||||
|
|
@ -85,10 +85,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errGetClaim)},
|
||||
|
|
@ -120,10 +120,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
|
|
@ -153,10 +153,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return errBoom })),
|
||||
|
|
@ -189,10 +189,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return nil })),
|
||||
|
|
@ -226,10 +226,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return nil })),
|
||||
|
|
@ -263,10 +263,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return nil })),
|
||||
|
|
@ -275,41 +275,6 @@ func TestClaimReconciler(t *testing.T) {
|
|||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"GetPortableClassError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
cm := &MockClaim{}
|
||||
cm.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *MockPortableClass:
|
||||
return errBoom
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
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
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
},
|
||||
"GetResourceClassError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
|
|
@ -318,15 +283,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
cm := &MockClaim{}
|
||||
cm.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
cm.SetClassReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *MockPortableClass:
|
||||
pc := &MockPortableClass{}
|
||||
pc.SetNonPortableClassReference(&corev1.ObjectReference{})
|
||||
*o = *pc
|
||||
return nil
|
||||
case *MockNonPortableClass:
|
||||
case *MockClass:
|
||||
return errBoom
|
||||
default:
|
||||
return errUnexpected
|
||||
|
|
@ -334,7 +294,7 @@ func TestClaimReconciler(t *testing.T) {
|
|||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
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)
|
||||
|
|
@ -342,10 +302,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
|
|
@ -358,15 +318,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
cm := &MockClaim{}
|
||||
cm.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
cm.SetClassReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *MockPortableClass:
|
||||
pc := &MockPortableClass{}
|
||||
pc.SetNonPortableClassReference(&corev1.ObjectReference{})
|
||||
*o = *pc
|
||||
return nil
|
||||
case *MockNonPortableClass:
|
||||
case *MockClass:
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
|
|
@ -374,7 +329,7 @@ func TestClaimReconciler(t *testing.T) {
|
|||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
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)
|
||||
|
|
@ -382,13 +337,13 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{WithManagedConfigurators(ManagedConfiguratorFn(
|
||||
func(_ context.Context, _ Claim, _ NonPortableClass, _ Managed) error { return errBoom },
|
||||
func(_ context.Context, _ Claim, _ Class, _ Managed) error { return errBoom },
|
||||
))},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
|
||||
|
|
@ -401,15 +356,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
cm := &MockClaim{}
|
||||
cm.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
cm.SetClassReference(&corev1.ObjectReference{})
|
||||
*o = *cm
|
||||
return nil
|
||||
case *MockPortableClass:
|
||||
pc := &MockPortableClass{}
|
||||
pc.SetNonPortableClassReference(&corev1.ObjectReference{})
|
||||
*o = *pc
|
||||
return nil
|
||||
case *MockNonPortableClass:
|
||||
case *MockClass:
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
|
|
@ -417,7 +367,7 @@ func TestClaimReconciler(t *testing.T) {
|
|||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetPortableClassReference(&corev1.LocalObjectReference{})
|
||||
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)
|
||||
|
|
@ -425,17 +375,17 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedConfigurators(ManagedConfiguratorFn(
|
||||
func(_ context.Context, _ Claim, _ NonPortableClass, _ Managed) error { return nil },
|
||||
func(_ context.Context, _ Claim, _ Class, _ Managed) error { return nil },
|
||||
)),
|
||||
WithManagedCreator(ManagedCreatorFn(
|
||||
func(_ context.Context, _ Claim, _ NonPortableClass, _ Managed) error { return errBoom },
|
||||
func(_ context.Context, _ Claim, _ Class, _ Managed) error { return errBoom },
|
||||
)),
|
||||
},
|
||||
},
|
||||
|
|
@ -474,10 +424,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
|
|
@ -513,10 +463,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
|
|
@ -552,10 +502,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
||||
|
|
@ -596,10 +546,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
||||
|
|
@ -643,10 +593,10 @@ func TestClaimReconciler(t *testing.T) {
|
|||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}, &MockManaged{}),
|
||||
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
use: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
use: ClassKind(MockGVK(&MockClass{})),
|
||||
with: ManagedKind(MockGVK(&MockManaged{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"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/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/meta"
|
||||
)
|
||||
|
||||
const (
|
||||
claimDefaultingControllerName = "resourceclaimdefaulter.crossplane.io"
|
||||
claimDefaultingReconcileTimeout = 1 * time.Minute
|
||||
claimDefaultingReconcileMaxJitterMs = 1500
|
||||
)
|
||||
|
||||
// A ClaimDefaultingReconciler 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 ClaimDefaultingReconciler struct {
|
||||
client client.Client
|
||||
newClaim func() Claim
|
||||
classKind ClassKind
|
||||
jitter Jitterer
|
||||
}
|
||||
|
||||
// A ClaimDefaultingReconcilerOption configures a ClaimDefaultingReconciler.
|
||||
type ClaimDefaultingReconcilerOption func(*ClaimDefaultingReconciler)
|
||||
|
||||
// WithDefaultingJitterer specifies the Jitterer a ClaimDefaultingReconciler should use.
|
||||
func WithDefaultingJitterer(j Jitterer) ClaimDefaultingReconcilerOption {
|
||||
return func(r *ClaimDefaultingReconciler) {
|
||||
r.jitter = j
|
||||
}
|
||||
}
|
||||
|
||||
// NewClaimDefaultingReconciler returns a ClaimDefaultingReconciler that sets
|
||||
// the class reference of a resource claim to the resource class annotated as
|
||||
// the default.
|
||||
func NewClaimDefaultingReconciler(m manager.Manager, of ClaimKind, to ClassKind, o ...ClaimDefaultingReconcilerOption) *ClaimDefaultingReconciler {
|
||||
nc := func() Claim { return MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(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 := &ClaimDefaultingReconciler{
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
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 *ClaimDefaultingReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
|
||||
log.V(logging.Debug).Info("Reconciling", "controller", claimDefaultingControllerName, "request", req)
|
||||
|
||||
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.
|
||||
return reconcile.Result{}, errors.Wrap(IgnoreNotFound(err), errGetClaim)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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.
|
||||
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.
|
||||
// There's nothing for us to do.
|
||||
return reconcile.Result{Requeue: false}, 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.
|
||||
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Update(ctx, claim), errUpdateClaim)
|
||||
}
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
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 resource
|
||||
|
||||
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/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var _ reconcile.Reconciler = &ClaimDefaultingReconciler{}
|
||||
|
||||
func TestClaimDefaultingReconciler(t *testing.T) {
|
||||
name := "coolName"
|
||||
uid := types.UID("definitely-a-uuid")
|
||||
|
||||
type args struct {
|
||||
m manager.Manager
|
||||
of ClaimKind
|
||||
to 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: &MockManager{
|
||||
c: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errGetClaim)},
|
||||
},
|
||||
"ClaimNotFound": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ClaimHasClassRef": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassReferencer: MockClassReferencer{Ref: &corev1.ObjectReference{}}}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"ListClassesError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(errBoom),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errListClasses)},
|
||||
},
|
||||
"NoClassesAnnotatedDefault": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(MockGVK(&MockClass{}))
|
||||
u.SetName(name)
|
||||
u.SetUID(uid)
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
l.Items = []unstructured.Unstructured{*u}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"UpdateClaimError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(MockGVK(&MockClass{}))
|
||||
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),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errUpdateClaim)},
|
||||
},
|
||||
"Successful": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(MockGVK(&MockClass{}))
|
||||
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 := &MockClaim{}
|
||||
want.SetClassReference(&corev1.ObjectReference{
|
||||
APIVersion: MockGVK(&MockClass{}).GroupVersion().String(),
|
||||
Kind: MockGVK(&MockClass{}).Kind,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
})
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewClaimDefaultingReconciler(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 TestClaimDefaultingReconcilerRandomness(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 := &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassSelector: MockClassSelector{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.(ClassReferencer)
|
||||
refs = append(refs, ls.GetClassReference())
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
}
|
||||
|
||||
r := NewClaimDefaultingReconciler(m,
|
||||
ClaimKind(MockGVK(&MockClaim{})),
|
||||
ClassKind(MockGVK(&MockClass{})),
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"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/crossplaneio/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/meta"
|
||||
)
|
||||
|
||||
const (
|
||||
claimSchedulingControllerName = "resourceclaimscheduler.crossplane.io"
|
||||
claimSchedulingReconcileTimeout = 1 * time.Minute
|
||||
claimSchedulingReconcileMaxJitterMs = 1500
|
||||
)
|
||||
|
||||
const (
|
||||
errListClasses = "cannot list resource classes"
|
||||
)
|
||||
|
||||
// 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 ClaimSchedulingReconciler 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 ClaimSchedulingReconciler is designed for use in
|
||||
// claim scheduling controllers that race several others to schedule a claim.
|
||||
type ClaimSchedulingReconciler struct {
|
||||
client client.Client
|
||||
newClaim func() Claim
|
||||
classKind ClassKind
|
||||
jitter Jitterer
|
||||
}
|
||||
|
||||
// A ClaimSchedulingReconcilerOption configures a ClaimSchedulingReconciler.
|
||||
type ClaimSchedulingReconcilerOption func(*ClaimSchedulingReconciler)
|
||||
|
||||
// WithSchedulingJitterer specifies the Jitterer a ClaimSchedulingReconciler should use.
|
||||
func WithSchedulingJitterer(j Jitterer) ClaimSchedulingReconcilerOption {
|
||||
return func(r *ClaimSchedulingReconciler) {
|
||||
r.jitter = j
|
||||
}
|
||||
}
|
||||
|
||||
// NewClaimSchedulingReconciler returns a ClaimSchedulingReconciler that
|
||||
// schedules resource claims to a resource class that matches their class
|
||||
// selector.
|
||||
func NewClaimSchedulingReconciler(m manager.Manager, of ClaimKind, to ClassKind, o ...ClaimSchedulingReconcilerOption) *ClaimSchedulingReconciler {
|
||||
nc := func() Claim { return MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(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 := &ClaimSchedulingReconciler{
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
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 *ClaimSchedulingReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
|
||||
log.V(logging.Debug).Info("Reconciling", "controller", claimSchedulingControllerName, "request", req)
|
||||
|
||||
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.
|
||||
return reconcile.Result{}, errors.Wrap(IgnoreNotFound(err), errGetClaim)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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.
|
||||
return reconcile.Result{}, errors.Wrap(err, errListClasses)
|
||||
}
|
||||
|
||||
if len(classes.Items) == 0 {
|
||||
// None of our classes matched the class selector.
|
||||
// There's nothing for us to do.
|
||||
return reconcile.Result{Requeue: false}, 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.
|
||||
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Update(ctx, claim), errUpdateClaim)
|
||||
}
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
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 resource
|
||||
|
||||
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/crossplaneio/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var _ reconcile.Reconciler = &ClaimDefaultingReconciler{}
|
||||
|
||||
func TestClaimSchedulingReconciler(t *testing.T) {
|
||||
name := "coolName"
|
||||
uid := types.UID("definitely-a-uuid")
|
||||
|
||||
type args struct {
|
||||
m manager.Manager
|
||||
of ClaimKind
|
||||
to 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: &MockManager{
|
||||
c: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errGetClaim)},
|
||||
},
|
||||
"ClaimNotFound": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ClaimHasClassRef": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassReferencer: MockClassReferencer{Ref: &corev1.ObjectReference{}}}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"ListClassesError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassSelector: MockClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(errBoom),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errListClasses)},
|
||||
},
|
||||
"NoClassesMatchLabels": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassSelector: MockClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
"UpdateClaimError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassSelector: MockClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(MockGVK(&MockClass{}))
|
||||
u.SetName(name)
|
||||
u.SetUID(uid)
|
||||
l := o.(*unstructured.UnstructuredList)
|
||||
l.Items = []unstructured.Unstructured{*u}
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errUpdateClaim)},
|
||||
},
|
||||
"Successful": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassSelector: MockClassSelector{Sel: &metav1.LabelSelector{}}}
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(MockGVK(&MockClass{}))
|
||||
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 := &MockClaim{}
|
||||
want.SetClassSelector(&metav1.LabelSelector{})
|
||||
want.SetClassReference(&corev1.ObjectReference{
|
||||
APIVersion: MockGVK(&MockClass{}).GroupVersion().String(),
|
||||
Kind: MockGVK(&MockClass{}).Kind,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
})
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
to: ClassKind(MockGVK(&MockClass{})),
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewClaimSchedulingReconciler(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 TestClaimSchedulingReconcilerRandomness(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 := &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
c := o.(*MockClaim)
|
||||
*c = MockClaim{MockClassSelector: MockClassSelector{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.(ClassReferencer)
|
||||
refs = append(refs, ls.GetClassReference())
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}),
|
||||
}
|
||||
|
||||
r := NewClaimSchedulingReconciler(m,
|
||||
ClaimKind(MockGVK(&MockClaim{})),
|
||||
ClassKind(MockGVK(&MockClass{})),
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ 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 Claim, cs NonPortableClass, mg Managed) error {
|
||||
func (cc ConfiguratorChain) Configure(ctx context.Context, cm Claim, cs Class, mg Managed) error {
|
||||
for _, c := range cc {
|
||||
if err := c.Configure(ctx, cm, cs, mg); err != nil {
|
||||
return err
|
||||
|
|
@ -52,16 +52,21 @@ func NewObjectMetaConfigurator(t runtime.ObjectTyper) *ObjectMetaConfigurator {
|
|||
}
|
||||
|
||||
// Configure the supplied Managed resource's object metadata.
|
||||
func (c *ObjectMetaConfigurator) Configure(_ context.Context, cm Claim, cs NonPortableClass, mg Managed) error {
|
||||
mg.SetNamespace(cs.GetNamespace())
|
||||
func (c *ObjectMetaConfigurator) Configure(_ context.Context, cm Claim, _ Class, mg Managed) error {
|
||||
mg.SetGenerateName(fmt.Sprintf("%s-%s-", cm.GetNamespace(), cm.GetName()))
|
||||
if meta.GetExternalName(cm) != "" {
|
||||
meta.SetExternalName(mg, meta.GetExternalName(cm))
|
||||
}
|
||||
// TODO(negz): Don't set this potentially cross-namespace owner reference.
|
||||
// We probably want to use the resource's reclaim policy, not Kubernetes
|
||||
// garbage collection, to determine whether to delete a managed resource
|
||||
// when its claim is deleted per https://github.com/crossplaneio/crossplane/issues/550
|
||||
|
||||
// TODO(negz): Avoid setting this owner reference? Kubernetes specifies that
|
||||
// cluster scoped resources cannot have namespaced owners, by design, but
|
||||
// the owner reference appears to work for cascading deletes.
|
||||
// https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents
|
||||
|
||||
// TODO(negz): We probably want to use the resource's reclaim policy, not
|
||||
// Kubernetes garbage collection, to determine whether to delete a managed
|
||||
// resource when its claim is deleted per
|
||||
// https://github.com/crossplaneio/crossplane/issues/550
|
||||
mg.SetOwnerReferences([]v1.OwnerReference{meta.AsOwner(meta.ReferenceTo(cm, MustGetKind(cm, c.typer)))})
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func TestConfiguratorChain(t *testing.T) {
|
|||
type args struct {
|
||||
ctx context.Context
|
||||
cm Claim
|
||||
cs NonPortableClass
|
||||
cs Class
|
||||
mg Managed
|
||||
}
|
||||
|
||||
|
|
@ -54,35 +54,35 @@ func TestConfiguratorChain(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{},
|
||||
cs: &MockNonPortableClass{},
|
||||
cs: &MockClass{},
|
||||
mg: &MockManaged{},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"SuccessulConfigurator": {
|
||||
cc: ConfiguratorChain{
|
||||
ManagedConfiguratorFn(func(_ context.Context, _ Claim, _ NonPortableClass, _ Managed) error {
|
||||
ManagedConfiguratorFn(func(_ context.Context, _ Claim, _ Class, _ Managed) error {
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{},
|
||||
cs: &MockNonPortableClass{},
|
||||
cs: &MockClass{},
|
||||
mg: &MockManaged{},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"ConfiguratorReturnsError": {
|
||||
cc: ConfiguratorChain{
|
||||
ManagedConfiguratorFn(func(_ context.Context, _ Claim, _ NonPortableClass, _ Managed) error {
|
||||
ManagedConfiguratorFn(func(_ context.Context, _ Claim, _ Class, _ Managed) error {
|
||||
return errBoom
|
||||
}),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{},
|
||||
cs: &MockNonPortableClass{},
|
||||
cs: &MockClass{},
|
||||
mg: &MockManaged{},
|
||||
},
|
||||
want: errBoom,
|
||||
|
|
@ -100,7 +100,6 @@ func TestConfiguratorChain(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConfigureObjectMeta(t *testing.T) {
|
||||
ns := "namespace"
|
||||
claimName := "myclaim"
|
||||
claimNS := "myclaimns"
|
||||
uid := types.UID("definitely-a-uuid")
|
||||
|
|
@ -108,7 +107,7 @@ func TestConfigureObjectMeta(t *testing.T) {
|
|||
type args struct {
|
||||
ctx context.Context
|
||||
cm Claim
|
||||
cs NonPortableClass
|
||||
cs Class
|
||||
mg Managed
|
||||
}
|
||||
|
||||
|
|
@ -127,12 +126,10 @@ func TestConfigureObjectMeta(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Name: claimName, Namespace: claimNS, UID: uid}},
|
||||
cs: &MockNonPortableClass{ObjectMeta: metav1.ObjectMeta{Namespace: ns}},
|
||||
mg: &MockManaged{},
|
||||
},
|
||||
want: want{
|
||||
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
GenerateName: claimNS + "-" + claimName + "-",
|
||||
OwnerReferences: []metav1.OwnerReference{{
|
||||
APIVersion: MockGVK(&MockClaim{}).GroupVersion().String(),
|
||||
|
|
|
|||
|
|
@ -1,170 +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 resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"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"
|
||||
|
||||
corev1alpha1 "github.com/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
controllerNameDefaultClass = "defaultclass.crossplane.io"
|
||||
defaultClassWait = 1 * time.Minute
|
||||
defaultClassReconcileTimeout = 1 * time.Minute
|
||||
)
|
||||
|
||||
// Label values for listing default portable classes
|
||||
const (
|
||||
LabelKeyDefaultClass = "default"
|
||||
LabelValueTrue = "true"
|
||||
)
|
||||
|
||||
// Error strings
|
||||
const (
|
||||
errFailedList = "unable to list portable classes for claim kind"
|
||||
errNoPortableClass = "unable to locate a portable class that specifies a default class for claim kind"
|
||||
errMultiplePortableClasses = "multiple portable classes that specify a default class defined for claim kind"
|
||||
)
|
||||
|
||||
// A PortableClassKind contains the type metadata for a kind of portable class.
|
||||
type PortableClassKind struct {
|
||||
Singular schema.GroupVersionKind
|
||||
Plural schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// DefaultClassReconciler reconciles resource claims to the
|
||||
// default resource class for their given kind according to existing
|
||||
// portable classes. Predicates ensure that only claims with no resource class
|
||||
// reference are reconciled.
|
||||
type DefaultClassReconciler struct {
|
||||
client client.Client
|
||||
converter runtime.ObjectConvertor
|
||||
labels map[string]string
|
||||
newClaim func() Claim
|
||||
newPortableClass func() PortableClass
|
||||
newPortableClassList func() PortableClassList
|
||||
}
|
||||
|
||||
// A DefaultClassReconcilerOption configures a DefaultClassReconciler.
|
||||
type DefaultClassReconcilerOption func(*DefaultClassReconciler)
|
||||
|
||||
// WithObjectConverter specifies how the DefaultClassReconciler should convert
|
||||
// an *UnstructuredList into a concrete list type.
|
||||
func WithObjectConverter(oc runtime.ObjectConvertor) DefaultClassReconcilerOption {
|
||||
return func(r *DefaultClassReconciler) {
|
||||
r.converter = oc
|
||||
}
|
||||
}
|
||||
|
||||
// WithLabels specifies how the DefaultClassReconciler should search
|
||||
// for a default class
|
||||
func WithLabels(labels map[string]string) DefaultClassReconcilerOption {
|
||||
return func(r *DefaultClassReconciler) {
|
||||
r.labels = labels
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultClassReconciler creates a new DefaultReconciler for the claim kind.
|
||||
func NewDefaultClassReconciler(m manager.Manager, of ClaimKind, by PortableClassKind, o ...DefaultClassReconcilerOption) *DefaultClassReconciler {
|
||||
nc := func() Claim { return MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(Claim) }
|
||||
np := func() PortableClass { return MustCreateObject(by.Singular, m.GetScheme()).(PortableClass) }
|
||||
npl := func() PortableClassList { return MustCreateObject(by.Plural, m.GetScheme()).(PortableClassList) }
|
||||
|
||||
// Panic early if we've been asked to reconcile a claim, portable class, or portable class list that has
|
||||
// not been registered with our controller manager's scheme.
|
||||
_, _, _ = nc(), np(), npl()
|
||||
|
||||
labels := map[string]string{LabelKeyDefaultClass: LabelValueTrue}
|
||||
|
||||
r := &DefaultClassReconciler{
|
||||
client: m.GetClient(),
|
||||
converter: m.GetScheme(),
|
||||
labels: labels,
|
||||
newClaim: nc,
|
||||
newPortableClass: np,
|
||||
newPortableClassList: npl,
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
ro(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Reconcile reconciles a claim to the default class reference for its kind.
|
||||
func (r *DefaultClassReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
|
||||
log.V(logging.Debug).Info("Reconciling", "request", req, "controller", controllerNameDefaultClass)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultClassReconcileTimeout)
|
||||
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.
|
||||
return reconcile.Result{}, errors.Wrap(IgnoreNotFound(err), errGetClaim)
|
||||
}
|
||||
|
||||
// Get portable classes for claim kind in claim's namespace
|
||||
portables := r.newPortableClassList()
|
||||
if err := r.client.List(ctx, portables, client.InNamespace(req.Namespace), client.MatchingLabels(r.labels)); err != nil {
|
||||
// If this is the first time we encounter listing error we'll be
|
||||
// requeued implicitly due to the status update. If not, we don't
|
||||
// care to requeue because list parameters will not change.
|
||||
claim.SetConditions(corev1alpha1.ReconcileError(errors.New(errFailedList)))
|
||||
return reconcile.Result{}, errors.Wrap(IgnoreNotFound(r.client.Status().Update(ctx, claim)), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
items := portables.GetPortableClassItems()
|
||||
// Check to see if no defaults defined for claim kind.
|
||||
if len(items) == 0 {
|
||||
// If this is the first time we encounter no default portable classes we'll be
|
||||
// requeued implicitly due to the status update. If not, we will requeue
|
||||
// after a time to see if a default portable class has been created.
|
||||
claim.SetConditions(corev1alpha1.ReconcileError(errors.New(errNoPortableClass)))
|
||||
return reconcile.Result{RequeueAfter: defaultClassWait}, errors.Wrap(IgnoreNotFound(r.client.Status().Update(ctx, claim)), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
// Check to see if multiple defaults defined for claim kind.
|
||||
if len(items) > 1 {
|
||||
// If this is the first time we encounter multiple default portable classes we'll be
|
||||
// requeued implicitly due to the status update. If not, we will requeue
|
||||
// after a time to see if only one default portable class exists.
|
||||
claim.SetConditions(corev1alpha1.ReconcileError(errors.New(errMultiplePortableClasses)))
|
||||
return reconcile.Result{RequeueAfter: defaultClassWait}, errors.Wrap(IgnoreNotFound(r.client.Status().Update(ctx, claim)), errUpdateClaimStatus)
|
||||
}
|
||||
|
||||
// Set portable class reference on claim to default portable class.
|
||||
portable := items[0]
|
||||
claim.SetPortableClassReference(&corev1.LocalObjectReference{Name: portable.GetName()})
|
||||
|
||||
// Do not requeue, claim controller will see update and claim
|
||||
// with class reference set will pass predicates.
|
||||
return reconcile.Result{Requeue: false}, errors.Wrap(IgnoreNotFound(r.client.Update(ctx, claim)), errUpdateClaimStatus)
|
||||
}
|
||||
|
|
@ -1,266 +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 resource
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var _ reconcile.Reconciler = &DefaultClassReconciler{}
|
||||
|
||||
type MockObjectConvertor struct {
|
||||
runtime.ObjectConvertor
|
||||
}
|
||||
|
||||
func (m *MockObjectConvertor) Convert(in, out, context interface{}) error {
|
||||
i, inok := in.(*unstructured.Unstructured)
|
||||
if !inok {
|
||||
return errors.Errorf("expected conversion input to be of type %s", reflect.TypeOf(unstructured.Unstructured{}).String())
|
||||
}
|
||||
_, outok := out.(*MockPortableClass)
|
||||
if !outok {
|
||||
return errors.Errorf("expected conversion input to be of type %s", reflect.TypeOf(MockPortableClass{}).String())
|
||||
}
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(i.Object, out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDefaultClassReconcile(t *testing.T) {
|
||||
type args struct {
|
||||
m manager.Manager
|
||||
of ClaimKind
|
||||
by PortableClassKind
|
||||
o []DefaultClassReconcilerOption
|
||||
}
|
||||
|
||||
type want struct {
|
||||
result reconcile.Result
|
||||
err error
|
||||
}
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
errUnexpected := errors.New("unexpected object type")
|
||||
|
||||
classRef := &corev1.ObjectReference{
|
||||
Name: "default-class",
|
||||
Namespace: "default-namespace",
|
||||
}
|
||||
portable := MockPortableClass{}
|
||||
portable.SetNonPortableClassReference(classRef)
|
||||
portableList := []PortableClass{
|
||||
&MockPortableClass{},
|
||||
}
|
||||
portableListTooMany := []PortableClass{
|
||||
&MockPortableClass{},
|
||||
&MockPortableClass{},
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"GetClaimError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockPortableClassList{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
by: PortableClassKind{Singular: MockGVK(&MockPortableClass{}), Plural: MockGVK(&MockPortableClassList{})},
|
||||
},
|
||||
want: want{err: errors.Wrap(errBoom, errGetClaim)},
|
||||
},
|
||||
"ListPoliciesError": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
*o = MockClaim{}
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockList: test.NewMockListFn(errBoom),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetConditions(v1alpha1.ReconcileError(errors.New(errFailedList)))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockPortableClassList{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
by: PortableClassKind{Singular: MockGVK(&MockPortableClass{}), Plural: MockGVK(&MockPortableClassList{})},
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"NoDefaultClass": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
*o = MockClaim{}
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockPortableClassList:
|
||||
*o = MockPortableClassList{}
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetConditions(v1alpha1.ReconcileError(errors.New(errNoPortableClass)))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockPortableClassList{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
by: PortableClassKind{Singular: MockGVK(&MockPortableClass{}), Plural: MockGVK(&MockPortableClassList{})},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: defaultClassWait}},
|
||||
},
|
||||
"MultipleDefaultClasses": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
*o = MockClaim{}
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockPortableClassList:
|
||||
pl := &MockPortableClassList{}
|
||||
pl.SetPortableClassItems(portableListTooMany)
|
||||
*o = *pl
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetConditions(v1alpha1.ReconcileError(errors.New(errMultiplePortableClasses)))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockPortableClassList{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
by: PortableClassKind{Singular: MockGVK(&MockPortableClass{}), Plural: MockGVK(&MockPortableClassList{})},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: defaultClassWait}},
|
||||
},
|
||||
"Successful": {
|
||||
args: args{
|
||||
m: &MockManager{
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockClaim:
|
||||
*o = MockClaim{}
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockPortableClassList:
|
||||
pl := &MockPortableClassList{}
|
||||
pl.SetPortableClassItems(portableList)
|
||||
*o = *pl
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &MockClaim{}
|
||||
want.SetPortableClassReference(&corev1.LocalObjectReference{Name: portable.GetName()})
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockPortableClassList{}),
|
||||
},
|
||||
of: ClaimKind(MockGVK(&MockClaim{})),
|
||||
by: PortableClassKind{Singular: MockGVK(&MockPortableClass{}), Plural: MockGVK(&MockPortableClassList{})},
|
||||
o: []DefaultClassReconcilerOption{WithObjectConverter(&MockObjectConvertor{})},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: false}},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewDefaultClassReconciler(tc.args.m, tc.args.of, tc.args.by, 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -44,16 +44,16 @@ type ClaimReferencer interface {
|
|||
GetClaimReference() *corev1.ObjectReference
|
||||
}
|
||||
|
||||
// A NonPortableClassReferencer may reference a non-portable resource class.
|
||||
type NonPortableClassReferencer interface {
|
||||
SetNonPortableClassReference(r *corev1.ObjectReference)
|
||||
GetNonPortableClassReference() *corev1.ObjectReference
|
||||
// A ClassSelector may reference a resource class.
|
||||
type ClassSelector interface {
|
||||
SetClassSelector(s *metav1.LabelSelector)
|
||||
GetClassSelector() *metav1.LabelSelector
|
||||
}
|
||||
|
||||
// A PortableClassReferencer may reference a local portable class.
|
||||
type PortableClassReferencer interface {
|
||||
SetPortableClassReference(r *corev1.LocalObjectReference)
|
||||
GetPortableClassReference() *corev1.LocalObjectReference
|
||||
// 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.
|
||||
|
|
@ -62,10 +62,18 @@ type ManagedResourceReferencer interface {
|
|||
GetResourceReference() *corev1.ObjectReference
|
||||
}
|
||||
|
||||
// A ConnectionSecretWriterTo may write a connection secret.
|
||||
// A LocalConnectionSecretWriterTo may write a connection secret to its own
|
||||
// namespace.
|
||||
type LocalConnectionSecretWriterTo interface {
|
||||
SetWriteConnectionSecretToReference(r *v1alpha1.LocalSecretReference)
|
||||
GetWriteConnectionSecretToReference() *v1alpha1.LocalSecretReference
|
||||
}
|
||||
|
||||
// A ConnectionSecretWriterTo may write a connection secret to an arbitrary
|
||||
// namespace.
|
||||
type ConnectionSecretWriterTo interface {
|
||||
SetWriteConnectionSecretToReference(r corev1.LocalObjectReference)
|
||||
GetWriteConnectionSecretToReference() corev1.LocalObjectReference
|
||||
SetWriteConnectionSecretToReference(r *v1alpha1.SecretReference)
|
||||
GetWriteConnectionSecretToReference() *v1alpha1.SecretReference
|
||||
}
|
||||
|
||||
// A Reclaimer may specify a ReclaimPolicy.
|
||||
|
|
@ -74,12 +82,6 @@ type Reclaimer interface {
|
|||
GetReclaimPolicy() v1alpha1.ReclaimPolicy
|
||||
}
|
||||
|
||||
// A PortableClassLister may contain a list of portable classes.
|
||||
type PortableClassLister interface {
|
||||
SetPortableClassItems(i []PortableClass)
|
||||
GetPortableClassItems() []PortableClass
|
||||
}
|
||||
|
||||
// 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).
|
||||
|
|
@ -87,17 +89,18 @@ type Claim interface {
|
|||
runtime.Object
|
||||
metav1.Object
|
||||
|
||||
PortableClassReferencer
|
||||
ClassSelector
|
||||
ClassReferencer
|
||||
ManagedResourceReferencer
|
||||
ConnectionSecretWriterTo
|
||||
LocalConnectionSecretWriterTo
|
||||
|
||||
Conditioned
|
||||
Bindable
|
||||
}
|
||||
|
||||
// A NonPortableClass is a Kubernetes object representing configuration
|
||||
// specifications for a manged resource.
|
||||
type NonPortableClass interface {
|
||||
// A Class is a Kubernetes object representing configuration specifications for
|
||||
// a managed resource.
|
||||
type Class interface {
|
||||
runtime.Object
|
||||
metav1.Object
|
||||
|
||||
|
|
@ -110,7 +113,7 @@ type Managed interface {
|
|||
runtime.Object
|
||||
metav1.Object
|
||||
|
||||
NonPortableClassReferencer
|
||||
ClassReferencer
|
||||
ClaimReferencer
|
||||
ConnectionSecretWriterTo
|
||||
Reclaimer
|
||||
|
|
@ -118,21 +121,3 @@ type Managed interface {
|
|||
Conditioned
|
||||
Bindable
|
||||
}
|
||||
|
||||
// A PortableClass is a Kubernetes object representing a default
|
||||
// behavior for a given claim kind.
|
||||
type PortableClass interface {
|
||||
runtime.Object
|
||||
metav1.Object
|
||||
|
||||
NonPortableClassReferencer
|
||||
}
|
||||
|
||||
// A PortableClassList is a Kubernetes object representing representing
|
||||
// a list of portable classes.
|
||||
type PortableClassList interface {
|
||||
runtime.Object
|
||||
metav1.ListInterface
|
||||
|
||||
PortableClassLister
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,21 +41,21 @@ type MockClaimReferencer struct{ Ref *corev1.ObjectReference }
|
|||
func (m *MockClaimReferencer) SetClaimReference(r *corev1.ObjectReference) { m.Ref = r }
|
||||
func (m *MockClaimReferencer) GetClaimReference() *corev1.ObjectReference { return m.Ref }
|
||||
|
||||
type MockNonPortableClassReferencer struct{ Ref *corev1.ObjectReference }
|
||||
type MockClassSelector struct{ Sel *metav1.LabelSelector }
|
||||
|
||||
func (m *MockNonPortableClassReferencer) SetNonPortableClassReference(r *corev1.ObjectReference) {
|
||||
func (m *MockClassSelector) SetClassSelector(s *metav1.LabelSelector) {
|
||||
m.Sel = s
|
||||
}
|
||||
func (m *MockClassSelector) GetClassSelector() *metav1.LabelSelector {
|
||||
return m.Sel
|
||||
}
|
||||
|
||||
type MockClassReferencer struct{ Ref *corev1.ObjectReference }
|
||||
|
||||
func (m *MockClassReferencer) SetClassReference(r *corev1.ObjectReference) {
|
||||
m.Ref = r
|
||||
}
|
||||
func (m *MockNonPortableClassReferencer) GetNonPortableClassReference() *corev1.ObjectReference {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
type MockPortableClassReferencer struct{ Ref *corev1.LocalObjectReference }
|
||||
|
||||
func (m *MockPortableClassReferencer) SetPortableClassReference(r *corev1.LocalObjectReference) {
|
||||
m.Ref = r
|
||||
}
|
||||
func (m *MockPortableClassReferencer) GetPortableClassReference() *corev1.LocalObjectReference {
|
||||
func (m *MockClassReferencer) GetClassReference() *corev1.ObjectReference {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
|
|
@ -64,12 +64,23 @@ type MockManagedResourceReferencer struct{ Ref *corev1.ObjectReference }
|
|||
func (m *MockManagedResourceReferencer) SetResourceReference(r *corev1.ObjectReference) { m.Ref = r }
|
||||
func (m *MockManagedResourceReferencer) GetResourceReference() *corev1.ObjectReference { return m.Ref }
|
||||
|
||||
type MockConnectionSecretWriterTo struct{ Ref corev1.LocalObjectReference }
|
||||
type MockLocalConnectionSecretWriterTo struct {
|
||||
Ref *v1alpha1.LocalSecretReference
|
||||
}
|
||||
|
||||
func (m *MockConnectionSecretWriterTo) SetWriteConnectionSecretToReference(r corev1.LocalObjectReference) {
|
||||
func (m *MockLocalConnectionSecretWriterTo) SetWriteConnectionSecretToReference(r *v1alpha1.LocalSecretReference) {
|
||||
m.Ref = r
|
||||
}
|
||||
func (m *MockConnectionSecretWriterTo) GetWriteConnectionSecretToReference() corev1.LocalObjectReference {
|
||||
func (m *MockLocalConnectionSecretWriterTo) GetWriteConnectionSecretToReference() *v1alpha1.LocalSecretReference {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
type MockConnectionSecretWriterTo struct{ Ref *v1alpha1.SecretReference }
|
||||
|
||||
func (m *MockConnectionSecretWriterTo) SetWriteConnectionSecretToReference(r *v1alpha1.SecretReference) {
|
||||
m.Ref = r
|
||||
}
|
||||
func (m *MockConnectionSecretWriterTo) GetWriteConnectionSecretToReference() *v1alpha1.SecretReference {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
|
|
@ -78,27 +89,23 @@ type MockReclaimer struct{ Policy v1alpha1.ReclaimPolicy }
|
|||
func (m *MockReclaimer) SetReclaimPolicy(p v1alpha1.ReclaimPolicy) { m.Policy = p }
|
||||
func (m *MockReclaimer) GetReclaimPolicy() v1alpha1.ReclaimPolicy { return m.Policy }
|
||||
|
||||
type MockPortableClassLister struct{ Items []PortableClass }
|
||||
|
||||
func (m *MockPortableClassLister) SetPortableClassItems(i []PortableClass) { m.Items = i }
|
||||
func (m *MockPortableClassLister) GetPortableClassItems() []PortableClass { return m.Items }
|
||||
|
||||
var _ Claim = &MockClaim{}
|
||||
|
||||
type MockClaim struct {
|
||||
runtime.Object
|
||||
|
||||
metav1.ObjectMeta
|
||||
MockPortableClassReferencer
|
||||
MockClassSelector
|
||||
MockClassReferencer
|
||||
MockManagedResourceReferencer
|
||||
MockConnectionSecretWriterTo
|
||||
MockLocalConnectionSecretWriterTo
|
||||
MockConditioned
|
||||
MockBindable
|
||||
}
|
||||
|
||||
var _ NonPortableClass = &MockNonPortableClass{}
|
||||
var _ Class = &MockClass{}
|
||||
|
||||
type MockNonPortableClass struct {
|
||||
type MockClass struct {
|
||||
runtime.Object
|
||||
|
||||
metav1.ObjectMeta
|
||||
|
|
@ -111,28 +118,10 @@ type MockManaged struct {
|
|||
runtime.Object
|
||||
|
||||
metav1.ObjectMeta
|
||||
MockNonPortableClassReferencer
|
||||
MockClassReferencer
|
||||
MockClaimReferencer
|
||||
MockConnectionSecretWriterTo
|
||||
MockReclaimer
|
||||
MockConditioned
|
||||
MockBindable
|
||||
}
|
||||
|
||||
var _ PortableClass = &MockPortableClass{}
|
||||
|
||||
type MockPortableClass struct {
|
||||
runtime.Object
|
||||
|
||||
metav1.ObjectMeta
|
||||
MockNonPortableClassReferencer
|
||||
}
|
||||
|
||||
var _ PortableClassList = &MockPortableClassList{}
|
||||
|
||||
type MockPortableClassList struct {
|
||||
runtime.Object
|
||||
|
||||
metav1.ListInterface
|
||||
MockPortableClassLister
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,9 @@ limitations under the License.
|
|||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
)
|
||||
|
|
@ -83,6 +79,23 @@ 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 {
|
||||
|
|
@ -158,59 +171,44 @@ func IsPropagated() PredicateFn {
|
|||
}
|
||||
}
|
||||
|
||||
// HasIndirectClassReferenceKind accepts namespaced objects that reference the
|
||||
// supplied non-portable class kind via the supplied portable class kind.
|
||||
func HasIndirectClassReferenceKind(c client.Client, oc runtime.ObjectCreater, k ClassKinds) PredicateFn {
|
||||
// HasClassSelector accepts resource claims that do not specify a resource
|
||||
// class selector.
|
||||
func HasClassSelector() PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
pcr, ok := obj.(PortableClassReferencer)
|
||||
cs, ok := obj.(ClassSelector)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
pr := pcr.GetPortableClassReference()
|
||||
if pr == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
n, ok := obj.(interface{ GetNamespace() string })
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), claimReconcileTimeout)
|
||||
defer cancel()
|
||||
|
||||
portable := MustCreateObject(k.Portable, oc).(PortableClass)
|
||||
p := types.NamespacedName{Namespace: n.GetNamespace(), Name: pr.Name}
|
||||
if err := c.Get(ctx, p, portable); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cr := portable.GetNonPortableClassReference()
|
||||
if cr == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
gvk := cr.GroupVersionKind()
|
||||
|
||||
return gvk == k.NonPortable
|
||||
return cs.GetClassSelector() != nil
|
||||
}
|
||||
}
|
||||
|
||||
// HasNoPortableClassReference accepts ResourceClaims that do not reference a
|
||||
// specific portable class
|
||||
func HasNoPortableClassReference() PredicateFn {
|
||||
// HasNoClassSelector accepts resource claims that do not specify a resource
|
||||
// class selector.
|
||||
func HasNoClassSelector() PredicateFn {
|
||||
return func(obj runtime.Object) bool {
|
||||
cr, ok := obj.(PortableClassReferencer)
|
||||
cs, ok := obj.(ClassSelector)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return cr.GetPortableClassReference() == nil
|
||||
return cs.GetClassSelector() == nil
|
||||
}
|
||||
}
|
||||
|
||||
// HasNoManagedResourceReference accepts ResourceClaims that do not reference a
|
||||
// specific Managed Resource
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -104,24 +104,20 @@ func TestHasManagedResourceReferenceKind(t *testing.T) {
|
|||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
c client.Client
|
||||
s runtime.ObjectCreater
|
||||
kind ManagedKind
|
||||
want bool
|
||||
}{
|
||||
"NotAClassReferencer": {
|
||||
c: &test.MockClient{},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockManaged{}),
|
||||
kind: ManagedKind(MockGVK(&MockManaged{})),
|
||||
want: false,
|
||||
},
|
||||
"HasNoResourceReference": {
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockManaged{}),
|
||||
obj: &MockClaim{},
|
||||
kind: ManagedKind(MockGVK(&MockManaged{})),
|
||||
want: false,
|
||||
},
|
||||
"HasCorrectResourceReference": {
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockManaged{}),
|
||||
obj: &MockClaim{
|
||||
MockManagedResourceReferencer: MockManagedResourceReferencer{
|
||||
Ref: &corev1.ObjectReference{
|
||||
|
|
@ -145,6 +141,47 @@ func TestHasManagedResourceReferenceKind(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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(MockGVK(&MockClass{})),
|
||||
want: false,
|
||||
},
|
||||
"HasNoClassReference": {
|
||||
obj: &MockClaim{},
|
||||
kind: ClassKind(MockGVK(&MockClass{})),
|
||||
want: false,
|
||||
},
|
||||
"HasCorrectClassReference": {
|
||||
obj: &MockClaim{
|
||||
MockClassReferencer: MockClassReferencer{
|
||||
Ref: &corev1.ObjectReference{
|
||||
APIVersion: MockGVK(&MockClass{}).GroupVersion().String(),
|
||||
Kind: MockGVK(&MockClass{}).Kind,
|
||||
},
|
||||
},
|
||||
},
|
||||
kind: ClassKind(MockGVK(&MockClass{})),
|
||||
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
|
||||
|
|
@ -335,100 +372,63 @@ func TestIsPropagated(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasIndirectClassReferenceKind(t *testing.T) {
|
||||
errUnexpected := errors.New("unexpected object type")
|
||||
|
||||
type withoutNamespace struct {
|
||||
runtime.Object
|
||||
*MockPortableClassReferencer
|
||||
}
|
||||
|
||||
func TestHasClassSelector(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
c client.Client
|
||||
s runtime.ObjectCreater
|
||||
kind ClassKinds
|
||||
want bool
|
||||
}{
|
||||
"NotAPortableClassReferencer": {
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}),
|
||||
kind: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
"NotAClassSelector": {
|
||||
want: false,
|
||||
},
|
||||
"NoPortableClassReference": {
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}),
|
||||
"NoClassSelector": {
|
||||
obj: &MockClaim{},
|
||||
kind: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
want: false,
|
||||
},
|
||||
"NotANamespacer": {
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}),
|
||||
obj: withoutNamespace{MockPortableClassReferencer: &MockPortableClassReferencer{Ref: &corev1.LocalObjectReference{}}},
|
||||
kind: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
want: false,
|
||||
},
|
||||
"GetPortableClassError": {
|
||||
c: &test.MockClient{MockGet: test.NewMockGetFn(errors.New("boom"))},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}),
|
||||
obj: &MockClaim{MockPortableClassReferencer: MockPortableClassReferencer{Ref: &corev1.LocalObjectReference{}}},
|
||||
kind: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
want: false,
|
||||
},
|
||||
"IncorrectNonPortableClassKind": {
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockPortableClass:
|
||||
pc := &MockPortableClass{}
|
||||
pc.SetNonPortableClassReference(&corev1.ObjectReference{})
|
||||
*o = *pc
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}),
|
||||
obj: &MockClaim{MockPortableClassReferencer: MockPortableClassReferencer{Ref: &corev1.LocalObjectReference{}}},
|
||||
kind: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
want: false,
|
||||
},
|
||||
"CorrectNonPortableClassKind": {
|
||||
c: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *MockPortableClass:
|
||||
pc := &MockPortableClass{}
|
||||
version, kind := MockGVK(&MockNonPortableClass{}).ToAPIVersionAndKind()
|
||||
pc.SetNonPortableClassReference(&corev1.ObjectReference{
|
||||
Kind: kind,
|
||||
APIVersion: version,
|
||||
})
|
||||
*o = *pc
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
}),
|
||||
},
|
||||
s: MockSchemeWith(&MockClaim{}, &MockPortableClass{}, &MockNonPortableClass{}),
|
||||
obj: &MockClaim{MockPortableClassReferencer: MockPortableClassReferencer{Ref: &corev1.LocalObjectReference{}}},
|
||||
kind: ClassKinds{Portable: MockGVK(&MockPortableClass{}), NonPortable: MockGVK(&MockNonPortableClass{})},
|
||||
"HasClassSelector": {
|
||||
obj: &MockClaim{MockClassSelector: MockClassSelector{Sel: &v1.LabelSelector{}}},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasIndirectClassReferenceKind(tc.c, tc.s, tc.kind)(tc.obj)
|
||||
got := HasClassSelector()(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("HasIndirectClassReferenceKind(...): -want, +got:\n%s", diff)
|
||||
t.Errorf("HasClassSelector(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNoPortableClassReference(t *testing.T) {
|
||||
func TestHasNoClassSelector(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
obj runtime.Object
|
||||
want bool
|
||||
}{
|
||||
"NotAClassSelector": {
|
||||
want: false,
|
||||
},
|
||||
"NoClassSelector": {
|
||||
obj: &MockClaim{},
|
||||
want: true,
|
||||
},
|
||||
"HasClassSelector": {
|
||||
obj: &MockClaim{MockClassSelector: MockClassSelector{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
|
||||
|
|
@ -441,16 +441,16 @@ func TestHasNoPortableClassReference(t *testing.T) {
|
|||
want: true,
|
||||
},
|
||||
"HasClassReference": {
|
||||
obj: &MockClaim{MockPortableClassReferencer: MockPortableClassReferencer{Ref: &corev1.LocalObjectReference{}}},
|
||||
obj: &MockClaim{MockClassReferencer: MockClassReferencer{Ref: &corev1.ObjectReference{}}},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := HasNoPortableClassReference()(tc.obj)
|
||||
got := HasNoClassReference()(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("NoClassReference(...): -want, +got:\n%s", diff)
|
||||
t.Errorf("HasNoClassReference(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -478,7 +478,7 @@ func TestHasNoMangedResourceReference(t *testing.T) {
|
|||
t.Run(name, func(t *testing.T) {
|
||||
got := HasNoManagedResourceReference()(tc.obj)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("NoManagedResourecReference(...): -want, +got:\n%s", diff)
|
||||
t.Errorf("HasNoManagedResourecReference(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func NewAPISecretPublisher(c client.Client, ot runtime.ObjectTyper) *APISecretPu
|
|||
// exists with the supplied ConnectionDetails.
|
||||
func (a *APISecretPublisher) PublishConnection(ctx context.Context, mg Managed, c ConnectionDetails) error {
|
||||
// This resource does not want to expose a connection secret.
|
||||
if mg.GetWriteConnectionSecretToReference().Name == "" {
|
||||
if mg.GetWriteConnectionSecretToReference() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
|
|
@ -122,6 +123,7 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
}
|
||||
|
||||
mgname := "coolmanaged"
|
||||
mgcsnamespace := "coolnamespace"
|
||||
mgcsname := "coolmanagedsecret"
|
||||
mgcsdata := map[string][]byte{
|
||||
"cool": []byte("data"),
|
||||
|
|
@ -163,7 +165,10 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
mg: &MockManaged{
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: &v1alpha1.SecretReference{
|
||||
Namespace: mgcsnamespace,
|
||||
Name: mgcsname,
|
||||
}},
|
||||
},
|
||||
c: ConnectionDetails{},
|
||||
},
|
||||
|
|
@ -183,7 +188,10 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
mg: &MockManaged{
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: &v1alpha1.SecretReference{
|
||||
Namespace: mgcsnamespace,
|
||||
Name: mgcsname,
|
||||
}},
|
||||
},
|
||||
c: ConnectionDetails{},
|
||||
},
|
||||
|
|
@ -195,6 +203,7 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
|
||||
MockCreate: test.NewMockCreateFn(nil, func(got runtime.Object) error {
|
||||
want := &corev1.Secret{}
|
||||
want.SetNamespace(mgcsnamespace)
|
||||
want.SetName(mgcsname)
|
||||
want.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
Name: mgname,
|
||||
|
|
@ -214,8 +223,11 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
mg: &MockManaged{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: &v1alpha1.SecretReference{
|
||||
Namespace: mgcsnamespace,
|
||||
Name: mgcsname,
|
||||
}},
|
||||
},
|
||||
c: ConnectionDetails(cddata),
|
||||
},
|
||||
|
|
@ -226,6 +238,7 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
s := &corev1.Secret{}
|
||||
s.SetNamespace(mgcsnamespace)
|
||||
s.SetName(mgcsname)
|
||||
s.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
Name: mgname,
|
||||
|
|
@ -238,6 +251,7 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
},
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &corev1.Secret{}
|
||||
want.SetNamespace(mgcsnamespace)
|
||||
want.SetName(mgcsname)
|
||||
want.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
Name: mgname,
|
||||
|
|
@ -257,8 +271,11 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
mg: &MockManaged{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: &v1alpha1.SecretReference{
|
||||
Namespace: mgcsnamespace,
|
||||
Name: mgcsname,
|
||||
}},
|
||||
},
|
||||
c: ConnectionDetails(cddata),
|
||||
},
|
||||
|
|
@ -269,6 +286,7 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
s := &corev1.Secret{}
|
||||
s.SetNamespace(mgcsnamespace)
|
||||
s.SetName(mgcsname)
|
||||
s.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
Name: mgname,
|
||||
|
|
@ -282,6 +300,7 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
},
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &corev1.Secret{}
|
||||
want.SetNamespace(mgcsnamespace)
|
||||
want.SetName(mgcsname)
|
||||
want.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
Name: mgname,
|
||||
|
|
@ -305,8 +324,11 @@ func TestAPISecretPublisher(t *testing.T) {
|
|||
args: args{
|
||||
ctx: context.Background(),
|
||||
mg: &MockManaged{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: corev1.LocalObjectReference{Name: mgcsname}},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: mgname},
|
||||
MockConnectionSecretWriterTo: MockConnectionSecretWriterTo{Ref: &v1alpha1.SecretReference{
|
||||
Namespace: mgcsnamespace,
|
||||
Name: mgcsname,
|
||||
}},
|
||||
},
|
||||
c: ConnectionDetails(cddata),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,18 +28,41 @@ import (
|
|||
"github.com/crossplaneio/crossplane-runtime/pkg/meta"
|
||||
)
|
||||
|
||||
// A ConnectionSecretOwner may create and manage a connection secret.
|
||||
// A LocalConnectionSecretOwner may create and manage a connection secret in its
|
||||
// own namespace.
|
||||
type LocalConnectionSecretOwner interface {
|
||||
metav1.Object
|
||||
LocalConnectionSecretWriterTo
|
||||
}
|
||||
|
||||
// LocalConnectionSecretFor creates a connection secret in the namespace of the
|
||||
// supplied LocalConnectionSecretOwner, assumed to be of the supplied kind.
|
||||
func LocalConnectionSecretFor(o LocalConnectionSecretOwner, kind schema.GroupVersionKind) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: o.GetNamespace(),
|
||||
Name: o.GetWriteConnectionSecretToReference().Name,
|
||||
OwnerReferences: []metav1.OwnerReference{meta.AsController(meta.ReferenceTo(o, kind))},
|
||||
},
|
||||
Data: make(map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// A ConnectionSecretOwner may create and manage a connection secret in an
|
||||
// arbitrary namespace.
|
||||
type ConnectionSecretOwner interface {
|
||||
metav1.Object
|
||||
ConnectionSecretWriterTo
|
||||
}
|
||||
|
||||
// ConnectionSecretFor the supplied ConnectionSecretOwner, assumed to be of the
|
||||
// supplied kind.
|
||||
// ConnectionSecretFor creates a connection for the supplied
|
||||
// ConnectionSecretOwner, assumed to be of the supplied kind. The secret is
|
||||
// written to 'default' namespace if the ConnectionSecretOwner does not specify
|
||||
// a namespace.
|
||||
func ConnectionSecretFor(o ConnectionSecretOwner, kind schema.GroupVersionKind) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: o.GetNamespace(),
|
||||
Namespace: o.GetWriteConnectionSecretToReference().Namespace,
|
||||
Name: o.GetWriteConnectionSecretToReference().Name,
|
||||
OwnerReferences: []metav1.OwnerReference{meta.AsController(meta.ReferenceTo(o, kind))},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,10 +32,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
namespace = "coolns"
|
||||
name = "cool"
|
||||
secretName = "coolsecret"
|
||||
uid = types.UID("definitely-a-uuid")
|
||||
namespace = "coolns"
|
||||
name = "cool"
|
||||
uid = types.UID("definitely-a-uuid")
|
||||
)
|
||||
|
||||
var MockOwnerGVK = schema.GroupVersionKind{
|
||||
|
|
@ -44,17 +43,87 @@ var MockOwnerGVK = schema.GroupVersionKind{
|
|||
Kind: "MockOwner",
|
||||
}
|
||||
|
||||
type MockLocalOwner struct {
|
||||
metav1.ObjectMeta
|
||||
Ref *v1alpha1.LocalSecretReference
|
||||
}
|
||||
|
||||
func (m *MockLocalOwner) GetWriteConnectionSecretToReference() *v1alpha1.LocalSecretReference {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
func (m *MockLocalOwner) SetWriteConnectionSecretToReference(r *v1alpha1.LocalSecretReference) {
|
||||
m.Ref = r
|
||||
}
|
||||
|
||||
func TestLocalConnectionSecretFor(t *testing.T) {
|
||||
secretName := "coolsecret"
|
||||
|
||||
type args struct {
|
||||
o LocalConnectionSecretOwner
|
||||
kind schema.GroupVersionKind
|
||||
}
|
||||
|
||||
controller := true
|
||||
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
want *corev1.Secret
|
||||
}{
|
||||
"Success": {
|
||||
args: args{
|
||||
o: &MockLocalOwner{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
},
|
||||
Ref: &v1alpha1.LocalSecretReference{Name: secretName},
|
||||
},
|
||||
kind: MockOwnerGVK,
|
||||
},
|
||||
want: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: secretName,
|
||||
OwnerReferences: []metav1.OwnerReference{{
|
||||
APIVersion: MockOwnerGVK.GroupVersion().String(),
|
||||
Kind: MockOwnerGVK.Kind,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
Controller: &controller,
|
||||
}},
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := LocalConnectionSecretFor(tc.args.o, tc.args.kind)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("LocalConnectionSecretFor(): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MockOwner struct {
|
||||
metav1.ObjectMeta
|
||||
Ref *v1alpha1.SecretReference
|
||||
}
|
||||
|
||||
func (m *MockOwner) GetWriteConnectionSecretToReference() corev1.LocalObjectReference {
|
||||
return corev1.LocalObjectReference{Name: secretName}
|
||||
func (m *MockOwner) GetWriteConnectionSecretToReference() *v1alpha1.SecretReference {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
func (m *MockOwner) SetWriteConnectionSecretToReference(_ corev1.LocalObjectReference) {}
|
||||
func (m *MockOwner) SetWriteConnectionSecretToReference(r *v1alpha1.SecretReference) {
|
||||
m.Ref = r
|
||||
}
|
||||
|
||||
func TestConnectionSecretFor(t *testing.T) {
|
||||
secretName := "coolsecret"
|
||||
|
||||
type args struct {
|
||||
o ConnectionSecretOwner
|
||||
kind schema.GroupVersionKind
|
||||
|
|
@ -68,11 +137,14 @@ func TestConnectionSecretFor(t *testing.T) {
|
|||
}{
|
||||
"Success": {
|
||||
args: args{
|
||||
o: &MockOwner{ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
}},
|
||||
o: &MockOwner{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
UID: uid,
|
||||
},
|
||||
Ref: &v1alpha1.SecretReference{Namespace: namespace, Name: secretName},
|
||||
},
|
||||
kind: MockOwnerGVK,
|
||||
},
|
||||
want: &corev1.Secret{
|
||||
|
|
|
|||
Loading…
Reference in New Issue