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