mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 18:09:58 +00:00
fix(controller): template concurrency (#1802)
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -46,7 +46,7 @@ all: manager
|
|||||||
# Run tests
|
# Run tests
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: test-clean generate manifests test-clean
|
test: test-clean generate manifests test-clean
|
||||||
@GO111MODULE=on go test -v $(shell go list ./... | grep -v "e2e") -coverprofile coverage.out
|
@GO111MODULE=on go test -race -v $(shell go list ./... | grep -v "e2e") -coverprofile coverage.out
|
||||||
|
|
||||||
.PHONY: test-clean
|
.PHONY: test-clean
|
||||||
test-clean: ## Clean tests cache
|
test-clean: ## Clean tests cache
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package template
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/valyala/fasttemplate"
|
"github.com/valyala/fasttemplate"
|
||||||
@@ -37,8 +38,15 @@ func TemplateForTenantAndNamespace(template string, tnt *capsulev1beta2.Tenant,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TemplateForTenantAndNamespace applies templating to all values in the provided map in place.
|
// TemplateForTenantAndNamespace applies templating to all values in the provided map in place.
|
||||||
func TemplateForTenantAndNamespaceMap(m map[string]string, tnt *capsulev1beta2.Tenant, ns *corev1.Namespace) {
|
func TemplateForTenantAndNamespaceMap(m map[string]string, tnt *capsulev1beta2.Tenant, ns *corev1.Namespace) map[string]string {
|
||||||
for k, v := range m {
|
if len(m) == 0 {
|
||||||
m[k] = TemplateForTenantAndNamespace(v, tnt, ns)
|
return map[string]string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out := maps.Clone(m)
|
||||||
|
for k, v := range out {
|
||||||
|
out[k] = TemplateForTenantAndNamespace(v, tnt, ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package template_test
|
package template_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
@@ -15,17 +16,13 @@ import (
|
|||||||
|
|
||||||
func newTenant(name string) *capsulev1beta2.Tenant {
|
func newTenant(name string) *capsulev1beta2.Tenant {
|
||||||
return &capsulev1beta2.Tenant{
|
return &capsulev1beta2.Tenant{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNamespace(name string) *v1.Namespace {
|
func newNamespace(name string) *v1.Namespace {
|
||||||
return &v1.Namespace{
|
return &v1.Namespace{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,13 +82,15 @@ func TestTemplateForTenantAndNamespace_OnlyNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateForTenantAndNamespace_NoDelimitersReturnsEmpty(t *testing.T) {
|
func TestTemplateForTenantAndNamespace_NoDelimiters_ReturnsInput(t *testing.T) {
|
||||||
tnt := newTenant("tenant-a")
|
tnt := newTenant("tenant-a")
|
||||||
ns := newNamespace("ns-1")
|
ns := newNamespace("ns-1")
|
||||||
|
|
||||||
got := tpl.TemplateForTenantAndNamespace("plain-value-without-templates", tnt, ns)
|
in := "plain-value-without-templates"
|
||||||
if got != "plain-value-without-templates" {
|
got := tpl.TemplateForTenantAndNamespace(in, tnt, ns)
|
||||||
t.Fatalf("expected empty string for input without delimiters, got %q", got)
|
|
||||||
|
if got != in {
|
||||||
|
t.Fatalf("expected %q, got %q", in, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,19 +110,24 @@ func TestTemplateForTenantAndNamespaceMap_ReplacesPlaceholders(t *testing.T) {
|
|||||||
tnt := newTenant("tenant-a")
|
tnt := newTenant("tenant-a")
|
||||||
ns := newNamespace("ns-1")
|
ns := newNamespace("ns-1")
|
||||||
|
|
||||||
m := map[string]string{
|
orig := map[string]string{
|
||||||
"key1": "tenant={{tenant.name}}, ns={{namespace}}",
|
"key1": "tenant={{tenant.name}}, ns={{namespace}}",
|
||||||
"key2": "plain-value",
|
"key2": "plain-value",
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
out := tpl.TemplateForTenantAndNamespaceMap(orig, tnt, ns)
|
||||||
|
|
||||||
if got := m["key1"]; got != "tenant=tenant-a, ns=ns-1" {
|
// output is templated
|
||||||
|
if got := out["key1"]; got != "tenant=tenant-a, ns=ns-1" {
|
||||||
t.Fatalf("key1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
t.Fatalf("key1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
||||||
}
|
}
|
||||||
|
if got := out["key2"]; got != "plain-value" {
|
||||||
|
t.Fatalf("key2: expected %q, got %q", "plain-value", got)
|
||||||
|
}
|
||||||
|
|
||||||
if got := m["key2"]; got != "plain-value" {
|
// input map must remain unchanged (new behavior)
|
||||||
t.Fatalf("key2: expected %q to remain unchanged, got %q", "plain-value", got)
|
if got := orig["key1"]; got != "tenant={{tenant.name}}, ns={{namespace}}" {
|
||||||
|
t.Fatalf("input map must not be mutated; key1 got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,41 +135,46 @@ func TestTemplateForTenantAndNamespaceMap_ReplacesPlaceholdersSpaces(t *testing.
|
|||||||
tnt := newTenant("tenant-a")
|
tnt := newTenant("tenant-a")
|
||||||
ns := newNamespace("ns-1")
|
ns := newNamespace("ns-1")
|
||||||
|
|
||||||
m := map[string]string{
|
orig := map[string]string{
|
||||||
"key1": "tenant={{ tenant.name }}, ns={{ namespace }}",
|
"key1": "tenant={{ tenant.name }}, ns={{ namespace }}",
|
||||||
"key2": "plain-value",
|
"key2": "plain-value",
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
out := tpl.TemplateForTenantAndNamespaceMap(orig, tnt, ns)
|
||||||
|
|
||||||
if got := m["key1"]; got != "tenant=tenant-a, ns=ns-1" {
|
if got := out["key1"]; got != "tenant=tenant-a, ns=ns-1" {
|
||||||
t.Fatalf("key1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
t.Fatalf("key1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
||||||
}
|
}
|
||||||
|
if got := out["key2"]; got != "plain-value" {
|
||||||
|
t.Fatalf("key2: expected %q, got %q", "plain-value", got)
|
||||||
|
}
|
||||||
|
|
||||||
if got := m["key2"]; got != "plain-value" {
|
// input map must remain unchanged
|
||||||
t.Fatalf("key2: expected %q to remain unchanged, got %q", "plain-value", got)
|
if got := orig["key1"]; got != "tenant={{ tenant.name }}, ns={{ namespace }}" {
|
||||||
|
t.Fatalf("input map must not be mutated; key1 got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateForTenantAndNamespaceMap_SkipsValuesWithoutDelimiters(t *testing.T) {
|
func TestTemplateForTenantAndNamespaceMap_TransformsValuesWithDelimiters(t *testing.T) {
|
||||||
tnt := newTenant("tenant-a")
|
tnt := newTenant("tenant-a")
|
||||||
ns := newNamespace("ns-1")
|
ns := newNamespace("ns-1")
|
||||||
|
|
||||||
// Note: no space after '{{' and before '}}', so the guard should skip it
|
orig := map[string]string{
|
||||||
m := map[string]string{
|
"t1": "hello {{tenant.name}}",
|
||||||
"noTemplate1": "hello {{tenant.name}}",
|
"t2": "namespace {{namespace}}",
|
||||||
"noTemplate2": "namespace {{namespace}}",
|
"t3": "static",
|
||||||
}
|
}
|
||||||
|
|
||||||
original2 := m["noTemplate2"]
|
out := tpl.TemplateForTenantAndNamespaceMap(orig, tnt, ns)
|
||||||
|
|
||||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
if got := out["t1"]; got != "hello tenant-a" {
|
||||||
|
t.Fatalf("t1: expected %q, got %q", "hello tenant-a", got)
|
||||||
if got := m["noTemplate1"]; got != "hello tenant-a" {
|
|
||||||
t.Fatalf("noTemplate1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
|
||||||
}
|
}
|
||||||
if got := m["noTemplate2"]; got != "namespace ns-1" {
|
if got := out["t2"]; got != "namespace ns-1" {
|
||||||
t.Fatalf("noTemplate2: expected %q to remain unchanged, got %q", original2, got)
|
t.Fatalf("t2: expected %q, got %q", "namespace ns-1", got)
|
||||||
|
}
|
||||||
|
if got := out["t3"]; got != "static" {
|
||||||
|
t.Fatalf("t3: expected %q, got %q", "static", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,22 +182,22 @@ func TestTemplateForTenantAndNamespaceMap_MixedKeys(t *testing.T) {
|
|||||||
tnt := newTenant("tenant-x")
|
tnt := newTenant("tenant-x")
|
||||||
ns := newNamespace("ns-x")
|
ns := newNamespace("ns-x")
|
||||||
|
|
||||||
m := map[string]string{
|
orig := map[string]string{
|
||||||
"onlyTenant": "T={{ tenant.name }}",
|
"onlyTenant": "T={{ tenant.name }}",
|
||||||
"onlyNS": "N={{ namespace }}",
|
"onlyNS": "N={{ namespace }}",
|
||||||
"none": "static",
|
"none": "static",
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
out := tpl.TemplateForTenantAndNamespaceMap(orig, tnt, ns)
|
||||||
|
|
||||||
if got := m["onlyTenant"]; got != "T=tenant-x" {
|
if got := out["onlyTenant"]; got != "T=tenant-x" {
|
||||||
t.Fatalf("onlyTenant: expected %q, got %q", "T=tenant-x", got)
|
t.Fatalf("onlyTenant: expected %q, got %q", "T=tenant-x", got)
|
||||||
}
|
}
|
||||||
if got := m["onlyNS"]; got != "N=ns-x" {
|
if got := out["onlyNS"]; got != "N=ns-x" {
|
||||||
t.Fatalf("onlyNS: expected %q, got %q", "N=ns-x", got)
|
t.Fatalf("onlyNS: expected %q, got %q", "N=ns-x", got)
|
||||||
}
|
}
|
||||||
if got := m["none"]; got != "static" {
|
if got := out["none"]; got != "static" {
|
||||||
t.Fatalf("none: expected %q to remain unchanged, got %q", "static", got)
|
t.Fatalf("none: expected %q, got %q", "static", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,14 +205,86 @@ func TestTemplateForTenantAndNamespaceMap_UnknownKeyBecomesEmpty(t *testing.T) {
|
|||||||
tnt := newTenant("tenant-a")
|
tnt := newTenant("tenant-a")
|
||||||
ns := newNamespace("ns-1")
|
ns := newNamespace("ns-1")
|
||||||
|
|
||||||
m := map[string]string{
|
orig := map[string]string{
|
||||||
"unknown": "X={{ unknown.key }}",
|
"unknown": "X={{ unknown.key }}",
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
out := tpl.TemplateForTenantAndNamespaceMap(orig, tnt, ns)
|
||||||
|
|
||||||
// fasttemplate with missing key returns an empty string for that placeholder
|
if got := out["unknown"]; got != "X=" {
|
||||||
if got := m["unknown"]; got != "X=" {
|
|
||||||
t.Fatalf("unknown: expected %q, got %q", "X=", got)
|
t.Fatalf("unknown: expected %q, got %q", "X=", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateForTenantAndNamespaceMap_EmptyOrNilInput(t *testing.T) {
|
||||||
|
tnt := newTenant("tenant-a")
|
||||||
|
ns := newNamespace("ns-1")
|
||||||
|
|
||||||
|
// nil map
|
||||||
|
outNil := tpl.TemplateForTenantAndNamespaceMap(nil, tnt, ns)
|
||||||
|
if outNil == nil {
|
||||||
|
t.Fatalf("expected non-nil map for nil input")
|
||||||
|
}
|
||||||
|
if len(outNil) != 0 {
|
||||||
|
t.Fatalf("expected empty map for nil input, got %v", outNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty map
|
||||||
|
outEmpty := tpl.TemplateForTenantAndNamespaceMap(map[string]string{}, tnt, ns)
|
||||||
|
if outEmpty == nil || len(outEmpty) != 0 {
|
||||||
|
t.Fatalf("expected empty map, got %v", outEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concurrency test: should never panic with "concurrent map writes"
|
||||||
|
// Run with: go test -race ./...
|
||||||
|
func TestTemplateForTenantAndNamespaceMap_Concurrency(t *testing.T) {
|
||||||
|
tnt := newTenant("tenant-a")
|
||||||
|
ns := newNamespace("ns-1")
|
||||||
|
|
||||||
|
// Shared input map across goroutines (this used to be unsafe if the function mutated in-place)
|
||||||
|
shared := map[string]string{
|
||||||
|
"k1": "tenant={{tenant.name}}",
|
||||||
|
"k2": "ns={{namespace}}",
|
||||||
|
"k3": "static",
|
||||||
|
}
|
||||||
|
|
||||||
|
const goroutines = 50
|
||||||
|
const iterations = 200
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(goroutines)
|
||||||
|
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
out := tpl.TemplateForTenantAndNamespaceMap(shared, tnt, ns)
|
||||||
|
|
||||||
|
// sanity checks
|
||||||
|
if out["k1"] != "tenant=tenant-a" {
|
||||||
|
t.Errorf("unexpected k1: %q", out["k1"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out["k2"] != "ns=ns-1" {
|
||||||
|
t.Errorf("unexpected k2: %q", out["k2"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out["k3"] != "static" {
|
||||||
|
t.Errorf("unexpected k3: %q", out["k3"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// verify input map was not mutated
|
||||||
|
if shared["k1"] != "tenant={{tenant.name}}" {
|
||||||
|
t.Fatalf("input map mutated under concurrency: k1=%q", shared["k1"])
|
||||||
|
}
|
||||||
|
if shared["k2"] != "ns={{namespace}}" {
|
||||||
|
t.Fatalf("input map mutated under concurrency: k2=%q", shared["k2"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
337
pkg/utils/tenant/metadata_test.go
Normal file
337
pkg/utils/tenant/metadata_test.go
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
// Copyright 2020-2025 Project Capsule Authors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package tenant_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
||||||
|
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||||
|
"github.com/projectcapsule/capsule/pkg/api"
|
||||||
|
"github.com/projectcapsule/capsule/pkg/api/meta"
|
||||||
|
tenant "github.com/projectcapsule/capsule/pkg/utils/tenant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
func ns(name string, uid types.UID) *corev1.Namespace {
|
||||||
|
n := &corev1.Namespace{}
|
||||||
|
n.SetName(name)
|
||||||
|
n.SetUID(uid)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func tenantWithName(name string) *capsulev1beta2.Tenant {
|
||||||
|
t := &capsulev1beta2.Tenant{}
|
||||||
|
t.SetName(name)
|
||||||
|
if t.Annotations == nil {
|
||||||
|
t.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustInstance(t *capsulev1beta2.Tenant, name string, uid types.UID, labels, ann map[string]string) {
|
||||||
|
// Ensure Status + instance storage exists in your impl;
|
||||||
|
// if TenantStatus needs initialization in your project, do it here.
|
||||||
|
|
||||||
|
item := &capsulev1beta2.TenantStatusNamespaceItem{
|
||||||
|
Name: name,
|
||||||
|
UID: uid,
|
||||||
|
Metadata: &capsulev1beta2.TenantStatusNamespaceMetadata{
|
||||||
|
Labels: labels,
|
||||||
|
Annotations: ann,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Status.UpdateInstance(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tests
|
||||||
|
|
||||||
|
func TestAddNamespaceNameLabels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
labels := map[string]string{"keep": "me"}
|
||||||
|
n := ns("myns", "u1")
|
||||||
|
|
||||||
|
tenant.AddNamespaceNameLabels(labels, n)
|
||||||
|
|
||||||
|
if got := labels["kubernetes.io/metadata.name"]; got != "myns" {
|
||||||
|
t.Fatalf("expected kubernetes.io/metadata.name to be %q, got %q", "myns", got)
|
||||||
|
}
|
||||||
|
if got := labels["keep"]; got != "me" {
|
||||||
|
t.Fatalf("expected existing key to remain, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddTenantNameLabel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
labels := map[string]string{}
|
||||||
|
n := ns("myns", "u1")
|
||||||
|
tt := tenantWithName("mytenant")
|
||||||
|
|
||||||
|
tenant.AddTenantNameLabel(labels, n, tt)
|
||||||
|
|
||||||
|
if got := labels[meta.TenantLabel]; got != "mytenant" {
|
||||||
|
t.Fatalf("expected %s to be %q, got %q", meta.TenantLabel, "mytenant", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildInstanceMetadataForNamespace_NoInstance(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
n := ns("myns", "u1")
|
||||||
|
tt := tenantWithName("t1")
|
||||||
|
|
||||||
|
labels, annotations := tenant.BuildInstanceMetadataForNamespace(n, tt)
|
||||||
|
|
||||||
|
if labels == nil || annotations == nil {
|
||||||
|
t.Fatalf("expected non-nil maps")
|
||||||
|
}
|
||||||
|
if len(labels) != 0 || len(annotations) != 0 {
|
||||||
|
t.Fatalf("expected empty maps, got labels=%v annotations=%v", labels, annotations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildInstanceMetadataForNamespace_WithInstance(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
n := ns("myns", "u1")
|
||||||
|
tt := tenantWithName("t1")
|
||||||
|
|
||||||
|
origLabels := map[string]string{"a": "1"}
|
||||||
|
origAnn := map[string]string{"x": "y"}
|
||||||
|
mustInstance(tt, n.GetName(), n.GetUID(), origLabels, origAnn)
|
||||||
|
|
||||||
|
labels, annotations := tenant.BuildInstanceMetadataForNamespace(n, tt)
|
||||||
|
|
||||||
|
// Implementation returns instance.Metadata maps directly (not cloned)
|
||||||
|
if labels["a"] != "1" || annotations["x"] != "y" {
|
||||||
|
t.Fatalf("unexpected returned metadata: labels=%v annotations=%v", labels, annotations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildNamespaceLabelsForTenant(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("additional labels copied + cordoned", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := tenantWithName("t1")
|
||||||
|
tt.Spec.Cordoned = true
|
||||||
|
tt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
|
||||||
|
//nolint:staticcheck
|
||||||
|
AdditionalMetadata: &api.AdditionalMetadataSpec{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"base": "label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := tenant.BuildNamespaceLabelsForTenant(tt)
|
||||||
|
|
||||||
|
if labels["base"] != "label" {
|
||||||
|
t.Fatalf("expected base label copied, got %v", labels)
|
||||||
|
}
|
||||||
|
if labels[meta.CordonedLabel] != "true" {
|
||||||
|
t.Fatalf("expected cordoned label true, got %v", labels[meta.CordonedLabel])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildNamespaceAnnotationsForTenant(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("copies additional annotations and forwards forbidden annotations", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := tenantWithName("t1")
|
||||||
|
tt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
|
||||||
|
//nolint:staticcheck
|
||||||
|
AdditionalMetadata: &api.AdditionalMetadataSpec{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"a": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.Annotations[meta.ForbiddenNamespaceLabelsAnnotation] = "l1,l2"
|
||||||
|
tt.Annotations[meta.ForbiddenNamespaceAnnotationsAnnotation] = "a1,a2"
|
||||||
|
|
||||||
|
ann := tenant.BuildNamespaceAnnotationsForTenant(tt)
|
||||||
|
|
||||||
|
if ann["a"] != "b" {
|
||||||
|
t.Fatalf("expected additional annotation copied, got %v", ann)
|
||||||
|
}
|
||||||
|
if ann[meta.ForbiddenNamespaceLabelsAnnotation] != "l1,l2" {
|
||||||
|
t.Fatalf("expected forbidden labels annotation forwarded, got %v", ann[meta.ForbiddenNamespaceLabelsAnnotation])
|
||||||
|
}
|
||||||
|
if ann[meta.ForbiddenNamespaceAnnotationsAnnotation] != "a1,a2" {
|
||||||
|
t.Fatalf("expected forbidden annotations forwarded, got %v", ann[meta.ForbiddenNamespaceAnnotationsAnnotation])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ingress/storage/registry exact join", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := tenantWithName("t1")
|
||||||
|
tt.Spec.IngressOptions.AllowedClasses = &api.DefaultAllowedListSpec{
|
||||||
|
SelectorAllowedListSpec: api.SelectorAllowedListSpec{
|
||||||
|
AllowedListSpec: api.AllowedListSpec{
|
||||||
|
Exact: []string{"nginx", "traefik"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Spec.StorageClasses = &api.DefaultAllowedListSpec{
|
||||||
|
SelectorAllowedListSpec: api.SelectorAllowedListSpec{
|
||||||
|
AllowedListSpec: api.AllowedListSpec{
|
||||||
|
Exact: []string{"fast", "slow"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Spec.ContainerRegistries = &api.AllowedListSpec{
|
||||||
|
Exact: []string{"docker.io", "ghcr.io"},
|
||||||
|
}
|
||||||
|
|
||||||
|
ann := tenant.BuildNamespaceAnnotationsForTenant(tt)
|
||||||
|
|
||||||
|
if ann[meta.AvailableIngressClassesAnnotation] != "nginx,traefik" {
|
||||||
|
t.Fatalf("unexpected ingress exact annotation: %v", ann[meta.AvailableIngressClassesAnnotation])
|
||||||
|
}
|
||||||
|
if ann[meta.AvailableStorageClassesAnnotation] != "fast,slow" {
|
||||||
|
t.Fatalf("unexpected storage exact annotation: %v", ann[meta.AvailableStorageClassesAnnotation])
|
||||||
|
}
|
||||||
|
if ann[meta.AllowedRegistriesAnnotation] != "docker.io,ghcr.io" {
|
||||||
|
t.Fatalf("unexpected registries exact annotation: %v", ann[meta.AllowedRegistriesAnnotation])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildNamespaceMetadataForTenant_AppliesAdditionalMetadataAndDoesNotOverwrite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := tenantWithName("tenant-x")
|
||||||
|
|
||||||
|
// Base AdditionalMetadata (staticcheck path)
|
||||||
|
tt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
|
||||||
|
//nolint:staticcheck
|
||||||
|
AdditionalMetadata: &api.AdditionalMetadataSpec{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"base": "keep",
|
||||||
|
"dup": "base-val",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"baseAnn": "keep",
|
||||||
|
"dupAnn": "base-ann",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
|
||||||
|
{
|
||||||
|
// Use an “empty selector” that should match everything in your selector logic.
|
||||||
|
// If your IsNamespaceSelectedBySelector behaves differently, set selector accordingly.
|
||||||
|
NamespaceSelector: nil,
|
||||||
|
Labels: map[string]string{
|
||||||
|
"extra": "{{ tenant.name }}",
|
||||||
|
"dup": "should-not-overwrite",
|
||||||
|
"namespaceKey": "{{ namespace }}",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"extraAnn": "{{ tenant.name }}",
|
||||||
|
"dupAnn": "should-not-overwrite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
n := ns("ns-1", "u1")
|
||||||
|
|
||||||
|
labels, ann, err := tenant.BuildNamespaceMetadataForTenant(n, tt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// templating must apply
|
||||||
|
if labels["extra"] != "tenant-x" {
|
||||||
|
t.Fatalf("expected templated label extra=tenant-x, got %q", labels["extra"])
|
||||||
|
}
|
||||||
|
if labels["namespaceKey"] != "ns-1" {
|
||||||
|
t.Fatalf("expected templated namespaceKey=ns-1, got %q", labels["namespaceKey"])
|
||||||
|
}
|
||||||
|
if ann["extraAnn"] != "tenant-x" {
|
||||||
|
t.Fatalf("expected templated annotation extraAnn=tenant-x, got %q", ann["extraAnn"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapMergeNoOverrite means base wins on duplicates
|
||||||
|
if labels["dup"] != "base-val" {
|
||||||
|
t.Fatalf("expected duplicate label to remain base-val, got %q", labels["dup"])
|
||||||
|
}
|
||||||
|
if ann["dupAnn"] != "base-ann" {
|
||||||
|
t.Fatalf("expected duplicate annotation to remain base-ann, got %q", ann["dupAnn"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// base keys remain
|
||||||
|
if labels["base"] != "keep" || ann["baseAnn"] != "keep" {
|
||||||
|
t.Fatalf("expected base metadata to remain, labels=%v ann=%v", labels, ann)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildNamespaceMetadataForTenant_Concurrency_NoConcurrentMapWrites(t *testing.T) {
|
||||||
|
// Don’t run this test in parallel with other tests if your package has global shared state.
|
||||||
|
// Keep it isolated; this is meant to be run with: go test -race ./...
|
||||||
|
tt := tenantWithName("tenant-race")
|
||||||
|
n := ns("ns-race", "u-race")
|
||||||
|
|
||||||
|
// Critical: reuse the SAME maps inside AdditionalMetadataList across goroutines.
|
||||||
|
// If TemplateForTenantAndNamespaceMap still mutates in-place, this can panic.
|
||||||
|
sharedLabels := map[string]string{
|
||||||
|
"l1": "{{ tenant.name }}",
|
||||||
|
"l2": "{{ namespace }}",
|
||||||
|
}
|
||||||
|
sharedAnn := map[string]string{
|
||||||
|
"a1": "{{ tenant.name }}",
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
|
||||||
|
AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
|
||||||
|
{
|
||||||
|
NamespaceSelector: nil,
|
||||||
|
Labels: sharedLabels,
|
||||||
|
Annotations: sharedAnn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const goroutines = 50
|
||||||
|
const iterations = 200
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(goroutines)
|
||||||
|
|
||||||
|
errCh := make(chan error, goroutines)
|
||||||
|
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
_, _, err := tenant.BuildNamespaceMetadataForTenant(n, tt)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
|
||||||
|
for err := range errCh {
|
||||||
|
t.Fatalf("unexpected error under concurrency: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,11 +59,11 @@ func BuildNamespaceMetadataForTenant(ns *corev1.Namespace, tnt *capsulev1beta2.T
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
template.TemplateForTenantAndNamespaceMap(md.Labels, tnt, ns)
|
tLabels := template.TemplateForTenantAndNamespaceMap(md.Labels, tnt, ns)
|
||||||
template.TemplateForTenantAndNamespaceMap(md.Annotations, tnt, ns)
|
tAnnotations := template.TemplateForTenantAndNamespaceMap(md.Annotations, tnt, ns)
|
||||||
|
|
||||||
utils.MapMergeNoOverrite(labels, md.Labels)
|
utils.MapMergeNoOverrite(labels, tLabels)
|
||||||
utils.MapMergeNoOverrite(annotations, md.Annotations)
|
utils.MapMergeNoOverrite(annotations, tAnnotations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2020-2025 Project Capsule Authors
|
// Copyright 2020-2025 Project Capsule Authors
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package tenant
|
package tenant_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||||
|
tenant "github.com/projectcapsule/capsule/pkg/utils/tenant"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsTenantOwnerReference(t *testing.T) {
|
func TestIsTenantOwnerReference(t *testing.T) {
|
||||||
@@ -23,7 +24,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "valid tenant ownerRef with exact group and version",
|
name: "valid tenant ownerRef with exact group and version",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1beta2",
|
APIVersion: capsuleGroup + "/v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: true,
|
want: true,
|
||||||
@@ -32,7 +33,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "valid tenant ownerRef with same group but different version",
|
name: "valid tenant ownerRef with same group but different version",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1",
|
APIVersion: capsuleGroup + "/v1",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: true, // we intentionally only check the group, not the version
|
want: true, // we intentionally only check the group, not the version
|
||||||
@@ -41,7 +42,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "wrong group",
|
name: "wrong group",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "other.group.io/v1beta2",
|
APIVersion: "other.group.io/v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
@@ -59,7 +60,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "empty APIVersion",
|
name: "empty APIVersion",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "",
|
APIVersion: "",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
@@ -68,7 +69,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "APIVersion without slash (only version)",
|
name: "APIVersion without slash (only version)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "v1beta2",
|
APIVersion: "v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
@@ -77,7 +78,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "APIVersion with empty group",
|
name: "APIVersion with empty group",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "/v1beta2",
|
APIVersion: "/v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
@@ -86,7 +87,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "APIVersion with empty version",
|
name: "APIVersion with empty version",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "",
|
APIVersion: "",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
@@ -95,7 +96,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
name: "APIVersion with extra slash in version (still ok as long as group matches)",
|
name: "APIVersion with extra slash in version (still ok as long as group matches)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1beta2/extra",
|
APIVersion: capsuleGroup + "/v1beta2/extra",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
@@ -114,7 +115,7 @@ func TestIsTenantOwnerReference(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tt := tt // capture
|
tt := tt // capture
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := IsTenantOwnerReference(tt.or)
|
got := tenant.IsTenantOwnerReference(tt.or)
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Fatalf("IsTenantOwnerReference(%+v) = %v, want %v", tt.or, got, tt.want)
|
t.Fatalf("IsTenantOwnerReference(%+v) = %v, want %v", tt.or, got, tt.want)
|
||||||
}
|
}
|
||||||
@@ -135,7 +136,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "valid tenant ownerRef with exact group and version (same tenant)",
|
name: "valid tenant ownerRef with exact group and version (same tenant)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1beta2",
|
APIVersion: capsuleGroup + "/v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -149,7 +150,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "valid tenant ownerRef with exact group and version (different tenant)",
|
name: "valid tenant ownerRef with exact group and version (different tenant)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1beta2",
|
APIVersion: capsuleGroup + "/v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -163,7 +164,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "valid tenant ownerRef with same group but different version (same tenant)",
|
name: "valid tenant ownerRef with same group but different version (same tenant)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1",
|
APIVersion: capsuleGroup + "/v1",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -177,7 +178,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "valid tenant ownerRef with same group but different version (different tenant)",
|
name: "valid tenant ownerRef with same group but different version (different tenant)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1",
|
APIVersion: capsuleGroup + "/v1",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -191,7 +192,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "wrong group",
|
name: "wrong group",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "other.group.io/v1beta2",
|
APIVersion: "other.group.io/v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -219,7 +220,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "empty APIVersion",
|
name: "empty APIVersion",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "",
|
APIVersion: "",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -233,7 +234,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "empty tenant",
|
name: "empty tenant",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1",
|
APIVersion: capsuleGroup + "/v1",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: nil,
|
tenant: nil,
|
||||||
@@ -243,7 +244,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "APIVersion without slash (only version)",
|
name: "APIVersion without slash (only version)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "v1beta2",
|
APIVersion: "v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -257,7 +258,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "APIVersion with empty group",
|
name: "APIVersion with empty group",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "/v1beta2",
|
APIVersion: "/v1beta2",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -271,7 +272,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "APIVersion with empty version",
|
name: "APIVersion with empty version",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: "",
|
APIVersion: "",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -285,7 +286,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
name: "APIVersion with extra slash in version (still ok as long as group matches)",
|
name: "APIVersion with extra slash in version (still ok as long as group matches)",
|
||||||
or: metav1.OwnerReference{
|
or: metav1.OwnerReference{
|
||||||
APIVersion: capsuleGroup + "/v1beta2/extra",
|
APIVersion: capsuleGroup + "/v1beta2/extra",
|
||||||
Kind: ObjectReferenceTenantKind,
|
Kind: tenant.ObjectReferenceTenantKind,
|
||||||
Name: "my-tenant",
|
Name: "my-tenant",
|
||||||
},
|
},
|
||||||
tenant: &capsulev1beta2.Tenant{
|
tenant: &capsulev1beta2.Tenant{
|
||||||
@@ -314,7 +315,7 @@ func TestIsTenantOwnerReferenceForTenant(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tt := tt // capture
|
tt := tt // capture
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := IsTenantOwnerReferenceForTenant(tt.or, tt.tenant)
|
got := tenant.IsTenantOwnerReferenceForTenant(tt.or, tt.tenant)
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Fatalf("IsTenantOwnerReference(%+v) = %v, want %v", tt.or, got, tt.want)
|
t.Fatalf("IsTenantOwnerReference(%+v) = %v, want %v", tt.or, got, tt.want)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user