1
16
17 package nodestatus
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "net"
24 "sort"
25 "strconv"
26 "testing"
27 "time"
28
29 cadvisorapiv1 "github.com/google/cadvisor/info/v1"
30 "github.com/google/go-cmp/cmp"
31 "github.com/stretchr/testify/assert"
32 "github.com/stretchr/testify/require"
33
34 v1 "k8s.io/api/core/v1"
35 apiequality "k8s.io/apimachinery/pkg/api/equality"
36 "k8s.io/apimachinery/pkg/api/resource"
37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
38 "k8s.io/apimachinery/pkg/util/rand"
39 "k8s.io/apimachinery/pkg/util/uuid"
40 utilfeature "k8s.io/apiserver/pkg/util/feature"
41 cloudprovider "k8s.io/cloud-provider"
42 fakecloud "k8s.io/cloud-provider/fake"
43 featuregatetesting "k8s.io/component-base/featuregate/testing"
44 "k8s.io/component-base/version"
45 "k8s.io/kubernetes/pkg/features"
46 "k8s.io/kubernetes/pkg/kubelet/cm"
47 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
48 kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
49 "k8s.io/kubernetes/pkg/kubelet/events"
50 "k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
51 "k8s.io/kubernetes/pkg/volume"
52 volumetest "k8s.io/kubernetes/pkg/volume/testing"
53 netutils "k8s.io/utils/net"
54 )
55
56 const (
57 testKubeletHostname = "hostname"
58 )
59
60
61 func TestNodeAddress(t *testing.T) {
62 type cloudProviderType int
63 const (
64 cloudProviderLegacy cloudProviderType = iota
65 cloudProviderExternal
66 cloudProviderNone
67 )
68 existingNodeAddress := v1.NodeAddress{Address: "10.1.1.2"}
69 cases := []struct {
70 name string
71 hostnameOverride bool
72 nodeIP net.IP
73 secondaryNodeIP net.IP
74 cloudProviderType cloudProviderType
75 nodeAddresses []v1.NodeAddress
76 expectedAddresses []v1.NodeAddress
77 existingAnnotations map[string]string
78 expectedAnnotations map[string]string
79 shouldError bool
80 shouldSetNodeAddressBeforeTest bool
81 }{
82 {
83 name: "A single InternalIP",
84 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
85 nodeAddresses: []v1.NodeAddress{
86 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
87 {Type: v1.NodeHostName, Address: testKubeletHostname},
88 },
89 expectedAddresses: []v1.NodeAddress{
90 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
91 {Type: v1.NodeHostName, Address: testKubeletHostname},
92 },
93 shouldError: false,
94 },
95 {
96 name: "NodeIP is external",
97 nodeIP: netutils.ParseIPSloppy("55.55.55.55"),
98 nodeAddresses: []v1.NodeAddress{
99 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
100 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
101 {Type: v1.NodeHostName, Address: testKubeletHostname},
102 },
103 expectedAddresses: []v1.NodeAddress{
104 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
105 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
106 {Type: v1.NodeHostName, Address: testKubeletHostname},
107 },
108 shouldError: false,
109 },
110 {
111
112 name: "InternalIP and ExternalIP are the same",
113 nodeIP: netutils.ParseIPSloppy("55.55.55.55"),
114 nodeAddresses: []v1.NodeAddress{
115 {Type: v1.NodeInternalIP, Address: "44.44.44.44"},
116 {Type: v1.NodeExternalIP, Address: "44.44.44.44"},
117 {Type: v1.NodeInternalIP, Address: "55.55.55.55"},
118 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
119 {Type: v1.NodeHostName, Address: testKubeletHostname},
120 },
121 expectedAddresses: []v1.NodeAddress{
122 {Type: v1.NodeInternalIP, Address: "55.55.55.55"},
123 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
124 {Type: v1.NodeHostName, Address: testKubeletHostname},
125 },
126 shouldError: false,
127 },
128 {
129 name: "An Internal/ExternalIP, an Internal/ExternalDNS",
130 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
131 nodeAddresses: []v1.NodeAddress{
132 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
133 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
134 {Type: v1.NodeInternalDNS, Address: "ip-10-1-1-1.us-west-2.compute.internal"},
135 {Type: v1.NodeExternalDNS, Address: "ec2-55-55-55-55.us-west-2.compute.amazonaws.com"},
136 {Type: v1.NodeHostName, Address: testKubeletHostname},
137 },
138 expectedAddresses: []v1.NodeAddress{
139 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
140 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
141 {Type: v1.NodeInternalDNS, Address: "ip-10-1-1-1.us-west-2.compute.internal"},
142 {Type: v1.NodeExternalDNS, Address: "ec2-55-55-55-55.us-west-2.compute.amazonaws.com"},
143 {Type: v1.NodeHostName, Address: testKubeletHostname},
144 },
145 shouldError: false,
146 },
147 {
148 name: "An Internal with multiple internal IPs",
149 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
150 nodeAddresses: []v1.NodeAddress{
151 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
152 {Type: v1.NodeInternalIP, Address: "10.2.2.2"},
153 {Type: v1.NodeInternalIP, Address: "10.3.3.3"},
154 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
155 {Type: v1.NodeHostName, Address: testKubeletHostname},
156 },
157 expectedAddresses: []v1.NodeAddress{
158 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
159 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
160 {Type: v1.NodeHostName, Address: testKubeletHostname},
161 },
162 shouldError: false,
163 },
164 {
165 name: "An InternalIP that isn't valid: should error",
166 nodeIP: netutils.ParseIPSloppy("10.2.2.2"),
167 nodeAddresses: []v1.NodeAddress{
168 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
169 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
170 {Type: v1.NodeHostName, Address: testKubeletHostname},
171 },
172 expectedAddresses: nil,
173 shouldError: true,
174 },
175 {
176 name: "no cloud reported hostnames",
177 nodeAddresses: []v1.NodeAddress{},
178 expectedAddresses: []v1.NodeAddress{
179 {Type: v1.NodeHostName, Address: testKubeletHostname},
180 },
181 shouldError: false,
182 },
183 {
184 name: "cloud reports hostname, no override",
185 nodeAddresses: []v1.NodeAddress{
186 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
187 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
188 {Type: v1.NodeHostName, Address: "cloud-host"},
189 },
190 expectedAddresses: []v1.NodeAddress{
191 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
192 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
193 {Type: v1.NodeHostName, Address: "cloud-host"},
194 },
195 shouldError: false,
196 },
197 {
198 name: "cloud reports hostname, nodeIP is set, no override",
199 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
200 nodeAddresses: []v1.NodeAddress{
201 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
202 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
203 {Type: v1.NodeHostName, Address: "cloud-host"},
204 },
205 expectedAddresses: []v1.NodeAddress{
206 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
207 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
208 {Type: v1.NodeHostName, Address: "cloud-host"},
209 },
210 shouldError: false,
211 },
212 {
213 name: "cloud reports hostname, overridden",
214 nodeAddresses: []v1.NodeAddress{
215 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
216 {Type: v1.NodeHostName, Address: "cloud-host"},
217 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
218 },
219 expectedAddresses: []v1.NodeAddress{
220 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
221 {Type: v1.NodeHostName, Address: testKubeletHostname},
222 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
223 },
224 hostnameOverride: true,
225 shouldError: false,
226 },
227 {
228 name: "cloud provider is external and nodeIP specified",
229 nodeIP: netutils.ParseIPSloppy("10.0.0.1"),
230 nodeAddresses: []v1.NodeAddress{},
231 cloudProviderType: cloudProviderExternal,
232 expectedAddresses: []v1.NodeAddress{
233 {Type: v1.NodeInternalIP, Address: "10.0.0.1"},
234 {Type: v1.NodeHostName, Address: testKubeletHostname},
235 },
236 shouldError: false,
237 },
238 {
239 name: "cloud provider is external and nodeIP unspecified",
240 nodeIP: netutils.ParseIPSloppy("::"),
241 nodeAddresses: []v1.NodeAddress{},
242 cloudProviderType: cloudProviderExternal,
243 expectedAddresses: []v1.NodeAddress{},
244 shouldError: false,
245 },
246 {
247 name: "cloud provider is external and no nodeIP",
248 nodeAddresses: []v1.NodeAddress{},
249 cloudProviderType: cloudProviderExternal,
250 expectedAddresses: []v1.NodeAddress{},
251 shouldError: false,
252 },
253 {
254 name: "cloud doesn't report hostname, no override, detected hostname mismatch",
255 nodeAddresses: []v1.NodeAddress{
256 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
257 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
258 },
259 expectedAddresses: []v1.NodeAddress{
260 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
261 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
262
263 },
264 shouldError: false,
265 },
266 {
267 name: "cloud doesn't report hostname, no override, detected hostname match",
268 nodeAddresses: []v1.NodeAddress{
269 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
270 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
271 {Type: v1.NodeExternalDNS, Address: testKubeletHostname},
272 },
273 expectedAddresses: []v1.NodeAddress{
274 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
275 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
276 {Type: v1.NodeExternalDNS, Address: testKubeletHostname},
277 {Type: v1.NodeHostName, Address: testKubeletHostname},
278 },
279 shouldError: false,
280 },
281 {
282 name: "cloud doesn't report hostname, nodeIP is set, no override, detected hostname match",
283 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
284 nodeAddresses: []v1.NodeAddress{
285 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
286 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
287 {Type: v1.NodeExternalDNS, Address: testKubeletHostname},
288 },
289 expectedAddresses: []v1.NodeAddress{
290 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
291 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
292 {Type: v1.NodeExternalDNS, Address: testKubeletHostname},
293 {Type: v1.NodeHostName, Address: testKubeletHostname},
294 },
295 shouldError: false,
296 },
297 {
298 name: "cloud doesn't report hostname, nodeIP is set, no override, detected hostname match with same type as nodeIP",
299 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
300 nodeAddresses: []v1.NodeAddress{
301 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
302 {Type: v1.NodeInternalIP, Address: testKubeletHostname},
303 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
304 },
305 expectedAddresses: []v1.NodeAddress{
306 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
307 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
308 {Type: v1.NodeHostName, Address: testKubeletHostname},
309 },
310 shouldError: false,
311 },
312 {
313 name: "cloud doesn't report hostname, hostname override, hostname mismatch",
314 nodeAddresses: []v1.NodeAddress{
315 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
316 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
317 },
318 expectedAddresses: []v1.NodeAddress{
319 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
320 {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
321 {Type: v1.NodeHostName, Address: testKubeletHostname},
322 },
323 hostnameOverride: true,
324 shouldError: false,
325 },
326 {
327 name: "Dual-stack cloud, with nodeIP, different IPv6 formats",
328 nodeIP: netutils.ParseIPSloppy("2600:1f14:1d4:d101::ba3d"),
329 nodeAddresses: []v1.NodeAddress{
330 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
331 {Type: v1.NodeInternalIP, Address: "2600:1f14:1d4:d101:0:0:0:ba3d"},
332 {Type: v1.NodeHostName, Address: testKubeletHostname},
333 },
334 expectedAddresses: []v1.NodeAddress{
335 {Type: v1.NodeInternalIP, Address: "2600:1f14:1d4:d101:0:0:0:ba3d"},
336 {Type: v1.NodeHostName, Address: testKubeletHostname},
337 },
338 shouldError: false,
339 },
340 {
341 name: "Dual-stack cloud, IPv4 first, no nodeIP",
342 nodeAddresses: []v1.NodeAddress{
343 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
344 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
345 {Type: v1.NodeHostName, Address: testKubeletHostname},
346 },
347 expectedAddresses: []v1.NodeAddress{
348 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
349 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
350 {Type: v1.NodeHostName, Address: testKubeletHostname},
351 },
352 shouldError: false,
353 },
354 {
355 name: "Dual-stack cloud, IPv6 first, no nodeIP",
356 nodeAddresses: []v1.NodeAddress{
357 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
358 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
359 {Type: v1.NodeHostName, Address: testKubeletHostname},
360 },
361 expectedAddresses: []v1.NodeAddress{
362 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
363 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
364 {Type: v1.NodeHostName, Address: testKubeletHostname},
365 },
366 shouldError: false,
367 },
368 {
369 name: "Dual-stack cloud, IPv4 first, request IPv4",
370 nodeIP: netutils.ParseIPSloppy("0.0.0.0"),
371 nodeAddresses: []v1.NodeAddress{
372 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
373 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
374 {Type: v1.NodeHostName, Address: testKubeletHostname},
375 },
376 expectedAddresses: []v1.NodeAddress{
377 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
378 {Type: v1.NodeHostName, Address: testKubeletHostname},
379 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
380 },
381 shouldError: false,
382 },
383 {
384 name: "Dual-stack cloud, IPv6 first, request IPv4",
385 nodeIP: netutils.ParseIPSloppy("0.0.0.0"),
386 nodeAddresses: []v1.NodeAddress{
387 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
388 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
389 {Type: v1.NodeHostName, Address: testKubeletHostname},
390 },
391 expectedAddresses: []v1.NodeAddress{
392 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
393 {Type: v1.NodeHostName, Address: testKubeletHostname},
394 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
395 },
396 shouldError: false,
397 },
398 {
399 name: "Dual-stack cloud, IPv4 first, request IPv6",
400 nodeIP: netutils.ParseIPSloppy("::"),
401 nodeAddresses: []v1.NodeAddress{
402 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
403 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
404 {Type: v1.NodeHostName, Address: testKubeletHostname},
405 },
406 expectedAddresses: []v1.NodeAddress{
407 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
408 {Type: v1.NodeHostName, Address: testKubeletHostname},
409 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
410 },
411 shouldError: false,
412 },
413 {
414 name: "Dual-stack cloud, IPv6 first, request IPv6",
415 nodeIP: netutils.ParseIPSloppy("::"),
416 nodeAddresses: []v1.NodeAddress{
417 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
418 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
419 {Type: v1.NodeHostName, Address: testKubeletHostname},
420 },
421 expectedAddresses: []v1.NodeAddress{
422 {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"},
423 {Type: v1.NodeHostName, Address: testKubeletHostname},
424 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
425 },
426 shouldError: false,
427 },
428 {
429 name: "Legacy cloud provider gets nodeIP annotation",
430 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
431 cloudProviderType: cloudProviderLegacy,
432 nodeAddresses: []v1.NodeAddress{
433 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
434 {Type: v1.NodeHostName, Address: testKubeletHostname},
435 },
436 expectedAddresses: []v1.NodeAddress{
437 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
438 {Type: v1.NodeHostName, Address: testKubeletHostname},
439 },
440 expectedAnnotations: map[string]string{
441 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1",
442 },
443 shouldError: false,
444 },
445 {
446 name: "External cloud provider gets nodeIP annotation",
447 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
448 cloudProviderType: cloudProviderExternal,
449 nodeAddresses: []v1.NodeAddress{
450 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
451 {Type: v1.NodeHostName, Address: testKubeletHostname},
452 },
453 expectedAddresses: []v1.NodeAddress{
454 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
455 {Type: v1.NodeHostName, Address: testKubeletHostname},
456 },
457 expectedAnnotations: map[string]string{
458 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1",
459 },
460 shouldError: false,
461 },
462 {
463 name: "External cloud provider, node address is already set",
464 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
465 cloudProviderType: cloudProviderExternal,
466 nodeAddresses: []v1.NodeAddress{existingNodeAddress},
467 expectedAddresses: []v1.NodeAddress{existingNodeAddress},
468 shouldError: true,
469 shouldSetNodeAddressBeforeTest: true,
470 },
471 {
472 name: "No cloud provider does not get nodeIP annotation",
473 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
474 cloudProviderType: cloudProviderNone,
475 nodeAddresses: []v1.NodeAddress{
476 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
477 {Type: v1.NodeHostName, Address: testKubeletHostname},
478 },
479 expectedAddresses: []v1.NodeAddress{
480 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
481 {Type: v1.NodeHostName, Address: testKubeletHostname},
482 },
483 expectedAnnotations: map[string]string{},
484 shouldError: false,
485 },
486 {
487 name: "Stale nodeIP annotation is removed when not using cloud provider",
488 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
489 cloudProviderType: cloudProviderNone,
490 nodeAddresses: []v1.NodeAddress{
491 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
492 {Type: v1.NodeHostName, Address: testKubeletHostname},
493 },
494 expectedAddresses: []v1.NodeAddress{
495 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
496 {Type: v1.NodeHostName, Address: testKubeletHostname},
497 },
498 existingAnnotations: map[string]string{
499 "alpha.kubernetes.io/provided-node-ip": "10.1.1.3",
500 },
501 expectedAnnotations: map[string]string{},
502 shouldError: false,
503 },
504 {
505 name: "Stale nodeIP annotation is removed when using cloud provider but no --node-ip",
506 nodeIP: nil,
507 cloudProviderType: cloudProviderLegacy,
508 nodeAddresses: []v1.NodeAddress{
509 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
510 {Type: v1.NodeHostName, Address: testKubeletHostname},
511 },
512 expectedAddresses: []v1.NodeAddress{
513 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
514 {Type: v1.NodeHostName, Address: testKubeletHostname},
515 },
516 existingAnnotations: map[string]string{
517 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1",
518 },
519 expectedAnnotations: map[string]string{},
520 shouldError: false,
521 },
522 {
523 name: "Incorrect nodeIP annotation is fixed",
524 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
525 cloudProviderType: cloudProviderExternal,
526 nodeAddresses: []v1.NodeAddress{
527 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
528 {Type: v1.NodeHostName, Address: testKubeletHostname},
529 },
530 expectedAddresses: []v1.NodeAddress{
531 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
532 {Type: v1.NodeHostName, Address: testKubeletHostname},
533 },
534 existingAnnotations: map[string]string{
535 "alpha.kubernetes.io/provided-node-ip": "10.1.1.3",
536 },
537 expectedAnnotations: map[string]string{
538 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1",
539 },
540 shouldError: false,
541 },
542 {
543
544
545
546 name: "Dual-stack cloud, with dual-stack nodeIPs",
547 nodeIP: netutils.ParseIPSloppy("2600:1f14:1d4:d101::ba3d"),
548 secondaryNodeIP: netutils.ParseIPSloppy("10.1.1.2"),
549 cloudProviderType: cloudProviderExternal,
550 nodeAddresses: []v1.NodeAddress{
551 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
552 {Type: v1.NodeInternalIP, Address: "10.1.1.2"},
553 {Type: v1.NodeInternalIP, Address: "2600:1f14:1d4:d101::ba3d"},
554 {Type: v1.NodeHostName, Address: testKubeletHostname},
555 },
556 expectedAddresses: []v1.NodeAddress{
557 {Type: v1.NodeInternalIP, Address: "2600:1f14:1d4:d101::ba3d"},
558 {Type: v1.NodeInternalIP, Address: "10.1.1.2"},
559 {Type: v1.NodeHostName, Address: testKubeletHostname},
560 },
561 expectedAnnotations: map[string]string{
562 "alpha.kubernetes.io/provided-node-ip": "2600:1f14:1d4:d101::ba3d,10.1.1.2",
563 },
564 shouldError: false,
565 },
566 {
567 name: "Upgrade to cloud dual-stack nodeIPs",
568 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
569 secondaryNodeIP: netutils.ParseIPSloppy("2600:1f14:1d4:d101::ba3d"),
570 cloudProviderType: cloudProviderExternal,
571 nodeAddresses: []v1.NodeAddress{
572 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
573 {Type: v1.NodeInternalIP, Address: "2600:1f14:1d4:d101::ba3d"},
574 {Type: v1.NodeHostName, Address: testKubeletHostname},
575 },
576 expectedAddresses: []v1.NodeAddress{
577 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
578 {Type: v1.NodeInternalIP, Address: "2600:1f14:1d4:d101::ba3d"},
579 {Type: v1.NodeHostName, Address: testKubeletHostname},
580 },
581 existingAnnotations: map[string]string{
582 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1",
583 },
584 expectedAnnotations: map[string]string{
585 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1,2600:1f14:1d4:d101::ba3d",
586 },
587 shouldError: false,
588 },
589 {
590 name: "Downgrade from cloud dual-stack nodeIPs",
591 nodeIP: netutils.ParseIPSloppy("10.1.1.1"),
592 cloudProviderType: cloudProviderExternal,
593 nodeAddresses: []v1.NodeAddress{
594 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
595 {Type: v1.NodeInternalIP, Address: "2600:1f14:1d4:d101::ba3d"},
596 {Type: v1.NodeHostName, Address: testKubeletHostname},
597 },
598 expectedAddresses: []v1.NodeAddress{
599 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
600 {Type: v1.NodeHostName, Address: testKubeletHostname},
601 },
602 existingAnnotations: map[string]string{
603 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1,2600:1f14:1d4:d101::ba3d",
604 },
605 expectedAnnotations: map[string]string{
606 "alpha.kubernetes.io/provided-node-ip": "10.1.1.1",
607 },
608 shouldError: false,
609 },
610 }
611 for _, testCase := range cases {
612 t.Run(testCase.name, func(t *testing.T) {
613 ctx := context.Background()
614
615 existingNode := &v1.Node{
616 ObjectMeta: metav1.ObjectMeta{
617 Name: testKubeletHostname,
618 Annotations: testCase.existingAnnotations,
619 },
620 Spec: v1.NodeSpec{},
621 Status: v1.NodeStatus{
622 Addresses: []v1.NodeAddress{},
623 },
624 }
625
626 if testCase.shouldSetNodeAddressBeforeTest {
627 existingNode.Status.Addresses = append(existingNode.Status.Addresses, existingNodeAddress)
628 }
629
630 nodeIPValidator := func(nodeIP net.IP) error {
631 return nil
632 }
633 hostname := testKubeletHostname
634
635 nodeAddressesFunc := func() ([]v1.NodeAddress, error) {
636 return testCase.nodeAddresses, nil
637 }
638
639
640 var cloud cloudprovider.Interface
641 if testCase.cloudProviderType == cloudProviderLegacy {
642 cloud = &fakecloud.Cloud{
643 Addresses: testCase.nodeAddresses,
644 Err: nil,
645 }
646 }
647
648 nodeIPs := []net.IP{testCase.nodeIP}
649 if testCase.secondaryNodeIP != nil {
650 nodeIPs = append(nodeIPs, testCase.secondaryNodeIP)
651 }
652
653
654 setter := NodeAddress(nodeIPs,
655 nodeIPValidator,
656 hostname,
657 testCase.hostnameOverride,
658 testCase.cloudProviderType == cloudProviderExternal,
659 cloud,
660 nodeAddressesFunc)
661
662
663 err := setter(ctx, existingNode)
664 if err != nil && !testCase.shouldError {
665 t.Fatalf("unexpected error: %v", err)
666 } else if err != nil && testCase.shouldError {
667
668 return
669 }
670
671 assert.True(t, apiequality.Semantic.DeepEqual(testCase.expectedAddresses, existingNode.Status.Addresses),
672 "Diff: %s", cmp.Diff(testCase.expectedAddresses, existingNode.Status.Addresses))
673 if testCase.expectedAnnotations != nil {
674 assert.True(t, apiequality.Semantic.DeepEqual(testCase.expectedAnnotations, existingNode.Annotations),
675 "Diff: %s", cmp.Diff(testCase.expectedAnnotations, existingNode.Annotations))
676 }
677 })
678 }
679 }
680
681
682 func TestNodeAddress_NoCloudProvider(t *testing.T) {
683 cases := []struct {
684 name string
685 nodeIPs []net.IP
686 expectedAddresses []v1.NodeAddress
687 shouldError bool
688 }{
689 {
690 name: "Single --node-ip",
691 nodeIPs: []net.IP{netutils.ParseIPSloppy("10.1.1.1")},
692 expectedAddresses: []v1.NodeAddress{
693 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
694 {Type: v1.NodeHostName, Address: testKubeletHostname},
695 },
696 },
697 {
698 name: "Invalid single --node-ip (using loopback)",
699 nodeIPs: []net.IP{netutils.ParseIPSloppy("127.0.0.1")},
700 shouldError: true,
701 },
702 {
703 name: "Dual --node-ips",
704 nodeIPs: []net.IP{netutils.ParseIPSloppy("10.1.1.1"), netutils.ParseIPSloppy("fd01::1234")},
705 expectedAddresses: []v1.NodeAddress{
706 {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
707 {Type: v1.NodeInternalIP, Address: "fd01::1234"},
708 {Type: v1.NodeHostName, Address: testKubeletHostname},
709 },
710 },
711 {
712 name: "Dual --node-ips but with invalid secondary IP (using multicast IP)",
713 nodeIPs: []net.IP{netutils.ParseIPSloppy("10.1.1.1"), netutils.ParseIPSloppy("224.0.0.0")},
714 shouldError: true,
715 },
716 }
717 for _, testCase := range cases {
718 t.Run(testCase.name, func(t *testing.T) {
719 ctx := context.Background()
720
721 existingNode := &v1.Node{
722 ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname, Annotations: make(map[string]string)},
723 Spec: v1.NodeSpec{},
724 Status: v1.NodeStatus{
725 Addresses: []v1.NodeAddress{},
726 },
727 }
728
729 nodeIPValidator := func(nodeIP net.IP) error {
730 if nodeIP.IsLoopback() {
731 return fmt.Errorf("nodeIP can't be loopback address")
732 } else if nodeIP.IsMulticast() {
733 return fmt.Errorf("nodeIP can't be a multicast address")
734 }
735 return nil
736 }
737 nodeAddressesFunc := func() ([]v1.NodeAddress, error) {
738 return nil, fmt.Errorf("not reached")
739 }
740
741
742 setter := NodeAddress(testCase.nodeIPs,
743 nodeIPValidator,
744 testKubeletHostname,
745 false,
746 false,
747 nil,
748 nodeAddressesFunc)
749
750
751 err := setter(ctx, existingNode)
752 if testCase.shouldError && err == nil {
753 t.Fatal("expected error but no error returned")
754 }
755 if err != nil && !testCase.shouldError {
756 t.Fatalf("unexpected error: %v", err)
757 }
758
759 assert.True(t, apiequality.Semantic.DeepEqual(testCase.expectedAddresses, existingNode.Status.Addresses),
760 "Diff: %s", cmp.Diff(testCase.expectedAddresses, existingNode.Status.Addresses))
761 })
762 }
763 }
764
765 func TestMachineInfo(t *testing.T) {
766 const nodeName = "test-node"
767
768 type dprc struct {
769 capacity v1.ResourceList
770 allocatable v1.ResourceList
771 inactive []string
772 }
773
774 cases := []struct {
775 desc string
776 node *v1.Node
777 maxPods int
778 podsPerCore int
779 machineInfo *cadvisorapiv1.MachineInfo
780 machineInfoError error
781 capacity v1.ResourceList
782 devicePluginResourceCapacity dprc
783 nodeAllocatableReservation v1.ResourceList
784 expectNode *v1.Node
785 expectEvents []testEvent
786 disableLocalStorageCapacityIsolation bool
787 }{
788 {
789 desc: "machine identifiers, basic capacity and allocatable",
790 node: &v1.Node{},
791 maxPods: 110,
792 machineInfo: &cadvisorapiv1.MachineInfo{
793 MachineID: "MachineID",
794 SystemUUID: "SystemUUID",
795 NumCores: 2,
796 MemoryCapacity: 1024,
797 },
798 expectNode: &v1.Node{
799 Status: v1.NodeStatus{
800 NodeInfo: v1.NodeSystemInfo{
801 MachineID: "MachineID",
802 SystemUUID: "SystemUUID",
803 },
804 Capacity: v1.ResourceList{
805 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
806 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
807 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
808 },
809 Allocatable: v1.ResourceList{
810 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
811 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
812 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
813 },
814 },
815 },
816 },
817 {
818 desc: "podsPerCore greater than zero, but less than maxPods/cores",
819 node: &v1.Node{},
820 maxPods: 10,
821 podsPerCore: 4,
822 machineInfo: &cadvisorapiv1.MachineInfo{
823 NumCores: 2,
824 MemoryCapacity: 1024,
825 },
826 expectNode: &v1.Node{
827 Status: v1.NodeStatus{
828 Capacity: v1.ResourceList{
829 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
830 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
831 v1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
832 },
833 Allocatable: v1.ResourceList{
834 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
835 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
836 v1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
837 },
838 },
839 },
840 },
841 {
842 desc: "podsPerCore greater than maxPods/cores",
843 node: &v1.Node{},
844 maxPods: 10,
845 podsPerCore: 6,
846 machineInfo: &cadvisorapiv1.MachineInfo{
847 NumCores: 2,
848 MemoryCapacity: 1024,
849 },
850 expectNode: &v1.Node{
851 Status: v1.NodeStatus{
852 Capacity: v1.ResourceList{
853 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
854 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
855 v1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
856 },
857 Allocatable: v1.ResourceList{
858 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
859 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
860 v1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
861 },
862 },
863 },
864 },
865 {
866 desc: "allocatable should equal capacity minus reservations",
867 node: &v1.Node{},
868 maxPods: 110,
869 machineInfo: &cadvisorapiv1.MachineInfo{
870 NumCores: 2,
871 MemoryCapacity: 1024,
872 },
873 nodeAllocatableReservation: v1.ResourceList{
874
875 v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
876 v1.ResourceMemory: *resource.NewQuantity(1, resource.BinarySI),
877 v1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
878 v1.ResourceEphemeralStorage: *resource.NewQuantity(1, resource.BinarySI),
879 },
880 expectNode: &v1.Node{
881 Status: v1.NodeStatus{
882 Capacity: v1.ResourceList{
883 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
884 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
885 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
886 },
887 Allocatable: v1.ResourceList{
888 v1.ResourceCPU: *resource.NewMilliQuantity(1999, resource.DecimalSI),
889 v1.ResourceMemory: *resource.NewQuantity(1023, resource.BinarySI),
890 v1.ResourcePods: *resource.NewQuantity(109, resource.DecimalSI),
891 },
892 },
893 },
894 },
895 {
896 desc: "allocatable memory does not double-count hugepages reservations",
897 node: &v1.Node{
898 Status: v1.NodeStatus{
899 Capacity: v1.ResourceList{
900
901
902 v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1, resource.BinarySI),
903 },
904 },
905 },
906 maxPods: 110,
907 machineInfo: &cadvisorapiv1.MachineInfo{
908 NumCores: 2,
909 MemoryCapacity: 1024,
910 },
911 expectNode: &v1.Node{
912 Status: v1.NodeStatus{
913 Capacity: v1.ResourceList{
914 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
915 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
916 v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1, resource.BinarySI),
917 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
918 },
919 Allocatable: v1.ResourceList{
920 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
921
922 v1.ResourceMemory: *resource.NewQuantity(1023, resource.BinarySI),
923 v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1, resource.BinarySI),
924 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
925 },
926 },
927 },
928 },
929 {
930 desc: "negative capacity resources should be set to 0 in allocatable",
931 node: &v1.Node{
932 Status: v1.NodeStatus{
933 Capacity: v1.ResourceList{
934 "negative-resource": *resource.NewQuantity(-1, resource.BinarySI),
935 },
936 },
937 },
938 maxPods: 110,
939 machineInfo: &cadvisorapiv1.MachineInfo{
940 NumCores: 2,
941 MemoryCapacity: 1024,
942 },
943 expectNode: &v1.Node{
944 Status: v1.NodeStatus{
945 Capacity: v1.ResourceList{
946 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
947 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
948 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
949 "negative-resource": *resource.NewQuantity(-1, resource.BinarySI),
950 },
951 Allocatable: v1.ResourceList{
952 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
953 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
954 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
955 "negative-resource": *resource.NewQuantity(0, resource.BinarySI),
956 },
957 },
958 },
959 },
960 {
961 desc: "hugepages reservation greater than node memory capacity should result in memory capacity set to 0",
962 node: &v1.Node{
963 Status: v1.NodeStatus{
964 Capacity: v1.ResourceList{
965 v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1025, resource.BinarySI),
966 },
967 },
968 },
969 maxPods: 110,
970 machineInfo: &cadvisorapiv1.MachineInfo{
971 NumCores: 2,
972 MemoryCapacity: 1024,
973 },
974 expectNode: &v1.Node{
975 Status: v1.NodeStatus{
976 Capacity: v1.ResourceList{
977 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
978 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
979 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
980 v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1025, resource.BinarySI),
981 },
982 Allocatable: v1.ResourceList{
983 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
984 v1.ResourceMemory: *resource.NewQuantity(0, resource.BinarySI),
985 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
986 v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1025, resource.BinarySI),
987 },
988 },
989 },
990 },
991 {
992 desc: "ephemeral storage is reflected in capacity and allocatable",
993 node: &v1.Node{},
994 maxPods: 110,
995 machineInfo: &cadvisorapiv1.MachineInfo{
996 NumCores: 2,
997 MemoryCapacity: 1024,
998 },
999 capacity: v1.ResourceList{
1000 v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
1001 },
1002 expectNode: &v1.Node{
1003 Status: v1.NodeStatus{
1004 Capacity: v1.ResourceList{
1005 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1006 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1007 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1008 v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
1009 },
1010 Allocatable: v1.ResourceList{
1011 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1012 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1013 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1014 v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
1015 },
1016 },
1017 },
1018 },
1019 {
1020 desc: "ephemeral storage is not reflected in capacity and allocatable because localStorageCapacityIsolation is disabled",
1021 node: &v1.Node{},
1022 maxPods: 110,
1023 machineInfo: &cadvisorapiv1.MachineInfo{
1024 NumCores: 2,
1025 MemoryCapacity: 1024,
1026 },
1027 capacity: v1.ResourceList{
1028 v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
1029 },
1030 expectNode: &v1.Node{
1031 Status: v1.NodeStatus{
1032 Capacity: v1.ResourceList{
1033 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1034 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1035 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1036 v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
1037 },
1038 Allocatable: v1.ResourceList{
1039 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1040 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1041 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1042 v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
1043 },
1044 },
1045 },
1046 disableLocalStorageCapacityIsolation: true,
1047 },
1048 {
1049 desc: "device plugin resources are reflected in capacity and allocatable",
1050 node: &v1.Node{},
1051 maxPods: 110,
1052 machineInfo: &cadvisorapiv1.MachineInfo{
1053 NumCores: 2,
1054 MemoryCapacity: 1024,
1055 },
1056 devicePluginResourceCapacity: dprc{
1057 capacity: v1.ResourceList{
1058 "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
1059 },
1060 allocatable: v1.ResourceList{
1061 "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
1062 },
1063 },
1064 expectNode: &v1.Node{
1065 Status: v1.NodeStatus{
1066 Capacity: v1.ResourceList{
1067 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1068 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1069 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1070 "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
1071 },
1072 Allocatable: v1.ResourceList{
1073 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1074 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1075 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1076 "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
1077 },
1078 },
1079 },
1080 },
1081 {
1082 desc: "inactive device plugin resources should have their capacity set to 0",
1083 node: &v1.Node{
1084 Status: v1.NodeStatus{
1085 Capacity: v1.ResourceList{
1086 "inactive": *resource.NewQuantity(1, resource.BinarySI),
1087 },
1088 },
1089 },
1090 maxPods: 110,
1091 machineInfo: &cadvisorapiv1.MachineInfo{
1092 NumCores: 2,
1093 MemoryCapacity: 1024,
1094 },
1095 devicePluginResourceCapacity: dprc{
1096 inactive: []string{"inactive"},
1097 },
1098 expectNode: &v1.Node{
1099 Status: v1.NodeStatus{
1100 Capacity: v1.ResourceList{
1101 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1102 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1103 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1104 "inactive": *resource.NewQuantity(0, resource.BinarySI),
1105 },
1106 Allocatable: v1.ResourceList{
1107 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1108 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1109 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1110 "inactive": *resource.NewQuantity(0, resource.BinarySI),
1111 },
1112 },
1113 },
1114 },
1115 {
1116 desc: "extended resources not present in capacity are removed from allocatable",
1117 node: &v1.Node{
1118 Status: v1.NodeStatus{
1119 Allocatable: v1.ResourceList{
1120 "example.com/extended": *resource.NewQuantity(1, resource.BinarySI),
1121 },
1122 },
1123 },
1124 maxPods: 110,
1125 machineInfo: &cadvisorapiv1.MachineInfo{
1126 NumCores: 2,
1127 MemoryCapacity: 1024,
1128 },
1129 expectNode: &v1.Node{
1130 Status: v1.NodeStatus{
1131 Capacity: v1.ResourceList{
1132 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1133 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1134 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1135 },
1136 Allocatable: v1.ResourceList{
1137 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1138 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1139 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1140 },
1141 },
1142 },
1143 },
1144 {
1145 desc: "on failure to get machine info, allocatable and capacity for memory and cpu are set to 0, pods to maxPods",
1146 node: &v1.Node{},
1147 maxPods: 110,
1148
1149 podsPerCore: 1,
1150 machineInfoError: fmt.Errorf("foo"),
1151 expectNode: &v1.Node{
1152 Status: v1.NodeStatus{
1153 Capacity: v1.ResourceList{
1154 v1.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI),
1155 v1.ResourceMemory: resource.MustParse("0Gi"),
1156 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1157 },
1158 Allocatable: v1.ResourceList{
1159 v1.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI),
1160 v1.ResourceMemory: resource.MustParse("0Gi"),
1161 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1162 },
1163 },
1164 },
1165 },
1166 {
1167 desc: "node reboot event is recorded",
1168 node: &v1.Node{
1169 Status: v1.NodeStatus{
1170 NodeInfo: v1.NodeSystemInfo{
1171 BootID: "foo",
1172 },
1173 },
1174 },
1175 maxPods: 110,
1176 machineInfo: &cadvisorapiv1.MachineInfo{
1177 BootID: "bar",
1178 NumCores: 2,
1179 MemoryCapacity: 1024,
1180 },
1181 expectNode: &v1.Node{
1182 Status: v1.NodeStatus{
1183 NodeInfo: v1.NodeSystemInfo{
1184 BootID: "bar",
1185 },
1186 Capacity: v1.ResourceList{
1187 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1188 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1189 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1190 },
1191 Allocatable: v1.ResourceList{
1192 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1193 v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
1194 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
1195 },
1196 },
1197 },
1198 expectEvents: []testEvent{
1199 {
1200 eventType: v1.EventTypeWarning,
1201 event: events.NodeRebooted,
1202 message: fmt.Sprintf("Node %s has been rebooted, boot id: %s", nodeName, "bar"),
1203 },
1204 },
1205 },
1206 }
1207
1208 for _, tc := range cases {
1209 t.Run(tc.desc, func(t *testing.T) {
1210 ctx := context.Background()
1211 machineInfoFunc := func() (*cadvisorapiv1.MachineInfo, error) {
1212 return tc.machineInfo, tc.machineInfoError
1213 }
1214 capacityFunc := func(localStorageCapacityIsolation bool) v1.ResourceList {
1215 return tc.capacity
1216 }
1217 devicePluginResourceCapacityFunc := func() (v1.ResourceList, v1.ResourceList, []string) {
1218 c := tc.devicePluginResourceCapacity
1219 return c.capacity, c.allocatable, c.inactive
1220 }
1221 nodeAllocatableReservationFunc := func() v1.ResourceList {
1222 return tc.nodeAllocatableReservation
1223 }
1224
1225 events := []testEvent{}
1226 recordEventFunc := func(eventType, event, message string) {
1227 events = append(events, testEvent{
1228 eventType: eventType,
1229 event: event,
1230 message: message,
1231 })
1232 }
1233
1234 setter := MachineInfo(nodeName, tc.maxPods, tc.podsPerCore, machineInfoFunc, capacityFunc,
1235 devicePluginResourceCapacityFunc, nodeAllocatableReservationFunc, recordEventFunc, tc.disableLocalStorageCapacityIsolation)
1236
1237 if err := setter(ctx, tc.node); err != nil {
1238 t.Fatalf("unexpected error: %v", err)
1239 }
1240
1241 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, tc.node),
1242 "Diff: %s", cmp.Diff(tc.expectNode, tc.node))
1243
1244 require.Equal(t, len(tc.expectEvents), len(events))
1245 for i := range tc.expectEvents {
1246 assert.Equal(t, tc.expectEvents[i], events[i])
1247 }
1248 })
1249 }
1250
1251 }
1252
1253 func TestVersionInfo(t *testing.T) {
1254 cases := []struct {
1255 desc string
1256 node *v1.Node
1257 versionInfo *cadvisorapiv1.VersionInfo
1258 versionInfoError error
1259 runtimeType string
1260 runtimeVersion kubecontainer.Version
1261 runtimeVersionError error
1262 expectNode *v1.Node
1263 expectError error
1264 kubeProxyVersion bool
1265 }{
1266 {
1267 desc: "versions set in node info",
1268 node: &v1.Node{},
1269 versionInfo: &cadvisorapiv1.VersionInfo{
1270 KernelVersion: "KernelVersion",
1271 ContainerOsVersion: "ContainerOSVersion",
1272 },
1273 runtimeType: "RuntimeType",
1274 runtimeVersion: &kubecontainertest.FakeVersion{
1275 Version: "RuntimeVersion",
1276 },
1277 expectNode: &v1.Node{
1278 Status: v1.NodeStatus{
1279 NodeInfo: v1.NodeSystemInfo{
1280 KernelVersion: "KernelVersion",
1281 OSImage: "ContainerOSVersion",
1282 ContainerRuntimeVersion: "RuntimeType://RuntimeVersion",
1283 KubeletVersion: version.Get().String(),
1284 KubeProxyVersion: version.Get().String(),
1285 },
1286 },
1287 },
1288 kubeProxyVersion: true,
1289 },
1290 {
1291 desc: "error getting version info",
1292 node: &v1.Node{},
1293 versionInfoError: fmt.Errorf("foo"),
1294 expectNode: &v1.Node{},
1295 expectError: fmt.Errorf("error getting version info: foo"),
1296 kubeProxyVersion: true,
1297 },
1298 {
1299 desc: "error getting runtime version results in Unknown runtime",
1300 node: &v1.Node{},
1301 versionInfo: &cadvisorapiv1.VersionInfo{},
1302 runtimeType: "RuntimeType",
1303 runtimeVersionError: fmt.Errorf("foo"),
1304 expectNode: &v1.Node{
1305 Status: v1.NodeStatus{
1306 NodeInfo: v1.NodeSystemInfo{
1307 ContainerRuntimeVersion: "RuntimeType://Unknown",
1308 KubeletVersion: version.Get().String(),
1309 KubeProxyVersion: version.Get().String(),
1310 },
1311 },
1312 },
1313 kubeProxyVersion: true,
1314 },
1315 {
1316 desc: "DisableNodeKubeProxyVersion FeatureGate enable, versions set in node info",
1317 node: &v1.Node{},
1318 versionInfo: &cadvisorapiv1.VersionInfo{
1319 KernelVersion: "KernelVersion",
1320 ContainerOsVersion: "ContainerOSVersion",
1321 },
1322 runtimeType: "RuntimeType",
1323 runtimeVersion: &kubecontainertest.FakeVersion{
1324 Version: "RuntimeVersion",
1325 },
1326 expectNode: &v1.Node{
1327 Status: v1.NodeStatus{
1328 NodeInfo: v1.NodeSystemInfo{
1329 KernelVersion: "KernelVersion",
1330 OSImage: "ContainerOSVersion",
1331 ContainerRuntimeVersion: "RuntimeType://RuntimeVersion",
1332 KubeletVersion: version.Get().String(),
1333 },
1334 },
1335 },
1336 kubeProxyVersion: false,
1337 },
1338 {
1339 desc: "DisableNodeKubeProxyVersion FeatureGate enable, KubeProxyVersion will be cleared if it is set.",
1340 node: &v1.Node{
1341 Status: v1.NodeStatus{
1342 NodeInfo: v1.NodeSystemInfo{
1343 KernelVersion: "KernelVersion",
1344 OSImage: "ContainerOSVersion",
1345 ContainerRuntimeVersion: "RuntimeType://RuntimeVersion",
1346 KubeletVersion: version.Get().String(),
1347 KubeProxyVersion: version.Get().String(),
1348 },
1349 },
1350 },
1351 versionInfo: &cadvisorapiv1.VersionInfo{
1352 KernelVersion: "KernelVersion",
1353 ContainerOsVersion: "ContainerOSVersion",
1354 },
1355 runtimeType: "RuntimeType",
1356 runtimeVersion: &kubecontainertest.FakeVersion{
1357 Version: "RuntimeVersion",
1358 },
1359 expectNode: &v1.Node{
1360 Status: v1.NodeStatus{
1361 NodeInfo: v1.NodeSystemInfo{
1362 KernelVersion: "KernelVersion",
1363 OSImage: "ContainerOSVersion",
1364 ContainerRuntimeVersion: "RuntimeType://RuntimeVersion",
1365 KubeletVersion: version.Get().String(),
1366 },
1367 },
1368 },
1369 kubeProxyVersion: false,
1370 },
1371 }
1372
1373 for _, tc := range cases {
1374 t.Run(tc.desc, func(t *testing.T) {
1375 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DisableNodeKubeProxyVersion, !tc.kubeProxyVersion)()
1376
1377 ctx := context.Background()
1378 versionInfoFunc := func() (*cadvisorapiv1.VersionInfo, error) {
1379 return tc.versionInfo, tc.versionInfoError
1380 }
1381 runtimeTypeFunc := func() string {
1382 return tc.runtimeType
1383 }
1384 runtimeVersionFunc := func(_ context.Context) (kubecontainer.Version, error) {
1385 return tc.runtimeVersion, tc.runtimeVersionError
1386 }
1387
1388 setter := VersionInfo(versionInfoFunc, runtimeTypeFunc, runtimeVersionFunc)
1389
1390 err := setter(ctx, tc.node)
1391 require.Equal(t, tc.expectError, err)
1392
1393 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, tc.node),
1394 "Diff: %s", cmp.Diff(tc.expectNode, tc.node))
1395 })
1396 }
1397 }
1398
1399 func TestImages(t *testing.T) {
1400 const (
1401 minImageSize = 23 * 1024 * 1024
1402 maxImageSize = 1000 * 1024 * 1024
1403 )
1404
1405 cases := []struct {
1406 desc string
1407 maxImages int32
1408 imageList []kubecontainer.Image
1409 imageListError error
1410 expectError error
1411 }{
1412 {
1413 desc: "max images enforced",
1414 maxImages: 1,
1415 imageList: makeImageList(2, 1, minImageSize, maxImageSize),
1416 },
1417 {
1418 desc: "no max images cap for -1",
1419 maxImages: -1,
1420 imageList: makeImageList(2, 1, minImageSize, maxImageSize),
1421 },
1422 {
1423 desc: "max names per image enforced",
1424 maxImages: -1,
1425 imageList: makeImageList(1, MaxNamesPerImageInNodeStatus+1, minImageSize, maxImageSize),
1426 },
1427 {
1428 desc: "images are sorted by size, descending",
1429 maxImages: -1,
1430
1431 imageList: []kubecontainer.Image{{Size: 3}, {Size: 1}, {Size: 4}, {Size: 2}},
1432 },
1433 {
1434 desc: "repo digests and tags both show up in image names",
1435 maxImages: -1,
1436
1437 imageList: []kubecontainer.Image{
1438 {
1439 RepoDigests: []string{"foo", "bar"},
1440 RepoTags: []string{"baz", "quux"},
1441 },
1442 },
1443 },
1444 {
1445 desc: "error getting image list, image list on node is reset to empty",
1446 maxImages: -1,
1447 imageListError: fmt.Errorf("foo"),
1448 expectError: fmt.Errorf("error getting image list: foo"),
1449 },
1450 }
1451
1452 for _, tc := range cases {
1453 t.Run(tc.desc, func(t *testing.T) {
1454 ctx := context.Background()
1455 imageListFunc := func() ([]kubecontainer.Image, error) {
1456
1457
1458
1459 sort.Sort(sliceutils.ByImageSize(tc.imageList))
1460 return tc.imageList, tc.imageListError
1461 }
1462
1463 setter := Images(tc.maxImages, imageListFunc)
1464
1465 node := &v1.Node{}
1466 err := setter(ctx, node)
1467 require.Equal(t, tc.expectError, err)
1468
1469 expectNode := &v1.Node{}
1470 if err == nil {
1471 expectNode.Status.Images = makeExpectedImageList(tc.imageList, tc.maxImages, MaxNamesPerImageInNodeStatus)
1472 }
1473 assert.True(t, apiequality.Semantic.DeepEqual(expectNode, node),
1474 "Diff: %s", cmp.Diff(expectNode, node))
1475 })
1476 }
1477
1478 }
1479
1480 func TestReadyCondition(t *testing.T) {
1481 now := time.Now()
1482 before := now.Add(-time.Second)
1483 nowFunc := func() time.Time { return now }
1484
1485 withCapacity := &v1.Node{
1486 Status: v1.NodeStatus{
1487 Capacity: v1.ResourceList{
1488 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1489 v1.ResourceMemory: *resource.NewQuantity(10e9, resource.BinarySI),
1490 v1.ResourcePods: *resource.NewQuantity(100, resource.DecimalSI),
1491 v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
1492 },
1493 },
1494 }
1495
1496 withoutStorageCapacity := &v1.Node{
1497 Status: v1.NodeStatus{
1498 Capacity: v1.ResourceList{
1499 v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
1500 v1.ResourceMemory: *resource.NewQuantity(10e9, resource.BinarySI),
1501 v1.ResourcePods: *resource.NewQuantity(100, resource.DecimalSI),
1502 },
1503 },
1504 }
1505
1506 cases := []struct {
1507 desc string
1508 node *v1.Node
1509 runtimeErrors error
1510 networkErrors error
1511 storageErrors error
1512 cmStatus cm.Status
1513 nodeShutdownManagerErrors error
1514 expectConditions []v1.NodeCondition
1515 expectEvents []testEvent
1516 disableLocalStorageCapacityIsolation bool
1517 }{
1518 {
1519 desc: "new, ready",
1520 node: withCapacity.DeepCopy(),
1521 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)},
1522
1523
1524
1525 },
1526 {
1527 desc: "new, ready: soft requirement warning",
1528 node: withCapacity.DeepCopy(),
1529 cmStatus: cm.Status{
1530 SoftRequirements: fmt.Errorf("foo"),
1531 },
1532 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status. WARNING: foo", now, now)},
1533 },
1534 {
1535 desc: "new, not ready: storage errors",
1536 node: withCapacity.DeepCopy(),
1537 storageErrors: errors.New("some storage error"),
1538 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "some storage error", now, now)},
1539 },
1540 {
1541 desc: "new, not ready: shutdown active",
1542 node: withCapacity.DeepCopy(),
1543 nodeShutdownManagerErrors: errors.New("node is shutting down"),
1544 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "node is shutting down", now, now)},
1545 },
1546 {
1547 desc: "new, not ready: runtime and network errors",
1548 node: withCapacity.DeepCopy(),
1549 runtimeErrors: errors.New("runtime"),
1550 networkErrors: errors.New("network"),
1551 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "[runtime, network]", now, now)},
1552 },
1553 {
1554 desc: "new, not ready: missing capacities",
1555 node: &v1.Node{},
1556 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "missing node capacity for resources: cpu, memory, pods, ephemeral-storage", now, now)},
1557 },
1558 {
1559 desc: "new, ready: localStorageCapacityIsolation is not supported",
1560 node: withoutStorageCapacity.DeepCopy(),
1561 disableLocalStorageCapacityIsolation: true,
1562 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)},
1563 },
1564
1565 {
1566 desc: "transition to ready",
1567 node: func() *v1.Node {
1568 node := withCapacity.DeepCopy()
1569 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(false, "", before, before)}
1570 return node
1571 }(),
1572 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)},
1573 expectEvents: []testEvent{
1574 {
1575 eventType: v1.EventTypeNormal,
1576 event: events.NodeReady,
1577 },
1578 },
1579 },
1580 {
1581 desc: "transition to not ready",
1582 node: func() *v1.Node {
1583 node := withCapacity.DeepCopy()
1584 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(true, "", before, before)}
1585 return node
1586 }(),
1587 runtimeErrors: errors.New("foo"),
1588 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "foo", now, now)},
1589 expectEvents: []testEvent{
1590 {
1591 eventType: v1.EventTypeNormal,
1592 event: events.NodeNotReady,
1593 },
1594 },
1595 },
1596 {
1597 desc: "ready, no transition",
1598 node: func() *v1.Node {
1599 node := withCapacity.DeepCopy()
1600 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(true, "", before, before)}
1601 return node
1602 }(),
1603 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", before, now)},
1604 expectEvents: []testEvent{},
1605 },
1606 {
1607 desc: "not ready, no transition",
1608 node: func() *v1.Node {
1609 node := withCapacity.DeepCopy()
1610 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(false, "", before, before)}
1611 return node
1612 }(),
1613 runtimeErrors: errors.New("foo"),
1614 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "foo", before, now)},
1615 expectEvents: []testEvent{},
1616 },
1617 }
1618 for _, tc := range cases {
1619 t.Run(tc.desc, func(t *testing.T) {
1620 ctx := context.Background()
1621 runtimeErrorsFunc := func() error {
1622 return tc.runtimeErrors
1623 }
1624 networkErrorsFunc := func() error {
1625 return tc.networkErrors
1626 }
1627 storageErrorsFunc := func() error {
1628 return tc.storageErrors
1629 }
1630 cmStatusFunc := func() cm.Status {
1631 return tc.cmStatus
1632 }
1633 nodeShutdownErrorsFunc := func() error {
1634 return tc.nodeShutdownManagerErrors
1635 }
1636 events := []testEvent{}
1637 recordEventFunc := func(eventType, event string) {
1638 events = append(events, testEvent{
1639 eventType: eventType,
1640 event: event,
1641 })
1642 }
1643
1644 setter := ReadyCondition(nowFunc, runtimeErrorsFunc, networkErrorsFunc, storageErrorsFunc, cmStatusFunc, nodeShutdownErrorsFunc, recordEventFunc, !tc.disableLocalStorageCapacityIsolation)
1645
1646 if err := setter(ctx, tc.node); err != nil {
1647 t.Fatalf("unexpected error: %v", err)
1648 }
1649
1650 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
1651 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions))
1652
1653 require.Equal(t, len(tc.expectEvents), len(events))
1654 for i := range tc.expectEvents {
1655 assert.Equal(t, tc.expectEvents[i], events[i])
1656 }
1657 })
1658 }
1659 }
1660
1661 func TestMemoryPressureCondition(t *testing.T) {
1662 now := time.Now()
1663 before := now.Add(-time.Second)
1664 nowFunc := func() time.Time { return now }
1665
1666 cases := []struct {
1667 desc string
1668 node *v1.Node
1669 pressure bool
1670 expectConditions []v1.NodeCondition
1671 expectEvents []testEvent
1672 }{
1673 {
1674 desc: "new, no pressure",
1675 node: &v1.Node{},
1676 pressure: false,
1677 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, now, now)},
1678 expectEvents: []testEvent{
1679 {
1680 eventType: v1.EventTypeNormal,
1681 event: "NodeHasSufficientMemory",
1682 },
1683 },
1684 },
1685 {
1686 desc: "new, pressure",
1687 node: &v1.Node{},
1688 pressure: true,
1689 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, now, now)},
1690 expectEvents: []testEvent{
1691 {
1692 eventType: v1.EventTypeNormal,
1693 event: "NodeHasInsufficientMemory",
1694 },
1695 },
1696 },
1697 {
1698 desc: "transition to pressure",
1699 node: &v1.Node{
1700 Status: v1.NodeStatus{
1701 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, before)},
1702 },
1703 },
1704 pressure: true,
1705 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, now, now)},
1706 expectEvents: []testEvent{
1707 {
1708 eventType: v1.EventTypeNormal,
1709 event: "NodeHasInsufficientMemory",
1710 },
1711 },
1712 },
1713 {
1714 desc: "transition to no pressure",
1715 node: &v1.Node{
1716 Status: v1.NodeStatus{
1717 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, before)},
1718 },
1719 },
1720 pressure: false,
1721 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, now, now)},
1722 expectEvents: []testEvent{
1723 {
1724 eventType: v1.EventTypeNormal,
1725 event: "NodeHasSufficientMemory",
1726 },
1727 },
1728 },
1729 {
1730 desc: "pressure, no transition",
1731 node: &v1.Node{
1732 Status: v1.NodeStatus{
1733 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, before)},
1734 },
1735 },
1736 pressure: true,
1737 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, now)},
1738 expectEvents: []testEvent{},
1739 },
1740 {
1741 desc: "no pressure, no transition",
1742 node: &v1.Node{
1743 Status: v1.NodeStatus{
1744 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, before)},
1745 },
1746 },
1747 pressure: false,
1748 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, now)},
1749 expectEvents: []testEvent{},
1750 },
1751 }
1752 for _, tc := range cases {
1753 t.Run(tc.desc, func(t *testing.T) {
1754 ctx := context.Background()
1755 events := []testEvent{}
1756 recordEventFunc := func(eventType, event string) {
1757 events = append(events, testEvent{
1758 eventType: eventType,
1759 event: event,
1760 })
1761 }
1762 pressureFunc := func() bool {
1763 return tc.pressure
1764 }
1765
1766 setter := MemoryPressureCondition(nowFunc, pressureFunc, recordEventFunc)
1767
1768 if err := setter(ctx, tc.node); err != nil {
1769 t.Fatalf("unexpected error: %v", err)
1770 }
1771
1772 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
1773 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions))
1774
1775 require.Equal(t, len(tc.expectEvents), len(events))
1776 for i := range tc.expectEvents {
1777 assert.Equal(t, tc.expectEvents[i], events[i])
1778 }
1779 })
1780 }
1781 }
1782
1783 func TestPIDPressureCondition(t *testing.T) {
1784 now := time.Now()
1785 before := now.Add(-time.Second)
1786 nowFunc := func() time.Time { return now }
1787
1788 cases := []struct {
1789 desc string
1790 node *v1.Node
1791 pressure bool
1792 expectConditions []v1.NodeCondition
1793 expectEvents []testEvent
1794 }{
1795 {
1796 desc: "new, no pressure",
1797 node: &v1.Node{},
1798 pressure: false,
1799 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)},
1800 expectEvents: []testEvent{
1801 {
1802 eventType: v1.EventTypeNormal,
1803 event: "NodeHasSufficientPID",
1804 },
1805 },
1806 },
1807 {
1808 desc: "new, pressure",
1809 node: &v1.Node{},
1810 pressure: true,
1811 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)},
1812 expectEvents: []testEvent{
1813 {
1814 eventType: v1.EventTypeNormal,
1815 event: "NodeHasInsufficientPID",
1816 },
1817 },
1818 },
1819 {
1820 desc: "transition to pressure",
1821 node: &v1.Node{
1822 Status: v1.NodeStatus{
1823 Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)},
1824 },
1825 },
1826 pressure: true,
1827 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)},
1828 expectEvents: []testEvent{
1829 {
1830 eventType: v1.EventTypeNormal,
1831 event: "NodeHasInsufficientPID",
1832 },
1833 },
1834 },
1835 {
1836 desc: "transition to no pressure",
1837 node: &v1.Node{
1838 Status: v1.NodeStatus{
1839 Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)},
1840 },
1841 },
1842 pressure: false,
1843 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)},
1844 expectEvents: []testEvent{
1845 {
1846 eventType: v1.EventTypeNormal,
1847 event: "NodeHasSufficientPID",
1848 },
1849 },
1850 },
1851 {
1852 desc: "pressure, no transition",
1853 node: &v1.Node{
1854 Status: v1.NodeStatus{
1855 Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)},
1856 },
1857 },
1858 pressure: true,
1859 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, now)},
1860 expectEvents: []testEvent{},
1861 },
1862 {
1863 desc: "no pressure, no transition",
1864 node: &v1.Node{
1865 Status: v1.NodeStatus{
1866 Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)},
1867 },
1868 },
1869 pressure: false,
1870 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, now)},
1871 expectEvents: []testEvent{},
1872 },
1873 }
1874 for _, tc := range cases {
1875 t.Run(tc.desc, func(t *testing.T) {
1876 ctx := context.Background()
1877 events := []testEvent{}
1878 recordEventFunc := func(eventType, event string) {
1879 events = append(events, testEvent{
1880 eventType: eventType,
1881 event: event,
1882 })
1883 }
1884 pressureFunc := func() bool {
1885 return tc.pressure
1886 }
1887
1888 setter := PIDPressureCondition(nowFunc, pressureFunc, recordEventFunc)
1889
1890 if err := setter(ctx, tc.node); err != nil {
1891 t.Fatalf("unexpected error: %v", err)
1892 }
1893
1894 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
1895 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions))
1896
1897 require.Equal(t, len(tc.expectEvents), len(events))
1898 for i := range tc.expectEvents {
1899 assert.Equal(t, tc.expectEvents[i], events[i])
1900 }
1901 })
1902 }
1903 }
1904
1905 func TestDiskPressureCondition(t *testing.T) {
1906 now := time.Now()
1907 before := now.Add(-time.Second)
1908 nowFunc := func() time.Time { return now }
1909
1910 cases := []struct {
1911 desc string
1912 node *v1.Node
1913 pressure bool
1914 expectConditions []v1.NodeCondition
1915 expectEvents []testEvent
1916 }{
1917 {
1918 desc: "new, no pressure",
1919 node: &v1.Node{},
1920 pressure: false,
1921 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, now, now)},
1922 expectEvents: []testEvent{
1923 {
1924 eventType: v1.EventTypeNormal,
1925 event: "NodeHasNoDiskPressure",
1926 },
1927 },
1928 },
1929 {
1930 desc: "new, pressure",
1931 node: &v1.Node{},
1932 pressure: true,
1933 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, now, now)},
1934 expectEvents: []testEvent{
1935 {
1936 eventType: v1.EventTypeNormal,
1937 event: "NodeHasDiskPressure",
1938 },
1939 },
1940 },
1941 {
1942 desc: "transition to pressure",
1943 node: &v1.Node{
1944 Status: v1.NodeStatus{
1945 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, before)},
1946 },
1947 },
1948 pressure: true,
1949 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, now, now)},
1950 expectEvents: []testEvent{
1951 {
1952 eventType: v1.EventTypeNormal,
1953 event: "NodeHasDiskPressure",
1954 },
1955 },
1956 },
1957 {
1958 desc: "transition to no pressure",
1959 node: &v1.Node{
1960 Status: v1.NodeStatus{
1961 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, before)},
1962 },
1963 },
1964 pressure: false,
1965 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, now, now)},
1966 expectEvents: []testEvent{
1967 {
1968 eventType: v1.EventTypeNormal,
1969 event: "NodeHasNoDiskPressure",
1970 },
1971 },
1972 },
1973 {
1974 desc: "pressure, no transition",
1975 node: &v1.Node{
1976 Status: v1.NodeStatus{
1977 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, before)},
1978 },
1979 },
1980 pressure: true,
1981 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, now)},
1982 expectEvents: []testEvent{},
1983 },
1984 {
1985 desc: "no pressure, no transition",
1986 node: &v1.Node{
1987 Status: v1.NodeStatus{
1988 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, before)},
1989 },
1990 },
1991 pressure: false,
1992 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, now)},
1993 expectEvents: []testEvent{},
1994 },
1995 }
1996 for _, tc := range cases {
1997 t.Run(tc.desc, func(t *testing.T) {
1998 ctx := context.Background()
1999 events := []testEvent{}
2000 recordEventFunc := func(eventType, event string) {
2001 events = append(events, testEvent{
2002 eventType: eventType,
2003 event: event,
2004 })
2005 }
2006 pressureFunc := func() bool {
2007 return tc.pressure
2008 }
2009
2010 setter := DiskPressureCondition(nowFunc, pressureFunc, recordEventFunc)
2011
2012 if err := setter(ctx, tc.node); err != nil {
2013 t.Fatalf("unexpected error: %v", err)
2014 }
2015
2016 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
2017 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions))
2018
2019 require.Equal(t, len(tc.expectEvents), len(events))
2020 for i := range tc.expectEvents {
2021 assert.Equal(t, tc.expectEvents[i], events[i])
2022 }
2023 })
2024 }
2025 }
2026
2027 func TestVolumesInUse(t *testing.T) {
2028 withVolumesInUse := &v1.Node{
2029 Status: v1.NodeStatus{
2030 VolumesInUse: []v1.UniqueVolumeName{"foo"},
2031 },
2032 }
2033
2034 cases := []struct {
2035 desc string
2036 node *v1.Node
2037 synced bool
2038 volumesInUse []v1.UniqueVolumeName
2039 expectVolumesInUse []v1.UniqueVolumeName
2040 }{
2041 {
2042 desc: "synced",
2043 node: withVolumesInUse.DeepCopy(),
2044 synced: true,
2045 volumesInUse: []v1.UniqueVolumeName{"bar"},
2046 expectVolumesInUse: []v1.UniqueVolumeName{"bar"},
2047 },
2048 {
2049 desc: "not synced",
2050 node: withVolumesInUse.DeepCopy(),
2051 synced: false,
2052 volumesInUse: []v1.UniqueVolumeName{"bar"},
2053 expectVolumesInUse: []v1.UniqueVolumeName{"foo"},
2054 },
2055 }
2056
2057 for _, tc := range cases {
2058 t.Run(tc.desc, func(t *testing.T) {
2059 ctx := context.Background()
2060 syncedFunc := func() bool {
2061 return tc.synced
2062 }
2063 volumesInUseFunc := func() []v1.UniqueVolumeName {
2064 return tc.volumesInUse
2065 }
2066
2067 setter := VolumesInUse(syncedFunc, volumesInUseFunc)
2068
2069 if err := setter(ctx, tc.node); err != nil {
2070 t.Fatalf("unexpected error: %v", err)
2071 }
2072
2073 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectVolumesInUse, tc.node.Status.VolumesInUse),
2074 "Diff: %s", cmp.Diff(tc.expectVolumesInUse, tc.node.Status.VolumesInUse))
2075 })
2076 }
2077 }
2078
2079 func TestVolumeLimits(t *testing.T) {
2080 const (
2081 volumeLimitKey = "attachable-volumes-fake-provider"
2082 volumeLimitVal = 16
2083 )
2084
2085 var cases = []struct {
2086 desc string
2087 volumePluginList []volume.VolumePluginWithAttachLimits
2088 expectNode *v1.Node
2089 }{
2090 {
2091 desc: "translate limits to capacity and allocatable for plugins that return successfully from GetVolumeLimits",
2092 volumePluginList: []volume.VolumePluginWithAttachLimits{
2093 &volumetest.FakeVolumePlugin{
2094 VolumeLimits: map[string]int64{volumeLimitKey: volumeLimitVal},
2095 },
2096 },
2097 expectNode: &v1.Node{
2098 Status: v1.NodeStatus{
2099 Capacity: v1.ResourceList{
2100 volumeLimitKey: *resource.NewQuantity(volumeLimitVal, resource.DecimalSI),
2101 },
2102 Allocatable: v1.ResourceList{
2103 volumeLimitKey: *resource.NewQuantity(volumeLimitVal, resource.DecimalSI),
2104 },
2105 },
2106 },
2107 },
2108 {
2109 desc: "skip plugins that return errors from GetVolumeLimits",
2110 volumePluginList: []volume.VolumePluginWithAttachLimits{
2111 &volumetest.FakeVolumePlugin{
2112 VolumeLimitsError: fmt.Errorf("foo"),
2113 },
2114 },
2115 expectNode: &v1.Node{},
2116 },
2117 {
2118 desc: "no plugins",
2119 expectNode: &v1.Node{},
2120 },
2121 }
2122
2123 for _, tc := range cases {
2124 t.Run(tc.desc, func(t *testing.T) {
2125 ctx := context.Background()
2126 volumePluginListFunc := func() []volume.VolumePluginWithAttachLimits {
2127 return tc.volumePluginList
2128 }
2129
2130 setter := VolumeLimits(volumePluginListFunc)
2131
2132 node := &v1.Node{}
2133 if err := setter(ctx, node); err != nil {
2134 t.Fatalf("unexpected error: %v", err)
2135 }
2136
2137 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, node),
2138 "Diff: %s", cmp.Diff(tc.expectNode, node))
2139 })
2140 }
2141 }
2142
2143 func TestDaemonEndpoints(t *testing.T) {
2144 for _, test := range []struct {
2145 name string
2146 endpoints *v1.NodeDaemonEndpoints
2147 expected *v1.NodeDaemonEndpoints
2148 }{
2149 {
2150 name: "empty daemon endpoints",
2151 endpoints: &v1.NodeDaemonEndpoints{},
2152 expected: &v1.NodeDaemonEndpoints{KubeletEndpoint: v1.DaemonEndpoint{Port: 0}},
2153 },
2154 {
2155 name: "daemon endpoints with specific port",
2156 endpoints: &v1.NodeDaemonEndpoints{KubeletEndpoint: v1.DaemonEndpoint{Port: 5678}},
2157 expected: &v1.NodeDaemonEndpoints{KubeletEndpoint: v1.DaemonEndpoint{Port: 5678}},
2158 },
2159 } {
2160 t.Run(test.name, func(t *testing.T) {
2161 ctx := context.Background()
2162 existingNode := &v1.Node{
2163 ObjectMeta: metav1.ObjectMeta{
2164 Name: testKubeletHostname,
2165 },
2166 Spec: v1.NodeSpec{},
2167 Status: v1.NodeStatus{
2168 Addresses: []v1.NodeAddress{},
2169 },
2170 }
2171
2172 setter := DaemonEndpoints(test.endpoints)
2173 if err := setter(ctx, existingNode); err != nil {
2174 t.Fatal(err)
2175 }
2176
2177 assert.Equal(t, *test.expected, existingNode.Status.DaemonEndpoints)
2178 })
2179 }
2180 }
2181
2182
2183
2184
2185 type testEvent struct {
2186 eventType string
2187 event string
2188 message string
2189 }
2190
2191
2192 func makeImageList(numImages, numTags, minSize, maxSize int32) []kubecontainer.Image {
2193 images := make([]kubecontainer.Image, numImages)
2194 for i := range images {
2195 image := &images[i]
2196 image.ID = string(uuid.NewUUID())
2197 image.RepoTags = makeImageTags(numTags)
2198 image.Size = rand.Int63nRange(int64(minSize), int64(maxSize+1))
2199 }
2200 return images
2201 }
2202
2203 func makeExpectedImageList(imageList []kubecontainer.Image, maxImages, maxNames int32) []v1.ContainerImage {
2204
2205 images := make([]kubecontainer.Image, len(imageList))
2206 copy(images, imageList)
2207
2208 sort.Sort(sliceutils.ByImageSize(images))
2209
2210 expectedImages := make([]v1.ContainerImage, len(images))
2211 for i := range images {
2212 image := &images[i]
2213 expectedImage := &expectedImages[i]
2214 names := append(image.RepoDigests, image.RepoTags...)
2215 if len(names) > int(maxNames) {
2216 names = names[0:maxNames]
2217 }
2218 expectedImage.Names = names
2219 expectedImage.SizeBytes = image.Size
2220 }
2221
2222 if maxImages > -1 &&
2223 int(maxImages) < len(expectedImages) {
2224 return expectedImages[0:maxImages]
2225 }
2226 return expectedImages
2227 }
2228
2229 func makeImageTags(num int32) []string {
2230 tags := make([]string, num)
2231 for i := range tags {
2232 tags[i] = "registry.k8s.io:v" + strconv.Itoa(i)
2233 }
2234 return tags
2235 }
2236
2237 func makeReadyCondition(ready bool, message string, transition, heartbeat time.Time) *v1.NodeCondition {
2238 if ready {
2239 return &v1.NodeCondition{
2240 Type: v1.NodeReady,
2241 Status: v1.ConditionTrue,
2242 Reason: "KubeletReady",
2243 Message: message,
2244 LastTransitionTime: metav1.NewTime(transition),
2245 LastHeartbeatTime: metav1.NewTime(heartbeat),
2246 }
2247 }
2248 return &v1.NodeCondition{
2249 Type: v1.NodeReady,
2250 Status: v1.ConditionFalse,
2251 Reason: "KubeletNotReady",
2252 Message: message,
2253 LastTransitionTime: metav1.NewTime(transition),
2254 LastHeartbeatTime: metav1.NewTime(heartbeat),
2255 }
2256 }
2257
2258 func makeMemoryPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
2259 if pressure {
2260 return &v1.NodeCondition{
2261 Type: v1.NodeMemoryPressure,
2262 Status: v1.ConditionTrue,
2263 Reason: "KubeletHasInsufficientMemory",
2264 Message: "kubelet has insufficient memory available",
2265 LastTransitionTime: metav1.NewTime(transition),
2266 LastHeartbeatTime: metav1.NewTime(heartbeat),
2267 }
2268 }
2269 return &v1.NodeCondition{
2270 Type: v1.NodeMemoryPressure,
2271 Status: v1.ConditionFalse,
2272 Reason: "KubeletHasSufficientMemory",
2273 Message: "kubelet has sufficient memory available",
2274 LastTransitionTime: metav1.NewTime(transition),
2275 LastHeartbeatTime: metav1.NewTime(heartbeat),
2276 }
2277 }
2278
2279 func makePIDPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
2280 if pressure {
2281 return &v1.NodeCondition{
2282 Type: v1.NodePIDPressure,
2283 Status: v1.ConditionTrue,
2284 Reason: "KubeletHasInsufficientPID",
2285 Message: "kubelet has insufficient PID available",
2286 LastTransitionTime: metav1.NewTime(transition),
2287 LastHeartbeatTime: metav1.NewTime(heartbeat),
2288 }
2289 }
2290 return &v1.NodeCondition{
2291 Type: v1.NodePIDPressure,
2292 Status: v1.ConditionFalse,
2293 Reason: "KubeletHasSufficientPID",
2294 Message: "kubelet has sufficient PID available",
2295 LastTransitionTime: metav1.NewTime(transition),
2296 LastHeartbeatTime: metav1.NewTime(heartbeat),
2297 }
2298 }
2299
2300 func makeDiskPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
2301 if pressure {
2302 return &v1.NodeCondition{
2303 Type: v1.NodeDiskPressure,
2304 Status: v1.ConditionTrue,
2305 Reason: "KubeletHasDiskPressure",
2306 Message: "kubelet has disk pressure",
2307 LastTransitionTime: metav1.NewTime(transition),
2308 LastHeartbeatTime: metav1.NewTime(heartbeat),
2309 }
2310 }
2311 return &v1.NodeCondition{
2312 Type: v1.NodeDiskPressure,
2313 Status: v1.ConditionFalse,
2314 Reason: "KubeletHasNoDiskPressure",
2315 Message: "kubelet has no disk pressure",
2316 LastTransitionTime: metav1.NewTime(transition),
2317 LastHeartbeatTime: metav1.NewTime(heartbeat),
2318 }
2319 }
2320
View as plain text