@@ -20,11 +20,17 @@ import (
20
20
"context"
21
21
"fmt"
22
22
23
+ cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
23
24
"github.com/pkg/errors"
25
+ corev1 "k8s.io/api/core/v1"
24
26
apierrors "k8s.io/apimachinery/pkg/api/errors"
27
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
+ "k8s.io/apimachinery/pkg/runtime"
25
29
"k8s.io/apimachinery/pkg/types"
30
+ kerrors "k8s.io/apimachinery/pkg/util/errors"
26
31
"k8s.io/client-go/tools/record"
27
32
"k8s.io/klog/v2"
33
+ "k8s.io/utils/ptr"
28
34
ctrl "sigs.k8s.io/controller-runtime"
29
35
"sigs.k8s.io/controller-runtime/pkg/client"
30
36
"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -35,9 +41,15 @@ import (
35
41
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
36
42
rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2"
37
43
expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
44
+ "sigs.k8s.io/cluster-api-provider-aws/v2/exp/utils"
45
+ "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud"
46
+ "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope"
47
+ stsservice "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/sts"
38
48
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
49
+ "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/rosa"
39
50
"sigs.k8s.io/cluster-api-provider-aws/v2/util/paused"
40
51
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
52
+ expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
41
53
"sigs.k8s.io/cluster-api/util"
42
54
"sigs.k8s.io/cluster-api/util/patch"
43
55
"sigs.k8s.io/cluster-api/util/predicates"
@@ -48,16 +60,20 @@ type ROSAClusterReconciler struct {
48
60
client.Client
49
61
Recorder record.EventRecorder
50
62
WatchFilterValue string
63
+ NewStsClient func (cloud.ScopeUsage , cloud.Session , logger.Wrapper , runtime.Object ) stsservice.STSClient
64
+ NewOCMClient func (ctx context.Context , rosaScope * scope.ROSAControlPlaneScope ) (rosa.OCMClient , error )
51
65
}
52
66
53
67
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters,verbs=get;list;watch;update;patch;delete
54
68
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters/status,verbs=get;update;patch
55
69
// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes;rosacontrolplanes/status,verbs=get;list;watch
56
70
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch
71
+ // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools;machinepools/status,verbs=get;list;watch;create
72
+ // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools;rosamachinepools/status,verbs=get;list;watch;create
57
73
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
58
74
59
75
func (r * ROSAClusterReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (_ ctrl.Result , reterr error ) {
60
- log := ctrl . LoggerFrom (ctx )
76
+ log := logger . FromContext (ctx )
61
77
log .Info ("Reconciling ROSACluster" )
62
78
63
79
// Fetch the ROSACluster instance
@@ -70,11 +86,17 @@ func (r *ROSAClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
70
86
return reconcile.Result {}, err
71
87
}
72
88
89
+ if ! rosaCluster .DeletionTimestamp .IsZero () {
90
+ log .Info ("Deleting ROSACluster." )
91
+ return reconcile.Result {}, nil
92
+ }
93
+
73
94
// Fetch the Cluster.
74
95
cluster , err := util .GetOwnerCluster (ctx , r .Client , rosaCluster .ObjectMeta )
75
96
if err != nil {
76
97
return reconcile.Result {}, err
77
98
}
99
+
78
100
if cluster == nil {
79
101
log .Info ("Cluster Controller has not yet set OwnerRef" )
80
102
return reconcile.Result {}, nil
@@ -111,13 +133,78 @@ func (r *ROSAClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
111
133
return reconcile.Result {}, fmt .Errorf ("failed to patch ROSACluster: %w" , err )
112
134
}
113
135
136
+ if controlPlane .Status .Ready {
137
+ rosaScope , err := scope .NewROSAControlPlaneScope (scope.ROSAControlPlaneScopeParams {
138
+ Client : r .Client ,
139
+ Cluster : cluster ,
140
+ ControlPlane : controlPlane ,
141
+ ControllerName : "" ,
142
+ Logger : log ,
143
+ NewStsClient : r .NewStsClient ,
144
+ })
145
+ if err != nil {
146
+ return ctrl.Result {}, fmt .Errorf ("failed to create rosa controlplane scope: %w" , err )
147
+ }
148
+
149
+ if r .NewOCMClient == nil {
150
+ return ctrl.Result {}, fmt .Errorf ("failed to create OCM client: NewOCMClient is nil" )
151
+ }
152
+
153
+ ocmClient , err := r .NewOCMClient (ctx , rosaScope )
154
+ if err != nil || ocmClient == nil {
155
+ return ctrl.Result {}, fmt .Errorf ("failed to create OCM client: %w" , err )
156
+ }
157
+
158
+ // List the ROSA-HCP nodePools and MachinePools
159
+ nodePools , err := ocmClient .GetNodePools (rosaScope .ControlPlane .Status .ID )
160
+ if err != nil {
161
+ return ctrl.Result {}, fmt .Errorf ("failed to get nodePools: %w" , err )
162
+ }
163
+
164
+ rosaMPNames , err := r .getRosaMachinePoolNames (ctx , cluster )
165
+ if err != nil {
166
+ return ctrl.Result {}, fmt .Errorf ("failed to get Rosa machinePool names: %w" , err )
167
+ }
168
+
169
+ // Ensure every NodePool has a MachinePool and create a corresponding MachinePool if it does not exist.
170
+ var errs []error
171
+ for _ , nodePool := range nodePools {
172
+ // continue if nodePool is not in ready state.
173
+ if ! rosa .IsNodePoolReady (nodePool ) {
174
+ continue
175
+ }
176
+ // continue if nodePool exist
177
+ if rosaMPNames [nodePool .ID ()] {
178
+ continue
179
+ }
180
+ // create ROSAMachinePool & MachinePool
181
+ rosaMachinePool , machinePool := r .buildROSAMachinePool (nodePool .ID (), cluster .Name , cluster .Namespace , nodePool )
182
+
183
+ log .Info (fmt .Sprintf ("Create ROSAMachinePool %s" , rosaMachinePool .Name ))
184
+ if err = r .Client .Create (ctx , rosaMachinePool ); err != nil {
185
+ errs = append (errs , err )
186
+ }
187
+
188
+ log .Info (fmt .Sprintf ("Create MachinePool %s" , machinePool .Name ))
189
+ if err = r .Client .Create (ctx , machinePool ); err != nil {
190
+ errs = append (errs , err )
191
+ }
192
+ }
193
+
194
+ if len (errs ) > 0 {
195
+ return ctrl.Result {}, kerrors .NewAggregate (errs )
196
+ }
197
+ }
198
+
114
199
log .Info ("Successfully reconciled ROSACluster" )
115
200
116
201
return reconcile.Result {}, nil
117
202
}
118
203
119
204
func (r * ROSAClusterReconciler ) SetupWithManager (ctx context.Context , mgr ctrl.Manager , options controller.Options ) error {
120
205
log := logger .FromContext (ctx )
206
+ r .NewOCMClient = rosa .NewWrappedOCMClient
207
+ r .NewStsClient = scope .NewSTSClient
121
208
122
209
rosaCluster := & expinfrav1.ROSACluster {}
123
210
@@ -196,3 +283,77 @@ func (r *ROSAClusterReconciler) rosaControlPlaneToManagedCluster(log *logger.Log
196
283
}
197
284
}
198
285
}
286
+
287
+ // getRosMachinePools returns a map of RosaMachinePool names associatd with the cluster.
288
+ func (r * ROSAClusterReconciler ) getRosaMachinePoolNames (ctx context.Context , cluster * clusterv1.Cluster ) (map [string ]bool , error ) {
289
+ selectors := []client.ListOption {
290
+ client .InNamespace (cluster .GetNamespace ()),
291
+ client.MatchingLabels {
292
+ clusterv1 .ClusterNameLabel : cluster .GetName (),
293
+ },
294
+ }
295
+
296
+ rosaMachinePoolList := & expinfrav1.ROSAMachinePoolList {}
297
+ err := r .Client .List (ctx , rosaMachinePoolList , selectors ... )
298
+ if err != nil {
299
+ return nil , err
300
+ }
301
+
302
+ rosaMPNames := make (map [string ]bool )
303
+ for _ , rosaMP := range rosaMachinePoolList .Items {
304
+ rosaMPNames [rosaMP .Spec .NodePoolName ] = true
305
+ }
306
+
307
+ return rosaMPNames , nil
308
+ }
309
+
310
+ // buildROSAMachinePool returns a ROSAMachinePool and its corresponding MachinePool.
311
+ func (r * ROSAClusterReconciler ) buildROSAMachinePool (nodePoolName string , clusterName string , namespace string , nodePool * cmv1.NodePool ) (* expinfrav1.ROSAMachinePool , * expclusterv1.MachinePool ) {
312
+ rosaMPSpec := utils .NodePoolToRosaMachinePoolSpec (nodePool )
313
+ rosaMachinePool := & expinfrav1.ROSAMachinePool {
314
+ TypeMeta : metav1.TypeMeta {
315
+ APIVersion : expinfrav1 .GroupVersion .String (),
316
+ Kind : "ROSAMachinePool" ,
317
+ },
318
+ ObjectMeta : metav1.ObjectMeta {
319
+ Name : nodePoolName ,
320
+ Namespace : namespace ,
321
+ Labels : map [string ]string {
322
+ clusterv1 .ClusterNameLabel : clusterName ,
323
+ },
324
+ },
325
+ Spec : rosaMPSpec ,
326
+ }
327
+ machinePool := & expclusterv1.MachinePool {
328
+ TypeMeta : metav1.TypeMeta {
329
+ APIVersion : expclusterv1 .GroupVersion .String (),
330
+ Kind : "MachinePool" ,
331
+ },
332
+ ObjectMeta : metav1.ObjectMeta {
333
+ Name : nodePoolName ,
334
+ Namespace : namespace ,
335
+ Labels : map [string ]string {
336
+ clusterv1 .ClusterNameLabel : clusterName ,
337
+ },
338
+ },
339
+ Spec : expclusterv1.MachinePoolSpec {
340
+ ClusterName : clusterName ,
341
+ Replicas : ptr .To (int32 (1 )),
342
+ Template : clusterv1.MachineTemplateSpec {
343
+ Spec : clusterv1.MachineSpec {
344
+ ClusterName : clusterName ,
345
+ Bootstrap : clusterv1.Bootstrap {
346
+ DataSecretName : ptr .To (string ("" )),
347
+ },
348
+ InfrastructureRef : corev1.ObjectReference {
349
+ APIVersion : expinfrav1 .GroupVersion .String (),
350
+ Kind : "ROSAMachinePool" ,
351
+ Name : rosaMachinePool .Name ,
352
+ },
353
+ },
354
+ },
355
+ },
356
+ }
357
+
358
+ return rosaMachinePool , machinePool
359
+ }
0 commit comments