Skip to content

Commit 11bba64

Browse files
authored
add initial LinodeCluster controller logic (#64)
add initial LinodeCluster controller logic
1 parent 27f3002 commit 11bba64

File tree

9 files changed

+396
-43
lines changed

9 files changed

+396
-43
lines changed

api/v1alpha1/linodecluster_types.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ type LinodeClusterStatus struct {
4444
Ready bool `json:"ready"`
4545

4646
// FailureReason will be set in the event that there is a terminal problem
47-
// reconciling the Machine and will contain a succinct value suitable
47+
// reconciling the LinodeCluster 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
53-
// reconciling the Machine and will contain a more verbose string suitable
53+
// reconciling the LinodeCluster 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
@@ -86,9 +86,18 @@ func (lm *LinodeCluster) SetConditions(conditions clusterv1.Conditions) {
8686

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

94103
// +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/scope/cluster.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ limitations under the License.
1717
package scope
1818

1919
import (
20+
"context"
2021
"errors"
2122
"fmt"
2223
"net/http"
2324

24-
infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
25+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
26+
27+
infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
2528
"github.com/linode/linodego"
2629
"golang.org/x/oauth2"
2730
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -33,7 +36,7 @@ import (
3336
type ClusterScopeParams struct {
3437
Client client.Client
3538
Cluster *clusterv1.Cluster
36-
LinodeCluster *infrav1.LinodeCluster
39+
LinodeCluster *infrav1alpha1.LinodeCluster
3740
}
3841

3942
func validateClusterScopeParams(params ClusterScopeParams) error {
@@ -90,5 +93,22 @@ type ClusterScope struct {
9093
PatchHelper *patch.Helper
9194
LinodeClient *linodego.Client
9295
Cluster *clusterv1.Cluster
93-
LinodeCluster *infrav1.LinodeCluster
96+
LinodeCluster *infrav1alpha1.LinodeCluster
97+
}
98+
99+
// PatchObject persists the cluster configuration and status.
100+
func (s *ClusterScope) PatchObject(ctx context.Context) error {
101+
return s.PatchHelper.Patch(ctx, s.LinodeCluster)
102+
}
103+
104+
// Close closes the current scope persisting the cluster configuration and status.
105+
func (s *ClusterScope) Close(ctx context.Context) error {
106+
return s.PatchObject(ctx)
107+
}
108+
109+
// AddFinalizer adds a finalizer and immediately patches the object to avoid any race conditions
110+
func (s *ClusterScope) AddFinalizer(ctx context.Context) error {
111+
controllerutil.AddFinalizer(s.LinodeCluster, infrav1alpha1.GroupVersion.String())
112+
113+
return s.Close(ctx)
94114
}

cloud/services/loadbalancers.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
if linodeNBs, err = clusterScope.LinodeClient.ListNodeBalancers(ctx, linodego.NewListOptions(1, string(rawFilter))); err != nil {
38+
logger.Info("Failed to list NodeBalancers", "error", err.Error())
39+
40+
return nil, err
41+
}
42+
if len(linodeNBs) == 1 {
43+
logger.Info(fmt.Sprintf("NodeBalancer %s already exists", *linodeNBs[0].Label))
44+
if !slices.Contains(linodeNBs[0].Tags, clusterUID) {
45+
err = errors.New("NodeBalancer conflict")
46+
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]))
47+
48+
return nil, err
49+
}
50+
51+
return &linodeNBs[0], nil
52+
}
53+
54+
logger.Info(fmt.Sprintf("Creating NodeBalancer %s-api-server", clusterScope.LinodeCluster.Name))
55+
createConfig := linodego.NodeBalancerCreateOptions{
56+
Label: util.Pointer(fmt.Sprintf("%s-api-server", clusterScope.LinodeCluster.Name)),
57+
Region: clusterScope.LinodeCluster.Spec.Region,
58+
Tags: tags,
59+
}
60+
61+
if linodeNB, err = clusterScope.LinodeClient.CreateNodeBalancer(ctx, createConfig); err != nil {
62+
logger.Info("Failed to create Linode NodeBalancer", "error", err.Error())
63+
64+
// Already exists is not an error
65+
apiErr := linodego.Error{}
66+
if errors.As(err, &apiErr) && apiErr.Code != http.StatusFound {
67+
return nil, err
68+
}
69+
70+
if linodeNB != nil {
71+
logger.Info("Linode NodeBalancer already exists", "existing", linodeNB.Label)
72+
}
73+
}
74+
75+
return linodeNB, nil
76+
}
77+
78+
// CreateNodeBalancerConfig creates NodeBalancer config if it does not exist
79+
func CreateNodeBalancerConfig(ctx context.Context, clusterScope *scope.ClusterScope, logger logr.Logger) (*linodego.NodeBalancerConfig, error) {
80+
var linodeNBConfigs []linodego.NodeBalancerConfig
81+
var linodeNBConfig *linodego.NodeBalancerConfig
82+
var err error
83+
84+
if linodeNBConfigs, err = clusterScope.LinodeClient.ListNodeBalancerConfigs(ctx, clusterScope.LinodeCluster.Spec.Network.NodeBalancerID, linodego.NewListOptions(1, "")); err != nil {
85+
logger.Info("Failed to list NodeBalancer Configs", "error", err.Error())
86+
87+
return nil, err
88+
}
89+
if len(linodeNBConfigs) == 1 {
90+
logger.Info("NodeBalancer ", strconv.Itoa(linodeNBConfigs[0].ID), " already exists")
91+
92+
return &linodeNBConfigs[0], err
93+
}
94+
lbPort := defaultLBPort
95+
if clusterScope.LinodeCluster.Spec.Network.LoadBalancerPort != 0 {
96+
lbPort = clusterScope.LinodeCluster.Spec.Network.LoadBalancerPort
97+
}
98+
createConfig := linodego.NodeBalancerConfigCreateOptions{
99+
Port: lbPort,
100+
Protocol: linodego.ProtocolTCP,
101+
Algorithm: linodego.AlgorithmRoundRobin,
102+
Check: linodego.CheckConnection,
103+
}
104+
105+
if linodeNBConfig, err = clusterScope.LinodeClient.CreateNodeBalancerConfig(ctx, clusterScope.LinodeCluster.Spec.Network.NodeBalancerID, createConfig); err != nil {
106+
logger.Info("Failed to create Linode NodeBalancer config", "error", err.Error())
107+
108+
return nil, err
109+
}
110+
111+
return linodeNBConfig, nil
112+
}

cmd/main.go

Lines changed: 9 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,10 @@ func main() {
103105
}
104106

105107
if err = (&controller2.LinodeClusterReconciler{
106-
Client: mgr.GetClient(),
107-
Scheme: mgr.GetScheme(),
108+
Client: mgr.GetClient(),
109+
Recorder: mgr.GetEventRecorderFor("LinodeClusterReconciler"),
110+
WatchFilterValue: clusterWatchFilter,
111+
LinodeApiKey: linodeToken,
108112
}).SetupWithManager(mgr); err != nil {
109113
setupLog.Error(err, "unable to create controller", "controller", "LinodeCluster")
110114
os.Exit(1)
@@ -119,7 +123,7 @@ func main() {
119123
setupLog.Error(err, "unable to create controller", "controller", "LinodeMachine")
120124
os.Exit(1)
121125
}
122-
//+kubebuilder:scaffold:builder
126+
// +kubebuilder:scaffold:builder
123127

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

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,21 @@ spec:
7272
description: NetworkSpec encapsulates all things related to Linode
7373
network.
7474
properties:
75-
nodebalancerID:
76-
description: NodebalancerID is the id of apiserver Nodebalancer.
75+
loadBalancerPort:
76+
description: LoadBalancerPort used by the api server. It must
77+
be valid ports range (1-65535). If omitted, default value is
78+
6443.
79+
maximum: 65535
80+
minimum: 1
81+
type: integer
82+
loadBalancerType:
83+
description: LoadBalancerType is the type of load balancer to
84+
use, defaults to NodeBalancer if not otherwise set
85+
enum:
86+
- NodeBalancer
87+
type: string
88+
nodeBalancerID:
89+
description: NodeBalancerID is the id of api server NodeBalancer.
7790
type: integer
7891
type: object
7992
region:
@@ -132,13 +145,13 @@ spec:
132145
type: array
133146
failureMessage:
134147
description: FailureMessage will be set in the event that there is
135-
a terminal problem reconciling the Machine and will contain a more
136-
verbose string suitable for logging and human consumption.
148+
a terminal problem reconciling the LinodeCluster and will contain
149+
a more verbose string suitable for logging and human consumption.
137150
type: string
138151
failureReason:
139152
description: FailureReason will be set in the event that there is
140-
a terminal problem reconciling the Machine and will contain a succinct
141-
value suitable for machine interpretation.
153+
a terminal problem reconciling the LinodeCluster and will contain
154+
a succinct value suitable for machine interpretation.
142155
type: string
143156
ready:
144157
description: Ready denotes that the cluster (infrastructure) is ready.

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

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