Merge pull request #30420 from janetkuo/sj-job-determi

Automatic merge from submit-queue

Name jobs created by sj deterministically

```release-note
Name the job created by scheduledjob (sj) deterministically with sj's name and a hash of job's scheduled time.
```

@erictune @soltysh

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.kubernetes.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.kubernetes.io/reviews/kubernetes/kubernetes/30420)
<!-- Reviewable:end -->
This commit is contained in:
Kubernetes Submit Queue 2016-08-11 23:14:02 -07:00 committed by GitHub
commit 4a5c852697
3 changed files with 21 additions and 14 deletions

View File

@ -209,7 +209,7 @@ func SyncOne(sj batch.ScheduledJob, js []batch.Job, now time.Time, jc jobControl
} }
} }
jobReq, err := getJobFromTemplate(&sj) jobReq, err := getJobFromTemplate(&sj, scheduledTime)
if err != nil { if err != nil {
glog.Errorf("Unable to make Job from template in %s: %v", nameForLog, err) glog.Errorf("Unable to make Job from template in %s: %v", nameForLog, err)
return return
@ -228,8 +228,8 @@ func SyncOne(sj batch.ScheduledJob, js []batch.Job, now time.Time, jc jobControl
// the next time. Actually, if we relist the SJs and Jobs on the next // the next time. Actually, if we relist the SJs and Jobs on the next
// iteration of SyncAll, we might not see our own status update, and // iteration of SyncAll, we might not see our own status update, and
// then post one again. So, we need to use the job name as a lock to // then post one again. So, we need to use the job name as a lock to
// prevent us from making the job twice. TODO: name the job // prevent us from making the job twice (name the job with hash of its
// deterministically (via hash of its scheduled time). // scheduled time).
// Add the just-started job to the status list. // Add the just-started job to the status list.
ref, err := getRef(jobResp) ref, err := getRef(jobResp)

View File

@ -19,6 +19,7 @@ package scheduledjob
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"hash/adler32"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
@ -29,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/types"
hashutil "k8s.io/kubernetes/pkg/util/hash"
) )
// Utilities for dealing with Jobs and ScheduledJobs and time. // Utilities for dealing with Jobs and ScheduledJobs and time.
@ -186,7 +188,7 @@ func addSeconds(schedule string) string {
// XXX unit test this // XXX unit test this
// getJobFromTemplate makes a Job from a ScheduledJob // getJobFromTemplate makes a Job from a ScheduledJob
func getJobFromTemplate(sj *batch.ScheduledJob) (*batch.Job, error) { func getJobFromTemplate(sj *batch.ScheduledJob, scheduledTime time.Time) (*batch.Job, error) {
// TODO: consider adding the following labels: // TODO: consider adding the following labels:
// nominal-start-time=$RFC_3339_DATE_OF_INTENDED_START -- for user convenience // nominal-start-time=$RFC_3339_DATE_OF_INTENDED_START -- for user convenience
// scheduled-job-name=$SJ_NAME -- for user convenience // scheduled-job-name=$SJ_NAME -- for user convenience
@ -197,16 +199,14 @@ func getJobFromTemplate(sj *batch.ScheduledJob) (*batch.Job, error) {
return nil, err return nil, err
} }
annotations[CreatedByAnnotation] = string(createdByRefJson) annotations[CreatedByAnnotation] = string(createdByRefJson)
// TODO: instead of using generateName, use a deterministic hash of the nominal // We want job names for a given nominal start time to have a deterministic name to avoid the same job being created twice
// start time, to prevent same job being created twice. name := fmt.Sprintf("%s-%d", sj.Name, getTimeHash(scheduledTime))
// We want job names for a given nominal start time to have a predictable name to avoid
prefix := fmt.Sprintf("%s-", sj.Name)
job := &batch.Job{ job := &batch.Job{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: labels, Labels: labels,
Annotations: annotations, Annotations: annotations,
GenerateName: prefix, Name: name,
}, },
} }
if err := api.Scheme.Convert(&sj.Spec.JobTemplate.Spec, &job.Spec); err != nil { if err := api.Scheme.Convert(&sj.Spec.JobTemplate.Spec, &job.Spec); err != nil {
@ -215,6 +215,12 @@ func getJobFromTemplate(sj *batch.ScheduledJob) (*batch.Job, error) {
return job, nil return job, nil
} }
func getTimeHash(scheduledTime time.Time) uint32 {
timeHasher := adler32.New()
hashutil.DeepHashObject(timeHasher, scheduledTime)
return timeHasher.Sum32()
}
// makeCreatedByRefJson makes a json string with an object reference for use in "created-by" annotation value // makeCreatedByRefJson makes a json string with an object reference for use in "created-by" annotation value
func makeCreatedByRefJson(object runtime.Object) (string, error) { func makeCreatedByRefJson(object runtime.Object) (string, error) {
createdByRef, err := api.GetReference(object) createdByRef, err := api.GetReference(object)

View File

@ -18,6 +18,7 @@ package scheduledjob
import ( import (
//"fmt" //"fmt"
"strings"
"testing" "testing"
"time" "time"
@ -72,12 +73,12 @@ func TestGetJobFromTemplate(t *testing.T) {
} }
var job *batch.Job var job *batch.Job
job, err := getJobFromTemplate(&sj) job, err := getJobFromTemplate(&sj, time.Time{})
if err != nil { if err != nil {
t.Errorf("Did not expect error: %s", err) t.Errorf("Did not expect error: %s", err)
} }
if job.ObjectMeta.GenerateName != "myscheduledjob-" { if !strings.HasPrefix(job.ObjectMeta.Name, "myscheduledjob-") {
t.Errorf("Wrong GenerateName") t.Errorf("Wrong Name")
} }
if len(job.ObjectMeta.Labels) != 1 { if len(job.ObjectMeta.Labels) != 1 {
t.Errorf("Wrong number of labels") t.Errorf("Wrong number of labels")