k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubelet/app/server_test.go (about) 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 }