Skip to content

Commit b138429

Browse files
authored
add simple Cluster scheduler implementation (#13)
* implement cluster scheduler * fix scheduler setup and distinguish between cluster and clusterrequest label selectors * add scheduler unit tests * rename fields * add 'local' selectors to cluster definitions in scheduler config * add scheduler docs * move cluster resources to platform cluster * fix kubebuilder CRD generation annotations * add scheduler cluster deletion and minor fixes * add helper function for request phase * enhance help text for --controllers flag * fix task generate * reduce cyclomatic complexity
1 parent 87c7e71 commit b138429

Some content is hidden

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

52 files changed

+1712
-720
lines changed

Taskfile.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ includes:
99
NESTED_MODULES: api
1010
API_DIRS: '{{.ROOT_DIR}}/api/provider/v1alpha1/... {{.ROOT_DIR}}/api/clusters/v1alpha1/...'
1111
MANIFEST_OUT: '{{.ROOT_DIR}}/api/crds/manifests'
12-
CODE_DIRS: '{{.ROOT_DIR}}/cmd/... {{.ROOT_DIR}}/internal/... {{.ROOT_DIR}}/test/... {{.ROOT_DIR}}/api/provider/v1alpha1/... {{.ROOT_DIR}}/api/clusters/v1alpha1/...'
12+
CODE_DIRS: '{{.ROOT_DIR}}/cmd/... {{.ROOT_DIR}}/internal/... {{.ROOT_DIR}}/api/provider/v1alpha1/... {{.ROOT_DIR}}/api/clusters/v1alpha1/...'
1313
COMPONENTS: 'openmcp-operator'
1414
REPO_URL: 'https://github.com/openmcp-project/openmcp-operator'
1515
GENERATE_DOCS_INDEX: "true"

api/clusters/v1alpha1/accessrequest_types.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ type AccessRequestSpec struct {
1212
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="clusterRef is immutable"
1313
ClusterRef NamespacedObjectReference `json:"clusterRef"`
1414

15+
// RequestRef is the reference to the ClusterRequest for whose Cluster access is requested.
16+
// Exactly one of clusterRef or requestRef must be set.
17+
// This value is immutable.
18+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="requestRef is immutable"
19+
RequestRef NamespacedObjectReference `json:"requestRef"`
20+
1521
// Permissions are the requested permissions.
1622
Permissions []PermissionsRequest `json:"permissions"`
1723
}
@@ -32,14 +38,20 @@ type AccessRequestStatus struct {
3238
CommonStatus `json:",inline"`
3339

3440
// Phase is the current phase of the request.
41+
// +kubebuilder:default=Pending
42+
// +kubebuilder:validation:Enum=Pending;Granted;Denied
3543
Phase RequestPhase `json:"phase"`
3644

37-
// TODO: expose actual access information
45+
// SecretRef holds the reference to the secret that contains the actual credentials.
46+
SecretRef *NamespacedObjectReference `json:"secretRef,omitempty"`
3847
}
3948

4049
// +kubebuilder:object:root=true
4150
// +kubebuilder:subresource:status
42-
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=onboarding"
51+
// +kubebuilder:resource:shortName=ar;areq
52+
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=platform"
53+
// +kubebuilder:selectablefield:JSONPath=".status.phase"
54+
// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Phase",type=string
4355

4456
// AccessRequest is the Schema for the accessrequests API
4557
type AccessRequest struct {

api/clusters/v1alpha1/cluster_types.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
88
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/apimachinery/pkg/util/sets"
910
)
1011

1112
// ClusterSpec defines the desired state of Cluster
@@ -81,15 +82,14 @@ const (
8182

8283
// +kubebuilder:object:root=true
8384
// +kubebuilder:subresource:status
84-
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=onboarding"
85-
// +kubebuilder:selectablefield:JSONPath=".spec.clusterProfileRef.name"
85+
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=platform"
86+
// +kubebuilder:selectablefield:JSONPath=".spec.profile"
8687
// +kubebuilder:printcolumn:JSONPath=".spec.purposes",name="Purposes",type=string
8788
// +kubebuilder:printcolumn:JSONPath=`.status.phase`,name="Phase",type=string
8889
// +kubebuilder:printcolumn:JSONPath=`.metadata.annotations["clusters.openmcp.cloud/k8sversion"]`,name="Version",type=string
8990
// +kubebuilder:printcolumn:JSONPath=`.metadata.annotations["clusters.openmcp.cloud/profile"]`,name="Profile",type=string
90-
// +kubebuilder:printcolumn:JSONPath=`.metadata.labels["environment.clusters.openmcp.cloud"]`,name="Env",type=string,priority=10
9191
// +kubebuilder:printcolumn:JSONPath=`.metadata.labels["provider.clusters.openmcp.cloud"]`,name="Provider",type=string, priority=10
92-
// +kubebuilder:printcolumn:JSONPath=".spec.clusterProfileRef.name",name="ProfileRef",type=string,priority=10
92+
// +kubebuilder:printcolumn:JSONPath=".spec.profile",name="ProfileRef",type=string,priority=10
9393
// +kubebuilder:printcolumn:JSONPath=`.metadata.annotations["clusters.openmcp.cloud/providerinfo"]`,name="Info",type=string,priority=10
9494
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
9595

@@ -132,12 +132,19 @@ func (cs *ClusterStatus) SetProviderStatus(from any) error {
132132

133133
// GetTenancyCount returns the number of ClusterRequests currently pointing to this cluster.
134134
// This is determined by counting the finalizers that have the corresponding prefix.
135+
// Note that only unique finalizers are counted, so if there are multiple identical request finalizers
136+
// (which should not happen), this method's return value might not match the actual number of finalizers with the prefix.
135137
func (c *Cluster) GetTenancyCount() int {
136-
count := 0
138+
return c.GetRequestUIDs().Len()
139+
}
140+
141+
// GetRequestUIDs returns the UIDs of all ClusterRequests that have marked this cluster with a corresponding finalizer.
142+
func (c *Cluster) GetRequestUIDs() sets.Set[string] {
143+
res := sets.New[string]()
137144
for _, fin := range c.Finalizers {
138145
if strings.HasPrefix(fin, RequestFinalizerOnClusterPrefix) {
139-
count++
146+
res.Insert(strings.TrimPrefix(fin, RequestFinalizerOnClusterPrefix))
140147
}
141148
}
142-
return count
149+
return res
143150
}

api/clusters/v1alpha1/clusterprofile_types.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ import (
66

77
// ClusterProfileSpec defines the desired state of Provider.
88
type ClusterProfileSpec struct {
9-
// Environment is the environment in which the ClusterProvider resides.
10-
Environment string `json:"environment"`
11-
129
// ProviderRef is a reference to the ClusterProvider
1310
ProviderRef ObjectReference `json:"providerRef"`
1411

@@ -30,11 +27,9 @@ type SupportedK8sVersion struct {
3027

3128
// +kubebuilder:object:root=true
3229
// +kubebuilder:resource:scope=Cluster,shortName=cprof;profile
33-
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=onboarding"
34-
// +kubebuilder:selectablefield:JSONPath=".spec.environment"
30+
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=platform"
3531
// +kubebuilder:selectablefield:JSONPath=".spec.providerRef.name"
3632
// +kubebuilder:selectablefield:JSONPath=".spec.providerConfigRef.name"
37-
// +kubebuilder:printcolumn:JSONPath=".spec.environment",name="Env",type=string
3833
// +kubebuilder:printcolumn:JSONPath=".spec.providerRef.name",name="Provider",type=string
3934
// +kubebuilder:printcolumn:JSONPath=".spec.providerConfigRef.name",name="Config",type=string
4035

api/clusters/v1alpha1/clusterrequest_types.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,35 @@ type ClusterRequestStatus struct {
1616
CommonStatus `json:",inline"`
1717

1818
// Phase is the current phase of the request.
19+
// +kubebuilder:default=Pending
20+
// +kubebuilder:validation:Enum=Pending;Granted;Denied
1921
Phase RequestPhase `json:"phase"`
2022

21-
// ClusterRef is the reference to the Cluster that was returned as a result of a granted request.
23+
// Cluster is the reference to the Cluster that was returned as a result of a granted request.
2224
// Note that this information needs to be recoverable in case this status is lost, e.g. by adding a back reference in form of a finalizer to the Cluster resource.
2325
// +optional
2426
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="clusterRef is immutable"
25-
ClusterRef *NamespacedObjectReference `json:"clusterRef,omitempty"`
27+
Cluster *NamespacedObjectReference `json:"clusterRef,omitempty"`
2628
}
2729

2830
type RequestPhase string
2931

32+
func (p RequestPhase) IsGranted() bool {
33+
return p == REQUEST_GRANTED
34+
}
35+
36+
func (p RequestPhase) IsDenied() bool {
37+
return p == REQUEST_DENIED
38+
}
39+
40+
func (p RequestPhase) IsPending() bool {
41+
return p == "" || p == REQUEST_PENDING
42+
}
43+
3044
// +kubebuilder:object:root=true
3145
// +kubebuilder:subresource:status
32-
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=onboarding"
46+
// +kubebuilder:resource:shortName=cr;creq
47+
// +kubebuilder:metadata:labels="openmcp.cloud/cluster=platform"
3348
// +kubebuilder:selectablefield:JSONPath=".spec.purpose"
3449
// +kubebuilder:selectablefield:JSONPath=".status.phase"
3550
// +kubebuilder:printcolumn:JSONPath=".spec.purpose",name="Purpose",type=string

api/clusters/v1alpha1/constants.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ const (
7575
EnvironmentAnnotation = "clusters.openmcp.cloud/environment"
7676
// ProviderAnnotation can be used to display the provider of the cluster.
7777
ProviderAnnotation = "clusters.openmcp.cloud/provider"
78+
79+
// DeleteWithoutRequestsLabel marks that the corresponding cluster can be deleted if the scheduler removes the last request pointing to it.
80+
// Its value must be "true" for the label to take effect.
81+
DeleteWithoutRequestsLabel = "clusters.openmcp.cloud/delete-without-requests"
7882
)
7983

8084
const (

api/clusters/v1alpha1/zz_generated.deepcopy.go

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

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

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@ metadata:
55
annotations:
66
controller-gen.kubebuilder.io/version: v0.17.3
77
labels:
8-
openmcp.cloud/cluster: onboarding
8+
openmcp.cloud/cluster: platform
99
name: accessrequests.clusters.openmcp.cloud
1010
spec:
1111
group: clusters.openmcp.cloud
1212
names:
1313
kind: AccessRequest
1414
listKind: AccessRequestList
1515
plural: accessrequests
16+
shortNames:
17+
- ar
18+
- areq
1619
singular: accessrequest
1720
scope: Namespaced
1821
versions:
19-
- name: v1alpha1
22+
- additionalPrinterColumns:
23+
- jsonPath: .status.phase
24+
name: Phase
25+
type: string
26+
name: v1alpha1
2027
schema:
2128
openAPIV3Schema:
2229
description: AccessRequest is the Schema for the accessrequests API
@@ -125,9 +132,30 @@ spec:
125132
- rules
126133
type: object
127134
type: array
135+
requestRef:
136+
description: |-
137+
RequestRef is the reference to the ClusterRequest for whose Cluster access is requested.
138+
Exactly one of clusterRef or requestRef must be set.
139+
This value is immutable.
140+
properties:
141+
name:
142+
description: Name is the name of the referenced resource.
143+
minLength: 1
144+
type: string
145+
namespace:
146+
description: Namespace is the namespace of the referenced resource.
147+
type: string
148+
required:
149+
- name
150+
- namespace
151+
type: object
152+
x-kubernetes-validations:
153+
- message: requestRef is immutable
154+
rule: self == oldSelf
128155
required:
129156
- clusterRef
130157
- permissions
158+
- requestRef
131159
type: object
132160
status:
133161
description: AccessRequestStatus defines the observed state of AccessRequest
@@ -181,18 +209,40 @@ spec:
181209
format: int64
182210
type: integer
183211
phase:
212+
default: Pending
184213
description: Phase is the current phase of the request.
214+
enum:
215+
- Pending
216+
- Granted
217+
- Denied
185218
type: string
186219
reason:
187220
description: Reason is expected to contain a CamelCased string that
188221
provides further information in a machine-readable format.
189222
type: string
223+
secretRef:
224+
description: SecretRef holds the reference to the secret that contains
225+
the actual credentials.
226+
properties:
227+
name:
228+
description: Name is the name of the referenced resource.
229+
minLength: 1
230+
type: string
231+
namespace:
232+
description: Namespace is the namespace of the referenced resource.
233+
type: string
234+
required:
235+
- name
236+
- namespace
237+
type: object
190238
required:
191239
- lastReconcileTime
192240
- observedGeneration
193241
- phase
194242
type: object
195243
type: object
244+
selectableFields:
245+
- jsonPath: .status.phase
196246
served: true
197247
storage: true
198248
subresources:

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ metadata:
55
annotations:
66
controller-gen.kubebuilder.io/version: v0.17.3
77
labels:
8-
openmcp.cloud/cluster: onboarding
8+
openmcp.cloud/cluster: platform
99
name: clusterprofiles.clusters.openmcp.cloud
1010
spec:
1111
group: clusters.openmcp.cloud
@@ -20,9 +20,6 @@ spec:
2020
scope: Cluster
2121
versions:
2222
- additionalPrinterColumns:
23-
- jsonPath: .spec.environment
24-
name: Env
25-
type: string
2623
- jsonPath: .spec.providerRef.name
2724
name: Provider
2825
type: string
@@ -53,10 +50,6 @@ spec:
5350
spec:
5451
description: ClusterProfileSpec defines the desired state of Provider.
5552
properties:
56-
environment:
57-
description: Environment is the environment in which the ClusterProvider
58-
resides.
59-
type: string
6053
providerConfigRef:
6154
description: ProviderConfigRef is a reference to the provider-specific
6255
configuration.
@@ -94,14 +87,12 @@ spec:
9487
type: object
9588
type: array
9689
required:
97-
- environment
9890
- providerConfigRef
9991
- providerRef
10092
- supportedVersions
10193
type: object
10294
type: object
10395
selectableFields:
104-
- jsonPath: .spec.environment
10596
- jsonPath: .spec.providerRef.name
10697
- jsonPath: .spec.providerConfigRef.name
10798
served: true

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ metadata:
55
annotations:
66
controller-gen.kubebuilder.io/version: v0.17.3
77
labels:
8-
openmcp.cloud/cluster: onboarding
8+
openmcp.cloud/cluster: platform
99
name: clusterrequests.clusters.openmcp.cloud
1010
spec:
1111
group: clusters.openmcp.cloud
1212
names:
1313
kind: ClusterRequest
1414
listKind: ClusterRequestList
1515
plural: clusterrequests
16+
shortNames:
17+
- cr
18+
- creq
1619
singular: clusterrequest
1720
scope: Namespaced
1821
versions:
@@ -61,7 +64,7 @@ spec:
6164
properties:
6265
clusterRef:
6366
description: |-
64-
ClusterRef is the reference to the Cluster that was returned as a result of a granted request.
67+
Cluster is the reference to the Cluster that was returned as a result of a granted request.
6568
Note that this information needs to be recoverable in case this status is lost, e.g. by adding a back reference in form of a finalizer to the Cluster resource.
6669
properties:
6770
name:
@@ -127,7 +130,12 @@ spec:
127130
format: int64
128131
type: integer
129132
phase:
133+
default: Pending
130134
description: Phase is the current phase of the request.
135+
enum:
136+
- Pending
137+
- Granted
138+
- Denied
131139
type: string
132140
reason:
133141
description: Reason is expected to contain a CamelCased string that

0 commit comments

Comments
 (0)