Skip to content

Commit

Permalink
Merge pull request #13 from hasheddan/portable-class
Browse files Browse the repository at this point in the history
Implement portable classes
  • Loading branch information
negz committed Sep 12, 2019
2 parents 7dc4e26 + 06cc35d commit 5230aa5
Show file tree
Hide file tree
Showing 14 changed files with 470 additions and 292 deletions.
43 changes: 24 additions & 19 deletions apis/core/v1alpha1/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ type ResourceClaimSpec struct {
// 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.

ClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
ResourceReference *corev1.ObjectReference `json:"resourceRef,omitempty"`
// PortableClassReference is a reference to a portable class by name.
PortableClassReference *corev1.LocalObjectReference `json:"classRef,omitempty"`
ResourceReference *corev1.ObjectReference `json:"resourceRef,omitempty"`
}

// ResourceClaimStatus represents the status of a resource claim. Claims should
Expand All @@ -63,17 +64,19 @@ type ResourceClaimStatus struct {
type ResourceSpec struct {
WriteConnectionSecretToReference corev1.LocalObjectReference `json:"writeConnectionSecretToRef,omitempty"`

ClaimReference *corev1.ObjectReference `json:"claimRef,omitempty"`
ClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
ProviderReference *corev1.ObjectReference `json:"providerRef"`
ClaimReference *corev1.ObjectReference `json:"claimRef,omitempty"`

// NonPortableClassReference is a reference to a non-portable class.
NonPortableClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
ProviderReference *corev1.ObjectReference `json:"providerRef"`

ReclaimPolicy ReclaimPolicy `json:"reclaimPolicy,omitempty"`
}

// ResourceClassSpecTemplate contains standard fields that all resource classes should
// include in their spec template. ResourceClassSpecTemplate should typically be embedded in a
// resource class specific struct.
type ResourceClassSpecTemplate struct {
// NonPortableClassSpecTemplate contains standard fields that all non-portable classes should
// include in their spec template. NonPortableClassSpecTemplate should typically be embedded in a
// non-portable class specific struct.
type NonPortableClassSpecTemplate struct {
ProviderReference *corev1.ObjectReference `json:"providerRef"`

ReclaimPolicy ReclaimPolicy `json:"reclaimPolicy,omitempty"`
Expand All @@ -87,18 +90,20 @@ type ResourceStatus struct {
BindingStatus `json:",inline"`
}

// Policy contains standard fields that all policies should include. Policy
// should typically be embedded in a specific resource claim policy.
type Policy struct {
DefaultClassReference *corev1.ObjectReference `json:"defaultClassRef,omitempty"`
// PortableClass contains standard fields that all portable classes should include. Class
// should typically be embedded in a specific portable class.
type PortableClass struct {

// NonPortableClassReference is a reference to a non-portable class.
NonPortableClassReference *corev1.ObjectReference `json:"classRef,omitempty"`
}

// SetDefaultClassReference of this Policy
func (p *Policy) SetDefaultClassReference(r *corev1.ObjectReference) {
p.DefaultClassReference = r
// SetNonPortableClassReference of this Class
func (c *PortableClass) SetNonPortableClassReference(r *corev1.ObjectReference) {
c.NonPortableClassReference = r
}

// GetDefaultClassReference of this Policy
func (p *Policy) GetDefaultClassReference() *corev1.ObjectReference {
return p.DefaultClassReference
// GetNonPortableClassReference of this Class
func (c *PortableClass) GetNonPortableClassReference() *corev1.ObjectReference {
return c.NonPortableClassReference
}
62 changes: 31 additions & 31 deletions apis/core/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pkg/resource/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ 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 Class, mg Managed) error {
func (a *APIManagedCreator) Create(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error {
cmr := meta.ReferenceTo(cm, MustGetKind(cm, a.typer))
csr := meta.ReferenceTo(cs, MustGetKind(cs, a.typer))
mgr := meta.ReferenceTo(mg, MustGetKind(mg, a.typer))

mg.SetClaimReference(cmr)
mg.SetClassReference(csr)
mg.SetNonPortableClassReference(csr)
if err := a.client.Create(ctx, mg); err != nil {
return errors.Wrap(err, errCreateManaged)
}
Expand Down
20 changes: 10 additions & 10 deletions pkg/resource/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestCreate(t *testing.T) {
type args struct {
ctx context.Context
cm Claim
cs Class
cs NonPortableClass
mg Managed
}

Expand All @@ -71,12 +71,12 @@ func TestCreate(t *testing.T) {
client: &test.MockClient{
MockCreate: test.NewMockCreateFn(errBoom),
},
typer: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
typer: MockSchemeWith(&MockClaim{}, &MockNonPortableClass{}, &MockManaged{}),
},
args: args{
ctx: context.Background(),
cm: &MockClaim{},
cs: &MockClass{},
cs: &MockNonPortableClass{},
mg: &MockManaged{},
},
want: errors.Wrap(errBoom, errCreateManaged),
Expand All @@ -87,12 +87,12 @@ func TestCreate(t *testing.T) {
MockCreate: test.NewMockCreateFn(nil),
MockUpdate: test.NewMockUpdateFn(errBoom),
},
typer: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
typer: MockSchemeWith(&MockClaim{}, &MockNonPortableClass{}, &MockManaged{}),
},
args: args{
ctx: context.Background(),
cm: &MockClaim{},
cs: &MockClass{},
cs: &MockNonPortableClass{},
mg: &MockManaged{},
},
want: errors.Wrap(errBoom, errUpdateClaim),
Expand All @@ -108,10 +108,10 @@ func TestCreate(t *testing.T) {
APIVersion: MockGVK(&MockClaim{}).GroupVersion().String(),
Kind: MockGVK(&MockClaim{}).Kind,
})
want.SetClassReference(&corev1.ObjectReference{
want.SetNonPortableClassReference(&corev1.ObjectReference{
Name: csname,
APIVersion: MockGVK(&MockClass{}).GroupVersion().String(),
Kind: MockGVK(&MockClass{}).Kind,
APIVersion: MockGVK(&MockNonPortableClass{}).GroupVersion().String(),
Kind: MockGVK(&MockNonPortableClass{}).Kind,
})
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
t.Errorf("-want, +got:\n%s", diff)
Expand All @@ -133,12 +133,12 @@ func TestCreate(t *testing.T) {
return nil
}),
},
typer: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
typer: MockSchemeWith(&MockClaim{}, &MockNonPortableClass{}, &MockManaged{}),
},
args: args{
ctx: context.Background(),
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Name: cmname}},
cs: &MockClass{ObjectMeta: metav1.ObjectMeta{Name: csname}},
cs: &MockNonPortableClass{ObjectMeta: metav1.ObjectMeta{Name: csname}},
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Name: mgname}},
},
want: nil,
Expand Down
73 changes: 48 additions & 25 deletions pkg/resource/claim_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ 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"
Expand Down Expand Up @@ -56,24 +57,27 @@ var log = logging.Logger.WithName("controller")
// A ClaimKind contains the type metadata for a kind of resource claim.
type ClaimKind schema.GroupVersionKind

// A ClassKind contains the type metadata for a kind of resource class.
type ClassKind schema.GroupVersionKind
// A ClassKinds contains the type metadata for a kind of resource class.
type ClassKinds struct {
Portable schema.GroupVersionKind
NonPortable schema.GroupVersionKind
}

// A ManagedKind contains the type metadata for a kind of managed resource.
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 Class, mg Managed) error
Configure(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error
}

// A ManagedConfiguratorFn is a function that sastisfies the
// ManagedConfigurator interface.
type ManagedConfiguratorFn func(ctx context.Context, cm Claim, cs Class, mg Managed) error
type ManagedConfiguratorFn func(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error

// Configure the supplied resource using the supplied claim and class.
func (fn ManagedConfiguratorFn) Configure(ctx context.Context, cm Claim, cs Class, mg Managed) error {
func (fn ManagedConfiguratorFn) Configure(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error {
return fn(ctx, cm, cs, mg)
}

Expand All @@ -82,14 +86,14 @@ func (fn ManagedConfiguratorFn) Configure(ctx context.Context, cm Claim, cs Clas
// 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 Class, mg Managed) error
Create(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error
}

// A ManagedCreatorFn is a function that sastisfies the ManagedCreator interface.
type ManagedCreatorFn func(ctx context.Context, cm Claim, cs Class, mg Managed) error
type ManagedCreatorFn func(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error

// Create the supplied resource.
func (fn ManagedCreatorFn) Create(ctx context.Context, cm Claim, cs Class, mg Managed) error {
func (fn ManagedCreatorFn) Create(ctx context.Context, cm Claim, cs NonPortableClass, mg Managed) error {
return fn(ctx, cm, cs, mg)
}

Expand Down Expand Up @@ -156,10 +160,11 @@ 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
newClass func() Class
newManaged func() Managed
client client.Client
newClaim func() Claim
newNonPortableClass func() NonPortableClass
newPortableClass func() PortableClass
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
Expand Down Expand Up @@ -252,22 +257,24 @@ 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 ClassKind, with ManagedKind, o ...ClaimReconcilerOption) *ClaimReconciler {
func NewClaimReconciler(m manager.Manager, of ClaimKind, using ClassKinds, with ManagedKind, o ...ClaimReconcilerOption) *ClaimReconciler {
nc := func() Claim { return MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(Claim) }
ns := func() Class { return MustCreateObject(schema.GroupVersionKind(using), m.GetScheme()).(Class) }
ns := func() NonPortableClass { return MustCreateObject(using.NonPortable, m.GetScheme()).(NonPortableClass) }
np := func() PortableClass { return MustCreateObject(using.Portable, m.GetScheme()).(PortableClass) }
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(), nr()
_, _, _, _ = nc(), ns(), np(), nr()

r := &ClaimReconciler{
client: m.GetClient(),
newClaim: nc,
newClass: ns,
newManaged: nr,
managed: defaultCRManaged(m),
claim: defaultCRClaim(m),
client: m.GetClient(),
newClaim: nc,
newNonPortableClass: ns,
newPortableClass: np,
newManaged: nr,
managed: defaultCRManaged(m),
claim: defaultCRClaim(m),
}

for _, ro := range o {
Expand Down Expand Up @@ -331,10 +338,26 @@ func (r *ClaimReconciler) Reconcile(req reconcile.Request) (reconcile.Result, er
}

if !meta.WasCreated(managed) {
class := r.newClass()
// Class reference should always be set by the time we get this far; our
// watch predicates require it.
if err := r.client.Get(ctx, meta.NamespacedNameOf(claim.GetClassReference()), class); err != nil {
portable := r.newPortableClass()
// Portable 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 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 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
Expand Down
Loading

0 comments on commit 5230aa5

Please sign in to comment.