Merge pull request #36021 from soltysh/cronjobs

Automatic merge from submit-queue

Rename ScheduledJobs to CronJobs

I went with @smarterclayton idea of registering named types in schema. This way we can support both the new (CronJobs) and old (ScheduledJobs) resource name. Fixes #32150.

fyi @erictune @caesarxuchao @janetkuo 

Not ready yet, but getting close there...

**Release note**:
```release-note
Rename ScheduledJobs to CronJobs.
```
This commit is contained in:
Kubernetes Submit Queue
2016-11-07 07:12:17 -08:00
committed by GitHub
75 changed files with 2964 additions and 2910 deletions

View File

@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package scheduledjob
package cronjob
/*
I did not use watch or expectations. Those add a lot of corner cases, and we aren't
expecting a large volume of jobs or scheduledJobs. (We are favoring correctness
over scalability. If we find a single controller thread is too slow because
there are a lot of Jobs or ScheduledJobs, we we can parallelize by Namespace.
there are a lot of Jobs or CronJobs, we we can parallelize by Namespace.
If we find the load on the API server is too high, we can use a watch and
UndeltaStore.)
@@ -49,9 +49,9 @@ import (
"k8s.io/kubernetes/pkg/util/wait"
)
// Utilities for dealing with Jobs and ScheduledJobs and time.
// Utilities for dealing with Jobs and CronJobs and time.
type ScheduledJobController struct {
type CronJobController struct {
kubeClient clientset.Interface
jobControl jobControlInterface
sjControl sjControlInterface
@@ -59,51 +59,51 @@ type ScheduledJobController struct {
recorder record.EventRecorder
}
func NewScheduledJobController(kubeClient clientset.Interface) *ScheduledJobController {
func NewCronJobController(kubeClient clientset.Interface) *CronJobController {
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
// TODO: remove the wrapper when every clients have moved to use the clientset.
eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")})
if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil {
metrics.RegisterMetricAndTrackRateLimiterUsage("scheduledjob_controller", kubeClient.Core().RESTClient().GetRateLimiter())
metrics.RegisterMetricAndTrackRateLimiterUsage("cronjob_controller", kubeClient.Core().RESTClient().GetRateLimiter())
}
jm := &ScheduledJobController{
jm := &CronJobController{
kubeClient: kubeClient,
jobControl: realJobControl{KubeClient: kubeClient},
sjControl: &realSJControl{KubeClient: kubeClient},
podControl: &realPodControl{KubeClient: kubeClient},
recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "scheduledjob-controller"}),
recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "cronjob-controller"}),
}
return jm
}
func NewScheduledJobControllerFromClient(kubeClient clientset.Interface) *ScheduledJobController {
jm := NewScheduledJobController(kubeClient)
func NewCronJobControllerFromClient(kubeClient clientset.Interface) *CronJobController {
jm := NewCronJobController(kubeClient)
return jm
}
// Run the main goroutine responsible for watching and syncing jobs.
func (jm *ScheduledJobController) Run(stopCh <-chan struct{}) {
func (jm *CronJobController) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
glog.Infof("Starting ScheduledJob Manager")
glog.Infof("Starting CronJob Manager")
// Check things every 10 second.
go wait.Until(jm.SyncAll, 10*time.Second, stopCh)
<-stopCh
glog.Infof("Shutting down ScheduledJob Manager")
glog.Infof("Shutting down CronJob Manager")
}
// SyncAll lists all the ScheduledJobs and Jobs and reconciles them.
func (jm *ScheduledJobController) SyncAll() {
sjl, err := jm.kubeClient.Batch().ScheduledJobs(api.NamespaceAll).List(api.ListOptions{})
// SyncAll lists all the CronJobs and Jobs and reconciles them.
func (jm *CronJobController) SyncAll() {
sjl, err := jm.kubeClient.Batch().CronJobs(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
glog.Errorf("Error listing scheduledjobs: %v", err)
glog.Errorf("Error listing cronjobs: %v", err)
return
}
sjs := sjl.Items
glog.V(4).Infof("Found %d scheduledjobs", len(sjs))
glog.V(4).Infof("Found %d cronjobs", len(sjs))
jl, err := jm.kubeClient.Batch().Jobs(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
@@ -121,11 +121,11 @@ func (jm *ScheduledJobController) SyncAll() {
}
}
// SyncOne reconciles a ScheduledJob with a list of any Jobs that it created.
// SyncOne reconciles a CronJob with a list of any Jobs that it created.
// All known jobs created by "sj" should be included in "js".
// The current time is passed in to facilitate testing.
// It has no receiver, to facilitate testing.
func SyncOne(sj batch.ScheduledJob, js []batch.Job, now time.Time, jc jobControlInterface, sjc sjControlInterface, pc podControlInterface, recorder record.EventRecorder) {
func SyncOne(sj batch.CronJob, js []batch.Job, now time.Time, jc jobControlInterface, sjc sjControlInterface, pc podControlInterface, recorder record.EventRecorder) {
nameForLog := fmt.Sprintf("%s/%s", sj.Namespace, sj.Name)
for i := range js {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package scheduledjob
package cronjob
import (
"testing"
@@ -72,17 +72,17 @@ func justAfterThePriorHour() time.Time {
return T1
}
// returns a scheduledJob with some fields filled in.
func scheduledJob() batch.ScheduledJob {
return batch.ScheduledJob{
// returns a cronJob with some fields filled in.
func cronJob() batch.CronJob {
return batch.CronJob{
ObjectMeta: api.ObjectMeta{
Name: "myscheduledjob",
Name: "mycronjob",
Namespace: "snazzycats",
UID: types.UID("1a2b3c"),
SelfLink: "/apis/batch/v2alpha1/namespaces/snazzycats/scheduledjobs/myscheduledjob",
SelfLink: "/apis/batch/v2alpha1/namespaces/snazzycats/cronjobs/mycronjob",
CreationTimestamp: unversioned.Time{Time: justBeforeTheHour()},
},
Spec: batch.ScheduledJobSpec{
Spec: batch.CronJobSpec{
Schedule: "* * * * ?",
ConcurrencyPolicy: batch.AllowConcurrent,
JobTemplate: batch.JobTemplateSpec{
@@ -190,7 +190,7 @@ func TestSyncOne_RunOrNot(t *testing.T) {
"still active, is time, not past deadline": {A, F, onTheHour, longDead, T, T, justAfterTheHour(), T, F, 2},
}
for name, tc := range testCases {
sj := scheduledJob()
sj := cronJob()
sj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy
sj.Spec.Suspend = &tc.suspend
sj.Spec.Schedule = tc.schedule
@@ -338,7 +338,7 @@ func TestSyncOne_Status(t *testing.T) {
for name, tc := range testCases {
// Setup the test
sj := scheduledJob()
sj := cronJob()
sj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy
sj.Spec.Suspend = &tc.suspend
sj.Spec.Schedule = tc.schedule

View File

@@ -14,5 +14,5 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package scheduledjob contains the controller for ScheduledJob objects.
package scheduledjob
// Package cronjob contains the controller for CronJob objects.
package cronjob

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package scheduledjob
package cronjob
import (
"fmt"
@@ -27,10 +27,10 @@ import (
"k8s.io/kubernetes/pkg/labels"
)
// sjControlInterface is an interface that knows how to update ScheduledJob status
// sjControlInterface is an interface that knows how to update CronJob status
// created as an interface to allow testing.
type sjControlInterface interface {
UpdateStatus(sj *batch.ScheduledJob) (*batch.ScheduledJob, error)
UpdateStatus(sj *batch.CronJob) (*batch.CronJob, error)
}
// realSJControl is the default implementation of sjControlInterface.
@@ -40,18 +40,18 @@ type realSJControl struct {
var _ sjControlInterface = &realSJControl{}
func (c *realSJControl) UpdateStatus(sj *batch.ScheduledJob) (*batch.ScheduledJob, error) {
return c.KubeClient.Batch().ScheduledJobs(sj.Namespace).UpdateStatus(sj)
func (c *realSJControl) UpdateStatus(sj *batch.CronJob) (*batch.CronJob, error) {
return c.KubeClient.Batch().CronJobs(sj.Namespace).UpdateStatus(sj)
}
// fakeSJControl is the default implementation of sjControlInterface.
type fakeSJControl struct {
Updates []batch.ScheduledJob
Updates []batch.CronJob
}
var _ sjControlInterface = &fakeSJControl{}
func (c *fakeSJControl) UpdateStatus(sj *batch.ScheduledJob) (*batch.ScheduledJob, error) {
func (c *fakeSJControl) UpdateStatus(sj *batch.CronJob) (*batch.CronJob, error) {
c.Updates = append(c.Updates, *sj)
return sj, nil
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package scheduledjob
package cronjob
import (
"encoding/json"
@@ -33,9 +33,9 @@ import (
hashutil "k8s.io/kubernetes/pkg/util/hash"
)
// Utilities for dealing with Jobs and ScheduledJobs and time.
// Utilities for dealing with Jobs and CronJobs and time.
func inActiveList(sj batch.ScheduledJob, uid types.UID) bool {
func inActiveList(sj batch.CronJob, uid types.UID) bool {
for _, j := range sj.Status.Active {
if j.UID == uid {
return true
@@ -44,7 +44,7 @@ func inActiveList(sj batch.ScheduledJob, uid types.UID) bool {
return false
}
func deleteFromActiveList(sj *batch.ScheduledJob, uid types.UID) {
func deleteFromActiveList(sj *batch.CronJob, uid types.UID) {
if sj == nil {
return
}
@@ -70,8 +70,8 @@ func getParentUIDFromJob(j batch.Job) (types.UID, bool) {
glog.V(4).Infof("Job with unparsable created-by annotation, name %s namespace %s: %v", j.Name, j.Namespace, err)
return types.UID(""), false
}
if sr.Reference.Kind != "ScheduledJob" {
glog.V(4).Infof("Job with non-ScheduledJob parent, name %s namespace %s", j.Name, j.Namespace)
if sr.Reference.Kind != "CronJob" {
glog.V(4).Infof("Job with non-CronJob parent, name %s namespace %s", j.Name, j.Namespace)
return types.UID(""), false
}
// Don't believe a job that claims to have a parent in a different namespace.
@@ -85,7 +85,7 @@ func getParentUIDFromJob(j batch.Job) (types.UID, bool) {
// groupJobsByParent groups jobs into a map keyed by the job parent UID (e.g. scheduledJob).
// It has no receiver, to facilitate testing.
func groupJobsByParent(sjs []batch.ScheduledJob, js []batch.Job) map[types.UID][]batch.Job {
func groupJobsByParent(sjs []batch.CronJob, js []batch.Job) map[types.UID][]batch.Job {
jobsBySj := make(map[types.UID][]batch.Job)
for _, job := range js {
parentUID, found := getParentUIDFromJob(job)
@@ -120,7 +120,7 @@ func getNextStartTimeAfter(schedule string, now time.Time) (time.Time, error) {
//
// If there are too many (>100) unstarted times, just give up and return an empty slice.
// If there were missed times prior to the last known start time, then those are not returned.
func getRecentUnmetScheduleTimes(sj batch.ScheduledJob, now time.Time) ([]time.Time, error) {
func getRecentUnmetScheduleTimes(sj batch.CronJob, now time.Time) ([]time.Time, error) {
starts := []time.Time{}
sched, err := cron.ParseStandard(sj.Spec.Schedule)
if err != nil {
@@ -135,7 +135,7 @@ func getRecentUnmetScheduleTimes(sj batch.ScheduledJob, now time.Time) ([]time.T
// in kubernetes says it may need to be recreated), or that we have
// started a job, but have not noticed it yet (distributed systems can
// have arbitrary delays). In any case, use the creation time of the
// ScheduledJob as last known start time.
// CronJob as last known start time.
earliestTime = sj.ObjectMeta.CreationTimestamp.Time
}
@@ -172,8 +172,8 @@ func getRecentUnmetScheduleTimes(sj batch.ScheduledJob, now time.Time) ([]time.T
// XXX unit test this
// getJobFromTemplate makes a Job from a ScheduledJob
func getJobFromTemplate(sj *batch.ScheduledJob, scheduledTime time.Time) (*batch.Job, error) {
// getJobFromTemplate makes a Job from a CronJob
func getJobFromTemplate(sj *batch.CronJob, scheduledTime time.Time) (*batch.Job, error) {
// TODO: consider adding the following labels:
// nominal-start-time=$RFC_3339_DATE_OF_INTENDED_START -- for user convenience
// scheduled-job-name=$SJ_NAME -- for user convenience

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package scheduledjob
package cronjob
import (
//"fmt"
@@ -37,14 +37,14 @@ func TestGetJobFromTemplate(t *testing.T) {
var one int64 = 1
var no bool = false
sj := batch.ScheduledJob{
sj := batch.CronJob{
ObjectMeta: api.ObjectMeta{
Name: "myscheduledjob",
Name: "mycronjob",
Namespace: "snazzycats",
UID: types.UID("1a2b3c"),
SelfLink: "/apis/extensions/v1beta1/namespaces/snazzycats/jobs/myscheduledjob",
SelfLink: "/apis/extensions/v1beta1/namespaces/snazzycats/jobs/mycronjob",
},
Spec: batch.ScheduledJobSpec{
Spec: batch.CronJobSpec{
Schedule: "* * * * ?",
ConcurrencyPolicy: batch.AllowConcurrent,
JobTemplate: batch.JobTemplateSpec{
@@ -77,7 +77,7 @@ func TestGetJobFromTemplate(t *testing.T) {
if err != nil {
t.Errorf("Did not expect error: %s", err)
}
if !strings.HasPrefix(job.ObjectMeta.Name, "myscheduledjob-") {
if !strings.HasPrefix(job.ObjectMeta.Name, "mycronjob-") {
t.Errorf("Wrong Name")
}
if len(job.ObjectMeta.Labels) != 1 {
@@ -90,7 +90,7 @@ func TestGetJobFromTemplate(t *testing.T) {
if !ok {
t.Errorf("Missing created-by annotation")
}
expectedCreatedBy := `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ScheduledJob","namespace":"snazzycats","name":"myscheduledjob","uid":"1a2b3c","apiVersion":"extensions"}}
expectedCreatedBy := `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"CronJob","namespace":"snazzycats","name":"mycronjob","uid":"1a2b3c","apiVersion":"extensions"}}
`
if len(v) != len(expectedCreatedBy) {
t.Errorf("Wrong length for created-by annotation, expected %v got %v", len(expectedCreatedBy), len(v))
@@ -140,7 +140,7 @@ func TestGetParentUIDFromJob(t *testing.T) {
}
{
// Case 2: Has UID annotation
j.ObjectMeta.Annotations = map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ScheduledJob","namespace":"default","name":"pi","uid":"5ef034e0-1890-11e6-8935-42010af0003e","apiVersion":"extensions","resourceVersion":"427339"}}`}
j.ObjectMeta.Annotations = map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"CronJob","namespace":"default","name":"pi","uid":"5ef034e0-1890-11e6-8935-42010af0003e","apiVersion":"extensions","resourceVersion":"427339"}}`}
expectedUID := types.UID("5ef034e0-1890-11e6-8935-42010af0003e")
@@ -158,14 +158,14 @@ func TestGroupJobsByParent(t *testing.T) {
uid1 := types.UID("11111111-1111-1111-1111-111111111111")
uid2 := types.UID("22222222-2222-2222-2222-222222222222")
uid3 := types.UID("33333333-3333-3333-3333-333333333333")
createdBy1 := map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ScheduledJob","namespace":"x","name":"pi","uid":"11111111-1111-1111-1111-111111111111","apiVersion":"extensions","resourceVersion":"111111"}}`}
createdBy2 := map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ScheduledJob","namespace":"x","name":"pi","uid":"22222222-2222-2222-2222-222222222222","apiVersion":"extensions","resourceVersion":"222222"}}`}
createdBy3 := map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ScheduledJob","namespace":"y","name":"pi","uid":"33333333-3333-3333-3333-333333333333","apiVersion":"extensions","resourceVersion":"333333"}}`}
createdBy1 := map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"CronJob","namespace":"x","name":"pi","uid":"11111111-1111-1111-1111-111111111111","apiVersion":"extensions","resourceVersion":"111111"}}`}
createdBy2 := map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"CronJob","namespace":"x","name":"pi","uid":"22222222-2222-2222-2222-222222222222","apiVersion":"extensions","resourceVersion":"222222"}}`}
createdBy3 := map[string]string{api.CreatedByAnnotation: `{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"CronJob","namespace":"y","name":"pi","uid":"33333333-3333-3333-3333-333333333333","apiVersion":"extensions","resourceVersion":"333333"}}`}
noCreatedBy := map[string]string{}
{
// Case 1: There are no jobs and scheduledJobs
sjs := []batch.ScheduledJob{}
sjs := []batch.CronJob{}
js := []batch.Job{}
jobsBySj := groupJobsByParent(sjs, js)
if len(jobsBySj) != 0 {
@@ -175,7 +175,7 @@ func TestGroupJobsByParent(t *testing.T) {
{
// Case 2: there is one controller with no job.
sjs := []batch.ScheduledJob{
sjs := []batch.CronJob{
{ObjectMeta: api.ObjectMeta{Name: "e", Namespace: "x", UID: uid1}},
}
js := []batch.Job{}
@@ -187,7 +187,7 @@ func TestGroupJobsByParent(t *testing.T) {
{
// Case 3: there is one controller with one job it created.
sjs := []batch.ScheduledJob{
sjs := []batch.CronJob{
{ObjectMeta: api.ObjectMeta{Name: "e", Namespace: "x", UID: uid1}},
}
js := []batch.Job{
@@ -219,7 +219,7 @@ func TestGroupJobsByParent(t *testing.T) {
{ObjectMeta: api.ObjectMeta{Name: "b", Namespace: "y", Annotations: createdBy3}},
{ObjectMeta: api.ObjectMeta{Name: "d", Namespace: "y", Annotations: noCreatedBy}},
}
sjs := []batch.ScheduledJob{
sjs := []batch.CronJob{
{ObjectMeta: api.ObjectMeta{Name: "e", Namespace: "x", UID: uid1}},
{ObjectMeta: api.ObjectMeta{Name: "f", Namespace: "x", UID: uid2}},
{ObjectMeta: api.ObjectMeta{Name: "g", Namespace: "y", UID: uid3}},
@@ -269,13 +269,13 @@ func TestGetRecentUnmetScheduleTimes(t *testing.T) {
t.Errorf("test setup error: %v", err)
}
sj := batch.ScheduledJob{
sj := batch.CronJob{
ObjectMeta: api.ObjectMeta{
Name: "myscheduledjob",
Name: "mycronjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: batch.ScheduledJobSpec{
Spec: batch.CronJobSpec{
Schedule: schedule,
ConcurrencyPolicy: batch.AllowConcurrent,
JobTemplate: batch.JobTemplateSpec{},