Files
Oliver BählerandGitHub 2252c530f4 feat(performance): removed duplicate client calls from all admission paths (#2054)
* chore

* perfromance improvements

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* perfromance improvements

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(performance): removed duplicate client calls from all admission paths

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-07-28 14:32:42 +02:00

928 lines
26 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package customquota
import (
"context"
"strings"
"testing"
"time"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/internal/cache"
"github.com/projectcapsule/capsule/pkg/api/meta"
caprunt "github.com/projectcapsule/capsule/pkg/api/runtime"
"github.com/projectcapsule/capsule/pkg/runtime/quota"
"github.com/projectcapsule/capsule/pkg/runtime/selectors"
)
func TestReserveCreateOnLedgerDryRunDoesNotMutate(t *testing.T) {
t.Parallel()
ctx := context.Background()
key := types.NamespacedName{Namespace: "tenant-a", Name: "pods"}
ledger := ledgerForTest(key, "2")
cl := ledgerClientForTest(t, ledger)
usage := resource.MustParse("1")
reservation := capsulev1beta2.QuantityLedgerReservation{
ID: "dry-run",
Usage: usage.DeepCopy(),
Delta: quantityPtr(usage),
}
allowed, effective, _, err := reserveCreateOnLedger(
ctx,
cl,
cl,
evaluatedQuota{MatchedQuota: quota.MatchedQuota{
Name: key.Name,
Namespace: key.Namespace,
Limit: resource.MustParse("3"),
}},
&reservation,
true,
)
if err != nil {
t.Fatalf("reserveCreateOnLedger() error = %v", err)
}
if !allowed {
t.Fatal("reserveCreateOnLedger() dry-run unexpectedly denied")
}
if effective.Cmp(resource.MustParse("3")) != 0 {
t.Fatalf("effective allocation = %s, want 3", effective.String())
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if got.Status.Allocated.Cmp(resource.MustParse("2")) != 0 {
t.Fatalf("persisted allocation = %s, want 2", got.Status.Allocated.String())
}
if len(got.Status.Reservations) != 0 {
t.Fatalf("persisted reservations = %d, want 0", len(got.Status.Reservations))
}
}
func TestReserveCreateOnLedgerReleasesExpiredReservation(t *testing.T) {
t.Parallel()
ctx := context.Background()
key := types.NamespacedName{Namespace: "tenant-a", Name: "pods"}
ledger := ledgerForTest(key, "2")
expiredAt := metav1.NewTime(time.Now().Add(-time.Minute))
expiredUsage := resource.MustParse("1")
ledger.Status.Reserved = expiredUsage.DeepCopy()
ledger.Status.Reservations = []capsulev1beta2.QuantityLedgerReservation{
{
ID: "expired",
Usage: expiredUsage.DeepCopy(),
Delta: quantityPtr(expiredUsage),
ExpiresAt: &expiredAt,
},
}
cl := ledgerClientForTest(t, ledger)
usage := resource.MustParse("1")
reservation := capsulev1beta2.QuantityLedgerReservation{
ID: "current",
Usage: usage.DeepCopy(),
Delta: quantityPtr(usage),
}
allowed, effective, _, err := reserveCreateOnLedger(
ctx,
cl,
cl,
evaluatedQuota{MatchedQuota: quota.MatchedQuota{
Name: key.Name,
Namespace: key.Namespace,
Limit: resource.MustParse("2"),
}},
&reservation,
false,
)
if err != nil {
t.Fatalf("reserveCreateOnLedger() error = %v", err)
}
if !allowed {
t.Fatal("reserveCreateOnLedger() denied after an expired reservation")
}
if effective.Cmp(resource.MustParse("2")) != 0 {
t.Fatalf("effective allocation = %s, want 2", effective.String())
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if len(got.Status.Reservations) != 1 || got.Status.Reservations[0].ID != "current" {
t.Fatalf("active reservations = %#v, want only current", got.Status.Reservations)
}
}
func TestReplaceUsageOnLedgerDoesNotReleaseDecreaseBeforePersistence(t *testing.T) {
t.Parallel()
ctx := context.Background()
key := types.NamespacedName{Namespace: "tenant-a", Name: "cpu"}
ledger := ledgerForTest(key, "10")
cl := ledgerClientForTest(t, ledger)
newUsage := resource.MustParse("1")
zero := resource.MustParse("0")
reservation := capsulev1beta2.QuantityLedgerReservation{
ID: "decrease",
Usage: newUsage.DeepCopy(),
Delta: quantityPtr(zero),
}
allowed, _, _, err := replaceUsageOnLedger(
ctx,
cl,
cl,
evaluatedQuota{MatchedQuota: quota.MatchedQuota{
Name: key.Name,
Namespace: key.Namespace,
Limit: resource.MustParse("10"),
}},
resource.MustParse("10"),
newUsage,
&reservation,
nil,
true,
false,
)
if err != nil {
t.Fatalf("replaceUsageOnLedger() error = %v", err)
}
if !allowed {
t.Fatal("replaceUsageOnLedger() unexpectedly denied a decrease")
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if got.Status.Allocated.Cmp(resource.MustParse("10")) != 0 {
t.Fatalf("allocation was released before persistence: got %s, want 10", got.Status.Allocated.String())
}
if len(got.Status.Reservations) != 1 {
t.Fatalf("reservations = %d, want 1", len(got.Status.Reservations))
}
if delta := reservationDelta(got.Status.Reservations[0]); !delta.IsZero() {
t.Fatalf("decrease reservation delta = %s, want 0", delta.String())
}
}
func TestReplaceUsageOnLedgerReservesOnlyPositiveDelta(t *testing.T) {
t.Parallel()
ctx := context.Background()
key := types.NamespacedName{Namespace: "tenant-a", Name: "cpu"}
ledger := ledgerForTest(key, "5")
cl := ledgerClientForTest(t, ledger)
newUsage := resource.MustParse("8")
delta := resource.MustParse("3")
reservation := capsulev1beta2.QuantityLedgerReservation{
ID: "increase",
Usage: newUsage.DeepCopy(),
Delta: quantityPtr(delta),
}
allowed, _, _, err := replaceUsageOnLedger(
ctx,
cl,
cl,
evaluatedQuota{MatchedQuota: quota.MatchedQuota{
Name: key.Name,
Namespace: key.Namespace,
Limit: resource.MustParse("8"),
}},
resource.MustParse("5"),
newUsage,
&reservation,
nil,
true,
false,
)
if err != nil {
t.Fatalf("replaceUsageOnLedger() error = %v", err)
}
if !allowed {
t.Fatal("replaceUsageOnLedger() unexpectedly denied")
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if got.Status.Allocated.Cmp(resource.MustParse("8")) != 0 {
t.Fatalf("allocation = %s, want 8", got.Status.Allocated.String())
}
if got.Status.Reserved.Cmp(delta) != 0 {
t.Fatalf("reserved = %s, want 3", got.Status.Reserved.String())
}
}
func TestRollbackUsageReplacementRemovesPendingDelete(t *testing.T) {
t.Parallel()
ctx := context.Background()
key := types.NamespacedName{Namespace: "tenant-a", Name: "cpu"}
ref := capsulev1beta2.QuantityLedgerObjectRef{
APIVersion: "v1",
Kind: "Pod",
Namespace: "tenant-a",
Name: "pod-a",
UID: "pod-uid",
}
ledger := ledgerForTest(key, "10")
ledger.Status.PendingDeletes = []capsulev1beta2.QuantityLedgerPendingDelete{
{ID: "request-a", ObjectRef: ref, CreatedAt: metav1.Now()},
{ID: "request-b", ObjectRef: ref, CreatedAt: metav1.Now()},
}
cl := ledgerClientForTest(t, ledger)
pendingDelete := &capsulev1beta2.QuantityLedgerPendingDelete{
ID: "request-a",
ObjectRef: ref,
}
if err := rollbackUsageReplacementOnLedger(
ctx,
cl,
cl,
key,
"",
resource.MustParse("10"),
resource.MustParse("1"),
pendingDelete,
); err != nil {
t.Fatalf("rollbackUsageReplacementOnLedger() error = %v", err)
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if len(got.Status.PendingDeletes) != 1 {
t.Fatalf("pending deletes = %d, want 1", len(got.Status.PendingDeletes))
}
if got.Status.PendingDeletes[0].ID != "request-b" {
t.Fatalf("remaining pending delete = %q, want request-b", got.Status.PendingDeletes[0].ID)
}
if got.Status.Allocated.Cmp(resource.MustParse("10")) != 0 {
t.Fatalf("allocation = %s, want 10", got.Status.Allocated.String())
}
}
func TestReservationDeltaBackwardsCompatibility(t *testing.T) {
t.Parallel()
usage := resource.MustParse("4")
got := reservationDelta(capsulev1beta2.QuantityLedgerReservation{Usage: usage})
if got.Cmp(usage) != 0 {
t.Fatalf("legacy reservation delta = %s, want %s", got.String(), usage.String())
}
}
func TestStatusSubresourceUpdateQueuesQuotaReconciliation(t *testing.T) {
t.Parallel()
ctx := context.Background()
scheme := runtime.NewScheme()
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
t.Fatalf("add Capsule scheme: %v", err)
}
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("add core scheme: %v", err)
}
key := types.NamespacedName{Namespace: "tenant-a", Name: "active-pod-cpu"}
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: key.Namespace},
}
customQuota := &capsulev1beta2.CustomQuota{
ObjectMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
Generation: 1,
},
Spec: capsulev1beta2.CustomQuotaSpec{
Limit: resource.MustParse("2"),
Sources: []capsulev1beta2.CustomQuotaSpecSource{
{
VersionKind: caprunt.VersionKind{APIVersion: "v1", Kind: "Pod"},
CustomQuotaSpecSourceConfig: capsulev1beta2.CustomQuotaSpecSourceConfig{
Operation: quota.OpAdd,
Path: ".spec.containers[*].resources.limits.cpu",
Selectors: []selectors.SelectorWithFields{
{
FieldSelectors: []string{
".status.phase!=Succeeded",
".status.phase!=Failed",
".status.phase!=Unknown",
},
},
},
},
},
},
},
Status: capsulev1beta2.CustomQuotaStatus{
ObservedGeneration: 1,
Usage: capsulev1beta2.CustomQuotaStatusUsage{
Used: resource.MustParse("2"),
Available: resource.MustParse("0"),
},
Conditions: meta.ConditionList{
{Type: meta.ReadyCondition, Status: metav1.ConditionTrue},
},
},
}
ledger := ledgerForTest(key, "2")
cl := fake.NewClientBuilder().
WithScheme(scheme).
WithStatusSubresource(&capsulev1beta2.QuantityLedger{}).
WithObjects(namespace, customQuota, ledger).
Build()
oldPod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "completed",
Namespace: key.Namespace,
UID: "pod-uid",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "main",
Resources: corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1"),
},
},
},
},
},
Status: corev1.PodStatus{Phase: corev1.PodRunning},
}
newPod := oldPod.DeepCopy()
newPod.Status.Phase = corev1.PodSucceeded
handler := &objectCalculationHandler{
targetsCache: cache.NewCompiledTargetsCache[string](),
jsonPathCache: cache.NewJSONPathCache(),
}
req := admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
UID: "status-request",
Operation: admissionv1.Update,
Kind: metav1.GroupVersionKind{Version: "v1", Kind: "Pod"},
Namespace: key.Namespace,
Name: oldPod.Name,
SubResource: "status",
OldObject: runtime.RawExtension{Object: oldPod},
Object: runtime.RawExtension{Object: newPod},
}}
if resp := handler.OnUpdate(cl, cl, nil, nil)(ctx, req); resp != nil {
t.Fatalf("status update response = %#v, want allowed", resp)
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if len(got.Status.PendingDeletes) != 1 {
t.Fatalf("pending deletes = %d, want 1", len(got.Status.PendingDeletes))
}
if got.Status.PendingDeletes[0].ObjectRef.UID != oldPod.UID {
t.Fatalf(
"pending delete UID = %q, want %q",
got.Status.PendingDeletes[0].ObjectRef.UID,
oldPod.UID,
)
}
}
func TestStatusSubresourceUpdateSkipsNotReadyQuota(t *testing.T) {
t.Parallel()
ctx := context.Background()
scheme := runtime.NewScheme()
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
t.Fatalf("add Capsule scheme: %v", err)
}
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("add core scheme: %v", err)
}
namespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "tenant-a"}}
customQuota := &capsulev1beta2.CustomQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "not-ready",
Namespace: namespace.Name,
Generation: 1,
},
Spec: capsulev1beta2.CustomQuotaSpec{
Limit: resource.MustParse("1"),
Sources: []capsulev1beta2.CustomQuotaSpecSource{
{
VersionKind: caprunt.VersionKind{APIVersion: "v1", Kind: "Pod"},
CustomQuotaSpecSourceConfig: capsulev1beta2.CustomQuotaSpecSourceConfig{
Operation: quota.OpCount,
},
},
},
},
Status: capsulev1beta2.CustomQuotaStatus{
ObservedGeneration: 1,
Conditions: meta.ConditionList{
{Type: meta.ReadyCondition, Status: metav1.ConditionFalse},
},
},
}
cl := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(namespace, customQuota).
Build()
oldPod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-a", Namespace: namespace.Name, UID: "pod-uid"},
Status: corev1.PodStatus{Phase: corev1.PodPending},
}
newPod := oldPod.DeepCopy()
newPod.Status.Phase = corev1.PodRunning
handler := &objectCalculationHandler{
targetsCache: cache.NewCompiledTargetsCache[string](),
jsonPathCache: cache.NewJSONPathCache(),
}
req := statusUpdateRequest("not-ready-status", oldPod, newPod)
if resp := handler.OnUpdate(cl, cl, nil, nil)(ctx, req); resp != nil {
t.Fatalf("status update response = %#v, want fail-open allow", resp)
}
}
func TestStatusSubresourceUpdateUsesOnePolicySnapshot(t *testing.T) {
t.Parallel()
ctx := context.Background()
scheme := runtime.NewScheme()
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
t.Fatalf("add Capsule scheme: %v", err)
}
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("add core scheme: %v", err)
}
key := types.NamespacedName{Namespace: "tenant-a", Name: "tracked-pods"}
namespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: key.Namespace}}
customQuota := &capsulev1beta2.CustomQuota{
ObjectMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
Generation: 1,
},
Spec: capsulev1beta2.CustomQuotaSpec{
Limit: resource.MustParse("10"),
Sources: []capsulev1beta2.CustomQuotaSpecSource{
{
VersionKind: caprunt.VersionKind{APIVersion: "v1", Kind: "Pod"},
CustomQuotaSpecSourceConfig: capsulev1beta2.CustomQuotaSpecSourceConfig{
Operation: quota.OpCount,
Selectors: []selectors.SelectorWithFields{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"track": "yes"},
},
},
},
},
},
},
},
Status: capsulev1beta2.CustomQuotaStatus{
ObservedGeneration: 1,
Usage: capsulev1beta2.CustomQuotaStatusUsage{
Used: resource.MustParse("1"),
Available: resource.MustParse("9"),
},
Conditions: meta.ConditionList{
{Type: meta.ReadyCondition, Status: metav1.ConditionTrue},
},
},
}
ledger := ledgerForTest(key, "1")
cl := fake.NewClientBuilder().
WithScheme(scheme).
WithStatusSubresource(&capsulev1beta2.QuantityLedger{}).
WithObjects(namespace, customQuota, ledger).
Build()
reader := &readinessFlappingReader{Reader: cl}
oldPod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-a",
Namespace: key.Namespace,
UID: "pod-uid",
Labels: map[string]string{"track": "yes"},
},
Status: corev1.PodStatus{Phase: corev1.PodPending},
}
newPod := oldPod.DeepCopy()
newPod.Status.Phase = corev1.PodRunning
handler := &objectCalculationHandler{
targetsCache: cache.NewCompiledTargetsCache[string](),
jsonPathCache: cache.NewJSONPathCache(),
}
req := statusUpdateRequest("unchanged-label-status", oldPod, newPod)
if resp := handler.OnUpdate(cl, reader, nil, nil)(ctx, req); resp != nil {
t.Fatalf("status update response = %#v, want allowed", resp)
}
if reader.customQuotaListCalls != 1 {
t.Fatalf(
"CustomQuota policy list calls = %d, want one immutable snapshot for old and new objects",
reader.customQuotaListCalls,
)
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if len(got.Status.Reservations) != 0 || len(got.Status.PendingDeletes) != 0 {
t.Fatalf(
"unchanged matching status update queued ledger work: reservations=%+v pendingDeletes=%+v",
got.Status.Reservations,
got.Status.PendingDeletes,
)
}
}
func TestStatusSubresourceIncreaseQueuesZeroDeltaWithoutEnforcement(t *testing.T) {
t.Parallel()
ctx := context.Background()
scheme := runtime.NewScheme()
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
t.Fatalf("add Capsule scheme: %v", err)
}
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("add core scheme: %v", err)
}
key := types.NamespacedName{Namespace: "tenant-a", Name: "running-pods"}
namespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: key.Namespace}}
customQuota := &capsulev1beta2.CustomQuota{
ObjectMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
Generation: 1,
},
Spec: capsulev1beta2.CustomQuotaSpec{
Limit: resource.MustParse("2"),
Sources: []capsulev1beta2.CustomQuotaSpecSource{
{
VersionKind: caprunt.VersionKind{APIVersion: "v1", Kind: "Pod"},
CustomQuotaSpecSourceConfig: capsulev1beta2.CustomQuotaSpecSourceConfig{
Operation: quota.OpCount,
Selectors: []selectors.SelectorWithFields{
{FieldSelectors: []string{".status.phase=Running"}},
},
},
},
},
},
Status: capsulev1beta2.CustomQuotaStatus{
ObservedGeneration: 1,
Usage: capsulev1beta2.CustomQuotaStatusUsage{
Used: resource.MustParse("2"),
Available: resource.MustParse("0"),
},
Conditions: meta.ConditionList{
{Type: meta.ReadyCondition, Status: metav1.ConditionTrue},
},
},
}
ledger := ledgerForTest(key, "2")
cl := fake.NewClientBuilder().
WithScheme(scheme).
WithStatusSubresource(&capsulev1beta2.QuantityLedger{}).
WithObjects(namespace, customQuota, ledger).
Build()
oldPod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod-a", Namespace: key.Namespace, UID: "pod-uid"},
Status: corev1.PodStatus{Phase: corev1.PodPending},
}
newPod := oldPod.DeepCopy()
newPod.Status.Phase = corev1.PodRunning
handler := &objectCalculationHandler{
targetsCache: cache.NewCompiledTargetsCache[string](),
jsonPathCache: cache.NewJSONPathCache(),
}
req := statusUpdateRequest("increase-status", oldPod, newPod)
if resp := handler.OnUpdate(cl, cl, nil, nil)(ctx, req); resp != nil {
t.Fatalf("status update response = %#v, want allowed beyond quota limit", resp)
}
got := &capsulev1beta2.QuantityLedger{}
if err := cl.Get(ctx, key, got); err != nil {
t.Fatalf("get ledger: %v", err)
}
if len(got.Status.Reservations) != 1 {
t.Fatalf("reservations = %d, want one reconciliation notification", len(got.Status.Reservations))
}
if delta := reservationDelta(got.Status.Reservations[0]); !delta.IsZero() {
t.Fatalf("status notification delta = %s, want 0", delta.String())
}
if got.Status.Allocated.Cmp(resource.MustParse("2")) != 0 {
t.Fatalf("allocated = %s, want unchanged value 2", got.Status.Allocated.String())
}
}
func TestTerminatingNamespaceBypassesQuotaProcessing(t *testing.T) {
t.Parallel()
ctx := context.Background()
scheme := runtime.NewScheme()
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("add core scheme: %v", err)
}
now := metav1.Now()
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "terminating",
DeletionTimestamp: &now,
Finalizers: []string{"kubernetes"},
},
}
cl := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(namespace).
Build()
handler := &objectCalculationHandler{
targetsCache: cache.NewCompiledTargetsCache[string](),
jsonPathCache: cache.NewJSONPathCache(),
}
req := admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
UID: "terminating-status",
Operation: admissionv1.Update,
Kind: metav1.GroupVersionKind{Version: "v1", Kind: "Pod"},
Namespace: namespace.Name,
Name: "pod-a",
SubResource: "status",
}}
if resp := handler.OnUpdate(cl, cl, nil, nil)(ctx, req); resp != nil {
t.Fatalf("terminating namespace status response = %#v, want allowed", resp)
}
req.Operation = admissionv1.Delete
req.SubResource = ""
if resp := handler.OnDelete(cl, cl, nil, nil)(ctx, req); resp != nil {
t.Fatalf("terminating namespace delete response = %#v, want allowed", resp)
}
}
func TestCustomQuotaReadyForAdmissionRequiresCurrentGeneration(t *testing.T) {
t.Parallel()
status := capsulev1beta2.CustomQuotaStatus{
ObservedGeneration: 3,
Conditions: meta.ConditionList{
{
Type: meta.ReadyCondition,
Status: metav1.ConditionTrue,
},
},
}
if !customQuotaReadyForAdmission(3, status) {
t.Fatal("current ready generation must be active")
}
if customQuotaReadyForAdmission(4, status) {
t.Fatal("stale ready status must not activate a new generation")
}
status.Conditions[0].Status = metav1.ConditionFalse
if customQuotaReadyForAdmission(3, status) {
t.Fatal("non-ready quota must not be active")
}
}
func TestMatchAllQuotasFailsClosedUntilCurrentGenerationIsReady(t *testing.T) {
t.Parallel()
ctx := context.Background()
scheme := runtime.NewScheme()
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
t.Fatalf("add Capsule scheme: %v", err)
}
customQuota := &capsulev1beta2.CustomQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "pods",
Namespace: "tenant-a",
Generation: 2,
},
Spec: capsulev1beta2.CustomQuotaSpec{
Limit: resource.MustParse("10"),
Sources: []capsulev1beta2.CustomQuotaSpecSource{
{
VersionKind: caprunt.VersionKind{APIVersion: "v1", Kind: "Pod"},
CustomQuotaSpecSourceConfig: capsulev1beta2.CustomQuotaSpecSourceConfig{
Operation: quota.OpCount,
},
},
},
},
Status: capsulev1beta2.CustomQuotaStatus{
ObservedGeneration: 1,
Conditions: meta.ConditionList{
{Type: meta.ReadyCondition, Status: metav1.ConditionTrue},
},
},
}
cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(customQuota).Build()
handler := &objectCalculationHandler{
targetsCache: cache.NewCompiledTargetsCache[string](),
jsonPathCache: cache.NewJSONPathCache(),
}
request := admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
Namespace: "tenant-a",
Kind: metav1.GroupVersionKind{Version: "v1", Kind: "Pod"},
}}
object := unstructured.Unstructured{}
object.SetAPIVersion("v1")
object.SetKind("Pod")
object.SetNamespace("tenant-a")
object.SetName("pod-a")
_, err := handler.matchAllQuotas(ctx, cl, request, object)
if err == nil || !strings.Contains(err.Error(), "not ready for generation 2") {
t.Fatalf("matchAllQuotas() error = %v, want current-generation readiness error", err)
}
current := &capsulev1beta2.CustomQuota{}
if err := cl.Get(ctx, types.NamespacedName{Namespace: "tenant-a", Name: "pods"}, current); err != nil {
t.Fatalf("get CustomQuota: %v", err)
}
current.Status.ObservedGeneration = current.Generation
if err := cl.Update(ctx, current); err != nil {
t.Fatalf("mark CustomQuota ready: %v", err)
}
matched, err := handler.matchAllQuotas(ctx, cl, request, object)
if err != nil {
t.Fatalf("matchAllQuotas() error = %v", err)
}
if len(matched) != 1 {
t.Fatalf("matchAllQuotas() matches = %d, want 1", len(matched))
}
}
func TestCompiledTargetsCacheRefreshesInPlace(t *testing.T) {
t.Parallel()
targetsCache := cache.NewCompiledTargetsCache[string]()
handler := &objectCalculationHandler{
targetsCache: targetsCache,
jsonPathCache: cache.NewJSONPathCache(),
}
customQuota := &capsulev1beta2.CustomQuota{
ObjectMeta: metav1.ObjectMeta{Name: "objects", Namespace: "tenant-a"},
Spec: capsulev1beta2.CustomQuotaSpec{
Sources: []capsulev1beta2.CustomQuotaSpecSource{
{
VersionKind: caprunt.VersionKind{APIVersion: "v1", Kind: "Pod"},
CustomQuotaSpecSourceConfig: capsulev1beta2.CustomQuotaSpecSourceConfig{
Operation: quota.OpCount,
},
},
},
},
}
key := "tenant-a/objects"
targetsCache.Set(key, []cache.CompiledTarget{
{
CustomQuotaStatusTarget: capsulev1beta2.CustomQuotaStatusTarget{
GroupVersionKind: metav1.GroupVersionKind{Version: "v1", Kind: "Service"},
},
},
})
compiled, err := handler.getOrCompileCustomQuotaTargets(customQuota)
if err != nil {
t.Fatalf("getOrCompileCustomQuotaTargets() error = %v", err)
}
if len(compiled) != 1 || compiled[0].Kind != "Pod" {
t.Fatalf("compiled targets = %#v, want current Pod source", compiled)
}
if entries := targetsCache.Stats(); entries != 1 {
t.Fatalf("compiled target cache entries = %d, want one stable quota key", entries)
}
}
func ledgerForTest(key types.NamespacedName, allocated string) *capsulev1beta2.QuantityLedger {
return &capsulev1beta2.QuantityLedger{
ObjectMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
},
Status: capsulev1beta2.QuantityLedgerStatus{
Allocated: resource.MustParse(allocated),
},
}
}
func statusUpdateRequest(
uid types.UID,
oldPod *corev1.Pod,
newPod *corev1.Pod,
) admission.Request {
return admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
UID: uid,
Operation: admissionv1.Update,
Kind: metav1.GroupVersionKind{Version: "v1", Kind: "Pod"},
Namespace: oldPod.Namespace,
Name: oldPod.Name,
SubResource: "status",
OldObject: runtime.RawExtension{Object: oldPod},
Object: runtime.RawExtension{Object: newPod},
}}
}
func ledgerClientForTest(t *testing.T, ledger *capsulev1beta2.QuantityLedger) client.Client {
t.Helper()
scheme := runtime.NewScheme()
if err := capsulev1beta2.AddToScheme(scheme); err != nil {
t.Fatalf("add Capsule scheme: %v", err)
}
return fake.NewClientBuilder().
WithScheme(scheme).
WithStatusSubresource(&capsulev1beta2.QuantityLedger{}).
WithObjects(ledger).
Build()
}
type readinessFlappingReader struct {
client.Reader
customQuotaListCalls int
}
func (r *readinessFlappingReader) List(
ctx context.Context,
list client.ObjectList,
opts ...client.ListOption,
) error {
if err := r.Reader.List(ctx, list, opts...); err != nil {
return err
}
quotas, ok := list.(*capsulev1beta2.CustomQuotaList)
if !ok {
return nil
}
r.customQuotaListCalls++
if r.customQuotaListCalls < 2 {
return nil
}
for i := range quotas.Items {
condition := quotas.Items[i].Status.Conditions.GetConditionByType(meta.ReadyCondition)
if condition != nil {
condition.Status = metav1.ConditionFalse
}
}
return nil
}