Files
tailscale/k8s-operator/reconciler/reconciler_test.go
T
David Bond be0e460a20 cmd/k8s-operator,k8s-operator: Kubernetes Peer Relays (#20495)
This commit contains the Kubernetes implementation of peer relays via the new `PeerRelay` CRD. It's a mega branch consisting of the commits of other PRs gone into this work:

1. https://github.com/tailscale/tailscale/pull/20211
2. https://github.com/tailscale/tailscale/pull/20329
3. https://github.com/tailscale/tailscale/pull/20423
4. https://github.com/tailscale/tailscale/pull/20503

An instance of the `PeerRelay` CRD deploys a `StatefulSet` of containerboot instances configured to advertise themselves as peer relays using the IP addresses configured via `LoadBalancer` services on each cloud provider (with some AWS specifics as it's less automatic than its competing cloud providers). 

Per replica, a `LoadBalancer` type `Service` resource is provisioned and its IP address is used to configure the respective relay.

This has been tested with success in AWS, GCP & Azure and provides additional modification to `Service` resources via the CRD for any other kinds of deployment environments. It also contains some work that may appear to be duplication of what already exists within `cmd/k8s-operator` so we can start building an appropriate migration path for `Connector`, `ProxyGroup` etc into respective `k8s-operator/reconciler/*` packages.

Closes https://github.com/tailscale/corp/issues/34524
2026-07-20 16:37:15 +01:00

142 lines
3.6 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !plan9
package reconciler_test
import (
"maps"
"slices"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrlreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile"
"tailscale.com/k8s-operator/reconciler"
)
func TestFinalizers(t *testing.T) {
t.Parallel()
object := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
},
StringData: map[string]string{
"hello": "world",
},
}
reconciler.SetFinalizer(object)
if !slices.Contains(object.Finalizers, reconciler.FinalizerName) {
t.Fatalf("object does not have finalizer %q: %v", reconciler.FinalizerName, object.Finalizers)
}
reconciler.RemoveFinalizer(object)
if slices.Contains(object.Finalizers, reconciler.FinalizerName) {
t.Fatalf("object still has finalizer %q: %v", reconciler.FinalizerName, object.Finalizers)
}
}
func TestLabels(t *testing.T) {
t.Parallel()
t.Run("cluster-scoped-parent", func(t *testing.T) {
got := reconciler.Labels("peerrelay", "test", "")
want := map[string]string{
"tailscale.com/managed": "true",
"tailscale.com/parent-resource-type": "peerrelay",
"tailscale.com/parent-resource": "test",
}
if !maps.Equal(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
})
t.Run("namespaced-parent", func(t *testing.T) {
got := reconciler.Labels("connector", "test", "kube-system")
want := map[string]string{
"tailscale.com/managed": "true",
"tailscale.com/parent-resource-type": "connector",
"tailscale.com/parent-resource": "test",
"tailscale.com/parent-resource-ns": "kube-system",
}
if !maps.Equal(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
})
}
func TestEnqueueForChild(t *testing.T) {
t.Parallel()
enqueue := reconciler.EnqueueForChild("peerrelay")
tests := []struct {
name string
labels map[string]string
want []ctrlreconcile.Request
}{
{
name: "matching-cluster-scoped",
labels: map[string]string{
"tailscale.com/managed": "true",
"tailscale.com/parent-resource-type": "peerrelay",
"tailscale.com/parent-resource": "test",
},
want: []ctrlreconcile.Request{{NamespacedName: types.NamespacedName{Name: "test"}}},
},
{
name: "matching-namespaced",
labels: map[string]string{
"tailscale.com/managed": "true",
"tailscale.com/parent-resource-type": "peerrelay",
"tailscale.com/parent-resource": "test",
"tailscale.com/parent-resource-ns": "kube-system",
},
want: []ctrlreconcile.Request{{NamespacedName: types.NamespacedName{Name: "test", Namespace: "kube-system"}}},
},
{
name: "not-managed",
labels: map[string]string{
"tailscale.com/parent-resource-type": "peerrelay",
"tailscale.com/parent-resource": "test",
},
},
{
name: "wrong-parent-type",
labels: map[string]string{
"tailscale.com/managed": "true",
"tailscale.com/parent-resource-type": "proxygroup",
"tailscale.com/parent-resource": "test",
},
},
{
name: "missing-parent-name",
labels: map[string]string{
"tailscale.com/managed": "true",
"tailscale.com/parent-resource-type": "peerrelay",
},
},
{
name: "no-labels",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
obj := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Labels: tc.labels}}
got := enqueue(t.Context(), obj)
if !slices.Equal(got, tc.want) {
t.Errorf("expected %v, got %v", tc.want, got)
}
})
}
}