Skip to content

Commit 9153851

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

11 files changed

+566
-7
lines changed

config/rbac/rbac_role.yaml

+10-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ rules:
1616
- update
1717
- patch
1818
- delete
19+
- apiGroups:
20+
- ""
21+
resources:
22+
- events
23+
verbs:
24+
- get
25+
- list
26+
- watch
27+
- create
1928
- apiGroups:
2029
- cluster.k8s.io
2130
resources:
@@ -94,9 +103,6 @@ rules:
94103
- apiGroups:
95104
- ""
96105
resources:
97-
- events
106+
- secrets
98107
verbs:
99108
- get
100-
- list
101-
- watch
102-
- create

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

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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/klog:go_default_library",
19+
"//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library",
20+
"//vendor/sigs.k8s.io/controller-runtime/pkg/controller:go_default_library",
21+
"//vendor/sigs.k8s.io/controller-runtime/pkg/handler:go_default_library",
22+
"//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library",
23+
"//vendor/sigs.k8s.io/controller-runtime/pkg/reconcile:go_default_library",
24+
"//vendor/sigs.k8s.io/controller-runtime/pkg/source:go_default_library",
25+
],
26+
)
27+
28+
go_test(
29+
name = "go_default_test",
30+
srcs = [
31+
"noderef_controller_suite_test.go",
32+
"noderef_controller_test.go",
33+
],
34+
embed = [":go_default_library"],
35+
deps = [
36+
"//pkg/apis:go_default_library",
37+
"//pkg/apis/cluster/v1alpha1:go_default_library",
38+
"//vendor/github.com/onsi/gomega:go_default_library",
39+
"//vendor/golang.org/x/net/context:go_default_library",
40+
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
41+
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
42+
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
43+
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
44+
"//vendor/k8s.io/client-go/rest:go_default_library",
45+
"//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library",
46+
"//vendor/sigs.k8s.io/controller-runtime/pkg/envtest:go_default_library",
47+
"//vendor/sigs.k8s.io/controller-runtime/pkg/manager:go_default_library",
48+
"//vendor/sigs.k8s.io/controller-runtime/pkg/reconcile:go_default_library",
49+
],
50+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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+
"sigs.k8s.io/cluster-api/pkg/controller/noderefutil"
24+
25+
"github.com/pkg/errors"
26+
apicorev1 "k8s.io/api/core/v1"
27+
apierrors "k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
31+
"k8s.io/klog"
32+
clusterv1alpha1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
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+
var (
43+
ErrNodeNotFound = errors.New("cannot find node with maching ProviderID")
44+
)
45+
46+
// Add creates a new NodeRef Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
47+
// and Start it when the Manager is Started.
48+
func Add(mgr manager.Manager) error {
49+
return add(mgr, newReconciler(mgr))
50+
}
51+
52+
// newReconciler returns a new reconcile.Reconciler
53+
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
54+
return &ReconcileNodeRef{Client: mgr.GetClient(), scheme: mgr.GetScheme()}
55+
}
56+
57+
// add adds a new Controller to mgr with r as the reconcile.Reconciler
58+
func add(mgr manager.Manager, r reconcile.Reconciler) error {
59+
// Create a new controller
60+
c, err := controller.New("noderef-controller", mgr, controller.Options{Reconciler: r})
61+
if err != nil {
62+
return err
63+
}
64+
65+
// Watch for changes to Machines.
66+
err = c.Watch(&source.Kind{Type: &clusterv1alpha1.Machine{}}, &handler.EnqueueRequestForObject{})
67+
if err != nil {
68+
return err
69+
}
70+
71+
return nil
72+
}
73+
74+
var _ reconcile.Reconciler = &ReconcileNodeRef{}
75+
76+
// ReconcileNodeRef reconciles a Machine object.
77+
type ReconcileNodeRef struct {
78+
client.Client
79+
scheme *runtime.Scheme
80+
}
81+
82+
// Reconcile watches Machines.
83+
// +kubebuilder:rbac:groups=,resources=secrets,verbs=get
84+
func (r *ReconcileNodeRef) Reconcile(request reconcile.Request) (reconcile.Result, error) {
85+
// Fetch the NodeRef instance
86+
machine := &clusterv1alpha1.Machine{}
87+
err := r.Get(context.TODO(), request.NamespacedName, machine)
88+
if err != nil {
89+
if apierrors.IsNotFound(err) {
90+
return reconcile.Result{}, nil
91+
}
92+
return reconcile.Result{}, err
93+
}
94+
95+
// Check that the Machine hasn't been deleted or in the process.
96+
if !machine.DeletionTimestamp.IsZero() {
97+
return reconcile.Result{}, nil
98+
}
99+
100+
// Check that the Machine doesn't already have a NodeRef.
101+
if machine.Status.NodeRef != nil {
102+
return reconcile.Result{}, nil
103+
}
104+
105+
// Check that the Machine has a cluster label.
106+
if machine.Labels[clusterv1alpha1.MachineClusterLabelName] == "" {
107+
klog.Warningf("Machine %q in namespace %q doesn't specify %q label, cannot set nodeRef", machine.Name, machine.Namespace, clusterv1alpha1.MachineClusterLabelName)
108+
return reconcile.Result{}, nil
109+
}
110+
111+
// Check that the Machine has a valid ProviderID.
112+
if machine.Spec.ProviderID == nil || *machine.Spec.ProviderID == "" {
113+
klog.Warningf("Machine %q in namespace %q doesn't have a valid ProviderID, cannot set nodeRef", machine.Name, machine.Namespace)
114+
return reconcile.Result{}, nil
115+
}
116+
117+
providerID, err := noderefutil.NewProviderID(*machine.Spec.ProviderID)
118+
if err != nil {
119+
return reconcile.Result{}, err
120+
}
121+
122+
cluster, err := r.getCluster(context.TODO(), machine)
123+
if err != nil {
124+
return reconcile.Result{}, err
125+
}
126+
127+
clusterClient, err := remote.NewClusterClient(r.Client, cluster)
128+
if err != nil {
129+
return reconcile.Result{}, err
130+
}
131+
132+
corev1Client, err := clusterClient.CoreV1()
133+
if err != nil {
134+
return reconcile.Result{}, err
135+
}
136+
137+
// Get the Node reference.
138+
nodeRef, err := r.getNodeReference(corev1Client, providerID)
139+
if err != nil {
140+
if err == ErrNodeNotFound {
141+
return reconcile.Result{RequeueAfter: 10 * time.Second}, nil
142+
}
143+
return reconcile.Result{}, err
144+
}
145+
146+
// Update Machine.
147+
machine.Status.NodeRef = nodeRef
148+
if err := r.Client.Update(context.TODO(), machine); err != nil {
149+
return reconcile.Result{}, err
150+
}
151+
152+
return reconcile.Result{}, nil
153+
}
154+
155+
func (r *ReconcileNodeRef) getCluster(ctx context.Context, machine *clusterv1alpha1.Machine) (*clusterv1alpha1.Cluster, error) {
156+
cluster := &clusterv1alpha1.Cluster{}
157+
key := client.ObjectKey{
158+
Namespace: machine.Namespace,
159+
Name: machine.Labels[clusterv1alpha1.MachineClusterLabelName],
160+
}
161+
162+
if err := r.Client.Get(ctx, key, cluster); err != nil {
163+
return nil, err
164+
}
165+
166+
return cluster, nil
167+
}
168+
169+
func (r *ReconcileNodeRef) getNodeReference(client corev1.NodesGetter, providerID *noderefutil.ProviderID) (*apicorev1.ObjectReference, error) {
170+
listOpt := metav1.ListOptions{}
171+
172+
for {
173+
nodeList, err := client.Nodes().List(listOpt)
174+
if err != nil {
175+
return nil, err
176+
}
177+
178+
for _, node := range nodeList.Items {
179+
nodeProviderID, err := noderefutil.NewProviderID(node.Spec.ProviderID)
180+
if err != nil {
181+
continue
182+
}
183+
184+
if providerID.Equals(nodeProviderID) {
185+
return &apicorev1.ObjectReference{
186+
Kind: node.Kind,
187+
APIVersion: node.APIVersion,
188+
Name: node.Name,
189+
UID: node.UID,
190+
}, nil
191+
}
192+
}
193+
194+
listOpt.Continue = nodeList.Continue
195+
if listOpt.Continue == "" {
196+
break
197+
}
198+
}
199+
200+
return nil, ErrNodeNotFound
201+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
stdlog "log"
21+
"os"
22+
"path/filepath"
23+
"sync"
24+
"testing"
25+
26+
"github.com/onsi/gomega"
27+
"k8s.io/client-go/kubernetes/scheme"
28+
"k8s.io/client-go/rest"
29+
"sigs.k8s.io/cluster-api/pkg/apis"
30+
"sigs.k8s.io/controller-runtime/pkg/envtest"
31+
"sigs.k8s.io/controller-runtime/pkg/manager"
32+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
33+
)
34+
35+
var cfg *rest.Config
36+
37+
func TestMain(m *testing.M) {
38+
t := &envtest.Environment{
39+
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")},
40+
}
41+
apis.AddToScheme(scheme.Scheme)
42+
43+
var err error
44+
if cfg, err = t.Start(); err != nil {
45+
stdlog.Fatal(err)
46+
}
47+
48+
code := m.Run()
49+
t.Stop()
50+
os.Exit(code)
51+
}
52+
53+
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and
54+
// writes the request to requests after Reconcile is finished.
55+
func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) {
56+
requests := make(chan reconcile.Request)
57+
fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
58+
result, err := inner.Reconcile(req)
59+
requests <- req
60+
return result, err
61+
})
62+
return fn, requests
63+
}
64+
65+
// StartTestManager adds recFn
66+
func StartTestManager(mgr manager.Manager, g *gomega.GomegaWithT) (chan struct{}, *sync.WaitGroup) {
67+
stop := make(chan struct{})
68+
wg := &sync.WaitGroup{}
69+
wg.Add(1)
70+
go func() {
71+
defer wg.Done()
72+
g.Expect(mgr.Start(stop)).NotTo(gomega.HaveOccurred())
73+
}()
74+
return stop, wg
75+
}

0 commit comments

Comments
 (0)