...

Source file src/k8s.io/kubernetes/pkg/kubelet/eviction/helpers_test.go

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

     1  /*
     2  Copyright 2016 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 eviction
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"sort"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	v1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/resource"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    34  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    35  	statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
    36  
    37  	"k8s.io/kubernetes/pkg/features"
    38  	evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
    39  	kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
    40  )
    41  
    42  func quantityMustParse(value string) *resource.Quantity {
    43  	q := resource.MustParse(value)
    44  	return &q
    45  }
    46  
    47  func TestGetReclaimableThreshold(t *testing.T) {
    48  	testCases := map[string]struct {
    49  		thresholds []evictionapi.Threshold
    50  	}{
    51  		"": {
    52  			thresholds: []evictionapi.Threshold{
    53  				{
    54  					Signal:   evictionapi.SignalAllocatableMemoryAvailable,
    55  					Operator: evictionapi.OpLessThan,
    56  					Value: evictionapi.ThresholdValue{
    57  						Quantity: quantityMustParse("150Mi"),
    58  					},
    59  					MinReclaim: &evictionapi.ThresholdValue{
    60  						Quantity: quantityMustParse("0"),
    61  					},
    62  				},
    63  				{
    64  					Signal:   evictionapi.SignalMemoryAvailable,
    65  					Operator: evictionapi.OpLessThan,
    66  					Value: evictionapi.ThresholdValue{
    67  						Quantity: quantityMustParse("150Mi"),
    68  					},
    69  					MinReclaim: &evictionapi.ThresholdValue{
    70  						Quantity: quantityMustParse("0"),
    71  					},
    72  				},
    73  				{
    74  					Signal:   evictionapi.SignalImageFsAvailable,
    75  					Operator: evictionapi.OpLessThan,
    76  					Value: evictionapi.ThresholdValue{
    77  						Quantity: quantityMustParse("150Mi"),
    78  					},
    79  					MinReclaim: &evictionapi.ThresholdValue{
    80  						Quantity: quantityMustParse("2Gi"),
    81  					},
    82  				},
    83  				{
    84  					Signal:   evictionapi.SignalNodeFsAvailable,
    85  					Operator: evictionapi.OpLessThan,
    86  					Value: evictionapi.ThresholdValue{
    87  						Quantity: quantityMustParse("100Mi"),
    88  					},
    89  					MinReclaim: &evictionapi.ThresholdValue{
    90  						Quantity: quantityMustParse("1Gi"),
    91  					},
    92  				},
    93  				{
    94  					Signal:   evictionapi.SignalContainerFsAvailable,
    95  					Operator: evictionapi.OpLessThan,
    96  					Value: evictionapi.ThresholdValue{
    97  						Quantity: quantityMustParse("100Mi"),
    98  					},
    99  					MinReclaim: &evictionapi.ThresholdValue{
   100  						Quantity: quantityMustParse("1Gi"),
   101  					},
   102  				},
   103  			},
   104  		},
   105  	}
   106  	for testName, testCase := range testCases {
   107  		sort.Sort(byEvictionPriority(testCase.thresholds))
   108  		_, _, ok := getReclaimableThreshold(testCase.thresholds)
   109  		if !ok {
   110  			t.Errorf("Didn't find reclaimable threshold, test: %v", testName)
   111  		}
   112  	}
   113  }
   114  
   115  func TestParseThresholdConfig(t *testing.T) {
   116  	gracePeriod, _ := time.ParseDuration("30s")
   117  	testCases := map[string]struct {
   118  		allocatableConfig       []string
   119  		evictionHard            map[string]string
   120  		evictionSoft            map[string]string
   121  		evictionSoftGracePeriod map[string]string
   122  		evictionMinReclaim      map[string]string
   123  		expectErr               bool
   124  		expectThresholds        []evictionapi.Threshold
   125  	}{
   126  		"no values": {
   127  			allocatableConfig:       []string{},
   128  			evictionHard:            map[string]string{},
   129  			evictionSoft:            map[string]string{},
   130  			evictionSoftGracePeriod: map[string]string{},
   131  			evictionMinReclaim:      map[string]string{},
   132  			expectErr:               false,
   133  			expectThresholds:        []evictionapi.Threshold{},
   134  		},
   135  		"all memory eviction values": {
   136  			allocatableConfig:       []string{kubetypes.NodeAllocatableEnforcementKey},
   137  			evictionHard:            map[string]string{"memory.available": "150Mi"},
   138  			evictionSoft:            map[string]string{"memory.available": "300Mi"},
   139  			evictionSoftGracePeriod: map[string]string{"memory.available": "30s"},
   140  			evictionMinReclaim:      map[string]string{"memory.available": "0"},
   141  			expectErr:               false,
   142  			expectThresholds: []evictionapi.Threshold{
   143  				{
   144  					Signal:   evictionapi.SignalAllocatableMemoryAvailable,
   145  					Operator: evictionapi.OpLessThan,
   146  					Value: evictionapi.ThresholdValue{
   147  						Quantity: quantityMustParse("150Mi"),
   148  					},
   149  					MinReclaim: &evictionapi.ThresholdValue{
   150  						Quantity: quantityMustParse("0"),
   151  					},
   152  				},
   153  				{
   154  					Signal:   evictionapi.SignalMemoryAvailable,
   155  					Operator: evictionapi.OpLessThan,
   156  					Value: evictionapi.ThresholdValue{
   157  						Quantity: quantityMustParse("150Mi"),
   158  					},
   159  					MinReclaim: &evictionapi.ThresholdValue{
   160  						Quantity: quantityMustParse("0"),
   161  					},
   162  				},
   163  				{
   164  					Signal:   evictionapi.SignalMemoryAvailable,
   165  					Operator: evictionapi.OpLessThan,
   166  					Value: evictionapi.ThresholdValue{
   167  						Quantity: quantityMustParse("300Mi"),
   168  					},
   169  					GracePeriod: gracePeriod,
   170  					MinReclaim: &evictionapi.ThresholdValue{
   171  						Quantity: quantityMustParse("0"),
   172  					},
   173  				},
   174  			},
   175  		},
   176  		"all memory eviction values in percentages": {
   177  			allocatableConfig:       []string{},
   178  			evictionHard:            map[string]string{"memory.available": "10%"},
   179  			evictionSoft:            map[string]string{"memory.available": "30%"},
   180  			evictionSoftGracePeriod: map[string]string{"memory.available": "30s"},
   181  			evictionMinReclaim:      map[string]string{"memory.available": "5%"},
   182  			expectErr:               false,
   183  			expectThresholds: []evictionapi.Threshold{
   184  				{
   185  					Signal:   evictionapi.SignalMemoryAvailable,
   186  					Operator: evictionapi.OpLessThan,
   187  					Value: evictionapi.ThresholdValue{
   188  						Percentage: 0.1,
   189  					},
   190  					MinReclaim: &evictionapi.ThresholdValue{
   191  						Percentage: 0.05,
   192  					},
   193  				},
   194  				{
   195  					Signal:   evictionapi.SignalMemoryAvailable,
   196  					Operator: evictionapi.OpLessThan,
   197  					Value: evictionapi.ThresholdValue{
   198  						Percentage: 0.3,
   199  					},
   200  					GracePeriod: gracePeriod,
   201  					MinReclaim: &evictionapi.ThresholdValue{
   202  						Percentage: 0.05,
   203  					},
   204  				},
   205  			},
   206  		},
   207  		"disk eviction values": {
   208  			allocatableConfig:       []string{},
   209  			evictionHard:            map[string]string{"imagefs.available": "150Mi", "nodefs.available": "100Mi"},
   210  			evictionSoft:            map[string]string{"imagefs.available": "300Mi", "nodefs.available": "200Mi"},
   211  			evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"},
   212  			evictionMinReclaim:      map[string]string{"imagefs.available": "2Gi", "nodefs.available": "1Gi"},
   213  			expectErr:               false,
   214  			expectThresholds: []evictionapi.Threshold{
   215  				{
   216  					Signal:   evictionapi.SignalImageFsAvailable,
   217  					Operator: evictionapi.OpLessThan,
   218  					Value: evictionapi.ThresholdValue{
   219  						Quantity: quantityMustParse("150Mi"),
   220  					},
   221  					MinReclaim: &evictionapi.ThresholdValue{
   222  						Quantity: quantityMustParse("2Gi"),
   223  					},
   224  				},
   225  				{
   226  					Signal:   evictionapi.SignalNodeFsAvailable,
   227  					Operator: evictionapi.OpLessThan,
   228  					Value: evictionapi.ThresholdValue{
   229  						Quantity: quantityMustParse("100Mi"),
   230  					},
   231  					MinReclaim: &evictionapi.ThresholdValue{
   232  						Quantity: quantityMustParse("1Gi"),
   233  					},
   234  				},
   235  				{
   236  					Signal:   evictionapi.SignalImageFsAvailable,
   237  					Operator: evictionapi.OpLessThan,
   238  					Value: evictionapi.ThresholdValue{
   239  						Quantity: quantityMustParse("300Mi"),
   240  					},
   241  					GracePeriod: gracePeriod,
   242  					MinReclaim: &evictionapi.ThresholdValue{
   243  						Quantity: quantityMustParse("2Gi"),
   244  					},
   245  				},
   246  				{
   247  					Signal:   evictionapi.SignalNodeFsAvailable,
   248  					Operator: evictionapi.OpLessThan,
   249  					Value: evictionapi.ThresholdValue{
   250  						Quantity: quantityMustParse("200Mi"),
   251  					},
   252  					GracePeriod: gracePeriod,
   253  					MinReclaim: &evictionapi.ThresholdValue{
   254  						Quantity: quantityMustParse("1Gi"),
   255  					},
   256  				},
   257  			},
   258  		},
   259  		"disk eviction values in percentages": {
   260  			allocatableConfig:       []string{},
   261  			evictionHard:            map[string]string{"imagefs.available": "15%", "nodefs.available": "10.5%"},
   262  			evictionSoft:            map[string]string{"imagefs.available": "30%", "nodefs.available": "20.5%"},
   263  			evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"},
   264  			evictionMinReclaim:      map[string]string{"imagefs.available": "10%", "nodefs.available": "5%"},
   265  			expectErr:               false,
   266  			expectThresholds: []evictionapi.Threshold{
   267  				{
   268  					Signal:   evictionapi.SignalImageFsAvailable,
   269  					Operator: evictionapi.OpLessThan,
   270  					Value: evictionapi.ThresholdValue{
   271  						Percentage: 0.15,
   272  					},
   273  					MinReclaim: &evictionapi.ThresholdValue{
   274  						Percentage: 0.1,
   275  					},
   276  				},
   277  				{
   278  					Signal:   evictionapi.SignalNodeFsAvailable,
   279  					Operator: evictionapi.OpLessThan,
   280  					Value: evictionapi.ThresholdValue{
   281  						Percentage: 0.105,
   282  					},
   283  					MinReclaim: &evictionapi.ThresholdValue{
   284  						Percentage: 0.05,
   285  					},
   286  				},
   287  				{
   288  					Signal:   evictionapi.SignalImageFsAvailable,
   289  					Operator: evictionapi.OpLessThan,
   290  					Value: evictionapi.ThresholdValue{
   291  						Percentage: 0.3,
   292  					},
   293  					GracePeriod: gracePeriod,
   294  					MinReclaim: &evictionapi.ThresholdValue{
   295  						Percentage: 0.1,
   296  					},
   297  				},
   298  				{
   299  					Signal:   evictionapi.SignalNodeFsAvailable,
   300  					Operator: evictionapi.OpLessThan,
   301  					Value: evictionapi.ThresholdValue{
   302  						Percentage: 0.205,
   303  					},
   304  					GracePeriod: gracePeriod,
   305  					MinReclaim: &evictionapi.ThresholdValue{
   306  						Percentage: 0.05,
   307  					},
   308  				},
   309  			},
   310  		},
   311  		"inode eviction values": {
   312  			allocatableConfig:       []string{},
   313  			evictionHard:            map[string]string{"imagefs.inodesFree": "150Mi", "nodefs.inodesFree": "100Mi"},
   314  			evictionSoft:            map[string]string{"imagefs.inodesFree": "300Mi", "nodefs.inodesFree": "200Mi"},
   315  			evictionSoftGracePeriod: map[string]string{"imagefs.inodesFree": "30s", "nodefs.inodesFree": "30s"},
   316  			evictionMinReclaim:      map[string]string{"imagefs.inodesFree": "2Gi", "nodefs.inodesFree": "1Gi"},
   317  			expectErr:               false,
   318  			expectThresholds: []evictionapi.Threshold{
   319  				{
   320  					Signal:   evictionapi.SignalImageFsInodesFree,
   321  					Operator: evictionapi.OpLessThan,
   322  					Value: evictionapi.ThresholdValue{
   323  						Quantity: quantityMustParse("150Mi"),
   324  					},
   325  					MinReclaim: &evictionapi.ThresholdValue{
   326  						Quantity: quantityMustParse("2Gi"),
   327  					},
   328  				},
   329  				{
   330  					Signal:   evictionapi.SignalNodeFsInodesFree,
   331  					Operator: evictionapi.OpLessThan,
   332  					Value: evictionapi.ThresholdValue{
   333  						Quantity: quantityMustParse("100Mi"),
   334  					},
   335  					MinReclaim: &evictionapi.ThresholdValue{
   336  						Quantity: quantityMustParse("1Gi"),
   337  					},
   338  				},
   339  				{
   340  					Signal:   evictionapi.SignalImageFsInodesFree,
   341  					Operator: evictionapi.OpLessThan,
   342  					Value: evictionapi.ThresholdValue{
   343  						Quantity: quantityMustParse("300Mi"),
   344  					},
   345  					GracePeriod: gracePeriod,
   346  					MinReclaim: &evictionapi.ThresholdValue{
   347  						Quantity: quantityMustParse("2Gi"),
   348  					},
   349  				},
   350  				{
   351  					Signal:   evictionapi.SignalNodeFsInodesFree,
   352  					Operator: evictionapi.OpLessThan,
   353  					Value: evictionapi.ThresholdValue{
   354  						Quantity: quantityMustParse("200Mi"),
   355  					},
   356  					GracePeriod: gracePeriod,
   357  					MinReclaim: &evictionapi.ThresholdValue{
   358  						Quantity: quantityMustParse("1Gi"),
   359  					},
   360  				},
   361  			},
   362  		},
   363  		"disable via 0%": {
   364  			allocatableConfig: []string{},
   365  			evictionHard:      map[string]string{"memory.available": "0%"},
   366  			evictionSoft:      map[string]string{"memory.available": "0%"},
   367  			expectErr:         false,
   368  			expectThresholds:  []evictionapi.Threshold{},
   369  		},
   370  		"disable via 100%": {
   371  			allocatableConfig: []string{},
   372  			evictionHard:      map[string]string{"memory.available": "100%"},
   373  			evictionSoft:      map[string]string{"memory.available": "100%"},
   374  			expectErr:         false,
   375  			expectThresholds:  []evictionapi.Threshold{},
   376  		},
   377  		"invalid-signal": {
   378  			allocatableConfig:       []string{},
   379  			evictionHard:            map[string]string{"mem.available": "150Mi"},
   380  			evictionSoft:            map[string]string{},
   381  			evictionSoftGracePeriod: map[string]string{},
   382  			evictionMinReclaim:      map[string]string{},
   383  			expectErr:               true,
   384  			expectThresholds:        []evictionapi.Threshold{},
   385  		},
   386  		"hard-signal-negative": {
   387  			allocatableConfig:       []string{},
   388  			evictionHard:            map[string]string{"memory.available": "-150Mi"},
   389  			evictionSoft:            map[string]string{},
   390  			evictionSoftGracePeriod: map[string]string{},
   391  			evictionMinReclaim:      map[string]string{},
   392  			expectErr:               true,
   393  			expectThresholds:        []evictionapi.Threshold{},
   394  		},
   395  		"hard-signal-negative-percentage": {
   396  			allocatableConfig:       []string{},
   397  			evictionHard:            map[string]string{"memory.available": "-15%"},
   398  			evictionSoft:            map[string]string{},
   399  			evictionSoftGracePeriod: map[string]string{},
   400  			evictionMinReclaim:      map[string]string{},
   401  			expectErr:               true,
   402  			expectThresholds:        []evictionapi.Threshold{},
   403  		},
   404  		"hard-signal-percentage-greater-than-100%": {
   405  			allocatableConfig:       []string{},
   406  			evictionHard:            map[string]string{"memory.available": "150%"},
   407  			evictionSoft:            map[string]string{},
   408  			evictionSoftGracePeriod: map[string]string{},
   409  			evictionMinReclaim:      map[string]string{},
   410  			expectErr:               true,
   411  			expectThresholds:        []evictionapi.Threshold{},
   412  		},
   413  		"soft-signal-negative": {
   414  			allocatableConfig:       []string{},
   415  			evictionHard:            map[string]string{},
   416  			evictionSoft:            map[string]string{"memory.available": "-150Mi"},
   417  			evictionSoftGracePeriod: map[string]string{},
   418  			evictionMinReclaim:      map[string]string{},
   419  			expectErr:               true,
   420  			expectThresholds:        []evictionapi.Threshold{},
   421  		},
   422  		"valid-and-invalid-signal": {
   423  			allocatableConfig:       []string{},
   424  			evictionHard:            map[string]string{"memory.available": "150Mi", "invalid.foo": "150Mi"},
   425  			evictionSoft:            map[string]string{},
   426  			evictionSoftGracePeriod: map[string]string{},
   427  			evictionMinReclaim:      map[string]string{},
   428  			expectErr:               true,
   429  			expectThresholds:        []evictionapi.Threshold{},
   430  		},
   431  		"soft-no-grace-period": {
   432  			allocatableConfig:       []string{},
   433  			evictionHard:            map[string]string{},
   434  			evictionSoft:            map[string]string{"memory.available": "150Mi"},
   435  			evictionSoftGracePeriod: map[string]string{},
   436  			evictionMinReclaim:      map[string]string{},
   437  			expectErr:               true,
   438  			expectThresholds:        []evictionapi.Threshold{},
   439  		},
   440  		"soft-negative-grace-period": {
   441  			allocatableConfig:       []string{},
   442  			evictionHard:            map[string]string{},
   443  			evictionSoft:            map[string]string{"memory.available": "150Mi"},
   444  			evictionSoftGracePeriod: map[string]string{"memory.available": "-30s"},
   445  			evictionMinReclaim:      map[string]string{},
   446  			expectErr:               true,
   447  			expectThresholds:        []evictionapi.Threshold{},
   448  		},
   449  		"negative-reclaim": {
   450  			allocatableConfig:       []string{},
   451  			evictionHard:            map[string]string{},
   452  			evictionSoft:            map[string]string{},
   453  			evictionSoftGracePeriod: map[string]string{},
   454  			evictionMinReclaim:      map[string]string{"memory.available": "-300Mi"},
   455  			expectErr:               true,
   456  			expectThresholds:        []evictionapi.Threshold{},
   457  		},
   458  	}
   459  	for testName, testCase := range testCases {
   460  		thresholds, err := ParseThresholdConfig(testCase.allocatableConfig, testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod, testCase.evictionMinReclaim)
   461  		if testCase.expectErr != (err != nil) {
   462  			t.Errorf("Err not as expected, test: %v, error expected: %v, actual: %v", testName, testCase.expectErr, err)
   463  		}
   464  		if !thresholdsEqual(testCase.expectThresholds, thresholds) {
   465  			t.Errorf("thresholds not as expected, test: %v, expected: %v, actual: %v", testName, testCase.expectThresholds, thresholds)
   466  		}
   467  	}
   468  }
   469  
   470  func TestAddAllocatableThresholds(t *testing.T) {
   471  	// About func addAllocatableThresholds, only someone threshold that "Signal" is "memory.available" and "GracePeriod" is 0,
   472  	// append this threshold(changed "Signal" to "allocatableMemory.available") to thresholds
   473  	testCases := map[string]struct {
   474  		thresholds []evictionapi.Threshold
   475  		expected   []evictionapi.Threshold
   476  	}{
   477  		"non-memory-signal": {
   478  			thresholds: []evictionapi.Threshold{
   479  				{
   480  					Signal:   evictionapi.SignalImageFsAvailable,
   481  					Operator: evictionapi.OpLessThan,
   482  					Value: evictionapi.ThresholdValue{
   483  						Quantity: quantityMustParse("150Mi"),
   484  					},
   485  					GracePeriod: 0,
   486  					MinReclaim: &evictionapi.ThresholdValue{
   487  						Quantity: quantityMustParse("0"),
   488  					},
   489  				},
   490  			},
   491  			expected: []evictionapi.Threshold{
   492  				{
   493  					Signal:   evictionapi.SignalImageFsAvailable,
   494  					Operator: evictionapi.OpLessThan,
   495  					Value: evictionapi.ThresholdValue{
   496  						Quantity: quantityMustParse("150Mi"),
   497  					},
   498  					GracePeriod: 0,
   499  					MinReclaim: &evictionapi.ThresholdValue{
   500  						Quantity: quantityMustParse("0"),
   501  					},
   502  				},
   503  			},
   504  		},
   505  		"memory-signal-with-grace": {
   506  			thresholds: []evictionapi.Threshold{
   507  				{
   508  					Signal:   evictionapi.SignalMemoryAvailable,
   509  					Operator: evictionapi.OpLessThan,
   510  					Value: evictionapi.ThresholdValue{
   511  						Quantity: quantityMustParse("150Mi"),
   512  					},
   513  					GracePeriod: 10,
   514  					MinReclaim: &evictionapi.ThresholdValue{
   515  						Quantity: quantityMustParse("0"),
   516  					},
   517  				},
   518  			},
   519  			expected: []evictionapi.Threshold{
   520  				{
   521  					Signal:   evictionapi.SignalMemoryAvailable,
   522  					Operator: evictionapi.OpLessThan,
   523  					Value: evictionapi.ThresholdValue{
   524  						Quantity: quantityMustParse("150Mi"),
   525  					},
   526  					GracePeriod: 10,
   527  					MinReclaim: &evictionapi.ThresholdValue{
   528  						Quantity: quantityMustParse("0"),
   529  					},
   530  				},
   531  			},
   532  		},
   533  		"memory-signal-without-grace": {
   534  			thresholds: []evictionapi.Threshold{
   535  				{
   536  					Signal:   evictionapi.SignalMemoryAvailable,
   537  					Operator: evictionapi.OpLessThan,
   538  					Value: evictionapi.ThresholdValue{
   539  						Quantity: quantityMustParse("150Mi"),
   540  					},
   541  					GracePeriod: 0,
   542  					MinReclaim: &evictionapi.ThresholdValue{
   543  						Quantity: quantityMustParse("0"),
   544  					},
   545  				},
   546  			},
   547  			expected: []evictionapi.Threshold{
   548  				{
   549  					Signal:   evictionapi.SignalAllocatableMemoryAvailable,
   550  					Operator: evictionapi.OpLessThan,
   551  					Value: evictionapi.ThresholdValue{
   552  						Quantity: quantityMustParse("150Mi"),
   553  					},
   554  					MinReclaim: &evictionapi.ThresholdValue{
   555  						Quantity: quantityMustParse("0"),
   556  					},
   557  				},
   558  				{
   559  					Signal:   evictionapi.SignalMemoryAvailable,
   560  					Operator: evictionapi.OpLessThan,
   561  					Value: evictionapi.ThresholdValue{
   562  						Quantity: quantityMustParse("150Mi"),
   563  					},
   564  					GracePeriod: 0,
   565  					MinReclaim: &evictionapi.ThresholdValue{
   566  						Quantity: quantityMustParse("0"),
   567  					},
   568  				},
   569  			},
   570  		},
   571  		"memory-signal-without-grace-two-thresholds": {
   572  			thresholds: []evictionapi.Threshold{
   573  				{
   574  					Signal:   evictionapi.SignalMemoryAvailable,
   575  					Operator: evictionapi.OpLessThan,
   576  					Value: evictionapi.ThresholdValue{
   577  						Quantity: quantityMustParse("150Mi"),
   578  					},
   579  					GracePeriod: 0,
   580  					MinReclaim: &evictionapi.ThresholdValue{
   581  						Quantity: quantityMustParse("0"),
   582  					},
   583  				},
   584  				{
   585  					Signal:   evictionapi.SignalMemoryAvailable,
   586  					Operator: evictionapi.OpLessThan,
   587  					Value: evictionapi.ThresholdValue{
   588  						Quantity: quantityMustParse("200Mi"),
   589  					},
   590  					GracePeriod: 0,
   591  					MinReclaim: &evictionapi.ThresholdValue{
   592  						Quantity: quantityMustParse("1Gi"),
   593  					},
   594  				},
   595  			},
   596  			expected: []evictionapi.Threshold{
   597  				{
   598  					Signal:   evictionapi.SignalAllocatableMemoryAvailable,
   599  					Operator: evictionapi.OpLessThan,
   600  					Value: evictionapi.ThresholdValue{
   601  						Quantity: quantityMustParse("150Mi"),
   602  					},
   603  					MinReclaim: &evictionapi.ThresholdValue{
   604  						Quantity: quantityMustParse("0"),
   605  					},
   606  				},
   607  				{
   608  					Signal:   evictionapi.SignalMemoryAvailable,
   609  					Operator: evictionapi.OpLessThan,
   610  					Value: evictionapi.ThresholdValue{
   611  						Quantity: quantityMustParse("150Mi"),
   612  					},
   613  					GracePeriod: 0,
   614  					MinReclaim: &evictionapi.ThresholdValue{
   615  						Quantity: quantityMustParse("0"),
   616  					},
   617  				},
   618  				{
   619  					Signal:   evictionapi.SignalAllocatableMemoryAvailable,
   620  					Operator: evictionapi.OpLessThan,
   621  					Value: evictionapi.ThresholdValue{
   622  						Quantity: quantityMustParse("200Mi"),
   623  					},
   624  					MinReclaim: &evictionapi.ThresholdValue{
   625  						Quantity: quantityMustParse("1Gi"),
   626  					},
   627  				},
   628  				{
   629  					Signal:   evictionapi.SignalMemoryAvailable,
   630  					Operator: evictionapi.OpLessThan,
   631  					Value: evictionapi.ThresholdValue{
   632  						Quantity: quantityMustParse("200Mi"),
   633  					},
   634  					GracePeriod: 0,
   635  					MinReclaim: &evictionapi.ThresholdValue{
   636  						Quantity: quantityMustParse("1Gi"),
   637  					},
   638  				},
   639  			},
   640  		},
   641  	}
   642  	for testName, testCase := range testCases {
   643  		t.Run(testName, func(t *testing.T) {
   644  			if !thresholdsEqual(testCase.expected, addAllocatableThresholds(testCase.thresholds)) {
   645  				t.Errorf("Err not as expected, test: %v, Unexpected data: %s", testName, cmp.Diff(testCase.expected, addAllocatableThresholds(testCase.thresholds)))
   646  			}
   647  		})
   648  	}
   649  }
   650  
   651  func thresholdsEqual(expected []evictionapi.Threshold, actual []evictionapi.Threshold) bool {
   652  	if len(expected) != len(actual) {
   653  		return false
   654  	}
   655  	for _, aThreshold := range expected {
   656  		equal := false
   657  		for _, bThreshold := range actual {
   658  			if thresholdEqual(aThreshold, bThreshold) {
   659  				equal = true
   660  			}
   661  		}
   662  		if !equal {
   663  			return false
   664  		}
   665  	}
   666  	for _, aThreshold := range actual {
   667  		equal := false
   668  		for _, bThreshold := range expected {
   669  			if thresholdEqual(aThreshold, bThreshold) {
   670  				equal = true
   671  			}
   672  		}
   673  		if !equal {
   674  			return false
   675  		}
   676  	}
   677  	return true
   678  }
   679  
   680  func thresholdEqual(a evictionapi.Threshold, b evictionapi.Threshold) bool {
   681  	return a.GracePeriod == b.GracePeriod &&
   682  		a.Operator == b.Operator &&
   683  		a.Signal == b.Signal &&
   684  		compareThresholdValue(*a.MinReclaim, *b.MinReclaim) &&
   685  		compareThresholdValue(a.Value, b.Value)
   686  }
   687  
   688  func TestOrderedByExceedsRequestMemory(t *testing.T) {
   689  	below := newPod("below-requests", -1, []v1.Container{
   690  		newContainer("below-requests", newResourceList("", "200Mi", ""), newResourceList("", "", "")),
   691  	}, nil)
   692  	exceeds := newPod("exceeds-requests", 1, []v1.Container{
   693  		newContainer("exceeds-requests", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
   694  	}, nil)
   695  	stats := map[*v1.Pod]statsapi.PodStats{
   696  		below:   newPodMemoryStats(below, resource.MustParse("199Mi")),   // -1 relative to request
   697  		exceeds: newPodMemoryStats(exceeds, resource.MustParse("101Mi")), // 1 relative to request
   698  	}
   699  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
   700  		result, found := stats[pod]
   701  		return result, found
   702  	}
   703  	pods := []*v1.Pod{below, exceeds}
   704  	orderedBy(exceedMemoryRequests(statsFn)).Sort(pods)
   705  
   706  	expected := []*v1.Pod{exceeds, below}
   707  	for i := range expected {
   708  		if pods[i] != expected[i] {
   709  			t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
   710  		}
   711  	}
   712  }
   713  
   714  func TestOrderedByExceedsRequestDisk(t *testing.T) {
   715  	below := newPod("below-requests", -1, []v1.Container{
   716  		newContainer("below-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("200Mi")}, newResourceList("", "", "")),
   717  	}, nil)
   718  	exceeds := newPod("exceeds-requests", 1, []v1.Container{
   719  		newContainer("exceeds-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("100Mi")}, newResourceList("", "", "")),
   720  	}, nil)
   721  	stats := map[*v1.Pod]statsapi.PodStats{
   722  		below:   newPodDiskStats(below, resource.MustParse("100Mi"), resource.MustParse("99Mi"), resource.MustParse("0Mi")),  // -1 relative to request
   723  		exceeds: newPodDiskStats(exceeds, resource.MustParse("90Mi"), resource.MustParse("11Mi"), resource.MustParse("0Mi")), // 1 relative to request
   724  	}
   725  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
   726  		result, found := stats[pod]
   727  		return result, found
   728  	}
   729  	pods := []*v1.Pod{below, exceeds}
   730  	orderedBy(exceedDiskRequests(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)).Sort(pods)
   731  
   732  	expected := []*v1.Pod{exceeds, below}
   733  	for i := range expected {
   734  		if pods[i] != expected[i] {
   735  			t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
   736  		}
   737  	}
   738  }
   739  
   740  func TestOrderedByPriority(t *testing.T) {
   741  	low := newPod("low-priority", -134, []v1.Container{
   742  		newContainer("low-priority", newResourceList("", "", ""), newResourceList("", "", "")),
   743  	}, nil)
   744  	medium := newPod("medium-priority", 1, []v1.Container{
   745  		newContainer("medium-priority", newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", "")),
   746  	}, nil)
   747  	high := newPod("high-priority", 12534, []v1.Container{
   748  		newContainer("high-priority", newResourceList("200m", "200Mi", ""), newResourceList("200m", "200Mi", "")),
   749  	}, nil)
   750  
   751  	pods := []*v1.Pod{high, medium, low}
   752  	orderedBy(priority).Sort(pods)
   753  
   754  	expected := []*v1.Pod{low, medium, high}
   755  	for i := range expected {
   756  		if pods[i] != expected[i] {
   757  			t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
   758  		}
   759  	}
   760  }
   761  
   762  func TestOrderedbyDisk(t *testing.T) {
   763  	pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{
   764  		newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")),
   765  	}, []v1.Volume{
   766  		newVolume("local-volume", v1.VolumeSource{
   767  			EmptyDir: &v1.EmptyDirVolumeSource{},
   768  		}),
   769  	})
   770  	pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{
   771  		newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")),
   772  	}, []v1.Volume{
   773  		newVolume("local-volume", v1.VolumeSource{
   774  			EmptyDir: &v1.EmptyDirVolumeSource{},
   775  		}),
   776  	})
   777  	pod3 := newPod("burstable-high", defaultPriority, []v1.Container{
   778  		newContainer("burstable-high", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
   779  	}, []v1.Volume{
   780  		newVolume("local-volume", v1.VolumeSource{
   781  			EmptyDir: &v1.EmptyDirVolumeSource{},
   782  		}),
   783  	})
   784  	pod4 := newPod("burstable-low", defaultPriority, []v1.Container{
   785  		newContainer("burstable-low", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
   786  	}, []v1.Volume{
   787  		newVolume("local-volume", v1.VolumeSource{
   788  			EmptyDir: &v1.EmptyDirVolumeSource{},
   789  		}),
   790  	})
   791  	pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{
   792  		newContainer("guaranteed-high", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
   793  	}, []v1.Volume{
   794  		newVolume("local-volume", v1.VolumeSource{
   795  			EmptyDir: &v1.EmptyDirVolumeSource{},
   796  		}),
   797  	})
   798  	pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{
   799  		newContainer("guaranteed-low", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
   800  	}, []v1.Volume{
   801  		newVolume("local-volume", v1.VolumeSource{
   802  			EmptyDir: &v1.EmptyDirVolumeSource{},
   803  		}),
   804  	})
   805  	stats := map[*v1.Pod]statsapi.PodStats{
   806  		pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("150Mi")), // 300Mi - 0 = 300Mi
   807  		pod2: newPodDiskStats(pod2, resource.MustParse("25Mi"), resource.MustParse("25Mi"), resource.MustParse("50Mi")),   // 100Mi - 0 = 100Mi
   808  		pod3: newPodDiskStats(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 350Mi - 100Mi = 250Mi
   809  		pod4: newPodDiskStats(pod4, resource.MustParse("25Mi"), resource.MustParse("35Mi"), resource.MustParse("50Mi")),   // 110Mi - 100Mi = 10Mi
   810  		pod5: newPodDiskStats(pod5, resource.MustParse("225Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 375Mi - 400Mi = -25Mi
   811  		pod6: newPodDiskStats(pod6, resource.MustParse("25Mi"), resource.MustParse("45Mi"), resource.MustParse("50Mi")),   // 120Mi - 400Mi = -280Mi
   812  	}
   813  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
   814  		result, found := stats[pod]
   815  		return result, found
   816  	}
   817  	pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
   818  	orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)).Sort(pods)
   819  	expected := []*v1.Pod{pod1, pod3, pod2, pod4, pod5, pod6}
   820  	for i := range expected {
   821  		if pods[i] != expected[i] {
   822  			t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
   823  		}
   824  	}
   825  }
   826  
   827  func TestOrderedbyInodes(t *testing.T) {
   828  	low := newPod("low", defaultPriority, []v1.Container{
   829  		newContainer("low", newResourceList("", "", ""), newResourceList("", "", "")),
   830  	}, []v1.Volume{
   831  		newVolume("local-volume", v1.VolumeSource{
   832  			EmptyDir: &v1.EmptyDirVolumeSource{},
   833  		}),
   834  	})
   835  	medium := newPod("medium", defaultPriority, []v1.Container{
   836  		newContainer("medium", newResourceList("", "", ""), newResourceList("", "", "")),
   837  	}, []v1.Volume{
   838  		newVolume("local-volume", v1.VolumeSource{
   839  			EmptyDir: &v1.EmptyDirVolumeSource{},
   840  		}),
   841  	})
   842  	high := newPod("high", defaultPriority, []v1.Container{
   843  		newContainer("high", newResourceList("", "", ""), newResourceList("", "", "")),
   844  	}, []v1.Volume{
   845  		newVolume("local-volume", v1.VolumeSource{
   846  			EmptyDir: &v1.EmptyDirVolumeSource{},
   847  		}),
   848  	})
   849  	stats := map[*v1.Pod]statsapi.PodStats{
   850  		low:    newPodInodeStats(low, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("50000")),     // 200000
   851  		medium: newPodInodeStats(medium, resource.MustParse("100000"), resource.MustParse("150000"), resource.MustParse("50000")), // 300000
   852  		high:   newPodInodeStats(high, resource.MustParse("200000"), resource.MustParse("150000"), resource.MustParse("50000")),   // 400000
   853  	}
   854  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
   855  		result, found := stats[pod]
   856  		return result, found
   857  	}
   858  	pods := []*v1.Pod{low, medium, high}
   859  	orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods)
   860  	expected := []*v1.Pod{high, medium, low}
   861  	for i := range expected {
   862  		if pods[i] != expected[i] {
   863  			t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
   864  		}
   865  	}
   866  }
   867  
   868  // TestOrderedByPriorityDisk ensures we order pods by priority and then greediest resource consumer
   869  func TestOrderedByPriorityDisk(t *testing.T) {
   870  	pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{
   871  		newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
   872  	}, []v1.Volume{
   873  		newVolume("local-volume", v1.VolumeSource{
   874  			EmptyDir: &v1.EmptyDirVolumeSource{},
   875  		}),
   876  	})
   877  	pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{
   878  		newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
   879  	}, []v1.Volume{
   880  		newVolume("local-volume", v1.VolumeSource{
   881  			EmptyDir: &v1.EmptyDirVolumeSource{},
   882  		}),
   883  	})
   884  	pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{
   885  		newContainer("above-requests-high-priority-high-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")),
   886  	}, []v1.Volume{
   887  		newVolume("local-volume", v1.VolumeSource{
   888  			EmptyDir: &v1.EmptyDirVolumeSource{},
   889  		}),
   890  	})
   891  	pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{
   892  		newContainer("above-requests-high-priority-low-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")),
   893  	}, []v1.Volume{
   894  		newVolume("local-volume", v1.VolumeSource{
   895  			EmptyDir: &v1.EmptyDirVolumeSource{},
   896  		}),
   897  	})
   898  	pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{
   899  		newContainer("below-requests-low-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
   900  	}, []v1.Volume{
   901  		newVolume("local-volume", v1.VolumeSource{
   902  			EmptyDir: &v1.EmptyDirVolumeSource{},
   903  		}),
   904  	})
   905  	pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{
   906  		newContainer("below-requests-low-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
   907  	}, []v1.Volume{
   908  		newVolume("local-volume", v1.VolumeSource{
   909  			EmptyDir: &v1.EmptyDirVolumeSource{},
   910  		}),
   911  	})
   912  	pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{
   913  		newContainer("below-requests-high-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
   914  	}, []v1.Volume{
   915  		newVolume("local-volume", v1.VolumeSource{
   916  			EmptyDir: &v1.EmptyDirVolumeSource{},
   917  		}),
   918  	})
   919  	pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{
   920  		newContainer("below-requests-high-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
   921  	}, []v1.Volume{
   922  		newVolume("local-volume", v1.VolumeSource{
   923  			EmptyDir: &v1.EmptyDirVolumeSource{},
   924  		}),
   925  	})
   926  	stats := map[*v1.Pod]statsapi.PodStats{
   927  		pod1: newPodDiskStats(pod1, resource.MustParse("200Mi"), resource.MustParse("100Mi"), resource.MustParse("200Mi")), // 500 relative to request
   928  		pod2: newPodDiskStats(pod2, resource.MustParse("10Mi"), resource.MustParse("10Mi"), resource.MustParse("30Mi")),    // 50 relative to request
   929  		pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("250Mi")), // 500 relative to request
   930  		pod4: newPodDiskStats(pod4, resource.MustParse("90Mi"), resource.MustParse("50Mi"), resource.MustParse("10Mi")),    // 50 relative to request
   931  		pod5: newPodDiskStats(pod5, resource.MustParse("500Mi"), resource.MustParse("200Mi"), resource.MustParse("100Mi")), // -200 relative to request
   932  		pod6: newPodDiskStats(pod6, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")),   // -800 relative to request
   933  		pod7: newPodDiskStats(pod7, resource.MustParse("250Mi"), resource.MustParse("500Mi"), resource.MustParse("50Mi")),  // -200 relative to request
   934  		pod8: newPodDiskStats(pod8, resource.MustParse("100Mi"), resource.MustParse("60Mi"), resource.MustParse("40Mi")),   // -800 relative to request
   935  	}
   936  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
   937  		result, found := stats[pod]
   938  		return result, found
   939  	}
   940  	pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1}
   941  	expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8}
   942  	fsStatsToMeasure := []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}
   943  	orderedBy(exceedDiskRequests(statsFn, fsStatsToMeasure, v1.ResourceEphemeralStorage), priority, disk(statsFn, fsStatsToMeasure, v1.ResourceEphemeralStorage)).Sort(pods)
   944  	for i := range expected {
   945  		if pods[i] != expected[i] {
   946  			t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
   947  		}
   948  	}
   949  }
   950  
   951  // TestOrderedByPriorityInodes ensures we order pods by priority and then greediest resource consumer
   952  func TestOrderedByPriorityInodes(t *testing.T) {
   953  	pod1 := newPod("low-priority-high-usage", lowPriority, []v1.Container{
   954  		newContainer("low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
   955  	}, []v1.Volume{
   956  		newVolume("local-volume", v1.VolumeSource{
   957  			EmptyDir: &v1.EmptyDirVolumeSource{},
   958  		}),
   959  	})
   960  	pod2 := newPod("low-priority-low-usage", lowPriority, []v1.Container{
   961  		newContainer("low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
   962  	}, []v1.Volume{
   963  		newVolume("local-volume", v1.VolumeSource{
   964  			EmptyDir: &v1.EmptyDirVolumeSource{},
   965  		}),
   966  	})
   967  	pod3 := newPod("high-priority-high-usage", highPriority, []v1.Container{
   968  		newContainer("high-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
   969  	}, []v1.Volume{
   970  		newVolume("local-volume", v1.VolumeSource{
   971  			EmptyDir: &v1.EmptyDirVolumeSource{},
   972  		}),
   973  	})
   974  	pod4 := newPod("high-priority-low-usage", highPriority, []v1.Container{
   975  		newContainer("high-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
   976  	}, []v1.Volume{
   977  		newVolume("local-volume", v1.VolumeSource{
   978  			EmptyDir: &v1.EmptyDirVolumeSource{},
   979  		}),
   980  	})
   981  	stats := map[*v1.Pod]statsapi.PodStats{
   982  		pod1: newPodInodeStats(pod1, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("250000")), // 400000
   983  		pod2: newPodInodeStats(pod2, resource.MustParse("60000"), resource.MustParse("30000"), resource.MustParse("10000")),   // 100000
   984  		pod3: newPodInodeStats(pod3, resource.MustParse("150000"), resource.MustParse("150000"), resource.MustParse("50000")), // 350000
   985  		pod4: newPodInodeStats(pod4, resource.MustParse("10000"), resource.MustParse("40000"), resource.MustParse("100000")),  // 150000
   986  	}
   987  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
   988  		result, found := stats[pod]
   989  		return result, found
   990  	}
   991  	pods := []*v1.Pod{pod4, pod3, pod2, pod1}
   992  	orderedBy(priority, disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods)
   993  	expected := []*v1.Pod{pod1, pod2, pod3, pod4}
   994  	for i := range expected {
   995  		if pods[i] != expected[i] {
   996  			t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
   997  		}
   998  	}
   999  }
  1000  
  1001  // TestOrderedByMemory ensures we order pods by greediest memory consumer relative to request.
  1002  func TestOrderedByMemory(t *testing.T) {
  1003  	pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{
  1004  		newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")),
  1005  	}, nil)
  1006  	pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{
  1007  		newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")),
  1008  	}, nil)
  1009  	pod3 := newPod("burstable-high", defaultPriority, []v1.Container{
  1010  		newContainer("burstable-high", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")),
  1011  	}, nil)
  1012  	pod4 := newPod("burstable-low", defaultPriority, []v1.Container{
  1013  		newContainer("burstable-low", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")),
  1014  	}, nil)
  1015  	pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{
  1016  		newContainer("guaranteed-high", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")),
  1017  	}, nil)
  1018  	pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{
  1019  		newContainer("guaranteed-low", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")),
  1020  	}, nil)
  1021  	stats := map[*v1.Pod]statsapi.PodStats{
  1022  		pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
  1023  		pod2: newPodMemoryStats(pod2, resource.MustParse("300Mi")), // 300 relative to request
  1024  		pod3: newPodMemoryStats(pod3, resource.MustParse("800Mi")), // 700 relative to request
  1025  		pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")), // 200 relative to request
  1026  		pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
  1027  		pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
  1028  	}
  1029  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  1030  		result, found := stats[pod]
  1031  		return result, found
  1032  	}
  1033  	pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
  1034  	orderedBy(memory(statsFn)).Sort(pods)
  1035  	expected := []*v1.Pod{pod3, pod1, pod2, pod4, pod5, pod6}
  1036  	for i := range expected {
  1037  		if pods[i] != expected[i] {
  1038  			t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  1039  		}
  1040  	}
  1041  }
  1042  
  1043  // TestOrderedByPriorityMemory ensures we order by priority and then memory consumption relative to request.
  1044  func TestOrderedByPriorityMemory(t *testing.T) {
  1045  	pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{
  1046  		newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  1047  	}, nil)
  1048  	pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{
  1049  		newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  1050  	}, nil)
  1051  	pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{
  1052  		newContainer("above-requests-high-priority-high-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
  1053  	}, nil)
  1054  	pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{
  1055  		newContainer("above-requests-high-priority-low-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
  1056  	}, nil)
  1057  	pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{
  1058  		newContainer("below-requests-low-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  1059  	}, nil)
  1060  	pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{
  1061  		newContainer("below-requests-low-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  1062  	}, nil)
  1063  	pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{
  1064  		newContainer("below-requests-high-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  1065  	}, nil)
  1066  	pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{
  1067  		newContainer("below-requests-high-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  1068  	}, nil)
  1069  	stats := map[*v1.Pod]statsapi.PodStats{
  1070  		pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
  1071  		pod2: newPodMemoryStats(pod2, resource.MustParse("50Mi")),  // 50 relative to request
  1072  		pod3: newPodMemoryStats(pod3, resource.MustParse("600Mi")), // 500 relative to request
  1073  		pod4: newPodMemoryStats(pod4, resource.MustParse("150Mi")), // 50 relative to request
  1074  		pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
  1075  		pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
  1076  		pod7: newPodMemoryStats(pod7, resource.MustParse("800Mi")), // -200 relative to request
  1077  		pod8: newPodMemoryStats(pod8, resource.MustParse("200Mi")), // -800 relative to request
  1078  	}
  1079  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  1080  		result, found := stats[pod]
  1081  		return result, found
  1082  	}
  1083  	pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1}
  1084  	expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8}
  1085  	orderedBy(exceedMemoryRequests(statsFn), priority, memory(statsFn)).Sort(pods)
  1086  	for i := range expected {
  1087  		if pods[i] != expected[i] {
  1088  			t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  1089  		}
  1090  	}
  1091  }
  1092  
  1093  // TestOrderedByPriorityProcess ensures we order by priority and then process consumption relative to request.
  1094  func TestOrderedByPriorityProcess(t *testing.T) {
  1095  	pod1 := newPod("low-priority-high-usage", lowPriority, nil, nil)
  1096  	pod2 := newPod("low-priority-low-usage", lowPriority, nil, nil)
  1097  	pod3 := newPod("high-priority-high-usage", highPriority, nil, nil)
  1098  	pod4 := newPod("high-priority-low-usage", highPriority, nil, nil)
  1099  	stats := map[*v1.Pod]statsapi.PodStats{
  1100  		pod1: newPodProcessStats(pod1, 20),
  1101  		pod2: newPodProcessStats(pod2, 6),
  1102  		pod3: newPodProcessStats(pod3, 20),
  1103  		pod4: newPodProcessStats(pod4, 5),
  1104  	}
  1105  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  1106  		result, found := stats[pod]
  1107  		return result, found
  1108  	}
  1109  	pods := []*v1.Pod{pod4, pod3, pod2, pod1}
  1110  	expected := []*v1.Pod{pod1, pod2, pod3, pod4}
  1111  	orderedBy(priority, process(statsFn)).Sort(pods)
  1112  	for i := range expected {
  1113  		if pods[i] != expected[i] {
  1114  			t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  1115  		}
  1116  	}
  1117  }
  1118  
  1119  func TestSortByEvictionPriority(t *testing.T) {
  1120  	for _, tc := range []struct {
  1121  		name       string
  1122  		thresholds []evictionapi.Threshold
  1123  		expected   []evictionapi.Threshold
  1124  	}{
  1125  		{
  1126  			name:       "empty threshold list",
  1127  			thresholds: []evictionapi.Threshold{},
  1128  			expected:   []evictionapi.Threshold{},
  1129  		},
  1130  		{
  1131  			name: "memory first",
  1132  			thresholds: []evictionapi.Threshold{
  1133  				{
  1134  					Signal: evictionapi.SignalNodeFsAvailable,
  1135  				},
  1136  				{
  1137  					Signal: evictionapi.SignalPIDAvailable,
  1138  				},
  1139  				{
  1140  					Signal: evictionapi.SignalMemoryAvailable,
  1141  				},
  1142  			},
  1143  			expected: []evictionapi.Threshold{
  1144  				{
  1145  					Signal: evictionapi.SignalMemoryAvailable,
  1146  				},
  1147  				{
  1148  					Signal: evictionapi.SignalNodeFsAvailable,
  1149  				},
  1150  				{
  1151  					Signal: evictionapi.SignalPIDAvailable,
  1152  				},
  1153  			},
  1154  		},
  1155  		{
  1156  			name: "allocatable memory first",
  1157  			thresholds: []evictionapi.Threshold{
  1158  				{
  1159  					Signal: evictionapi.SignalNodeFsAvailable,
  1160  				},
  1161  				{
  1162  					Signal: evictionapi.SignalPIDAvailable,
  1163  				},
  1164  				{
  1165  					Signal: evictionapi.SignalAllocatableMemoryAvailable,
  1166  				},
  1167  			},
  1168  			expected: []evictionapi.Threshold{
  1169  				{
  1170  					Signal: evictionapi.SignalAllocatableMemoryAvailable,
  1171  				},
  1172  				{
  1173  					Signal: evictionapi.SignalNodeFsAvailable,
  1174  				},
  1175  				{
  1176  					Signal: evictionapi.SignalPIDAvailable,
  1177  				},
  1178  			},
  1179  		},
  1180  	} {
  1181  		t.Run(tc.name, func(t *testing.T) {
  1182  			sort.Sort(byEvictionPriority(tc.thresholds))
  1183  			for i := range tc.expected {
  1184  				if tc.thresholds[i].Signal != tc.expected[i].Signal {
  1185  					t.Errorf("At index %d, expected threshold with signal %s, but got %s", i, tc.expected[i].Signal, tc.thresholds[i].Signal)
  1186  				}
  1187  			}
  1188  
  1189  		})
  1190  	}
  1191  }
  1192  
  1193  type fakeSummaryProvider struct {
  1194  	result *statsapi.Summary
  1195  }
  1196  
  1197  func (f *fakeSummaryProvider) Get(ctx context.Context, updateStats bool) (*statsapi.Summary, error) {
  1198  	return f.result, nil
  1199  }
  1200  
  1201  func (f *fakeSummaryProvider) GetCPUAndMemoryStats(ctx context.Context) (*statsapi.Summary, error) {
  1202  	return f.result, nil
  1203  }
  1204  
  1205  // newPodStats returns a pod stat where each container is using the specified working set
  1206  // each pod must have a Name, UID, Namespace
  1207  func newPodStats(pod *v1.Pod, podWorkingSetBytes uint64) statsapi.PodStats {
  1208  	return statsapi.PodStats{
  1209  		PodRef: statsapi.PodReference{
  1210  			Name:      pod.Name,
  1211  			Namespace: pod.Namespace,
  1212  			UID:       string(pod.UID),
  1213  		},
  1214  		Memory: &statsapi.MemoryStats{
  1215  			WorkingSetBytes: &podWorkingSetBytes,
  1216  		},
  1217  	}
  1218  }
  1219  
  1220  func TestMakeSignalObservations(t *testing.T) {
  1221  	podMaker := func(name, namespace, uid string, numContainers int) *v1.Pod {
  1222  		pod := &v1.Pod{}
  1223  		pod.Name = name
  1224  		pod.Namespace = namespace
  1225  		pod.UID = types.UID(uid)
  1226  		pod.Spec = v1.PodSpec{}
  1227  		for i := 0; i < numContainers; i++ {
  1228  			pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{
  1229  				Name: fmt.Sprintf("ctr%v", i),
  1230  			})
  1231  		}
  1232  		return pod
  1233  	}
  1234  	nodeAvailableBytes := uint64(1024 * 1024 * 1024)
  1235  	nodeWorkingSetBytes := uint64(1024 * 1024 * 1024)
  1236  	allocatableMemoryCapacity := uint64(5 * 1024 * 1024 * 1024)
  1237  	imageFsAvailableBytes := uint64(1024 * 1024)
  1238  	imageFsCapacityBytes := uint64(1024 * 1024 * 2)
  1239  	nodeFsAvailableBytes := uint64(1024)
  1240  	nodeFsCapacityBytes := uint64(1024 * 2)
  1241  	imageFsInodesFree := uint64(1024)
  1242  	imageFsInodes := uint64(1024 * 1024)
  1243  	nodeFsInodesFree := uint64(1024)
  1244  	nodeFsInodes := uint64(1024 * 1024)
  1245  	containerFsAvailableBytes := uint64(1024 * 1024 * 2)
  1246  	containerFsCapacityBytes := uint64(1024 * 1024 * 8)
  1247  	containerFsInodesFree := uint64(1024 * 2)
  1248  	containerFsInodes := uint64(1024 * 2)
  1249  	maxPID := int64(255816)
  1250  	numberOfRunningProcesses := int64(1000)
  1251  	fakeStats := &statsapi.Summary{
  1252  		Node: statsapi.NodeStats{
  1253  			Memory: &statsapi.MemoryStats{
  1254  				AvailableBytes:  &nodeAvailableBytes,
  1255  				WorkingSetBytes: &nodeWorkingSetBytes,
  1256  			},
  1257  			Runtime: &statsapi.RuntimeStats{
  1258  				ImageFs: &statsapi.FsStats{
  1259  					AvailableBytes: &imageFsAvailableBytes,
  1260  					CapacityBytes:  &imageFsCapacityBytes,
  1261  					InodesFree:     &imageFsInodesFree,
  1262  					Inodes:         &imageFsInodes,
  1263  				},
  1264  				ContainerFs: &statsapi.FsStats{
  1265  					AvailableBytes: &containerFsAvailableBytes,
  1266  					CapacityBytes:  &containerFsCapacityBytes,
  1267  					InodesFree:     &containerFsInodesFree,
  1268  					Inodes:         &containerFsInodes,
  1269  				},
  1270  			},
  1271  			Rlimit: &statsapi.RlimitStats{
  1272  				MaxPID:                &maxPID,
  1273  				NumOfRunningProcesses: &numberOfRunningProcesses,
  1274  			},
  1275  			Fs: &statsapi.FsStats{
  1276  				AvailableBytes: &nodeFsAvailableBytes,
  1277  				CapacityBytes:  &nodeFsCapacityBytes,
  1278  				InodesFree:     &nodeFsInodesFree,
  1279  				Inodes:         &nodeFsInodes,
  1280  			},
  1281  			SystemContainers: []statsapi.ContainerStats{
  1282  				{
  1283  					Name: statsapi.SystemContainerPods,
  1284  					Memory: &statsapi.MemoryStats{
  1285  						AvailableBytes:  &nodeAvailableBytes,
  1286  						WorkingSetBytes: &nodeWorkingSetBytes,
  1287  					},
  1288  				},
  1289  			},
  1290  		},
  1291  		Pods: []statsapi.PodStats{},
  1292  	}
  1293  	pods := []*v1.Pod{
  1294  		podMaker("pod1", "ns1", "uuid1", 1),
  1295  		podMaker("pod1", "ns2", "uuid2", 1),
  1296  		podMaker("pod3", "ns3", "uuid3", 1),
  1297  	}
  1298  	podWorkingSetBytes := uint64(1024 * 1024 * 1024)
  1299  	for _, pod := range pods {
  1300  		fakeStats.Pods = append(fakeStats.Pods, newPodStats(pod, podWorkingSetBytes))
  1301  	}
  1302  	res := quantityMustParse("5Gi")
  1303  	// Allocatable thresholds are always 100%.  Verify that Threshold == Capacity.
  1304  	if res.CmpInt64(int64(allocatableMemoryCapacity)) != 0 {
  1305  		t.Errorf("Expected Threshold %v to be equal to value %v", res.Value(), allocatableMemoryCapacity)
  1306  	}
  1307  	actualObservations, statsFunc := makeSignalObservations(fakeStats)
  1308  	allocatableMemQuantity, found := actualObservations[evictionapi.SignalAllocatableMemoryAvailable]
  1309  	if !found {
  1310  		t.Errorf("Expected allocatable memory observation, but didnt find one")
  1311  	}
  1312  	if expectedBytes := int64(nodeAvailableBytes); allocatableMemQuantity.available.Value() != expectedBytes {
  1313  		t.Errorf("Expected %v, actual: %v", expectedBytes, allocatableMemQuantity.available.Value())
  1314  	}
  1315  	if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); allocatableMemQuantity.capacity.Value() != expectedBytes {
  1316  		t.Errorf("Expected %v, actual: %v", expectedBytes, allocatableMemQuantity.capacity.Value())
  1317  	}
  1318  	memQuantity, found := actualObservations[evictionapi.SignalMemoryAvailable]
  1319  	if !found {
  1320  		t.Error("Expected available memory observation")
  1321  	}
  1322  	if expectedBytes := int64(nodeAvailableBytes); memQuantity.available.Value() != expectedBytes {
  1323  		t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.available.Value())
  1324  	}
  1325  	if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); memQuantity.capacity.Value() != expectedBytes {
  1326  		t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.capacity.Value())
  1327  	}
  1328  	nodeFsQuantity, found := actualObservations[evictionapi.SignalNodeFsAvailable]
  1329  	if !found {
  1330  		t.Error("Expected available nodefs observation")
  1331  	}
  1332  	if expectedBytes := int64(nodeFsAvailableBytes); nodeFsQuantity.available.Value() != expectedBytes {
  1333  		t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.available.Value())
  1334  	}
  1335  	if expectedBytes := int64(nodeFsCapacityBytes); nodeFsQuantity.capacity.Value() != expectedBytes {
  1336  		t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.capacity.Value())
  1337  	}
  1338  	nodeFsInodesQuantity, found := actualObservations[evictionapi.SignalNodeFsInodesFree]
  1339  	if !found {
  1340  		t.Error("Expected inodes free nodefs observation")
  1341  	}
  1342  	if expected := int64(nodeFsInodesFree); nodeFsInodesQuantity.available.Value() != expected {
  1343  		t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.available.Value())
  1344  	}
  1345  	if expected := int64(nodeFsInodes); nodeFsInodesQuantity.capacity.Value() != expected {
  1346  		t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.capacity.Value())
  1347  	}
  1348  	imageFsQuantity, found := actualObservations[evictionapi.SignalImageFsAvailable]
  1349  	if !found {
  1350  		t.Error("Expected available imagefs observation")
  1351  	}
  1352  	if expectedBytes := int64(imageFsAvailableBytes); imageFsQuantity.available.Value() != expectedBytes {
  1353  		t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.available.Value())
  1354  	}
  1355  	if expectedBytes := int64(imageFsCapacityBytes); imageFsQuantity.capacity.Value() != expectedBytes {
  1356  		t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.capacity.Value())
  1357  	}
  1358  	containerFsQuantity, found := actualObservations[evictionapi.SignalContainerFsAvailable]
  1359  	if !found {
  1360  		t.Error("Expected available containerfs observation")
  1361  	}
  1362  	if expectedBytes := int64(containerFsAvailableBytes); containerFsQuantity.available.Value() != expectedBytes {
  1363  		t.Errorf("Expected %v, actual: %v", expectedBytes, containerFsQuantity.available.Value())
  1364  	}
  1365  	if expectedBytes := int64(containerFsCapacityBytes); containerFsQuantity.capacity.Value() != expectedBytes {
  1366  		t.Errorf("Expected %v, actual: %v", expectedBytes, containerFsQuantity.capacity.Value())
  1367  	}
  1368  	imageFsInodesQuantity, found := actualObservations[evictionapi.SignalImageFsInodesFree]
  1369  	if !found {
  1370  		t.Error("Expected inodes free imagefs observation")
  1371  	}
  1372  	if expected := int64(imageFsInodesFree); imageFsInodesQuantity.available.Value() != expected {
  1373  		t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.available.Value())
  1374  	}
  1375  	if expected := int64(imageFsInodes); imageFsInodesQuantity.capacity.Value() != expected {
  1376  		t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.capacity.Value())
  1377  	}
  1378  	containerFsInodesQuantity, found := actualObservations[evictionapi.SignalContainerFsInodesFree]
  1379  	if !found {
  1380  		t.Error("Expected indoes free containerfs observation")
  1381  	}
  1382  	if expected := int64(containerFsInodesFree); containerFsInodesQuantity.available.Value() != expected {
  1383  		t.Errorf("Expected %v, actual: %v", expected, containerFsInodesQuantity.available.Value())
  1384  	}
  1385  	if expected := int64(containerFsInodes); containerFsInodesQuantity.capacity.Value() != expected {
  1386  		t.Errorf("Expected %v, actual: %v", expected, containerFsInodesQuantity.capacity.Value())
  1387  	}
  1388  
  1389  	pidQuantity, found := actualObservations[evictionapi.SignalPIDAvailable]
  1390  	if !found {
  1391  		t.Error("Expected available memory observation")
  1392  	}
  1393  	if expectedBytes := int64(maxPID); pidQuantity.capacity.Value() != expectedBytes {
  1394  		t.Errorf("Expected %v, actual: %v", expectedBytes, pidQuantity.capacity.Value())
  1395  	}
  1396  	if expectedBytes := int64(maxPID - numberOfRunningProcesses); pidQuantity.available.Value() != expectedBytes {
  1397  		t.Errorf("Expected %v, actual: %v", expectedBytes, pidQuantity.available.Value())
  1398  	}
  1399  	for _, pod := range pods {
  1400  		podStats, found := statsFunc(pod)
  1401  		if !found {
  1402  			t.Errorf("Pod stats were not found for pod %v", pod.UID)
  1403  		}
  1404  		if *podStats.Memory.WorkingSetBytes != podWorkingSetBytes {
  1405  			t.Errorf("Pod working set expected %v, actual: %v", podWorkingSetBytes, *podStats.Memory.WorkingSetBytes)
  1406  		}
  1407  	}
  1408  }
  1409  
  1410  func TestThresholdsMet(t *testing.T) {
  1411  	hardThreshold := evictionapi.Threshold{
  1412  		Signal:   evictionapi.SignalMemoryAvailable,
  1413  		Operator: evictionapi.OpLessThan,
  1414  		Value: evictionapi.ThresholdValue{
  1415  			Quantity: quantityMustParse("1Gi"),
  1416  		},
  1417  		MinReclaim: &evictionapi.ThresholdValue{
  1418  			Quantity: quantityMustParse("500Mi"),
  1419  		},
  1420  	}
  1421  	testCases := map[string]struct {
  1422  		enforceMinReclaim bool
  1423  		thresholds        []evictionapi.Threshold
  1424  		observations      signalObservations
  1425  		result            []evictionapi.Threshold
  1426  	}{
  1427  		"empty": {
  1428  			enforceMinReclaim: false,
  1429  			thresholds:        []evictionapi.Threshold{},
  1430  			observations:      signalObservations{},
  1431  			result:            []evictionapi.Threshold{},
  1432  		},
  1433  		"threshold-met-memory": {
  1434  			enforceMinReclaim: false,
  1435  			thresholds:        []evictionapi.Threshold{hardThreshold},
  1436  			observations: signalObservations{
  1437  				evictionapi.SignalMemoryAvailable: signalObservation{
  1438  					available: quantityMustParse("500Mi"),
  1439  				},
  1440  			},
  1441  			result: []evictionapi.Threshold{hardThreshold},
  1442  		},
  1443  		"threshold-not-met": {
  1444  			enforceMinReclaim: false,
  1445  			thresholds:        []evictionapi.Threshold{hardThreshold},
  1446  			observations: signalObservations{
  1447  				evictionapi.SignalMemoryAvailable: signalObservation{
  1448  					available: quantityMustParse("2Gi"),
  1449  				},
  1450  			},
  1451  			result: []evictionapi.Threshold{},
  1452  		},
  1453  		"threshold-met-with-min-reclaim": {
  1454  			enforceMinReclaim: true,
  1455  			thresholds:        []evictionapi.Threshold{hardThreshold},
  1456  			observations: signalObservations{
  1457  				evictionapi.SignalMemoryAvailable: signalObservation{
  1458  					available: quantityMustParse("1.05Gi"),
  1459  				},
  1460  			},
  1461  			result: []evictionapi.Threshold{hardThreshold},
  1462  		},
  1463  		"threshold-not-met-with-min-reclaim": {
  1464  			enforceMinReclaim: true,
  1465  			thresholds:        []evictionapi.Threshold{hardThreshold},
  1466  			observations: signalObservations{
  1467  				evictionapi.SignalMemoryAvailable: signalObservation{
  1468  					available: quantityMustParse("2Gi"),
  1469  				},
  1470  			},
  1471  			result: []evictionapi.Threshold{},
  1472  		},
  1473  	}
  1474  	for testName, testCase := range testCases {
  1475  		actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinReclaim)
  1476  		if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1477  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1478  		}
  1479  	}
  1480  }
  1481  
  1482  func TestThresholdsUpdatedStats(t *testing.T) {
  1483  	updatedThreshold := evictionapi.Threshold{
  1484  		Signal: evictionapi.SignalMemoryAvailable,
  1485  	}
  1486  	locationUTC, err := time.LoadLocation("UTC")
  1487  	if err != nil {
  1488  		t.Error(err)
  1489  		return
  1490  	}
  1491  	testCases := map[string]struct {
  1492  		thresholds   []evictionapi.Threshold
  1493  		observations signalObservations
  1494  		last         signalObservations
  1495  		result       []evictionapi.Threshold
  1496  	}{
  1497  		"empty": {
  1498  			thresholds:   []evictionapi.Threshold{},
  1499  			observations: signalObservations{},
  1500  			last:         signalObservations{},
  1501  			result:       []evictionapi.Threshold{},
  1502  		},
  1503  		"no-time": {
  1504  			thresholds: []evictionapi.Threshold{updatedThreshold},
  1505  			observations: signalObservations{
  1506  				evictionapi.SignalMemoryAvailable: signalObservation{},
  1507  			},
  1508  			last:   signalObservations{},
  1509  			result: []evictionapi.Threshold{updatedThreshold},
  1510  		},
  1511  		"no-last-observation": {
  1512  			thresholds: []evictionapi.Threshold{updatedThreshold},
  1513  			observations: signalObservations{
  1514  				evictionapi.SignalMemoryAvailable: signalObservation{
  1515  					time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1516  				},
  1517  			},
  1518  			last:   signalObservations{},
  1519  			result: []evictionapi.Threshold{updatedThreshold},
  1520  		},
  1521  		"time-machine": {
  1522  			thresholds: []evictionapi.Threshold{updatedThreshold},
  1523  			observations: signalObservations{
  1524  				evictionapi.SignalMemoryAvailable: signalObservation{
  1525  					time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1526  				},
  1527  			},
  1528  			last: signalObservations{
  1529  				evictionapi.SignalMemoryAvailable: signalObservation{
  1530  					time: metav1.Date(2016, 1, 1, 0, 1, 0, 0, locationUTC),
  1531  				},
  1532  			},
  1533  			result: []evictionapi.Threshold{},
  1534  		},
  1535  		"same-observation": {
  1536  			thresholds: []evictionapi.Threshold{updatedThreshold},
  1537  			observations: signalObservations{
  1538  				evictionapi.SignalMemoryAvailable: signalObservation{
  1539  					time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1540  				},
  1541  			},
  1542  			last: signalObservations{
  1543  				evictionapi.SignalMemoryAvailable: signalObservation{
  1544  					time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1545  				},
  1546  			},
  1547  			result: []evictionapi.Threshold{},
  1548  		},
  1549  		"new-observation": {
  1550  			thresholds: []evictionapi.Threshold{updatedThreshold},
  1551  			observations: signalObservations{
  1552  				evictionapi.SignalMemoryAvailable: signalObservation{
  1553  					time: metav1.Date(2016, 1, 1, 0, 1, 0, 0, locationUTC),
  1554  				},
  1555  			},
  1556  			last: signalObservations{
  1557  				evictionapi.SignalMemoryAvailable: signalObservation{
  1558  					time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1559  				},
  1560  			},
  1561  			result: []evictionapi.Threshold{updatedThreshold},
  1562  		},
  1563  	}
  1564  	for testName, testCase := range testCases {
  1565  		actual := thresholdsUpdatedStats(testCase.thresholds, testCase.observations, testCase.last)
  1566  		if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1567  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1568  		}
  1569  	}
  1570  }
  1571  
  1572  func TestPercentageThresholdsMet(t *testing.T) {
  1573  	specificThresholds := []evictionapi.Threshold{
  1574  		{
  1575  			Signal:   evictionapi.SignalMemoryAvailable,
  1576  			Operator: evictionapi.OpLessThan,
  1577  			Value: evictionapi.ThresholdValue{
  1578  				Percentage: 0.2,
  1579  			},
  1580  			MinReclaim: &evictionapi.ThresholdValue{
  1581  				Percentage: 0.05,
  1582  			},
  1583  		},
  1584  		{
  1585  			Signal:   evictionapi.SignalNodeFsAvailable,
  1586  			Operator: evictionapi.OpLessThan,
  1587  			Value: evictionapi.ThresholdValue{
  1588  				Percentage: 0.3,
  1589  			},
  1590  		},
  1591  	}
  1592  
  1593  	testCases := map[string]struct {
  1594  		enforceMinRelaim bool
  1595  		thresholds       []evictionapi.Threshold
  1596  		observations     signalObservations
  1597  		result           []evictionapi.Threshold
  1598  	}{
  1599  		"BothMet": {
  1600  			enforceMinRelaim: false,
  1601  			thresholds:       specificThresholds,
  1602  			observations: signalObservations{
  1603  				evictionapi.SignalMemoryAvailable: signalObservation{
  1604  					available: quantityMustParse("100Mi"),
  1605  					capacity:  quantityMustParse("1000Mi"),
  1606  				},
  1607  				evictionapi.SignalNodeFsAvailable: signalObservation{
  1608  					available: quantityMustParse("100Gi"),
  1609  					capacity:  quantityMustParse("1000Gi"),
  1610  				},
  1611  			},
  1612  			result: specificThresholds,
  1613  		},
  1614  		"NoneMet": {
  1615  			enforceMinRelaim: false,
  1616  			thresholds:       specificThresholds,
  1617  			observations: signalObservations{
  1618  				evictionapi.SignalMemoryAvailable: signalObservation{
  1619  					available: quantityMustParse("300Mi"),
  1620  					capacity:  quantityMustParse("1000Mi"),
  1621  				},
  1622  				evictionapi.SignalNodeFsAvailable: signalObservation{
  1623  					available: quantityMustParse("400Gi"),
  1624  					capacity:  quantityMustParse("1000Gi"),
  1625  				},
  1626  			},
  1627  			result: []evictionapi.Threshold{},
  1628  		},
  1629  		"DiskMet": {
  1630  			enforceMinRelaim: false,
  1631  			thresholds:       specificThresholds,
  1632  			observations: signalObservations{
  1633  				evictionapi.SignalMemoryAvailable: signalObservation{
  1634  					available: quantityMustParse("300Mi"),
  1635  					capacity:  quantityMustParse("1000Mi"),
  1636  				},
  1637  				evictionapi.SignalNodeFsAvailable: signalObservation{
  1638  					available: quantityMustParse("100Gi"),
  1639  					capacity:  quantityMustParse("1000Gi"),
  1640  				},
  1641  			},
  1642  			result: []evictionapi.Threshold{specificThresholds[1]},
  1643  		},
  1644  		"MemoryMet": {
  1645  			enforceMinRelaim: false,
  1646  			thresholds:       specificThresholds,
  1647  			observations: signalObservations{
  1648  				evictionapi.SignalMemoryAvailable: signalObservation{
  1649  					available: quantityMustParse("100Mi"),
  1650  					capacity:  quantityMustParse("1000Mi"),
  1651  				},
  1652  				evictionapi.SignalNodeFsAvailable: signalObservation{
  1653  					available: quantityMustParse("400Gi"),
  1654  					capacity:  quantityMustParse("1000Gi"),
  1655  				},
  1656  			},
  1657  			result: []evictionapi.Threshold{specificThresholds[0]},
  1658  		},
  1659  		"MemoryMetWithMinReclaim": {
  1660  			enforceMinRelaim: true,
  1661  			thresholds:       specificThresholds,
  1662  			observations: signalObservations{
  1663  				evictionapi.SignalMemoryAvailable: signalObservation{
  1664  					available: quantityMustParse("225Mi"),
  1665  					capacity:  quantityMustParse("1000Mi"),
  1666  				},
  1667  			},
  1668  			result: []evictionapi.Threshold{specificThresholds[0]},
  1669  		},
  1670  		"MemoryNotMetWithMinReclaim": {
  1671  			enforceMinRelaim: true,
  1672  			thresholds:       specificThresholds,
  1673  			observations: signalObservations{
  1674  				evictionapi.SignalMemoryAvailable: signalObservation{
  1675  					available: quantityMustParse("300Mi"),
  1676  					capacity:  quantityMustParse("1000Mi"),
  1677  				},
  1678  			},
  1679  			result: []evictionapi.Threshold{},
  1680  		},
  1681  	}
  1682  	for testName, testCase := range testCases {
  1683  		actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinRelaim)
  1684  		if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1685  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1686  		}
  1687  	}
  1688  }
  1689  
  1690  func TestThresholdsFirstObservedAt(t *testing.T) {
  1691  	hardThreshold := evictionapi.Threshold{
  1692  		Signal:   evictionapi.SignalMemoryAvailable,
  1693  		Operator: evictionapi.OpLessThan,
  1694  		Value: evictionapi.ThresholdValue{
  1695  			Quantity: quantityMustParse("1Gi"),
  1696  		},
  1697  	}
  1698  	now := metav1.Now()
  1699  	oldTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
  1700  	testCases := map[string]struct {
  1701  		thresholds     []evictionapi.Threshold
  1702  		lastObservedAt thresholdsObservedAt
  1703  		now            time.Time
  1704  		result         thresholdsObservedAt
  1705  	}{
  1706  		"empty": {
  1707  			thresholds:     []evictionapi.Threshold{},
  1708  			lastObservedAt: thresholdsObservedAt{},
  1709  			now:            now.Time,
  1710  			result:         thresholdsObservedAt{},
  1711  		},
  1712  		"no-previous-observation": {
  1713  			thresholds:     []evictionapi.Threshold{hardThreshold},
  1714  			lastObservedAt: thresholdsObservedAt{},
  1715  			now:            now.Time,
  1716  			result: thresholdsObservedAt{
  1717  				hardThreshold: now.Time,
  1718  			},
  1719  		},
  1720  		"previous-observation": {
  1721  			thresholds: []evictionapi.Threshold{hardThreshold},
  1722  			lastObservedAt: thresholdsObservedAt{
  1723  				hardThreshold: oldTime.Time,
  1724  			},
  1725  			now: now.Time,
  1726  			result: thresholdsObservedAt{
  1727  				hardThreshold: oldTime.Time,
  1728  			},
  1729  		},
  1730  	}
  1731  	for testName, testCase := range testCases {
  1732  		actual := thresholdsFirstObservedAt(testCase.thresholds, testCase.lastObservedAt, testCase.now)
  1733  		if !reflect.DeepEqual(actual, testCase.result) {
  1734  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1735  		}
  1736  	}
  1737  }
  1738  
  1739  func TestThresholdsMetGracePeriod(t *testing.T) {
  1740  	now := metav1.Now()
  1741  	hardThreshold := evictionapi.Threshold{
  1742  		Signal:   evictionapi.SignalMemoryAvailable,
  1743  		Operator: evictionapi.OpLessThan,
  1744  		Value: evictionapi.ThresholdValue{
  1745  			Quantity: quantityMustParse("1Gi"),
  1746  		},
  1747  	}
  1748  	softThreshold := evictionapi.Threshold{
  1749  		Signal:   evictionapi.SignalMemoryAvailable,
  1750  		Operator: evictionapi.OpLessThan,
  1751  		Value: evictionapi.ThresholdValue{
  1752  			Quantity: quantityMustParse("2Gi"),
  1753  		},
  1754  		GracePeriod: 1 * time.Minute,
  1755  	}
  1756  	oldTime := metav1.NewTime(now.Time.Add(-2 * time.Minute))
  1757  	testCases := map[string]struct {
  1758  		observedAt thresholdsObservedAt
  1759  		now        time.Time
  1760  		result     []evictionapi.Threshold
  1761  	}{
  1762  		"empty": {
  1763  			observedAt: thresholdsObservedAt{},
  1764  			now:        now.Time,
  1765  			result:     []evictionapi.Threshold{},
  1766  		},
  1767  		"hard-threshold-met": {
  1768  			observedAt: thresholdsObservedAt{
  1769  				hardThreshold: now.Time,
  1770  			},
  1771  			now:    now.Time,
  1772  			result: []evictionapi.Threshold{hardThreshold},
  1773  		},
  1774  		"soft-threshold-not-met": {
  1775  			observedAt: thresholdsObservedAt{
  1776  				softThreshold: now.Time,
  1777  			},
  1778  			now:    now.Time,
  1779  			result: []evictionapi.Threshold{},
  1780  		},
  1781  		"soft-threshold-met": {
  1782  			observedAt: thresholdsObservedAt{
  1783  				softThreshold: oldTime.Time,
  1784  			},
  1785  			now:    now.Time,
  1786  			result: []evictionapi.Threshold{softThreshold},
  1787  		},
  1788  	}
  1789  	for testName, testCase := range testCases {
  1790  		actual := thresholdsMetGracePeriod(testCase.observedAt, now.Time)
  1791  		if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1792  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1793  		}
  1794  	}
  1795  }
  1796  
  1797  func TestNodeConditions(t *testing.T) {
  1798  	testCases := map[string]struct {
  1799  		inputs []evictionapi.Threshold
  1800  		result []v1.NodeConditionType
  1801  	}{
  1802  		"empty-list": {
  1803  			inputs: []evictionapi.Threshold{},
  1804  			result: []v1.NodeConditionType{},
  1805  		},
  1806  		"memory.available": {
  1807  			inputs: []evictionapi.Threshold{
  1808  				{Signal: evictionapi.SignalMemoryAvailable},
  1809  			},
  1810  			result: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1811  		},
  1812  	}
  1813  	for testName, testCase := range testCases {
  1814  		actual := nodeConditions(testCase.inputs)
  1815  		if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
  1816  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1817  		}
  1818  	}
  1819  }
  1820  
  1821  func TestNodeConditionsLastObservedAt(t *testing.T) {
  1822  	now := metav1.Now()
  1823  	oldTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
  1824  	testCases := map[string]struct {
  1825  		nodeConditions []v1.NodeConditionType
  1826  		lastObservedAt nodeConditionsObservedAt
  1827  		now            time.Time
  1828  		result         nodeConditionsObservedAt
  1829  	}{
  1830  		"no-previous-observation": {
  1831  			nodeConditions: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1832  			lastObservedAt: nodeConditionsObservedAt{},
  1833  			now:            now.Time,
  1834  			result: nodeConditionsObservedAt{
  1835  				v1.NodeMemoryPressure: now.Time,
  1836  			},
  1837  		},
  1838  		"previous-observation": {
  1839  			nodeConditions: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1840  			lastObservedAt: nodeConditionsObservedAt{
  1841  				v1.NodeMemoryPressure: oldTime.Time,
  1842  			},
  1843  			now: now.Time,
  1844  			result: nodeConditionsObservedAt{
  1845  				v1.NodeMemoryPressure: now.Time,
  1846  			},
  1847  		},
  1848  		"old-observation": {
  1849  			nodeConditions: []v1.NodeConditionType{},
  1850  			lastObservedAt: nodeConditionsObservedAt{
  1851  				v1.NodeMemoryPressure: oldTime.Time,
  1852  			},
  1853  			now: now.Time,
  1854  			result: nodeConditionsObservedAt{
  1855  				v1.NodeMemoryPressure: oldTime.Time,
  1856  			},
  1857  		},
  1858  	}
  1859  	for testName, testCase := range testCases {
  1860  		actual := nodeConditionsLastObservedAt(testCase.nodeConditions, testCase.lastObservedAt, testCase.now)
  1861  		if !reflect.DeepEqual(actual, testCase.result) {
  1862  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1863  		}
  1864  	}
  1865  }
  1866  
  1867  func TestNodeConditionsObservedSince(t *testing.T) {
  1868  	now := metav1.Now()
  1869  	observedTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
  1870  	testCases := map[string]struct {
  1871  		observedAt nodeConditionsObservedAt
  1872  		period     time.Duration
  1873  		now        time.Time
  1874  		result     []v1.NodeConditionType
  1875  	}{
  1876  		"in-period": {
  1877  			observedAt: nodeConditionsObservedAt{
  1878  				v1.NodeMemoryPressure: observedTime.Time,
  1879  			},
  1880  			period: 2 * time.Minute,
  1881  			now:    now.Time,
  1882  			result: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1883  		},
  1884  		"out-of-period": {
  1885  			observedAt: nodeConditionsObservedAt{
  1886  				v1.NodeMemoryPressure: observedTime.Time,
  1887  			},
  1888  			period: 30 * time.Second,
  1889  			now:    now.Time,
  1890  			result: []v1.NodeConditionType{},
  1891  		},
  1892  	}
  1893  	for testName, testCase := range testCases {
  1894  		actual := nodeConditionsObservedSince(testCase.observedAt, testCase.period, testCase.now)
  1895  		if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
  1896  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1897  		}
  1898  	}
  1899  }
  1900  
  1901  func TestHasNodeConditions(t *testing.T) {
  1902  	testCases := map[string]struct {
  1903  		inputs []v1.NodeConditionType
  1904  		item   v1.NodeConditionType
  1905  		result bool
  1906  	}{
  1907  		"has-condition": {
  1908  			inputs: []v1.NodeConditionType{v1.NodeReady, v1.NodeDiskPressure, v1.NodeMemoryPressure},
  1909  			item:   v1.NodeMemoryPressure,
  1910  			result: true,
  1911  		},
  1912  		"does-not-have-condition": {
  1913  			inputs: []v1.NodeConditionType{v1.NodeReady, v1.NodeDiskPressure},
  1914  			item:   v1.NodeMemoryPressure,
  1915  			result: false,
  1916  		},
  1917  	}
  1918  	for testName, testCase := range testCases {
  1919  		if actual := hasNodeCondition(testCase.inputs, testCase.item); actual != testCase.result {
  1920  			t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1921  		}
  1922  	}
  1923  }
  1924  
  1925  func TestParsePercentage(t *testing.T) {
  1926  	testCases := map[string]struct {
  1927  		hasError bool
  1928  		value    float32
  1929  	}{
  1930  		"blah": {
  1931  			hasError: true,
  1932  		},
  1933  		"25.5%": {
  1934  			value: 0.255,
  1935  		},
  1936  		"foo%": {
  1937  			hasError: true,
  1938  		},
  1939  		"12%345": {
  1940  			hasError: true,
  1941  		},
  1942  	}
  1943  	for input, expected := range testCases {
  1944  		value, err := parsePercentage(input)
  1945  		if (err != nil) != expected.hasError {
  1946  			t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.hasError, err != nil)
  1947  		}
  1948  		if value != expected.value {
  1949  			t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.value, value)
  1950  		}
  1951  	}
  1952  }
  1953  
  1954  func TestCompareThresholdValue(t *testing.T) {
  1955  	testCases := []struct {
  1956  		a, b  evictionapi.ThresholdValue
  1957  		equal bool
  1958  	}{
  1959  		{
  1960  			a: evictionapi.ThresholdValue{
  1961  				Quantity: resource.NewQuantity(123, resource.BinarySI),
  1962  			},
  1963  			b: evictionapi.ThresholdValue{
  1964  				Quantity: resource.NewQuantity(123, resource.BinarySI),
  1965  			},
  1966  			equal: true,
  1967  		},
  1968  		{
  1969  			a: evictionapi.ThresholdValue{
  1970  				Quantity: resource.NewQuantity(123, resource.BinarySI),
  1971  			},
  1972  			b: evictionapi.ThresholdValue{
  1973  				Quantity: resource.NewQuantity(456, resource.BinarySI),
  1974  			},
  1975  			equal: false,
  1976  		},
  1977  		{
  1978  			a: evictionapi.ThresholdValue{
  1979  				Quantity: resource.NewQuantity(123, resource.BinarySI),
  1980  			},
  1981  			b: evictionapi.ThresholdValue{
  1982  				Percentage: 0.1,
  1983  			},
  1984  			equal: false,
  1985  		},
  1986  		{
  1987  			a: evictionapi.ThresholdValue{
  1988  				Percentage: 0.1,
  1989  			},
  1990  			b: evictionapi.ThresholdValue{
  1991  				Percentage: 0.1,
  1992  			},
  1993  			equal: true,
  1994  		},
  1995  		{
  1996  			a: evictionapi.ThresholdValue{
  1997  				Percentage: 0.2,
  1998  			},
  1999  			b: evictionapi.ThresholdValue{
  2000  				Percentage: 0.1,
  2001  			},
  2002  			equal: false,
  2003  		},
  2004  	}
  2005  
  2006  	for i, testCase := range testCases {
  2007  		if compareThresholdValue(testCase.a, testCase.b) != testCase.equal ||
  2008  			compareThresholdValue(testCase.b, testCase.a) != testCase.equal {
  2009  			t.Errorf("Test case: %v failed", i)
  2010  		}
  2011  	}
  2012  }
  2013  func TestAddContainerFsThresholds(t *testing.T) {
  2014  	gracePeriod := time.Duration(1)
  2015  	testCases := []struct {
  2016  		description                   string
  2017  		imageFs                       bool
  2018  		containerFs                   bool
  2019  		expectedContainerFsHard       evictionapi.Threshold
  2020  		expectedContainerFsSoft       evictionapi.Threshold
  2021  		expectedContainerFsINodesHard evictionapi.Threshold
  2022  		expectedContainerFsINodesSoft evictionapi.Threshold
  2023  		expectErr                     bool
  2024  		thresholdList                 []evictionapi.Threshold
  2025  	}{
  2026  		{
  2027  			description: "single filesystem",
  2028  			imageFs:     false,
  2029  			containerFs: false,
  2030  			expectedContainerFsHard: evictionapi.Threshold{
  2031  				Signal:   evictionapi.SignalContainerFsAvailable,
  2032  				Operator: evictionapi.OpLessThan,
  2033  				Value: evictionapi.ThresholdValue{
  2034  					Quantity: quantityMustParse("100Mi"),
  2035  				},
  2036  				MinReclaim: &evictionapi.ThresholdValue{
  2037  					Quantity: quantityMustParse("1Gi"),
  2038  				},
  2039  			},
  2040  			expectedContainerFsSoft: evictionapi.Threshold{
  2041  				Signal:   evictionapi.SignalContainerFsAvailable,
  2042  				Operator: evictionapi.OpLessThan,
  2043  				Value: evictionapi.ThresholdValue{
  2044  					Quantity: quantityMustParse("200Mi"),
  2045  				},
  2046  				GracePeriod: gracePeriod,
  2047  				MinReclaim: &evictionapi.ThresholdValue{
  2048  					Quantity: quantityMustParse("1Gi"),
  2049  				},
  2050  			},
  2051  			expectedContainerFsINodesHard: evictionapi.Threshold{
  2052  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2053  				Operator: evictionapi.OpLessThan,
  2054  				Value: evictionapi.ThresholdValue{
  2055  					Quantity: quantityMustParse("100Mi"),
  2056  				},
  2057  				MinReclaim: &evictionapi.ThresholdValue{
  2058  					Quantity: quantityMustParse("1Gi"),
  2059  				},
  2060  			},
  2061  			expectedContainerFsINodesSoft: evictionapi.Threshold{
  2062  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2063  				Operator: evictionapi.OpLessThan,
  2064  				Value: evictionapi.ThresholdValue{
  2065  					Quantity: quantityMustParse("200Mi"),
  2066  				},
  2067  				MinReclaim: &evictionapi.ThresholdValue{
  2068  					Quantity: quantityMustParse("2Gi"),
  2069  				},
  2070  				GracePeriod: gracePeriod,
  2071  			},
  2072  
  2073  			thresholdList: []evictionapi.Threshold{
  2074  				{
  2075  					Signal:   evictionapi.SignalNodeFsAvailable,
  2076  					Operator: evictionapi.OpLessThan,
  2077  					Value: evictionapi.ThresholdValue{
  2078  						Quantity: quantityMustParse("100Mi"),
  2079  					},
  2080  					MinReclaim: &evictionapi.ThresholdValue{
  2081  						Quantity: quantityMustParse("1Gi"),
  2082  					},
  2083  				},
  2084  				{
  2085  					Signal:   evictionapi.SignalNodeFsAvailable,
  2086  					Operator: evictionapi.OpLessThan,
  2087  					Value: evictionapi.ThresholdValue{
  2088  						Quantity: quantityMustParse("200Mi"),
  2089  					},
  2090  					GracePeriod: gracePeriod,
  2091  					MinReclaim: &evictionapi.ThresholdValue{
  2092  						Quantity: quantityMustParse("1Gi"),
  2093  					},
  2094  				},
  2095  				{
  2096  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2097  					Operator: evictionapi.OpLessThan,
  2098  					Value: evictionapi.ThresholdValue{
  2099  						Quantity: quantityMustParse("200Mi"),
  2100  					},
  2101  					MinReclaim: &evictionapi.ThresholdValue{
  2102  						Quantity: quantityMustParse("2Gi"),
  2103  					},
  2104  					GracePeriod: gracePeriod,
  2105  				},
  2106  				{
  2107  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2108  					Operator: evictionapi.OpLessThan,
  2109  					Value: evictionapi.ThresholdValue{
  2110  						Quantity: quantityMustParse("100Mi"),
  2111  					},
  2112  					MinReclaim: &evictionapi.ThresholdValue{
  2113  						Quantity: quantityMustParse("1Gi"),
  2114  					},
  2115  				},
  2116  				{
  2117  					Signal:   evictionapi.SignalImageFsAvailable,
  2118  					Operator: evictionapi.OpLessThan,
  2119  					Value: evictionapi.ThresholdValue{
  2120  						Quantity: quantityMustParse("100Mi"),
  2121  					},
  2122  					MinReclaim: &evictionapi.ThresholdValue{
  2123  						Quantity: quantityMustParse("1Gi"),
  2124  					},
  2125  				},
  2126  
  2127  				{
  2128  					Signal:   evictionapi.SignalImageFsAvailable,
  2129  					Operator: evictionapi.OpLessThan,
  2130  					Value: evictionapi.ThresholdValue{
  2131  						Quantity: quantityMustParse("300Mi"),
  2132  					},
  2133  					GracePeriod: gracePeriod,
  2134  					MinReclaim: &evictionapi.ThresholdValue{
  2135  						Quantity: quantityMustParse("2Gi"),
  2136  					},
  2137  				},
  2138  				{
  2139  					Signal:   evictionapi.SignalImageFsInodesFree,
  2140  					Operator: evictionapi.OpLessThan,
  2141  					Value: evictionapi.ThresholdValue{
  2142  						Quantity: quantityMustParse("150Mi"),
  2143  					},
  2144  					MinReclaim: &evictionapi.ThresholdValue{
  2145  						Quantity: quantityMustParse("2Gi"),
  2146  					},
  2147  					GracePeriod: gracePeriod,
  2148  				},
  2149  				{
  2150  					Signal:   evictionapi.SignalImageFsInodesFree,
  2151  					Operator: evictionapi.OpLessThan,
  2152  					Value: evictionapi.ThresholdValue{
  2153  						Quantity: quantityMustParse("150Mi"),
  2154  					},
  2155  					MinReclaim: &evictionapi.ThresholdValue{
  2156  						Quantity: quantityMustParse("2Gi"),
  2157  					},
  2158  				},
  2159  			},
  2160  		},
  2161  		{
  2162  			description: "image filesystem",
  2163  			imageFs:     true,
  2164  			containerFs: false,
  2165  			expectedContainerFsHard: evictionapi.Threshold{
  2166  				Signal:   evictionapi.SignalContainerFsAvailable,
  2167  				Operator: evictionapi.OpLessThan,
  2168  				Value: evictionapi.ThresholdValue{
  2169  					Quantity: quantityMustParse("150Mi"),
  2170  				},
  2171  				MinReclaim: &evictionapi.ThresholdValue{
  2172  					Quantity: quantityMustParse("1.5Gi"),
  2173  				},
  2174  			},
  2175  			expectedContainerFsSoft: evictionapi.Threshold{
  2176  				Signal:   evictionapi.SignalContainerFsAvailable,
  2177  				Operator: evictionapi.OpLessThan,
  2178  				Value: evictionapi.ThresholdValue{
  2179  					Quantity: quantityMustParse("300Mi"),
  2180  				},
  2181  				GracePeriod: gracePeriod,
  2182  				MinReclaim: &evictionapi.ThresholdValue{
  2183  					Quantity: quantityMustParse("3Gi"),
  2184  				},
  2185  			},
  2186  			expectedContainerFsINodesHard: evictionapi.Threshold{
  2187  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2188  				Operator: evictionapi.OpLessThan,
  2189  				Value: evictionapi.ThresholdValue{
  2190  					Quantity: quantityMustParse("300Mi"),
  2191  				},
  2192  				MinReclaim: &evictionapi.ThresholdValue{
  2193  					Quantity: quantityMustParse("2Gi"),
  2194  				},
  2195  			},
  2196  			expectedContainerFsINodesSoft: evictionapi.Threshold{
  2197  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2198  				Operator: evictionapi.OpLessThan,
  2199  				Value: evictionapi.ThresholdValue{
  2200  					Quantity: quantityMustParse("150Mi"),
  2201  				},
  2202  				MinReclaim: &evictionapi.ThresholdValue{
  2203  					Quantity: quantityMustParse("2Gi"),
  2204  				},
  2205  				GracePeriod: gracePeriod,
  2206  			},
  2207  			thresholdList: []evictionapi.Threshold{
  2208  				{
  2209  					Signal:   evictionapi.SignalNodeFsAvailable,
  2210  					Operator: evictionapi.OpLessThan,
  2211  					Value: evictionapi.ThresholdValue{
  2212  						Quantity: quantityMustParse("100Mi"),
  2213  					},
  2214  					MinReclaim: &evictionapi.ThresholdValue{
  2215  						Quantity: quantityMustParse("1Gi"),
  2216  					},
  2217  				},
  2218  				{
  2219  					Signal:   evictionapi.SignalNodeFsAvailable,
  2220  					Operator: evictionapi.OpLessThan,
  2221  					Value: evictionapi.ThresholdValue{
  2222  						Quantity: quantityMustParse("200Mi"),
  2223  					},
  2224  					GracePeriod: gracePeriod,
  2225  					MinReclaim: &evictionapi.ThresholdValue{
  2226  						Quantity: quantityMustParse("1Gi"),
  2227  					},
  2228  				},
  2229  				{
  2230  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2231  					Operator: evictionapi.OpLessThan,
  2232  					Value: evictionapi.ThresholdValue{
  2233  						Quantity: quantityMustParse("200Mi"),
  2234  					},
  2235  					MinReclaim: &evictionapi.ThresholdValue{
  2236  						Quantity: quantityMustParse("2Gi"),
  2237  					},
  2238  					GracePeriod: gracePeriod,
  2239  				},
  2240  				{
  2241  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2242  					Operator: evictionapi.OpLessThan,
  2243  					Value: evictionapi.ThresholdValue{
  2244  						Quantity: quantityMustParse("150Mi"),
  2245  					},
  2246  					MinReclaim: &evictionapi.ThresholdValue{
  2247  						Quantity: quantityMustParse("1.5Gi"),
  2248  					},
  2249  				},
  2250  				{
  2251  					Signal:   evictionapi.SignalImageFsAvailable,
  2252  					Operator: evictionapi.OpLessThan,
  2253  					Value: evictionapi.ThresholdValue{
  2254  						Quantity: quantityMustParse("150Mi"),
  2255  					},
  2256  					MinReclaim: &evictionapi.ThresholdValue{
  2257  						Quantity: quantityMustParse("1.5Gi"),
  2258  					},
  2259  				},
  2260  				{
  2261  					Signal:   evictionapi.SignalImageFsAvailable,
  2262  					Operator: evictionapi.OpLessThan,
  2263  					Value: evictionapi.ThresholdValue{
  2264  						Quantity: quantityMustParse("300Mi"),
  2265  					},
  2266  					GracePeriod: gracePeriod,
  2267  					MinReclaim: &evictionapi.ThresholdValue{
  2268  						Quantity: quantityMustParse("3Gi"),
  2269  					},
  2270  				},
  2271  				{
  2272  					Signal:   evictionapi.SignalImageFsInodesFree,
  2273  					Operator: evictionapi.OpLessThan,
  2274  					Value: evictionapi.ThresholdValue{
  2275  						Quantity: quantityMustParse("150Mi"),
  2276  					},
  2277  					MinReclaim: &evictionapi.ThresholdValue{
  2278  						Quantity: quantityMustParse("2Gi"),
  2279  					},
  2280  					GracePeriod: gracePeriod,
  2281  				},
  2282  				{
  2283  					Signal:   evictionapi.SignalImageFsInodesFree,
  2284  					Operator: evictionapi.OpLessThan,
  2285  					Value: evictionapi.ThresholdValue{
  2286  						Quantity: quantityMustParse("300Mi"),
  2287  					},
  2288  					MinReclaim: &evictionapi.ThresholdValue{
  2289  						Quantity: quantityMustParse("2Gi"),
  2290  					},
  2291  				},
  2292  			},
  2293  		},
  2294  		{
  2295  			description: "container and image are separate",
  2296  			imageFs:     true,
  2297  			containerFs: true,
  2298  			expectedContainerFsHard: evictionapi.Threshold{
  2299  				Signal:   evictionapi.SignalContainerFsAvailable,
  2300  				Operator: evictionapi.OpLessThan,
  2301  				Value: evictionapi.ThresholdValue{
  2302  					Quantity: quantityMustParse("100Mi"),
  2303  				},
  2304  				MinReclaim: &evictionapi.ThresholdValue{
  2305  					Quantity: quantityMustParse("1Gi"),
  2306  				},
  2307  			},
  2308  			expectedContainerFsSoft: evictionapi.Threshold{
  2309  				Signal:   evictionapi.SignalContainerFsAvailable,
  2310  				Operator: evictionapi.OpLessThan,
  2311  				Value: evictionapi.ThresholdValue{
  2312  					Quantity: quantityMustParse("200Mi"),
  2313  				},
  2314  				GracePeriod: gracePeriod,
  2315  				MinReclaim: &evictionapi.ThresholdValue{
  2316  					Quantity: quantityMustParse("1Gi"),
  2317  				},
  2318  			},
  2319  			expectedContainerFsINodesHard: evictionapi.Threshold{
  2320  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2321  				Operator: evictionapi.OpLessThan,
  2322  				Value: evictionapi.ThresholdValue{
  2323  					Quantity: quantityMustParse("100Mi"),
  2324  				},
  2325  				MinReclaim: &evictionapi.ThresholdValue{
  2326  					Quantity: quantityMustParse("1Gi"),
  2327  				},
  2328  			},
  2329  			expectedContainerFsINodesSoft: evictionapi.Threshold{
  2330  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2331  				Operator: evictionapi.OpLessThan,
  2332  				Value: evictionapi.ThresholdValue{
  2333  					Quantity: quantityMustParse("200Mi"),
  2334  				},
  2335  				MinReclaim: &evictionapi.ThresholdValue{
  2336  					Quantity: quantityMustParse("2Gi"),
  2337  				},
  2338  				GracePeriod: gracePeriod,
  2339  			},
  2340  			thresholdList: []evictionapi.Threshold{
  2341  				{
  2342  					Signal:   evictionapi.SignalNodeFsAvailable,
  2343  					Operator: evictionapi.OpLessThan,
  2344  					Value: evictionapi.ThresholdValue{
  2345  						Quantity: quantityMustParse("100Mi"),
  2346  					},
  2347  					MinReclaim: &evictionapi.ThresholdValue{
  2348  						Quantity: quantityMustParse("1Gi"),
  2349  					},
  2350  				},
  2351  				{
  2352  					Signal:   evictionapi.SignalNodeFsAvailable,
  2353  					Operator: evictionapi.OpLessThan,
  2354  					Value: evictionapi.ThresholdValue{
  2355  						Quantity: quantityMustParse("200Mi"),
  2356  					},
  2357  					GracePeriod: gracePeriod,
  2358  					MinReclaim: &evictionapi.ThresholdValue{
  2359  						Quantity: quantityMustParse("1Gi"),
  2360  					},
  2361  				},
  2362  				{
  2363  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2364  					Operator: evictionapi.OpLessThan,
  2365  					Value: evictionapi.ThresholdValue{
  2366  						Quantity: quantityMustParse("200Mi"),
  2367  					},
  2368  					MinReclaim: &evictionapi.ThresholdValue{
  2369  						Quantity: quantityMustParse("2Gi"),
  2370  					},
  2371  					GracePeriod: gracePeriod,
  2372  				},
  2373  				{
  2374  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2375  					Operator: evictionapi.OpLessThan,
  2376  					Value: evictionapi.ThresholdValue{
  2377  						Quantity: quantityMustParse("100Mi"),
  2378  					},
  2379  					MinReclaim: &evictionapi.ThresholdValue{
  2380  						Quantity: quantityMustParse("1Gi"),
  2381  					},
  2382  				},
  2383  				{
  2384  					Signal:   evictionapi.SignalImageFsAvailable,
  2385  					Operator: evictionapi.OpLessThan,
  2386  					Value: evictionapi.ThresholdValue{
  2387  						Quantity: quantityMustParse("150Mi"),
  2388  					},
  2389  					MinReclaim: &evictionapi.ThresholdValue{
  2390  						Quantity: quantityMustParse("1.5Gi"),
  2391  					},
  2392  				},
  2393  				{
  2394  					Signal:   evictionapi.SignalImageFsAvailable,
  2395  					Operator: evictionapi.OpLessThan,
  2396  					Value: evictionapi.ThresholdValue{
  2397  						Quantity: quantityMustParse("300Mi"),
  2398  					},
  2399  					GracePeriod: gracePeriod,
  2400  					MinReclaim: &evictionapi.ThresholdValue{
  2401  						Quantity: quantityMustParse("3Gi"),
  2402  					},
  2403  				},
  2404  				{
  2405  					Signal:   evictionapi.SignalImageFsInodesFree,
  2406  					Operator: evictionapi.OpLessThan,
  2407  					Value: evictionapi.ThresholdValue{
  2408  						Quantity: quantityMustParse("150Mi"),
  2409  					},
  2410  					MinReclaim: &evictionapi.ThresholdValue{
  2411  						Quantity: quantityMustParse("2Gi"),
  2412  					},
  2413  					GracePeriod: gracePeriod,
  2414  				},
  2415  				{
  2416  					Signal:   evictionapi.SignalImageFsInodesFree,
  2417  					Operator: evictionapi.OpLessThan,
  2418  					Value: evictionapi.ThresholdValue{
  2419  						Quantity: quantityMustParse("300Mi"),
  2420  					},
  2421  					MinReclaim: &evictionapi.ThresholdValue{
  2422  						Quantity: quantityMustParse("2Gi"),
  2423  					},
  2424  				},
  2425  			},
  2426  		},
  2427  		{
  2428  			description: "single filesystem; existing containerfsstats",
  2429  			imageFs:     false,
  2430  			containerFs: false,
  2431  			expectErr:   true,
  2432  			expectedContainerFsHard: evictionapi.Threshold{
  2433  				Signal:   evictionapi.SignalContainerFsAvailable,
  2434  				Operator: evictionapi.OpLessThan,
  2435  				Value: evictionapi.ThresholdValue{
  2436  					Quantity: quantityMustParse("100Mi"),
  2437  				},
  2438  				MinReclaim: &evictionapi.ThresholdValue{
  2439  					Quantity: quantityMustParse("1Gi"),
  2440  				},
  2441  			},
  2442  			expectedContainerFsSoft: evictionapi.Threshold{
  2443  				Signal:   evictionapi.SignalContainerFsAvailable,
  2444  				Operator: evictionapi.OpLessThan,
  2445  				Value: evictionapi.ThresholdValue{
  2446  					Quantity: quantityMustParse("200Mi"),
  2447  				},
  2448  				GracePeriod: gracePeriod,
  2449  				MinReclaim: &evictionapi.ThresholdValue{
  2450  					Quantity: quantityMustParse("1Gi"),
  2451  				},
  2452  			},
  2453  			expectedContainerFsINodesHard: evictionapi.Threshold{
  2454  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2455  				Operator: evictionapi.OpLessThan,
  2456  				Value: evictionapi.ThresholdValue{
  2457  					Quantity: quantityMustParse("100Mi"),
  2458  				},
  2459  				MinReclaim: &evictionapi.ThresholdValue{
  2460  					Quantity: quantityMustParse("1Gi"),
  2461  				},
  2462  			},
  2463  			expectedContainerFsINodesSoft: evictionapi.Threshold{
  2464  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2465  				Operator: evictionapi.OpLessThan,
  2466  				Value: evictionapi.ThresholdValue{
  2467  					Quantity: quantityMustParse("200Mi"),
  2468  				},
  2469  				MinReclaim: &evictionapi.ThresholdValue{
  2470  					Quantity: quantityMustParse("2Gi"),
  2471  				},
  2472  				GracePeriod: gracePeriod,
  2473  			},
  2474  
  2475  			thresholdList: []evictionapi.Threshold{
  2476  				{
  2477  					Signal:   evictionapi.SignalNodeFsAvailable,
  2478  					Operator: evictionapi.OpLessThan,
  2479  					Value: evictionapi.ThresholdValue{
  2480  						Quantity: quantityMustParse("100Mi"),
  2481  					},
  2482  					MinReclaim: &evictionapi.ThresholdValue{
  2483  						Quantity: quantityMustParse("1Gi"),
  2484  					},
  2485  				},
  2486  				{
  2487  					Signal:   evictionapi.SignalNodeFsAvailable,
  2488  					Operator: evictionapi.OpLessThan,
  2489  					Value: evictionapi.ThresholdValue{
  2490  						Quantity: quantityMustParse("200Mi"),
  2491  					},
  2492  					GracePeriod: gracePeriod,
  2493  					MinReclaim: &evictionapi.ThresholdValue{
  2494  						Quantity: quantityMustParse("1Gi"),
  2495  					},
  2496  				},
  2497  				{
  2498  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2499  					Operator: evictionapi.OpLessThan,
  2500  					Value: evictionapi.ThresholdValue{
  2501  						Quantity: quantityMustParse("200Mi"),
  2502  					},
  2503  					MinReclaim: &evictionapi.ThresholdValue{
  2504  						Quantity: quantityMustParse("2Gi"),
  2505  					},
  2506  					GracePeriod: gracePeriod,
  2507  				},
  2508  				{
  2509  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2510  					Operator: evictionapi.OpLessThan,
  2511  					Value: evictionapi.ThresholdValue{
  2512  						Quantity: quantityMustParse("100Mi"),
  2513  					},
  2514  					MinReclaim: &evictionapi.ThresholdValue{
  2515  						Quantity: quantityMustParse("1Gi"),
  2516  					},
  2517  				},
  2518  				{
  2519  					Signal:   evictionapi.SignalImageFsAvailable,
  2520  					Operator: evictionapi.OpLessThan,
  2521  					Value: evictionapi.ThresholdValue{
  2522  						Quantity: quantityMustParse("100Mi"),
  2523  					},
  2524  					MinReclaim: &evictionapi.ThresholdValue{
  2525  						Quantity: quantityMustParse("1Gi"),
  2526  					},
  2527  				},
  2528  
  2529  				{
  2530  					Signal:   evictionapi.SignalImageFsAvailable,
  2531  					Operator: evictionapi.OpLessThan,
  2532  					Value: evictionapi.ThresholdValue{
  2533  						Quantity: quantityMustParse("300Mi"),
  2534  					},
  2535  					GracePeriod: gracePeriod,
  2536  					MinReclaim: &evictionapi.ThresholdValue{
  2537  						Quantity: quantityMustParse("2Gi"),
  2538  					},
  2539  				},
  2540  				{
  2541  					Signal:   evictionapi.SignalImageFsInodesFree,
  2542  					Operator: evictionapi.OpLessThan,
  2543  					Value: evictionapi.ThresholdValue{
  2544  						Quantity: quantityMustParse("150Mi"),
  2545  					},
  2546  					MinReclaim: &evictionapi.ThresholdValue{
  2547  						Quantity: quantityMustParse("2Gi"),
  2548  					},
  2549  					GracePeriod: gracePeriod,
  2550  				},
  2551  				{
  2552  					Signal:   evictionapi.SignalImageFsInodesFree,
  2553  					Operator: evictionapi.OpLessThan,
  2554  					Value: evictionapi.ThresholdValue{
  2555  						Quantity: quantityMustParse("150Mi"),
  2556  					},
  2557  					MinReclaim: &evictionapi.ThresholdValue{
  2558  						Quantity: quantityMustParse("2Gi"),
  2559  					},
  2560  				},
  2561  				{
  2562  					Signal:   evictionapi.SignalContainerFsAvailable,
  2563  					Operator: evictionapi.OpLessThan,
  2564  					Value: evictionapi.ThresholdValue{
  2565  						Quantity: quantityMustParse("500Mi"),
  2566  					},
  2567  					MinReclaim: &evictionapi.ThresholdValue{
  2568  						Quantity: quantityMustParse("5Gi"),
  2569  					},
  2570  				},
  2571  
  2572  				{
  2573  					Signal:   evictionapi.SignalContainerFsAvailable,
  2574  					Operator: evictionapi.OpLessThan,
  2575  					Value: evictionapi.ThresholdValue{
  2576  						Quantity: quantityMustParse("500Mi"),
  2577  					},
  2578  					GracePeriod: gracePeriod,
  2579  					MinReclaim: &evictionapi.ThresholdValue{
  2580  						Quantity: quantityMustParse("5Gi"),
  2581  					},
  2582  				},
  2583  				{
  2584  					Signal:   evictionapi.SignalContainerFsInodesFree,
  2585  					Operator: evictionapi.OpLessThan,
  2586  					Value: evictionapi.ThresholdValue{
  2587  						Quantity: quantityMustParse("500Mi"),
  2588  					},
  2589  					MinReclaim: &evictionapi.ThresholdValue{
  2590  						Quantity: quantityMustParse("5Gi"),
  2591  					},
  2592  					GracePeriod: gracePeriod,
  2593  				},
  2594  				{
  2595  					Signal:   evictionapi.SignalContainerFsInodesFree,
  2596  					Operator: evictionapi.OpLessThan,
  2597  					Value: evictionapi.ThresholdValue{
  2598  						Quantity: quantityMustParse("500Mi"),
  2599  					},
  2600  					MinReclaim: &evictionapi.ThresholdValue{
  2601  						Quantity: quantityMustParse("5Gi"),
  2602  					},
  2603  				},
  2604  			},
  2605  		},
  2606  		{
  2607  			description: "image filesystem; expect error",
  2608  			imageFs:     true,
  2609  			containerFs: false,
  2610  			expectErr:   true,
  2611  			expectedContainerFsHard: evictionapi.Threshold{
  2612  				Signal:   evictionapi.SignalContainerFsAvailable,
  2613  				Operator: evictionapi.OpLessThan,
  2614  				Value: evictionapi.ThresholdValue{
  2615  					Quantity: quantityMustParse("150Mi"),
  2616  				},
  2617  				MinReclaim: &evictionapi.ThresholdValue{
  2618  					Quantity: quantityMustParse("1.5Gi"),
  2619  				},
  2620  			},
  2621  			expectedContainerFsSoft: evictionapi.Threshold{
  2622  				Signal:   evictionapi.SignalContainerFsAvailable,
  2623  				Operator: evictionapi.OpLessThan,
  2624  				Value: evictionapi.ThresholdValue{
  2625  					Quantity: quantityMustParse("300Mi"),
  2626  				},
  2627  				GracePeriod: gracePeriod,
  2628  				MinReclaim: &evictionapi.ThresholdValue{
  2629  					Quantity: quantityMustParse("3Gi"),
  2630  				},
  2631  			},
  2632  			expectedContainerFsINodesHard: evictionapi.Threshold{
  2633  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2634  				Operator: evictionapi.OpLessThan,
  2635  				Value: evictionapi.ThresholdValue{
  2636  					Quantity: quantityMustParse("300Mi"),
  2637  				},
  2638  				MinReclaim: &evictionapi.ThresholdValue{
  2639  					Quantity: quantityMustParse("2Gi"),
  2640  				},
  2641  			},
  2642  			expectedContainerFsINodesSoft: evictionapi.Threshold{
  2643  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2644  				Operator: evictionapi.OpLessThan,
  2645  				Value: evictionapi.ThresholdValue{
  2646  					Quantity: quantityMustParse("150Mi"),
  2647  				},
  2648  				MinReclaim: &evictionapi.ThresholdValue{
  2649  					Quantity: quantityMustParse("2Gi"),
  2650  				},
  2651  				GracePeriod: gracePeriod,
  2652  			},
  2653  			thresholdList: []evictionapi.Threshold{
  2654  				{
  2655  					Signal:   evictionapi.SignalNodeFsAvailable,
  2656  					Operator: evictionapi.OpLessThan,
  2657  					Value: evictionapi.ThresholdValue{
  2658  						Quantity: quantityMustParse("100Mi"),
  2659  					},
  2660  					MinReclaim: &evictionapi.ThresholdValue{
  2661  						Quantity: quantityMustParse("1Gi"),
  2662  					},
  2663  				},
  2664  				{
  2665  					Signal:   evictionapi.SignalNodeFsAvailable,
  2666  					Operator: evictionapi.OpLessThan,
  2667  					Value: evictionapi.ThresholdValue{
  2668  						Quantity: quantityMustParse("200Mi"),
  2669  					},
  2670  					GracePeriod: gracePeriod,
  2671  					MinReclaim: &evictionapi.ThresholdValue{
  2672  						Quantity: quantityMustParse("1Gi"),
  2673  					},
  2674  				},
  2675  				{
  2676  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2677  					Operator: evictionapi.OpLessThan,
  2678  					Value: evictionapi.ThresholdValue{
  2679  						Quantity: quantityMustParse("200Mi"),
  2680  					},
  2681  					MinReclaim: &evictionapi.ThresholdValue{
  2682  						Quantity: quantityMustParse("2Gi"),
  2683  					},
  2684  					GracePeriod: gracePeriod,
  2685  				},
  2686  				{
  2687  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2688  					Operator: evictionapi.OpLessThan,
  2689  					Value: evictionapi.ThresholdValue{
  2690  						Quantity: quantityMustParse("150Mi"),
  2691  					},
  2692  					MinReclaim: &evictionapi.ThresholdValue{
  2693  						Quantity: quantityMustParse("1.5Gi"),
  2694  					},
  2695  				},
  2696  				{
  2697  					Signal:   evictionapi.SignalImageFsAvailable,
  2698  					Operator: evictionapi.OpLessThan,
  2699  					Value: evictionapi.ThresholdValue{
  2700  						Quantity: quantityMustParse("150Mi"),
  2701  					},
  2702  					MinReclaim: &evictionapi.ThresholdValue{
  2703  						Quantity: quantityMustParse("1.5Gi"),
  2704  					},
  2705  				},
  2706  				{
  2707  					Signal:   evictionapi.SignalImageFsAvailable,
  2708  					Operator: evictionapi.OpLessThan,
  2709  					Value: evictionapi.ThresholdValue{
  2710  						Quantity: quantityMustParse("300Mi"),
  2711  					},
  2712  					GracePeriod: gracePeriod,
  2713  					MinReclaim: &evictionapi.ThresholdValue{
  2714  						Quantity: quantityMustParse("3Gi"),
  2715  					},
  2716  				},
  2717  				{
  2718  					Signal:   evictionapi.SignalImageFsInodesFree,
  2719  					Operator: evictionapi.OpLessThan,
  2720  					Value: evictionapi.ThresholdValue{
  2721  						Quantity: quantityMustParse("150Mi"),
  2722  					},
  2723  					MinReclaim: &evictionapi.ThresholdValue{
  2724  						Quantity: quantityMustParse("2Gi"),
  2725  					},
  2726  					GracePeriod: gracePeriod,
  2727  				},
  2728  				{
  2729  					Signal:   evictionapi.SignalImageFsInodesFree,
  2730  					Operator: evictionapi.OpLessThan,
  2731  					Value: evictionapi.ThresholdValue{
  2732  						Quantity: quantityMustParse("300Mi"),
  2733  					},
  2734  					MinReclaim: &evictionapi.ThresholdValue{
  2735  						Quantity: quantityMustParse("2Gi"),
  2736  					},
  2737  				},
  2738  				{
  2739  					Signal:   evictionapi.SignalContainerFsAvailable,
  2740  					Operator: evictionapi.OpLessThan,
  2741  					Value: evictionapi.ThresholdValue{
  2742  						Quantity: quantityMustParse("500Mi"),
  2743  					},
  2744  					MinReclaim: &evictionapi.ThresholdValue{
  2745  						Quantity: quantityMustParse("5Gi"),
  2746  					},
  2747  				},
  2748  
  2749  				{
  2750  					Signal:   evictionapi.SignalContainerFsAvailable,
  2751  					Operator: evictionapi.OpLessThan,
  2752  					Value: evictionapi.ThresholdValue{
  2753  						Quantity: quantityMustParse("500Mi"),
  2754  					},
  2755  					GracePeriod: gracePeriod,
  2756  					MinReclaim: &evictionapi.ThresholdValue{
  2757  						Quantity: quantityMustParse("5Gi"),
  2758  					},
  2759  				},
  2760  				{
  2761  					Signal:   evictionapi.SignalContainerFsInodesFree,
  2762  					Operator: evictionapi.OpLessThan,
  2763  					Value: evictionapi.ThresholdValue{
  2764  						Quantity: quantityMustParse("500Mi"),
  2765  					},
  2766  					MinReclaim: &evictionapi.ThresholdValue{
  2767  						Quantity: quantityMustParse("5Gi"),
  2768  					},
  2769  					GracePeriod: gracePeriod,
  2770  				},
  2771  				{
  2772  					Signal:   evictionapi.SignalContainerFsInodesFree,
  2773  					Operator: evictionapi.OpLessThan,
  2774  					Value: evictionapi.ThresholdValue{
  2775  						Quantity: quantityMustParse("500Mi"),
  2776  					},
  2777  					MinReclaim: &evictionapi.ThresholdValue{
  2778  						Quantity: quantityMustParse("5Gi"),
  2779  					},
  2780  				},
  2781  			},
  2782  		},
  2783  		{
  2784  			description: "container and image are separate; expect error",
  2785  			imageFs:     true,
  2786  			containerFs: true,
  2787  			expectErr:   true,
  2788  			expectedContainerFsHard: evictionapi.Threshold{
  2789  				Signal:   evictionapi.SignalContainerFsAvailable,
  2790  				Operator: evictionapi.OpLessThan,
  2791  				Value: evictionapi.ThresholdValue{
  2792  					Quantity: quantityMustParse("100Mi"),
  2793  				},
  2794  				MinReclaim: &evictionapi.ThresholdValue{
  2795  					Quantity: quantityMustParse("1Gi"),
  2796  				},
  2797  			},
  2798  			expectedContainerFsSoft: evictionapi.Threshold{
  2799  				Signal:   evictionapi.SignalContainerFsAvailable,
  2800  				Operator: evictionapi.OpLessThan,
  2801  				Value: evictionapi.ThresholdValue{
  2802  					Quantity: quantityMustParse("200Mi"),
  2803  				},
  2804  				GracePeriod: gracePeriod,
  2805  				MinReclaim: &evictionapi.ThresholdValue{
  2806  					Quantity: quantityMustParse("1Gi"),
  2807  				},
  2808  			},
  2809  			expectedContainerFsINodesHard: evictionapi.Threshold{
  2810  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2811  				Operator: evictionapi.OpLessThan,
  2812  				Value: evictionapi.ThresholdValue{
  2813  					Quantity: quantityMustParse("100Mi"),
  2814  				},
  2815  				MinReclaim: &evictionapi.ThresholdValue{
  2816  					Quantity: quantityMustParse("1Gi"),
  2817  				},
  2818  			},
  2819  			expectedContainerFsINodesSoft: evictionapi.Threshold{
  2820  				Signal:   evictionapi.SignalContainerFsInodesFree,
  2821  				Operator: evictionapi.OpLessThan,
  2822  				Value: evictionapi.ThresholdValue{
  2823  					Quantity: quantityMustParse("200Mi"),
  2824  				},
  2825  				MinReclaim: &evictionapi.ThresholdValue{
  2826  					Quantity: quantityMustParse("2Gi"),
  2827  				},
  2828  				GracePeriod: gracePeriod,
  2829  			},
  2830  			thresholdList: []evictionapi.Threshold{
  2831  				{
  2832  					Signal:   evictionapi.SignalNodeFsAvailable,
  2833  					Operator: evictionapi.OpLessThan,
  2834  					Value: evictionapi.ThresholdValue{
  2835  						Quantity: quantityMustParse("100Mi"),
  2836  					},
  2837  					MinReclaim: &evictionapi.ThresholdValue{
  2838  						Quantity: quantityMustParse("1Gi"),
  2839  					},
  2840  				},
  2841  				{
  2842  					Signal:   evictionapi.SignalNodeFsAvailable,
  2843  					Operator: evictionapi.OpLessThan,
  2844  					Value: evictionapi.ThresholdValue{
  2845  						Quantity: quantityMustParse("200Mi"),
  2846  					},
  2847  					GracePeriod: gracePeriod,
  2848  					MinReclaim: &evictionapi.ThresholdValue{
  2849  						Quantity: quantityMustParse("1Gi"),
  2850  					},
  2851  				},
  2852  				{
  2853  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2854  					Operator: evictionapi.OpLessThan,
  2855  					Value: evictionapi.ThresholdValue{
  2856  						Quantity: quantityMustParse("200Mi"),
  2857  					},
  2858  					MinReclaim: &evictionapi.ThresholdValue{
  2859  						Quantity: quantityMustParse("2Gi"),
  2860  					},
  2861  					GracePeriod: gracePeriod,
  2862  				},
  2863  				{
  2864  					Signal:   evictionapi.SignalNodeFsInodesFree,
  2865  					Operator: evictionapi.OpLessThan,
  2866  					Value: evictionapi.ThresholdValue{
  2867  						Quantity: quantityMustParse("100Mi"),
  2868  					},
  2869  					MinReclaim: &evictionapi.ThresholdValue{
  2870  						Quantity: quantityMustParse("1Gi"),
  2871  					},
  2872  				},
  2873  				{
  2874  					Signal:   evictionapi.SignalImageFsAvailable,
  2875  					Operator: evictionapi.OpLessThan,
  2876  					Value: evictionapi.ThresholdValue{
  2877  						Quantity: quantityMustParse("150Mi"),
  2878  					},
  2879  					MinReclaim: &evictionapi.ThresholdValue{
  2880  						Quantity: quantityMustParse("1.5Gi"),
  2881  					},
  2882  				},
  2883  				{
  2884  					Signal:   evictionapi.SignalImageFsAvailable,
  2885  					Operator: evictionapi.OpLessThan,
  2886  					Value: evictionapi.ThresholdValue{
  2887  						Quantity: quantityMustParse("300Mi"),
  2888  					},
  2889  					GracePeriod: gracePeriod,
  2890  					MinReclaim: &evictionapi.ThresholdValue{
  2891  						Quantity: quantityMustParse("3Gi"),
  2892  					},
  2893  				},
  2894  				{
  2895  					Signal:   evictionapi.SignalImageFsInodesFree,
  2896  					Operator: evictionapi.OpLessThan,
  2897  					Value: evictionapi.ThresholdValue{
  2898  						Quantity: quantityMustParse("150Mi"),
  2899  					},
  2900  					MinReclaim: &evictionapi.ThresholdValue{
  2901  						Quantity: quantityMustParse("2Gi"),
  2902  					},
  2903  					GracePeriod: gracePeriod,
  2904  				},
  2905  				{
  2906  					Signal:   evictionapi.SignalImageFsInodesFree,
  2907  					Operator: evictionapi.OpLessThan,
  2908  					Value: evictionapi.ThresholdValue{
  2909  						Quantity: quantityMustParse("300Mi"),
  2910  					},
  2911  					MinReclaim: &evictionapi.ThresholdValue{
  2912  						Quantity: quantityMustParse("2Gi"),
  2913  					},
  2914  				},
  2915  				{
  2916  					Signal:   evictionapi.SignalContainerFsAvailable,
  2917  					Operator: evictionapi.OpLessThan,
  2918  					Value: evictionapi.ThresholdValue{
  2919  						Quantity: quantityMustParse("500Mi"),
  2920  					},
  2921  					MinReclaim: &evictionapi.ThresholdValue{
  2922  						Quantity: quantityMustParse("5Gi"),
  2923  					},
  2924  				},
  2925  
  2926  				{
  2927  					Signal:   evictionapi.SignalContainerFsAvailable,
  2928  					Operator: evictionapi.OpLessThan,
  2929  					Value: evictionapi.ThresholdValue{
  2930  						Quantity: quantityMustParse("500Mi"),
  2931  					},
  2932  					GracePeriod: gracePeriod,
  2933  					MinReclaim: &evictionapi.ThresholdValue{
  2934  						Quantity: quantityMustParse("5Gi"),
  2935  					},
  2936  				},
  2937  				{
  2938  					Signal:   evictionapi.SignalContainerFsInodesFree,
  2939  					Operator: evictionapi.OpLessThan,
  2940  					Value: evictionapi.ThresholdValue{
  2941  						Quantity: quantityMustParse("500Mi"),
  2942  					},
  2943  					MinReclaim: &evictionapi.ThresholdValue{
  2944  						Quantity: quantityMustParse("5Gi"),
  2945  					},
  2946  					GracePeriod: gracePeriod,
  2947  				},
  2948  				{
  2949  					Signal:   evictionapi.SignalContainerFsInodesFree,
  2950  					Operator: evictionapi.OpLessThan,
  2951  					Value: evictionapi.ThresholdValue{
  2952  						Quantity: quantityMustParse("500Mi"),
  2953  					},
  2954  					MinReclaim: &evictionapi.ThresholdValue{
  2955  						Quantity: quantityMustParse("5Gi"),
  2956  					},
  2957  				},
  2958  			},
  2959  		},
  2960  	}
  2961  
  2962  	for _, testCase := range testCases {
  2963  		t.Run(testCase.description, func(t *testing.T) {
  2964  			expected, err := UpdateContainerFsThresholds(testCase.thresholdList, testCase.imageFs, testCase.containerFs)
  2965  			if err != nil && !testCase.expectErr {
  2966  				t.Fatalf("got error but did not expect any")
  2967  			}
  2968  			hardContainerFsMatch := -1
  2969  			softContainerFsMatch := -1
  2970  			hardContainerFsINodesMatch := -1
  2971  			softContainerFsINodesMatch := -1
  2972  			for idx, val := range expected {
  2973  				if val.Signal == evictionapi.SignalContainerFsAvailable && isHardEvictionThreshold(val) {
  2974  					if !reflect.DeepEqual(val, testCase.expectedContainerFsHard) {
  2975  						t.Fatalf("want %v got %v", testCase.expectedContainerFsHard, val)
  2976  					}
  2977  					hardContainerFsMatch = idx
  2978  				}
  2979  				if val.Signal == evictionapi.SignalContainerFsAvailable && !isHardEvictionThreshold(val) {
  2980  					if !reflect.DeepEqual(val, testCase.expectedContainerFsSoft) {
  2981  						t.Fatalf("want %v got %v", testCase.expectedContainerFsSoft, val)
  2982  					}
  2983  					softContainerFsMatch = idx
  2984  				}
  2985  				if val.Signal == evictionapi.SignalContainerFsInodesFree && isHardEvictionThreshold(val) {
  2986  					if !reflect.DeepEqual(val, testCase.expectedContainerFsINodesHard) {
  2987  						t.Fatalf("want %v got %v", testCase.expectedContainerFsINodesHard, val)
  2988  					}
  2989  					hardContainerFsINodesMatch = idx
  2990  				}
  2991  				if val.Signal == evictionapi.SignalContainerFsInodesFree && !isHardEvictionThreshold(val) {
  2992  					if !reflect.DeepEqual(val, testCase.expectedContainerFsINodesSoft) {
  2993  						t.Fatalf("want %v got %v", testCase.expectedContainerFsINodesSoft, val)
  2994  					}
  2995  					softContainerFsINodesMatch = idx
  2996  				}
  2997  			}
  2998  			if hardContainerFsMatch == -1 {
  2999  				t.Fatalf("did not find hard containerfs.available")
  3000  			}
  3001  			if softContainerFsMatch == -1 {
  3002  				t.Fatalf("did not find soft containerfs.available")
  3003  			}
  3004  			if hardContainerFsINodesMatch == -1 {
  3005  				t.Fatalf("did not find hard containerfs.inodesfree")
  3006  			}
  3007  			if softContainerFsINodesMatch == -1 {
  3008  				t.Fatalf("did not find soft containerfs.inodesfree")
  3009  			}
  3010  		})
  3011  	}
  3012  }
  3013  
  3014  // newPodInodeStats returns stats with specified usage amounts.
  3015  func newPodInodeStats(pod *v1.Pod, rootFsInodesUsed, logsInodesUsed, perLocalVolumeInodesUsed resource.Quantity) statsapi.PodStats {
  3016  	result := statsapi.PodStats{
  3017  		PodRef: statsapi.PodReference{
  3018  			Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  3019  		},
  3020  	}
  3021  	rootFsUsed := uint64(rootFsInodesUsed.Value())
  3022  	logsUsed := uint64(logsInodesUsed.Value())
  3023  	for range pod.Spec.Containers {
  3024  		result.Containers = append(result.Containers, statsapi.ContainerStats{
  3025  			Rootfs: &statsapi.FsStats{
  3026  				InodesUsed: &rootFsUsed,
  3027  			},
  3028  			Logs: &statsapi.FsStats{
  3029  				InodesUsed: &logsUsed,
  3030  			},
  3031  		})
  3032  	}
  3033  
  3034  	perLocalVolumeUsed := uint64(perLocalVolumeInodesUsed.Value())
  3035  	for _, volumeName := range localVolumeNames(pod) {
  3036  		result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
  3037  			Name: volumeName,
  3038  			FsStats: statsapi.FsStats{
  3039  				InodesUsed: &perLocalVolumeUsed,
  3040  			},
  3041  		})
  3042  	}
  3043  	return result
  3044  }
  3045  
  3046  // newPodDiskStats returns stats with specified usage amounts.
  3047  func newPodDiskStats(pod *v1.Pod, rootFsUsed, logsUsed, perLocalVolumeUsed resource.Quantity) statsapi.PodStats {
  3048  	result := statsapi.PodStats{
  3049  		PodRef: statsapi.PodReference{
  3050  			Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  3051  		},
  3052  	}
  3053  
  3054  	rootFsUsedBytes := uint64(rootFsUsed.Value())
  3055  	logsUsedBytes := uint64(logsUsed.Value())
  3056  	for range pod.Spec.Containers {
  3057  		result.Containers = append(result.Containers, statsapi.ContainerStats{
  3058  			Rootfs: &statsapi.FsStats{
  3059  				UsedBytes: &rootFsUsedBytes,
  3060  			},
  3061  			Logs: &statsapi.FsStats{
  3062  				UsedBytes: &logsUsedBytes,
  3063  			},
  3064  		})
  3065  	}
  3066  
  3067  	perLocalVolumeUsedBytes := uint64(perLocalVolumeUsed.Value())
  3068  	for _, volumeName := range localVolumeNames(pod) {
  3069  		result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
  3070  			Name: volumeName,
  3071  			FsStats: statsapi.FsStats{
  3072  				UsedBytes: &perLocalVolumeUsedBytes,
  3073  			},
  3074  		})
  3075  	}
  3076  
  3077  	return result
  3078  }
  3079  
  3080  func newPodMemoryStats(pod *v1.Pod, workingSet resource.Quantity) statsapi.PodStats {
  3081  	workingSetBytes := uint64(workingSet.Value())
  3082  	return statsapi.PodStats{
  3083  		PodRef: statsapi.PodReference{
  3084  			Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  3085  		},
  3086  		Memory: &statsapi.MemoryStats{
  3087  			WorkingSetBytes: &workingSetBytes,
  3088  		},
  3089  		VolumeStats: []statsapi.VolumeStats{
  3090  			{
  3091  				FsStats: statsapi.FsStats{
  3092  					UsedBytes: &workingSetBytes,
  3093  				},
  3094  				Name: "local-volume",
  3095  			},
  3096  		},
  3097  		Containers: []statsapi.ContainerStats{
  3098  			{
  3099  				Name: pod.Name,
  3100  				Logs: &statsapi.FsStats{
  3101  					UsedBytes: &workingSetBytes,
  3102  				},
  3103  				Rootfs: &statsapi.FsStats{UsedBytes: &workingSetBytes},
  3104  			},
  3105  		},
  3106  		EphemeralStorage: &statsapi.FsStats{UsedBytes: &workingSetBytes},
  3107  	}
  3108  }
  3109  
  3110  func newPodProcessStats(pod *v1.Pod, num uint64) statsapi.PodStats {
  3111  	return statsapi.PodStats{
  3112  		PodRef: statsapi.PodReference{
  3113  			Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  3114  		},
  3115  		ProcessStats: &statsapi.ProcessStats{
  3116  			ProcessCount: &num,
  3117  		},
  3118  	}
  3119  }
  3120  
  3121  func newResourceList(cpu, memory, disk string) v1.ResourceList {
  3122  	res := v1.ResourceList{}
  3123  	if cpu != "" {
  3124  		res[v1.ResourceCPU] = resource.MustParse(cpu)
  3125  	}
  3126  	if memory != "" {
  3127  		res[v1.ResourceMemory] = resource.MustParse(memory)
  3128  	}
  3129  	if disk != "" {
  3130  		res[v1.ResourceEphemeralStorage] = resource.MustParse(disk)
  3131  	}
  3132  	return res
  3133  }
  3134  
  3135  func newResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequirements {
  3136  	res := v1.ResourceRequirements{}
  3137  	res.Requests = requests
  3138  	res.Limits = limits
  3139  	return res
  3140  }
  3141  
  3142  func newContainer(name string, requests v1.ResourceList, limits v1.ResourceList) v1.Container {
  3143  	return v1.Container{
  3144  		Name:      name,
  3145  		Resources: newResourceRequirements(requests, limits),
  3146  	}
  3147  }
  3148  
  3149  func newVolume(name string, volumeSource v1.VolumeSource) v1.Volume {
  3150  	return v1.Volume{
  3151  		Name:         name,
  3152  		VolumeSource: volumeSource,
  3153  	}
  3154  }
  3155  
  3156  // newPod uses the name as the uid.  Make names unique for testing.
  3157  func newPod(name string, priority int32, containers []v1.Container, volumes []v1.Volume) *v1.Pod {
  3158  	return &v1.Pod{
  3159  		ObjectMeta: metav1.ObjectMeta{
  3160  			Name: name,
  3161  			UID:  types.UID(name),
  3162  		},
  3163  		Spec: v1.PodSpec{
  3164  			Containers: containers,
  3165  			Volumes:    volumes,
  3166  			Priority:   &priority,
  3167  		},
  3168  	}
  3169  }
  3170  
  3171  // nodeConditionList is a simple alias to support equality checking independent of order
  3172  type nodeConditionList []v1.NodeConditionType
  3173  
  3174  // Equal adds the ability to check equality between two lists of node conditions.
  3175  func (s1 nodeConditionList) Equal(s2 nodeConditionList) bool {
  3176  	if len(s1) != len(s2) {
  3177  		return false
  3178  	}
  3179  	for _, item := range s1 {
  3180  		if !hasNodeCondition(s2, item) {
  3181  			return false
  3182  		}
  3183  	}
  3184  	return true
  3185  }
  3186  
  3187  // thresholdList is a simple alias to support equality checking independent of order
  3188  type thresholdList []evictionapi.Threshold
  3189  
  3190  // Equal adds the ability to check equality between two lists of node conditions.
  3191  func (s1 thresholdList) Equal(s2 thresholdList) bool {
  3192  	if len(s1) != len(s2) {
  3193  		return false
  3194  	}
  3195  	for _, item := range s1 {
  3196  		if !hasThreshold(s2, item) {
  3197  			return false
  3198  		}
  3199  	}
  3200  	return true
  3201  }
  3202  
  3203  func TestEvictonMessageWithResourceResize(t *testing.T) {
  3204  	testpod := newPod("testpod", 1, []v1.Container{
  3205  		newContainer("testcontainer", newResourceList("", "200Mi", ""), newResourceList("", "", "")),
  3206  	}, nil)
  3207  	testpod.Status = v1.PodStatus{
  3208  		ContainerStatuses: []v1.ContainerStatus{
  3209  			{
  3210  				Name:               "testcontainer",
  3211  				AllocatedResources: newResourceList("", "100Mi", ""),
  3212  			},
  3213  		},
  3214  	}
  3215  	testpodMemory := resource.MustParse("150Mi")
  3216  	testpodStats := newPodMemoryStats(testpod, testpodMemory)
  3217  	testpodMemoryBytes := uint64(testpodMemory.Value())
  3218  	testpodStats.Containers = []statsapi.ContainerStats{
  3219  		{
  3220  			Name: "testcontainer",
  3221  			Memory: &statsapi.MemoryStats{
  3222  				WorkingSetBytes: &testpodMemoryBytes,
  3223  			},
  3224  		},
  3225  	}
  3226  	stats := map[*v1.Pod]statsapi.PodStats{
  3227  		testpod: testpodStats,
  3228  	}
  3229  	statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  3230  		result, found := stats[pod]
  3231  		return result, found
  3232  	}
  3233  	threshold := []evictionapi.Threshold{}
  3234  	observations := signalObservations{}
  3235  
  3236  	for _, enabled := range []bool{true, false} {
  3237  		t.Run(fmt.Sprintf("InPlacePodVerticalScaling enabled=%v", enabled), func(t *testing.T) {
  3238  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, enabled)()
  3239  			msg, _ := evictionMessage(v1.ResourceMemory, testpod, statsFn, threshold, observations)
  3240  			if enabled {
  3241  				if !strings.Contains(msg, "testcontainer was using 150Mi, request is 100Mi") {
  3242  					t.Errorf("Expected 'exceeds memory' eviction message was not found.")
  3243  				}
  3244  			} else {
  3245  				if strings.Contains(msg, "which exceeds its request") {
  3246  					t.Errorf("Found 'exceeds memory' eviction message which was not expected.")
  3247  				}
  3248  			}
  3249  		})
  3250  	}
  3251  }
  3252  

View as plain text