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: &parallelism,
    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: &parallelism,
   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  }