k8s.io/kubernetes@v1.29.3/pkg/kubelet/nodestatus/setters_test.go (about)

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