Skip to content

Commit 9e90e91

Browse files
committed
implement accessrequest controller
1 parent 26438f0 commit 9e90e91

25 files changed

+645
-100
lines changed

api/clusters/v1alpha1/clusterrequest_types.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type ClusterRequestSpec struct {
1111
Purpose string `json:"purpose"`
1212
}
1313

14-
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.clusterRef) || has(self.clusterRef)", message="clusterRef may not be removed once set"
14+
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.cluster) || has(self.cluster)", message="cluster may not be removed once set"
1515
type ClusterRequestStatus struct {
1616
CommonStatus `json:",inline"`
1717

@@ -23,8 +23,8 @@ type ClusterRequestStatus struct {
2323
// Cluster is the reference to the Cluster that was returned as a result of a granted request.
2424
// 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.
2525
// +optional
26-
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="clusterRef is immutable"
27-
Cluster *NamespacedObjectReference `json:"clusterRef,omitempty"`
26+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="cluster is immutable"
27+
Cluster *NamespacedObjectReference `json:"cluster,omitempty"`
2828
}
2929

3030
type RequestPhase string
@@ -49,6 +49,8 @@ func (p RequestPhase) IsPending() bool {
4949
// +kubebuilder:selectablefield:JSONPath=".status.phase"
5050
// +kubebuilder:printcolumn:JSONPath=".spec.purpose",name="Purpose",type=string
5151
// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Phase",type=string
52+
// +kubebuilder:printcolumn:JSONPath=".status.cluster.name",name="Cluster",type=string
53+
// +kubebuilder:printcolumn:JSONPath=".status.cluster.namespace",name="Cluster-NS",type=string
5254

5355
// ClusterRequest is the Schema for the clusters API
5456
type ClusterRequest struct {

api/clusters/v1alpha1/constants.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,28 +57,30 @@ const (
5757

5858
const (
5959
// ClusterLabel can be used on CRDs to indicate onto which cluster they should be deployed.
60-
ClusterLabel = "openmcp.cloud/cluster"
60+
ClusterLabel = ParentGroupName + "/cluster"
6161
// OperationAnnotation is used to trigger specific operations on resources.
62-
OperationAnnotation = "openmcp.cloud/operation"
62+
OperationAnnotation = ParentGroupName + "/operation"
6363
// OperationAnnotationValueIgnore is used to ignore the resource.
6464
OperationAnnotationValueIgnore = "ignore"
6565
// OperationAnnotationValueReconcile is used to trigger a reconcile on the resource.
6666
OperationAnnotationValueReconcile = "reconcile"
6767

6868
// K8sVersionAnnotation can be used to display the k8s version of the cluster.
69-
K8sVersionAnnotation = "clusters.openmcp.cloud/k8sversion"
69+
K8sVersionAnnotation = GroupName + "/k8sversion"
7070
// ProviderInfoAnnotation can be used to display provider-specific information about the cluster.
71-
ProviderInfoAnnotation = "clusters.openmcp.cloud/providerinfo"
71+
ProviderInfoAnnotation = GroupName + "/providerinfo"
7272
// ProfileNameAnnotation can be used to display the actual name (not the hash) of the cluster profile.
73-
ProfileNameAnnotation = "clusters.openmcp.cloud/profile"
73+
ProfileNameAnnotation = GroupName + "/profile"
7474
// EnvironmentAnnotation can be used to display the environment of the cluster.
75-
EnvironmentAnnotation = "clusters.openmcp.cloud/environment"
75+
EnvironmentAnnotation = GroupName + "/environment"
7676
// ProviderAnnotation can be used to display the provider of the cluster.
77-
ProviderAnnotation = "clusters.openmcp.cloud/provider"
77+
ProviderAnnotation = GroupName + "/provider"
7878

7979
// DeleteWithoutRequestsLabel marks that the corresponding cluster can be deleted if the scheduler removes the last request pointing to it.
8080
// Its value must be "true" for the label to take effect.
81-
DeleteWithoutRequestsLabel = "clusters.openmcp.cloud/delete-without-requests"
81+
DeleteWithoutRequestsLabel = GroupName + "/delete-without-requests"
82+
// ProviderLabel is used to indicate the provider that is responsible for an AccessRequest.
83+
ProviderLabel = "provider." + GroupName
8284
)
8385

8486
const (

api/clusters/v1alpha1/constants/reasons.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const (
55
ReasonOnboardingClusterInteractionProblem = "OnboardingClusterInteractionProblem"
66
// ReasonPlatformClusterInteractionProblem is used when the platform cluster cannot be reached.
77
ReasonPlatformClusterInteractionProblem = "PlatformClusterInteractionProblem"
8+
// ReasonInvalidReference means that a reference points to a non-existing or otherwise invalid object.
9+
ReasonInvalidReference = "InvalidReference"
810
// ReasonConfigurationProblem indicates that something is configured incorrectly.
911
ReasonConfigurationProblem = "ConfigurationProblem"
1012
// ReasonInternalError indicates that something went wrong internally.

api/clusters/v1alpha1/groupversion_info.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import (
77
"sigs.k8s.io/controller-runtime/pkg/scheme"
88
)
99

10-
const GroupName = "clusters.openmcp.cloud"
10+
const ParentGroupName = "openmcp.cloud"
11+
const GroupName = "clusters." + ParentGroupName
1112

1213
var (
1314
// GroupVersion is group version used to register these objects

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ spec:
2626
- jsonPath: .status.phase
2727
name: Phase
2828
type: string
29+
- jsonPath: .status.cluster.name
30+
name: Cluster
31+
type: string
32+
- jsonPath: .status.cluster.namespace
33+
name: Cluster-NS
34+
type: string
2935
name: v1alpha1
3036
schema:
3137
openAPIV3Schema:
@@ -62,7 +68,7 @@ spec:
6268
rule: self == oldSelf
6369
status:
6470
properties:
65-
clusterRef:
71+
cluster:
6672
description: |-
6773
Cluster is the reference to the Cluster that was returned as a result of a granted request.
6874
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.
@@ -79,7 +85,7 @@ spec:
7985
- namespace
8086
type: object
8187
x-kubernetes-validations:
82-
- message: clusterRef is immutable
88+
- message: cluster is immutable
8389
rule: self == oldSelf
8490
conditions:
8591
description: Conditions contains the conditions.
@@ -147,8 +153,8 @@ spec:
147153
- phase
148154
type: object
149155
x-kubernetes-validations:
150-
- message: clusterRef may not be removed once set
151-
rule: '!has(oldSelf.clusterRef) || has(self.clusterRef)'
156+
- message: cluster may not be removed once set
157+
rule: '!has(oldSelf.cluster) || has(self.cluster)'
152158
type: object
153159
selectableFields:
154160
- jsonPath: .spec.purpose

cmd/openmcp-operator/app/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (o *InitOptions) Run(ctx context.Context) error {
8080
crdManager.AddCRDLabelToClusterMapping(clustersv1alpha1.PURPOSE_ONBOARDING, o.Clusters.Onboarding)
8181
crdManager.AddCRDLabelToClusterMapping(clustersv1alpha1.PURPOSE_PLATFORM, o.Clusters.Platform)
8282

83-
if err := crdManager.CreateOrUpdateCRDs(ctx, nil); err != nil {
83+
if err := crdManager.CreateOrUpdateCRDs(ctx, &log); err != nil {
8484
return fmt.Errorf("error creating/updating CRDs: %w", err)
8585
}
8686

cmd/openmcp-operator/app/run.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/openmcp-project/openmcp-operator/api/install"
2727
"github.com/openmcp-project/openmcp-operator/api/provider/v1alpha1"
28+
"github.com/openmcp-project/openmcp-operator/internal/controllers/accessrequest"
2829
"github.com/openmcp-project/openmcp-operator/internal/controllers/provider"
2930
"github.com/openmcp-project/openmcp-operator/internal/controllers/scheduler"
3031
)
@@ -33,6 +34,7 @@ var setupLog logging.Logger
3334
var allControllers = []string{
3435
strings.ToLower(scheduler.ControllerName),
3536
strings.ToLower(provider.ControllerName),
37+
strings.ToLower(accessrequest.ControllerName),
3638
}
3739

3840
func NewRunCommand(so *SharedOptions) *cobra.Command {
@@ -299,6 +301,13 @@ func (o *RunOptions) Run(ctx context.Context) error {
299301
}
300302
}
301303

304+
// setup accessrequest controller
305+
if slices.Contains(o.Controllers, strings.ToLower(accessrequest.ControllerName)) {
306+
if err := accessrequest.NewAccessRequestReconciler(o.Clusters.Platform, o.Config.AccessRequest).SetupWithManager(mgr); err != nil {
307+
return fmt.Errorf("unable to setup accessrequest controller: %w", err)
308+
}
309+
}
310+
302311
// setup deployment controller
303312
if slices.Contains(o.Controllers, strings.ToLower(provider.ControllerName)) {
304313
utilruntime.Must(clientgoscheme.AddToScheme(mgr.GetScheme()))

docs/controller/accessrequest.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# AccessRequest Controller
2+
3+
The _AccessRequest Controller_ is responsible for labelling `AccessRequest` resources with the name of the ClusterProvider that is responsible for them.
4+
5+
This is needed because the information, which ClusterProvider is responsible for answering the `AccessRequest` is contained in the referenced `ClusterProfile`. Depending on `AccessRequest`'s spec, a `Cluster` and potentially also a `ClusterRequest` must be fetched before the `ClusterProfile` is known, which then has to be fetched too. If multiple ClusterProviders are running in the cluster, all of them would need to fetch these resources, only for all but one of them to notice that they are not responsible and don't have to do anything.
6+
7+
To increase performance and simplify reconciliation logic in the individual ClusterProviders, this central AccessRequest controller takes over the task of figuring out the responsible ClusterProvider and adds a `provider.clusters.openmcp.cloud` label with its name to the `AccessRequest` resource. It reacts only on resources which do not yet have this label, so it should reconcile each `AccessRequest` only once (excluding repeated reconciliations due to errors).
8+
9+
ClusterProviders should only reconcile `AccessRequest` resources where the value of the `provider.clusters.openmcp.cloud` label matches their own provider name and ignore resources with other values or if the label is missing completely.
10+
11+
## Configuration
12+
13+
The AccessRequest controller is run as long as `accessrequest` is included in the `--controllers` flag. It is included by default.
14+
15+
The entire configuration for the AccessRequest controller is optional.
16+
```yaml
17+
accessRequest: # optional
18+
selector: # optional
19+
matchLabels: <...> # optional
20+
matchExpressions: <...> # optional
21+
```
22+
23+
The following fields can be specified inside the `accessRequest` node:
24+
- `selector` _(optional)_
25+
- A standard k8s label selector, as it is also used in Deployments, for example. If specified, only `AccessRequest` resources matching the selector are reconciled by the controller. This can be used to distribute resources between multiple instances of the AccessRequest controller watching the same cluster.

internal/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ type Config struct {
2626

2727
// Scheduler is the configuration for the cluster scheduler.
2828
Scheduler *SchedulerConfig `json:"scheduler,omitempty"`
29+
30+
// AccessRequest is the configuration for the access request controller.
31+
AccessRequest *AccessRequestConfig `json:"accessRequest,omitempty"`
2932
}
3033

3134
// Dump is used for logging and debugging purposes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
6+
"k8s.io/apimachinery/pkg/util/validation/field"
7+
)
8+
9+
type AccessRequestConfig struct {
10+
// If set, only AccessRequests that match the selector will be reconciled.
11+
Selector *Selector `json:"selector,omitempty"`
12+
}
13+
14+
func (c *AccessRequestConfig) Validate(fldPath *field.Path) error {
15+
return c.Selector.Validate(fldPath.Child("selector"))
16+
}
17+
18+
func (c *AccessRequestConfig) Complete(fldPath *field.Path) error {
19+
if err := c.Selector.Complete(fldPath.Child("selector")); err != nil {
20+
return fmt.Errorf("error completing selector: %w", err)
21+
}
22+
23+
return nil
24+
}

0 commit comments

Comments
 (0)