k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/resource/validation/validation_resourceclaimparameters_test.go (about) 1 /* 2 Copyright 2022 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 "testing" 21 22 "github.com/stretchr/testify/assert" 23 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/util/validation/field" 26 "k8s.io/kubernetes/pkg/apis/resource" 27 "k8s.io/utils/ptr" 28 ) 29 30 func testResourceClaimParameters(name, namespace string, requests []resource.DriverRequests) *resource.ResourceClaimParameters { 31 return &resource.ResourceClaimParameters{ 32 ObjectMeta: metav1.ObjectMeta{ 33 Name: name, 34 Namespace: namespace, 35 }, 36 DriverRequests: requests, 37 } 38 } 39 40 var goodRequests []resource.DriverRequests 41 42 func TestValidateResourceClaimParameters(t *testing.T) { 43 goodName := "foo" 44 badName := "!@#$%^" 45 badValue := "spaces not allowed" 46 now := metav1.Now() 47 48 scenarios := map[string]struct { 49 parameters *resource.ResourceClaimParameters 50 wantFailures field.ErrorList 51 }{ 52 "good": { 53 parameters: testResourceClaimParameters(goodName, goodName, goodRequests), 54 }, 55 "missing-name": { 56 wantFailures: field.ErrorList{field.Required(field.NewPath("metadata", "name"), "name or generateName is required")}, 57 parameters: testResourceClaimParameters("", goodName, goodRequests), 58 }, 59 "missing-namespace": { 60 wantFailures: field.ErrorList{field.Required(field.NewPath("metadata", "namespace"), "")}, 61 parameters: testResourceClaimParameters(goodName, "", goodRequests), 62 }, 63 "bad-name": { 64 wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "name"), badName, "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])?)*')")}, 65 parameters: testResourceClaimParameters(badName, goodName, goodRequests), 66 }, 67 "bad-namespace": { 68 wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "namespace"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')")}, 69 parameters: testResourceClaimParameters(goodName, badName, goodRequests), 70 }, 71 "generate-name": { 72 parameters: func() *resource.ResourceClaimParameters { 73 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 74 parameters.GenerateName = "prefix-" 75 return parameters 76 }(), 77 }, 78 "uid": { 79 parameters: func() *resource.ResourceClaimParameters { 80 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 81 parameters.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d" 82 return parameters 83 }(), 84 }, 85 "resource-version": { 86 parameters: func() *resource.ResourceClaimParameters { 87 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 88 parameters.ResourceVersion = "1" 89 return parameters 90 }(), 91 }, 92 "generation": { 93 parameters: func() *resource.ResourceClaimParameters { 94 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 95 parameters.Generation = 100 96 return parameters 97 }(), 98 }, 99 "creation-timestamp": { 100 parameters: func() *resource.ResourceClaimParameters { 101 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 102 parameters.CreationTimestamp = now 103 return parameters 104 }(), 105 }, 106 "deletion-grace-period-seconds": { 107 parameters: func() *resource.ResourceClaimParameters { 108 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 109 parameters.DeletionGracePeriodSeconds = ptr.To[int64](10) 110 return parameters 111 }(), 112 }, 113 "owner-references": { 114 parameters: func() *resource.ResourceClaimParameters { 115 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 116 parameters.OwnerReferences = []metav1.OwnerReference{ 117 { 118 APIVersion: "v1", 119 Kind: "pod", 120 Name: "foo", 121 UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d", 122 }, 123 } 124 return parameters 125 }(), 126 }, 127 "finalizers": { 128 parameters: func() *resource.ResourceClaimParameters { 129 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 130 parameters.Finalizers = []string{ 131 "example.com/foo", 132 } 133 return parameters 134 }(), 135 }, 136 "managed-fields": { 137 parameters: func() *resource.ResourceClaimParameters { 138 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 139 parameters.ManagedFields = []metav1.ManagedFieldsEntry{ 140 { 141 FieldsType: "FieldsV1", 142 Operation: "Apply", 143 APIVersion: "apps/v1", 144 Manager: "foo", 145 }, 146 } 147 return parameters 148 }(), 149 }, 150 "good-labels": { 151 parameters: func() *resource.ResourceClaimParameters { 152 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 153 parameters.Labels = map[string]string{ 154 "apps.kubernetes.io/name": "test", 155 } 156 return parameters 157 }(), 158 }, 159 "bad-labels": { 160 wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "labels"), badValue, "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')")}, 161 parameters: func() *resource.ResourceClaimParameters { 162 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 163 parameters.Labels = map[string]string{ 164 "hello-world": badValue, 165 } 166 return parameters 167 }(), 168 }, 169 "good-annotations": { 170 parameters: func() *resource.ResourceClaimParameters { 171 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 172 parameters.Annotations = map[string]string{ 173 "foo": "bar", 174 } 175 return parameters 176 }(), 177 }, 178 "bad-annotations": { 179 wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"), badName, "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")}, 180 parameters: func() *resource.ResourceClaimParameters { 181 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 182 parameters.Annotations = map[string]string{ 183 badName: "hello world", 184 } 185 return parameters 186 }(), 187 }, 188 189 "empty-model": { 190 wantFailures: field.ErrorList{field.Required(field.NewPath("driverRequests").Index(0).Child("requests").Index(0), "exactly one structured model field must be set")}, 191 parameters: func() *resource.ResourceClaimParameters { 192 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 193 parameters.DriverRequests = []resource.DriverRequests{{DriverName: goodName, Requests: []resource.ResourceRequest{{}}}} 194 return parameters 195 }(), 196 }, 197 198 "empty-requests": { 199 wantFailures: field.ErrorList{field.Required(field.NewPath("driverRequests").Index(0).Child("requests"), "empty entries with no requests are not allowed")}, 200 parameters: func() *resource.ResourceClaimParameters { 201 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 202 parameters.DriverRequests = []resource.DriverRequests{{DriverName: goodName}} 203 return parameters 204 }(), 205 }, 206 207 "invalid-driver": { 208 wantFailures: field.ErrorList{field.Invalid(field.NewPath("driverRequests").Index(1).Child("driverName"), badName, "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])?)*')")}, 209 parameters: func() *resource.ResourceClaimParameters { 210 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 211 parameters.DriverRequests = []resource.DriverRequests{ 212 { 213 DriverName: goodName, 214 Requests: []resource.ResourceRequest{ 215 { 216 ResourceRequestModel: resource.ResourceRequestModel{ 217 NamedResources: &resource.NamedResourcesRequest{Selector: "true"}, 218 }, 219 }, 220 }, 221 }, 222 { 223 DriverName: badName, 224 Requests: []resource.ResourceRequest{ 225 { 226 ResourceRequestModel: resource.ResourceRequestModel{ 227 NamedResources: &resource.NamedResourcesRequest{Selector: "true"}, 228 }, 229 }, 230 }, 231 }, 232 } 233 return parameters 234 }(), 235 }, 236 237 "duplicate-driver": { 238 wantFailures: field.ErrorList{field.Duplicate(field.NewPath("driverRequests").Index(1).Child("driverName"), goodName)}, 239 parameters: func() *resource.ResourceClaimParameters { 240 parameters := testResourceClaimParameters(goodName, goodName, goodRequests) 241 parameters.DriverRequests = []resource.DriverRequests{ 242 { 243 DriverName: goodName, 244 Requests: []resource.ResourceRequest{ 245 { 246 ResourceRequestModel: resource.ResourceRequestModel{ 247 NamedResources: &resource.NamedResourcesRequest{Selector: "true"}, 248 }, 249 }, 250 }, 251 }, 252 { 253 DriverName: goodName, 254 Requests: []resource.ResourceRequest{ 255 { 256 ResourceRequestModel: resource.ResourceRequestModel{ 257 NamedResources: &resource.NamedResourcesRequest{Selector: "true"}, 258 }, 259 }, 260 }, 261 }, 262 } 263 return parameters 264 }(), 265 }, 266 } 267 268 for name, scenario := range scenarios { 269 t.Run(name, func(t *testing.T) { 270 errs := ValidateResourceClaimParameters(scenario.parameters) 271 assert.Equal(t, scenario.wantFailures, errs) 272 }) 273 } 274 } 275 276 func TestValidateResourceClaimParametersUpdate(t *testing.T) { 277 name := "valid" 278 validResourceClaimParameters := testResourceClaimParameters(name, name, nil) 279 280 scenarios := map[string]struct { 281 oldResourceClaimParameters *resource.ResourceClaimParameters 282 update func(claim *resource.ResourceClaimParameters) *resource.ResourceClaimParameters 283 wantFailures field.ErrorList 284 }{ 285 "valid-no-op-update": { 286 oldResourceClaimParameters: validResourceClaimParameters, 287 update: func(claim *resource.ResourceClaimParameters) *resource.ResourceClaimParameters { return claim }, 288 }, 289 "invalid-name-update": { 290 oldResourceClaimParameters: validResourceClaimParameters, 291 update: func(claim *resource.ResourceClaimParameters) *resource.ResourceClaimParameters { 292 claim.Name += "-update" 293 return claim 294 }, 295 wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "name"), name+"-update", "field is immutable")}, 296 }, 297 } 298 299 for name, scenario := range scenarios { 300 t.Run(name, func(t *testing.T) { 301 scenario.oldResourceClaimParameters.ResourceVersion = "1" 302 errs := ValidateResourceClaimParametersUpdate(scenario.update(scenario.oldResourceClaimParameters.DeepCopy()), scenario.oldResourceClaimParameters) 303 assert.Equal(t, scenario.wantFailures, errs) 304 }) 305 } 306 }