55 "fmt"
66 "time"
77
8+ "github.com/aws/aws-sdk-go/aws"
9+ "github.com/aws/aws-sdk-go/service/ec2"
810 "github.com/blang/semver"
911 "github.com/google/go-cmp/cmp"
1012 cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
@@ -15,6 +17,7 @@ import (
1517 "k8s.io/apimachinery/pkg/runtime/schema"
1618 "k8s.io/client-go/tools/record"
1719 "k8s.io/klog/v2"
20+ "k8s.io/utils/ptr"
1821 ctrl "sigs.k8s.io/controller-runtime"
1922 "sigs.k8s.io/controller-runtime/pkg/client"
2023 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -130,6 +133,7 @@ func (r *ROSAMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Requ
130133 MachinePool : machinePool ,
131134 RosaMachinePool : rosaMachinePool ,
132135 Logger : log ,
136+ Endpoints : r .Endpoints ,
133137 })
134138 if err != nil {
135139 return ctrl.Result {}, errors .Wrap (err , "failed to create scope" )
@@ -198,6 +202,17 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context,
198202 }
199203
200204 rosaMachinePool := machinePoolScope .RosaMachinePool
205+ machinePool := machinePoolScope .MachinePool
206+
207+ if rosaMachinePool .Spec .Autoscaling != nil && ! annotations .ReplicasManagedByExternalAutoscaler (machinePool ) {
208+ // make sure cluster.x-k8s.io/replicas-managed-by annotation is set on CAPI MachinePool when autoscaling is enabled.
209+ annotations .AddAnnotations (machinePool , map [string ]string {
210+ clusterv1 .ReplicasManagedByAnnotation : "rosa" ,
211+ })
212+ if err := machinePoolScope .PatchCAPIMachinePoolObject (ctx ); err != nil {
213+ return ctrl.Result {}, err
214+ }
215+ }
201216
202217 nodePool , found , err := ocmClient .GetNodePool (machinePoolScope .ControlPlane .Status .ID , rosaMachinePool .Spec .NodePoolName )
203218 if err != nil {
@@ -210,9 +225,25 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context,
210225 return ctrl.Result {}, fmt .Errorf ("failed to ensure rosaMachinePool: %w" , err )
211226 }
212227
213- // TODO (alberto): discover and store providerIDs from aws so the CAPI controller can match then to Nodes and report readiness.
214- rosaMachinePool .Status .Replicas = int32 (nodePool .Status ().CurrentReplicas ())
215- if nodePool .Replicas () == nodePool .Status ().CurrentReplicas () && nodePool .Status ().Message () == "" {
228+ currentReplicas := int32 (nodePool .Status ().CurrentReplicas ())
229+ if annotations .ReplicasManagedByExternalAutoscaler (machinePool ) {
230+ // Set MachinePool replicas to rosa autoscaling replicas
231+ if * machinePool .Spec .Replicas != currentReplicas {
232+ machinePoolScope .Info ("Setting MachinePool replicas to rosa autoscaling replicas" ,
233+ "local" , * machinePool .Spec .Replicas ,
234+ "external" , currentReplicas )
235+ machinePool .Spec .Replicas = & currentReplicas
236+ if err := machinePoolScope .PatchCAPIMachinePoolObject (ctx ); err != nil {
237+ return ctrl.Result {}, err
238+ }
239+ }
240+ }
241+ if err := r .reconcileProviderIDList (ctx , machinePoolScope , nodePool ); err != nil {
242+ return ctrl.Result {}, fmt .Errorf ("failed to reconcile ProviderIDList: %w" , err )
243+ }
244+
245+ rosaMachinePool .Status .Replicas = currentReplicas
246+ if rosa .IsNodePoolReady (nodePool ) {
216247 conditions .MarkTrue (rosaMachinePool , expinfrav1 .RosaMachinePoolReadyCondition )
217248 rosaMachinePool .Status .Ready = true
218249
@@ -234,7 +265,7 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context,
234265 return ctrl.Result {RequeueAfter : time .Second * 60 }, nil
235266 }
236267
237- npBuilder := nodePoolBuilder (rosaMachinePool .Spec , machinePoolScope . MachinePool .Spec )
268+ npBuilder := nodePoolBuilder (rosaMachinePool .Spec , machinePool .Spec )
238269 nodePoolSpec , err := npBuilder .Build ()
239270 if err != nil {
240271 return ctrl.Result {}, fmt .Errorf ("failed to build rosa nodepool: %w" , err )
@@ -294,20 +325,7 @@ func (r *ROSAMachinePoolReconciler) reconcileMachinePoolVersion(machinePoolScope
294325 }
295326
296327 if scheduledUpgrade == nil {
297- policy , err := ocmClient .BuildNodeUpgradePolicy (version , nodePool .ID (), ocm.UpgradeScheduling {
298- AutomaticUpgrades : false ,
299- // The OCM API places guardrails around the minimum and maximum delay that a user can request,
300- // for the next run of the upgrade, which is [5min,6mo]. Set our next run request to something
301- // slightly longer than 5min to make sure we account for the latency between when we send this
302- // request and when the server processes it.
303- // https://gitlab.cee.redhat.com/service/uhc-clusters-service/-/blob/master/cmd/clusters-service/servecmd/apiserver/upgrade_policy_handlers.go
304- NextRun : time .Now ().Add (6 * time .Minute ),
305- })
306- if err != nil {
307- return fmt .Errorf ("failed to create nodePool upgrade schedule to version %s: %w" , version , err )
308- }
309-
310- scheduledUpgrade , err = ocmClient .ScheduleNodePoolUpgrade (clusterID , nodePool .ID (), policy )
328+ scheduledUpgrade , err = rosa .ScheduleNodePoolUpgrade (ocmClient , clusterID , nodePool , version , time .Now ())
311329 if err != nil {
312330 return fmt .Errorf ("failed to schedule nodePool upgrade to version %s: %w" , version , err )
313331 }
@@ -453,6 +471,47 @@ func nodePoolToRosaMachinePoolSpec(nodePool *cmv1.NodePool) expinfrav1.RosaMachi
453471 return spec
454472}
455473
474+ func (r * ROSAMachinePoolReconciler ) reconcileProviderIDList (ctx context.Context , machinePoolScope * scope.RosaMachinePoolScope , nodePool * cmv1.NodePool ) error {
475+ tags := nodePool .AWSNodePool ().Tags ()
476+ if len (tags ) == 0 {
477+ // can't identify EC2 instances belonging to this NodePool without tags.
478+ return nil
479+ }
480+
481+ ec2Svc := scope .NewEC2Client (machinePoolScope , machinePoolScope , & machinePoolScope .Logger , machinePoolScope .InfraCluster ())
482+ response , err := ec2Svc .DescribeInstancesWithContext (ctx , & ec2.DescribeInstancesInput {
483+ Filters : buildEC2FiltersFromTags (tags ),
484+ })
485+ if err != nil {
486+ return err
487+ }
488+
489+ var providerIDList []string
490+ for _ , reservation := range response .Reservations {
491+ for _ , instance := range reservation .Instances {
492+ providerID := scope .GenerateProviderID (* instance .Placement .AvailabilityZone , * instance .InstanceId )
493+ providerIDList = append (providerIDList , providerID )
494+ }
495+ }
496+
497+ machinePoolScope .RosaMachinePool .Spec .ProviderIDList = providerIDList
498+ return nil
499+ }
500+
501+ func buildEC2FiltersFromTags (tags map [string ]string ) []* ec2.Filter {
502+ filters := make ([]* ec2.Filter , len (tags ))
503+ for key , value := range tags {
504+ filters = append (filters , & ec2.Filter {
505+ Name : ptr .To (fmt .Sprintf ("tag:%s" , key )),
506+ Values : aws .StringSlice ([]string {
507+ value ,
508+ }),
509+ })
510+ }
511+
512+ return filters
513+ }
514+
456515func rosaControlPlaneToRosaMachinePoolMapFunc (c client.Client , gvk schema.GroupVersionKind , log logger.Wrapper ) handler.MapFunc {
457516 return func (ctx context.Context , o client.Object ) []reconcile.Request {
458517 rosaControlPlane , ok := o .(* rosacontrolplanev1.ROSAControlPlane )
0 commit comments