1
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
230 tempDir := t.TempDir()
231
232 kubeletConfig := &kubeletconfiginternal.KubeletConfiguration{}
233 kubeletFlags := &options.KubeletFlags{}
234
235 if test.kubeletConfig != nil {
236
237 kubeletConfFile := filepath.Join(tempDir, "kubelet.conf")
238 yamlData, err := yaml.Marshal(test.kubeletConfig)
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
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
258 err = mergeKubeletConfigurations(kubeletConfig, kubeletConfDir)
259 require.NoError(t, err, "failed to merge kubelet drop-in configs")
260 }
261
262
263 err := kubeletConfigFlagPrecedence(kubeletConfig, test.cliArgs)
264 require.NoError(t, err, "failed to set the kubelet config flag precedence")
265
266
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