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