From 33d778879319595b532f0e7b1bf2abcaeade7e72 Mon Sep 17 00:00:00 2001 From: Anthony Yeh Date: Wed, 12 Apr 2017 17:26:35 -0700 Subject: [PATCH] CronJob: Add e2e test for adoption. Currently, an e2e test is the only way to ensure we have the proper RBAC permissions to adopt Jobs. --- test/e2e/cronjob.go | 51 +++++++++++++++++++++++++++++++++ test/e2e/framework/jobs_util.go | 25 ++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/test/e2e/cronjob.go b/test/e2e/cronjob.go index f28fa3b25da..d6cadef3a71 100644 --- a/test/e2e/cronjob.go +++ b/test/e2e/cronjob.go @@ -33,6 +33,7 @@ import ( batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" batchv2alpha1 "k8s.io/kubernetes/pkg/apis/batch/v2alpha1" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" + "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/job" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/test/e2e/framework" @@ -274,6 +275,53 @@ var _ = framework.KubeDescribe("CronJob", func() { err = deleteCronJob(f.ClientSet, f.Namespace.Name, cronJob.Name) Expect(err).NotTo(HaveOccurred()) }) + + // Adopt Jobs it owns that don't have ControllerRef yet. + // That is, the Jobs were created by a pre-v1.6.0 master. + It("should adopt Jobs it owns that don't have ControllerRef yet", func() { + By("Creating a cronjob") + cronJob := newTestCronJob("adopt", "*/1 * * * ?", batchv2alpha1.ForbidConcurrent, + sleepCommand, nil) + // Replace cronJob with the one returned from Create() so it has the UID. + // Save Kind since it won't be populated in the returned cronJob. + kind := cronJob.Kind + cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) + Expect(err).NotTo(HaveOccurred()) + cronJob.Kind = kind + + By("Ensuring a Job is running") + err = waitForActiveJobs(f.ClientSet, f.Namespace.Name, cronJob.Name, 1) + Expect(err).NotTo(HaveOccurred()) + + By("Orphaning a Job") + jobs, err := f.ClientSet.BatchV1().Jobs(f.Namespace.Name).List(metav1.ListOptions{}) + Expect(err).NotTo(HaveOccurred()) + Expect(jobs.Items).To(HaveLen(1)) + job := jobs.Items[0] + framework.UpdateJobFunc(f.ClientSet, f.Namespace.Name, job.Name, func(job *batchv1.Job) { + job.OwnerReferences = nil + }) + + By("Checking that the CronJob readopts the Job") + Expect(wait.Poll(framework.Poll, cronJobTimeout, func() (bool, error) { + job, err := framework.GetJob(f.ClientSet, f.Namespace.Name, job.Name) + if err != nil { + return false, err + } + controllerRef := controller.GetControllerOf(job) + if controllerRef == nil { + return false, nil + } + if controllerRef.Kind != cronJob.Kind || controllerRef.Name != cronJob.Name || controllerRef.UID != cronJob.UID { + return false, fmt.Errorf("Job has wrong controllerRef: got %v, want %v", controllerRef, cronJob) + } + return true, nil + })).To(Succeed(), "wait for Job %q to be readopted", job.Name) + + By("Removing CronJob") + err = deleteCronJob(f.ClientSet, f.Namespace.Name, cronJob.Name) + Expect(err).NotTo(HaveOccurred()) + }) }) // newTestCronJob returns a cronjob which does one of several testing behaviors. @@ -285,6 +333,9 @@ func newTestCronJob(name, schedule string, concurrencyPolicy batchv2alpha1.Concu ObjectMeta: metav1.ObjectMeta{ Name: name, }, + TypeMeta: metav1.TypeMeta{ + Kind: "CronJob", + }, Spec: batchv2alpha1.CronJobSpec{ Schedule: schedule, ConcurrencyPolicy: concurrencyPolicy, diff --git a/test/e2e/framework/jobs_util.go b/test/e2e/framework/jobs_util.go index 0862e615284..e333f8ebefb 100644 --- a/test/e2e/framework/jobs_util.go +++ b/test/e2e/framework/jobs_util.go @@ -17,8 +17,10 @@ limitations under the License. package framework import ( + "fmt" "time" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/wait" @@ -120,6 +122,29 @@ func UpdateJob(c clientset.Interface, ns string, job *batch.Job) (*batch.Job, er return c.Batch().Jobs(ns).Update(job) } +// UpdateJobFunc updates the job object. It retries if there is a conflict, throw out error if +// there is any other errors. name is the job name, updateFn is the function updating the +// job object. +func UpdateJobFunc(c clientset.Interface, ns, name string, updateFn func(job *batch.Job)) { + ExpectNoError(wait.Poll(time.Millisecond*500, time.Second*30, func() (bool, error) { + job, err := GetJob(c, ns, name) + if err != nil { + return false, fmt.Errorf("failed to get pod %q: %v", name, err) + } + updateFn(job) + _, err = UpdateJob(c, ns, job) + if err == nil { + Logf("Successfully updated job %q", name) + return true, nil + } + if errors.IsConflict(err) { + Logf("Conflicting update to job %q, re-get and re-update: %v", name, err) + return false, nil + } + return false, fmt.Errorf("failed to update job %q: %v", name, err) + })) +} + // DeleteJob uses c to delete the Job named name in namespace ns. If the returned error is nil, the Job has been // deleted. func DeleteJob(c clientset.Interface, ns, name string) error {