Skip to content

Commit

Permalink
Merge d5a278f into c7766dc
Browse files Browse the repository at this point in the history
  • Loading branch information
scothis committed May 9, 2022
2 parents c7766dc + d5a278f commit b0a4344
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 10 deletions.
17 changes: 8 additions & 9 deletions README.md
Expand Up @@ -530,6 +530,12 @@ func StashExampleSubReconciler(c reconcilers.Config) reconcilers.SubReconciler {

The [`Tracker`](https://pkg.go.dev/github.com/vmware-labs/reconciler-runtime/tracker#Tracker) provides a means for one resource to watch another resource for mutations, triggering the reconciliation of the resource defining the reference.

It's common to work with a resource that is also tracked. The [Config.TrackAndGet](https://pkg.go.dev/github.com/vmware-labs/reconciler-runtime/reconcilers#Config.TrackAndGet) method uses the same signature as client.Get, but additionally tracks the resource.

In the [Setup](https://pkg.go.dev/github.com/vmware-labs/reconciler-runtime@v0.4.0/reconcilers#SyncReconciler) method, a watch is created that will notify the handler every time a resource of that kind is mutated. The [EnqueueTracked](https://pkg.go.dev/github.com/vmware-labs/reconciler-runtime/reconcilers#EnqueueTracked) helper returns a list of resources that are tracking the given resource, those resources are enqueued for the reconciler.

The tracker will automatically expire a track request if not periodically renewed. By default, the TTL is 2x the resync internal. This ensures all tracked resources will naturally have the tracking relationship refreshed as part of the normal reconciliation resource. There is no need to manually untrack a resource.

**Example:**

The stream gateways in projectriff fetch the image references they use to run from a ConfigMap, when the values change, we want to detect and rollout the updated images.
Expand All @@ -544,15 +550,8 @@ func InMemoryGatewaySyncConfigReconciler(c reconcilers.Config, namespace string)

var config corev1.ConfigMap
key := types.NamespacedName{Namespace: namespace, Name: inmemoryGatewayImages}
// track config for new images
c.Tracker.Track(
// the resource to track, GVK and NamespacedName
tracker.NewKey(schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, key),
// the resource to enqueue, NamespacedName only
types.NamespacedName{Namespace: parent.Namespace, Name: parent.Name},
)
// get the configmap
if err := c.Get(ctx, key, &config); err != nil {
// track config for new images, get the configmap
if err := c.TrackAndGet(ctx, key, &config); err != nil {
return err
}
// consume the configmap
Expand Down
29 changes: 28 additions & 1 deletion reconcilers/reconcilers.go
Expand Up @@ -69,14 +69,27 @@ 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 {
name := typeName(apiType)
log := newWarnOnceLogger(ctrl.Log.WithName("controllers").WithName(name))
return Config{
Log: log,
Tracker: tracker.New(syncPeriod),
Tracker: tracker.New(2 * syncPeriod),
}.WithCluster(mgr)
}

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 b0a4344

Please sign in to comment.