k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/batch/job/storage/storage_test.go (about) 1 /* 2 Copyright 2015 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 storage 18 19 import ( 20 "testing" 21 22 "k8s.io/utils/ptr" 23 24 batchv1 "k8s.io/api/batch/v1" 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/internalversion" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/fields" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apimachinery/pkg/runtime" 31 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 32 "k8s.io/apiserver/pkg/registry/generic" 33 genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" 34 "k8s.io/apiserver/pkg/registry/rest" 35 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" 36 "k8s.io/apiserver/pkg/warning" 37 "k8s.io/kubernetes/pkg/apis/batch" 38 api "k8s.io/kubernetes/pkg/apis/core" 39 "k8s.io/kubernetes/pkg/registry/registrytest" 40 ) 41 42 func newStorage(t *testing.T) (*JobStorage, *etcd3testing.EtcdTestServer) { 43 etcdStorage, server := registrytest.NewEtcdStorage(t, batch.GroupName) 44 restOptions := generic.RESTOptions{ 45 StorageConfig: etcdStorage, 46 Decorator: generic.UndecoratedStorage, 47 DeleteCollectionWorkers: 1, 48 ResourcePrefix: "jobs", 49 } 50 jobStorage, err := NewStorage(restOptions) 51 if err != nil { 52 t.Fatalf("unexpected error from REST storage: %v", err) 53 } 54 return &jobStorage, server 55 } 56 57 func validNewJob() *batch.Job { 58 completions := int32(1) 59 parallelism := int32(1) 60 return &batch.Job{ 61 ObjectMeta: metav1.ObjectMeta{ 62 Name: "foo", 63 Namespace: "default", 64 }, 65 Spec: batch.JobSpec{ 66 Completions: &completions, 67 Parallelism: ¶llelism, 68 Selector: &metav1.LabelSelector{ 69 MatchLabels: map[string]string{"a": "b"}, 70 }, 71 ManualSelector: newBool(true), 72 Template: api.PodTemplateSpec{ 73 ObjectMeta: metav1.ObjectMeta{ 74 Labels: map[string]string{"a": "b"}, 75 }, 76 Spec: api.PodSpec{ 77 Containers: []api.Container{ 78 { 79 Name: "test", 80 Image: "test_image", 81 ImagePullPolicy: api.PullIfNotPresent, 82 TerminationMessagePolicy: api.TerminationMessageReadFile, 83 }, 84 }, 85 RestartPolicy: api.RestartPolicyOnFailure, 86 DNSPolicy: api.DNSClusterFirst, 87 }, 88 }, 89 }, 90 } 91 } 92 93 func validNewV1Job() *batchv1.Job { 94 completions := int32(1) 95 parallelism := int32(1) 96 return &batchv1.Job{ 97 ObjectMeta: metav1.ObjectMeta{ 98 Name: "foo", 99 Namespace: "default", 100 }, 101 Spec: batchv1.JobSpec{ 102 Completions: &completions, 103 Parallelism: ¶llelism, 104 Selector: &metav1.LabelSelector{ 105 MatchLabels: map[string]string{"a": "b"}, 106 }, 107 ManualSelector: newBool(true), 108 Template: corev1.PodTemplateSpec{ 109 ObjectMeta: metav1.ObjectMeta{ 110 Labels: map[string]string{"a": "b"}, 111 }, 112 Spec: corev1.PodSpec{ 113 Containers: []corev1.Container{ 114 { 115 Name: "test", 116 Image: "test_image", 117 ImagePullPolicy: corev1.PullIfNotPresent, 118 TerminationMessagePolicy: corev1.TerminationMessageReadFile, 119 }, 120 }, 121 RestartPolicy: corev1.RestartPolicyOnFailure, 122 DNSPolicy: corev1.DNSClusterFirst, 123 }, 124 }, 125 }, 126 } 127 } 128 129 func TestCreate(t *testing.T) { 130 storage, server := newStorage(t) 131 defer server.Terminate(t) 132 defer storage.Job.Store.DestroyFunc() 133 test := genericregistrytest.New(t, storage.Job.Store) 134 validJob := validNewJob() 135 validJob.ObjectMeta = metav1.ObjectMeta{} 136 test.TestCreate( 137 // valid 138 validJob, 139 // invalid (empty selector) 140 &batch.Job{ 141 Spec: batch.JobSpec{ 142 ManualSelector: ptr.To(false), 143 Completions: validJob.Spec.Completions, 144 Selector: &metav1.LabelSelector{}, 145 Template: validJob.Spec.Template, 146 }, 147 }, 148 ) 149 } 150 151 func TestUpdate(t *testing.T) { 152 storage, server := newStorage(t) 153 defer server.Terminate(t) 154 defer storage.Job.Store.DestroyFunc() 155 test := genericregistrytest.New(t, storage.Job.Store) 156 two := int32(2) 157 test.TestUpdate( 158 // valid 159 validNewJob(), 160 // updateFunc 161 func(obj runtime.Object) runtime.Object { 162 object := obj.(*batch.Job) 163 object.Spec.Parallelism = &two 164 return object 165 }, 166 // invalid updateFunc 167 func(obj runtime.Object) runtime.Object { 168 object := obj.(*batch.Job) 169 object.Spec.Selector = &metav1.LabelSelector{} 170 return object 171 }, 172 func(obj runtime.Object) runtime.Object { 173 object := obj.(*batch.Job) 174 object.Spec.Completions = &two 175 return object 176 }, 177 ) 178 } 179 180 func TestDelete(t *testing.T) { 181 storage, server := newStorage(t) 182 defer server.Terminate(t) 183 defer storage.Job.Store.DestroyFunc() 184 test := genericregistrytest.New(t, storage.Job.Store) 185 test.TestDelete(validNewJob()) 186 } 187 188 type dummyRecorder struct { 189 agent string 190 text string 191 } 192 193 func (r *dummyRecorder) AddWarning(agent, text string) { 194 r.agent = agent 195 r.text = text 196 return 197 } 198 199 func (r *dummyRecorder) getWarning() string { 200 return r.text 201 } 202 203 var _ warning.Recorder = &dummyRecorder{} 204 205 func TestJobDeletion(t *testing.T) { 206 orphanDependents := true 207 orphanDeletionPropagation := metav1.DeletePropagationOrphan 208 backgroundDeletionPropagation := metav1.DeletePropagationBackground 209 job := validNewV1Job() 210 ctx := genericapirequest.NewDefaultContext() 211 key := "/jobs/" + metav1.NamespaceDefault + "/foo" 212 tests := []struct { 213 description string 214 expectWarning bool 215 deleteOptions *metav1.DeleteOptions 216 listOptions *internalversion.ListOptions 217 requestInfo *genericapirequest.RequestInfo 218 }{ 219 { 220 description: "deletion: no policy, v1, warning", 221 expectWarning: true, 222 deleteOptions: &metav1.DeleteOptions{}, 223 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 224 }, 225 { 226 description: "deletion: no policy, v2, no warning", 227 expectWarning: false, 228 deleteOptions: &metav1.DeleteOptions{}, 229 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v2"}, 230 }, 231 { 232 description: "deletion: no policy, no APIVersion, no warning", 233 expectWarning: false, 234 deleteOptions: &metav1.DeleteOptions{}, 235 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: ""}, 236 }, 237 { 238 description: "deletion: orphan dependents, no warnings", 239 expectWarning: false, 240 deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents}, 241 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 242 }, 243 { 244 description: "deletion: orphan deletion, no warnings", 245 expectWarning: false, 246 deleteOptions: &metav1.DeleteOptions{PropagationPolicy: &orphanDeletionPropagation}, 247 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 248 }, 249 { 250 description: "deletion: background deletion, no warnings", 251 expectWarning: false, 252 deleteOptions: &metav1.DeleteOptions{PropagationPolicy: &backgroundDeletionPropagation}, 253 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 254 }, 255 { 256 description: "deleteCollection: no policy, v1, warning", 257 expectWarning: true, 258 deleteOptions: &metav1.DeleteOptions{}, 259 listOptions: &internalversion.ListOptions{}, 260 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 261 }, 262 { 263 description: "deleteCollection: no policy, v2, no warning", 264 expectWarning: false, 265 deleteOptions: &metav1.DeleteOptions{}, 266 listOptions: &internalversion.ListOptions{}, 267 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v2"}, 268 }, 269 { 270 description: "deleteCollection: no policy, no APIVersion, no warning", 271 expectWarning: false, 272 deleteOptions: &metav1.DeleteOptions{}, 273 listOptions: &internalversion.ListOptions{}, 274 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: ""}, 275 }, 276 { 277 description: "deleteCollection: orphan dependents, no warnings", 278 expectWarning: false, 279 deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents}, 280 listOptions: &internalversion.ListOptions{}, 281 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 282 }, 283 { 284 description: "deletionCollection: orphan deletion, no warnings", 285 expectWarning: false, 286 deleteOptions: &metav1.DeleteOptions{PropagationPolicy: &orphanDeletionPropagation}, 287 listOptions: &internalversion.ListOptions{}, 288 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 289 }, 290 { 291 description: "deletionCollection: background deletion, no warnings", 292 expectWarning: false, 293 deleteOptions: &metav1.DeleteOptions{PropagationPolicy: &backgroundDeletionPropagation}, 294 listOptions: &internalversion.ListOptions{}, 295 requestInfo: &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1"}, 296 }, 297 } 298 for _, test := range tests { 299 t.Run(test.description, func(t *testing.T) { 300 storage, server := newStorage(t) 301 defer server.Terminate(t) 302 defer storage.Job.Store.DestroyFunc() 303 dc := dummyRecorder{agent: "", text: ""} 304 ctx = genericapirequest.WithRequestInfo(ctx, test.requestInfo) 305 ctxWithRecorder := warning.WithWarningRecorder(ctx, &dc) 306 // Create the object 307 if err := storage.Job.Storage.Create(ctxWithRecorder, key, job, nil, 0, false); err != nil { 308 t.Fatalf("unexpected error: %v", err) 309 } 310 _, _, err := storage.Job.Delete(ctxWithRecorder, job.Name, rest.ValidateAllObjectFunc, test.deleteOptions) 311 if err != nil { 312 t.Fatalf("unexpected error: %v", err) 313 } 314 _, err = storage.Job.DeleteCollection(ctxWithRecorder, rest.ValidateAllObjectFunc, test.deleteOptions, test.listOptions) 315 if err != nil { 316 t.Fatalf("unexpected error: %v", err) 317 } 318 if test.expectWarning { 319 if dc.getWarning() != deleteOptionWarnings { 320 t.Fatalf("expected delete option warning but did not get one") 321 } 322 } 323 }) 324 } 325 } 326 327 func TestGet(t *testing.T) { 328 storage, server := newStorage(t) 329 defer server.Terminate(t) 330 defer storage.Job.Store.DestroyFunc() 331 test := genericregistrytest.New(t, storage.Job.Store) 332 test.TestGet(validNewJob()) 333 } 334 335 func TestList(t *testing.T) { 336 storage, server := newStorage(t) 337 defer server.Terminate(t) 338 defer storage.Job.Store.DestroyFunc() 339 test := genericregistrytest.New(t, storage.Job.Store) 340 test.TestList(validNewJob()) 341 } 342 343 func TestWatch(t *testing.T) { 344 storage, server := newStorage(t) 345 defer server.Terminate(t) 346 defer storage.Job.Store.DestroyFunc() 347 test := genericregistrytest.New(t, storage.Job.Store) 348 test.TestWatch( 349 validNewJob(), 350 // matching labels 351 []labels.Set{}, 352 // not matching labels 353 []labels.Set{ 354 {"x": "y"}, 355 }, 356 // matching fields 357 []fields.Set{}, 358 // not matching fields 359 []fields.Set{ 360 {"metadata.name": "xyz"}, 361 {"name": "foo"}, 362 }, 363 ) 364 } 365 366 // TODO: test update /status 367 368 func newBool(val bool) *bool { 369 p := new(bool) 370 *p = val 371 return p 372 } 373 374 func TestCategories(t *testing.T) { 375 storage, server := newStorage(t) 376 defer server.Terminate(t) 377 defer storage.Job.Store.DestroyFunc() 378 expected := []string{"all"} 379 registrytest.AssertCategories(t, storage.Job, expected) 380 }