Skip to content

Commit 0f96702

Browse files
author
Rahul Sharma
committed
add unittests for node_controller
1 parent b1b9af7 commit 0f96702

File tree

3 files changed

+167
-3
lines changed

3 files changed

+167
-3
lines changed

cloud/linode/node_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (s *nodeController) processNext() bool {
120120
case *linodego.Error:
121121
if deleteErr.Code >= http.StatusInternalServerError || deleteErr.Code == http.StatusTooManyRequests {
122122
klog.Errorf("failed to add metadata for node (%s); retrying in 1 minute: %s", node.Name, err)
123-
s.queue.AddAfter(node, retryInterval)
123+
s.queue.AddAfter(node, defaultRetryInterval)
124124
}
125125

126126
default:
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package linode
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net"
7+
"net/http"
8+
"testing"
9+
"time"
10+
11+
"github.com/golang/mock/gomock"
12+
"github.com/linode/linode-cloud-controller-manager/cloud/annotations"
13+
"github.com/linode/linode-cloud-controller-manager/cloud/linode/client/mocks"
14+
"github.com/linode/linodego"
15+
"github.com/stretchr/testify/assert"
16+
v1 "k8s.io/api/core/v1"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"k8s.io/client-go/kubernetes/fake"
19+
"k8s.io/client-go/util/workqueue"
20+
)
21+
22+
func TestNodeController_processNext(t *testing.T) {
23+
// Mock dependencies
24+
ctrl := gomock.NewController(t)
25+
defer ctrl.Finish()
26+
client := mocks.NewMockClient(ctrl)
27+
kubeClient := fake.NewSimpleClientset()
28+
queue := workqueue.NewTypedDelayingQueueWithConfig(workqueue.TypedDelayingQueueConfig[any]{Name: "testQueue"})
29+
node := &v1.Node{
30+
ObjectMeta: metav1.ObjectMeta{
31+
Name: "test",
32+
Labels: map[string]string{},
33+
Annotations: map[string]string{},
34+
},
35+
Spec: v1.NodeSpec{ProviderID: "linode://111"},
36+
}
37+
38+
_, err := kubeClient.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{})
39+
assert.NoError(t, err, "expected no error during node creation")
40+
41+
controller := &nodeController{
42+
kubeclient: kubeClient,
43+
instances: newInstances(client),
44+
queue: queue,
45+
metadataLastUpdate: make(map[string]time.Time),
46+
ttl: defaultMetadataTTL,
47+
}
48+
49+
t.Run("should return no error", func(t *testing.T) {
50+
queue.Add(node)
51+
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, errors.New("lookup failed"))
52+
result := controller.processNext()
53+
assert.True(t, result, "processNext should return true")
54+
if queue.Len() != 0 {
55+
t.Errorf("expected queue to be empty, got %d items", queue.Len())
56+
}
57+
})
58+
59+
t.Run("should return no error if node exists", func(t *testing.T) {
60+
queue.Add(node)
61+
publicIP := net.ParseIP("172.234.31.123")
62+
privateIP := net.ParseIP("192.168.159.135")
63+
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{
64+
{ID: 111, Label: "test", IPv4: []*net.IP{&publicIP, &privateIP}, HostUUID: "111"},
65+
}, nil)
66+
result := controller.processNext()
67+
assert.True(t, result, "processNext should return true")
68+
if queue.Len() != 0 {
69+
t.Errorf("expected queue to be empty, got %d items", queue.Len())
70+
}
71+
})
72+
73+
t.Run("should return error and requeue when it gets 429 from linode API", func(t *testing.T) {
74+
queue = workqueue.NewTypedDelayingQueueWithConfig(workqueue.TypedDelayingQueueConfig[any]{Name: "testQueue1"})
75+
queue.Add(node)
76+
controller.queue = queue
77+
client := mocks.NewMockClient(ctrl)
78+
controller.instances = newInstances(client)
79+
defaultRetryInterval = 1 * time.Nanosecond
80+
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusTooManyRequests, Message: "Too many requests"})
81+
result := controller.processNext()
82+
time.Sleep(1 * time.Second)
83+
assert.True(t, result, "processNext should return true")
84+
if queue.Len() == 0 {
85+
t.Errorf("expected queue to not be empty, got it empty")
86+
}
87+
})
88+
89+
t.Run("should return error and requeue when it gets error >= 500 from linode API", func(t *testing.T) {
90+
queue = workqueue.NewTypedDelayingQueueWithConfig(workqueue.TypedDelayingQueueConfig[any]{Name: "testQueue2"})
91+
queue.Add(node)
92+
controller.queue = queue
93+
client := mocks.NewMockClient(ctrl)
94+
controller.instances = newInstances(client)
95+
defaultRetryInterval = 1 * time.Nanosecond
96+
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusInternalServerError, Message: "Too many requests"})
97+
result := controller.processNext()
98+
time.Sleep(1 * time.Second)
99+
assert.True(t, result, "processNext should return true")
100+
if queue.Len() == 0 {
101+
t.Errorf("expected queue to not be empty, got it empty")
102+
}
103+
})
104+
}
105+
106+
func TestNodeController_handleNode(t *testing.T) {
107+
// Mock dependencies
108+
ctrl := gomock.NewController(t)
109+
defer ctrl.Finish()
110+
client := mocks.NewMockClient(ctrl)
111+
kubeClient := fake.NewSimpleClientset()
112+
node := &v1.Node{
113+
ObjectMeta: metav1.ObjectMeta{
114+
Name: "test-node",
115+
Labels: map[string]string{},
116+
Annotations: map[string]string{},
117+
},
118+
Spec: v1.NodeSpec{ProviderID: "linode://123"},
119+
}
120+
_, err := kubeClient.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{})
121+
assert.NoError(t, err, "expected no error during node creation")
122+
123+
instCache := newInstances(client)
124+
nodeCtrl := newNodeController(kubeClient, client, nil, instCache)
125+
126+
// Test: Successful metadata update
127+
publicIP := net.ParseIP("172.234.31.123")
128+
privateIP := net.ParseIP("192.168.159.135")
129+
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{
130+
{ID: 123, Label: "test-node", IPv4: []*net.IP{&publicIP, &privateIP}, HostUUID: "123"},
131+
}, nil)
132+
err = nodeCtrl.handleNode(context.TODO(), node)
133+
assert.NoError(t, err, "expected no error during handleNode")
134+
135+
// Check metadataLastUpdate
136+
lastUpdate := nodeCtrl.LastMetadataUpdate("test-node")
137+
if time.Since(lastUpdate) > 5*time.Second {
138+
t.Errorf("metadataLastUpdate was not updated correctly")
139+
}
140+
141+
// Annotations set, no update needed as ttl not reached
142+
node.Labels[annotations.AnnLinodeHostUUID] = "123"
143+
node.Annotations[annotations.AnnLinodeNodePrivateIP] = privateIP.String()
144+
err = nodeCtrl.handleNode(context.TODO(), node)
145+
assert.NoError(t, err, "expected no error during handleNode")
146+
147+
// Lookup failure for linode instance
148+
client = mocks.NewMockClient(ctrl)
149+
nodeCtrl.instances = newInstances(client)
150+
nodeCtrl.metadataLastUpdate["test-node"] = time.Now().Add(-2 * nodeCtrl.ttl)
151+
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, errors.New("lookup failed"))
152+
err = nodeCtrl.handleNode(context.TODO(), node)
153+
assert.Error(t, err, "expected error during handleNode, got nil")
154+
155+
// All fields already set
156+
client = mocks.NewMockClient(ctrl)
157+
nodeCtrl.instances = newInstances(client)
158+
nodeCtrl.metadataLastUpdate["test-node"] = time.Now().Add(-2 * nodeCtrl.ttl)
159+
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{
160+
{ID: 123, Label: "test-node", IPv4: []*net.IP{&publicIP, &privateIP}, HostUUID: "123"},
161+
}, nil)
162+
err = nodeCtrl.handleNode(context.TODO(), node)
163+
assert.NoError(t, err, "expected no error during handleNode")
164+
}

cloud/linode/service_controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"k8s.io/klog/v2"
1616
)
1717

18-
const retryInterval = time.Minute * 1
18+
var defaultRetryInterval = time.Minute * 1
1919

2020
type serviceController struct {
2121
loadbalancers *loadbalancers
@@ -98,7 +98,7 @@ func (s *serviceController) processNextDeletion() bool {
9898
case *linodego.Error:
9999
if deleteErr.Code >= http.StatusInternalServerError || deleteErr.Code == http.StatusTooManyRequests {
100100
klog.Errorf("failed to delete NodeBalancer for service (%s); retrying in 1 minute: %s", getServiceNn(service), err)
101-
s.queue.AddAfter(service, retryInterval)
101+
s.queue.AddAfter(service, defaultRetryInterval)
102102
}
103103

104104
default:

0 commit comments

Comments
 (0)