Skip to content

Commit ed215d8

Browse files
committed
pass Cluster conditions to MCP
1 parent 50157e8 commit ed215d8

File tree

4 files changed

+124
-16
lines changed

4 files changed

+124
-16
lines changed

api/core/v2alpha1/constants.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const (
2424
ConditionMeta = "Meta"
2525

2626
ConditionClusterRequestReady = "ClusterRequestReady"
27+
ConditionClusterConditionsSynced = "ClusterConditionsSynced"
28+
ConditionPrefixClusterCondition = "Cluster."
2729
ConditionPrefixOIDCAccessReady = "OIDCAccessReady."
2830
ConditionAllAccessReady = "AllAccessReady"
2931
ConditionAllServicesDeleted = "AllServicesDeleted"

internal/controllers/managedcontrolplane/clusters.go

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ import (
1717
corev2alpha1 "github.com/openmcp-project/openmcp-operator/api/core/v2alpha1"
1818
)
1919

20-
func (r *ManagedControlPlaneReconciler) deleteRelatedClusterRequests(ctx context.Context, mcp *corev2alpha1.ManagedControlPlaneV2, platformNamespace string) (sets.Set[string], errutils.ReasonableError) {
20+
func (r *ManagedControlPlaneReconciler) deleteRelatedClusterRequests(ctx context.Context, mcp *corev2alpha1.ManagedControlPlaneV2, platformNamespace string) (sets.Set[string], *clustersv1alpha1.Cluster, errutils.ReasonableError) {
2121
log := logging.FromContextOrPanic(ctx)
2222

2323
// delete depending cluster requests, if any
2424
crNames := sets.New[string]()
2525

2626
if mcp == nil {
2727
log.Debug("MCP is nil, no need to check for cluster requests")
28-
return crNames, nil
28+
return crNames, nil, nil
2929
}
3030

3131
// identify cluster request finalizers
@@ -37,7 +37,7 @@ func (r *ManagedControlPlaneReconciler) deleteRelatedClusterRequests(ctx context
3737

3838
if crNames.Len() == 0 {
3939
log.Debug("No cluster request finalizers found on MCP")
40-
return crNames, nil
40+
return crNames, nil, nil
4141
}
4242

4343
// fetch cluster requests, if any exist
@@ -56,18 +56,34 @@ func (r *ManagedControlPlaneReconciler) deleteRelatedClusterRequests(ctx context
5656
resources[crName] = cr
5757
}
5858
if rerr := errs.Aggregate(); rerr != nil {
59-
return sets.KeySet(resources), rerr
59+
return sets.KeySet(resources), nil, rerr
6060
}
6161

6262
// delete cluster requests
63+
var cluster *clustersv1alpha1.Cluster
6364
errs = errutils.NewReasonableErrorList()
6465
for crName, cr := range resources {
65-
if crName == mcp.Name && len(resources) > 1 {
66-
// skip the MCP's main ClusterRequest for now
67-
// we want to make sure that all other ClusterRequests are deleted first
68-
// in case the corresponding clusters are hosting resources that depend on the MCP cluster
69-
log.Debug("Skipping deletion of MCP's primary ClusterRequest, because there are other ClusterRequests to delete first", "crName", crName, "namespace", cr.GetNamespace())
70-
continue
66+
if crName == mcp.Name {
67+
// this is the primary ClusterRequest for the MCP cluster
68+
// try to fetch the corresponding Cluster resource
69+
// to sync its conditions to the MCP
70+
if cr != nil && cr.Status.Cluster != nil {
71+
cluster = &clustersv1alpha1.Cluster{}
72+
cluster.Name = cr.Status.Cluster.Name
73+
cluster.Namespace = cr.Status.Cluster.Namespace
74+
if err := r.PlatformCluster.Client().Get(ctx, client.ObjectKeyFromObject(cluster), cluster); err != nil {
75+
// only log the error, this is not critical and should not break the function
76+
log.Error(fmt.Errorf("unable to get Cluster '%s/%s': %w", cluster.Namespace, cluster.Name, err), "error trying to fetch the primary MCP Cluster resource for condition sync")
77+
cluster = nil
78+
}
79+
}
80+
if len(resources) > 1 {
81+
// skip the MCP's main ClusterRequest for now
82+
// we want to make sure that all other ClusterRequests are deleted first
83+
// in case the corresponding clusters are hosting resources that depend on the MCP cluster
84+
log.Debug("Skipping deletion of MCP's primary ClusterRequest, because there are other ClusterRequests to delete first", "crName", crName, "namespace", cr.GetNamespace())
85+
continue
86+
}
7187
}
7288
if !cr.GetDeletionTimestamp().IsZero() {
7389
log.Debug("ClusterRequest resource already marked for deletion", "crName", crName, "namespace", cr.GetNamespace())
@@ -84,8 +100,8 @@ func (r *ManagedControlPlaneReconciler) deleteRelatedClusterRequests(ctx context
84100
}
85101
}
86102
if rerr := errs.Aggregate(); rerr != nil {
87-
return sets.KeySet(resources), rerr
103+
return sets.KeySet(resources), cluster, rerr
88104
}
89105

90-
return sets.KeySet(resources), nil
106+
return sets.KeySet(resources), cluster, nil
91107
}

internal/controllers/managedcontrolplane/controller.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,26 @@ func (r *ManagedControlPlaneReconciler) handleCreateOrUpdate(ctx context.Context
236236
log.Debug("ClusterRequest is ready", "clusterRequestName", cr.Name, "clusterRequestNamespace", cr.Namespace)
237237
createCon(corev2alpha1.ConditionClusterRequestReady, metav1.ConditionTrue, "", "ClusterRequest is ready")
238238

239+
// fetch Cluster conditions to display them on the MCP
240+
cluster := &clustersv1alpha1.Cluster{}
241+
if cr.Status.Cluster == nil {
242+
// should not happen if the ClusterRequest is granted
243+
rr.ReconcileError = errutils.WithReason(fmt.Errorf("ClusterRequest '%s/%s' does not have a ClusterRef set", cr.Namespace, cr.Name), cconst.ReasonInternalError)
244+
createCon(corev2alpha1.ConditionClusterConditionsSynced, metav1.ConditionFalse, rr.ReconcileError.Reason(), rr.ReconcileError.Error())
245+
return rr
246+
}
247+
cluster.Name = cr.Status.Cluster.Name
248+
cluster.Namespace = cr.Status.Cluster.Namespace
249+
if err := r.PlatformCluster.Client().Get(ctx, client.ObjectKeyFromObject(cluster), cluster); err != nil {
250+
rr.ReconcileError = errutils.WithReason(fmt.Errorf("unable to get Cluster '%s/%s': %w", cluster.Namespace, cluster.Name, err), cconst.ReasonPlatformClusterInteractionProblem)
251+
createCon(corev2alpha1.ConditionClusterConditionsSynced, metav1.ConditionFalse, rr.ReconcileError.Reason(), rr.ReconcileError.Error())
252+
return rr
253+
}
254+
for _, con := range cluster.Status.Conditions {
255+
createCon(corev2alpha1.ConditionPrefixClusterCondition+con.Type, con.Status, con.Reason, con.Message)
256+
}
257+
createCon(corev2alpha1.ConditionClusterConditionsSynced, metav1.ConditionTrue, "", "Cluster conditions have been synced to MCP")
258+
239259
// manage AccessRequests
240260
allAccessReady, removeConditions, rerr := r.manageAccessRequests(ctx, mcp, platformNamespace, cr, createCon)
241261
rr.ConditionsToRemove = removeConditions.UnsortedList()
@@ -253,6 +273,7 @@ func (r *ManagedControlPlaneReconciler) handleCreateOrUpdate(ctx context.Context
253273
return rr
254274
}
255275

276+
//nolint:gocyclo
256277
func (r *ManagedControlPlaneReconciler) handleDelete(ctx context.Context, mcp *corev2alpha1.ManagedControlPlaneV2) ReconcileResult {
257278
log := logging.FromContextOrPanic(ctx)
258279
log.Info("Handling deletion of ManagedControlPlane resource")
@@ -319,12 +340,28 @@ func (r *ManagedControlPlaneReconciler) handleDelete(ctx context.Context, mcp *c
319340
log.Debug("All AccessRequests deleted")
320341

321342
// delete cluster requests related to this MCP
322-
remainingCRs, rerr := r.deleteRelatedClusterRequests(ctx, mcp, platformNamespace)
343+
remainingCRs, primaryCluster, rerr := r.deleteRelatedClusterRequests(ctx, mcp, platformNamespace)
323344
if rerr != nil {
324345
rr.ReconcileError = rerr
325346
createCon(corev2alpha1.ConditionAllClusterRequestsDeleted, metav1.ConditionFalse, rr.ReconcileError.Reason(), rr.ReconcileError.Error())
326347
return rr
327348
}
349+
if primaryCluster != nil {
350+
// sync Cluster conditions to the MCP
351+
for _, con := range primaryCluster.Status.Conditions {
352+
createCon(corev2alpha1.ConditionPrefixClusterCondition+con.Type, con.Status, con.Reason, con.Message)
353+
}
354+
createCon(corev2alpha1.ConditionClusterConditionsSynced, metav1.ConditionTrue, "", "Cluster conditions have been synced to MCP")
355+
} else {
356+
// since this point is only reached if no error occurred during r.deleteRelatedClusterRequests, we can assume that the primaryCluster is nil because it does not exist
357+
for _, con := range mcp.Status.Conditions {
358+
// remove all conditions that were synced from the Cluster from the MCP to avoid having unhealthy leftovers
359+
if strings.HasPrefix(con.Type, corev2alpha1.ConditionPrefixClusterCondition) {
360+
rr.ConditionsToRemove = append(rr.ConditionsToRemove, con.Type)
361+
}
362+
}
363+
createCon(corev2alpha1.ConditionClusterConditionsSynced, metav1.ConditionTrue, "", "Primary Cluster for MCP does not exist anymore")
364+
}
328365
finalizersToRemove := sets.New(filters.FilterSlice(mcp.Finalizers, func(args ...any) bool {
329366
fin, ok := args[0].(string)
330367
if !ok {

internal/controllers/managedcontrolplane/controller_test.go

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func defaultTestSetup(testDirPathSegments ...string) (*managedcontrolplane.Manag
6262
}
6363
envB := testutils.NewComplexEnvironmentBuilder().
6464
WithFakeClient(platform, install.InstallOperatorAPIsPlatform(runtime.NewScheme())).
65-
WithDynamicObjectsWithStatus(platform, &clustersv1alpha1.ClusterRequest{}, &clustersv1alpha1.AccessRequest{}).
65+
WithDynamicObjectsWithStatus(platform, &clustersv1alpha1.ClusterRequest{}, &clustersv1alpha1.AccessRequest{}, &clustersv1alpha1.Cluster{}).
6666
WithFakeClient(onboarding, install.InstallOperatorAPIsOnboarding(runtime.NewScheme())).
6767
WithReconcilerConstructor(mcpRec, func(clients ...client.Client) reconcile.Reconciler {
6868
mcpr, err := managedcontrolplane.NewManagedControlPlaneReconciler(clusters.NewTestClusterFromClient(platform, clients[0]), clusters.NewTestClusterFromClient(onboarding, clients[1]), nil, cfg.ManagedControlPlane)
@@ -131,15 +131,44 @@ var _ = Describe("ManagedControlPlane Controller", func() {
131131
WithReason(cconst.ReasonWaitingForClusterRequest)),
132132
))
133133

134-
// fake ClusterRequest ready status
134+
// fake ClusterRequest ready status and Cluster resource
135135
By("fake: ClusterRequest readiness")
136+
cluster := &clustersv1alpha1.Cluster{}
137+
cluster.SetName("cluster-01")
138+
cluster.SetNamespace(platformNamespace)
139+
cluster.Spec.Purposes = []string{rec.Config.MCPClusterPurpose}
140+
Expect(env.Client(platform).Create(env.Ctx, cluster)).To(Succeed())
141+
cluster.Status.Conditions = []metav1.Condition{
142+
{
143+
Type: "TestCondition1",
144+
Status: metav1.ConditionTrue,
145+
Reason: "TestReason",
146+
Message: "This is a test condition",
147+
LastTransitionTime: metav1.Now(),
148+
ObservedGeneration: 1,
149+
},
150+
{
151+
Type: "TestCondition2",
152+
Status: metav1.ConditionFalse,
153+
Reason: "TestReason",
154+
Message: "This is another test condition",
155+
LastTransitionTime: metav1.Now(),
156+
ObservedGeneration: 1,
157+
},
158+
}
159+
Expect(env.Client(platform).Status().Update(env.Ctx, cluster)).To(Succeed())
136160
cr.Status.Phase = clustersv1alpha1.REQUEST_GRANTED
161+
cr.Status.Cluster = &commonapi.ObjectReference{
162+
Name: cluster.Name,
163+
Namespace: cluster.Namespace,
164+
}
137165
Expect(env.Client(platform).Status().Update(env.Ctx, cr)).To(Succeed())
138166

139167
// reconcile the MCP again
140168
// expected outcome:
141169
// - multiple access requests have been created on the platform cluster, one for each configured OIDC provider
142170
// - the mcp has conditions that reflect that it is waiting for the access requests (one for each OIDC provider and one overall one)
171+
// - the mcp has taken over the conditions from the Cluster resource with a prefix
143172
// - the mcp should be requeued with a short requeueAfter duration
144173
By("second MCP reconciliation")
145174
res = env.ShouldReconcile(mcpRec, testutils.RequestFromObject(mcp))
@@ -154,6 +183,19 @@ var _ = Describe("ManagedControlPlane Controller", func() {
154183
WithType(corev2alpha1.ConditionAllAccessReady).
155184
WithStatus(metav1.ConditionFalse).
156185
WithReason(cconst.ReasonWaitingForAccessRequest)),
186+
MatchCondition(TestCondition().
187+
WithType(corev2alpha1.ConditionPrefixClusterCondition+"TestCondition1").
188+
WithStatus(metav1.ConditionTrue).
189+
WithReason("TestReason").
190+
WithMessage("This is a test condition")),
191+
MatchCondition(TestCondition().
192+
WithType(corev2alpha1.ConditionPrefixClusterCondition+"TestCondition2").
193+
WithStatus(metav1.ConditionFalse).
194+
WithReason("TestReason").
195+
WithMessage("This is another test condition")),
196+
MatchCondition(TestCondition().
197+
WithType(corev2alpha1.ConditionClusterConditionsSynced).
198+
WithStatus(metav1.ConditionTrue)),
157199
))
158200
oidcProviders := []commonapi.OIDCProviderConfig{*rec.Config.DefaultOIDCProvider.DeepCopy()}
159201
oidcProviders[0].RoleBindings = mcp.Spec.IAM.RoleBindings
@@ -552,10 +594,12 @@ var _ = Describe("ManagedControlPlane Controller", func() {
552594
WithReason(cconst.ReasonWaitingForClusterRequestDeletion)),
553595
))
554596

555-
// remove finalizer from cr
597+
// remove finalizer from cr and remove Cluster resource
556598
By("fake: removing finalizer from primary ClusterRequest")
557599
controllerutil.RemoveFinalizer(cr, "dummy")
558600
Expect(env.Client(platform).Update(env.Ctx, cr)).To(Succeed())
601+
Expect(env.Client(platform).Delete(env.Ctx, cluster)).To(Succeed())
602+
Expect(env.Client(platform).Get(env.Ctx, client.ObjectKeyFromObject(cluster), cluster)).To(MatchError(apierrors.IsNotFound, "IsNotFound"))
559603

560604
// add finalizer to MCP namespace
561605
By("fake: adding finalizer to MCP namespace")
@@ -567,6 +611,7 @@ var _ = Describe("ManagedControlPlane Controller", func() {
567611
// expected outcome:
568612
// - the MCP namespace has a deletion timestamp
569613
// - the MCP has a condition stating that it is waiting for the MCP namespace to be deleted
614+
// - the Cluster conditions should have been removed
570615
// - the MCP should be requeued with a short requeueAfter duration
571616
By("fifth MCP reconciliation after delete")
572617
res = env.ShouldReconcile(mcpRec, testutils.RequestFromObject(mcp))
@@ -580,6 +625,14 @@ var _ = Describe("ManagedControlPlane Controller", func() {
580625
WithType(corev2alpha1.ConditionMeta).
581626
WithStatus(metav1.ConditionFalse).
582627
WithReason(cconst.ReasonWaitingForNamespaceDeletion)),
628+
MatchCondition(TestCondition().
629+
WithType(corev2alpha1.ConditionClusterConditionsSynced).
630+
WithStatus(metav1.ConditionTrue)),
631+
))
632+
Expect(mcp.Status.Conditions).ToNot(ContainElements(
633+
MatchFields(IgnoreExtras, Fields{
634+
"Type": ContainSubstring(corev2alpha1.ConditionPrefixClusterCondition),
635+
}),
583636
))
584637

585638
// remove finalizer from MCP namespace

0 commit comments

Comments
 (0)