@@ -19,16 +19,21 @@ package controllers
1919import (
2020 "fmt"
2121 "reflect"
22+ "strings"
23+ "time"
2224
2325 "github.com/pkg/errors"
2426 apiv1 "k8s.io/api/core/v1"
2527 apierrors "k8s.io/apimachinery/pkg/api/errors"
2628 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27- "k8s.io/apimachinery/pkg/runtime/schema"
29+ "k8s.io/apimachinery/pkg/types"
30+ "k8s.io/apimachinery/pkg/util/wait"
2831 clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2"
2932 clusterutilv1 "sigs.k8s.io/cluster-api/util"
3033 "sigs.k8s.io/cluster-api/util/patch"
3134 ctrl "sigs.k8s.io/controller-runtime"
35+ "sigs.k8s.io/controller-runtime/pkg/client"
36+ "sigs.k8s.io/controller-runtime/pkg/event"
3237 "sigs.k8s.io/controller-runtime/pkg/handler"
3338 "sigs.k8s.io/controller-runtime/pkg/manager"
3439 "sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -42,8 +47,7 @@ import (
4247)
4348
4449const (
45- clusterControllerName = "vspherecluster-controller"
46- apiEndpointPort = 6443
50+ apiEndpointPort = 6443
4751)
4852
4953// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;patch
@@ -57,7 +61,11 @@ const (
5761func AddClusterControllerToManager (ctx * context.ControllerManagerContext , mgr manager.Manager ) error {
5862
5963 var (
60- controllerNameShort = clusterControllerName
64+ controlledType = & infrav1.VSphereCluster {}
65+ controlledTypeName = reflect .TypeOf (controlledType ).Elem ().Name ()
66+ controlledTypeGVK = infrav1 .GroupVersion .WithKind (controlledTypeName )
67+
68+ controllerNameShort = fmt .Sprintf ("%s-controller" , strings .ToLower (controlledTypeName ))
6169 controllerNameLong = fmt .Sprintf ("%s/%s/%s" , ctx .Namespace , ctx .Name , controllerNameShort )
6270 )
6371
@@ -69,21 +77,37 @@ func AddClusterControllerToManager(ctx *context.ControllerManagerContext, mgr ma
6977 Logger : ctx .Logger .WithName (controllerNameShort ),
7078 }
7179
72- controlledType := & infrav1.VSphereCluster {}
73- controlledTypeName := reflect .TypeOf (controlledType ).Elem ().Name ()
80+ reconciler := clusterReconciler {ControllerContext : controllerContext }
7481
7582 return ctrl .NewControllerManagedBy (mgr ).
83+ // Watch the controlled, infrastructure resource.
7684 For (controlledType ).
85+ // Watch the CAPI resource that owns this infrastructure resource.
7786 Watches (
7887 & source.Kind {Type : & clusterv1.Cluster {}},
7988 & handler.EnqueueRequestsFromMapFunc {
80- ToRequests : clusterutilv1 .MachineToInfrastructureMapFunc (schema.GroupVersionKind {
81- Group : infrav1 .SchemeBuilder .GroupVersion .Group ,
82- Version : infrav1 .SchemeBuilder .GroupVersion .Version ,
83- Kind : controlledTypeName ,
84- }),
85- }).
86- Complete (clusterReconciler {ControllerContext : controllerContext })
89+ ToRequests : clusterutilv1 .ClusterToInfrastructureMapFunc (controlledTypeGVK ),
90+ },
91+ ).
92+ // Watch the infrastructure machine resources that belong to the control
93+ // plane. This controller needs to reconcile the infrastructure cluster
94+ // once a control plane machine has an IP address.
95+ Watches (
96+ & source.Kind {Type : & infrav1.VSphereMachine {}},
97+ & handler.EnqueueRequestsFromMapFunc {
98+ ToRequests : handler .ToRequestsFunc (reconciler .controlPlaneMachineToCluster ),
99+ },
100+ ).
101+ // Watch a GenericEvent channel for the controlled resource.
102+ //
103+ // This is useful when there are events outside of Kubernetes that
104+ // should cause a resource to be synchronized, such as a goroutine
105+ // waiting on some asynchronous, external task to complete.
106+ Watches (
107+ & source.Channel {Source : ctx .GetGenericEventChannelFor (controlledTypeGVK )},
108+ & handler.EnqueueRequestForObject {},
109+ ).
110+ Complete (reconciler )
87111}
88112
89113type clusterReconciler struct {
@@ -184,11 +208,20 @@ func (r clusterReconciler) reconcileNormal(ctx *context.ClusterContext) (reconci
184208
185209 // Update the VSphereCluster resource with its API enpoints.
186210 if err := r .reconcileAPIEndpoints (ctx ); err != nil {
211+ if err == infrautilv1 .ErrNoMachineIPAddr {
212+ ctx .Logger .Info ("Waiting on an API endpoint" )
213+ return reconcile.Result {}, nil
214+ }
187215 return reconcile.Result {}, errors .Wrapf (err ,
188216 "failed to reconcile API endpoints for VSphereCluster %s/%s" ,
189217 ctx .VSphereCluster .Namespace , ctx .VSphereCluster .Name )
190218 }
191219
220+ // Wait until the API server is online and accessible.
221+ if ! r .isAPIServerOnline (ctx ) {
222+ return reconcile.Result {}, nil
223+ }
224+
192225 // Create the cloud config secret for the target cluster.
193226 if err := r .reconcileCloudConfigSecret (ctx ); err != nil {
194227 return reconcile.Result {}, errors .Wrapf (err ,
@@ -296,11 +329,35 @@ func (r clusterReconciler) reconcileAPIEndpoints(ctx *context.ClusterContext) er
296329 // for this VSphereCluster into the analogous CAPI Cluster using an
297330 // UnstructuredReader.
298331 ctx .VSphereCluster .Status .APIEndpoints = []infrav1.APIEndpoint {apiEndpoint }
332+ vsphereCluster := ctx .VSphereCluster .DeepCopy ()
333+
334+ // Enqueue a reconcile request for the cluster when the target API
335+ // server is online.
336+ go func () {
337+ // Block until the target API server is online.
338+ wait .PollImmediateInfinite (time .Second * 1 , func () (bool , error ) { return r .isAPIServerOnline (ctx ), nil })
339+ ctx .Logger .Info ("triggering GenericEvent" , "reason" , "api-server-online" )
340+ eventChannel := ctx .GetGenericEventChannelFor (vsphereCluster .GetObjectKind ().GroupVersionKind ())
341+ eventChannel <- event.GenericEvent {
342+ Meta : vsphereCluster ,
343+ Object : vsphereCluster ,
344+ }
345+ }()
346+
299347 return nil
300348 }
301349 return infrautilv1 .ErrNoMachineIPAddr
302350}
303351
352+ func (r clusterReconciler ) isAPIServerOnline (ctx * context.ClusterContext ) bool {
353+ if client , err := infrautilv1 .NewKubeClient (ctx , ctx .Client , ctx .Cluster ); err == nil {
354+ if _ , err := client .CoreV1 ().Nodes ().List (metav1.ListOptions {}); err == nil {
355+ return true
356+ }
357+ }
358+ return false
359+ }
360+
304361func (r clusterReconciler ) reconcileCloudProvider (ctx * context.ClusterContext ) error {
305362 // if the cloud provider image is not specified, then we do nothing
306363 cloudproviderConfig := ctx .VSphereCluster .Spec .CloudProviderConfiguration .ProviderConfig .Cloud
@@ -512,3 +569,64 @@ func (r clusterReconciler) reconcileCloudConfigSecret(ctx *context.ClusterContex
512569
513570 return nil
514571}
572+
573+ // controlPlaneMachineToCluster is a handler.ToRequestsFunc to be used
574+ // to enqueue requests for reconciliation for VSphereCluster to update
575+ // its status.apiEndpoints field.
576+ func (r clusterReconciler ) controlPlaneMachineToCluster (o handler.MapObject ) []ctrl.Request {
577+ vsphereMachine , ok := o .Object .(* infrav1.VSphereMachine )
578+ if ! ok {
579+ r .Logger .Error (nil , fmt .Sprintf ("expected a VSphereMachine but got a %T" , o .Object ))
580+ return nil
581+ }
582+ if ! infrautilv1 .IsControlPlaneMachine (vsphereMachine ) {
583+ return nil
584+ }
585+ if len (vsphereMachine .Status .Network ) == 0 {
586+ return nil
587+ }
588+ // Get the VSphereMachine's preferred IP address.
589+ if _ , err := infrautilv1 .GetMachinePreferredIPAddress (vsphereMachine ); err != nil {
590+ if err == infrautilv1 .ErrNoMachineIPAddr {
591+ return nil
592+ }
593+ r .Logger .Error (err , "failed to get preferred IP address for VSphereMachine" ,
594+ "namespace" , vsphereMachine .Namespace , "name" , vsphereMachine .Name )
595+ return nil
596+ }
597+
598+ // Fetch the CAPI Cluster.
599+ cluster , err := clusterutilv1 .GetClusterFromMetadata (r , r .Client , vsphereMachine .ObjectMeta )
600+ if err != nil {
601+ r .Logger .Error (err , "VSphereMachine is missing cluster label or cluster does not exist" ,
602+ "namespace" , vsphereMachine .Namespace , "name" , vsphereMachine .Name )
603+ return nil
604+ }
605+
606+ if cluster .Status .ControlPlaneInitialized {
607+ return nil
608+ }
609+
610+ // Fetch the VSphereCluster
611+ vsphereCluster := & infrav1.VSphereCluster {}
612+ vsphereClusterKey := client.ObjectKey {
613+ Namespace : vsphereMachine .Namespace ,
614+ Name : cluster .Spec .InfrastructureRef .Name ,
615+ }
616+ if err := r .Client .Get (r , vsphereClusterKey , vsphereCluster ); err != nil {
617+ r .Logger .Error (err , "failed to get VSphereCluster" ,
618+ "namespace" , vsphereClusterKey .Namespace , "name" , vsphereClusterKey .Name )
619+ return nil
620+ }
621+
622+ if len (vsphereCluster .Status .APIEndpoints ) > 0 {
623+ return nil
624+ }
625+
626+ return []ctrl.Request {{
627+ NamespacedName : types.NamespacedName {
628+ Namespace : vsphereClusterKey .Namespace ,
629+ Name : vsphereClusterKey .Name ,
630+ },
631+ }}
632+ }
0 commit comments