Skip to content

Commit 0d7073a

Browse files
committed
add preemptive scheduling logic
1 parent 808de5b commit 0d7073a

File tree

8 files changed

+233
-56
lines changed

8 files changed

+233
-56
lines changed

api/clusters/v1alpha1/cluster_types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,19 @@ func (c *Cluster) GetRequestUIDs() sets.Set[string] {
152152
}
153153
return res
154154
}
155+
156+
// GetPreemptiveTenancyCount works like GetTenancyCount, but for preemptive ClusterRequests.
157+
func (c *Cluster) GetPreemptiveTenancyCount() int {
158+
return c.GetPreemptiveRequestUIDs().Len()
159+
}
160+
161+
// GetPreemptiveRequestUIDs returns the UIDs of all preemptive ClusterRequests that have marked this cluster with a corresponding finalizer.
162+
func (c *Cluster) GetPreemptiveRequestUIDs() sets.Set[string] {
163+
res := sets.New[string]()
164+
for _, fin := range c.Finalizers {
165+
if strings.HasPrefix(fin, PreemptiveRequestFinalizerOnClusterPrefix) {
166+
res.Insert(strings.TrimPrefix(fin, PreemptiveRequestFinalizerOnClusterPrefix))
167+
}
168+
}
169+
return res
170+
}

api/clusters/v1alpha1/clusterrequest_types.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ type ClusterRequestSpec struct {
99
// Purpose is the purpose of the requested cluster.
1010
// +kubebuilder:validation:MinLength=1
1111
Purpose string `json:"purpose"`
12+
13+
// Preemptive determines whether the request is preemptive.
14+
// Preemptive requests are used to create clusters in advance, so that they are ready when needed.
15+
// AccessRequests for preemptive clusters are not allowed.
16+
// +optional
17+
// +kubebuilder:default=false
18+
Preemptive bool `json:"preemptive,omitempty"`
1219
}
1320

1421
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.cluster) || has(self.cluster)", message="cluster may not be removed once set"
@@ -46,9 +53,11 @@ func (p RequestPhase) IsPending() bool {
4653
// +kubebuilder:resource:shortName=cr;creq
4754
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=platform"
4855
// +kubebuilder:selectablefield:JSONPath=".spec.purpose"
56+
// +kubebuilder:selectablefield:JSONPath=".spec.preemptive"
4957
// +kubebuilder:selectablefield:JSONPath=".status.phase"
5058
// +kubebuilder:printcolumn:JSONPath=".spec.purpose",name="Purpose",type=string
5159
// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Phase",type=string
60+
// +kubebuilder:printcolumn:JSONPath=".spec.preemptive",name="Preemptive",type=string
5261
// +kubebuilder:printcolumn:JSONPath=".status.cluster.name",name="Cluster",type=string
5362
// +kubebuilder:printcolumn:JSONPath=".status.cluster.namespace",name="Cluster-NS",type=string
5463

@@ -67,7 +76,7 @@ type ClusterRequest struct {
6776
type ClusterRequestList struct {
6877
metav1.TypeMeta `json:",inline"`
6978
metav1.ListMeta `json:"metadata,omitempty"`
70-
Items []Cluster `json:"items"`
79+
Items []ClusterRequest `json:"items"`
7180
}
7281

7382
func init() {
@@ -77,5 +86,9 @@ func init() {
7786
// FinalizerForCluster returns the finalizer that is used to mark that a specific request has pointed to a specific cluster.
7887
// Apart from preventing the Cluster's deletion, this information is used to recover the Cluster if the status of the ClusterRequest ever gets lost.
7988
func (cr *ClusterRequest) FinalizerForCluster() string {
80-
return RequestFinalizerOnClusterPrefix + string(cr.UID)
89+
prefix := RequestFinalizerOnClusterPrefix
90+
if cr.Spec.Preemptive {
91+
prefix = PreemptiveRequestFinalizerOnClusterPrefix
92+
}
93+
return prefix + string(cr.UID)
8194
}

api/clusters/v1alpha1/constants.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,6 @@ const (
8282
ClusterRequestFinalizer = GroupName + "/request"
8383
// RequestFinalizerOnClusterPrefix is the prefix for the finalizers that mark a Cluster as being referenced by a ClusterRequest.
8484
RequestFinalizerOnClusterPrefix = "request." + GroupName + "/"
85+
// PreemptiveRequestFinalizerOnClusterPrefix is the prefix for the finalizers that mark a Cluster as being referenced by a preemptive ClusterRequest.
86+
PreemptiveRequestFinalizerOnClusterPrefix = "preemptive." + GroupName + "/"
8587
)

api/clusters/v1alpha1/zz_generated.deepcopy.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/crds/manifests/clusters.openmcp.cloud_clusterrequests.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ spec:
2626
- jsonPath: .status.phase
2727
name: Phase
2828
type: string
29+
- jsonPath: .spec.preemptive
30+
name: Preemptive
31+
type: string
2932
- jsonPath: .status.cluster.name
3033
name: Cluster
3134
type: string
@@ -56,6 +59,13 @@ spec:
5659
type: object
5760
spec:
5861
properties:
62+
preemptive:
63+
default: false
64+
description: |-
65+
Preemptive determines whether the request is preemptive.
66+
Preemptive requests are used to create clusters in advance, so that they are ready when needed.
67+
AccessRequests for preemptive clusters are not allowed.
68+
type: boolean
5969
purpose:
6070
description: Purpose is the purpose of the requested cluster.
6171
minLength: 1
@@ -158,6 +168,7 @@ spec:
158168
type: object
159169
selectableFields:
160170
- jsonPath: .spec.purpose
171+
- jsonPath: .spec.preemptive
161172
- jsonPath: .status.phase
162173
served: true
163174
storage: true

docs/controller/scheduler.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,11 @@ For clusters with unlimited tenancy count, `metadata.generateName` takes precede
120120
By default, the scheduler marks every `Cluster` that it has created itself with a `clusters.openmcp.cloud/delete-without-requests: "true"` label. When a `ClusterRequest` is deleted, the scheduler removes the request's finalizers from all clusters and if it was the last request finalizer on that cluster and the cluster has the aforementioned label, the scheduler will delete the cluster.
121121

122122
To prevent the scheduler from deleting a cluster that was created by it after the last request finalizer has been removed from the `Cluster` resource, add the label with any value except `"true"` to the cluster's template in the scheduler configuration.
123+
124+
Note that the `ClusterRequest` resource is removed immediately and does not wait for potential deletion of the corresponding cluster.
125+
126+
## Preemptive Scheduling
127+
128+
To avoid long waiting times for `ClusterRequest`s, it is possible to request clusters preemptively. A `ClusterRequest` with `spec.preemptive` set to `true` is referred to as a 'preemptive request'. These requests behave like regular requests, with one important difference: a regular request prefers taking over an existing `Cluster` belonging to a preemptive request over creating a new `Cluster`. If that happens, the replaced preemptive request will be rescheduled, potentially resulting in a new `Cluster`.
129+
130+
Think of preemptive requests as reservations for clusters (or workload capacity on shared clusters) which are used by regular requests.

internal/config/config_scheduler.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,13 @@ func (c *SchedulerConfig) Complete(fldPath *field.Path) error {
172172

173173
return nil
174174
}
175+
176+
func (cd *ClusterDefinition) IsExclusive() bool {
177+
return cd.Template.Spec.Tenancy == clustersv1alpha1.TENANCY_EXCLUSIVE
178+
}
179+
func (cd *ClusterDefinition) IsShared() bool {
180+
return cd.Template.Spec.Tenancy == clustersv1alpha1.TENANCY_SHARED
181+
}
182+
func (cd *ClusterDefinition) IsSharedUnlimitedly() bool {
183+
return cd.IsShared() && cd.TenancyCount == 0
184+
}

0 commit comments

Comments
 (0)