-
Notifications
You must be signed in to change notification settings - Fork 6
feat: adding CommittedResource CRD #730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| // Copyright SAP SE | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package v1alpha1 | ||
|
|
||
| import ( | ||
| "k8s.io/apimachinery/pkg/api/resource" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| ) | ||
|
|
||
| // Only confirmed and guaranteed commitments result in active Reservation slots. | ||
| type CommitmentStatus string | ||
|
|
||
| const ( | ||
| // CommitmentStatusPlanned: StartTime not yet reached; no resources guaranteed, no Reservation slots yet. | ||
| CommitmentStatusPlanned CommitmentStatus = "planned" | ||
| // CommitmentStatusPending: StartTime reached; no resources guaranteed, no Reservation slots yet. | ||
| CommitmentStatusPending CommitmentStatus = "pending" | ||
| // CommitmentStatusGuaranteed: StartTime not reached yet; resources are guaranteed latest starting from StartTime, Reservation slots in sync. | ||
| CommitmentStatusGuaranteed CommitmentStatus = "guaranteed" | ||
| // CommitmentStatusConfirmed: StartTime reached; resources are guaranteed, Reservation slots in sync. | ||
| CommitmentStatusConfirmed CommitmentStatus = "confirmed" | ||
| // CommitmentStatusSuperseded: replaced by another commitment; no resources guaranteed, Reservation slots removed. | ||
| CommitmentStatusSuperseded CommitmentStatus = "superseded" | ||
| // CommitmentStatusExpired: past EndTime; no resources guaranteed, Reservation slots removed. | ||
| CommitmentStatusExpired CommitmentStatus = "expired" | ||
| ) | ||
|
|
||
| // CommittedResourceType identifies the kind of resource a commitment covers. | ||
| type CommittedResourceType string | ||
|
|
||
| const ( | ||
| // CommittedResourceTypeMemory: RAM commitment; drives flavor-based Reservation slot creation. | ||
| CommittedResourceTypeMemory CommittedResourceType = "memory" | ||
| // CommittedResourceTypeCores: CPU core commitment; verified arithmetically, no Reservation slots created. | ||
| CommittedResourceTypeCores CommittedResourceType = "cores" | ||
| ) | ||
|
|
||
| // CommittedResourceSpec defines the desired state of CommittedResource, | ||
| type CommittedResourceSpec struct { | ||
| // UUID of the commitment this resource corresponds to. | ||
| // +kubebuilder:validation:Required | ||
| CommitmentUUID string `json:"commitmentUUID"` | ||
|
|
||
| // SchedulingDomain specifies the scheduling domain for this committed resource (e.g., "nova", "ironcore"). | ||
| // +kubebuilder:validation:Optional | ||
| SchedulingDomain SchedulingDomain `json:"schedulingDomain,omitempty"` | ||
|
|
||
| // FlavorGroupName identifies the flavor group this commitment targets, e.g. "kvm_v2_hana_s". | ||
| // +kubebuilder:validation:Required | ||
| FlavorGroupName string `json:"flavorGroupName"` | ||
|
|
||
| // ResourceType identifies the kind of resource committed: memory drives Reservation slots; cores uses an arithmetic check only. | ||
| // +kubebuilder:validation:Enum=memory;cores | ||
| // +kubebuilder:validation:Required | ||
| ResourceType CommittedResourceType `json:"resourceType"` | ||
|
|
||
| // Amount is the total committed quantity. | ||
| // memory: MiB expressed in K8s binary SI notation (e.g. "1280Gi", "640Mi"). | ||
| // cores: integer core count (e.g. "40"). | ||
| // +kubebuilder:validation:Required | ||
| Amount resource.Quantity `json:"amount"` | ||
|
Comment on lines
+58
to
+62
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, does this mean that we would create multiple committed resources for one reservation so we can keep track of both cpu cores and RAM? I would have expected something more like this, but maybe I am off-track here:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With Limes we have always one resource per commitment. So we have e.g. two commitment for the same project, when the user wants to specify memory and cpu for a GP group. Still our controller can read both related CR CRDs to do more advanced flavor selection.. considering the CPU cores the user has in mind while choosing the right flavor sizes for the reservations of the memory cr crd.
Comment on lines
+58
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Inspect generated CRDs and consumers for amount validation.
# Expected: Either the generated CommittedResource CRD has amount positivity/type validation,
# or Go consumers reject non-positive amounts before using Spec.Amount.Value().
set -euo pipefail
echo "Generated CRD snippets containing amount validation:"
fd -i 'committedresource|crd|cortex' --extension yaml --extension yml --extension json \
--exec rg -n -C5 'amount:|x-kubernetes-validations|minimum|pattern' {}
echo
echo "Go consumers of Spec.Amount:"
rg -n -C5 --type=go '\.Spec\.Amount|Amount\.Value\(|Amount\.Sign\(|Amount\.MilliValue\('Repository: cobaltcore-dev/cortex Length of output: 21606 🏁 Script executed: find . -name "*committed*" -type f | grep -E "\.(go|yaml|yml)$"Repository: cobaltcore-dev/cortex Length of output: 107 🏁 Script executed: # Check the committed_resource_types.go file for kubebuilder validation markers
cat -n api/v1alpha1/committed_resource_types.go | head -100Repository: cobaltcore-dev/cortex Length of output: 5270 🏁 Script executed: # Look for the consumer code
cat -n internal/scheduling/reservations/commitments/state.go | sed -n '200,250p'Repository: cobaltcore-dev/cortex Length of output: 2122 🏁 Script executed: # Search for admission webhooks and validation rules
rg -n "Amount|validation.*positive|minimum.*Amount" --type=go | grep -i "commit\|valid\|webhook"Repository: cobaltcore-dev/cortex Length of output: 8691 🏁 Script executed: # Check if there are any validation webhook implementations
fd -type f -name "*.go" | xargs rg -l "ValidatingWebhook|Validate" | head -20Repository: cobaltcore-dev/cortex Length of output: 1333 🏁 Script executed: # Search for kubebuilder CEL validation rules (x-kubernetes-validations)
rg -n "x-kubernetes-validations|kubebuilder:validation:Minimum" api/Repository: cobaltcore-dev/cortex Length of output: 47 Add validation to reject non-positive committed amounts.
Add 🤖 Prompt for AI Agents |
||
|
|
||
| // AvailabilityZone specifies the availability zone for this commitment. | ||
| // +kubebuilder:validation:Required | ||
| AvailabilityZone string `json:"availabilityZone"` | ||
|
|
||
| // ProjectID of the OpenStack project this commitment belongs to. | ||
| // +kubebuilder:validation:Required | ||
| ProjectID string `json:"projectID"` | ||
|
|
||
| // DomainID of the OpenStack domain this commitment belongs to. | ||
| // +kubebuilder:validation:Required | ||
| DomainID string `json:"domainID"` | ||
|
|
||
| // StartTime is the activation time for Reservation slots. | ||
| // Nil for guaranteed commitments (slots are active from creation); set to ConfirmedAt for confirmed ones. | ||
| // +kubebuilder:validation:Optional | ||
| StartTime *metav1.Time `json:"startTime,omitempty"` | ||
|
|
||
| // EndTime is when Reservation slots expire. Nil for unbounded commitments with no expiry. | ||
| // +kubebuilder:validation:Optional | ||
| EndTime *metav1.Time `json:"endTime,omitempty"` | ||
|
|
||
| // ConfirmedAt is when the commitment was confirmed. | ||
| // +kubebuilder:validation:Optional | ||
| ConfirmedAt *metav1.Time `json:"confirmedAt,omitempty"` | ||
|
|
||
| // State is the lifecycle state of the commitment. | ||
| // +kubebuilder:validation:Enum=planned;pending;guaranteed;confirmed;superseded;expired | ||
| // +kubebuilder:validation:Required | ||
| State CommitmentStatus `json:"state"` | ||
|
Comment on lines
+89
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Check whether all FromCommittedResource call sites filter Spec.State.
# Expected: Each call path should restrict processing to CommitmentStatusGuaranteed/Confirmed
# before active CommitmentState objects are produced.
set -euo pipefail
echo "FromCommittedResource call sites:"
rg -n -C8 --type=go '\bFromCommittedResource\s*\('
echo
echo "State checks in Go code:"
rg -n -C5 --type=go 'CommitmentStatus(Guaranteed|Confirmed|Planned|Pending|Superseded|Expired)|\.Spec\.State'Repository: cobaltcore-dev/cortex Length of output: 5131 🏁 Script executed: # Get the full FromCommittedResource function implementation
sed -n '210,250p' internal/scheduling/reservations/commitments/state.goRepository: cobaltcore-dev/cortex Length of output: 1519 🏁 Script executed: # Find all call sites of FromCommittedResource
rg -n '\bFromCommittedResource\b' --type=goRepository: cobaltcore-dev/cortex Length of output: 343 🏁 Script executed: # Search for FromCommittedResource with various patterns
rg 'FromCommittedResource' --type=go -A 2 -B 2
echo "---"
echo "Search for any references to the function (without parentheses):"
rg 'FromCommittedResource' --type=goRepository: cobaltcore-dev/cortex Length of output: 1214 🏁 Script executed: # Search for patterns where CommittedResource is processed
rg 'CommittedResource' --type=go | grep -E '(func|switch|if|case).*Spec\.State|State.*:=' | head -20Repository: cobaltcore-dev/cortex Length of output: 47 🏁 Script executed: # Search for CommitmentState usage (the return type of FromCommittedResource)
rg 'CommitmentState' --type=go | head -30Repository: cobaltcore-dev/cortex Length of output: 4280 🏁 Script executed: # Look for controllers or reconcilers handling CommittedResource
rg 'CommittedResource' --type=go -l | xargs -I {} grep -l 'controller\|reconcil' {} 2>/dev/nullRepository: cobaltcore-dev/cortex Length of output: 1358 🏁 Script executed: # Check for TODO, FIXME, or WIP markers related to commitments
rg -i '(TODO|FIXME|WIP).*commitment|commitment.*(TODO|FIXME|WIP)' --type=goRepository: cobaltcore-dev/cortex Length of output: 391 🏁 Script executed: # Check syncer.go for FromCommittedResource usage and state filtering
cat -n internal/scheduling/reservations/commitments/syncer.go | head -150Repository: cobaltcore-dev/cortex Length of output: 5977 🏁 Script executed: # Check controller.go to see how CommittedResource is processed
rg -n 'FromCommittedResource|Spec\.State' internal/scheduling/reservations/commitments/controller.go -A 5 -B 5Repository: cobaltcore-dev/cortex Length of output: 47 🏁 Script executed: # Search for CommittedResource CRD creation and usage in controller
grep -n 'CommittedResource' internal/scheduling/reservations/commitments/controller.go | head -20Repository: cobaltcore-dev/cortex Length of output: 1481 🏁 Script executed: # Search for where CommittedResource objects flow
rg 'v1alpha1\.CommittedResource|CommittedResource\{' --type=go -B 3 -A 3 | head -80Repository: cobaltcore-dev/cortex Length of output: 6861 🏁 Script executed: # Check if FromCommittedResource might be called indirectly or is simply unused
rg '&CommitmentState\{|CommitmentState\{' --type=go | grep -v test | head -20Repository: cobaltcore-dev/cortex Length of output: 601 🏁 Script executed: # Search for any registration of FromCommittedResource as a function pointer
rg 'FromCommittedResource' --type=go
# Check the entire state.go file for context on FromCommittedResource
wc -l internal/scheduling/reservations/commitments/state.go
# Look for any interface definitions or function type assignments
rg 'func\s*\(' --type=go internal/scheduling/reservations/commitments/state.go | grep -A 1 -B 1 'CommittedResource'Repository: cobaltcore-dev/cortex Length of output: 393 🏁 Script executed: # Check if there's a controller.go that reconciles CommittedResource objects
grep -n 'CommittedResource' internal/scheduling/reservations/commitments/controller.go | head -5
# Look at the full picture: where are CommittedResource objects coming from?
rg 'watch.*CommittedResource|For.*CommittedResource' --type=goRepository: cobaltcore-dev/cortex Length of output: 1174 Add state filtering to The function Add a guard to reject if cr.Spec.State != v1alpha1.CommitmentStatusGuaranteed && cr.Spec.State != v1alpha1.CommitmentStatusConfirmed {
return nil, errors.New("only guaranteed and confirmed commitments are supported")
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // CommittedResourceStatus defines the observed state of CommittedResource. | ||
| type CommittedResourceStatus struct { | ||
| // AcceptedAmount is the quantity the controller last successfully provisioned as Reservation slots. | ||
| // Nil if the spec has never been successfully reconciled. | ||
| // +kubebuilder:validation:Optional | ||
| AcceptedAmount *resource.Quantity `json:"acceptedAmount,omitempty"` | ||
|
|
||
| // AcceptedAt is when the controller last successfully reconciled the spec into Reservation slots. | ||
| // +kubebuilder:validation:Optional | ||
| AcceptedAt *metav1.Time `json:"acceptedAt,omitempty"` | ||
|
|
||
| // LastChanged is when the spec was last written by the syncer. | ||
| // When AcceptedAt is older than LastChanged, the controller has pending work. | ||
| // +kubebuilder:validation:Optional | ||
| LastChanged *metav1.Time `json:"lastChanged,omitempty"` | ||
|
|
||
| // LastReconcileAt is when the controller last ran its reconcile loop for this resource. | ||
| // +kubebuilder:validation:Optional | ||
| LastReconcileAt *metav1.Time `json:"lastReconcileAt,omitempty"` | ||
|
|
||
| // AssignedVMs holds the UUIDs of VMs deterministically assigned to this committed resource. | ||
| // Populated by the usage reconciler; used to compute UsedAmount and drive the quota controller. | ||
| // +kubebuilder:validation:Optional | ||
| AssignedVMs []string `json:"assignedVMs,omitempty"` | ||
|
|
||
| // UsedAmount is the sum of assigned VM resources expressed in the same units as Spec.Amount. | ||
| // Populated by the usage reconciler. | ||
| // +kubebuilder:validation:Optional | ||
| UsedAmount *resource.Quantity `json:"usedAmount,omitempty"` | ||
|
|
||
| // LastUsageReconcileAt is when the usage reconciler last updated AssignedVMs and UsedAmount. | ||
| // +kubebuilder:validation:Optional | ||
| LastUsageReconcileAt *metav1.Time `json:"lastUsageReconcileAt,omitempty"` | ||
|
|
||
| // Conditions holds the current status conditions. | ||
| // +kubebuilder:validation:Optional | ||
| Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` | ||
| } | ||
|
|
||
| // +kubebuilder:object:root=true | ||
| // +kubebuilder:subresource:status | ||
| // +kubebuilder:resource:scope=Cluster | ||
| // +kubebuilder:printcolumn:name="Project",type="string",JSONPath=".spec.projectID" | ||
| // +kubebuilder:printcolumn:name="FlavorGroup",type="string",JSONPath=".spec.flavorGroupName" | ||
| // +kubebuilder:printcolumn:name="ResourceType",type="string",JSONPath=".spec.resourceType" | ||
| // +kubebuilder:printcolumn:name="AZ",type="string",JSONPath=".spec.availabilityZone" | ||
| // +kubebuilder:printcolumn:name="Amount",type="string",JSONPath=".spec.amount" | ||
| // +kubebuilder:printcolumn:name="AcceptedAmount",type="string",JSONPath=".status.acceptedAmount" | ||
| // +kubebuilder:printcolumn:name="UsedAmount",type="string",JSONPath=".status.usedAmount" | ||
| // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".spec.state" | ||
| // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" | ||
| // +kubebuilder:printcolumn:name="StartTime",type="date",JSONPath=".spec.startTime",priority=1 | ||
| // +kubebuilder:printcolumn:name="EndTime",type="date",JSONPath=".spec.endTime",priority=1 | ||
|
|
||
| // CommittedResource is the Schema for the committedresources API | ||
| type CommittedResource struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
|
|
||
| // +optional | ||
| metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` | ||
|
|
||
| // +required | ||
| Spec CommittedResourceSpec `json:"spec"` | ||
|
|
||
| // +optional | ||
| Status CommittedResourceStatus `json:"status,omitempty,omitzero"` | ||
| } | ||
|
|
||
| // +kubebuilder:object:root=true | ||
|
|
||
| // CommittedResourceList contains a list of CommittedResource | ||
| type CommittedResourceList struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
| metav1.ListMeta `json:"metadata,omitempty"` | ||
| Items []CommittedResource `json:"items"` | ||
| } | ||
|
|
||
| func init() { | ||
| SchemeBuilder.Register(&CommittedResource{}, &CommittedResourceList{}) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we not work in multiples of smallest flavor here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CRD should be self contained, so not depending implicitly on e.g. flavor group definition, also considering that we were told the groups are likely to change afterwards. So the Liquid API will do the translation and writes the raw amount here