k8s.io/kubernetes@v1.29.3/pkg/apis/apiserverinternal/validation/validation_test.go (about) 1 /* 2 Copyright 2020 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 validation 18 19 import ( 20 "strings" 21 "testing" 22 23 "k8s.io/apimachinery/pkg/util/validation/field" 24 "k8s.io/kubernetes/pkg/apis/apiserverinternal" 25 "k8s.io/utils/pointer" 26 ) 27 28 func TestValidateServerStorageVersion(t *testing.T) { 29 cases := []struct { 30 ssv apiserverinternal.ServerStorageVersion 31 expectedErr string 32 }{{ 33 ssv: apiserverinternal.ServerStorageVersion{ 34 APIServerID: "-fea", 35 EncodingVersion: "v1alpha1", 36 DecodableVersions: []string{"v1alpha1", "v1"}, 37 ServedVersions: []string{"v1alpha1", "v1"}, 38 }, 39 expectedErr: "apiServerID: Invalid value", 40 }, { 41 ssv: apiserverinternal.ServerStorageVersion{ 42 APIServerID: "fea", 43 EncodingVersion: "v1alpha1", 44 DecodableVersions: []string{"v1beta1", "v1"}, 45 ServedVersions: []string{"v1beta1", "v1"}, 46 }, 47 expectedErr: "decodableVersions must include encodingVersion", 48 }, { 49 ssv: apiserverinternal.ServerStorageVersion{ 50 APIServerID: "fea", 51 EncodingVersion: "v1alpha1", 52 DecodableVersions: []string{"v1alpha1", "v1", "-fea"}, 53 ServedVersions: []string{"v1alpha1", "v1", "-fea"}, 54 }, 55 expectedErr: "decodableVersions[2]: Invalid value", 56 }, { 57 ssv: apiserverinternal.ServerStorageVersion{ 58 APIServerID: "fea", 59 EncodingVersion: "v1alpha1", 60 DecodableVersions: []string{"v1alpha1", "v1"}, 61 ServedVersions: []string{"v1alpha1", "v1"}, 62 }, 63 expectedErr: "", 64 }, { 65 ssv: apiserverinternal.ServerStorageVersion{ 66 APIServerID: "fea", 67 EncodingVersion: "v1alpha1", 68 DecodableVersions: []string{"v1alpha1", "v1"}, 69 ServedVersions: []string{"v1alpha1", "v1"}, 70 }, 71 expectedErr: "", 72 }, { 73 ssv: apiserverinternal.ServerStorageVersion{ 74 APIServerID: "fea", 75 EncodingVersion: "mygroup.com/v2", 76 DecodableVersions: []string{"v1alpha1", "v1", "mygroup.com/v2"}, 77 ServedVersions: []string{"v1alpha1", "v1", "mygroup.com/v2"}, 78 }, 79 expectedErr: "", 80 }, { 81 ssv: apiserverinternal.ServerStorageVersion{ 82 APIServerID: "fea", 83 EncodingVersion: "v1alpha1", 84 DecodableVersions: []string{"v1alpha1", "v1"}, 85 ServedVersions: []string{"/v3"}, 86 }, 87 expectedErr: `[].servedVersions[0]: Invalid value: "/v3": group part: must be non-empty`, 88 }, { 89 ssv: apiserverinternal.ServerStorageVersion{ 90 APIServerID: "fea", 91 EncodingVersion: "mygroup.com/v2", 92 DecodableVersions: []string{"mygroup.com/v2", "/v3"}, 93 ServedVersions: []string{"mygroup.com/v2", "/v3"}, 94 }, 95 expectedErr: `[].decodableVersions[1]: Invalid value: "/v3": group part: must be non-empty`, 96 }, { 97 ssv: apiserverinternal.ServerStorageVersion{ 98 APIServerID: "fea", 99 EncodingVersion: "mygroup.com/v2", 100 DecodableVersions: []string{"mygroup.com/v2", "/v3"}, 101 ServedVersions: []string{"mygroup.com/"}, 102 }, 103 expectedErr: `[].servedVersions[0]: Invalid value: "mygroup.com/": version part: must be non-empty`, 104 }, { 105 ssv: apiserverinternal.ServerStorageVersion{ 106 APIServerID: "fea", 107 EncodingVersion: "mygroup.com/v2", 108 DecodableVersions: []string{"mygroup.com/v2", "mygroup.com/"}, 109 ServedVersions: []string{"mygroup.com/v2", "mygroup.com/"}, 110 }, 111 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/": version part: must be non-empty`, 112 }, { 113 ssv: apiserverinternal.ServerStorageVersion{ 114 APIServerID: "fea", 115 EncodingVersion: "/v3", 116 DecodableVersions: []string{"mygroup.com/v2", "/v3"}, 117 ServedVersions: []string{"mygroup.com/v2", "/v3"}, 118 }, 119 expectedErr: `[].encodingVersion: Invalid value: "/v3": group part: must be non-empty`, 120 }, { 121 ssv: apiserverinternal.ServerStorageVersion{ 122 APIServerID: "fea", 123 EncodingVersion: "v1", 124 DecodableVersions: []string{"v1", "mygroup_com/v2"}, 125 ServedVersions: []string{"v1", "mygroup_com/v2"}, 126 }, 127 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup_com/v2": group part: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`, 128 }, { 129 ssv: apiserverinternal.ServerStorageVersion{ 130 APIServerID: "fea", 131 EncodingVersion: "v1", 132 DecodableVersions: []string{"v1", "mygroup.com/v2"}, 133 ServedVersions: []string{"v1", "mygroup_com/v2"}, 134 }, 135 expectedErr: `[].servedVersions[1]: Invalid value: "mygroup_com/v2": group part: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`, 136 }, { 137 ssv: apiserverinternal.ServerStorageVersion{ 138 APIServerID: "fea", 139 EncodingVersion: "v1", 140 DecodableVersions: []string{"v1", "mygroup.com/v2_"}, 141 ServedVersions: []string{"v1", "mygroup.com/v2_"}, 142 }, 143 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2_": version part: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`, 144 }, { 145 ssv: apiserverinternal.ServerStorageVersion{ 146 APIServerID: "fea", 147 EncodingVersion: "v1", 148 DecodableVersions: []string{"v1", "mygroup.com/v2"}, 149 ServedVersions: []string{"v1", "mygroup.com/v2_"}, 150 }, 151 expectedErr: `[].servedVersions[1]: Invalid value: "mygroup.com/v2_": version part: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`, 152 }, { 153 ssv: apiserverinternal.ServerStorageVersion{ 154 APIServerID: "fea", 155 EncodingVersion: "v1", 156 DecodableVersions: []string{"v1", "mygroup.com/v2/myresource"}, 157 ServedVersions: []string{"v1", "mygroup.com/v2/myresource"}, 158 }, 159 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2/myresource": an apiVersion is a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')`, 160 }, { 161 ssv: apiserverinternal.ServerStorageVersion{ 162 APIServerID: "fea", 163 EncodingVersion: "v1", 164 DecodableVersions: []string{"v1", "mygroup.com/v2"}, 165 ServedVersions: []string{"v1", "mygroup.com/v2/myresource"}, 166 }, 167 expectedErr: `[].servedVersions[1]: Invalid value: "mygroup.com/v2/myresource": an apiVersion is a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')`, 168 }, { 169 ssv: apiserverinternal.ServerStorageVersion{ 170 APIServerID: "fea", 171 EncodingVersion: "v1alpha1", 172 DecodableVersions: []string{"v1alpha1", "v1"}, 173 ServedVersions: []string{"v2"}, 174 }, 175 expectedErr: `[].servedVersions[0]: Invalid value: "v2": individual served version : v2 must be included in decodableVersions : [v1alpha1 v1]`, 176 }} 177 178 for _, tc := range cases { 179 err := validateServerStorageVersion(tc.ssv, field.NewPath("")).ToAggregate() 180 if err == nil && len(tc.expectedErr) == 0 { 181 continue 182 } 183 if err != nil && len(tc.expectedErr) == 0 { 184 t.Errorf("unexpected error %v", err) 185 continue 186 } 187 if err == nil && len(tc.expectedErr) != 0 { 188 t.Errorf("unexpected empty error") 189 continue 190 } 191 if !strings.Contains(err.Error(), tc.expectedErr) { 192 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err) 193 } 194 } 195 } 196 197 func TestValidateStorageVersionStatus(t *testing.T) { 198 cases := []struct { 199 svs apiserverinternal.StorageVersionStatus 200 expectedErr string 201 }{{ 202 svs: apiserverinternal.StorageVersionStatus{ 203 StorageVersions: []apiserverinternal.ServerStorageVersion{{ 204 APIServerID: "1", 205 EncodingVersion: "v1alpha1", 206 DecodableVersions: []string{"v1alpha1", "v1"}, 207 }, { 208 APIServerID: "2", 209 EncodingVersion: "v1alpha1", 210 DecodableVersions: []string{"v1alpha1", "v1"}, 211 }}, 212 CommonEncodingVersion: pointer.String("v1alpha1"), 213 }, 214 expectedErr: "", 215 }, { 216 svs: apiserverinternal.StorageVersionStatus{ 217 StorageVersions: []apiserverinternal.ServerStorageVersion{{ 218 APIServerID: "1", 219 EncodingVersion: "v1alpha1", 220 DecodableVersions: []string{"v1alpha1", "v1"}, 221 }, { 222 APIServerID: "1", 223 EncodingVersion: "v1beta1", 224 DecodableVersions: []string{"v1alpha1", "v1"}, 225 }}, 226 CommonEncodingVersion: pointer.String("v1alpha1"), 227 }, 228 expectedErr: "storageVersions[1].apiServerID: Duplicate value: \"1\"", 229 }} 230 231 for _, tc := range cases { 232 err := validateStorageVersionStatus(tc.svs, field.NewPath("")).ToAggregate() 233 if err == nil && len(tc.expectedErr) == 0 { 234 continue 235 } 236 if err != nil && len(tc.expectedErr) == 0 { 237 t.Errorf("unexpected error %v", err) 238 continue 239 } 240 if err == nil && len(tc.expectedErr) != 0 { 241 t.Errorf("unexpected empty error") 242 continue 243 } 244 if !strings.Contains(err.Error(), tc.expectedErr) { 245 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err) 246 } 247 } 248 } 249 250 func TestValidateCommonVersion(t *testing.T) { 251 cases := []struct { 252 status apiserverinternal.StorageVersionStatus 253 expectedErr string 254 }{{ 255 status: apiserverinternal.StorageVersionStatus{ 256 StorageVersions: []apiserverinternal.ServerStorageVersion{}, 257 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), 258 }, 259 expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", 260 }, { 261 status: apiserverinternal.StorageVersionStatus{ 262 StorageVersions: []apiserverinternal.ServerStorageVersion{{ 263 APIServerID: "1", 264 EncodingVersion: "v1alpha1", 265 }, { 266 APIServerID: "2", 267 EncodingVersion: "v1", 268 }}, 269 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), 270 }, 271 expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet", 272 }, { 273 status: apiserverinternal.StorageVersionStatus{ 274 StorageVersions: []apiserverinternal.ServerStorageVersion{{ 275 APIServerID: "1", 276 EncodingVersion: "v1alpha1", 277 }, { 278 APIServerID: "2", 279 EncodingVersion: "v1alpha1", 280 }}, 281 CommonEncodingVersion: nil, 282 }, 283 expectedErr: "Invalid value: \"null\": the common encoding version is v1alpha1", 284 }, { 285 status: apiserverinternal.StorageVersionStatus{ 286 StorageVersions: []apiserverinternal.ServerStorageVersion{{ 287 APIServerID: "1", 288 EncodingVersion: "v1alpha1", 289 }, { 290 APIServerID: "2", 291 EncodingVersion: "v1alpha1", 292 }}, 293 CommonEncodingVersion: func() *string { a := "v1"; return &a }(), 294 }, 295 expectedErr: "Invalid value: \"v1\": the actual common encoding version is v1alpha1", 296 }, { 297 status: apiserverinternal.StorageVersionStatus{ 298 StorageVersions: []apiserverinternal.ServerStorageVersion{{ 299 APIServerID: "1", 300 EncodingVersion: "v1alpha1", 301 }, { 302 APIServerID: "2", 303 EncodingVersion: "v1alpha1", 304 }}, 305 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), 306 }, 307 expectedErr: "", 308 }, { 309 status: apiserverinternal.StorageVersionStatus{ 310 StorageVersions: []apiserverinternal.ServerStorageVersion{{ 311 APIServerID: "1", 312 EncodingVersion: "v1alpha1", 313 }}, 314 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(), 315 }, 316 expectedErr: "", 317 }} 318 for _, tc := range cases { 319 err := validateCommonVersion(tc.status, field.NewPath("")) 320 if err == nil && len(tc.expectedErr) == 0 { 321 continue 322 } 323 if err != nil && len(tc.expectedErr) == 0 { 324 t.Errorf("unexpected error %v", err) 325 continue 326 } 327 if err == nil && len(tc.expectedErr) != 0 { 328 t.Errorf("unexpected empty error") 329 continue 330 } 331 if !strings.Contains(err.Error(), tc.expectedErr) { 332 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err) 333 } 334 } 335 } 336 337 func TestValidateStorageVersionCondition(t *testing.T) { 338 cases := []struct { 339 conditions []apiserverinternal.StorageVersionCondition 340 expectedErr string 341 }{{ 342 conditions: []apiserverinternal.StorageVersionCondition{{ 343 Type: "-fea", 344 Status: "True", 345 Reason: "unknown", 346 Message: "unknown", 347 }}, 348 expectedErr: "type: Invalid value", 349 }, { 350 conditions: []apiserverinternal.StorageVersionCondition{{ 351 Type: "fea", 352 Status: "-True", 353 Reason: "unknown", 354 Message: "unknown", 355 }}, 356 expectedErr: "status: Invalid value", 357 }, { 358 conditions: []apiserverinternal.StorageVersionCondition{{ 359 Type: "fea", 360 Status: "True", 361 Message: "unknown", 362 }}, 363 expectedErr: "Required value: reason cannot be empty", 364 }, { 365 conditions: []apiserverinternal.StorageVersionCondition{{ 366 Type: "fea", 367 Status: "True", 368 Reason: "unknown", 369 }}, 370 expectedErr: "Required value: message cannot be empty", 371 }, { 372 conditions: []apiserverinternal.StorageVersionCondition{{ 373 Type: "fea", 374 Status: "True", 375 Reason: "unknown", 376 Message: "unknown", 377 }, { 378 Type: "fea", 379 Status: "True", 380 Reason: "unknown", 381 Message: "unknown", 382 }}, 383 expectedErr: `"fea": the type of the condition is not unique, it also appears in conditions[0]`, 384 }, { 385 conditions: []apiserverinternal.StorageVersionCondition{{ 386 Type: "fea", 387 Status: "True", 388 Reason: "unknown", 389 Message: "unknown", 390 }}, 391 expectedErr: "", 392 }} 393 for _, tc := range cases { 394 err := validateStorageVersionCondition(tc.conditions, field.NewPath("")).ToAggregate() 395 if err == nil && len(tc.expectedErr) == 0 { 396 continue 397 } 398 if err != nil && len(tc.expectedErr) == 0 { 399 t.Errorf("unexpected error %v", err) 400 continue 401 } 402 if err == nil && len(tc.expectedErr) != 0 { 403 t.Errorf("unexpected empty error") 404 continue 405 } 406 if !strings.Contains(err.Error(), tc.expectedErr) { 407 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err) 408 } 409 } 410 } 411 412 func TestValidateStorageVersionName(t *testing.T) { 413 cases := []struct { 414 name string 415 expectedErr string 416 }{{ 417 name: "", 418 expectedErr: `name must be in the form of <group>.<resource>`, 419 }, { 420 name: "pods", 421 expectedErr: `name must be in the form of <group>.<resource>`, 422 }, { 423 name: "core.pods", 424 expectedErr: "", 425 }, { 426 name: "authentication.k8s.io.tokenreviews", 427 expectedErr: "", 428 }, { 429 name: strings.Repeat("x", 253) + ".tokenreviews", 430 expectedErr: "", 431 }, { 432 name: strings.Repeat("x", 254) + ".tokenreviews", 433 expectedErr: `the group segment must be no more than 253 characters`, 434 }, { 435 name: "authentication.k8s.io." + strings.Repeat("x", 63), 436 expectedErr: "", 437 }, { 438 name: "authentication.k8s.io." + strings.Repeat("x", 64), 439 expectedErr: `the resource segment must be no more than 63 characters`, 440 }} 441 for _, tc := range cases { 442 errs := ValidateStorageVersionName(tc.name, false) 443 if errs == nil && len(tc.expectedErr) == 0 { 444 continue 445 } 446 if errs != nil && len(tc.expectedErr) == 0 { 447 t.Errorf("unexpected error %v", errs) 448 continue 449 } 450 if errs == nil && len(tc.expectedErr) != 0 { 451 t.Errorf("unexpected empty error") 452 continue 453 } 454 found := false 455 for _, msg := range errs { 456 if msg == tc.expectedErr { 457 found = true 458 } 459 } 460 if !found { 461 t.Errorf("expected error to contain %s, got %v", tc.expectedErr, errs) 462 } 463 } 464 }