Skip to content

Commit fe09ec0

Browse files
committed
NodeRef controller
Signed-off-by: Vince Prignano <[email protected]>
1 parent 7365154 commit fe09ec0

13 files changed

+598
-27
lines changed

config/rbac/rbac_role.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,11 @@ rules:
101101
- update
102102
- patch
103103
- delete
104+
- apiGroups:
105+
- ""
106+
resources:
107+
- secrets
108+
verbs:
109+
- get
110+
- list
111+
- watch

pkg/controller/BUILD.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ go_library(
66
"add_machinedeployment.go",
77
"add_machineset.go",
88
"add_node.go",
9+
"add_noderef.go",
910
"controller.go",
1011
],
1112
importpath = "sigs.k8s.io/cluster-api/pkg/controller",
@@ -14,6 +15,7 @@ go_library(
1415
"//pkg/controller/machinedeployment:go_default_library",
1516
"//pkg/controller/machineset:go_default_library",
1617
"//pkg/controller/node:go_default_library",
18+
"//pkg/controller/noderef:go_default_library",
1719
"//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library",
1820
],
1921
)

pkg/controller/add_noderef.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 controller
18+
19+
import (
20+
"sigs.k8s.io/cluster-api/pkg/controller/noderef"
21+
)
22+
23+
func init() {
24+
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
25+
AddToManagerFuncs = append(AddToManagerFuncs, noderef.Add)
26+
}

pkg/controller/noderef/BUILD.bazel

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["noderef_controller.go"],
6+
importpath = "sigs.k8s.io/cluster-api/pkg/controller/noderef",
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"//pkg/apis/cluster/v1alpha1:go_default_library",
10+
"//pkg/controller/noderefutil:go_default_library",
11+
"//pkg/controller/remote:go_default_library",
12+
"//vendor/github.com/pkg/errors:go_default_library",
13+
"//vendor/k8s.io/api/core/v1:go_default_library",
14+
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
15+
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
16+
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
17+
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
18+
"//vendor/k8s.io/client-go/tools/record:go_default_library",
19+
"//vendor/k8s.io/klog:go_default_library",
20+
"//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library",
21+
"//vendor/sigs.k8s.io/controller-runtime/pkg/controller:go_default_library",
22+
"//vendor/sigs.k8s.io/controller-runtime/pkg/handler:go_default_library",
23+
"//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library",
24+
"//vendor/sigs.k8s.io/controller-runtime/pkg/reconcile:go_default_library",
25+
"//vendor/sigs.k8s.io/controller-runtime/pkg/source:go_default_library",
26+
],
27+
)
28+
29+
go_test(
30+
name = "go_default_test",
31+
srcs = [
32+
"noderef_controller_suite_test.go",
33+
"noderef_controller_test.go",
34+
],
35+
embed = [":go_default_library"],
36+
deps = [
37+
"//pkg/apis:go_default_library",
38+
"//pkg/apis/cluster/v1alpha1:go_default_library",
39+
"//vendor/github.com/onsi/gomega:go_default_library",
40+
"//vendor/golang.org/x/net/context:go_default_library",
41+
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
42+
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
43+
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
44+
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
45+
"//vendor/k8s.io/client-go/rest:go_default_library",
46+
"//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library",
47+
"//vendor/sigs.k8s.io/controller-runtime/pkg/envtest:go_default_library",
48+
"//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library",
49+
"//vendor/sigs.k8s.io/controller-runtime/pkg/reconcile:go_default_library",
50+
],
51+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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 noderef
18+
19+
import (
20+
"context"
21+
"time"
22+
23+
"github.com/pkg/errors"
24+
apicorev1 "k8s.io/api/core/v1"
25+
apierrors "k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
29+
"k8s.io/client-go/tools/record"
30+
"k8s.io/klog"
31+
"sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
32+
"sigs.k8s.io/cluster-api/pkg/controller/noderefutil"
33+
"sigs.k8s.io/cluster-api/pkg/controller/remote"
34+
"sigs.k8s.io/controller-runtime/pkg/client"
35+
"sigs.k8s.io/controller-runtime/pkg/controller"
36+
"sigs.k8s.io/controller-runtime/pkg/handler"
37+
"sigs.k8s.io/controller-runtime/pkg/manager"
38+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
39+
"sigs.k8s.io/controller-runtime/pkg/source"
40+
)
41+
42+
// controllerName is the name of this controller
43+
const controllerName = "noderef-controller"
44+
45+
var (
46+
ErrNodeNotFound = errors.New("cannot find node with maching ProviderID")
47+
)
48+
49+
// Add creates a new NodeRef Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
50+
// and Start it when the Manager is Started.
51+
func Add(mgr manager.Manager) error {
52+
return add(mgr, newReconciler(mgr))
53+
}
54+
55+
// newReconciler returns a new reconcile.Reconciler
56+
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
57+
return &ReconcileNodeRef{Client: mgr.GetClient(), scheme: mgr.GetScheme(), recorder: mgr.GetRecorder(controllerName)}
58+
}
59+
60+
// add adds a new Controller to mgr with r as the reconcile.Reconciler
61+
func add(mgr manager.Manager, r reconcile.Reconciler) error {
62+
// Create a new controller
63+
c, err := controller.New("noderef-controller", mgr, controller.Options{Reconciler: r})
64+
if err != nil {
65+
return err
66+
}
67+
68+
// Watch for changes to Machines.
69+
return c.Watch(&source.Kind{Type: &v1alpha1.Machine{}}, &handler.EnqueueRequestForObject{})
70+
}
71+
72+
var _ reconcile.Reconciler = &ReconcileNodeRef{}
73+
74+
// ReconcileNodeRef reconciles a Machine object.
75+
type ReconcileNodeRef struct {
76+
client.Client
77+
scheme *runtime.Scheme
78+
recorder record.EventRecorder
79+
}
80+
81+
// Reconcile watches Machines.
82+
// +kubebuilder:rbac:groups=,resources=secrets,verbs=get;list;watch
83+
func (r *ReconcileNodeRef) Reconcile(request reconcile.Request) (reconcile.Result, error) {
84+
klog.Infof("Reconcile request for Machine %q in namespace %q", request.Name, request.Namespace)
85+
ctx := context.Background()
86+
87+
// Fetch the NodeRef instance
88+
machine := &v1alpha1.Machine{}
89+
err := r.Get(ctx, request.NamespacedName, machine)
90+
if err != nil {
91+
if apierrors.IsNotFound(err) {
92+
klog.V(2).Infof("Machine %q in namespace %q is not found, won't reconcile", machine.Name, machine.Namespace)
93+
return reconcile.Result{}, nil
94+
}
95+
return reconcile.Result{}, err
96+
}
97+
98+
// Check that the Machine hasn't been deleted or in the process.
99+
if !machine.DeletionTimestamp.IsZero() {
100+
klog.V(2).Infof("Machine %q in namespace %q has been deleted, won't reconcile", machine.Name, machine.Namespace)
101+
return reconcile.Result{}, nil
102+
}
103+
104+
// Check that the Machine doesn't already have a NodeRef.
105+
if machine.Status.NodeRef != nil {
106+
klog.V(2).Infof("Machine %q in namespace %q already has a NodeRef, won't reconcile", machine.Name, machine.Namespace)
107+
return reconcile.Result{}, nil
108+
}
109+
110+
// Check that the Machine has a cluster label.
111+
if machine.Labels[v1alpha1.MachineClusterLabelName] == "" {
112+
klog.V(2).Infof("Machine %q in namespace %q doesn't specify %q label, won't reconcile", machine.Name, machine.Namespace,
113+
v1alpha1.MachineClusterLabelName)
114+
return reconcile.Result{}, nil
115+
}
116+
117+
// Check that the Machine has a valid ProviderID.
118+
if machine.Spec.ProviderID == nil || *machine.Spec.ProviderID == "" {
119+
klog.Warningf("Machine %q in namespace %q doesn't have a valid ProviderID, retrying later", machine.Name, machine.Namespace)
120+
return reconcile.Result{RequeueAfter: 30 * time.Second}, nil
121+
}
122+
123+
result, err := r.reconcile(ctx, machine)
124+
if err != nil {
125+
klog.Errorf("Failed to assign NodeRef to Machine %q: %v", request.NamespacedName, err)
126+
r.recorder.Eventf(machine, apicorev1.EventTypeWarning, "NodeRefReconcileError", "%v", err)
127+
return result, err
128+
}
129+
130+
klog.Infof("Set Machine's (%q in namespace %q) NodeRef to %q", machine.Name, machine.Namespace, machine.Status.NodeRef.Name)
131+
return result, nil
132+
}
133+
134+
func (r ReconcileNodeRef) reconcile(ctx context.Context, machine *v1alpha1.Machine) (reconcile.Result, error) {
135+
providerID, err := noderefutil.NewProviderID(*machine.Spec.ProviderID)
136+
if err != nil {
137+
return reconcile.Result{}, err
138+
}
139+
140+
cluster, err := r.getCluster(ctx, machine)
141+
if err != nil {
142+
return reconcile.Result{}, err
143+
}
144+
145+
clusterClient, err := remote.NewClusterClient(r.Client, cluster)
146+
if err != nil {
147+
return reconcile.Result{}, err
148+
}
149+
150+
corev1Client, err := clusterClient.CoreV1()
151+
if err != nil {
152+
return reconcile.Result{}, err
153+
}
154+
155+
// Get the Node reference.
156+
nodeRef, err := r.getNodeReference(corev1Client, providerID)
157+
if err != nil {
158+
if err == ErrNodeNotFound {
159+
klog.Warningf("Cannot find a matching Node for Machine %q in namespace %q, retrying later", machine.Name, machine.Namespace)
160+
return reconcile.Result{RequeueAfter: 10 * time.Second}, nil
161+
}
162+
return reconcile.Result{}, err
163+
}
164+
165+
// Update Machine.
166+
machine.Status.NodeRef = nodeRef
167+
if err := r.Client.Status().Update(ctx, machine); err != nil {
168+
return reconcile.Result{}, err
169+
}
170+
171+
return reconcile.Result{}, nil
172+
}
173+
174+
func (r *ReconcileNodeRef) getCluster(ctx context.Context, machine *v1alpha1.Machine) (*v1alpha1.Cluster, error) {
175+
cluster := &v1alpha1.Cluster{}
176+
key := client.ObjectKey{
177+
Namespace: machine.Namespace,
178+
Name: machine.Labels[v1alpha1.MachineClusterLabelName],
179+
}
180+
181+
if err := r.Client.Get(ctx, key, cluster); err != nil {
182+
return nil, err
183+
}
184+
185+
return cluster, nil
186+
}
187+
188+
func (r *ReconcileNodeRef) getNodeReference(client corev1.NodesGetter, providerID *noderefutil.ProviderID) (*apicorev1.ObjectReference, error) {
189+
listOpt := metav1.ListOptions{}
190+
191+
for {
192+
nodeList, err := client.Nodes().List(listOpt)
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
for _, node := range nodeList.Items {
198+
nodeProviderID, err := noderefutil.NewProviderID(node.Spec.ProviderID)
199+
if err != nil {
200+
continue
201+
}
202+
203+
if providerID.Equals(nodeProviderID) {
204+
return &apicorev1.ObjectReference{
205+
Kind: node.Kind,
206+
APIVersion: node.APIVersion,
207+
Name: node.Name,
208+
UID: node.UID,
209+
}, nil
210+
}
211+
}
212+
213+
listOpt.Continue = nodeList.Continue
214+
if listOpt.Continue == "" {
215+
break
216+
}
217+
}
218+
219+
return nil, ErrNodeNotFound
220+
}

0 commit comments

Comments
 (0)