...

Source file src/k8s.io/kubernetes/cmd/kubelet/app/server_test.go

Documentation: k8s.io/kubernetes/cmd/kubelet/app

     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 app
    18  
    19  import (
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/require"
    27  	"gopkg.in/yaml.v2"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/kubernetes/cmd/kubelet/app/options"
    30  	kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config"
    31  )
    32  
    33  func TestValueOfAllocatableResources(t *testing.T) {
    34  	testCases := []struct {
    35  		kubeReserved   map[string]string
    36  		systemReserved map[string]string
    37  		errorExpected  bool
    38  		name           string
    39  	}{
    40  		{
    41  			kubeReserved:   map[string]string{"cpu": "200m", "memory": "-150G", "ephemeral-storage": "10Gi"},
    42  			systemReserved: map[string]string{"cpu": "200m", "memory": "15Ki"},
    43  			errorExpected:  true,
    44  			name:           "negative quantity value",
    45  		},
    46  		{
    47  			kubeReserved:   map[string]string{"cpu": "200m", "memory": "150Gi", "ephemeral-storage": "10Gi"},
    48  			systemReserved: map[string]string{"cpu": "200m", "memory": "15Ky"},
    49  			errorExpected:  true,
    50  			name:           "invalid quantity unit",
    51  		},
    52  		{
    53  			kubeReserved:   map[string]string{"cpu": "200m", "memory": "15G", "ephemeral-storage": "10Gi"},
    54  			systemReserved: map[string]string{"cpu": "200m", "memory": "15Ki"},
    55  			errorExpected:  false,
    56  			name:           "Valid resource quantity",
    57  		},
    58  	}
    59  
    60  	for _, test := range testCases {
    61  		_, err1 := parseResourceList(test.kubeReserved)
    62  		_, err2 := parseResourceList(test.systemReserved)
    63  		if test.errorExpected {
    64  			if err1 == nil && err2 == nil {
    65  				t.Errorf("%s: error expected", test.name)
    66  			}
    67  		} else {
    68  			if err1 != nil || err2 != nil {
    69  				t.Errorf("%s: unexpected error: %v, %v", test.name, err1, err2)
    70  			}
    71  		}
    72  	}
    73  }
    74  
    75  func TestMergeKubeletConfigurations(t *testing.T) {
    76  	testCases := []struct {
    77  		kubeletConfig           *kubeletconfiginternal.KubeletConfiguration
    78  		dropin1                 string
    79  		dropin2                 string
    80  		overwrittenConfigFields map[string]interface{}
    81  		cliArgs                 []string
    82  		name                    string
    83  	}{
    84  		{
    85  			kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{
    86  				TypeMeta: metav1.TypeMeta{
    87  					Kind:       "KubeletConfiguration",
    88  					APIVersion: "kubelet.config.k8s.io/v1beta1",
    89  				},
    90  				Port:         int32(9090),
    91  				ReadOnlyPort: int32(10257),
    92  			},
    93  			dropin1: `
    94  apiVersion: kubelet.config.k8s.io/v1beta1
    95  kind: KubeletConfiguration
    96  port: 9090
    97  `,
    98  			dropin2: `
    99  apiVersion: kubelet.config.k8s.io/v1beta1
   100  kind: KubeletConfiguration
   101  port: 8080
   102  readOnlyPort: 10255
   103  `,
   104  			overwrittenConfigFields: map[string]interface{}{
   105  				"Port":         int32(8080),
   106  				"ReadOnlyPort": int32(10255),
   107  			},
   108  			name: "kubelet.conf.d overrides kubelet.conf",
   109  		},
   110  		{
   111  			kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{
   112  				TypeMeta: metav1.TypeMeta{
   113  					Kind:       "KubeletConfiguration",
   114  					APIVersion: "kubelet.config.k8s.io/v1beta1",
   115  				},
   116  				ReadOnlyPort:  int32(10256),
   117  				KubeReserved:  map[string]string{"memory": "100Mi"},
   118  				SyncFrequency: metav1.Duration{Duration: 5 * time.Minute},
   119  			},
   120  			dropin1: `
   121  apiVersion: kubelet.config.k8s.io/v1beta1
   122  kind: KubeletConfiguration
   123  readOnlyPort: 10255
   124  kubeReserved:
   125    memory: 150Mi
   126    cpu: 200m
   127  `,
   128  			dropin2: `
   129  apiVersion: kubelet.config.k8s.io/v1beta1
   130  kind: KubeletConfiguration
   131  readOnlyPort: 10257
   132  kubeReserved:
   133    memory: 100Mi
   134  `,
   135  			overwrittenConfigFields: map[string]interface{}{
   136  				"ReadOnlyPort": int32(10257),
   137  				"KubeReserved": map[string]string{
   138  					"cpu":    "200m",
   139  					"memory": "100Mi",
   140  				},
   141  				"SyncFrequency": metav1.Duration{Duration: 5 * time.Minute},
   142  			},
   143  			name: "kubelet.conf.d overrides kubelet.conf with subfield override",
   144  		},
   145  		{
   146  			kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{
   147  				TypeMeta: metav1.TypeMeta{
   148  					Kind:       "KubeletConfiguration",
   149  					APIVersion: "kubelet.config.k8s.io/v1beta1",
   150  				},
   151  				Port:       int32(9090),
   152  				ClusterDNS: []string{"192.168.1.3", "192.168.1.4"},
   153  			},
   154  			dropin1: `
   155  apiVersion: kubelet.config.k8s.io/v1beta1
   156  kind: KubeletConfiguration
   157  port: 9090
   158  systemReserved:
   159    memory: 1Gi
   160  `,
   161  			dropin2: `
   162  apiVersion: kubelet.config.k8s.io/v1beta1
   163  kind: KubeletConfiguration
   164  port: 8080
   165  readOnlyPort: 10255
   166  systemReserved:
   167    memory: 2Gi
   168  clusterDNS:
   169    - 192.168.1.1
   170    - 192.168.1.5
   171    - 192.168.1.8
   172  `,
   173  			overwrittenConfigFields: map[string]interface{}{
   174  				"Port":         int32(8080),
   175  				"ReadOnlyPort": int32(10255),
   176  				"SystemReserved": map[string]string{
   177  					"memory": "2Gi",
   178  				},
   179  				"ClusterDNS": []string{"192.168.1.1", "192.168.1.5", "192.168.1.8"},
   180  			},
   181  			name: "kubelet.conf.d overrides kubelet.conf with slices/lists",
   182  		},
   183  		{
   184  			kubeletConfig: nil,
   185  			dropin1: `
   186  apiVersion: kubelet.config.k8s.io/v1beta1
   187  kind: KubeletConfiguration
   188  port: 9090
   189  `,
   190  			dropin2: `
   191  apiVersion: kubelet.config.k8s.io/v1beta1
   192  kind: KubeletConfiguration
   193  port: 8080
   194  readOnlyPort: 10255
   195  `,
   196  			overwrittenConfigFields: map[string]interface{}{
   197  				"Port":         int32(8081),
   198  				"ReadOnlyPort": int32(10256),
   199  			},
   200  			cliArgs: []string{
   201  				"--port=8081",
   202  				"--read-only-port=10256",
   203  			},
   204  			name: "cli args override kubelet.conf.d",
   205  		},
   206  		{
   207  			kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{
   208  				TypeMeta: metav1.TypeMeta{
   209  					Kind:       "KubeletConfiguration",
   210  					APIVersion: "kubelet.config.k8s.io/v1beta1",
   211  				},
   212  				Port:       int32(9090),
   213  				ClusterDNS: []string{"192.168.1.3"},
   214  			},
   215  			overwrittenConfigFields: map[string]interface{}{
   216  				"Port":       int32(9090),
   217  				"ClusterDNS": []string{"192.168.1.2"},
   218  			},
   219  			cliArgs: []string{
   220  				"--port=9090",
   221  				"--cluster-dns=192.168.1.2",
   222  			},
   223  			name: "cli args override kubelet.conf",
   224  		},
   225  	}
   226  
   227  	for _, test := range testCases {
   228  		t.Run(test.name, func(t *testing.T) {
   229  			// Prepare a temporary directory for testing
   230  			tempDir := t.TempDir()
   231  
   232  			kubeletConfig := &kubeletconfiginternal.KubeletConfiguration{}
   233  			kubeletFlags := &options.KubeletFlags{}
   234  
   235  			if test.kubeletConfig != nil {
   236  				// Create the Kubeletconfig
   237  				kubeletConfFile := filepath.Join(tempDir, "kubelet.conf")
   238  				yamlData, err := yaml.Marshal(test.kubeletConfig) // Convert struct to YAML
   239  				require.NoError(t, err, "failed to convert kubelet config to YAML")
   240  				err = os.WriteFile(kubeletConfFile, yamlData, 0644)
   241  				require.NoError(t, err, "failed to create config from YAML data")
   242  				kubeletFlags.KubeletConfigFile = kubeletConfFile
   243  				kubeletConfig = test.kubeletConfig
   244  			}
   245  			if len(test.dropin1) > 0 || len(test.dropin2) > 0 {
   246  				// Create kubelet.conf.d directory and drop-in configuration files
   247  				kubeletConfDir := filepath.Join(tempDir, "kubelet.conf.d")
   248  				err := os.Mkdir(kubeletConfDir, 0755)
   249  				require.NoError(t, err, "Failed to create kubelet.conf.d directory")
   250  
   251  				err = os.WriteFile(filepath.Join(kubeletConfDir, "10-kubelet.conf"), []byte(test.dropin1), 0644)
   252  				require.NoError(t, err, "failed to create config from a yaml file")
   253  
   254  				err = os.WriteFile(filepath.Join(kubeletConfDir, "20-kubelet.conf"), []byte(test.dropin2), 0644)
   255  				require.NoError(t, err, "failed to create config from a yaml file")
   256  
   257  				// Merge the kubelet configurations
   258  				err = mergeKubeletConfigurations(kubeletConfig, kubeletConfDir)
   259  				require.NoError(t, err, "failed to merge kubelet drop-in configs")
   260  			}
   261  
   262  			// Use kubelet config flag precedence
   263  			err := kubeletConfigFlagPrecedence(kubeletConfig, test.cliArgs)
   264  			require.NoError(t, err, "failed to set the kubelet config flag precedence")
   265  
   266  			// Verify the merged configuration fields
   267  			for fieldName, expectedValue := range test.overwrittenConfigFields {
   268  				value := reflect.ValueOf(kubeletConfig).Elem()
   269  				field := value.FieldByName(fieldName)
   270  				require.Equal(t, expectedValue, field.Interface(), "Field mismatch: "+fieldName)
   271  			}
   272  		})
   273  	}
   274  }
   275  

View as plain text