From df124fed14d76d45d6640028ed509657bad380bf Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Wed, 13 Jul 2016 17:56:30 +0200 Subject: [PATCH 1/5] ScheduledJob suspend being pointer leftovers --- pkg/api/testing/fuzzer.go | 8 ++++++++ pkg/apis/batch/types.go | 2 +- pkg/apis/batch/v2alpha1/defaults.go | 3 +++ pkg/apis/batch/v2alpha1/types.go | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index f48aca43585..73b54762eab 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -169,6 +169,14 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source) j.ManualSelector = nil } }, + func(sj *batch.ScheduledJobSpec, c fuzz.Continue) { + c.FuzzNoCustom(sj) + suspend := c.RandBool() + sj.Suspend = &suspend + sds := int64(c.RandUint64()) + sj.StartingDeadlineSeconds = &sds + sj.Schedule = c.RandString() + }, func(cp *batch.ConcurrencyPolicy, c fuzz.Continue) { policies := []batch.ConcurrencyPolicy{batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent} *cp = policies[c.Rand.Intn(len(policies))] diff --git a/pkg/apis/batch/types.go b/pkg/apis/batch/types.go index 61a35d47fa7..7db3057bebc 100644 --- a/pkg/apis/batch/types.go +++ b/pkg/apis/batch/types.go @@ -209,7 +209,7 @@ type ScheduledJobSpec struct { // Suspend flag tells the controller to suspend subsequent executions, it does // not apply to already started executions. Defaults to false. - Suspend *bool `json:"suspend"` + Suspend *bool `json:"suspend,omitempty"` // JobTemplate is the object that describes the job that will be created when // executing a ScheduledJob. diff --git a/pkg/apis/batch/v2alpha1/defaults.go b/pkg/apis/batch/v2alpha1/defaults.go index a7d24ceac91..42f820074b8 100644 --- a/pkg/apis/batch/v2alpha1/defaults.go +++ b/pkg/apis/batch/v2alpha1/defaults.go @@ -46,4 +46,7 @@ func SetDefaults_ScheduledJob(obj *ScheduledJob) { if obj.Spec.ConcurrencyPolicy == "" { obj.Spec.ConcurrencyPolicy = AllowConcurrent } + if obj.Spec.Suspend == nil { + obj.Spec.Suspend = new(bool) + } } diff --git a/pkg/apis/batch/v2alpha1/types.go b/pkg/apis/batch/v2alpha1/types.go index 0402df2e813..ce44235f2d5 100644 --- a/pkg/apis/batch/v2alpha1/types.go +++ b/pkg/apis/batch/v2alpha1/types.go @@ -211,7 +211,7 @@ type ScheduledJobSpec struct { // Suspend flag tells the controller to suspend subsequent executions, it does // not apply to already started executions. Defaults to false. - Suspend *bool `json:"suspend" protobuf:"varint,4,opt,name=suspend"` + Suspend *bool `json:"suspend,omitempty" protobuf:"varint,4,opt,name=suspend"` // JobTemplate is the object that describes the job that will be created when // executing a ScheduledJob. From 17c8feee0f058bd7e4a4c19ded635d4f4a7d73b1 Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Wed, 13 Jul 2016 17:56:46 +0200 Subject: [PATCH 2/5] ScheduledJob suspend being pointer leftovers - generated changes --- pkg/apis/batch/types.generated.go | 47 +++++++++++++--------- pkg/apis/batch/v2alpha1/types.generated.go | 47 +++++++++++++--------- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/pkg/apis/batch/types.generated.go b/pkg/apis/batch/types.generated.go index 03f0d248c64..4088aaa510e 100644 --- a/pkg/apis/batch/types.generated.go +++ b/pkg/apis/batch/types.generated.go @@ -3518,11 +3518,12 @@ func (x *ScheduledJobSpec) CodecEncodeSelf(e *codec1978.Encoder) { const yyr2 bool = false yyq2[1] = x.StartingDeadlineSeconds != nil yyq2[2] = x.ConcurrencyPolicy != "" + yyq2[3] = x.Suspend != nil var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(5) } else { - yynn2 = 3 + yynn2 = 2 for _, b := range yyq2 { if b { yynn2++ @@ -3602,30 +3603,36 @@ func (x *ScheduledJobSpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.Suspend == nil { - r.EncodeNil() - } else { - yy15 := *x.Suspend - yym16 := z.EncBinary() - _ = yym16 - if false { + if yyq2[3] { + if x.Suspend == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(yy15)) + yy15 := *x.Suspend + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeBool(bool(yy15)) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("suspend")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Suspend == nil { - r.EncodeNil() - } else { - yy17 := *x.Suspend - yym18 := z.EncBinary() - _ = yym18 - if false { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("suspend")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Suspend == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(yy17)) + yy17 := *x.Suspend + yym18 := z.EncBinary() + _ = yym18 + if false { + } else { + r.EncodeBool(bool(yy17)) + } } } } diff --git a/pkg/apis/batch/v2alpha1/types.generated.go b/pkg/apis/batch/v2alpha1/types.generated.go index bd48550ca77..ff8495eb45c 100644 --- a/pkg/apis/batch/v2alpha1/types.generated.go +++ b/pkg/apis/batch/v2alpha1/types.generated.go @@ -3494,11 +3494,12 @@ func (x *ScheduledJobSpec) CodecEncodeSelf(e *codec1978.Encoder) { const yyr2 bool = false yyq2[1] = x.StartingDeadlineSeconds != nil yyq2[2] = x.ConcurrencyPolicy != "" + yyq2[3] = x.Suspend != nil var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(5) } else { - yynn2 = 3 + yynn2 = 2 for _, b := range yyq2 { if b { yynn2++ @@ -3578,30 +3579,36 @@ func (x *ScheduledJobSpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.Suspend == nil { - r.EncodeNil() - } else { - yy15 := *x.Suspend - yym16 := z.EncBinary() - _ = yym16 - if false { + if yyq2[3] { + if x.Suspend == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(yy15)) + yy15 := *x.Suspend + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeBool(bool(yy15)) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("suspend")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Suspend == nil { - r.EncodeNil() - } else { - yy17 := *x.Suspend - yym18 := z.EncBinary() - _ = yym18 - if false { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("suspend")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Suspend == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(yy17)) + yy17 := *x.Suspend + yym18 := z.EncBinary() + _ = yym18 + if false { + } else { + r.EncodeBool(bool(yy17)) + } } } } From f0b8edacccf60565cd77d4117aa074f77767bfbd Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Wed, 18 May 2016 15:25:05 +0200 Subject: [PATCH 3/5] ScheduledJob storage leftovers --- pkg/master/storage_batch.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/master/storage_batch.go b/pkg/master/storage_batch.go index b6e793296e0..09de5faf8eb 100644 --- a/pkg/master/storage_batch.go +++ b/pkg/master/storage_batch.go @@ -23,6 +23,7 @@ import ( batchapiv2alpha1 "k8s.io/kubernetes/pkg/apis/batch/v2alpha1" "k8s.io/kubernetes/pkg/genericapiserver" jobetcd "k8s.io/kubernetes/pkg/registry/job/etcd" + scheduledjobetcd "k8s.io/kubernetes/pkg/registry/scheduledjob/etcd" ) type BatchRESTStorageProvider struct{} @@ -65,5 +66,10 @@ func (p BatchRESTStorageProvider) v2alpha1Storage(apiResourceConfigSource generi storage["jobs"] = jobsStorage storage["jobs/status"] = jobsStatusStorage } + if apiResourceConfigSource.ResourceEnabled(version.WithResource("scheduledjobs")) { + scheduledJobsStorage, scheduledJobsStatusStorage := scheduledjobetcd.NewREST(restOptionsGetter(batch.Resource("scheduledjobs"))) + storage["scheduledjobs"] = scheduledJobsStorage + storage["scheduledjobs/status"] = scheduledJobsStatusStorage + } return storage } From e6c327048e5f4fa3fc78ea54dc4e3eb4c8d489f6 Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Mon, 23 May 2016 12:39:40 +0200 Subject: [PATCH 4/5] Forced using batch/v2alpha1 for storing ScheduledJob --- cmd/kube-apiserver/app/server.go | 2 ++ .../cmd/federation-apiserver/app/server.go | 2 +- .../default_storage_factory_builder.go | 20 +++++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 78d424fabab..d82dde1b850 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -143,6 +143,8 @@ func Run(s *options.APIServer) error { storageFactory, err := genericapiserver.BuildDefaultStorageFactory( s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, genericapiserver.NewDefaultResourceEncodingConfig(), storageGroupsToEncodingVersion, + // FIXME: this GroupVersionResource override should be configurable + []unversioned.GroupVersionResource{batch.Resource("scheduledjobs").WithVersion("v2alpha1")}, master.DefaultAPIResourceConfigSource(), s.RuntimeConfig) if err != nil { glog.Fatalf("error in initializing storage factory: %s", err) diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go index ef40f664a74..4384749e599 100644 --- a/federation/cmd/federation-apiserver/app/server.go +++ b/federation/cmd/federation-apiserver/app/server.go @@ -71,7 +71,7 @@ func Run(s *genericoptions.ServerRunOptions) error { storageFactory, err := genericapiserver.BuildDefaultStorageFactory( s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, genericapiserver.NewDefaultResourceEncodingConfig(), storageGroupsToEncodingVersion, - resourceConfig, s.RuntimeConfig) + []unversioned.GroupVersionResource{}, resourceConfig, s.RuntimeConfig) if err != nil { glog.Fatalf("error in initializing storage factory: %s", err) } diff --git a/pkg/genericapiserver/default_storage_factory_builder.go b/pkg/genericapiserver/default_storage_factory_builder.go index db9d07e9c7d..8219f1525e2 100644 --- a/pkg/genericapiserver/default_storage_factory_builder.go +++ b/pkg/genericapiserver/default_storage_factory_builder.go @@ -32,9 +32,11 @@ import ( // Merges defaultResourceConfig with the user specified overrides and merges // defaultAPIResourceConfig with the corresponding user specified overrides as well. func BuildDefaultStorageFactory(storageConfig storagebackend.Config, defaultMediaType string, serializer runtime.StorageSerializer, - defaultResourceEncoding *DefaultResourceEncodingConfig, storageEncodingOverrides map[string]unversioned.GroupVersion, defaultAPIResourceConfig *ResourceConfig, resourceConfigOverrides config.ConfigurationMap) (*DefaultStorageFactory, error) { + defaultResourceEncoding *DefaultResourceEncodingConfig, storageEncodingOverrides map[string]unversioned.GroupVersion, resourceEncodingOverrides []unversioned.GroupVersionResource, + defaultAPIResourceConfig *ResourceConfig, resourceConfigOverrides config.ConfigurationMap) (*DefaultStorageFactory, error) { - resourceEncodingConfig := mergeResourceEncodingConfigs(defaultResourceEncoding, storageEncodingOverrides) + resourceEncodingConfig := mergeGroupEncodingConfigs(defaultResourceEncoding, storageEncodingOverrides) + resourceEncodingConfig = mergeResourceEncodingConfigs(resourceEncodingConfig, resourceEncodingOverrides) apiResourceConfig, err := mergeAPIResourceConfigs(defaultAPIResourceConfig, resourceConfigOverrides) if err != nil { return nil, err @@ -42,8 +44,18 @@ func BuildDefaultStorageFactory(storageConfig storagebackend.Config, defaultMedi return NewDefaultStorageFactory(storageConfig, defaultMediaType, serializer, resourceEncodingConfig, apiResourceConfig), nil } -// Merges the given defaultAPIResourceConfig with the given storageEncodingOverrides. -func mergeResourceEncodingConfigs(defaultResourceEncoding *DefaultResourceEncodingConfig, storageEncodingOverrides map[string]unversioned.GroupVersion) *DefaultResourceEncodingConfig { +// Merges the given defaultResourceConfig with specifc GroupvVersionResource overrides. +func mergeResourceEncodingConfigs(defaultResourceEncoding *DefaultResourceEncodingConfig, resourceEncodingOverrides []unversioned.GroupVersionResource) *DefaultResourceEncodingConfig { + resourceEncodingConfig := defaultResourceEncoding + for _, gvr := range resourceEncodingOverrides { + resourceEncodingConfig.SetResourceEncoding(gvr.GroupResource(), gvr.GroupVersion(), + unversioned.GroupVersion{Group: gvr.Group, Version: runtime.APIVersionInternal}) + } + return resourceEncodingConfig +} + +// Merges the given defaultResourceConfig with specifc GroupVersion overrides. +func mergeGroupEncodingConfigs(defaultResourceEncoding *DefaultResourceEncodingConfig, storageEncodingOverrides map[string]unversioned.GroupVersion) *DefaultResourceEncodingConfig { resourceEncodingConfig := defaultResourceEncoding for group, storageEncodingVersion := range storageEncodingOverrides { resourceEncodingConfig.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) From b5c68a9015308a7c4dfac396cdbe64f058aa3f73 Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Wed, 18 May 2016 10:11:35 +0200 Subject: [PATCH 5/5] ScheduledJob kubectl changes --- pkg/kubectl/cmd/run.go | 10 +++- pkg/kubectl/cmd/util/factory.go | 12 ++-- pkg/kubectl/describe.go | 101 +++++++++++++++++++++++++++++++- pkg/kubectl/resource_printer.go | 39 ++++++++++++ pkg/kubectl/run.go | 99 +++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 9 deletions(-) diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index f534fa8ada3..0074d24807b 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -69,7 +69,10 @@ var ( kubectl run nginx --image=nginx --command -- ... # Start the perl container to compute π to 2000 places and print it out. - kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'`) + kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)' + + # Start the scheduled job to compute π to 2000 places and print it out every 5 minutes. + kubectl run pi --schedule="* 0/5 * * * ?" --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'`) ) func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { @@ -118,6 +121,7 @@ func addRunFlags(cmd *cobra.Command) { cmd.Flags().String("service-generator", "service/v2", "The name of the generator to use for creating a service. Only used if --expose is true") cmd.Flags().String("service-overrides", "", "An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true.") cmd.Flags().Bool("quiet", false, "If true, suppress prompt messages.") + cmd.Flags().String("schedule", "", "A schedule in the Cron format the job should be run with.") } func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string, argsLenAtDash int) error { @@ -154,6 +158,10 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob } generatorName := cmdutil.GetFlagString(cmd, "generator") + schedule := cmdutil.GetFlagString(cmd, "schedule") + if len(schedule) != 0 && len(generatorName) == 0 { + generatorName = "scheduledjob/v2alpha1" + } if len(generatorName) == 0 { client, err := f.Client() if err != nil { diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index cffaf44741f..b25dbaf125d 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -164,6 +164,7 @@ const ( DeploymentV1Beta1GeneratorName = "deployment/v1beta1" JobV1Beta1GeneratorName = "job/v1beta1" JobV1GeneratorName = "job/v1" + ScheduledJobV2Alpha1GeneratorName = "scheduledjob/v2alpha1" NamespaceV1GeneratorName = "namespace/v1" ResourceQuotaV1GeneratorName = "resourcequotas/v1" SecretV1GeneratorName = "secret/v1" @@ -180,11 +181,12 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator { ServiceV2GeneratorName: kubectl.ServiceGeneratorV2{}, } generators["run"] = map[string]kubectl.Generator{ - RunV1GeneratorName: kubectl.BasicReplicationController{}, - RunPodV1GeneratorName: kubectl.BasicPod{}, - DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, - JobV1Beta1GeneratorName: kubectl.JobV1Beta1{}, - JobV1GeneratorName: kubectl.JobV1{}, + RunV1GeneratorName: kubectl.BasicReplicationController{}, + RunPodV1GeneratorName: kubectl.BasicPod{}, + DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, + JobV1Beta1GeneratorName: kubectl.JobV1Beta1{}, + JobV1GeneratorName: kubectl.JobV1{}, + ScheduledJobV2Alpha1GeneratorName: kubectl.ScheduledJobV2Alpha1{}, } generators["autoscale"] = map[string]kubectl.Generator{ HorizontalPodAutoscalerV1Beta1GeneratorName: kubectl.HorizontalPodAutoscalerV1Beta1{}, diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index f67e949c8a5..96ec6868435 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -109,6 +109,7 @@ func describerMap(c *client.Client) map[unversioned.GroupKind]Describer { extensions.Kind("Deployment"): &DeploymentDescriber{adapter.FromUnversionedClient(c)}, extensions.Kind("Job"): &JobDescriber{c}, batch.Kind("Job"): &JobDescriber{c}, + batch.Kind("ScheduledJob"): &ScheduledJobDescriber{adapter.FromUnversionedClient(c)}, apps.Kind("PetSet"): &PetSetDescriber{c}, extensions.Kind("Ingress"): &IngressDescriber{c}, } @@ -1019,6 +1020,14 @@ func describeStatus(stateName string, state api.ContainerState, out io.Writer) { } } +func printBoolPtr(value *bool) string { + if value != nil { + return printBool(*value) + } + + return "" +} + func printBool(value bool) string { if value { return "True" @@ -1148,18 +1157,18 @@ func describeReplicaSet(rs *extensions.ReplicaSet, events *api.EventList, runnin // JobDescriber generates information about a job and the pods it has created. type JobDescriber struct { - client *client.Client + client.Interface } func (d *JobDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) { - job, err := d.client.Extensions().Jobs(namespace).Get(name) + job, err := d.Batch().Jobs(namespace).Get(name) if err != nil { return "", err } var events *api.EventList if describerSettings.ShowEvents { - events, _ = d.client.Events(namespace).Search(job) + events, _ = d.Events(namespace).Search(job) } return describeJob(job, events) @@ -1194,6 +1203,92 @@ func describeJob(job *batch.Job, events *api.EventList) (string, error) { }) } +// ScheduledJobDescriber generates information about a scheduled job and the jobs it has created. +type ScheduledJobDescriber struct { + clientset.Interface +} + +func (d *ScheduledJobDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) { + scheduledJob, err := d.Batch().ScheduledJobs(namespace).Get(name) + if err != nil { + return "", err + } + + var events *api.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheduledJob) + } + + return describeScheduledJob(scheduledJob, events) +} + +func describeScheduledJob(scheduledJob *batch.ScheduledJob, events *api.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + fmt.Fprintf(out, "Name:\t%s\n", scheduledJob.Name) + fmt.Fprintf(out, "Namespace:\t%s\n", scheduledJob.Namespace) + fmt.Fprintf(out, "Schedule:\t%s\n", scheduledJob.Spec.Schedule) + fmt.Fprintf(out, "Concurrency Policy:\t%s\n", scheduledJob.Spec.ConcurrencyPolicy) + fmt.Fprintf(out, "Suspend:\t%s\n", printBoolPtr(scheduledJob.Spec.Suspend)) + if scheduledJob.Spec.StartingDeadlineSeconds != nil { + fmt.Fprintf(out, "Starting Deadline Seconds:\t%ds\n", *scheduledJob.Spec.StartingDeadlineSeconds) + } else { + fmt.Fprintf(out, "Starting Deadline Seconds:\t\n") + } + describeJobTemplate(scheduledJob.Spec.JobTemplate, out) + printLabelsMultiline(out, "Labels", scheduledJob.Labels) + if scheduledJob.Status.LastScheduleTime != nil { + fmt.Fprintf(out, "Last Schedule Time:\t%s\n", scheduledJob.Status.LastScheduleTime.Time.Format(time.RFC1123Z)) + } else { + fmt.Fprintf(out, "Last Schedule Time:\t\n") + } + printActiveJobs(out, "Active Jobs", scheduledJob.Status.Active) + if events != nil { + DescribeEvents(events, out) + } + return nil + }) +} + +func describeJobTemplate(jobTemplate batch.JobTemplateSpec, out io.Writer) { + fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&jobTemplate.Spec.Template.Spec)) + if jobTemplate.Spec.Selector != nil { + selector, _ := unversioned.LabelSelectorAsSelector(jobTemplate.Spec.Selector) + fmt.Fprintf(out, "Selector:\t%s\n", selector) + } else { + fmt.Fprintf(out, "Selector:\t\n") + } + if jobTemplate.Spec.Parallelism != nil { + fmt.Fprintf(out, "Parallelism:\t%d\n", *jobTemplate.Spec.Parallelism) + } else { + fmt.Fprintf(out, "Parallelism:\t\n") + } + if jobTemplate.Spec.Completions != nil { + fmt.Fprintf(out, "Completions:\t%d\n", *jobTemplate.Spec.Completions) + } else { + fmt.Fprintf(out, "Completions:\t\n") + } + if jobTemplate.Spec.ActiveDeadlineSeconds != nil { + fmt.Fprintf(out, "Active Deadline Seconds:\t%ds\n", *jobTemplate.Spec.ActiveDeadlineSeconds) + } + describeVolumes(jobTemplate.Spec.Template.Spec.Volumes, out, "") +} + +func printActiveJobs(out io.Writer, title string, jobs []api.ObjectReference) { + fmt.Fprintf(out, "%s:\t", title) + if len(jobs) == 0 { + fmt.Fprintln(out, "") + return + } + + for i, job := range jobs { + if i != 0 { + fmt.Fprint(out, ", ") + } + fmt.Fprintf(out, "%s", job.Name) + } + fmt.Fprintln(out, "") +} + // DaemonSetDescriber generates information about a daemon set and the pods it has created. type DaemonSetDescriber struct { client.Interface diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 5e4fc1ef081..4631f1e43b1 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -414,6 +414,7 @@ var podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLA var replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"} var replicaSetColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"} var jobColumns = []string{"NAME", "DESIRED", "SUCCESSFUL", "AGE"} +var scheduledJobColumns = []string{"NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST-SCHEDULE"} var serviceColumns = []string{"NAME", "CLUSTER-IP", "EXTERNAL-IP", "PORT(S)", "AGE"} var ingressColumns = []string{"NAME", "HOSTS", "ADDRESS", "PORTS", "AGE"} var petSetColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"} @@ -459,6 +460,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(daemonSetColumns, printDaemonSetList) h.Handler(jobColumns, printJob) h.Handler(jobColumns, printJobList) + h.Handler(scheduledJobColumns, printScheduledJob) + h.Handler(scheduledJobColumns, printScheduledJobList) h.Handler(serviceColumns, printService) h.Handler(serviceColumns, printServiceList) h.Handler(ingressColumns, printIngress) @@ -970,6 +973,42 @@ func printJobList(list *batch.JobList, w io.Writer, options PrintOptions) error return nil } +func printScheduledJob(scheduledJob *batch.ScheduledJob, w io.Writer, options PrintOptions) error { + name := scheduledJob.Name + namespace := scheduledJob.Namespace + + if options.WithNamespace { + if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { + return err + } + } + + lastScheduleTime := "" + if scheduledJob.Status.LastScheduleTime != nil { + lastScheduleTime = scheduledJob.Status.LastScheduleTime.Time.Format(time.RFC1123Z) + } + if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\n", + name, + scheduledJob.Spec.Schedule, + printBoolPtr(scheduledJob.Spec.Suspend), + len(scheduledJob.Status.Active), + lastScheduleTime, + ); err != nil { + return err + } + + return nil +} + +func printScheduledJobList(list *batch.ScheduledJobList, w io.Writer, options PrintOptions) error { + for _, scheduledJob := range list.Items { + if err := printScheduledJob(&scheduledJob, w, options); err != nil { + return err + } + } + return nil +} + // loadBalancerStatusStringer behaves mostly like a string interface and converts the given status to a string. // `wide` indicates whether the returned value is meant for --o=wide output. If not, it's clipped to 16 bytes. func loadBalancerStatusStringer(s api.LoadBalancerStatus, wide bool) string { diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index 0b89b7814f6..76bafe203ce 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -27,6 +27,7 @@ import ( "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/batch" batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" + batchv2alpha1 "k8s.io/kubernetes/pkg/apis/batch/v2alpha1" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation" @@ -394,6 +395,104 @@ func (JobV1) Generate(genericParams map[string]interface{}) (runtime.Object, err return &job, nil } +type ScheduledJobV2Alpha1 struct{} + +func (ScheduledJobV2Alpha1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"labels", false}, + {"default-name", false}, + {"name", true}, + {"image", true}, + {"port", false}, + {"hostport", false}, + {"stdin", false}, + {"leave-stdin-open", false}, + {"tty", false}, + {"command", false}, + {"args", false}, + {"env", false}, + {"requests", false}, + {"limits", false}, + {"restart", false}, + {"schedule", true}, + } +} + +func (ScheduledJobV2Alpha1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { + args, err := getArgs(genericParams) + if err != nil { + return nil, err + } + + envs, err := getV1Envs(genericParams) + if err != nil { + return nil, err + } + + params, err := getParams(genericParams) + if err != nil { + return nil, err + } + + name, err := getName(params) + if err != nil { + return nil, err + } + + labels, err := getLabels(params, true, name) + if err != nil { + return nil, err + } + + podSpec, err := makeV1PodSpec(params, name) + if err != nil { + return nil, err + } + + if err = updateV1PodContainers(params, args, envs, podSpec); err != nil { + return nil, err + } + + leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false) + if err != nil { + return nil, err + } + podSpec.Containers[0].StdinOnce = !leaveStdinOpen && podSpec.Containers[0].Stdin + + if err := updateV1PodPorts(params, podSpec); err != nil { + return nil, err + } + + restartPolicy := v1.RestartPolicy(params["restart"]) + if len(restartPolicy) == 0 { + restartPolicy = v1.RestartPolicyNever + } + podSpec.RestartPolicy = restartPolicy + + scheduledJob := batchv2alpha1.ScheduledJob{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + Labels: labels, + }, + Spec: batchv2alpha1.ScheduledJobSpec{ + Schedule: params["schedule"], + ConcurrencyPolicy: batchv2alpha1.AllowConcurrent, + JobTemplate: batchv2alpha1.JobTemplateSpec{ + Spec: batchv2alpha1.JobSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: labels, + }, + Spec: *podSpec, + }, + }, + }, + }, + } + + return &scheduledJob, nil +} + type BasicReplicationController struct{} func (BasicReplicationController) ParamNames() []GeneratorParam {