...

Source file src/k8s.io/kubernetes/pkg/kubelet/kuberuntime/helpers_linux_test.go

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

     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 kuberuntime
    18  
    19  import (
    20  	"path/filepath"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    28  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    29  	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
    30  	"k8s.io/kubernetes/pkg/features"
    31  	"k8s.io/kubernetes/pkg/kubelet/cm"
    32  )
    33  
    34  func seccompLocalhostRef(profileName string) string {
    35  	return filepath.Join(fakeSeccompProfileRoot, profileName)
    36  }
    37  
    38  func TestMilliCPUToQuota(t *testing.T) {
    39  	for _, testCase := range []struct {
    40  		msg      string
    41  		input    int64
    42  		expected int64
    43  		period   uint64
    44  	}{
    45  		{
    46  			msg:      "all-zero",
    47  			input:    int64(0),
    48  			expected: int64(0),
    49  			period:   uint64(0),
    50  		},
    51  		{
    52  			msg:      "5 input default quota and period",
    53  			input:    int64(5),
    54  			expected: int64(1000),
    55  			period:   uint64(100000),
    56  		},
    57  		{
    58  			msg:      "9 input default quota and period",
    59  			input:    int64(9),
    60  			expected: int64(1000),
    61  			period:   uint64(100000),
    62  		},
    63  		{
    64  			msg:      "10 input default quota and period",
    65  			input:    int64(10),
    66  			expected: int64(1000),
    67  			period:   uint64(100000),
    68  		},
    69  		{
    70  			msg:      "200 input 20k quota and default period",
    71  			input:    int64(200),
    72  			expected: int64(20000),
    73  			period:   uint64(100000),
    74  		},
    75  		{
    76  			msg:      "500 input 50k quota and default period",
    77  			input:    int64(500),
    78  			expected: int64(50000),
    79  			period:   uint64(100000),
    80  		},
    81  		{
    82  			msg:      "1k input 100k quota and default period",
    83  			input:    int64(1000),
    84  			expected: int64(100000),
    85  			period:   uint64(100000),
    86  		},
    87  		{
    88  			msg:      "1500 input 150k quota and default period",
    89  			input:    int64(1500),
    90  			expected: int64(150000),
    91  			period:   uint64(100000),
    92  		}} {
    93  		t.Run(testCase.msg, func(t *testing.T) {
    94  			quota := milliCPUToQuota(testCase.input, int64(testCase.period))
    95  			if quota != testCase.expected {
    96  				t.Errorf("Input %v and %v, expected quota %v, but got quota %v", testCase.input, testCase.period, testCase.expected, quota)
    97  			}
    98  		})
    99  	}
   100  }
   101  
   102  func TestMilliCPUToQuotaWithCustomCPUCFSQuotaPeriod(t *testing.T) {
   103  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CPUCFSQuotaPeriod, true)()
   104  
   105  	for _, testCase := range []struct {
   106  		msg      string
   107  		input    int64
   108  		expected int64
   109  		period   uint64
   110  	}{
   111  		{
   112  			msg:      "all-zero",
   113  			input:    int64(0),
   114  			expected: int64(0),
   115  			period:   uint64(0),
   116  		},
   117  		{
   118  			msg:      "5 input default quota and period",
   119  			input:    int64(5),
   120  			expected: minQuotaPeriod,
   121  			period:   uint64(100000),
   122  		},
   123  		{
   124  			msg:      "9 input default quota and period",
   125  			input:    int64(9),
   126  			expected: minQuotaPeriod,
   127  			period:   uint64(100000),
   128  		},
   129  		{
   130  			msg:      "10 input default quota and period",
   131  			input:    int64(10),
   132  			expected: minQuotaPeriod,
   133  			period:   uint64(100000),
   134  		},
   135  		{
   136  			msg:      "200 input 20k quota and default period",
   137  			input:    int64(200),
   138  			expected: int64(20000),
   139  			period:   uint64(100000),
   140  		},
   141  		{
   142  			msg:      "500 input 50k quota and default period",
   143  			input:    int64(500),
   144  			expected: int64(50000),
   145  			period:   uint64(100000),
   146  		},
   147  		{
   148  			msg:      "1k input 100k quota and default period",
   149  			input:    int64(1000),
   150  			expected: int64(100000),
   151  			period:   uint64(100000),
   152  		},
   153  		{
   154  			msg:      "1500 input 150k quota and default period",
   155  			input:    int64(1500),
   156  			expected: int64(150000),
   157  			period:   uint64(100000),
   158  		},
   159  		{
   160  			msg:      "5 input 10k period and default quota expected",
   161  			input:    int64(5),
   162  			period:   uint64(10000),
   163  			expected: minQuotaPeriod,
   164  		},
   165  		{
   166  			msg:      "5 input 5k period and default quota expected",
   167  			input:    int64(5),
   168  			period:   uint64(5000),
   169  			expected: minQuotaPeriod,
   170  		},
   171  		{
   172  			msg:      "9 input 10k period and default quota expected",
   173  			input:    int64(9),
   174  			period:   uint64(10000),
   175  			expected: minQuotaPeriod,
   176  		},
   177  		{
   178  			msg:      "10 input 200k period and 2000 quota expected",
   179  			input:    int64(10),
   180  			period:   uint64(200000),
   181  			expected: int64(2000),
   182  		},
   183  		{
   184  			msg:      "200 input 200k period and 40k quota",
   185  			input:    int64(200),
   186  			period:   uint64(200000),
   187  			expected: int64(40000),
   188  		},
   189  		{
   190  			msg:      "500 input 20k period and 20k expected quota",
   191  			input:    int64(500),
   192  			period:   uint64(20000),
   193  			expected: int64(10000),
   194  		},
   195  		{
   196  			msg:      "1000 input 10k period and 10k expected quota",
   197  			input:    int64(1000),
   198  			period:   uint64(10000),
   199  			expected: int64(10000),
   200  		},
   201  		{
   202  			msg:      "1500 input 5000 period and 7500 expected quota",
   203  			input:    int64(1500),
   204  			period:   uint64(5000),
   205  			expected: int64(7500),
   206  		}} {
   207  		t.Run(testCase.msg, func(t *testing.T) {
   208  			quota := milliCPUToQuota(testCase.input, int64(testCase.period))
   209  			if quota != testCase.expected {
   210  				t.Errorf("Input %v and %v, expected quota %v, but got quota %v", testCase.input, testCase.period, testCase.expected, quota)
   211  			}
   212  		})
   213  	}
   214  }
   215  
   216  func TestGetSeccompProfile(t *testing.T) {
   217  	_, _, m, err := createTestRuntimeManager()
   218  	require.NoError(t, err)
   219  
   220  	unconfinedProfile := &runtimeapi.SecurityProfile{
   221  		ProfileType: runtimeapi.SecurityProfile_Unconfined,
   222  	}
   223  
   224  	runtimeDefaultProfile := &runtimeapi.SecurityProfile{
   225  		ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
   226  	}
   227  
   228  	tests := []struct {
   229  		description     string
   230  		annotation      map[string]string
   231  		podSc           *v1.PodSecurityContext
   232  		containerSc     *v1.SecurityContext
   233  		containerName   string
   234  		expectedProfile *runtimeapi.SecurityProfile
   235  		expectedError   string
   236  	}{
   237  		{
   238  			description:     "no seccomp should return unconfined",
   239  			expectedProfile: unconfinedProfile,
   240  		},
   241  		{
   242  			description:     "pod seccomp profile set to unconfined returns unconfined",
   243  			podSc:           &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
   244  			expectedProfile: unconfinedProfile,
   245  		},
   246  		{
   247  			description:     "container seccomp profile set to unconfined returns unconfined",
   248  			containerSc:     &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
   249  			expectedProfile: unconfinedProfile,
   250  		},
   251  		{
   252  			description:     "pod seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
   253  			podSc:           &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
   254  			expectedProfile: runtimeDefaultProfile,
   255  		},
   256  		{
   257  			description:     "container seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
   258  			containerSc:     &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
   259  			expectedProfile: runtimeDefaultProfile,
   260  		},
   261  		{
   262  			description: "pod seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
   263  			podSc:       &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename")}},
   264  			expectedProfile: &runtimeapi.SecurityProfile{
   265  				ProfileType:  runtimeapi.SecurityProfile_Localhost,
   266  				LocalhostRef: seccompLocalhostRef("filename"),
   267  			},
   268  		},
   269  		{
   270  			description:   "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
   271  			podSc:         &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
   272  			expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
   273  		},
   274  		{
   275  			description:   "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
   276  			containerSc:   &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
   277  			expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
   278  		},
   279  		{
   280  			description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
   281  			containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename2")}},
   282  			expectedProfile: &runtimeapi.SecurityProfile{
   283  				ProfileType:  runtimeapi.SecurityProfile_Localhost,
   284  				LocalhostRef: seccompLocalhostRef("filename2"),
   285  			},
   286  		},
   287  		{
   288  			description:     "prioritise container field over pod field",
   289  			podSc:           &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
   290  			containerSc:     &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
   291  			expectedProfile: runtimeDefaultProfile,
   292  		},
   293  		{
   294  			description:   "prioritise container field over pod field",
   295  			podSc:         &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-pod-profile.json")}},
   296  			containerSc:   &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-cont-profile.json")}},
   297  			containerName: "container1",
   298  			expectedProfile: &runtimeapi.SecurityProfile{
   299  				ProfileType:  runtimeapi.SecurityProfile_Localhost,
   300  				LocalhostRef: seccompLocalhostRef("field-cont-profile.json"),
   301  			},
   302  		},
   303  	}
   304  
   305  	for i, test := range tests {
   306  		seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, false)
   307  		if test.expectedError != "" {
   308  			assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
   309  		} else {
   310  			assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
   311  			assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
   312  		}
   313  	}
   314  }
   315  
   316  func TestGetSeccompProfileDefaultSeccomp(t *testing.T) {
   317  	_, _, m, err := createTestRuntimeManager()
   318  	require.NoError(t, err)
   319  
   320  	unconfinedProfile := &runtimeapi.SecurityProfile{
   321  		ProfileType: runtimeapi.SecurityProfile_Unconfined,
   322  	}
   323  
   324  	runtimeDefaultProfile := &runtimeapi.SecurityProfile{
   325  		ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
   326  	}
   327  
   328  	tests := []struct {
   329  		description     string
   330  		annotation      map[string]string
   331  		podSc           *v1.PodSecurityContext
   332  		containerSc     *v1.SecurityContext
   333  		containerName   string
   334  		expectedProfile *runtimeapi.SecurityProfile
   335  		expectedError   string
   336  	}{
   337  		{
   338  			description:     "no seccomp should return RuntimeDefault",
   339  			expectedProfile: runtimeDefaultProfile,
   340  		},
   341  		{
   342  			description:     "pod seccomp profile set to unconfined returns unconfined",
   343  			podSc:           &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
   344  			expectedProfile: unconfinedProfile,
   345  		},
   346  		{
   347  			description:     "container seccomp profile set to unconfined returns unconfined",
   348  			containerSc:     &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
   349  			expectedProfile: unconfinedProfile,
   350  		},
   351  		{
   352  			description:     "pod seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
   353  			podSc:           &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
   354  			expectedProfile: runtimeDefaultProfile,
   355  		},
   356  		{
   357  			description:     "container seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
   358  			containerSc:     &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
   359  			expectedProfile: runtimeDefaultProfile,
   360  		},
   361  		{
   362  			description: "pod seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
   363  			podSc:       &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename")}},
   364  			expectedProfile: &runtimeapi.SecurityProfile{
   365  				ProfileType:  runtimeapi.SecurityProfile_Localhost,
   366  				LocalhostRef: seccompLocalhostRef("filename"),
   367  			},
   368  		},
   369  		{
   370  			description:   "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
   371  			podSc:         &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
   372  			expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
   373  		},
   374  		{
   375  			description:   "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
   376  			containerSc:   &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
   377  			expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
   378  		},
   379  		{
   380  			description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
   381  			containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename2")}},
   382  			expectedProfile: &runtimeapi.SecurityProfile{
   383  				ProfileType:  runtimeapi.SecurityProfile_Localhost,
   384  				LocalhostRef: seccompLocalhostRef("filename2"),
   385  			},
   386  		},
   387  		{
   388  			description:     "prioritise container field over pod field",
   389  			podSc:           &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
   390  			containerSc:     &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
   391  			expectedProfile: runtimeDefaultProfile,
   392  		},
   393  		{
   394  			description:   "prioritise container field over pod field",
   395  			podSc:         &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-pod-profile.json")}},
   396  			containerSc:   &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-cont-profile.json")}},
   397  			containerName: "container1",
   398  			expectedProfile: &runtimeapi.SecurityProfile{
   399  				ProfileType:  runtimeapi.SecurityProfile_Localhost,
   400  				LocalhostRef: seccompLocalhostRef("field-cont-profile.json"),
   401  			},
   402  		},
   403  	}
   404  
   405  	for i, test := range tests {
   406  		seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, true)
   407  		if test.expectedError != "" {
   408  			assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
   409  		} else {
   410  			assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
   411  			assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
   412  		}
   413  	}
   414  }
   415  
   416  func getLocal(v string) *string {
   417  	return &v
   418  }
   419  
   420  func TestSharesToMilliCPU(t *testing.T) {
   421  	knownMilliCPUToShares := map[int64]int64{
   422  		0:    2,
   423  		1:    2,
   424  		2:    2,
   425  		3:    3,
   426  		4:    4,
   427  		32:   32,
   428  		64:   65,
   429  		100:  102,
   430  		250:  256,
   431  		500:  512,
   432  		1000: 1024,
   433  		1500: 1536,
   434  		2000: 2048,
   435  	}
   436  
   437  	t.Run("sharesToMilliCPUTest", func(t *testing.T) {
   438  		var testMilliCPU int64
   439  		for testMilliCPU = 0; testMilliCPU <= 2000; testMilliCPU++ {
   440  			shares := int64(cm.MilliCPUToShares(testMilliCPU))
   441  			if expectedShares, found := knownMilliCPUToShares[testMilliCPU]; found {
   442  				if shares != expectedShares {
   443  					t.Errorf("Test milliCPIToShares: Input milliCPU %v, expected shares %v, but got %v", testMilliCPU, expectedShares, shares)
   444  				}
   445  			}
   446  			expectedMilliCPU := testMilliCPU
   447  			if testMilliCPU < 2 {
   448  				expectedMilliCPU = 2
   449  			}
   450  			milliCPU := sharesToMilliCPU(shares)
   451  			if milliCPU != expectedMilliCPU {
   452  				t.Errorf("Test sharesToMilliCPU: Input shares %v, expected milliCPU %v, but got %v", shares, expectedMilliCPU, milliCPU)
   453  			}
   454  		}
   455  	})
   456  }
   457  
   458  func TestQuotaToMilliCPU(t *testing.T) {
   459  	for _, tc := range []struct {
   460  		name     string
   461  		quota    int64
   462  		period   int64
   463  		expected int64
   464  	}{
   465  		{
   466  			name:     "50m",
   467  			quota:    int64(5000),
   468  			period:   int64(100000),
   469  			expected: int64(50),
   470  		},
   471  		{
   472  			name:     "750m",
   473  			quota:    int64(75000),
   474  			period:   int64(100000),
   475  			expected: int64(750),
   476  		},
   477  		{
   478  			name:     "1000m",
   479  			quota:    int64(100000),
   480  			period:   int64(100000),
   481  			expected: int64(1000),
   482  		},
   483  		{
   484  			name:     "1500m",
   485  			quota:    int64(150000),
   486  			period:   int64(100000),
   487  			expected: int64(1500),
   488  		}} {
   489  		t.Run(tc.name, func(t *testing.T) {
   490  			milliCPU := quotaToMilliCPU(tc.quota, tc.period)
   491  			if milliCPU != tc.expected {
   492  				t.Errorf("Test %s: Input quota %v and period %v, expected milliCPU %v, but got %v", tc.name, tc.quota, tc.period, tc.expected, milliCPU)
   493  			}
   494  		})
   495  	}
   496  }
   497  

View as plain text