k8s.io/kubernetes@v1.29.3/pkg/kubelet/nodestatus/setters_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package 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 // TODO(mtaufen): below is ported from the old kubelet_node_status_test.go code, potentially add more test coverage for NodeAddress setter in future 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 // Accommodating #45201 and #49202 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}, // detected hostname is auto-added in the absence of cloud-reported hostnames 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"}, // cloud-reported hostname wins over detected hostname 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"}, // cloud-reported hostname wins over detected hostname 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}, // hostname-override wins over cloud-reported hostname 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 // detected hostname is not auto-added if it doesn't match any cloud-reported addresses 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}, // cloud-reported address value matches detected hostname 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}, // detected hostname gets auto-added 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}, // cloud-reported address value matches detected hostname 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}, // detected hostname gets auto-added 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}, // cloud-reported address value matches detected hostname 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}, // detected hostname gets auto-added 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}, // overridden hostname gets auto-added 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 // We don't have to test "legacy cloud provider with dual-stack 544 // IPs" etc because we won't have gotten this far with an invalid 545 // config like that. 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 // testCase setup 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 // cloud provider is expected to be nil if external provider is set or there is no cloud provider 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 // construct setter 654 setter := NodeAddress(nodeIPs, 655 nodeIPValidator, 656 hostname, 657 testCase.hostnameOverride, 658 testCase.cloudProviderType == cloudProviderExternal, 659 cloud, 660 nodeAddressesFunc) 661 662 // call setter on existing node 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 // expected an error, and got one, so just return early here 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 // We can't test failure or autodetection cases here because the relevant code isn't mockable 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 // testCase setup 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 // construct setter 742 setter := NodeAddress(testCase.nodeIPs, 743 nodeIPValidator, 744 testKubeletHostname, 745 false, // hostnameOverridden 746 false, // externalCloudProvider 747 nil, // cloud 748 nodeAddressesFunc) 749 750 // call setter on existing node 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 // reserve 1 unit for each resource 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 // it's impossible on any real system to reserve 1 byte, 901 // but we just need to test that the setter does the math 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 // memory has 1-unit difference for hugepages reservation 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 // podsPerCore is not accounted for when getting machine info fails 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 // construct setter 1234 setter := MachineInfo(nodeName, tc.maxPods, tc.podsPerCore, machineInfoFunc, capacityFunc, 1235 devicePluginResourceCapacityFunc, nodeAllocatableReservationFunc, recordEventFunc, tc.disableLocalStorageCapacityIsolation) 1236 // call setter on node 1237 if err := setter(ctx, tc.node); err != nil { 1238 t.Fatalf("unexpected error: %v", err) 1239 } 1240 // check expected node 1241 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, tc.node), 1242 "Diff: %s", cmp.Diff(tc.expectNode, tc.node)) 1243 // check expected events 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 // construct setter 1388 setter := VersionInfo(versionInfoFunc, runtimeTypeFunc, runtimeVersionFunc) 1389 // call setter on node 1390 err := setter(ctx, tc.node) 1391 require.Equal(t, tc.expectError, err) 1392 // check expected node 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 // makeExpectedImageList will sort them for expectedNode when the test case is run 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 // makeExpectedImageList will use both digests and tags 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 // today, imageListFunc is expected to return a sorted list, 1457 // but we may choose to sort in the setter at some future point 1458 // (e.g. if the image cache stopped sorting for us) 1459 sort.Sort(sliceutils.ByImageSize(tc.imageList)) 1460 return tc.imageList, tc.imageListError 1461 } 1462 // construct setter 1463 setter := Images(tc.maxImages, imageListFunc) 1464 // call setter on node 1465 node := &v1.Node{} 1466 err := setter(ctx, node) 1467 require.Equal(t, tc.expectError, err) 1468 // check expected node, image list should be reset to empty when there is an error 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 appArmorValidateHostFunc func() error 1513 cmStatus cm.Status 1514 nodeShutdownManagerErrors error 1515 expectConditions []v1.NodeCondition 1516 expectEvents []testEvent 1517 disableLocalStorageCapacityIsolation bool 1518 }{ 1519 { 1520 desc: "new, ready", 1521 node: withCapacity.DeepCopy(), 1522 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)}, 1523 // TODO(mtaufen): The current behavior is that we don't send an event for the initial NodeReady condition, 1524 // the reason for this is unclear, so we may want to actually send an event, and change these test cases 1525 // to ensure an event is sent. 1526 }, 1527 { 1528 desc: "new, ready: apparmor validator passed", 1529 node: withCapacity.DeepCopy(), 1530 appArmorValidateHostFunc: func() error { return nil }, 1531 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status. AppArmor enabled", now, now)}, 1532 }, 1533 { 1534 desc: "new, ready: apparmor validator failed", 1535 node: withCapacity.DeepCopy(), 1536 appArmorValidateHostFunc: func() error { return fmt.Errorf("foo") }, 1537 // absence of an additional message is understood to mean that AppArmor is disabled 1538 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)}, 1539 }, 1540 { 1541 desc: "new, ready: soft requirement warning", 1542 node: withCapacity.DeepCopy(), 1543 cmStatus: cm.Status{ 1544 SoftRequirements: fmt.Errorf("foo"), 1545 }, 1546 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status. WARNING: foo", now, now)}, 1547 }, 1548 { 1549 desc: "new, not ready: storage errors", 1550 node: withCapacity.DeepCopy(), 1551 storageErrors: errors.New("some storage error"), 1552 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "some storage error", now, now)}, 1553 }, 1554 { 1555 desc: "new, not ready: shutdown active", 1556 node: withCapacity.DeepCopy(), 1557 nodeShutdownManagerErrors: errors.New("node is shutting down"), 1558 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "node is shutting down", now, now)}, 1559 }, 1560 { 1561 desc: "new, not ready: runtime and network errors", 1562 node: withCapacity.DeepCopy(), 1563 runtimeErrors: errors.New("runtime"), 1564 networkErrors: errors.New("network"), 1565 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "[runtime, network]", now, now)}, 1566 }, 1567 { 1568 desc: "new, not ready: missing capacities", 1569 node: &v1.Node{}, 1570 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "missing node capacity for resources: cpu, memory, pods, ephemeral-storage", now, now)}, 1571 }, 1572 { 1573 desc: "new, ready: localStorageCapacityIsolation is not supported", 1574 node: withoutStorageCapacity.DeepCopy(), 1575 disableLocalStorageCapacityIsolation: true, 1576 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)}, 1577 }, 1578 // the transition tests ensure timestamps are set correctly, no need to test the entire condition matrix in this section 1579 { 1580 desc: "transition to ready", 1581 node: func() *v1.Node { 1582 node := withCapacity.DeepCopy() 1583 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(false, "", before, before)} 1584 return node 1585 }(), 1586 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)}, 1587 expectEvents: []testEvent{ 1588 { 1589 eventType: v1.EventTypeNormal, 1590 event: events.NodeReady, 1591 }, 1592 }, 1593 }, 1594 { 1595 desc: "transition to not ready", 1596 node: func() *v1.Node { 1597 node := withCapacity.DeepCopy() 1598 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(true, "", before, before)} 1599 return node 1600 }(), 1601 runtimeErrors: errors.New("foo"), 1602 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "foo", now, now)}, 1603 expectEvents: []testEvent{ 1604 { 1605 eventType: v1.EventTypeNormal, 1606 event: events.NodeNotReady, 1607 }, 1608 }, 1609 }, 1610 { 1611 desc: "ready, no transition", 1612 node: func() *v1.Node { 1613 node := withCapacity.DeepCopy() 1614 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(true, "", before, before)} 1615 return node 1616 }(), 1617 expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", before, now)}, 1618 expectEvents: []testEvent{}, 1619 }, 1620 { 1621 desc: "not ready, no transition", 1622 node: func() *v1.Node { 1623 node := withCapacity.DeepCopy() 1624 node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(false, "", before, before)} 1625 return node 1626 }(), 1627 runtimeErrors: errors.New("foo"), 1628 expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "foo", before, now)}, 1629 expectEvents: []testEvent{}, 1630 }, 1631 } 1632 for _, tc := range cases { 1633 t.Run(tc.desc, func(t *testing.T) { 1634 ctx := context.Background() 1635 runtimeErrorsFunc := func() error { 1636 return tc.runtimeErrors 1637 } 1638 networkErrorsFunc := func() error { 1639 return tc.networkErrors 1640 } 1641 storageErrorsFunc := func() error { 1642 return tc.storageErrors 1643 } 1644 cmStatusFunc := func() cm.Status { 1645 return tc.cmStatus 1646 } 1647 nodeShutdownErrorsFunc := func() error { 1648 return tc.nodeShutdownManagerErrors 1649 } 1650 events := []testEvent{} 1651 recordEventFunc := func(eventType, event string) { 1652 events = append(events, testEvent{ 1653 eventType: eventType, 1654 event: event, 1655 }) 1656 } 1657 // construct setter 1658 setter := ReadyCondition(nowFunc, runtimeErrorsFunc, networkErrorsFunc, storageErrorsFunc, tc.appArmorValidateHostFunc, cmStatusFunc, nodeShutdownErrorsFunc, recordEventFunc, !tc.disableLocalStorageCapacityIsolation) 1659 // call setter on node 1660 if err := setter(ctx, tc.node); err != nil { 1661 t.Fatalf("unexpected error: %v", err) 1662 } 1663 // check expected condition 1664 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions), 1665 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions)) 1666 // check expected events 1667 require.Equal(t, len(tc.expectEvents), len(events)) 1668 for i := range tc.expectEvents { 1669 assert.Equal(t, tc.expectEvents[i], events[i]) 1670 } 1671 }) 1672 } 1673 } 1674 1675 func TestMemoryPressureCondition(t *testing.T) { 1676 now := time.Now() 1677 before := now.Add(-time.Second) 1678 nowFunc := func() time.Time { return now } 1679 1680 cases := []struct { 1681 desc string 1682 node *v1.Node 1683 pressure bool 1684 expectConditions []v1.NodeCondition 1685 expectEvents []testEvent 1686 }{ 1687 { 1688 desc: "new, no pressure", 1689 node: &v1.Node{}, 1690 pressure: false, 1691 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, now, now)}, 1692 expectEvents: []testEvent{ 1693 { 1694 eventType: v1.EventTypeNormal, 1695 event: "NodeHasSufficientMemory", 1696 }, 1697 }, 1698 }, 1699 { 1700 desc: "new, pressure", 1701 node: &v1.Node{}, 1702 pressure: true, 1703 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, now, now)}, 1704 expectEvents: []testEvent{ 1705 { 1706 eventType: v1.EventTypeNormal, 1707 event: "NodeHasInsufficientMemory", 1708 }, 1709 }, 1710 }, 1711 { 1712 desc: "transition to pressure", 1713 node: &v1.Node{ 1714 Status: v1.NodeStatus{ 1715 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, before)}, 1716 }, 1717 }, 1718 pressure: true, 1719 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, now, now)}, 1720 expectEvents: []testEvent{ 1721 { 1722 eventType: v1.EventTypeNormal, 1723 event: "NodeHasInsufficientMemory", 1724 }, 1725 }, 1726 }, 1727 { 1728 desc: "transition to no pressure", 1729 node: &v1.Node{ 1730 Status: v1.NodeStatus{ 1731 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, before)}, 1732 }, 1733 }, 1734 pressure: false, 1735 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, now, now)}, 1736 expectEvents: []testEvent{ 1737 { 1738 eventType: v1.EventTypeNormal, 1739 event: "NodeHasSufficientMemory", 1740 }, 1741 }, 1742 }, 1743 { 1744 desc: "pressure, no transition", 1745 node: &v1.Node{ 1746 Status: v1.NodeStatus{ 1747 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, before)}, 1748 }, 1749 }, 1750 pressure: true, 1751 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, now)}, 1752 expectEvents: []testEvent{}, 1753 }, 1754 { 1755 desc: "no pressure, no transition", 1756 node: &v1.Node{ 1757 Status: v1.NodeStatus{ 1758 Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, before)}, 1759 }, 1760 }, 1761 pressure: false, 1762 expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, now)}, 1763 expectEvents: []testEvent{}, 1764 }, 1765 } 1766 for _, tc := range cases { 1767 t.Run(tc.desc, func(t *testing.T) { 1768 ctx := context.Background() 1769 events := []testEvent{} 1770 recordEventFunc := func(eventType, event string) { 1771 events = append(events, testEvent{ 1772 eventType: eventType, 1773 event: event, 1774 }) 1775 } 1776 pressureFunc := func() bool { 1777 return tc.pressure 1778 } 1779 // construct setter 1780 setter := MemoryPressureCondition(nowFunc, pressureFunc, recordEventFunc) 1781 // call setter on node 1782 if err := setter(ctx, tc.node); err != nil { 1783 t.Fatalf("unexpected error: %v", err) 1784 } 1785 // check expected condition 1786 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions), 1787 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions)) 1788 // check expected events 1789 require.Equal(t, len(tc.expectEvents), len(events)) 1790 for i := range tc.expectEvents { 1791 assert.Equal(t, tc.expectEvents[i], events[i]) 1792 } 1793 }) 1794 } 1795 } 1796 1797 func TestPIDPressureCondition(t *testing.T) { 1798 now := time.Now() 1799 before := now.Add(-time.Second) 1800 nowFunc := func() time.Time { return now } 1801 1802 cases := []struct { 1803 desc string 1804 node *v1.Node 1805 pressure bool 1806 expectConditions []v1.NodeCondition 1807 expectEvents []testEvent 1808 }{ 1809 { 1810 desc: "new, no pressure", 1811 node: &v1.Node{}, 1812 pressure: false, 1813 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)}, 1814 expectEvents: []testEvent{ 1815 { 1816 eventType: v1.EventTypeNormal, 1817 event: "NodeHasSufficientPID", 1818 }, 1819 }, 1820 }, 1821 { 1822 desc: "new, pressure", 1823 node: &v1.Node{}, 1824 pressure: true, 1825 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)}, 1826 expectEvents: []testEvent{ 1827 { 1828 eventType: v1.EventTypeNormal, 1829 event: "NodeHasInsufficientPID", 1830 }, 1831 }, 1832 }, 1833 { 1834 desc: "transition to pressure", 1835 node: &v1.Node{ 1836 Status: v1.NodeStatus{ 1837 Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)}, 1838 }, 1839 }, 1840 pressure: true, 1841 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)}, 1842 expectEvents: []testEvent{ 1843 { 1844 eventType: v1.EventTypeNormal, 1845 event: "NodeHasInsufficientPID", 1846 }, 1847 }, 1848 }, 1849 { 1850 desc: "transition to no pressure", 1851 node: &v1.Node{ 1852 Status: v1.NodeStatus{ 1853 Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)}, 1854 }, 1855 }, 1856 pressure: false, 1857 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)}, 1858 expectEvents: []testEvent{ 1859 { 1860 eventType: v1.EventTypeNormal, 1861 event: "NodeHasSufficientPID", 1862 }, 1863 }, 1864 }, 1865 { 1866 desc: "pressure, no transition", 1867 node: &v1.Node{ 1868 Status: v1.NodeStatus{ 1869 Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)}, 1870 }, 1871 }, 1872 pressure: true, 1873 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, now)}, 1874 expectEvents: []testEvent{}, 1875 }, 1876 { 1877 desc: "no pressure, no transition", 1878 node: &v1.Node{ 1879 Status: v1.NodeStatus{ 1880 Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)}, 1881 }, 1882 }, 1883 pressure: false, 1884 expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, now)}, 1885 expectEvents: []testEvent{}, 1886 }, 1887 } 1888 for _, tc := range cases { 1889 t.Run(tc.desc, func(t *testing.T) { 1890 ctx := context.Background() 1891 events := []testEvent{} 1892 recordEventFunc := func(eventType, event string) { 1893 events = append(events, testEvent{ 1894 eventType: eventType, 1895 event: event, 1896 }) 1897 } 1898 pressureFunc := func() bool { 1899 return tc.pressure 1900 } 1901 // construct setter 1902 setter := PIDPressureCondition(nowFunc, pressureFunc, recordEventFunc) 1903 // call setter on node 1904 if err := setter(ctx, tc.node); err != nil { 1905 t.Fatalf("unexpected error: %v", err) 1906 } 1907 // check expected condition 1908 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions), 1909 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions)) 1910 // check expected events 1911 require.Equal(t, len(tc.expectEvents), len(events)) 1912 for i := range tc.expectEvents { 1913 assert.Equal(t, tc.expectEvents[i], events[i]) 1914 } 1915 }) 1916 } 1917 } 1918 1919 func TestDiskPressureCondition(t *testing.T) { 1920 now := time.Now() 1921 before := now.Add(-time.Second) 1922 nowFunc := func() time.Time { return now } 1923 1924 cases := []struct { 1925 desc string 1926 node *v1.Node 1927 pressure bool 1928 expectConditions []v1.NodeCondition 1929 expectEvents []testEvent 1930 }{ 1931 { 1932 desc: "new, no pressure", 1933 node: &v1.Node{}, 1934 pressure: false, 1935 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, now, now)}, 1936 expectEvents: []testEvent{ 1937 { 1938 eventType: v1.EventTypeNormal, 1939 event: "NodeHasNoDiskPressure", 1940 }, 1941 }, 1942 }, 1943 { 1944 desc: "new, pressure", 1945 node: &v1.Node{}, 1946 pressure: true, 1947 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, now, now)}, 1948 expectEvents: []testEvent{ 1949 { 1950 eventType: v1.EventTypeNormal, 1951 event: "NodeHasDiskPressure", 1952 }, 1953 }, 1954 }, 1955 { 1956 desc: "transition to pressure", 1957 node: &v1.Node{ 1958 Status: v1.NodeStatus{ 1959 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, before)}, 1960 }, 1961 }, 1962 pressure: true, 1963 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, now, now)}, 1964 expectEvents: []testEvent{ 1965 { 1966 eventType: v1.EventTypeNormal, 1967 event: "NodeHasDiskPressure", 1968 }, 1969 }, 1970 }, 1971 { 1972 desc: "transition to no pressure", 1973 node: &v1.Node{ 1974 Status: v1.NodeStatus{ 1975 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, before)}, 1976 }, 1977 }, 1978 pressure: false, 1979 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, now, now)}, 1980 expectEvents: []testEvent{ 1981 { 1982 eventType: v1.EventTypeNormal, 1983 event: "NodeHasNoDiskPressure", 1984 }, 1985 }, 1986 }, 1987 { 1988 desc: "pressure, no transition", 1989 node: &v1.Node{ 1990 Status: v1.NodeStatus{ 1991 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, before)}, 1992 }, 1993 }, 1994 pressure: true, 1995 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, now)}, 1996 expectEvents: []testEvent{}, 1997 }, 1998 { 1999 desc: "no pressure, no transition", 2000 node: &v1.Node{ 2001 Status: v1.NodeStatus{ 2002 Conditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, before)}, 2003 }, 2004 }, 2005 pressure: false, 2006 expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, now)}, 2007 expectEvents: []testEvent{}, 2008 }, 2009 } 2010 for _, tc := range cases { 2011 t.Run(tc.desc, func(t *testing.T) { 2012 ctx := context.Background() 2013 events := []testEvent{} 2014 recordEventFunc := func(eventType, event string) { 2015 events = append(events, testEvent{ 2016 eventType: eventType, 2017 event: event, 2018 }) 2019 } 2020 pressureFunc := func() bool { 2021 return tc.pressure 2022 } 2023 // construct setter 2024 setter := DiskPressureCondition(nowFunc, pressureFunc, recordEventFunc) 2025 // call setter on node 2026 if err := setter(ctx, tc.node); err != nil { 2027 t.Fatalf("unexpected error: %v", err) 2028 } 2029 // check expected condition 2030 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions), 2031 "Diff: %s", cmp.Diff(tc.expectConditions, tc.node.Status.Conditions)) 2032 // check expected events 2033 require.Equal(t, len(tc.expectEvents), len(events)) 2034 for i := range tc.expectEvents { 2035 assert.Equal(t, tc.expectEvents[i], events[i]) 2036 } 2037 }) 2038 } 2039 } 2040 2041 func TestVolumesInUse(t *testing.T) { 2042 withVolumesInUse := &v1.Node{ 2043 Status: v1.NodeStatus{ 2044 VolumesInUse: []v1.UniqueVolumeName{"foo"}, 2045 }, 2046 } 2047 2048 cases := []struct { 2049 desc string 2050 node *v1.Node 2051 synced bool 2052 volumesInUse []v1.UniqueVolumeName 2053 expectVolumesInUse []v1.UniqueVolumeName 2054 }{ 2055 { 2056 desc: "synced", 2057 node: withVolumesInUse.DeepCopy(), 2058 synced: true, 2059 volumesInUse: []v1.UniqueVolumeName{"bar"}, 2060 expectVolumesInUse: []v1.UniqueVolumeName{"bar"}, 2061 }, 2062 { 2063 desc: "not synced", 2064 node: withVolumesInUse.DeepCopy(), 2065 synced: false, 2066 volumesInUse: []v1.UniqueVolumeName{"bar"}, 2067 expectVolumesInUse: []v1.UniqueVolumeName{"foo"}, 2068 }, 2069 } 2070 2071 for _, tc := range cases { 2072 t.Run(tc.desc, func(t *testing.T) { 2073 ctx := context.Background() 2074 syncedFunc := func() bool { 2075 return tc.synced 2076 } 2077 volumesInUseFunc := func() []v1.UniqueVolumeName { 2078 return tc.volumesInUse 2079 } 2080 // construct setter 2081 setter := VolumesInUse(syncedFunc, volumesInUseFunc) 2082 // call setter on node 2083 if err := setter(ctx, tc.node); err != nil { 2084 t.Fatalf("unexpected error: %v", err) 2085 } 2086 // check expected volumes 2087 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectVolumesInUse, tc.node.Status.VolumesInUse), 2088 "Diff: %s", cmp.Diff(tc.expectVolumesInUse, tc.node.Status.VolumesInUse)) 2089 }) 2090 } 2091 } 2092 2093 func TestVolumeLimits(t *testing.T) { 2094 const ( 2095 volumeLimitKey = "attachable-volumes-fake-provider" 2096 volumeLimitVal = 16 2097 ) 2098 2099 var cases = []struct { 2100 desc string 2101 volumePluginList []volume.VolumePluginWithAttachLimits 2102 expectNode *v1.Node 2103 }{ 2104 { 2105 desc: "translate limits to capacity and allocatable for plugins that return successfully from GetVolumeLimits", 2106 volumePluginList: []volume.VolumePluginWithAttachLimits{ 2107 &volumetest.FakeVolumePlugin{ 2108 VolumeLimits: map[string]int64{volumeLimitKey: volumeLimitVal}, 2109 }, 2110 }, 2111 expectNode: &v1.Node{ 2112 Status: v1.NodeStatus{ 2113 Capacity: v1.ResourceList{ 2114 volumeLimitKey: *resource.NewQuantity(volumeLimitVal, resource.DecimalSI), 2115 }, 2116 Allocatable: v1.ResourceList{ 2117 volumeLimitKey: *resource.NewQuantity(volumeLimitVal, resource.DecimalSI), 2118 }, 2119 }, 2120 }, 2121 }, 2122 { 2123 desc: "skip plugins that return errors from GetVolumeLimits", 2124 volumePluginList: []volume.VolumePluginWithAttachLimits{ 2125 &volumetest.FakeVolumePlugin{ 2126 VolumeLimitsError: fmt.Errorf("foo"), 2127 }, 2128 }, 2129 expectNode: &v1.Node{}, 2130 }, 2131 { 2132 desc: "no plugins", 2133 expectNode: &v1.Node{}, 2134 }, 2135 } 2136 2137 for _, tc := range cases { 2138 t.Run(tc.desc, func(t *testing.T) { 2139 ctx := context.Background() 2140 volumePluginListFunc := func() []volume.VolumePluginWithAttachLimits { 2141 return tc.volumePluginList 2142 } 2143 // construct setter 2144 setter := VolumeLimits(volumePluginListFunc) 2145 // call setter on node 2146 node := &v1.Node{} 2147 if err := setter(ctx, node); err != nil { 2148 t.Fatalf("unexpected error: %v", err) 2149 } 2150 // check expected node 2151 assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, node), 2152 "Diff: %s", cmp.Diff(tc.expectNode, node)) 2153 }) 2154 } 2155 } 2156 2157 func TestDaemonEndpoints(t *testing.T) { 2158 for _, test := range []struct { 2159 name string 2160 endpoints *v1.NodeDaemonEndpoints 2161 expected *v1.NodeDaemonEndpoints 2162 }{ 2163 { 2164 name: "empty daemon endpoints", 2165 endpoints: &v1.NodeDaemonEndpoints{}, 2166 expected: &v1.NodeDaemonEndpoints{KubeletEndpoint: v1.DaemonEndpoint{Port: 0}}, 2167 }, 2168 { 2169 name: "daemon endpoints with specific port", 2170 endpoints: &v1.NodeDaemonEndpoints{KubeletEndpoint: v1.DaemonEndpoint{Port: 5678}}, 2171 expected: &v1.NodeDaemonEndpoints{KubeletEndpoint: v1.DaemonEndpoint{Port: 5678}}, 2172 }, 2173 } { 2174 t.Run(test.name, func(t *testing.T) { 2175 ctx := context.Background() 2176 existingNode := &v1.Node{ 2177 ObjectMeta: metav1.ObjectMeta{ 2178 Name: testKubeletHostname, 2179 }, 2180 Spec: v1.NodeSpec{}, 2181 Status: v1.NodeStatus{ 2182 Addresses: []v1.NodeAddress{}, 2183 }, 2184 } 2185 2186 setter := DaemonEndpoints(test.endpoints) 2187 if err := setter(ctx, existingNode); err != nil { 2188 t.Fatal(err) 2189 } 2190 2191 assert.Equal(t, *test.expected, existingNode.Status.DaemonEndpoints) 2192 }) 2193 } 2194 } 2195 2196 // Test Helpers: 2197 2198 // testEvent is used to record events for tests 2199 type testEvent struct { 2200 eventType string 2201 event string 2202 message string 2203 } 2204 2205 // makeImageList randomly generates a list of images with the given count 2206 func makeImageList(numImages, numTags, minSize, maxSize int32) []kubecontainer.Image { 2207 images := make([]kubecontainer.Image, numImages) 2208 for i := range images { 2209 image := &images[i] 2210 image.ID = string(uuid.NewUUID()) 2211 image.RepoTags = makeImageTags(numTags) 2212 image.Size = rand.Int63nRange(int64(minSize), int64(maxSize+1)) 2213 } 2214 return images 2215 } 2216 2217 func makeExpectedImageList(imageList []kubecontainer.Image, maxImages, maxNames int32) []v1.ContainerImage { 2218 // copy the imageList, we do not want to mutate it in-place and accidentally edit a test case 2219 images := make([]kubecontainer.Image, len(imageList)) 2220 copy(images, imageList) 2221 // sort images by size 2222 sort.Sort(sliceutils.ByImageSize(images)) 2223 // convert to []v1.ContainerImage and truncate the list of names 2224 expectedImages := make([]v1.ContainerImage, len(images)) 2225 for i := range images { 2226 image := &images[i] 2227 expectedImage := &expectedImages[i] 2228 names := append(image.RepoDigests, image.RepoTags...) 2229 if len(names) > int(maxNames) { 2230 names = names[0:maxNames] 2231 } 2232 expectedImage.Names = names 2233 expectedImage.SizeBytes = image.Size 2234 } 2235 // -1 means no limit, truncate result list if necessary. 2236 if maxImages > -1 && 2237 int(maxImages) < len(expectedImages) { 2238 return expectedImages[0:maxImages] 2239 } 2240 return expectedImages 2241 } 2242 2243 func makeImageTags(num int32) []string { 2244 tags := make([]string, num) 2245 for i := range tags { 2246 tags[i] = "registry.k8s.io:v" + strconv.Itoa(i) 2247 } 2248 return tags 2249 } 2250 2251 func makeReadyCondition(ready bool, message string, transition, heartbeat time.Time) *v1.NodeCondition { 2252 if ready { 2253 return &v1.NodeCondition{ 2254 Type: v1.NodeReady, 2255 Status: v1.ConditionTrue, 2256 Reason: "KubeletReady", 2257 Message: message, 2258 LastTransitionTime: metav1.NewTime(transition), 2259 LastHeartbeatTime: metav1.NewTime(heartbeat), 2260 } 2261 } 2262 return &v1.NodeCondition{ 2263 Type: v1.NodeReady, 2264 Status: v1.ConditionFalse, 2265 Reason: "KubeletNotReady", 2266 Message: message, 2267 LastTransitionTime: metav1.NewTime(transition), 2268 LastHeartbeatTime: metav1.NewTime(heartbeat), 2269 } 2270 } 2271 2272 func makeMemoryPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition { 2273 if pressure { 2274 return &v1.NodeCondition{ 2275 Type: v1.NodeMemoryPressure, 2276 Status: v1.ConditionTrue, 2277 Reason: "KubeletHasInsufficientMemory", 2278 Message: "kubelet has insufficient memory available", 2279 LastTransitionTime: metav1.NewTime(transition), 2280 LastHeartbeatTime: metav1.NewTime(heartbeat), 2281 } 2282 } 2283 return &v1.NodeCondition{ 2284 Type: v1.NodeMemoryPressure, 2285 Status: v1.ConditionFalse, 2286 Reason: "KubeletHasSufficientMemory", 2287 Message: "kubelet has sufficient memory available", 2288 LastTransitionTime: metav1.NewTime(transition), 2289 LastHeartbeatTime: metav1.NewTime(heartbeat), 2290 } 2291 } 2292 2293 func makePIDPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition { 2294 if pressure { 2295 return &v1.NodeCondition{ 2296 Type: v1.NodePIDPressure, 2297 Status: v1.ConditionTrue, 2298 Reason: "KubeletHasInsufficientPID", 2299 Message: "kubelet has insufficient PID available", 2300 LastTransitionTime: metav1.NewTime(transition), 2301 LastHeartbeatTime: metav1.NewTime(heartbeat), 2302 } 2303 } 2304 return &v1.NodeCondition{ 2305 Type: v1.NodePIDPressure, 2306 Status: v1.ConditionFalse, 2307 Reason: "KubeletHasSufficientPID", 2308 Message: "kubelet has sufficient PID available", 2309 LastTransitionTime: metav1.NewTime(transition), 2310 LastHeartbeatTime: metav1.NewTime(heartbeat), 2311 } 2312 } 2313 2314 func makeDiskPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition { 2315 if pressure { 2316 return &v1.NodeCondition{ 2317 Type: v1.NodeDiskPressure, 2318 Status: v1.ConditionTrue, 2319 Reason: "KubeletHasDiskPressure", 2320 Message: "kubelet has disk pressure", 2321 LastTransitionTime: metav1.NewTime(transition), 2322 LastHeartbeatTime: metav1.NewTime(heartbeat), 2323 } 2324 } 2325 return &v1.NodeCondition{ 2326 Type: v1.NodeDiskPressure, 2327 Status: v1.ConditionFalse, 2328 Reason: "KubeletHasNoDiskPressure", 2329 Message: "kubelet has no disk pressure", 2330 LastTransitionTime: metav1.NewTime(transition), 2331 LastHeartbeatTime: metav1.NewTime(heartbeat), 2332 } 2333 }