Skip to content

Commit 7ef629a

Browse files
author
Rahul Sharma
committed
add unittests for node_controller
1 parent b1b9af7 commit 7ef629a

File tree

3 files changed

+165
-3
lines changed

3 files changed

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

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)