Skip to content

Commit c5ffa1e

Browse files
committed
feat(admission): improve performance for namespaced admissions
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
1 parent 9589ebd commit c5ffa1e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1845
-388
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
bin
99
dist/
1010
config/
11+
builds/
1112

1213
# Test binary, build with `go test -c`
1314
*.test

Makefile

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ helm-test-exec: ct helm-controller-version ko-build-all
100100
$(MAKE) e2e-load-image CLUSTER_NAME=capsule-charts IMAGE=$(CAPSULE_IMG) VERSION=v0.0.0
101101
@$(KUBECTL) create ns capsule-system || true
102102
$(MAKE) dev-install-deps
103+
$(MAKE) dev-install-grafana-operator-crds
103104
@$(CT) install --config $(SRC_ROOT)/.github/configs/ct.yaml --namespace=capsule-system --all --debug
104105

105106
# Setup development env
@@ -111,7 +112,7 @@ dev-build: kind
111112
dev-destroy: kind
112113
$(KIND) delete cluster --name capsule
113114

114-
dev-install-deps: dev-setup-fluxcd dev-setup-cert-manager dev-install-gw-api-crds dev-install-grafana-operator-crds dev-install-prometheus-crds wait-for-helmreleases
115+
dev-install-deps: dev-setup-fluxcd dev-setup-cert-manager dev-install-gw-api-crds wait-for-helmreleases
115116

116117
API_GW := none
117118
API_GW_VERSION := v1.3.0
@@ -171,6 +172,7 @@ dev-setup:
171172
export CA_BUNDLE=`openssl base64 -in /tmp/k8s-webhook-server/serving-certs/tls.crt | tr -d '\n'`; \
172173
$(HELM) upgrade \
173174
--dependency-update \
175+
--force-conflicts \
174176
--debug \
175177
--install \
176178
--namespace capsule-system \
@@ -226,6 +228,8 @@ dev-setup-capsule-example: dev-setup-fluxcd
226228
@$(KUBECTL) create ns green-prod --as bob --as-group projectcapsule.dev || true
227229
@$(KUBECTL) create ns solar-test --as alice --as-group projectcapsule.dev || true
228230
@$(KUBECTL) create ns solar-prod --as alice --as-group projectcapsule.dev || true
231+
@$(KUBECTL) apply -f hack/distro/capsule/example-setup/claims.yaml
232+
229233

230234
wait-for-helmreleases:
231235
@ echo "Waiting for all HelmReleases to have observedGeneration >= 0..."
@@ -234,6 +238,42 @@ wait-for-helmreleases:
234238
done
235239

236240

241+
ENTERPRISE_VERSION ?= "0.13.0-rc.2"
242+
ENTERPRISE_REGISTRY ?= "oci.peakscale.ch"
243+
244+
enterprise-prerelease:
245+
mkdir -p ./builds
246+
$(MAKE) CAPSULE_IMG=$(ENTERPRISE_REGISTRY)/prereleases/images/capsule VERSION=$(ENTERPRISE_VERSION) ko-publish-capsule
247+
$(HELM) package ./charts/capsule --app-version=$(ENTERPRISE_VERSION) --version=$(ENTERPRISE_VERSION) --destination ./builds/
248+
$(HELM) push ./builds/capsule-$(ENTERPRISE_VERSION).tgz oci://$(ENTERPRISE_REGISTRY)/prereleases/charts/
249+
$(MAKE) deploy-enterprise
250+
rm -rf ./builds
251+
252+
deploy-enterprise:
253+
@echo ""
254+
@echo "Deploying Capsule Prerelease (Enterprise) $(ENTERPRISE_VERSION)"
255+
@echo ""
256+
@echo "1) Create image pull secret (Change the credentials with the ones provided to you):"
257+
@echo ""
258+
@echo "kubectl create secret docker-registry capsule-enterprise -n capsule-system \\"
259+
@echo " --docker-username='robot\$$name' \\"
260+
@echo " --docker-password='serviceaccount-password' \\"
261+
@echo " --docker-server='$(ENTERPRISE_REGISTRY)'"
262+
@echo ""
263+
@echo "2) Deploy Capsule:"
264+
@echo ""
265+
@echo "helm upgrade --install capsule \\"
266+
@echo " oci://$(ENTERPRISE_REGISTRY)/prereleases/charts/capsule \\"
267+
@echo " --namespace capsule-system \\"
268+
@echo " --version $(ENTERPRISE_VERSION) \\"
269+
@echo " --reuse-values \\"
270+
@echo " --set manager.image.registry=$(ENTERPRISE_REGISTRY) \\"
271+
@echo " --set manager.image.repository=prereleases/images/capsule \\"
272+
@echo " --set manager.image.tag=$(ENTERPRISE_VERSION) \\"
273+
@echo " --set manager.image.pullPolicy=Always \\"
274+
@echo " --set 'serviceAccount.imagePullSecrets={capsule-enterprise}'"
275+
@echo ""
276+
237277
####################
238278
# -- Docker
239279
####################

api/v1beta2/capsuleconfiguration_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
99

1010
"github.com/projectcapsule/capsule/pkg/api"
11+
"github.com/projectcapsule/capsule/pkg/api/meta"
1112
)
1213

1314
// CapsuleConfigurationSpec defines the Capsule configuration.
@@ -89,7 +90,7 @@ type DynamicAdmission struct {
8990

9091
type DynamicAdmissionConfig struct {
9192
// Name the Admission Webhook
92-
Name api.Name `json:"name,omitempty"`
93+
Name meta.RFC1123Name `json:"name,omitempty"`
9394
// Labels added to the Admission Webhook
9495
// +optional
9596
Labels map[string]string `json:"labels,omitempty"`

api/v1beta2/namespace_options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ type NamespaceOptions struct {
1717
AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
1818
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant via a list. Optional.
1919
AdditionalMetadataList []api.AdditionalMetadataSelectorSpec `json:"additionalMetadataList,omitempty"`
20+
// Required Metadata for namespace within this tenant
21+
// +optional
22+
RequiredMetadata *RequiredMetadata `json:"requiredMetadata,omitzero"`
2023
// Define the labels that a Tenant Owner cannot set for their Namespace resources.
2124
// +optional
2225
ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitzero"`
@@ -27,3 +30,13 @@ type NamespaceOptions struct {
2730
//+kubebuilder:default:=false
2831
ManagedMetadataOnly bool `json:"managedMetadataOnly,omitempty"`
2932
}
33+
34+
type RequiredMetadata struct {
35+
// Labels that must be defined for each namespace
36+
// +optional
37+
Labels map[string]string `json:"labels,omitzero"`
38+
39+
// Annotations that must be defined for each namespace
40+
// +optional
41+
Annotations map[string]string `json:"annotations,omitzero"`
42+
}

api/v1beta2/resourcepool_func.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
corev1 "k8s.io/api/core/v1"
1212
"k8s.io/apimachinery/pkg/api/resource"
1313

14-
"github.com/projectcapsule/capsule/pkg/api"
14+
"github.com/projectcapsule/capsule/pkg/api/meta"
1515
)
1616

1717
func (r *ResourcePool) GetQuotaName() string {
@@ -79,9 +79,10 @@ func (r *ResourcePool) AddClaimToStatus(claim *ResourcePoolClaim) {
7979
}
8080

8181
scl := &ResourcePoolClaimsItem{
82-
StatusNameUID: api.StatusNameUID{
83-
UID: claim.UID,
84-
Name: api.Name(claim.Name),
82+
NamespacedRFC1123ObjectReferenceWithNamespaceWithUID: meta.NamespacedRFC1123ObjectReferenceWithNamespaceWithUID{
83+
UID: claim.UID,
84+
Name: meta.RFC1123Name(claim.Name),
85+
Namespace: meta.RFC1123SubdomainName(claim.Namespace),
8586
},
8687
Claims: claim.Spec.ResourceClaims,
8788
}

api/v1beta2/resourcepool_func_test.go

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1212
"k8s.io/apimachinery/pkg/types"
1313

14-
"github.com/projectcapsule/capsule/pkg/api"
1514
"github.com/projectcapsule/capsule/pkg/api/meta"
1615
"github.com/stretchr/testify/assert"
1716
)
@@ -34,7 +33,7 @@ func TestGetClaimFromStatus(t *testing.T) {
3433
Claims: ResourcePoolNamespaceClaimsStatus{
3534
ns: {
3635
&ResourcePoolClaimsItem{
37-
StatusNameUID: api.StatusNameUID{
36+
NamespacedRFC1123ObjectReferenceWithNamespaceWithUID: meta.NamespacedRFC1123ObjectReferenceWithNamespaceWithUID{
3837
UID: testUID,
3938
},
4039
Claims: corev1.ResourceList{
@@ -127,8 +126,9 @@ func TestAddRemoveClaimToStatus(t *testing.T) {
127126
pool.AddClaimToStatus(claim)
128127

129128
stored := pool.GetClaimFromStatus(claim)
129+
130130
assert.NotNil(t, stored)
131-
assert.Equal(t, api.Name("claim-1"), stored.Name)
131+
assert.Equal(t, meta.RFC1123Name("claim-1"), stored.Name)
132132

133133
pool.RemoveClaimFromStatus(claim)
134134
assert.Nil(t, pool.GetClaimFromStatus(claim))
@@ -219,7 +219,7 @@ func TestGetNamespaceClaims(t *testing.T) {
219219
Claims: ResourcePoolNamespaceClaimsStatus{
220220
"ns": {
221221
&ResourcePoolClaimsItem{
222-
StatusNameUID: api.StatusNameUID{UID: "uid1"},
222+
NamespacedRFC1123ObjectReferenceWithNamespaceWithUID: meta.NamespacedRFC1123ObjectReferenceWithNamespaceWithUID{UID: "uid1"},
223223
Claims: corev1.ResourceList{
224224
corev1.ResourceLimitsCPU: resource.MustParse("1"),
225225
},
@@ -260,36 +260,59 @@ func TestIsBoundToResourcePool_2(t *testing.T) {
260260
t.Run("bound to resource pool (Assigned=True)", func(t *testing.T) {
261261
claim := &ResourcePoolClaim{
262262
Status: ResourcePoolClaimStatus{
263-
Condition: metav1.Condition{
264-
Type: meta.BoundCondition,
265-
Status: metav1.ConditionTrue,
266-
},
263+
Conditions: meta.ConditionList{},
267264
},
268265
}
269-
assert.Equal(t, true, claim.IsBoundToResourcePool())
266+
267+
assert.Equal(t, false, claim.IsBoundInResourcePool())
270268
})
271269

272270
t.Run("not bound - wrong condition type", func(t *testing.T) {
273271
claim := &ResourcePoolClaim{
274272
Status: ResourcePoolClaimStatus{
275-
Condition: metav1.Condition{
276-
Type: "Other",
277-
Status: metav1.ConditionTrue,
273+
Conditions: meta.ConditionList{
274+
meta.Condition{},
275+
},
276+
},
277+
}
278+
279+
cond := meta.NewAssignedCondition(claim)
280+
cond.Status = metav1.ConditionFalse
281+
claim.Status.Conditions.UpdateConditionByType(cond)
282+
283+
assert.Equal(t, false, claim.IsBoundInResourcePool())
284+
})
285+
286+
t.Run("not bound - condition not true", func(t *testing.T) {
287+
claim := &ResourcePoolClaim{
288+
Status: ResourcePoolClaimStatus{
289+
Conditions: meta.ConditionList{
290+
meta.Condition{},
278291
},
279292
},
280293
}
281-
assert.Equal(t, false, claim.IsBoundToResourcePool())
294+
295+
cond := meta.NewBoundCondition(claim)
296+
cond.Status = metav1.ConditionFalse
297+
claim.Status.Conditions.UpdateConditionByType(cond)
298+
299+
assert.Equal(t, false, claim.IsBoundInResourcePool())
282300
})
283301

284302
t.Run("not bound - condition not true", func(t *testing.T) {
285303
claim := &ResourcePoolClaim{
286304
Status: ResourcePoolClaimStatus{
287-
Condition: metav1.Condition{
288-
Type: meta.BoundCondition,
289-
Status: metav1.ConditionFalse,
305+
Conditions: meta.ConditionList{
306+
meta.Condition{},
290307
},
291308
},
292309
}
293-
assert.Equal(t, false, claim.IsBoundToResourcePool())
310+
311+
cond := meta.NewBoundCondition(claim)
312+
cond.Status = metav1.ConditionTrue
313+
claim.Status.Conditions.UpdateConditionByType(cond)
314+
315+
assert.Equal(t, true, claim.IsBoundInResourcePool())
294316
})
317+
295318
}

api/v1beta2/resourcepool_status.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"k8s.io/apimachinery/pkg/types"
99

1010
"github.com/projectcapsule/capsule/pkg/api"
11+
"github.com/projectcapsule/capsule/pkg/api/meta"
1112
)
1213

1314
// GlobalResourceQuotaStatus defines the observed state of GlobalResourceQuota.
@@ -28,6 +29,8 @@ type ResourcePoolStatus struct {
2829
Allocation ResourcePoolQuotaStatus `json:"allocation,omitzero"`
2930
// Exhaustions from claims associated with the pool
3031
Exhaustions map[string]api.PoolExhaustionResource `json:"exhaustions,omitempty"`
32+
// Conditions for the resource claim
33+
Conditions meta.ConditionList `json:"conditions,omitzero"`
3134
}
3235

3336
type ResourcePoolNamespaceClaimsStatus map[string]ResourcePoolClaimsList
@@ -60,7 +63,7 @@ func (r *ResourcePoolClaimsList) GetClaimByUID(uid types.UID) *ResourcePoolClaim
6063
// ResourceQuotaClaimStatus defines the observed state of ResourceQuotaClaim.
6164
type ResourcePoolClaimsItem struct {
6265
// Reference to the GlobalQuota being claimed from
63-
api.StatusNameUID `json:",inline"`
66+
meta.NamespacedRFC1123ObjectReferenceWithNamespaceWithUID `json:",inline"`
6467

6568
// Claimed resources
6669
Claims corev1.ResourceList `json:"claims,omitempty"`

api/v1beta2/resourcepool_types.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import (
77
corev1 "k8s.io/api/core/v1"
88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
99

10-
"github.com/projectcapsule/capsule/pkg/api/misc"
10+
"github.com/projectcapsule/capsule/pkg/runtime/selectors"
1111
)
1212

1313
// ResourcePoolSpec.
1414
type ResourcePoolSpec struct {
1515
// Selector to match the namespaces that should be managed by the GlobalResourceQuota
16-
Selectors []misc.NamespaceSelector `json:"selectors,omitempty"`
16+
Selectors []selectors.NamespaceSelector `json:"selectors,omitempty"`
1717
// Define the resourcequota served by this resourcepool.
1818
Quota corev1.ResourceQuotaSpec `json:"quota"`
1919
// The Defaults given for each namespace, the default is not counted towards the total allocation
@@ -27,19 +27,19 @@ type ResourcePoolSpec struct {
2727
}
2828

2929
type ResourcePoolSpecConfiguration struct {
30-
// With this option all resources which can be allocated are set to 0 for the resourcequota defaults.
30+
// With this option all resources which can be allocated are set to 0 for the resourcequota defaults. (Default false)
3131
// +kubebuilder:default=false
3232
DefaultsAssignZero *bool `json:"defaultsZero,omitempty"`
3333
// Claims are queued whenever they are allocated to a pool. A pool tries to allocate claims in order based on their
3434
// creation date. But no matter their creation time, if a claim is requesting too much resources it's put into the queue
3535
// but if a lower priority claim still has enough space in the available resources, it will be able to claim them. Eventough
3636
// it's priority was lower
3737
// Enabling this option respects to Order. Meaning the Creationtimestamp matters and if a resource is put into the queue, no
38-
// other claim can claim the same resources with lower priority.
38+
// other claim can claim the same resources with lower priority. (Default false)
3939
// +kubebuilder:default=false
4040
OrderedQueue *bool `json:"orderedQueue,omitempty"`
4141
// When a resourcepool is deleted, the resourceclaims bound to it are disassociated from the resourcepool but not deleted.
42-
// By Enabling this option, the resourceclaims will be deleted when the resourcepool is deleted, if they are in bound state.
42+
// By Enabling this option, the resourceclaims will be deleted when the resourcepool is deleted, if they are in bound state. (Default false)
4343
// +kubebuilder:default=false
4444
DeleteBoundResources *bool `json:"deleteBoundResources,omitempty"`
4545
}
@@ -49,6 +49,8 @@ type ResourcePoolSpecConfiguration struct {
4949
// +kubebuilder:resource:scope=Cluster,shortName=quotapool
5050
// +kubebuilder:printcolumn:name="Claims",type="integer",JSONPath=".status.claimCount",description="The total amount of Claims bound"
5151
// +kubebuilder:printcolumn:name="Namespaces",type="integer",JSONPath=".status.namespaceCount",description="The total amount of Namespaces considered"
52+
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="Reconcile Status"
53+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="Reconcile Message"
5254
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
5355

5456
// Resourcepools allows you to define a set of resources as known from ResoureQuotas. The Resourcepools are defined at cluster-scope an should

api/v1beta2/resourcepoolclaim_func.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,52 @@ import (
1010
)
1111

1212
// Indicate the claim is bound to a resource pool.
13-
func (r *ResourcePoolClaim) IsBoundToResourcePool() bool {
14-
if r.Status.Condition.Type == meta.BoundCondition &&
15-
r.Status.Condition.Status == metav1.ConditionTrue {
13+
func (r *ResourcePoolClaim) IsExhaustedInResourcePool() bool {
14+
condition := r.Status.Conditions.GetConditionByType(meta.ExhaustedCondition)
15+
16+
if condition == nil {
17+
return false
18+
}
19+
20+
if condition.Status == metav1.ConditionTrue {
1621
return true
1722
}
1823

1924
return false
2025
}
26+
27+
func (r *ResourcePoolClaim) IsAssignedInResourcePool() bool {
28+
condition := r.Status.Conditions.GetConditionByType(meta.AssignedCondition)
29+
30+
if condition == nil {
31+
return false
32+
}
33+
34+
if condition.Status == metav1.ConditionTrue {
35+
return true
36+
}
37+
38+
return false
39+
}
40+
41+
func (r *ResourcePoolClaim) IsBoundInResourcePool() bool {
42+
condition := r.Status.Conditions.GetConditionByType(meta.BoundCondition)
43+
44+
if condition == nil {
45+
return false
46+
}
47+
48+
if condition.Status == metav1.ConditionTrue {
49+
return true
50+
}
51+
52+
return false
53+
}
54+
55+
func (r *ResourcePoolClaim) GetPool() string {
56+
if name := string(r.Status.Pool.Name); name != "" {
57+
return name
58+
}
59+
60+
return r.Spec.Pool
61+
}

0 commit comments

Comments
 (0)