Skip to content

Commit 86af7a6

Browse files
authored
refactor: split ProjectQuota CRD into per-AZ CRDs (#827)
- Add AvailabilityZone field to ProjectQuotaSpec - Change CRD naming from 'quota-{projectID}' to 'quota-{projectID}-{az}' - Status fields (TotalUsage, PaygUsage) are now flat map[string]int64 per AZ - Add ProjectQuotaResourceRouter in multicluster routers.go - Update QuotaController to manage per-AZ CRDs - Update HandleQuota API to create one CRD per project+AZ combination - Update usage.go to aggregate across per-AZ CRDs - derivePaygUsage is preserved and used in both reconcile paths - Re-add Create_EmptyResources and Update_WithNewMetadata test cases - Add partial AZ coverage tests (unit + integration) - Add total calculation verification tests with multi-resource multi-AZ
1 parent 80b718f commit 86af7a6

11 files changed

Lines changed: 905 additions & 677 deletions

File tree

api/v1alpha1/project_quota_types.go

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,9 @@ import (
77
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
88
)
99

10-
// ResourceQuota holds the quota for a single resource with per-AZ breakdown.
11-
// Maps to liquid.ResourceQuotaRequest from the LIQUID API.
12-
// See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ResourceQuotaRequest
13-
type ResourceQuota struct {
14-
// Quota is the total quota across all AZs (for compatibility).
15-
// Corresponds to liquid.ResourceQuotaRequest.Quota.
16-
// +kubebuilder:validation:Required
17-
Quota int64 `json:"quota"`
18-
19-
// PerAZ holds the per-availability-zone quota breakdown.
20-
// Key: availability zone name, Value: quota for that AZ.
21-
// Only populated for AZSeparatedTopology resources.
22-
// Corresponds to liquid.ResourceQuotaRequest.PerAZ[az].Quota.
23-
// See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#AZResourceQuotaRequest
24-
// +kubebuilder:validation:Optional
25-
PerAZ map[string]int64 `json:"perAZ,omitempty"`
26-
}
27-
28-
// ResourceQuotaUsage holds per-AZ PAYG usage for a single resource.
29-
type ResourceQuotaUsage struct {
30-
// PerAZ holds per-availability-zone PAYG usage values.
31-
// Key: availability zone name, Value: PAYG usage in that AZ.
32-
// +kubebuilder:validation:Optional
33-
PerAZ map[string]int64 `json:"perAZ,omitempty"`
34-
}
35-
3610
// ProjectQuotaSpec defines the desired state of ProjectQuota.
3711
// Populated from PUT /v1/projects/:uuid/quota payloads (liquid.ServiceQuotaRequest).
12+
// Each ProjectQuota CRD represents quota for ONE project in ONE availability zone.
3813
// See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ServiceQuotaRequest
3914
type ProjectQuotaSpec struct {
4015
// ProjectID of the OpenStack project this quota belongs to.
@@ -57,12 +32,18 @@ type ProjectQuotaSpec struct {
5732
// +kubebuilder:validation:Optional
5833
DomainName string `json:"domainName,omitempty"`
5934

60-
// Quota maps LIQUID resource names to their per-AZ quota.
35+
// AvailabilityZone is the AZ this quota CRD covers (e.g. "qa-de-1a").
36+
// In a multi-cluster setup, this determines which cluster the CRD is routed to.
37+
// +kubebuilder:validation:Required
38+
// +kubebuilder:validation:MinLength=1
39+
AvailabilityZone string `json:"availabilityZone"`
40+
41+
// Quota maps LIQUID resource names to their quota value for THIS availability zone.
6142
// Key: liquid.ResourceName (e.g. "hw_version_hana_v2_ram")
62-
// Mirrors liquid.ServiceQuotaRequest.Resources with AZSeparatedTopology.
63-
// See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ServiceQuotaRequest
43+
// Value: per-AZ quota from liquid.AZResourceQuotaRequest.Quota
44+
// See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#AZResourceQuotaRequest
6445
// +kubebuilder:validation:Optional
65-
Quota map[string]ResourceQuota `json:"quota,omitempty"`
46+
Quota map[string]int64 `json:"quota,omitempty"`
6647
}
6748

6849
// ProjectQuotaStatus defines the observed state of ProjectQuota.
@@ -75,17 +56,17 @@ type ProjectQuotaStatus struct {
7556
// +kubebuilder:validation:Optional
7657
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
7758

78-
// TotalUsage tracks per-resource per-AZ total resource consumption (all VMs in this project).
59+
// TotalUsage tracks per-resource total resource consumption in this AZ (all VMs in this project+AZ).
7960
// Persisted by the quota controller; updated by full reconcile and HV instance diffs.
8061
// Key: liquid.ResourceName
8162
// +kubebuilder:validation:Optional
82-
TotalUsage map[string]ResourceQuotaUsage `json:"totalUsage,omitempty"`
63+
TotalUsage map[string]int64 `json:"totalUsage,omitempty"`
8364

84-
// PaygUsage tracks per-resource per-AZ pay-as-you-go usage.
65+
// PaygUsage tracks per-resource pay-as-you-go usage in this AZ.
8566
// Derived as TotalUsage - CRUsage (clamped >= 0).
8667
// Key: liquid.ResourceName
8768
// +kubebuilder:validation:Optional
88-
PaygUsage map[string]ResourceQuotaUsage `json:"paygUsage,omitempty"`
69+
PaygUsage map[string]int64 `json:"paygUsage,omitempty"`
8970

9071
// LastReconcileAt is when the controller last reconciled this project's quota (any path).
9172
// +kubebuilder:validation:Optional
@@ -106,13 +87,16 @@ type ProjectQuotaStatus struct {
10687
// +kubebuilder:subresource:status
10788
// +kubebuilder:resource:scope=Cluster
10889
// +kubebuilder:printcolumn:name="Project",type="string",JSONPath=".spec.projectID"
90+
// +kubebuilder:printcolumn:name="AZ",type="string",JSONPath=".spec.availabilityZone"
10991
// +kubebuilder:printcolumn:name="Domain",type="string",JSONPath=".spec.domainID"
11092
// +kubebuilder:printcolumn:name="LastReconcile",type="date",JSONPath=".status.lastReconcileAt"
11193
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
11294

11395
// ProjectQuota is the Schema for the projectquotas API.
11496
// It persists quota values pushed by Limes via the LIQUID quota endpoint
11597
// (PUT /v1/projects/:uuid/quota → liquid.ServiceQuotaRequest).
98+
// Each CRD stores quota for one project in one availability zone.
99+
// In a multi-cluster setup, it is routed to the cluster serving that AZ.
116100
// See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ServiceQuotaRequest
117101
type ProjectQuota struct {
118102
metav1.TypeMeta `json:",inline"`

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 6 additions & 50 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helm/library/cortex/files/crds/cortex.cloud_projectquotas.yaml

Lines changed: 24 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ spec:
1818
- jsonPath: .spec.projectID
1919
name: Project
2020
type: string
21+
- jsonPath: .spec.availabilityZone
22+
name: AZ
23+
type: string
2124
- jsonPath: .spec.domainID
2225
name: Domain
2326
type: string
@@ -34,6 +37,8 @@ spec:
3437
ProjectQuota is the Schema for the projectquotas API.
3538
It persists quota values pushed by Limes via the LIQUID quota endpoint
3639
(PUT /v1/projects/:uuid/quota → liquid.ServiceQuotaRequest).
40+
Each CRD stores quota for one project in one availability zone.
41+
In a multi-cluster setup, it is routed to the cluster serving that AZ.
3742
See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ServiceQuotaRequest
3843
properties:
3944
apiVersion:
@@ -57,8 +62,15 @@ spec:
5762
description: |-
5863
ProjectQuotaSpec defines the desired state of ProjectQuota.
5964
Populated from PUT /v1/projects/:uuid/quota payloads (liquid.ServiceQuotaRequest).
65+
Each ProjectQuota CRD represents quota for ONE project in ONE availability zone.
6066
See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ServiceQuotaRequest
6167
properties:
68+
availabilityZone:
69+
description: |-
70+
AvailabilityZone is the AZ this quota CRD covers (e.g. "qa-de-1a").
71+
In a multi-cluster setup, this determines which cluster the CRD is routed to.
72+
minLength: 1
73+
type: string
6274
domainID:
6375
description: |-
6476
DomainID of the OpenStack domain this project belongs to.
@@ -81,38 +93,16 @@ spec:
8193
type: string
8294
quota:
8395
additionalProperties:
84-
description: |-
85-
ResourceQuota holds the quota for a single resource with per-AZ breakdown.
86-
Maps to liquid.ResourceQuotaRequest from the LIQUID API.
87-
See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ResourceQuotaRequest
88-
properties:
89-
perAZ:
90-
additionalProperties:
91-
format: int64
92-
type: integer
93-
description: |-
94-
PerAZ holds the per-availability-zone quota breakdown.
95-
Key: availability zone name, Value: quota for that AZ.
96-
Only populated for AZSeparatedTopology resources.
97-
Corresponds to liquid.ResourceQuotaRequest.PerAZ[az].Quota.
98-
See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#AZResourceQuotaRequest
99-
type: object
100-
quota:
101-
description: |-
102-
Quota is the total quota across all AZs (for compatibility).
103-
Corresponds to liquid.ResourceQuotaRequest.Quota.
104-
format: int64
105-
type: integer
106-
required:
107-
- quota
108-
type: object
96+
format: int64
97+
type: integer
10998
description: |-
110-
Quota maps LIQUID resource names to their per-AZ quota.
99+
Quota maps LIQUID resource names to their quota value for THIS availability zone.
111100
Key: liquid.ResourceName (e.g. "hw_version_hana_v2_ram")
112-
Mirrors liquid.ServiceQuotaRequest.Resources with AZSeparatedTopology.
113-
See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#ServiceQuotaRequest
101+
Value: per-AZ quota from liquid.AZResourceQuotaRequest.Quota
102+
See: https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid#AZResourceQuotaRequest
114103
type: object
115104
required:
105+
- availabilityZone
116106
- domainID
117107
- projectID
118108
type: object
@@ -200,39 +190,19 @@ spec:
200190
type: integer
201191
paygUsage:
202192
additionalProperties:
203-
description: ResourceQuotaUsage holds per-AZ PAYG usage for a single
204-
resource.
205-
properties:
206-
perAZ:
207-
additionalProperties:
208-
format: int64
209-
type: integer
210-
description: |-
211-
PerAZ holds per-availability-zone PAYG usage values.
212-
Key: availability zone name, Value: PAYG usage in that AZ.
213-
type: object
214-
type: object
193+
format: int64
194+
type: integer
215195
description: |-
216-
PaygUsage tracks per-resource per-AZ pay-as-you-go usage.
196+
PaygUsage tracks per-resource pay-as-you-go usage in this AZ.
217197
Derived as TotalUsage - CRUsage (clamped >= 0).
218198
Key: liquid.ResourceName
219199
type: object
220200
totalUsage:
221201
additionalProperties:
222-
description: ResourceQuotaUsage holds per-AZ PAYG usage for a single
223-
resource.
224-
properties:
225-
perAZ:
226-
additionalProperties:
227-
format: int64
228-
type: integer
229-
description: |-
230-
PerAZ holds per-availability-zone PAYG usage values.
231-
Key: availability zone name, Value: PAYG usage in that AZ.
232-
type: object
233-
type: object
202+
format: int64
203+
type: integer
234204
description: |-
235-
TotalUsage tracks per-resource per-AZ total resource consumption (all VMs in this project).
205+
TotalUsage tracks per-resource total resource consumption in this AZ (all VMs in this project+AZ).
236206
Persisted by the quota controller; updated by full reconcile and HV instance diffs.
237207
Key: liquid.ResourceName
238208
type: object

0 commit comments

Comments
 (0)