1
16
17 package validation
18
19 import (
20 "strings"
21 "testing"
22
23 v1 "k8s.io/api/core/v1"
24 "k8s.io/apimachinery/pkg/api/resource"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/util/sets"
27 "k8s.io/apimachinery/pkg/util/validation/field"
28 "k8s.io/kubernetes/pkg/apis/core"
29 )
30
31 func TestValidateResourceRequirements(t *testing.T) {
32 successCase := []struct {
33 name string
34 requirements v1.ResourceRequirements
35 }{{
36 name: "Resources with Requests equal to Limits",
37 requirements: v1.ResourceRequirements{
38 Requests: v1.ResourceList{
39 v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
40 v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
41 },
42 Limits: v1.ResourceList{
43 v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
44 v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
45 },
46 },
47 }, {
48 name: "Resources with only Limits",
49 requirements: v1.ResourceRequirements{
50 Limits: v1.ResourceList{
51 v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
52 v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
53 v1.ResourceName("my.org/resource"): resource.MustParse("10"),
54 },
55 },
56 }, {
57 name: "Resources with only Requests",
58 requirements: v1.ResourceRequirements{
59 Requests: v1.ResourceList{
60 v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
61 v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
62 v1.ResourceName("my.org/resource"): resource.MustParse("10"),
63 },
64 },
65 }, {
66 name: "Resources with Requests Less Than Limits",
67 requirements: v1.ResourceRequirements{
68 Requests: v1.ResourceList{
69 v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
70 v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"),
71 v1.ResourceName("my.org/resource"): resource.MustParse("9"),
72 },
73 Limits: v1.ResourceList{
74 v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
75 v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
76 v1.ResourceName("my.org/resource"): resource.MustParse("9"),
77 },
78 },
79 }}
80 for _, tc := range successCase {
81 t.Run(tc.name, func(t *testing.T) {
82 if errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")); len(errs) != 0 {
83 t.Errorf("unexpected error: %v", errs)
84 }
85 })
86 }
87
88 errorCase := []struct {
89 name string
90 requirements v1.ResourceRequirements
91 skipLimitValueCheck bool
92 skipRequestValueCheck bool
93 }{{
94 name: "Resources with Requests Larger Than Limits",
95 requirements: v1.ResourceRequirements{
96 Requests: v1.ResourceList{
97 v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
98 v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
99 v1.ResourceName("my.org/resource"): resource.MustParse("10m"),
100 },
101 Limits: v1.ResourceList{
102 v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
103 v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"),
104 v1.ResourceName("my.org/resource"): resource.MustParse("9m"),
105 },
106 },
107 }, {
108 name: "Invalid Resources with Requests",
109 requirements: v1.ResourceRequirements{
110 Requests: v1.ResourceList{
111 v1.ResourceName("my.org"): resource.MustParse("10m"),
112 },
113 },
114 skipRequestValueCheck: true,
115 }, {
116 name: "Invalid Resources with Limits",
117 requirements: v1.ResourceRequirements{
118 Limits: v1.ResourceList{
119 v1.ResourceName("my.org"): resource.MustParse("9m"),
120 },
121 },
122 skipLimitValueCheck: true,
123 }}
124 for _, tc := range errorCase {
125 t.Run(tc.name, func(t *testing.T) {
126 errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources"))
127 if len(errs) == 0 {
128 t.Errorf("expected error")
129 }
130 validateNamesAndValuesInDescription(t, tc.requirements.Limits, errs, tc.skipLimitValueCheck, "limit")
131 validateNamesAndValuesInDescription(t, tc.requirements.Requests, errs, tc.skipRequestValueCheck, "request")
132 })
133 }
134 }
135
136 func validateNamesAndValuesInDescription(t *testing.T, r v1.ResourceList, errs field.ErrorList, skipValueTest bool, rl string) {
137 for name, value := range r {
138 containsName := false
139 containsValue := false
140
141 for _, e := range errs {
142 if strings.Contains(e.Error(), name.String()) {
143 containsName = true
144 }
145
146 if strings.Contains(e.Error(), value.String()) {
147 containsValue = true
148 }
149 }
150 if !containsName {
151 t.Errorf("error must contain %s name", rl)
152 }
153 if !containsValue && !skipValueTest {
154 t.Errorf("error must contain %s value", rl)
155 }
156 }
157 }
158
159 func TestValidateContainerResourceName(t *testing.T) {
160 successCase := []struct {
161 name string
162 ResourceName core.ResourceName
163 }{{
164 name: "CPU resource",
165 ResourceName: "cpu",
166 }, {
167 name: "Memory resource",
168 ResourceName: "memory",
169 }, {
170 name: "Hugepages resource",
171 ResourceName: "hugepages-2Mi",
172 }, {
173 name: "Namespaced resource",
174 ResourceName: "kubernetes.io/resource-foo",
175 }, {
176 name: "Extended Resource",
177 ResourceName: "my.org/resource-bar",
178 }}
179 for _, tc := range successCase {
180 t.Run(tc.name, func(t *testing.T) {
181 if errs := ValidateContainerResourceName(tc.ResourceName, field.NewPath(string(tc.ResourceName))); len(errs) != 0 {
182 t.Errorf("unexpected error: %v", errs)
183 }
184 })
185 }
186
187 errorCase := []struct {
188 name string
189 ResourceName core.ResourceName
190 }{{
191 name: "Invalid standard resource",
192 ResourceName: "cpu-core",
193 }, {
194 name: "Invalid namespaced resource",
195 ResourceName: "kubernetes.io/",
196 }, {
197 name: "Invalid extended resource",
198 ResourceName: "my.org-foo-resource",
199 }}
200 for _, tc := range errorCase {
201 t.Run(tc.name, func(t *testing.T) {
202 if errs := ValidateContainerResourceName(tc.ResourceName, field.NewPath(string(tc.ResourceName))); len(errs) == 0 {
203 t.Errorf("expected error")
204 }
205 })
206 }
207 }
208
209 func TestValidatePodLogOptions(t *testing.T) {
210
211 var (
212 positiveLine = int64(8)
213 negativeLine = int64(-8)
214 limitBytesGreaterThan1 = int64(12)
215 limitBytesLessThan1 = int64(0)
216 sinceSecondsGreaterThan1 = int64(10)
217 sinceSecondsLessThan1 = int64(0)
218 timestamp = metav1.Now()
219 )
220
221 successCase := []struct {
222 name string
223 podLogOptions v1.PodLogOptions
224 }{{
225 name: "Empty PodLogOptions",
226 podLogOptions: v1.PodLogOptions{},
227 }, {
228 name: "PodLogOptions with TailLines",
229 podLogOptions: v1.PodLogOptions{
230 TailLines: &positiveLine,
231 },
232 }, {
233 name: "PodLogOptions with LimitBytes",
234 podLogOptions: v1.PodLogOptions{
235 LimitBytes: &limitBytesGreaterThan1,
236 },
237 }, {
238 name: "PodLogOptions with only sinceSeconds",
239 podLogOptions: v1.PodLogOptions{
240 SinceSeconds: &sinceSecondsGreaterThan1,
241 },
242 }, {
243 name: "PodLogOptions with LimitBytes with TailLines",
244 podLogOptions: v1.PodLogOptions{
245 LimitBytes: &limitBytesGreaterThan1,
246 TailLines: &positiveLine,
247 },
248 }, {
249 name: "PodLogOptions with LimitBytes with TailLines with SinceSeconds",
250 podLogOptions: v1.PodLogOptions{
251 LimitBytes: &limitBytesGreaterThan1,
252 TailLines: &positiveLine,
253 SinceSeconds: &sinceSecondsGreaterThan1,
254 },
255 }}
256 for _, tc := range successCase {
257 t.Run(tc.name, func(t *testing.T) {
258 if errs := ValidatePodLogOptions(&tc.podLogOptions); len(errs) != 0 {
259 t.Errorf("unexpected error: %v", errs)
260 }
261 })
262 }
263
264 errorCase := []struct {
265 name string
266 podLogOptions v1.PodLogOptions
267 }{{
268 name: "Invalid podLogOptions with Negative TailLines",
269 podLogOptions: v1.PodLogOptions{
270 TailLines: &negativeLine,
271 LimitBytes: &limitBytesGreaterThan1,
272 SinceSeconds: &sinceSecondsGreaterThan1,
273 },
274 }, {
275 name: "Invalid podLogOptions with zero or negative LimitBytes",
276 podLogOptions: v1.PodLogOptions{
277 TailLines: &positiveLine,
278 LimitBytes: &limitBytesLessThan1,
279 SinceSeconds: &sinceSecondsGreaterThan1,
280 },
281 }, {
282 name: "Invalid podLogOptions with zero or negative SinceSeconds",
283 podLogOptions: v1.PodLogOptions{
284 TailLines: &negativeLine,
285 LimitBytes: &limitBytesGreaterThan1,
286 SinceSeconds: &sinceSecondsLessThan1,
287 },
288 }, {
289 name: "Invalid podLogOptions with both SinceSeconds and SinceTime set",
290 podLogOptions: v1.PodLogOptions{
291 TailLines: &negativeLine,
292 LimitBytes: &limitBytesGreaterThan1,
293 SinceSeconds: &sinceSecondsGreaterThan1,
294 SinceTime: ×tamp,
295 },
296 }}
297 for _, tc := range errorCase {
298 t.Run(tc.name, func(t *testing.T) {
299 if errs := ValidatePodLogOptions(&tc.podLogOptions); len(errs) == 0 {
300 t.Errorf("expected error")
301 }
302 })
303 }
304 }
305
306 func TestAccumulateUniqueHostPorts(t *testing.T) {
307 successCase := []struct {
308 name string
309 containers []v1.Container
310 accumulator *sets.String
311 fldPath *field.Path
312 }{{
313 name: "HostPort is not allocated while containers use the same port with different protocol",
314 containers: []v1.Container{{
315 Ports: []v1.ContainerPort{{
316 HostPort: 8080,
317 Protocol: v1.ProtocolUDP,
318 }},
319 }, {
320 Ports: []v1.ContainerPort{{
321 HostPort: 8080,
322 Protocol: v1.ProtocolTCP,
323 }},
324 }},
325 accumulator: &sets.String{},
326 fldPath: field.NewPath("spec", "containers"),
327 }, {
328 name: "HostPort is not allocated while containers use different ports",
329 containers: []v1.Container{{
330 Ports: []v1.ContainerPort{{
331 HostPort: 8080,
332 Protocol: v1.ProtocolUDP,
333 }},
334 }, {
335 Ports: []v1.ContainerPort{{
336 HostPort: 8081,
337 Protocol: v1.ProtocolUDP,
338 }},
339 }},
340 accumulator: &sets.String{},
341 fldPath: field.NewPath("spec", "containers"),
342 }}
343 for _, tc := range successCase {
344 t.Run(tc.name, func(t *testing.T) {
345 if errs := AccumulateUniqueHostPorts(tc.containers, tc.accumulator, tc.fldPath); len(errs) != 0 {
346 t.Errorf("unexpected error: %v", errs)
347 }
348 })
349 }
350 errorCase := []struct {
351 name string
352 containers []v1.Container
353 accumulator *sets.String
354 fldPath *field.Path
355 }{{
356 name: "HostPort is already allocated while containers use the same port with UDP",
357 containers: []v1.Container{{
358 Ports: []v1.ContainerPort{{
359 HostPort: 8080,
360 Protocol: v1.ProtocolUDP,
361 }},
362 }, {
363 Ports: []v1.ContainerPort{{
364 HostPort: 8080,
365 Protocol: v1.ProtocolUDP,
366 }},
367 }},
368 accumulator: &sets.String{},
369 fldPath: field.NewPath("spec", "containers"),
370 }, {
371 name: "HostPort is already allocated",
372 containers: []v1.Container{{
373 Ports: []v1.ContainerPort{{
374 HostPort: 8080,
375 Protocol: v1.ProtocolUDP,
376 }},
377 }, {
378 Ports: []v1.ContainerPort{{
379 HostPort: 8081,
380 Protocol: v1.ProtocolUDP,
381 }},
382 }},
383 accumulator: &sets.String{"8080/UDP": sets.Empty{}},
384 fldPath: field.NewPath("spec", "containers"),
385 }}
386 for _, tc := range errorCase {
387 t.Run(tc.name, func(t *testing.T) {
388 if errs := AccumulateUniqueHostPorts(tc.containers, tc.accumulator, tc.fldPath); len(errs) == 0 {
389 t.Errorf("expected error, but get nil")
390 }
391 })
392 }
393 }
394
View as plain text