Skip to content

Commit c0e10a0

Browse files
committed
add loadbalancing controller for capv
Signed-off-by: Yassine TIJANI <[email protected]>
1 parent 136247b commit c0e10a0

File tree

6 files changed

+283
-51
lines changed

6 files changed

+283
-51
lines changed

controllers/cluster.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controllers
18+
19+
import (
20+
goctx "context"
21+
"fmt"
22+
23+
"github.com/go-logr/logr"
24+
"github.com/pkg/errors"
25+
clusterutilv1 "sigs.k8s.io/cluster-api/util"
26+
ctrl "sigs.k8s.io/controller-runtime"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
29+
30+
infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/api/v1alpha2"
31+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/config"
32+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/context"
33+
)
34+
35+
// ErrNoOwnerCluster is returned when a vsphereCluster has no ownerRef to a cluster
36+
var ErrNoOwnerCluster = errors.New("No owner found for vsphereCluster")
37+
38+
// ErrVsphereClusterNotFound is returned when no vsphereCluster is found
39+
var ErrVsphereClusterNotFound = errors.New("vsphereCluster not found")
40+
41+
// GetClusterContext creates clusterContext wrapped with logs
42+
func GetClusterContext(req ctrl.Request, logger logr.Logger, client client.Client) (ctx *context.ClusterContext, _ ctrl.Result, reterr error) {
43+
parentContext := goctx.Background()
44+
45+
logger = logger.WithName(fmt.Sprintf("namespace=%s", req.Namespace)).
46+
WithName(fmt.Sprintf("vsphereCluster=%s", req.Name))
47+
48+
// Fetch the VSphereCluster instance
49+
vsphereCluster := &infrav1.VSphereCluster{}
50+
reterr = client.Get(parentContext, req.NamespacedName, vsphereCluster)
51+
if reterr != nil {
52+
return nil, reconcile.Result{}, ErrVsphereClusterNotFound
53+
}
54+
55+
logger = logger.WithName(vsphereCluster.APIVersion)
56+
57+
// Fetch the Cluster.
58+
cluster, reterr := clusterutilv1.GetOwnerCluster(parentContext, client, vsphereCluster.ObjectMeta)
59+
if reterr != nil {
60+
return nil, reconcile.Result{}, reterr
61+
}
62+
if cluster == nil {
63+
logger.Info("Waiting for Cluster Controller to set OwnerRef on VSphereCluster")
64+
return nil, reconcile.Result{RequeueAfter: config.DefaultRequeue}, ErrNoOwnerCluster
65+
}
66+
67+
logger = logger.WithName(fmt.Sprintf("cluster=%s", cluster.Name))
68+
69+
// Create the context.
70+
ctx, reterr = context.NewClusterContext(&context.ClusterContextParams{
71+
Context: parentContext,
72+
Cluster: cluster,
73+
VSphereCluster: vsphereCluster,
74+
Client: client,
75+
Logger: logger,
76+
})
77+
if reterr != nil {
78+
return nil, reconcile.Result{}, errors.Errorf("failed to create cluster context: %+v", reterr)
79+
}
80+
return ctx, reconcile.Result{}, nil
81+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package loadbalancer
18+
19+
import (
20+
"github.com/go-logr/logr"
21+
"github.com/pkg/errors"
22+
23+
"k8s.io/client-go/tools/record"
24+
clusterutilv1 "sigs.k8s.io/cluster-api/util"
25+
ctrl "sigs.k8s.io/controller-runtime"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
28+
29+
infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/api/v1alpha2"
30+
"sigs.k8s.io/cluster-api-provider-vsphere/api/v1alpha2/loadbalancer"
31+
"sigs.k8s.io/cluster-api-provider-vsphere/controllers"
32+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/config"
33+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/context"
34+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/services"
35+
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/services/loadbalancer/aws"
36+
infrautilv1 "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/util"
37+
)
38+
39+
type providerFunc func(config *loadbalancer.Config, client client.Client, logger logr.Logger) (services.LoadBalancerService, error)
40+
41+
var providerFactory = map[string]providerFunc{loadbalancer.AwsProvider: aws.NewProvider}
42+
43+
// Reconciler reconciles load balancers
44+
type Reconciler struct {
45+
client.Client
46+
ProviderName string
47+
Recorder record.EventRecorder
48+
Log logr.Logger
49+
}
50+
51+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=vsphereclusters,verbs=get;list;watch;
52+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=vsphereclusters/status,verbs=get;update;patch
53+
54+
// Reconcile ensures the back-end state reflects the Kubernetes state intent for a LoadBalancer resource.
55+
func (r *Reconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr error) {
56+
logger := r.Log.WithName("loadBalancer")
57+
58+
ctx, result, err := controllers.GetClusterContext(req, logger, r)
59+
if err != nil {
60+
return result, err
61+
}
62+
vsphereCluster := ctx.VSphereCluster
63+
// check if the cluster does not have Load balancing config
64+
if vsphereCluster.Spec.LoadBalancerConfiguration == nil {
65+
return ctrl.Result{}, reterr
66+
}
67+
68+
// Always close the context when exiting this function so we can persist any VSphereCluster changes.
69+
defer func() {
70+
if err := ctx.Patch(); err != nil && reterr == nil {
71+
reterr = err
72+
}
73+
}()
74+
75+
// Handle deleted clusters
76+
if !vsphereCluster.DeletionTimestamp.IsZero() {
77+
return r.reconcileDelete(ctx)
78+
}
79+
80+
// Handle non-deleted clusters
81+
return r.reconcileNormal(ctx)
82+
}
83+
84+
func (r *Reconciler) reconcileDelete(ctx *context.ClusterContext) (ctrl.Result, error) {
85+
vsphereCluster := ctx.VSphereCluster
86+
newProvider, ok := providerFactory[r.ProviderName]
87+
if !ok {
88+
err := errors.Errorf("load balancer factory missing for %q", r.ProviderName)
89+
ctx.Logger.Error(err, "unable to initialize the load balancer provider")
90+
return ctrl.Result{}, err
91+
}
92+
loadBalancerProvider, err := newProvider(vsphereCluster.Spec.LoadBalancerConfiguration, r.Client, ctx.Logger)
93+
if err != nil {
94+
return ctrl.Result{}, err
95+
}
96+
97+
if err = loadBalancerProvider.Delete(ctx.Cluster); err != nil {
98+
return reconcile.Result{RequeueAfter: config.DefaultRequeue}, err
99+
}
100+
vsphereCluster.Finalizers = clusterutilv1.Filter(vsphereCluster.Finalizers, infrav1.LoadBalancerFinalizer)
101+
return ctrl.Result{}, nil
102+
}
103+
104+
func (r *Reconciler) reconcileNormal(ctx *context.ClusterContext) (ctrl.Result, error) {
105+
vsphereCluster := ctx.VSphereCluster
106+
newProvider, ok := providerFactory[r.ProviderName]
107+
if !ok {
108+
err := errors.Errorf("load balancer factory missing for %q", r.ProviderName)
109+
ctx.Logger.Error(err, "unable to initialize the load balancer provider")
110+
return ctrl.Result{}, err
111+
}
112+
loadBalancerProvider, err := newProvider(vsphereCluster.Spec.LoadBalancerConfiguration, r.Client, ctx.Logger)
113+
if err != nil {
114+
return ctrl.Result{}, err
115+
}
116+
ctx.Logger.V(4).Info("reconciling loadbalancer")
117+
118+
machines, err := infrautilv1.GetMachinesInCluster(ctx, r.Client, vsphereCluster.Namespace, vsphereCluster.Name)
119+
if err != nil {
120+
return ctrl.Result{}, err
121+
}
122+
ctx.Logger.V(6).Info("got machines for cluster while reconciling load balancer")
123+
124+
controlPlaneMachines := clusterutilv1.GetControlPlaneMachines(machines)
125+
vsphereMachines, err := infrautilv1.GetVSphereMachinesInCluster(ctx, r.Client, vsphereCluster.Namespace, vsphereCluster.Name)
126+
if err != nil {
127+
return ctrl.Result{}, err
128+
}
129+
ctx.Logger.V(6).Info("got vsphere machines for cluster while reconciling load balancer")
130+
131+
controlPlaneIPs := []string{}
132+
for _, controlPlane := range controlPlaneMachines {
133+
vsphereMachine, ok := vsphereMachines[controlPlane.Name]
134+
if !ok {
135+
ctx.Logger.V(6).Info("machine not yet linked to the cluster", "machine-name", controlPlane.Name)
136+
continue
137+
}
138+
ip, err := infrautilv1.GetMachinePreferredIPAddress(vsphereMachine)
139+
if err == infrautilv1.ErrParseCIDR {
140+
return ctrl.Result{}, err
141+
}
142+
controlPlaneIPs = append(controlPlaneIPs, ip)
143+
144+
}
145+
ctx.Logger.V(4).Info("gathered controlplane IPs", "controlplane-ips", controlPlaneIPs)
146+
147+
apiEndpoint, err := loadBalancerProvider.Reconcile(ctx.Cluster, controlPlaneIPs)
148+
if err != nil {
149+
return ctrl.Result{}, err
150+
}
151+
vsphereCluster.Finalizers = append(vsphereCluster.Finalizers, infrav1.LoadBalancerFinalizer)
152+
vsphereCluster.Status.APIEndpoints = append(vsphereCluster.Status.APIEndpoints, infrav1.APIEndpoint{
153+
Host: apiEndpoint.Host,
154+
Port: apiEndpoint.Port,
155+
})
156+
vsphereCluster.Status.Ready = true
157+
return ctrl.Result{}, nil
158+
}
159+
160+
// SetupWithManager adds this controller to the provided manager.
161+
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
162+
return ctrl.NewControllerManagedBy(mgr).
163+
For(&infrav1.VSphereCluster{}).
164+
Complete(r)
165+
}

controllers/vspherecluster_controller.go

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
package controllers
1818

1919
import (
20-
goctx "context"
2120
"fmt"
2221

2322
"github.com/go-logr/logr"
@@ -32,7 +31,6 @@ import (
3231
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3332

3433
infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/api/v1alpha2"
35-
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/config"
3634
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/context"
3735
infrautilv1 "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/util"
3836
)
@@ -56,48 +54,16 @@ type VSphereClusterReconciler struct {
5654

5755
// Reconcile ensures the back-end state reflects the Kubernetes resource state intent.
5856
func (r *VSphereClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr error) {
59-
parentContext := goctx.Background()
57+
logger := r.Log.WithName(controllerName)
6058

61-
logger := r.Log.WithName(controllerName).
62-
WithName(fmt.Sprintf("namespace=%s", req.Namespace)).
63-
WithName(fmt.Sprintf("vsphereCluster=%s", req.Name))
64-
65-
// Fetch the VSphereCluster instance
66-
vsphereCluster := &infrav1.VSphereCluster{}
67-
err := r.Get(parentContext, req.NamespacedName, vsphereCluster)
59+
ctx, result, err := GetClusterContext(req, logger, r)
6860
if err != nil {
69-
if apierrors.IsNotFound(err) {
70-
return reconcile.Result{}, nil
61+
if err == ErrNoOwnerCluster || err == ErrVsphereClusterNotFound {
62+
return result, nil
7163
}
72-
return reconcile.Result{}, err
73-
}
74-
75-
logger = logger.WithName(vsphereCluster.APIVersion)
76-
77-
// Fetch the Cluster.
78-
cluster, err := clusterutilv1.GetOwnerCluster(parentContext, r.Client, vsphereCluster.ObjectMeta)
79-
if err != nil {
80-
return reconcile.Result{}, err
81-
}
82-
if cluster == nil {
83-
logger.Info("Waiting for Cluster Controller to set OwnerRef on VSphereCluster")
84-
return reconcile.Result{RequeueAfter: config.DefaultRequeue}, nil
64+
return result, err
8565
}
86-
87-
logger = logger.WithName(fmt.Sprintf("cluster=%s", cluster.Name))
88-
89-
// Create the context.
90-
ctx, err := context.NewClusterContext(&context.ClusterContextParams{
91-
Context: parentContext,
92-
Cluster: cluster,
93-
VSphereCluster: vsphereCluster,
94-
Client: r.Client,
95-
Logger: logger,
96-
})
97-
if err != nil {
98-
return reconcile.Result{}, errors.Errorf("failed to create cluster context: %+v", err)
99-
}
100-
66+
vsphereCluster := ctx.VSphereCluster
10167
// Always close the context when exiting this function so we can persist any VSphereCluster changes.
10268
defer func() {
10369
if err := ctx.Patch(); err != nil && reterr == nil {
@@ -130,7 +96,7 @@ func (r *VSphereClusterReconciler) reconcileNormal(ctx *context.ClusterContext)
13096
// * Downloading OVAs into the content library for any machines that
13197
// use them.
13298
// * Create any load balancers for VMC on AWS, etc.
133-
ctx.VSphereCluster.Status.Ready = true
99+
ctx.VSphereCluster.Status.Ready = ctx.VSphereCluster.Spec.LoadBalancerConfiguration == nil
134100
ctx.Logger.V(6).Info("VSphereCluster is infrastructure-ready")
135101

136102
// If the VSphereCluster doesn't have our finalizer, add it.
@@ -141,12 +107,13 @@ func (r *VSphereClusterReconciler) reconcileNormal(ctx *context.ClusterContext)
141107
"cluster-namespace", ctx.VSphereCluster.Namespace,
142108
"cluster-name", ctx.VSphereCluster.Name)
143109
}
144-
145-
// Update the VSphereCluster resource with its API enpoints.
146-
if err := r.reconcileAPIEndpoints(ctx); err != nil {
147-
return reconcile.Result{}, errors.Wrapf(err,
148-
"failed to reconcile API endpoints for VSphereCluster %s/%s",
149-
ctx.VSphereCluster.Namespace, ctx.VSphereCluster.Name)
110+
if ctx.VSphereCluster.Spec.LoadBalancerConfiguration == nil {
111+
// Update the VSphereCluster resource with its API enpoints.
112+
if err := r.reconcileAPIEndpoints(ctx); err != nil {
113+
return reconcile.Result{}, errors.Wrapf(err,
114+
"failed to reconcile API endpoints for VSphereCluster %s/%s",
115+
ctx.VSphereCluster.Namespace, ctx.VSphereCluster.Name)
116+
}
150117
}
151118

152119
// Create the cloud config secret for the target cluster.

controllers/vspheremachine_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func (r *VSphereMachineReconciler) reconcileNormal(ctx *context.MachineContext)
196196

197197
if !ctx.Cluster.Status.InfrastructureReady {
198198
ctx.Logger.Info("Cluster infrastructure is not ready yet, requeuing machine")
199-
return reconcile.Result{RequeueAfter: config.DefaultRequeue}, nil
199+
return reconcile.Result{}, nil
200200
}
201201

202202
// Make sure bootstrap data is available and populated.

main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434

3535
infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/api/v1alpha2"
3636
"sigs.k8s.io/cluster-api-provider-vsphere/controllers"
37+
"sigs.k8s.io/cluster-api-provider-vsphere/controllers/loadbalancer"
3738
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/config"
3839
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/record"
3940
)
@@ -83,6 +84,9 @@ func main() {
8384
"Bind address to expose the pprof profiler")
8485
syncPeriod := flag.Duration("sync-period", defaultSyncPeriod,
8586
"The interval at which cluster-api objects are synchronized")
87+
loadBalancerProvider := flag.String(
88+
"loadbalancer-provider", "",
89+
"the name of the load balancer provider")
8690
flag.DurationVar(&config.DefaultRequeue, "requeue-period", defaultRequeuePeriod,
8791
"The default amount of time to wait before an operation is requeued.")
8892
flag.Parse()
@@ -129,6 +133,17 @@ func main() {
129133
setupLog.Error(err, "unable to create controller", "controller", "VSphereCluster")
130134
os.Exit(1)
131135
}
136+
if *loadBalancerProvider != "" {
137+
if err = (&loadbalancer.Reconciler{
138+
Client: mgr.GetClient(),
139+
ProviderName: *loadBalancerProvider,
140+
Log: ctrl.Log.WithName("controllers").WithName("loadBalancer"),
141+
Recorder: mgr.GetEventRecorderFor("loadBalancer-controller"),
142+
}).SetupWithManager(mgr); err != nil {
143+
setupLog.Error(err, "unable to create controller", "controller", "loadBalancer")
144+
os.Exit(1)
145+
}
146+
}
132147
// +kubebuilder:scaffold:builder
133148

134149
setupLog.Info("starting manager")

0 commit comments

Comments
 (0)