1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package validation
18
19import (
20	"math/rand"
21	"reflect"
22	"strings"
23	"testing"
24	"time"
25
26	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27	"k8s.io/apimachinery/pkg/util/validation/field"
28)
29
30const (
31	maxLengthErrMsg = "must be no more than"
32	namePartErrMsg  = "name part must consist of"
33	nameErrMsg      = "a qualified name must consist of"
34)
35
36// Ensure custom name functions are allowed
37func TestValidateObjectMetaCustomName(t *testing.T) {
38	errs := ValidateObjectMeta(
39		&metav1.ObjectMeta{Name: "test", GenerateName: "foo"},
40		false,
41		func(s string, prefix bool) []string {
42			if s == "test" {
43				return nil
44			}
45			return []string{"name-gen"}
46		},
47		field.NewPath("field"))
48	if len(errs) != 1 {
49		t.Fatalf("unexpected errors: %v", errs)
50	}
51	if !strings.Contains(errs[0].Error(), "name-gen") {
52		t.Errorf("unexpected error message: %v", errs)
53	}
54}
55
56// Ensure namespace names follow dns label format
57func TestValidateObjectMetaNamespaces(t *testing.T) {
58	errs := ValidateObjectMeta(
59		&metav1.ObjectMeta{Name: "test", Namespace: "foo.bar"},
60		true,
61		func(s string, prefix bool) []string {
62			return nil
63		},
64		field.NewPath("field"))
65	if len(errs) != 1 {
66		t.Fatalf("unexpected errors: %v", errs)
67	}
68	if !strings.Contains(errs[0].Error(), `Invalid value: "foo.bar"`) {
69		t.Errorf("unexpected error message: %v", errs)
70	}
71	maxLength := 63
72	letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
73	b := make([]rune, maxLength+1)
74	for i := range b {
75		b[i] = letters[rand.Intn(len(letters))]
76	}
77	errs = ValidateObjectMeta(
78		&metav1.ObjectMeta{Name: "test", Namespace: string(b)},
79		true,
80		func(s string, prefix bool) []string {
81			return nil
82		},
83		field.NewPath("field"))
84	if len(errs) != 2 {
85		t.Fatalf("unexpected errors: %v", errs)
86	}
87	if !strings.Contains(errs[0].Error(), "Invalid value") || !strings.Contains(errs[1].Error(), "Invalid value") {
88		t.Errorf("unexpected error message: %v", errs)
89	}
90}
91
92func TestValidateObjectMetaOwnerReferences(t *testing.T) {
93	trueVar := true
94	falseVar := false
95	testCases := []struct {
96		description          string
97		ownerReferences      []metav1.OwnerReference
98		expectError          bool
99		expectedErrorMessage string
100	}{
101		{
102			description: "simple success - third party extension.",
103			ownerReferences: []metav1.OwnerReference{
104				{
105					APIVersion: "customresourceVersion",
106					Kind:       "customresourceKind",
107					Name:       "name",
108					UID:        "1",
109				},
110			},
111			expectError:          false,
112			expectedErrorMessage: "",
113		},
114		{
115			description: "simple failures - event shouldn't be set as an owner",
116			ownerReferences: []metav1.OwnerReference{
117				{
118					APIVersion: "v1",
119					Kind:       "Event",
120					Name:       "name",
121					UID:        "1",
122				},
123			},
124			expectError:          true,
125			expectedErrorMessage: "is disallowed from being an owner",
126		},
127		{
128			description: "simple controller ref success - one reference with Controller set",
129			ownerReferences: []metav1.OwnerReference{
130				{
131					APIVersion: "customresourceVersion",
132					Kind:       "customresourceKind",
133					Name:       "name",
134					UID:        "1",
135					Controller: &falseVar,
136				},
137				{
138					APIVersion: "customresourceVersion",
139					Kind:       "customresourceKind",
140					Name:       "name",
141					UID:        "2",
142					Controller: &trueVar,
143				},
144				{
145					APIVersion: "customresourceVersion",
146					Kind:       "customresourceKind",
147					Name:       "name",
148					UID:        "3",
149					Controller: &falseVar,
150				},
151				{
152					APIVersion: "customresourceVersion",
153					Kind:       "customresourceKind",
154					Name:       "name",
155					UID:        "4",
156				},
157			},
158			expectError:          false,
159			expectedErrorMessage: "",
160		},
161		{
162			description: "simple controller ref failure - two references with Controller set",
163			ownerReferences: []metav1.OwnerReference{
164				{
165					APIVersion: "customresourceVersion",
166					Kind:       "customresourceKind",
167					Name:       "name",
168					UID:        "1",
169					Controller: &falseVar,
170				},
171				{
172					APIVersion: "customresourceVersion",
173					Kind:       "customresourceKind",
174					Name:       "name",
175					UID:        "2",
176					Controller: &trueVar,
177				},
178				{
179					APIVersion: "customresourceVersion",
180					Kind:       "customresourceKind",
181					Name:       "name",
182					UID:        "3",
183					Controller: &trueVar,
184				},
185				{
186					APIVersion: "customresourceVersion",
187					Kind:       "customresourceKind",
188					Name:       "name",
189					UID:        "4",
190				},
191			},
192			expectError:          true,
193			expectedErrorMessage: "Only one reference can have Controller set to true",
194		},
195	}
196
197	for _, tc := range testCases {
198		errs := ValidateObjectMeta(
199			&metav1.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences},
200			true,
201			func(s string, prefix bool) []string {
202				return nil
203			},
204			field.NewPath("field"))
205		if len(errs) != 0 && !tc.expectError {
206			t.Errorf("unexpected error: %v in test case %v", errs, tc.description)
207		}
208		if len(errs) == 0 && tc.expectError {
209			t.Errorf("expect error in test case %v", tc.description)
210		}
211		if len(errs) != 0 && !strings.Contains(errs[0].Error(), tc.expectedErrorMessage) {
212			t.Errorf("unexpected error message: %v in test case %v", errs, tc.description)
213		}
214	}
215}
216
217func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) {
218	if errs := ValidateObjectMetaUpdate(
219		&metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
220		&metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
221		field.NewPath("field"),
222	); len(errs) != 1 {
223		t.Fatalf("unexpected errors: %v", errs)
224	}
225	if errs := ValidateObjectMetaUpdate(
226		&metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
227		&metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
228		field.NewPath("field"),
229	); len(errs) != 1 {
230		t.Fatalf("unexpected errors: %v", errs)
231	}
232	if errs := ValidateObjectMetaUpdate(
233		&metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
234		&metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(11, 0))},
235		field.NewPath("field"),
236	); len(errs) != 1 {
237		t.Fatalf("unexpected errors: %v", errs)
238	}
239}
240
241func TestValidateFinalizersUpdate(t *testing.T) {
242	testcases := map[string]struct {
243		Old         metav1.ObjectMeta
244		New         metav1.ObjectMeta
245		ExpectedErr string
246	}{
247		"invalid adding finalizers": {
248			Old:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
249			New:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}},
250			ExpectedErr: "y/b",
251		},
252		"invalid changing finalizers": {
253			Old:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
254			New:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/b"}},
255			ExpectedErr: "x/b",
256		},
257		"valid removing finalizers": {
258			Old:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}},
259			New:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
260			ExpectedErr: "",
261		},
262		"valid adding finalizers for objects not being deleted": {
263			Old:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a"}},
264			New:         metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a", "y/b"}},
265			ExpectedErr: "",
266		},
267	}
268	for name, tc := range testcases {
269		errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
270		if len(errs) == 0 {
271			if len(tc.ExpectedErr) != 0 {
272				t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr)
273			}
274		} else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) {
275			t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a)
276		}
277	}
278}
279
280func TestValidateFinalizersPreventConflictingFinalizers(t *testing.T) {
281	testcases := map[string]struct {
282		ObjectMeta  metav1.ObjectMeta
283		ExpectedErr string
284	}{
285		"conflicting finalizers": {
286			ObjectMeta:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents}},
287			ExpectedErr: "cannot be both set",
288		},
289	}
290	for name, tc := range testcases {
291		errs := ValidateObjectMeta(&tc.ObjectMeta, false, NameIsDNSSubdomain, field.NewPath("field"))
292		if len(errs) == 0 {
293			if len(tc.ExpectedErr) != 0 {
294				t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr)
295			}
296		} else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) {
297			t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a)
298		}
299	}
300}
301
302func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
303	now := metav1.NewTime(time.Unix(1000, 0).UTC())
304	later := metav1.NewTime(time.Unix(2000, 0).UTC())
305	gracePeriodShort := int64(30)
306	gracePeriodLong := int64(40)
307
308	testcases := map[string]struct {
309		Old          metav1.ObjectMeta
310		New          metav1.ObjectMeta
311		ExpectedNew  metav1.ObjectMeta
312		ExpectedErrs []string
313	}{
314		"valid without deletion fields": {
315			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
316			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
317			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
318			ExpectedErrs: []string{},
319		},
320		"valid with deletion fields": {
321			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
322			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
323			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
324			ExpectedErrs: []string{},
325		},
326
327		"invalid set deletionTimestamp": {
328			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
329			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
330			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
331			ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: 1970-01-01 00:16:40 +0000 UTC: field is immutable"},
332		},
333		"invalid clear deletionTimestamp": {
334			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
335			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
336			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
337			ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"null\": field is immutable"},
338		},
339		"invalid change deletionTimestamp": {
340			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
341			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
342			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
343			ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: 1970-01-01 00:33:20 +0000 UTC: field is immutable"},
344		},
345
346		"invalid set deletionGracePeriodSeconds": {
347			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
348			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
349			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
350			ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable"},
351		},
352		"invalid clear deletionGracePeriodSeconds": {
353			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
354			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
355			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
356			ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: \"null\": field is immutable"},
357		},
358		"invalid change deletionGracePeriodSeconds": {
359			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
360			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong},
361			ExpectedNew:  metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong},
362			ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable"},
363		},
364	}
365
366	for k, tc := range testcases {
367		errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
368		if len(errs) != len(tc.ExpectedErrs) {
369			t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs)
370			t.Logf("%s: Got: %#v", k, errs)
371			t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs))
372			continue
373		}
374		for i := range errs {
375			if errs[i].Error() != tc.ExpectedErrs[i] {
376				t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
377			}
378		}
379		if !reflect.DeepEqual(tc.New, tc.ExpectedNew) {
380			t.Errorf("%s: Expected after validation:\n%#v\ngot\n%#v", k, tc.ExpectedNew, tc.New)
381		}
382	}
383}
384
385func TestObjectMetaGenerationUpdate(t *testing.T) {
386	testcases := map[string]struct {
387		Old          metav1.ObjectMeta
388		New          metav1.ObjectMeta
389		ExpectedErrs []string
390	}{
391		"invalid generation change - decremented": {
392			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
393			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 4},
394			ExpectedErrs: []string{"field.generation: Invalid value: 4: must not be decremented"},
395		},
396		"valid generation change - incremented by one": {
397			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 1},
398			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 2},
399			ExpectedErrs: []string{},
400		},
401		"valid generation field - not updated": {
402			Old:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
403			New:          metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
404			ExpectedErrs: []string{},
405		},
406	}
407
408	for k, tc := range testcases {
409		errList := []string{}
410		errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
411		if len(errs) != len(tc.ExpectedErrs) {
412			t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs)
413			for _, err := range errs {
414				errList = append(errList, err.Error())
415			}
416			t.Logf("%s: Got: %#v", k, errList)
417			t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs))
418			continue
419		}
420		for i := range errList {
421			if errList[i] != tc.ExpectedErrs[i] {
422				t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errList[i])
423			}
424		}
425	}
426}
427
428// Ensure trailing slash is allowed in generate name
429func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) {
430	errs := ValidateObjectMeta(
431		&metav1.ObjectMeta{Name: "test", GenerateName: "foo-"},
432		false,
433		NameIsDNSSubdomain,
434		field.NewPath("field"))
435	if len(errs) != 0 {
436		t.Fatalf("unexpected errors: %v", errs)
437	}
438}
439
440func TestValidateAnnotations(t *testing.T) {
441	successCases := []map[string]string{
442		{"simple": "bar"},
443		{"now-with-dashes": "bar"},
444		{"1-starts-with-num": "bar"},
445		{"1234": "bar"},
446		{"simple/simple": "bar"},
447		{"now-with-dashes/simple": "bar"},
448		{"now-with-dashes/now-with-dashes": "bar"},
449		{"now.with.dots/simple": "bar"},
450		{"now-with.dashes-and.dots/simple": "bar"},
451		{"1-num.2-num/3-num": "bar"},
452		{"1234/5678": "bar"},
453		{"1.2.3.4/5678": "bar"},
454		{"UpperCase123": "bar"},
455		{"a": strings.Repeat("b", totalAnnotationSizeLimitB-1)},
456		{
457			"a": strings.Repeat("b", totalAnnotationSizeLimitB/2-1),
458			"c": strings.Repeat("d", totalAnnotationSizeLimitB/2-1),
459		},
460	}
461	for i := range successCases {
462		errs := ValidateAnnotations(successCases[i], field.NewPath("field"))
463		if len(errs) != 0 {
464			t.Errorf("case[%d] expected success, got %#v", i, errs)
465		}
466	}
467
468	nameErrorCases := []struct {
469		annotations map[string]string
470		expect      string
471	}{
472		{map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg},
473		{map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg},
474		{map[string]string{"only/one/slash": "bar"}, nameErrMsg},
475		{map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg},
476	}
477	for i := range nameErrorCases {
478		errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field"))
479		if len(errs) != 1 {
480			t.Errorf("case[%d]: expected failure", i)
481		} else {
482			if !strings.Contains(errs[0].Detail, nameErrorCases[i].expect) {
483				t.Errorf("case[%d]: error details do not include %q: %q", i, nameErrorCases[i].expect, errs[0].Detail)
484			}
485		}
486	}
487	totalSizeErrorCases := []map[string]string{
488		{"a": strings.Repeat("b", totalAnnotationSizeLimitB)},
489		{
490			"a": strings.Repeat("b", totalAnnotationSizeLimitB/2),
491			"c": strings.Repeat("d", totalAnnotationSizeLimitB/2),
492		},
493	}
494	for i := range totalSizeErrorCases {
495		errs := ValidateAnnotations(totalSizeErrorCases[i], field.NewPath("field"))
496		if len(errs) != 1 {
497			t.Errorf("case[%d] expected failure", i)
498		}
499	}
500}
501