mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-19 04:26:45 +00:00
* feat: implement performance optimizations --------- Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
255 lines
8.6 KiB
Go
255 lines
8.6 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package tenant_test
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
|
"github.com/projectcapsule/capsule/pkg/api/meta"
|
|
"github.com/projectcapsule/capsule/pkg/api/rbac"
|
|
"github.com/projectcapsule/capsule/pkg/runtime/sanitize"
|
|
"github.com/projectcapsule/capsule/pkg/tenant"
|
|
"github.com/projectcapsule/capsule/pkg/users"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
)
|
|
|
|
func TestNamespaceTerminationHelpers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
cl := tenantFakeClient(t,
|
|
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "busy", Name: "pod-a"}},
|
|
)
|
|
|
|
pending, err := tenant.NamespaceIsPendingPodTerminating(ctx, cl, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "busy"}})
|
|
if err != nil {
|
|
t.Fatalf("NamespaceIsPendingPodTerminating() unexpected error: %v", err)
|
|
}
|
|
if !pending {
|
|
t.Fatalf("NamespaceIsPendingPodTerminating() = false, want true")
|
|
}
|
|
|
|
pending, err = tenant.NamespaceIsPendingPodTerminating(ctx, cl, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "empty"}})
|
|
if err != nil {
|
|
t.Fatalf("NamespaceIsPendingPodTerminating(empty) unexpected error: %v", err)
|
|
}
|
|
if pending {
|
|
t.Fatalf("NamespaceIsPendingPodTerminating(empty) = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestNamespaceIsPendingUnmanagedTerminationByStatus(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
tnt := tenantObject("tenant-a", withUID(types.UID("tenant-uid")))
|
|
tnt.Status.UpdateInstance(&capsulev1beta2.TenantStatusNamespaceItem{
|
|
Name: "tenant-a-ns",
|
|
Conditions: meta.ConditionList{{
|
|
Type: meta.TerminatingCondition,
|
|
Status: metav1.ConditionTrue,
|
|
}},
|
|
})
|
|
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
|
Name: "tenant-a-ns",
|
|
OwnerReferences: []metav1.OwnerReference{tenantOwnerReference("tenant-a", types.UID("tenant-uid"))},
|
|
}}
|
|
cl := tenantFakeClient(t, tnt, ns)
|
|
|
|
pending, err := tenant.NamespaceIsPendingUnmanagedTerminationByStatus(ctx, cl, ns)
|
|
if err != nil {
|
|
t.Fatalf("NamespaceIsPendingUnmanagedTerminationByStatus() unexpected error: %v", err)
|
|
}
|
|
if !pending {
|
|
t.Fatalf("NamespaceIsPendingUnmanagedTerminationByStatus() = false, want true")
|
|
}
|
|
|
|
pending, err = tenant.NamespaceIsPendingUnmanagedTerminationByStatus(ctx, cl, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "unowned"}})
|
|
if err != nil {
|
|
t.Fatalf("NamespaceIsPendingUnmanagedTerminationByStatus(unowned) unexpected error: %v", err)
|
|
}
|
|
if pending {
|
|
t.Fatalf("NamespaceIsPendingUnmanagedTerminationByStatus(unowned) = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestResolveNamespaceTenant(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
tnt := tenantObject("tenant-a", withUID(types.UID("tenant-uid")))
|
|
cl := tenantFakeClient(t, tnt)
|
|
|
|
valid := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
|
Name: "tenant-a-ns",
|
|
Labels: map[string]string{meta.TenantLabel: "tenant-a"},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
tenantOwnerReference("tenant-a", types.UID("tenant-uid")),
|
|
},
|
|
}}
|
|
got, err := tenant.ResolveNamespaceTenant(ctx, cl, valid)
|
|
if err != nil {
|
|
t.Fatalf("ResolveNamespaceTenant() unexpected error: %v", err)
|
|
}
|
|
if got == nil || got.Name != "tenant-a" {
|
|
t.Fatalf("ResolveNamespaceTenant() = %#v, want tenant-a", got)
|
|
}
|
|
|
|
if got, err := tenant.ResolveNamespaceTenant(ctx, cl, nil); err != nil || got != nil {
|
|
t.Fatalf("ResolveNamespaceTenant(nil) = %#v, %v, want nil nil", got, err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
ns *corev1.Namespace
|
|
want string
|
|
}{
|
|
{
|
|
name: "label without owner reference",
|
|
ns: &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{meta.TenantLabel: "tenant-a"},
|
|
}},
|
|
want: "no Tenant ownerReference",
|
|
},
|
|
{
|
|
name: "owner reference without label",
|
|
ns: &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
|
OwnerReferences: []metav1.OwnerReference{tenantOwnerReference("tenant-a", types.UID("tenant-uid"))},
|
|
}},
|
|
want: "but no tenant label",
|
|
},
|
|
{
|
|
name: "mismatched label",
|
|
ns: &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{meta.TenantLabel: "other"},
|
|
OwnerReferences: []metav1.OwnerReference{tenantOwnerReference("tenant-a", types.UID("tenant-uid"))},
|
|
}},
|
|
want: "does not match owner reference",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
_, err := tenant.ResolveNamespaceTenant(ctx, cl, tt.ns)
|
|
if err == nil || !strings.Contains(err.Error(), tt.want) {
|
|
t.Fatalf("%s error = %v, want substring %q", tt.name, err, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCollectTenantNamespaceByLabel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
cl := tenantFakeClient(t,
|
|
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "a", Labels: map[string]string{meta.TenantLabel: "tenant-a", "env": "prod"}}},
|
|
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "b", Labels: map[string]string{meta.TenantLabel: "tenant-a", "env": "dev"}}},
|
|
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "c", Labels: map[string]string{meta.TenantLabel: "tenant-b", "env": "prod"}}},
|
|
)
|
|
|
|
namespaces, err := tenant.CollectTenantNamespaceByLabel(ctx, cl, *tenantObject("tenant-a"), &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{"env": "prod"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CollectTenantNamespaceByLabel() unexpected error: %v", err)
|
|
}
|
|
if names := collectedNamespaceNames(namespaces); !reflect.DeepEqual(names, []string{"a"}) {
|
|
t.Fatalf("CollectTenantNamespaceByLabel() = %#v, want namespace a", names)
|
|
}
|
|
}
|
|
|
|
func TestNamespaceOwnershipAndContexts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tnt := tenantObject("tenant-a", withUID(types.UID("tenant-uid")), withStatusOwner(rbac.UserOwner, "alice"))
|
|
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
|
Name: "tenant-a-ns",
|
|
OwnerReferences: []metav1.OwnerReference{tenantOwnerReference("tenant-a", types.UID("tenant-uid"))},
|
|
}}
|
|
|
|
if !tenant.NamespaceIsOwned(context.Background(), nil, nil, ns, tnt, users.AdmissionUser{Username: "alice"}) {
|
|
t.Fatalf("NamespaceIsOwned() = false, want true for status owner")
|
|
}
|
|
if tenant.NamespaceIsOwned(context.Background(), nil, nil, ns, tnt, users.AdmissionUser{Username: "bob"}) {
|
|
t.Fatalf("NamespaceIsOwned() = true, want false for non-owner")
|
|
}
|
|
|
|
scheme := runtime.NewScheme()
|
|
if err := corev1.AddToScheme(scheme); err != nil {
|
|
t.Fatalf("adding core scheme: %v", err)
|
|
}
|
|
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
|
|
t.Fatalf("adding capsule scheme: %v", err)
|
|
}
|
|
|
|
ctx, err := tenant.NewTenantNamespaceContext(tnt, ns, scheme, sanitize.DefaultSanitizeOptions())
|
|
if err != nil {
|
|
t.Fatalf("NewTenantNamespaceContext() unexpected error: %v", err)
|
|
}
|
|
if ctx["tenant"] == nil || ctx["namespace"] == nil {
|
|
t.Fatalf("NewTenantNamespaceContext() = %#v, want tenant and namespace contexts", ctx)
|
|
}
|
|
|
|
fast := tenant.FastContextForTenantAndNamespace(tnt, ns)
|
|
if !reflect.DeepEqual(fast, map[string]string{"tenant.name": "tenant-a", "namespace": "tenant-a-ns"}) {
|
|
t.Fatalf("FastContextForTenantAndNamespace() = %#v", fast)
|
|
}
|
|
}
|
|
|
|
func TestTenantNamespaceContextIncludesStatusWhenRetained(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scheme := runtime.NewScheme()
|
|
if err := corev1.AddToScheme(scheme); err != nil {
|
|
t.Fatalf("adding core scheme: %v", err)
|
|
}
|
|
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
|
|
t.Fatalf("adding Capsule scheme: %v", err)
|
|
}
|
|
|
|
tnt := &capsulev1beta2.Tenant{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "tenant-a"},
|
|
Status: capsulev1beta2.TenantStatus{State: capsulev1beta2.TenantStateActive},
|
|
}
|
|
ns := &corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "tenant-a-ns"},
|
|
Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive},
|
|
}
|
|
|
|
opts := sanitize.DefaultSanitizeOptions()
|
|
opts.StripStatus = false
|
|
|
|
ctx, err := tenant.NewTenantNamespaceContext(tnt, ns, scheme, opts)
|
|
if err != nil {
|
|
t.Fatalf("NewTenantNamespaceContext() unexpected error: %v", err)
|
|
}
|
|
|
|
tenantContext := ctx["tenant"].(map[string]any)
|
|
tenantStatus := tenantContext["status"].(map[string]any)
|
|
if tenantStatus["state"] != string(capsulev1beta2.TenantStateActive) {
|
|
t.Fatalf("tenant status state = %#v, want Active", tenantStatus["state"])
|
|
}
|
|
|
|
namespaceContext := ctx["namespace"].(map[string]any)
|
|
namespaceStatus := namespaceContext["status"].(map[string]any)
|
|
if namespaceStatus["phase"] != string(corev1.NamespaceActive) {
|
|
t.Fatalf("namespace status phase = %#v, want Active", namespaceStatus["phase"])
|
|
}
|
|
}
|
|
|
|
func collectedNamespaceNames(namespaces []corev1.Namespace) []string {
|
|
names := make([]string, 0, len(namespaces))
|
|
for _, ns := range namespaces {
|
|
names = append(names, ns.Name)
|
|
}
|
|
|
|
return names
|
|
}
|