...

Source file src/k8s.io/kubernetes/pkg/kubelet/nodestatus/setters_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/nodestatus

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

View as plain text