Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/controller/dnsrecordset_replicator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func (r *DNSRecordSetReplicator) ensureDownstreamRecordSet(ctx context.Context,
shadow.SetName(md.Name)

res, cErr := controllerutil.CreateOrPatch(ctx, r.DownstreamClient, &shadow, func() error {
shadow.Labels = md.Labels
shadow.Annotations = md.Annotations
if !equality.Semantic.DeepEqual(shadow.Spec, upstream.Spec) {
shadow.Spec = upstream.Spec
}
Expand Down
190 changes: 190 additions & 0 deletions internal/controller/dnsrecordset_replicator_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: AGPL-3.0-only

package controller

import (
"context"
"testing"

dnsv1alpha1 "go.miloapis.com/dns-operator/api/v1alpha1"
"go.miloapis.com/dns-operator/internal/downstreamclient"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type annotatingStrategy struct {
fakeStrategy
}

func (s annotatingStrategy) ObjectMetaFromUpstreamObject(_ context.Context, obj metav1.Object) (metav1.ObjectMeta, error) {
return metav1.ObjectMeta{
Namespace: s.namespace,
Name: obj.GetName(),
Annotations: map[string]string{
downstreamclient.UpstreamOwnerNamespaceAnnotation: obj.GetNamespace(),
},
}, nil
}

func (s annotatingStrategy) SetControllerReference(_ context.Context, owner, controlled metav1.Object, _ ...controllerutil.OwnerReferenceOption) error {
annotations := controlled.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations[downstreamclient.UpstreamOwnerClusterNameAnnotation] = "cluster-test"
annotations[downstreamclient.UpstreamOwnerGroupAnnotation] = "dns.networking.miloapis.com"
annotations[downstreamclient.UpstreamOwnerKindAnnotation] = "DNSRecordSet"
annotations[downstreamclient.UpstreamOwnerNameAnnotation] = owner.GetName()
annotations[downstreamclient.UpstreamOwnerNamespaceAnnotation] = owner.GetNamespace()
controlled.SetAnnotations(annotations)
return nil
}

func newTestScheme(t *testing.T) *runtime.Scheme {
t.Helper()
s := runtime.NewScheme()
for _, add := range []func(*runtime.Scheme) error{
dnsv1alpha1.AddToScheme,
corev1.AddToScheme,
} {
if err := add(s); err != nil {
t.Fatalf("add scheme: %v", err)
}
}
return s
}

func TestEnsureDownstreamRecordSet_NewShadowGetsAnnotations(t *testing.T) {
t.Parallel()
scheme := newTestScheme(t)

longName := "v4-2ed208a11f54412a92e8a2619eb662ea-prism-staging-env-datum-net-a-a59c082e"

upstream := &dnsv1alpha1.DNSRecordSet{
ObjectMeta: metav1.ObjectMeta{
Name: longName,
Namespace: "default",
UID: "upstream-uid-1",
},
Spec: dnsv1alpha1.DNSRecordSetSpec{
DNSZoneRef: corev1.LocalObjectReference{Name: "zone-a"},
RecordType: dnsv1alpha1.RRTypeA,
},
}

downstreamClient := fake.NewClientBuilder().WithScheme(scheme).Build()

strategy := annotatingStrategy{fakeStrategy{
namespace: "ns-downstream",
client: downstreamClient,
}}

r := &DNSRecordSetReplicator{DownstreamClient: downstreamClient}

res, err := r.ensureDownstreamRecordSet(context.Background(), strategy, upstream)
if err != nil {
t.Fatalf("ensureDownstreamRecordSet: %v", err)
}
if res != controllerutil.OperationResultCreated {
t.Fatalf("expected Created, got %s", res)
}

var shadow dnsv1alpha1.DNSRecordSet
if err := downstreamClient.Get(context.Background(), types.NamespacedName{
Namespace: "ns-downstream",
Name: longName,
}, &shadow); err != nil {
t.Fatalf("get shadow: %v", err)
}

wantAnnotations := map[string]string{
downstreamclient.UpstreamOwnerClusterNameAnnotation: "cluster-test",
downstreamclient.UpstreamOwnerGroupAnnotation: "dns.networking.miloapis.com",
downstreamclient.UpstreamOwnerKindAnnotation: "DNSRecordSet",
downstreamclient.UpstreamOwnerNameAnnotation: longName,
downstreamclient.UpstreamOwnerNamespaceAnnotation: "default",
}
for k, want := range wantAnnotations {
if got := shadow.Annotations[k]; got != want {
t.Errorf("annotation %s = %q, want %q", k, got, want)
}
}

if len(shadow.Labels) != 0 {
t.Errorf("expected no labels on new shadow, got %v", shadow.Labels)
}
}

func TestEnsureDownstreamRecordSet_ExistingLabelsPreserved(t *testing.T) {
t.Parallel()
scheme := newTestScheme(t)

upstream := &dnsv1alpha1.DNSRecordSet{
ObjectMeta: metav1.ObjectMeta{
Name: "short-name",
Namespace: "default",
UID: "upstream-uid-2",
},
Spec: dnsv1alpha1.DNSRecordSetSpec{
DNSZoneRef: corev1.LocalObjectReference{Name: "zone-a"},
RecordType: dnsv1alpha1.RRTypeA,
},
}

oldLabels := map[string]string{
downstreamclient.UpstreamOwnerClusterNameAnnotation: "cluster-test",
downstreamclient.UpstreamOwnerGroupAnnotation: "dns.networking.miloapis.com",
downstreamclient.UpstreamOwnerKindAnnotation: "DNSRecordSet",
downstreamclient.UpstreamOwnerNameAnnotation: "short-name",
downstreamclient.UpstreamOwnerNamespaceAnnotation: "default",
}

existingShadow := &dnsv1alpha1.DNSRecordSet{
ObjectMeta: metav1.ObjectMeta{
Name: "short-name",
Namespace: "ns-downstream",
Labels: oldLabels,
},
Spec: upstream.Spec,
}

downstreamClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(existingShadow).
Build()

strategy := annotatingStrategy{fakeStrategy{
namespace: "ns-downstream",
client: downstreamClient,
}}

r := &DNSRecordSetReplicator{DownstreamClient: downstreamClient}

_, err := r.ensureDownstreamRecordSet(context.Background(), strategy, upstream)
if err != nil {
t.Fatalf("ensureDownstreamRecordSet: %v", err)
}

var shadow dnsv1alpha1.DNSRecordSet
if err := downstreamClient.Get(context.Background(), types.NamespacedName{
Namespace: "ns-downstream",
Name: "short-name",
}, &shadow); err != nil {
t.Fatalf("get shadow: %v", err)
}

for k, want := range oldLabels {
if got := shadow.Labels[k]; got != want {
t.Errorf("old label %s = %q, want %q (should be preserved)", k, got, want)
}
}

if shadow.Annotations[downstreamclient.UpstreamOwnerNameAnnotation] != "short-name" {
t.Errorf("expected name annotation to be set, got %q",
shadow.Annotations[downstreamclient.UpstreamOwnerNameAnnotation])
}
}
2 changes: 1 addition & 1 deletion internal/controller/dnszone_replicator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func (r *DNSZoneReplicator) ensureDownstreamZone(ctx context.Context, strategy d
shadow.SetName(md.Name)

res, cErr := controllerutil.CreateOrPatch(ctx, strategy.GetClient(), &shadow, func() error {
shadow.Labels = md.Labels
shadow.Annotations = md.Annotations
if !equality.Semantic.DeepEqual(shadow.Spec, upstream.Spec) {
shadow.Spec = upstream.Spec
}
Expand Down
27 changes: 21 additions & 6 deletions internal/downstreamclient/enqueue_upstream_owner.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type empty struct{}

// TypedEnqueueRequestForUpstreamOwner enqueues Requests for the upstream Owners of an object.
//
// This handler depends on the `compute.datumapis.com/upstream-namespace` label
// This handler depends on the `meta.datumapis.com/upstream-*` annotations
// to exist on the resource for the event.
func TypedEnqueueRequestForUpstreamOwner[object client.Object](ownerType client.Object) mchandler.TypedEventHandlerFunc[object, mcreconcile.Request] {

Expand Down Expand Up @@ -106,17 +106,32 @@ func (e *enqueueRequestForOwner[object]) parseOwnerTypeGroupKind(scheme *runtime
// getOwnerReconcileRequest looks at object and builds a map of reconcile.Request to reconcile
// owners of object that match e.OwnerType.
func (e *enqueueRequestForOwner[object]) getOwnerReconcileRequest(obj metav1.Object, result map[mcreconcile.Request]empty) {
labels := obj.GetLabels()
if labels[UpstreamOwnerKindLabel] == e.groupKind.Kind && labels[UpstreamOwnerGroupLabel] == e.groupKind.Group {
meta := upstreamOwnerMeta(obj)

if meta[UpstreamOwnerKindAnnotation] == e.groupKind.Kind && meta[UpstreamOwnerGroupAnnotation] == e.groupKind.Group {
request := mcreconcile.Request{
Request: reconcile.Request{
NamespacedName: types.NamespacedName{
Name: labels[UpstreamOwnerNameLabel],
Namespace: labels[UpstreamOwnerNamespaceLabel],
Name: meta[UpstreamOwnerNameAnnotation],
Namespace: meta[UpstreamOwnerNamespaceAnnotation],
},
},
ClusterName: strings.TrimPrefix(strings.ReplaceAll(labels[UpstreamOwnerClusterNameLabel], "_", "/"), "cluster-"),
ClusterName: strings.TrimPrefix(strings.ReplaceAll(meta[UpstreamOwnerClusterNameAnnotation], "_", "/"), "cluster-"),
}
result[request] = empty{}
}
}

// upstreamOwnerMeta returns upstream owner metadata from the object,
// preferring annotations but falling back to labels for backwards
// compatibility with resources created before the labels-to-annotations
// migration.
func upstreamOwnerMeta(obj metav1.Object) map[string]string {
if annotations := obj.GetAnnotations(); annotations[UpstreamOwnerKindAnnotation] != "" {
return annotations
}
if labels := obj.GetLabels(); labels[UpstreamOwnerKindAnnotation] != "" {
return labels
}
return nil
}
Loading
Loading