1
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
137 validJob,
138
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
158 validNewJob(),
159
160 func(obj runtime.Object) runtime.Object {
161 object := obj.(*batch.Job)
162 object.Spec.Parallelism = &two
163 return object
164 },
165
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
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
350 []labels.Set{},
351
352 []labels.Set{
353 {"x": "y"},
354 },
355
356 []fields.Set{},
357
358 []fields.Set{
359 {"metadata.name": "xyz"},
360 {"name": "foo"},
361 },
362 )
363 }
364
365
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 }
380
View as plain text