Skip to content

Commit 78e332a

Browse files
committed
add initial LinodeCluster controller logic
1 parent ad28192 commit 78e332a

File tree

7 files changed

+379
-31
lines changed

7 files changed

+379
-31
lines changed

api/v1alpha1/linodecluster_types.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ type LinodeClusterStatus struct {
4747
// reconciling the Machine and will contain a succinct value suitable
4848
// for machine interpretation.
4949
// +optional
50-
FailureReason *errors.MachineStatusError `json:"failureReason"`
50+
FailureReason *errors.ClusterStatusError `json:"failureReason,omitempty"`
5151

5252
// FailureMessage will be set in the event that there is a terminal problem
5353
// reconciling the Machine and will contain a more verbose string suitable
5454
// for logging and human consumption.
5555
// +optional
56-
FailureMessage *string `json:"failureMessage"`
56+
FailureMessage *string `json:"failureMessage,omitempty"`
5757

5858
// Conditions defines current service state of the LinodeCluster.
5959
// +optional
@@ -85,9 +85,18 @@ func (lm *LinodeCluster) SetConditions(conditions clusterv1.Conditions) {
8585

8686
// NetworkSpec encapsulates Linode networking resources.
8787
type NetworkSpec struct {
88-
// NodebalancerID is the id of apiserver Nodebalancer.
88+
// LoadBalancerType is the type of load balancer to use, defaults to NodeBalancer if not otherwise set
89+
// +kubebuilder:validation:Enum=NodeBalancer
8990
// +optional
90-
NodebalancerID int `json:"nodebalancerID,omitempty"`
91+
LoadBalancerType string `json:"loadBalancerType,omitempty"`
92+
// LoadBalancerPort used by the api server. It must be valid ports range (1-65535). If omitted, default value is 6443.
93+
// +kubebuilder:validation:Minimum=1
94+
// +kubebuilder:validation:Maximum=65535
95+
// +optional
96+
LoadBalancerPort int `json:"loadBalancerPort,omitempty"`
97+
// NodeBalancerID is the id of api server NodeBalancer.
98+
// +optional
99+
NodeBalancerID int `json:"nodeBalancerID,omitempty"`
91100
}
92101

93102
// +kubebuilder:object:root=true

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/services/loadbalancers.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package services
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"net/http"
9+
"slices"
10+
"strconv"
11+
12+
"github.com/go-logr/logr"
13+
"github.com/linode/cluster-api-provider-linode/cloud/scope"
14+
"github.com/linode/cluster-api-provider-linode/util"
15+
"github.com/linode/linodego"
16+
)
17+
18+
var (
19+
defaultLBPort = 6443
20+
)
21+
22+
// CreateNodeBalancer creates a new NodeBalancer if one doesn't exist
23+
func CreateNodeBalancer(ctx context.Context, clusterScope *scope.ClusterScope, logger logr.Logger) (*linodego.NodeBalancer, error) {
24+
var linodeNBs []linodego.NodeBalancer
25+
var linodeNB *linodego.NodeBalancer
26+
NBLabel := fmt.Sprintf("%s-api-server", clusterScope.LinodeCluster.Name)
27+
clusterUID := string(clusterScope.LinodeCluster.UID)
28+
tags := []string{string(clusterScope.LinodeCluster.UID)}
29+
filter := map[string]string{
30+
"label": NBLabel,
31+
}
32+
33+
rawFilter, err := json.Marshal(filter)
34+
if err != nil {
35+
return nil, err
36+
}
37+
logger.Info("Creating NodeBalancer")
38+
if linodeNBs, err = clusterScope.LinodeClient.ListNodeBalancers(ctx, linodego.NewListOptions(1, string(rawFilter))); err != nil {
39+
logger.Info("Failed to list NodeBalancers", "error", err.Error())
40+
41+
return nil, err
42+
}
43+
44+
switch len(linodeNBs) {
45+
case 1:
46+
logger.Info(fmt.Sprintf("NodeBalancer %s already exists", *linodeNBs[0].Label))
47+
if !slices.Contains(linodeNBs[0].Tags, clusterUID) {
48+
err = errors.New("NodeBalancer conflict")
49+
logger.Error(err, fmt.Sprintf("NodeBalancer %s is not associated with cluster UID %s. Owner cluster is %s", *linodeNBs[0].Label, clusterUID, linodeNBs[0].Tags[0]))
50+
51+
return nil, err
52+
}
53+
linodeNB = &linodeNBs[0]
54+
case 0:
55+
logger.Info(fmt.Sprintf("Creating NodeBalancer %s-api-server", clusterScope.LinodeCluster.Name))
56+
createConfig := linodego.NodeBalancerCreateOptions{
57+
Label: util.Pointer(fmt.Sprintf("%s-api-server", clusterScope.LinodeCluster.Name)),
58+
Region: clusterScope.LinodeCluster.Spec.Region,
59+
ClientConnThrottle: nil,
60+
Tags: tags,
61+
}
62+
63+
if linodeNB, err = clusterScope.LinodeClient.CreateNodeBalancer(ctx, createConfig); err != nil {
64+
logger.Info("Failed to create Linode NodeBalancer", "error", err.Error())
65+
66+
// Already exists is not an error
67+
apiErr := linodego.Error{}
68+
if errors.As(err, &apiErr) && apiErr.Code != http.StatusFound {
69+
return nil, err
70+
}
71+
72+
err = nil
73+
74+
if linodeNB != nil {
75+
logger.Info("Linode NodeBalancer already exists", "existing", linodeNB.Label)
76+
}
77+
}
78+
79+
default:
80+
err = errors.New("multiple NodeBalancers")
81+
82+
logger.Error(err, "Panic! Multiple NodeBalancers found. This might be a concurrency issue in the controller!!!", "filters", string(rawFilter))
83+
84+
return nil, err
85+
}
86+
87+
if linodeNB == nil {
88+
err = errors.New("missing NodeBalancer")
89+
90+
logger.Error(err, "Panic! Failed to create NodeBalancer")
91+
92+
return nil, err
93+
}
94+
95+
return linodeNB, nil
96+
}
97+
98+
// CreateNodeBalancerConfig creates NodeBalancer config if it does not exist
99+
func CreateNodeBalancerConfig(ctx context.Context, clusterScope *scope.ClusterScope, logger logr.Logger) (*linodego.NodeBalancerConfig, error) {
100+
var linodeNBConfigs []linodego.NodeBalancerConfig
101+
var linodeNBConfig *linodego.NodeBalancerConfig
102+
var err error
103+
104+
if linodeNBConfigs, err = clusterScope.LinodeClient.ListNodeBalancerConfigs(ctx, clusterScope.LinodeCluster.Spec.Network.NodeBalancerID, linodego.NewListOptions(1, "")); err != nil {
105+
logger.Info("Failed to list NodeBalancer Configs", "error", err.Error())
106+
107+
return nil, err
108+
}
109+
lbPort := defaultLBPort
110+
if clusterScope.LinodeCluster.Spec.Network.LoadBalancerPort != 0 {
111+
lbPort = clusterScope.LinodeCluster.Spec.Network.LoadBalancerPort
112+
}
113+
switch len(linodeNBConfigs) {
114+
case 1:
115+
logger.Info("NodeBalancer ", strconv.Itoa(linodeNBConfigs[0].ID), " already exists")
116+
linodeNBConfig = &linodeNBConfigs[0]
117+
118+
case 0:
119+
createConfig := linodego.NodeBalancerConfigCreateOptions{
120+
Port: lbPort,
121+
Protocol: linodego.ProtocolTCP,
122+
Algorithm: linodego.AlgorithmRoundRobin,
123+
Check: linodego.CheckConnection,
124+
}
125+
126+
if linodeNBConfig, err = clusterScope.LinodeClient.CreateNodeBalancerConfig(ctx, clusterScope.LinodeCluster.Spec.Network.NodeBalancerID, createConfig); err != nil {
127+
logger.Info("Failed to create Linode NodeBalancer config", "error", err.Error())
128+
129+
return nil, err
130+
}
131+
132+
default:
133+
err = errors.New("multiple NodeBalancer Configs")
134+
135+
logger.Error(err, "Panic! Multiple NodeBalancer Configs found. This might be a concurrency issue in the controller!!!")
136+
137+
return nil, err
138+
}
139+
140+
return linodeNBConfig, nil
141+
}

cmd/main.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import (
3939
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
4040

4141
infrastructurev1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
42-
//+kubebuilder:scaffold:imports
42+
// +kubebuilder:scaffold:imports
4343
)
4444

4545
var (
@@ -51,7 +51,7 @@ func init() {
5151
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
5252
utilruntime.Must(capi.AddToScheme(scheme))
5353
utilruntime.Must(infrastructurev1alpha1.AddToScheme(scheme))
54-
//+kubebuilder:scaffold:scheme
54+
// +kubebuilder:scaffold:scheme
5555
}
5656

5757
func main() {
@@ -62,10 +62,12 @@ func main() {
6262
}
6363

6464
var machineWatchFilter string
65+
var clusterWatchFilter string
6566
var metricsAddr string
6667
var enableLeaderElection bool
6768
var probeAddr string
6869
flag.StringVar(&machineWatchFilter, "machine-watch-filter", "", "The machines to watch by label.")
70+
flag.StringVar(&clusterWatchFilter, "cluster-watch-filter", "", "The clusters to watch by label.")
6971
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
7072
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
7173
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
@@ -103,8 +105,11 @@ func main() {
103105
}
104106

105107
if err = (&controller2.LinodeClusterReconciler{
106-
Client: mgr.GetClient(),
107-
Scheme: mgr.GetScheme(),
108+
Client: mgr.GetClient(),
109+
Scheme: mgr.GetScheme(),
110+
Recorder: mgr.GetEventRecorderFor("LinodeClusterReconciler"),
111+
WatchFilterValue: clusterWatchFilter,
112+
LinodeApiKey: linodeToken,
108113
}).SetupWithManager(mgr); err != nil {
109114
setupLog.Error(err, "unable to create controller", "controller", "LinodeCluster")
110115
os.Exit(1)
@@ -119,7 +124,7 @@ func main() {
119124
setupLog.Error(err, "unable to create controller", "controller", "LinodeMachine")
120125
os.Exit(1)
121126
}
122-
//+kubebuilder:scaffold:builder
127+
// +kubebuilder:scaffold:builder
123128

124129
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
125130
setupLog.Error(err, "unable to set up health check")

config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclusters.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,21 @@ spec:
6868
description: NetworkSpec encapsulates all things related to Linode
6969
network.
7070
properties:
71-
nodebalancerID:
72-
description: NodebalancerID is the id of apiserver Nodebalancer.
71+
loadBalancerPort:
72+
description: LoadBalancerPort used by the api server. It must
73+
be valid ports range (1-65535). If omitted, default value is
74+
6443.
75+
maximum: 65535
76+
minimum: 1
77+
type: integer
78+
loadBalancerType:
79+
description: LoadBalancerType is the type of load balancer to
80+
use, defaults to NodeBalancer if not otherwise set
81+
enum:
82+
- NodeBalancer
83+
type: string
84+
nodeBalancerID:
85+
description: NodeBalancerID is the id of api server NodeBalancer.
7386
type: integer
7487
type: object
7588
region:

config/crd/kustomization.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# common labels for CRD resources as required by
2+
# https://cluster-api.sigs.k8s.io/developer/providers/contracts.html#api-version-labels
3+
commonLabels:
4+
cluster.x-k8s.io/v1beta1: v1alpha1
5+
16
# This kustomization.yaml is not intended to be run by itself,
27
# since it depends on service name and namespace that are out of this kustomize package.
38
# It should be run by config/default

0 commit comments

Comments
 (0)