...

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

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

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package eviction
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	gomock "github.com/golang/mock/gomock"
    27  	"k8s.io/apimachinery/pkg/api/resource"
    28  	statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
    29  	evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
    30  )
    31  
    32  const testCgroupPath = "/sys/fs/cgroups/memory"
    33  
    34  func nodeSummary(available, workingSet, usage resource.Quantity, allocatable bool) *statsapi.Summary {
    35  	availableBytes := uint64(available.Value())
    36  	workingSetBytes := uint64(workingSet.Value())
    37  	usageBytes := uint64(usage.Value())
    38  	memoryStats := statsapi.MemoryStats{
    39  		AvailableBytes:  &availableBytes,
    40  		WorkingSetBytes: &workingSetBytes,
    41  		UsageBytes:      &usageBytes,
    42  	}
    43  	if allocatable {
    44  		return &statsapi.Summary{
    45  			Node: statsapi.NodeStats{
    46  				SystemContainers: []statsapi.ContainerStats{
    47  					{
    48  						Name:   statsapi.SystemContainerPods,
    49  						Memory: &memoryStats,
    50  					},
    51  				},
    52  			},
    53  		}
    54  	}
    55  	return &statsapi.Summary{
    56  		Node: statsapi.NodeStats{
    57  			Memory: &memoryStats,
    58  		},
    59  	}
    60  }
    61  
    62  func newTestMemoryThresholdNotifier(threshold evictionapi.Threshold, factory NotifierFactory, handler func(string)) *memoryThresholdNotifier {
    63  	return &memoryThresholdNotifier{
    64  		threshold:  threshold,
    65  		cgroupPath: testCgroupPath,
    66  		events:     make(chan struct{}),
    67  		factory:    factory,
    68  		handler:    handler,
    69  	}
    70  }
    71  
    72  func TestUpdateThreshold(t *testing.T) {
    73  	testCases := []struct {
    74  		description        string
    75  		available          resource.Quantity
    76  		workingSet         resource.Quantity
    77  		usage              resource.Quantity
    78  		evictionThreshold  evictionapi.Threshold
    79  		expectedThreshold  resource.Quantity
    80  		updateThresholdErr error
    81  		expectErr          bool
    82  	}{
    83  		{
    84  			description: "node level threshold",
    85  			available:   resource.MustParse("3Gi"),
    86  			usage:       resource.MustParse("2Gi"),
    87  			workingSet:  resource.MustParse("1Gi"),
    88  			evictionThreshold: evictionapi.Threshold{
    89  				Signal:   evictionapi.SignalMemoryAvailable,
    90  				Operator: evictionapi.OpLessThan,
    91  				Value: evictionapi.ThresholdValue{
    92  					Quantity: quantityMustParse("1Gi"),
    93  				},
    94  			},
    95  			expectedThreshold:  resource.MustParse("4Gi"),
    96  			updateThresholdErr: nil,
    97  			expectErr:          false,
    98  		},
    99  		{
   100  			description: "allocatable threshold",
   101  			available:   resource.MustParse("4Gi"),
   102  			usage:       resource.MustParse("3Gi"),
   103  			workingSet:  resource.MustParse("1Gi"),
   104  			evictionThreshold: evictionapi.Threshold{
   105  				Signal:   evictionapi.SignalAllocatableMemoryAvailable,
   106  				Operator: evictionapi.OpLessThan,
   107  				Value: evictionapi.ThresholdValue{
   108  					Quantity: quantityMustParse("1Gi"),
   109  				},
   110  			},
   111  			expectedThreshold:  resource.MustParse("6Gi"),
   112  			updateThresholdErr: nil,
   113  			expectErr:          false,
   114  		},
   115  		{
   116  			description: "error updating node level threshold",
   117  			available:   resource.MustParse("3Gi"),
   118  			usage:       resource.MustParse("2Gi"),
   119  			workingSet:  resource.MustParse("1Gi"),
   120  			evictionThreshold: evictionapi.Threshold{
   121  				Signal:   evictionapi.SignalMemoryAvailable,
   122  				Operator: evictionapi.OpLessThan,
   123  				Value: evictionapi.ThresholdValue{
   124  					Quantity: quantityMustParse("1Gi"),
   125  				},
   126  			},
   127  			expectedThreshold:  resource.MustParse("4Gi"),
   128  			updateThresholdErr: fmt.Errorf("unexpected error"),
   129  			expectErr:          true,
   130  		},
   131  	}
   132  
   133  	mockCtrl := gomock.NewController(t)
   134  	defer mockCtrl.Finish()
   135  
   136  	for _, tc := range testCases {
   137  		t.Run(tc.description, func(t *testing.T) {
   138  			notifierFactory := NewMockNotifierFactory(mockCtrl)
   139  			notifier := NewMockCgroupNotifier(mockCtrl)
   140  
   141  			m := newTestMemoryThresholdNotifier(tc.evictionThreshold, notifierFactory, nil)
   142  			notifierFactory.EXPECT().NewCgroupNotifier(testCgroupPath, memoryUsageAttribute, tc.expectedThreshold.Value()).Return(notifier, tc.updateThresholdErr).Times(1)
   143  			var events chan<- struct{} = m.events
   144  			notifier.EXPECT().Start(events).Return().AnyTimes()
   145  			err := m.UpdateThreshold(nodeSummary(tc.available, tc.workingSet, tc.usage, isAllocatableEvictionThreshold(tc.evictionThreshold)))
   146  			if err != nil && !tc.expectErr {
   147  				t.Errorf("Unexpected error updating threshold: %v", err)
   148  			} else if err == nil && tc.expectErr {
   149  				t.Errorf("Expected error updating threshold, but got nil")
   150  			}
   151  		})
   152  	}
   153  }
   154  
   155  func TestStart(t *testing.T) {
   156  	noResources := resource.MustParse("0")
   157  	threshold := evictionapi.Threshold{
   158  		Signal:   evictionapi.SignalMemoryAvailable,
   159  		Operator: evictionapi.OpLessThan,
   160  		Value: evictionapi.ThresholdValue{
   161  			Quantity: &noResources,
   162  		},
   163  	}
   164  	mockCtrl := gomock.NewController(t)
   165  	defer mockCtrl.Finish()
   166  	notifierFactory := NewMockNotifierFactory(mockCtrl)
   167  	notifier := NewMockCgroupNotifier(mockCtrl)
   168  
   169  	var wg sync.WaitGroup
   170  	wg.Add(4)
   171  	m := newTestMemoryThresholdNotifier(threshold, notifierFactory, func(string) {
   172  		wg.Done()
   173  	})
   174  	notifierFactory.EXPECT().NewCgroupNotifier(testCgroupPath, memoryUsageAttribute, int64(0)).Return(notifier, nil).Times(1)
   175  
   176  	var events chan<- struct{} = m.events
   177  	notifier.EXPECT().Start(events).DoAndReturn(func(events chan<- struct{}) {
   178  		for i := 0; i < 4; i++ {
   179  			events <- struct{}{}
   180  		}
   181  	})
   182  
   183  	err := m.UpdateThreshold(nodeSummary(noResources, noResources, noResources, isAllocatableEvictionThreshold(threshold)))
   184  	if err != nil {
   185  		t.Errorf("Unexpected error updating threshold: %v", err)
   186  	}
   187  
   188  	go m.Start()
   189  
   190  	wg.Wait()
   191  }
   192  
   193  func TestThresholdDescription(t *testing.T) {
   194  	testCases := []struct {
   195  		description        string
   196  		evictionThreshold  evictionapi.Threshold
   197  		expectedSubstrings []string
   198  		omittedSubstrings  []string
   199  	}{
   200  		{
   201  			description: "hard node level threshold",
   202  			evictionThreshold: evictionapi.Threshold{
   203  				Signal:   evictionapi.SignalMemoryAvailable,
   204  				Operator: evictionapi.OpLessThan,
   205  				Value: evictionapi.ThresholdValue{
   206  					Quantity: quantityMustParse("2Gi"),
   207  				},
   208  			},
   209  			expectedSubstrings: []string{"hard"},
   210  			omittedSubstrings:  []string{"allocatable", "soft"},
   211  		},
   212  		{
   213  			description: "soft node level threshold",
   214  			evictionThreshold: evictionapi.Threshold{
   215  				Signal:   evictionapi.SignalMemoryAvailable,
   216  				Operator: evictionapi.OpLessThan,
   217  				Value: evictionapi.ThresholdValue{
   218  					Quantity: quantityMustParse("2Gi"),
   219  				},
   220  				GracePeriod: time.Minute * 2,
   221  			},
   222  			expectedSubstrings: []string{"soft"},
   223  			omittedSubstrings:  []string{"allocatable", "hard"},
   224  		},
   225  		{
   226  			description: "hard allocatable threshold",
   227  			evictionThreshold: evictionapi.Threshold{
   228  				Signal:   evictionapi.SignalAllocatableMemoryAvailable,
   229  				Operator: evictionapi.OpLessThan,
   230  				Value: evictionapi.ThresholdValue{
   231  					Quantity: quantityMustParse("2Gi"),
   232  				},
   233  				GracePeriod: time.Minute * 2,
   234  			},
   235  			expectedSubstrings: []string{"soft", "allocatable"},
   236  			omittedSubstrings:  []string{"hard"},
   237  		},
   238  		{
   239  			description: "soft allocatable threshold",
   240  			evictionThreshold: evictionapi.Threshold{
   241  				Signal:   evictionapi.SignalAllocatableMemoryAvailable,
   242  				Operator: evictionapi.OpLessThan,
   243  				Value: evictionapi.ThresholdValue{
   244  					Quantity: quantityMustParse("2Gi"),
   245  				},
   246  			},
   247  			expectedSubstrings: []string{"hard", "allocatable"},
   248  			omittedSubstrings:  []string{"soft"},
   249  		},
   250  	}
   251  
   252  	for _, tc := range testCases {
   253  		t.Run(tc.description, func(t *testing.T) {
   254  			m := &memoryThresholdNotifier{
   255  				notifier:   &MockCgroupNotifier{},
   256  				threshold:  tc.evictionThreshold,
   257  				cgroupPath: testCgroupPath,
   258  			}
   259  			desc := m.Description()
   260  			for _, expected := range tc.expectedSubstrings {
   261  				if !strings.Contains(desc, expected) {
   262  					t.Errorf("expected description for notifier with threshold %+v to contain %s, but it did not", tc.evictionThreshold, expected)
   263  				}
   264  			}
   265  			for _, omitted := range tc.omittedSubstrings {
   266  				if strings.Contains(desc, omitted) {
   267  					t.Errorf("expected description for notifier with threshold %+v NOT to contain %s, but it did", tc.evictionThreshold, omitted)
   268  				}
   269  			}
   270  		})
   271  	}
   272  }
   273  

View as plain text