k8s-operator: adding final tests

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
chaosinthecrd 2025-05-13 14:25:02 +01:00
parent ddbcbbbe52
commit f2758c49a7
No known key found for this signature in database
GPG Key ID: 87942E75F71EF65D
3 changed files with 179 additions and 122 deletions

View File

@ -1802,6 +1802,54 @@ func Test_metricsResourceCreation(t *testing.T) {
// object). We cannot test this using the fake client.
}
func TestIgnorePGService(t *testing.T) {
// NOTE: creating proxygroup stuff just to be sure that it's all ignored
_, _, fc, _ := setupServiceTest(t)
ft := &fakeTSClient{}
zl, err := zap.NewDevelopment()
if err != nil {
t.Fatal(err)
}
clock := tstest.NewClock(tstest.ClockOpts{})
sr := &ServiceReconciler{
Client: fc,
ssr: &tailscaleSTSReconciler{
Client: fc,
tsClient: ft,
defaultTags: []string{"tag:k8s"},
operatorNamespace: "operator-ns",
proxyImage: "tailscale/tailscale",
},
logger: zl.Sugar(),
clock: clock,
}
// Create a service that we should manage, and check that the initial round
// of objects looks right.
mustCreate(t, fc, &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
// The apiserver is supposed to set the UID, but the fake client
// doesn't. So, set it explicitly because other code later depends
// on it being set.
UID: types.UID("1234-UID"),
Annotations: map[string]string{
"tailscale.com/proxygroup": "test-pg",
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "10.20.30.40",
Type: corev1.ServiceTypeClusterIP,
},
})
expectReconciled(t, sr, "default", "test")
findNoGenName(t, fc, "default", "test", "svc")
}
func toFQDN(t *testing.T, s string) dnsname.FQDN {
t.Helper()
fqdn, err := dnsname.ToFQDN(s)

View File

@ -145,7 +145,7 @@ func setupServiceTest(t *testing.T) (*HAServiceReconciler, *corev1.Secret, clien
Labels: pgSecretLabels("test-pg", "config"),
},
Data: map[string][]byte{
tsoperator.TailscaledConfigFileName(106): []byte("{}"),
tsoperator.TailscaledConfigFileName(106): []byte(`{"Version":""}`),
},
}
@ -224,6 +224,110 @@ func setupServiceTest(t *testing.T) (*HAServiceReconciler, *corev1.Secret, clien
return svcPGR, pgStateSecret, fc, ft
}
func TestServicePGReconciler_MultiCluster(t *testing.T) {
var ft *fakeTSClient
var lc localClient
for i := 0; i <= 10; i++ {
pgr, stateSecret, fc, fti := setupServiceTest(t)
if i == 0 {
ft = fti
lc = pgr.lc
} else {
pgr.tsClient = ft
pgr.lc = lc
}
svc, _ := setupTestService(t, "test-multi-cluster", "", "4.3.2.1", fc, stateSecret)
expectReconciled(t, pgr, "default", svc.Name)
vipSvcs, err := ft.ListVIPServices(context.Background())
if err != nil {
t.Fatalf("getting VIPService: %v", err)
}
if len(vipSvcs) != 1 {
t.Fatalf("unexpected number of VIPServices (%d)", len(vipSvcs))
}
for name := range vipSvcs {
t.Logf("found vip service with name %q", name.String())
}
}
}
func TestIgnoreRegularService(t *testing.T) {
pgr, _, fc, ft := setupServiceTest(t)
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
// The apiserver is supposed to set the UID, but the fake client
// doesn't. So, set it explicitly because other code later depends
// on it being set.
UID: types.UID("1234-UID"),
Annotations: map[string]string{
"tailscale.com/expose": "true",
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "10.20.30.40",
Type: corev1.ServiceTypeClusterIP,
},
}
// hostname := svc.Namespace + svc.Name
mustCreate(t, fc, svc)
expectReconciled(t, pgr, "default", "test")
verifyTailscaledConfig(t, fc, nil)
vipSvcs, err := ft.ListVIPServices(context.Background())
if err == nil {
t.Fatalf("failed to list VIPServices")
}
if len(vipSvcs) > 0 {
t.Fatal("unexpected vip services found")
}
}
func removeEl(s []string, value string) []string {
result := s[:0]
for _, v := range s {
if v != value {
result = append(result, v)
}
}
return result
}
func updateIngressConfigSecret(t *testing.T, fc client.Client, stateSecret *corev1.Secret, serviceName string, clusterIP string) {
ingressConfig := ingressservices.Configs{
fmt.Sprintf("svc:%s", serviceName): ingressservices.Config{
IPv4Mapping: &ingressservices.Mapping{
VIPServiceIP: netip.MustParseAddr(vipTestIP),
ClusterIP: netip.MustParseAddr(clusterIP),
},
},
}
ingressStatus := ingressservices.Status{
Configs: ingressConfig,
PodIPv4: "4.3.2.1",
}
icJson, err := json.Marshal(ingressStatus)
if err != nil {
t.Fatal("failed to json marshal ingress config")
}
mustUpdate(t, fc, stateSecret.Namespace, stateSecret.Name, func(sec *corev1.Secret) {
mak.Set(&sec.Data, ingressservices.IngressConfigKey, icJson)
})
}
func setupTestService(t *testing.T, svcName string, hostname string, clusterIP string, fc client.Client, stateSecret *corev1.Secret) (svc *corev1.Service, eps *discoveryv1.EndpointSlice) {
uid := rand.IntN(100)
svc = &corev1.Service{
@ -271,124 +375,3 @@ func setupTestService(t *testing.T, svcName string, hostname string, clusterIP s
return svc, eps
}
// func TestServicePGReconciler_MultiCluster(t *testing.T) {
// ingPGR, fc, ft := setupIngressTest(t)
// ingPGR.operatorID = "operator-1"
//
// // Create initial Ingress
// ing := &networkingv1.Ingress{
// TypeMeta: metav1.TypeMeta{Kind: "Ingress", APIVersion: "networking.k8s.io/v1"},
// ObjectMeta: metav1.ObjectMeta{
// Name: "test-ingress",
// Namespace: "default",
// UID: types.UID("1234-UID"),
// Annotations: map[string]string{
// "tailscale.com/proxy-group": "test-pg",
// },
// },
// Spec: networkingv1.IngressSpec{
// IngressClassName: ptr.To("tailscale"),
// TLS: []networkingv1.IngressTLS{
// {Hosts: []string{"my-svc"}},
// },
// },
// }
// mustCreate(t, fc, ing)
//
// // Simulate existing VIPService from another cluster
// existingVIPSvc := &tailscale.VIPService{
// Name: "svc:my-svc",
// Annotations: map[string]string{
// ownerAnnotation: `{"ownerrefs":[{"operatorID":"operator-2"}]}`,
// },
// }
// ft.vipServices = map[tailcfg.ServiceName]*tailscale.VIPService{
// "svc:my-svc": existingVIPSvc,
// }
//
// // Verify reconciliation adds our operator reference
// expectReconciled(t, ingPGR, "default", "test-ingress")
//
// vipSvc, err := ft.GetVIPService(context.Background(), "svc:my-svc")
// if err != nil {
// t.Fatalf("getting VIPService: %v", err)
// }
// if vipSvc == nil {
// t.Fatal("VIPService not found")
// }
//
// o, err := parseOwnerAnnotation(vipSvc)
// if err != nil {
// t.Fatalf("parsing owner annotation: %v", err)
// }
//
// wantOwnerRefs := []OwnerRef{
// {OperatorID: "operator-2"},
// {OperatorID: "operator-1"},
// }
// if !reflect.DeepEqual(o.OwnerRefs, wantOwnerRefs) {
// t.Errorf("incorrect owner refs\ngot: %+v\nwant: %+v", o.OwnerRefs, wantOwnerRefs)
// }
//
// // Delete the Ingress and verify VIPService still exists with one owner ref
// if err := fc.Delete(context.Background(), ing); err != nil {
// t.Fatalf("deleting Ingress: %v", err)
// }
// expectRequeue(t, ingPGR, "default", "test-ingress")
//
// vipSvc, err = ft.GetVIPService(context.Background(), "svc:my-svc")
// if err != nil {
// t.Fatalf("getting VIPService after deletion: %v", err)
// }
// if vipSvc == nil {
// t.Fatal("VIPService was incorrectly deleted")
// }
//
// o, err = parseOwnerAnnotation(vipSvc)
// if err != nil {
// t.Fatalf("parsing owner annotation: %v", err)
// }
//
// wantOwnerRefs = []OwnerRef{
// {OperatorID: "operator-2"},
// }
// if !reflect.DeepEqual(o.OwnerRefs, wantOwnerRefs) {
// t.Errorf("incorrect owner refs after deletion\ngot: %+v\nwant: %+v", o.OwnerRefs, wantOwnerRefs)
// }
// }
func removeEl(s []string, value string) []string {
result := s[:0]
for _, v := range s {
if v != value {
result = append(result, v)
}
}
return result
}
func updateIngressConfigSecret(t *testing.T, fc client.Client, stateSecret *corev1.Secret, serviceName string, clusterIP string) {
ingressConfig := ingressservices.Configs{
fmt.Sprintf("svc:%s", serviceName): ingressservices.Config{
IPv4Mapping: &ingressservices.Mapping{
VIPServiceIP: netip.MustParseAddr(vipTestIP),
ClusterIP: netip.MustParseAddr(clusterIP),
},
},
}
ingressStatus := ingressservices.Status{
Configs: ingressConfig,
PodIPv4: "4.3.2.1",
}
icJson, err := json.Marshal(ingressStatus)
if err != nil {
t.Fatal("failed to json marshal ingress config")
}
mustUpdate(t, fc, stateSecret.Namespace, stateSecret.Name, func(sec *corev1.Secret) {
mak.Set(&sec.Data, ingressservices.IngressConfigKey, icJson)
})
}

View File

@ -565,6 +565,23 @@ func expectedSecret(t *testing.T, cl client.Client, opts configOpts) *corev1.Sec
return s
}
func findNoGenName(t *testing.T, client client.Client, ns, name, typ string) {
t.Helper()
labels := map[string]string{
kubetypes.LabelManaged: "true",
LabelParentName: name,
LabelParentNamespace: ns,
LabelParentType: typ,
}
s, err := getSingleObject[corev1.Secret](context.Background(), client, "operator-ns", labels)
if err != nil {
t.Fatalf("finding secrets for %q: %v", name, err)
}
if s != nil {
t.Fatalf("found unexpected secret with name %q", s.GetName())
}
}
func findGenName(t *testing.T, client client.Client, ns, name, typ string) (full, noSuffix string) {
t.Helper()
labels := map[string]string{
@ -893,6 +910,15 @@ func (c *fakeTSClient) GetVIPService(ctx context.Context, name tailcfg.ServiceNa
return svc, nil
}
func (c *fakeTSClient) ListVIPServices(ctx context.Context) (map[tailcfg.ServiceName]*tailscale.VIPService, error) {
c.Lock()
defer c.Unlock()
if c.vipServices == nil {
return nil, &tailscale.ErrResponse{Status: http.StatusNotFound}
}
return c.vipServices, nil
}
func (c *fakeTSClient) CreateOrUpdateVIPService(ctx context.Context, svc *tailscale.VIPService) error {
c.Lock()
defer c.Unlock()