Skip to content

Commit d096226

Browse files
authored
Merge pull request #686 from akutz/feature/task-source
Reconcile based on task completion
2 parents 025bdc5 + db1b4fc commit d096226

File tree

13 files changed

+641
-191
lines changed

13 files changed

+641
-191
lines changed

.golangci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ linters:
2020
disable-all: true
2121
# Run with --fast=false for more extensive checks
2222
fast: true
23+
run:
24+
skip-files:
25+
- ".*zz_generated.*\\.go"
26+
timeout: 5m
2327
issue:
2428
max-same-issues: 0
2529
max-per-linter: 0

controllers/vspherecluster_controller.go

Lines changed: 131 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,21 @@ package controllers
1919
import (
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

4449
const (
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 (
5761
func 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

89113
type 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+
304361
func (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

Comments
 (0)