Skip to content

Commit

Permalink
TrackAndGet
Browse files Browse the repository at this point in the history
Streamline the flow for looking up a resource and watching it for
changes. The Tracker has long lived on the Config object as a peer to
the Client, it's very common to Track() a resource and then Get() that
same resource. Now we can do both, this effectively makes tracking
transparent as TrackAndGet() and Get() have the same method signature.

Before:

    c.Tracker.Track(
        ctx,
        tracker.NewKey(
           schema.GroupVersionKind{Version: "v1", Kind: "ServiceAccount"},
           types.NamespacedName{Namespace: parent.Namespace, Name: serviceAccountName},
        ),
        types.NamespacedName{Namespace: parent.Namespace, Name: parent.Name},
    )
    serviceAccount := corev1.ServiceAccount{}
    err := c.Get(ctx, types.NamespacedName{Namespace: parent.Namespace, Name: serviceAccountName}, &serviceAccount)

After:

    serviceAccount := corev1.ServiceAccount{}
    err := c.TrackAndGet(ctx, types.NamespacedName{Namespace: parent.Namespace, Name: serviceAccountName}, &serviceAccount)

The implementation assumes the ctx extends from a ParentReconciler.

Signed-off-by: Scott Andrews <andrewssc@vmware.com>
  • Loading branch information
scothis committed May 9, 2022
1 parent c7766dc commit 74c9fb7
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 0 deletions.
27 changes: 27 additions & 0 deletions reconcilers/reconcilers.go
Expand Up @@ -69,6 +69,19 @@ func (c Config) WithCluster(cluster cluster.Cluster) Config {
}
}

// TrackAndGet tracks the resources for changes and returns the current value. The track is
// registered even when the resource does not exists so that its creation can be tracked.
//
// Equivlent to calling both `c.Tracker.Track(...)` and `c.Client.Get(...)`
func (c Config) TrackAndGet(ctx context.Context, key types.NamespacedName, obj client.Object) error {
c.Tracker.Track(
ctx,
tracker.NewKey(gvk(obj, c.Scheme()), key),
RetrieveRequest(ctx).NamespacedName,
)
return c.Get(ctx, key, obj)
}

// NewConfig creates a Config for a specific API type. Typically passed into a
// reconciler.
func NewConfig(mgr ctrl.Manager, apiType client.Object, syncPeriod time.Duration) Config {
Expand Down Expand Up @@ -135,6 +148,7 @@ func (r *ParentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
WithValues("parentType", gvk(r.Type, c.Scheme()))
ctx = logr.NewContext(ctx, log)

ctx = StashRequest(ctx, req)
ctx = StashConfig(ctx, c)
ctx = StashParentConfig(ctx, c)
ctx = StashParentType(ctx, r.Type)
Expand Down Expand Up @@ -253,11 +267,16 @@ func (r *ParentReconciler) syncLastTransitionTime(proposed, original []metav1.Co
}
}

const requestStashKey StashKey = "reconciler-runtime:request"
const configStashKey StashKey = "reconciler-runtime:config"
const parentConfigStashKey StashKey = "reconciler-runtime:parentConfig"
const parentTypeStashKey StashKey = "reconciler-runtime:parentType"
const castParentTypeStashKey StashKey = "reconciler-runtime:castParentType"

func StashRequest(ctx context.Context, req ctrl.Request) context.Context {
return context.WithValue(ctx, requestStashKey, req)
}

func StashConfig(ctx context.Context, config Config) context.Context {
return context.WithValue(ctx, configStashKey, config)
}
Expand All @@ -274,6 +293,14 @@ func StashCastParentType(ctx context.Context, currentType client.Object) context
return context.WithValue(ctx, castParentTypeStashKey, currentType)
}

func RetrieveRequest(ctx context.Context) ctrl.Request {
value := ctx.Value(requestStashKey)
if req, ok := value.(ctrl.Request); ok {
return req
}
return ctrl.Request{}
}

func RetrieveConfig(ctx context.Context) Config {
value := ctx.Value(configStashKey)
if config, ok := value.(Config); ok {
Expand Down
61 changes: 61 additions & 0 deletions reconcilers/reconcilers_test.go
Expand Up @@ -34,6 +34,67 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func TestConfig_TrackAndGet(t *testing.T) {
testNamespace := "test-namespace"
testName := "test-resource"

scheme := runtime.NewScheme()
_ = resources.AddToScheme(scheme)
_ = clientgoscheme.AddToScheme(scheme)

resource := dies.TestResourceBlank.
MetadataDie(func(d *diemetav1.ObjectMetaDie) {
d.Namespace(testNamespace)
d.Name(testName)
d.CreationTimestamp(metav1.NewTime(time.UnixMilli(1000)))
})

configMap := diecorev1.ConfigMapBlank.
MetadataDie(func(d *diemetav1.ObjectMetaDie) {
d.Namespace("track-namespace")
d.Name("track-name")
}).
AddData("greeting", "hello")

rts := rtesting.SubReconcilerTestSuite{{
Name: "track and get",
Parent: resource,
GivenObjects: []client.Object{
configMap,
},
ExpectTracks: []rtesting.TrackRequest{
rtesting.NewTrackRequest(configMap, resource, scheme),
},
}, {
Name: "track with not found get",
Parent: resource,
ShouldErr: true,
ExpectTracks: []rtesting.TrackRequest{
rtesting.NewTrackRequest(configMap, resource, scheme),
},
}}

rts.Test(t, scheme, func(t *testing.T, rtc *rtesting.SubReconcilerTestCase, c reconcilers.Config) reconcilers.SubReconciler {
return &reconcilers.SyncReconciler{
Sync: func(ctx context.Context, parent *resources.TestResource) error {
c := reconcilers.RetrieveConfig(ctx)

cm := &corev1.ConfigMap{}
err := c.TrackAndGet(ctx, types.NamespacedName{Namespace: "track-namespace", Name: "track-name"}, cm)
if err != nil {
return err
}

if expected, actual := "hello", cm.Data["greeting"]; expected != actual {
// should never get here
panic(fmt.Errorf("expected configmap to have greeting %q, found %q", expected, actual))
}
return nil
},
}
})
}

func TestParentReconcilerWithNoStatus(t *testing.T) {
testNamespace := "test-namespace"
testName := "test-resource-no-status"
Expand Down
4 changes: 4 additions & 0 deletions testing/subreconciler.go
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/vmware-labs/reconciler-runtime/reconcilers"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand Down Expand Up @@ -170,6 +171,9 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto
// this value is also set by the test client when resource are added as givens
parent.SetResourceVersion("999")
}
ctx = reconcilers.StashRequest(ctx, reconcile.Request{
NamespacedName: types.NamespacedName{Namespace: parent.GetNamespace(), Name: parent.GetName()},
})
ctx = reconcilers.StashParentType(ctx, parent.DeepCopyObject().(client.Object))
ctx = reconcilers.StashCastParentType(ctx, parent.DeepCopyObject().(client.Object))

Expand Down

0 comments on commit 74c9fb7

Please sign in to comment.