From 0b74c7c9fc2b8c69465d6942a45fab86cba1c283 Mon Sep 17 00:00:00 2001 From: zzx Date: Fri, 24 May 2019 10:14:23 +0800 Subject: [PATCH 1/7] modify shortname of namespace and name we use shortname "-n" for the namespace, and "-N" for the name --- pkg/cli/job/list.go | 2 +- pkg/cli/job/resume.go | 4 ++-- pkg/cli/job/run.go | 4 ++-- pkg/cli/job/suspend.go | 4 ++-- pkg/cli/job/view.go | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/cli/job/list.go b/pkg/cli/job/list.go index 41dff254b6..0a3a7e767e 100644 --- a/pkg/cli/job/list.go +++ b/pkg/cli/job/list.go @@ -74,7 +74,7 @@ var listJobFlags = &listFlags{} func InitListFlags(cmd *cobra.Command) { initFlags(cmd, &listJobFlags.commonFlags) - cmd.Flags().StringVarP(&listJobFlags.Namespace, "namespace", "N", "default", "the namespace of job") + cmd.Flags().StringVarP(&listJobFlags.Namespace, "namespace", "n", "default", "the namespace of job") cmd.Flags().StringVarP(&listJobFlags.SchedulerName, "scheduler", "S", "", "list job with specified scheduler name") } diff --git a/pkg/cli/job/resume.go b/pkg/cli/job/resume.go index 54f6a7853a..da573ee1b9 100644 --- a/pkg/cli/job/resume.go +++ b/pkg/cli/job/resume.go @@ -37,8 +37,8 @@ var resumeJobFlags = &resumeFlags{} func InitResumeFlags(cmd *cobra.Command) { initFlags(cmd, &resumeJobFlags.commonFlags) - cmd.Flags().StringVarP(&resumeJobFlags.Namespace, "namespace", "N", "default", "the namespace of job") - cmd.Flags().StringVarP(&resumeJobFlags.JobName, "name", "n", "", "the name of job") + cmd.Flags().StringVarP(&resumeJobFlags.Namespace, "namespace", "n", "default", "the namespace of job") + cmd.Flags().StringVarP(&resumeJobFlags.JobName, "name", "N", "", "the name of job") } // ResumeJob resumes the job diff --git a/pkg/cli/job/run.go b/pkg/cli/job/run.go index 1270aa7bff..094b677dcb 100644 --- a/pkg/cli/job/run.go +++ b/pkg/cli/job/run.go @@ -47,8 +47,8 @@ func InitRunFlags(cmd *cobra.Command) { initFlags(cmd, &launchJobFlags.commonFlags) cmd.Flags().StringVarP(&launchJobFlags.Image, "image", "i", "busybox", "the container image of job") - cmd.Flags().StringVarP(&launchJobFlags.Namespace, "namespace", "N", "default", "the namespace of job") - cmd.Flags().StringVarP(&launchJobFlags.Name, "name", "n", "test", "the name of job") + cmd.Flags().StringVarP(&launchJobFlags.Namespace, "namespace", "n", "default", "the namespace of job") + cmd.Flags().StringVarP(&launchJobFlags.Name, "name", "N", "test", "the name of job") cmd.Flags().IntVarP(&launchJobFlags.MinAvailable, "min", "m", 1, "the minimal available tasks of job") cmd.Flags().IntVarP(&launchJobFlags.Replicas, "replicas", "r", 1, "the total tasks of job") cmd.Flags().StringVarP(&launchJobFlags.Requests, "requests", "R", "cpu=1000m,memory=100Mi", "the resource request of the task") diff --git a/pkg/cli/job/suspend.go b/pkg/cli/job/suspend.go index 3f6ddcc9b2..f5d2b18cd3 100644 --- a/pkg/cli/job/suspend.go +++ b/pkg/cli/job/suspend.go @@ -36,8 +36,8 @@ var suspendJobFlags = &suspendFlags{} func InitSuspendFlags(cmd *cobra.Command) { initFlags(cmd, &suspendJobFlags.commonFlags) - cmd.Flags().StringVarP(&suspendJobFlags.Namespace, "namespace", "N", "default", "the namespace of job") - cmd.Flags().StringVarP(&suspendJobFlags.JobName, "name", "n", "", "the name of job") + cmd.Flags().StringVarP(&suspendJobFlags.Namespace, "namespace", "n", "default", "the namespace of job") + cmd.Flags().StringVarP(&suspendJobFlags.JobName, "name", "N", "", "the name of job") } // SuspendJob suspends the job diff --git a/pkg/cli/job/view.go b/pkg/cli/job/view.go index 68125b18bf..6e4cf572b0 100644 --- a/pkg/cli/job/view.go +++ b/pkg/cli/job/view.go @@ -43,8 +43,8 @@ var viewJobFlags = &viewFlags{} func InitViewFlags(cmd *cobra.Command) { initFlags(cmd, &viewJobFlags.commonFlags) - cmd.Flags().StringVarP(&viewJobFlags.Namespace, "namespace", "N", "default", "the namespace of job") - cmd.Flags().StringVarP(&viewJobFlags.JobName, "name", "n", "", "the name of job") + cmd.Flags().StringVarP(&viewJobFlags.Namespace, "namespace", "n", "default", "the namespace of job") + cmd.Flags().StringVarP(&viewJobFlags.JobName, "name", "N", "", "the name of job") } // ViewJob gives full details of the job From 21b517abb9704f89916ecb9c92328bc3956cdc65 Mon Sep 17 00:00:00 2001 From: zzx Date: Fri, 24 May 2019 14:54:42 +0800 Subject: [PATCH 2/7] fix the job created by 'job run' unable to display by 'job list' problem --- pkg/cli/job/run.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/job/run.go b/pkg/cli/job/run.go index 094b677dcb..b472d0b808 100644 --- a/pkg/cli/job/run.go +++ b/pkg/cli/job/run.go @@ -53,7 +53,7 @@ func InitRunFlags(cmd *cobra.Command) { cmd.Flags().IntVarP(&launchJobFlags.Replicas, "replicas", "r", 1, "the total tasks of job") cmd.Flags().StringVarP(&launchJobFlags.Requests, "requests", "R", "cpu=1000m,memory=100Mi", "the resource request of the task") cmd.Flags().StringVarP(&launchJobFlags.Limits, "limits", "L", "cpu=1000m,memory=100Mi", "the resource limit of the task") - cmd.Flags().StringVarP(&listJobFlags.SchedulerName, "scheduler", "S", "kube-batch", "the scheduler for this job") + cmd.Flags().StringVarP(&launchJobFlags.SchedulerName, "scheduler", "S", "kube-batch", "the scheduler for this job") } var jobName = "job.volcano.sh" @@ -82,6 +82,7 @@ func RunJob() error { }, Spec: vkapi.JobSpec{ MinAvailable: int32(launchJobFlags.MinAvailable), + SchedulerName: launchJobFlags.SchedulerName, Tasks: []vkapi.TaskSpec{ { Replicas: int32(launchJobFlags.Replicas), @@ -92,7 +93,6 @@ func RunJob() error { Labels: map[string]string{jobName: launchJobFlags.Name}, }, Spec: v1.PodSpec{ - SchedulerName: launchJobFlags.SchedulerName, RestartPolicy: v1.RestartPolicyNever, Containers: []v1.Container{ { From 946365ce820f1702d316babe9d677d7d10e686c2 Mon Sep 17 00:00:00 2001 From: zzx Date: Fri, 24 May 2019 17:08:27 +0800 Subject: [PATCH 3/7] add "\n" when command execute failed --- cmd/cli/vkctl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cli/vkctl.go b/cmd/cli/vkctl.go index 5e5511d3f2..2860894dba 100644 --- a/cmd/cli/vkctl.go +++ b/cmd/cli/vkctl.go @@ -45,7 +45,7 @@ func main() { rootCmd.AddCommand(versionCommand()) if err := rootCmd.Execute(); err != nil { - fmt.Printf("Failed to execute command: %v", err) + fmt.Printf("Failed to execute command: %v\n", err) } } From 26104243f56a1bc6344e8fc9c3cd2da70f3eef2d Mon Sep 17 00:00:00 2001 From: "zhangcheng95@huawei.com" Date: Wed, 29 May 2019 18:44:32 +0800 Subject: [PATCH 4/7] vkctl job view --- pkg/cli/job/list.go | 13 ++- pkg/cli/job/util.go | 52 ++++++++++++ pkg/cli/job/view.go | 198 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 235 insertions(+), 28 deletions(-) diff --git a/pkg/cli/job/list.go b/pkg/cli/job/list.go index 0a3a7e767e..7a89d6d357 100644 --- a/pkg/cli/job/list.go +++ b/pkg/cli/job/list.go @@ -20,7 +20,7 @@ import ( "fmt" "io" "os" - + "strings" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -34,6 +34,8 @@ type listFlags struct { Namespace string SchedulerName string + allNamespace bool + selector string } const ( @@ -76,6 +78,8 @@ func InitListFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&listJobFlags.Namespace, "namespace", "n", "default", "the namespace of job") cmd.Flags().StringVarP(&listJobFlags.SchedulerName, "scheduler", "S", "", "list job with specified scheduler name") + cmd.Flags().BoolVarP(&listJobFlags.allNamespace, "all-namespaces", "", false, "list jobs in all namespaces") + cmd.Flags().StringVarP(&listJobFlags.selector, "selector", "", "", "fuzzy matching jobName") } // ListJobs lists all jobs details @@ -84,7 +88,9 @@ func ListJobs() error { if err != nil { return err } - + if listJobFlags.allNamespace { + listJobFlags.Namespace = "" + } jobClient := versioned.NewForConfigOrDie(config) jobs, err := jobClient.BatchV1alpha1().Jobs(listJobFlags.Namespace).List(metav1.ListOptions{}) if err != nil { @@ -113,6 +119,9 @@ func PrintJobs(jobs *v1alpha1.JobList, writer io.Writer) { if listJobFlags.SchedulerName != "" && listJobFlags.SchedulerName != job.Spec.SchedulerName { continue } + if !strings.Contains(job.Name, listJobFlags.selector) { + continue + } replicas := int32(0) for _, ts := range job.Spec.Tasks { replicas += ts.Replicas diff --git a/pkg/cli/job/util.go b/pkg/cli/job/util.go index 8a5db6a313..2654bcd208 100644 --- a/pkg/cli/job/util.go +++ b/pkg/cli/job/util.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "strings" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -97,3 +98,54 @@ func createJobCommand(config *rest.Config, ns, name string, action vkbatchv1.Act return nil } + + +func translateTimestampSince(timestamp metav1.Time) string { + if timestamp.IsZero() { + return "" + } + return HumanDuration(time.Since(timestamp.Time)) +} + +func HumanDuration(d time.Duration) string { + // Allow deviation no more than 2 seconds(excluded) to tolerate machine time + // inconsistence, it can be considered as almost now. + if seconds := int(d.Seconds()); seconds < -1 { + return fmt.Sprintf("") + } else if seconds < 0 { + return fmt.Sprintf("0s") + } else if seconds < 60*2 { + return fmt.Sprintf("%ds", seconds) + } + minutes := int(d / time.Minute) + if minutes < 10 { + s := int(d/time.Second) % 60 + if s == 0 { + return fmt.Sprintf("%dm", minutes) + } + return fmt.Sprintf("%dm%ds", minutes, s) + } else if minutes < 60*3 { + return fmt.Sprintf("%dm", minutes) + } + hours := int(d / time.Hour) + if hours < 8 { + m := int(d/time.Minute) % 60 + if m == 0 { + return fmt.Sprintf("%dh", hours) + } + return fmt.Sprintf("%dh%dm", hours, m) + } else if hours < 48 { + return fmt.Sprintf("%dh", hours) + } else if hours < 24*8 { + h := hours % 24 + if h == 0 { + return fmt.Sprintf("%dd", hours/24) + } + return fmt.Sprintf("%dd%dh", hours/24, h) + } else if hours < 24*365*2 { + return fmt.Sprintf("%dd", hours/24) + } else if hours < 24*365*8 { + return fmt.Sprintf("%dy%dd", hours/24/365, (hours/24)%365) + } + return fmt.Sprintf("%dy", int(hours/24/365)) +} diff --git a/pkg/cli/job/view.go b/pkg/cli/job/view.go index 6e4cf572b0..d131d8f914 100644 --- a/pkg/cli/job/view.go +++ b/pkg/cli/job/view.go @@ -21,10 +21,14 @@ import ( "io" "os" "strings" + "encoding/json" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/kubernetes" + coreV1 "k8s.io/api/core/v1" "volcano.sh/volcano/pkg/apis/batch/v1alpha1" "volcano.sh/volcano/pkg/client/clientset/versioned" @@ -37,6 +41,12 @@ type viewFlags struct { JobName string } +const ( + LEVEL_0 = iota + LEVEL_1 + LEVEL_2 +) + var viewJobFlags = &viewFlags{} // InitViewFlags init the view command flags @@ -67,35 +77,171 @@ func ViewJob() error { fmt.Printf("No resources found\n") return nil } - PrintJob(job, os.Stdout) - + PrintJobInfo(job, os.Stdout) + PrintEvents(GetEvents(config, job), os.Stdout) return nil } -// PrintJob prints the job details -func PrintJob(job *v1alpha1.Job, writer io.Writer) { - replicas := int32(0) - for _, ts := range job.Spec.Tasks { - replicas += ts.Replicas - } - lines := []string{ - fmt.Sprintf("%s:\t\t%s", Name, job.Name), - fmt.Sprintf("%s:\t%s", Creation, job.CreationTimestamp.Format("2006-01-02 15:04:05")), - fmt.Sprintf("%s:\t%d", Replicas, replicas), - fmt.Sprintf("%s:\t\t%d", Min, job.Status.MinAvailable), - fmt.Sprintf("%s:\t%s", Scheduler, job.Spec.SchedulerName), - "Status", - fmt.Sprintf(" %s:\t%s", Phase, job.Status.State.Phase), - fmt.Sprintf(" %s:\t%d", Version, job.Status.Version), - fmt.Sprintf(" %s:\t%d", RetryCount, job.Status.RetryCount), - fmt.Sprintf(" %s:\t%d", Pending, job.Status.Pending), - fmt.Sprintf(" %s:\t%d", Running, job.Status.Running), - fmt.Sprintf(" %s:\t%d", Succeeded, job.Status.Succeeded), - fmt.Sprintf(" %s:\t%d", Failed, job.Status.Failed), - fmt.Sprintf(" %s:\t%d", Terminating, job.Status.Terminating), - } - _, err := fmt.Fprint(writer, strings.Join(lines, "\n"), "\n") +func PrintJobInfo(job *v1alpha1.Job, writer io.Writer) { + WriteLine(writer, LEVEL_0, "Name: \t%s\n", job.Name) + WriteLine(writer, LEVEL_0, "Namespace: \t%s\n", job.Namespace) + if len(job.Labels) > 0 { + label, _ := json.Marshal(job.Labels) + WriteLine(writer, LEVEL_0, "Labels: \t%s\n", string(label)) + } else { + WriteLine(writer, LEVEL_0, "Labels: \t\n") + } + if len(job.Annotations) > 0 { + annotation, _ := json.Marshal(job.Annotations) + WriteLine(writer, LEVEL_0, "Annotations:\t%s\n", string(annotation)) + } else { + WriteLine(writer, LEVEL_0, "Annotations:\t\n") + } + WriteLine(writer, LEVEL_0, "API Version:\t%s\n", job.APIVersion) + WriteLine(writer, LEVEL_0, "Kind: \t%s\n", job.Kind) + + WriteLine(writer, LEVEL_0, "Metadata:\n") + WriteLine(writer, LEVEL_1, "Creation Timestamp:\t%s\n", job.CreationTimestamp) + WriteLine(writer, LEVEL_1, "Generate Name: \t%s\n", job.GenerateName) + WriteLine(writer, LEVEL_1, "Generation: \t%d\n", job.Generation) + WriteLine(writer, LEVEL_1, "Resource Version: \t%s\n", job.ResourceVersion) + WriteLine(writer, LEVEL_1, "Self Link: \t%s\n", job.SelfLink) + WriteLine(writer, LEVEL_1, "UID: \t%s\n", job.UID) + + WriteLine(writer, LEVEL_0, "Spec:\n") + WriteLine(writer, LEVEL_1, "Min Available: \t%d\n", job.Spec.MinAvailable) + WriteLine(writer, LEVEL_1, "Plugins:\n") + WriteLine(writer, LEVEL_2, "Env:\t%v\n", job.Spec.Plugins["env"]) + WriteLine(writer, LEVEL_2, "Ssh:\t%v\n", job.Spec.Plugins["ssh"]) + WriteLine(writer, LEVEL_1, "Scheduler Name: \t%s\n", job.Spec.SchedulerName) + WriteLine(writer, LEVEL_1, "Tasks:\n") + for i := 0; i 0 { + WriteLine(writer, LEVEL_1, "Succeeded: \t%d\n", job.Status.Succeeded) + } + if job.Status.Pending > 0 { + WriteLine(writer, LEVEL_1, "Pending: \t%d\n", job.Status.Pending) + } + if job.Status.Running > 0 { + WriteLine(writer, LEVEL_1, "Running: \t%d\n", job.Status.Running) + } + if job.Status.Failed > 0 { + WriteLine(writer, LEVEL_1, "Failed: \t%d\n", job.Status.Failed) + } + if job.Status.Terminating > 0 { + WriteLine(writer, LEVEL_1, "Terminating: \t%d\n", job.Status.Terminating) + } + if job.Status.RetryCount > 0 { + WriteLine(writer, LEVEL_1, "RetryCount: \t%d\n", job.Status.RetryCount) + } + if job.Status.MinAvailable > 0 { + WriteLine(writer, LEVEL_1, "Min Available:\t%d\n", job.Status.MinAvailable) + } + if job.Status.Version > 0 { + WriteLine(writer, LEVEL_1, "Version: \t%d\n", job.Status.Version) + } + + WriteLine(writer, LEVEL_1, "State:\n") + WriteLine(writer, LEVEL_2, "Phase:\t%s\n", job.Status.State.Phase) + if len(job.Status.ControlledResources) > 0 { + WriteLine(writer, LEVEL_1, "Controlled Resources:\n") + for key, value := range job.Status.ControlledResources { + WriteLine(writer, LEVEL_2, "%s: \t%s\n", key, value) + } + } +} + +func PrintEvents(events []coreV1.Event, writer io.Writer) { + if len(events) > 0 { + WriteLine(writer, LEVEL_0, "%s:\n%-15s\t%-40s\t%-30s\t%-40s\t%s\n", "Events", "Type", "Reason", "Age", "Form", "Message") + WriteLine(writer, LEVEL_0, "%-15s\t%-40s\t%-30s\t%-40s\t%s\n", "-------", "-------", "-------", "-------", "-------") + for _, e := range events { + var interval string + if e.Count > 1 { + interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, translateTimestampSince(e.FirstTimestamp)) + } else { + interval = translateTimestampSince(e.FirstTimestamp) + } + EventSourceString := []string{e.Source.Component} + if len(e.Source.Host) > 0 { + EventSourceString = append(EventSourceString, e.Source.Host) + } + WriteLine(writer,LEVEL_0, "%-15v\t%-40v\t%-30s\t%-40s\t%v\n", + e.Type, + e.Reason, + interval, + strings.Join(EventSourceString, ", "), + strings.TrimSpace(e.Message), + ) + } + } else { + WriteLine(writer, LEVEL_0, "Events: \t\n") + } + +} + +func GetEvents(config *rest.Config, job *v1alpha1.Job) []coreV1.Event { + kubernetes, err := kubernetes.NewForConfig(config) if err != nil { - fmt.Printf("Failed to print view command result: %s.\n", err) + fmt.Printf("%v\n",err) + return nil } + events, _ := kubernetes.CoreV1().Events(viewJobFlags.Namespace).List(metav1.ListOptions{}) + var jobEvents []coreV1.Event + for _, v := range events.Items { + if strings.HasPrefix(v.ObjectMeta.Name,job.Name) { + jobEvents = append(jobEvents, v) + } + } + return jobEvents } + +func WriteLine(writer io.Writer, spaces int, content string, params ...interface{}) { + prefix := "" + for i := 0; i < spaces; i++ { + prefix += " " + } + fmt.Fprintf(writer, prefix + content, params...) +} + From 173889b9231096dad351f5ad9d90f7d45ba449c6 Mon Sep 17 00:00:00 2001 From: "zhangcheng95@huawei.com" Date: Fri, 31 May 2019 10:54:02 +0800 Subject: [PATCH 5/7] HasPrefix(eventName, jobName + '.') --- pkg/cli/job/view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/job/view.go b/pkg/cli/job/view.go index d131d8f914..2e7a8033c3 100644 --- a/pkg/cli/job/view.go +++ b/pkg/cli/job/view.go @@ -230,7 +230,7 @@ func GetEvents(config *rest.Config, job *v1alpha1.Job) []coreV1.Event { events, _ := kubernetes.CoreV1().Events(viewJobFlags.Namespace).List(metav1.ListOptions{}) var jobEvents []coreV1.Event for _, v := range events.Items { - if strings.HasPrefix(v.ObjectMeta.Name,job.Name) { + if strings.HasPrefix(v.ObjectMeta.Name, job.Name + ".") { jobEvents = append(jobEvents, v) } } From 4c674f9418473912e1bcb8dac9e735441ed5a919 Mon Sep 17 00:00:00 2001 From: wangyuqing4 Date: Thu, 13 Jun 2019 15:02:46 +0800 Subject: [PATCH 6/7] command support vkctl job run -f filename.yaml --- cmd/cli/vkctl.go | 1 + pkg/cli/job/delete.go | 4 +-- pkg/cli/job/list.go | 5 +-- pkg/cli/job/run.go | 61 ++++++++++++++++++++++++++++----- pkg/cli/job/util.go | 1 - pkg/cli/job/view.go | 79 +++++++++++++++++++++---------------------- 6 files changed, 97 insertions(+), 54 deletions(-) diff --git a/cmd/cli/vkctl.go b/cmd/cli/vkctl.go index 2860894dba..965220bb6d 100644 --- a/cmd/cli/vkctl.go +++ b/cmd/cli/vkctl.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/util/wait" + "volcano.sh/volcano/pkg/version" ) diff --git a/pkg/cli/job/delete.go b/pkg/cli/job/delete.go index 6662a2c0fa..7b6eb3be38 100644 --- a/pkg/cli/job/delete.go +++ b/pkg/cli/job/delete.go @@ -39,8 +39,8 @@ var deleteJobFlags = &deleteFlags{} func InitDeleteFlags(cmd *cobra.Command) { initFlags(cmd, &deleteJobFlags.commonFlags) - cmd.Flags().StringVarP(&deleteJobFlags.Namespace, "namespace", "N", "default", "the namespace of job") - cmd.Flags().StringVarP(&deleteJobFlags.JobName, "name", "n", "", "the name of job") + cmd.Flags().StringVarP(&deleteJobFlags.Namespace, "namespace", "n", "default", "the namespace of job") + cmd.Flags().StringVarP(&deleteJobFlags.JobName, "name", "N", "", "the name of job") } // DeleteJob delete the job diff --git a/pkg/cli/job/list.go b/pkg/cli/job/list.go index 7a89d6d357..3ef742f1e9 100644 --- a/pkg/cli/job/list.go +++ b/pkg/cli/job/list.go @@ -21,6 +21,7 @@ import ( "io" "os" "strings" + "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -34,8 +35,8 @@ type listFlags struct { Namespace string SchedulerName string - allNamespace bool - selector string + allNamespace bool + selector string } const ( diff --git a/pkg/cli/job/run.go b/pkg/cli/job/run.go index b472d0b808..d3f9a57094 100644 --- a/pkg/cli/job/run.go +++ b/pkg/cli/job/run.go @@ -17,10 +17,15 @@ limitations under the License. package job import ( + "fmt" + "io/ioutil" + "strings" + "github.com/spf13/cobra" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" vkapi "volcano.sh/volcano/pkg/apis/batch/v1alpha1" "volcano.sh/volcano/pkg/client/clientset/versioned" @@ -38,6 +43,7 @@ type runFlags struct { Requests string Limits string SchedulerName string + FileName string } var launchJobFlags = &runFlags{} @@ -54,6 +60,7 @@ func InitRunFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&launchJobFlags.Requests, "requests", "R", "cpu=1000m,memory=100Mi", "the resource request of the task") cmd.Flags().StringVarP(&launchJobFlags.Limits, "limits", "L", "cpu=1000m,memory=100Mi", "the resource limit of the task") cmd.Flags().StringVarP(&launchJobFlags.SchedulerName, "scheduler", "S", "kube-batch", "the scheduler for this job") + cmd.Flags().StringVarP(&launchJobFlags.FileName, "filename", "f", "", "the yaml file of job") } var jobName = "job.volcano.sh" @@ -75,13 +82,56 @@ func RunJob() error { return err } - job := &vkapi.Job{ + job, err := readFile(launchJobFlags.FileName) + if err != nil { + return err + } + + if job == nil { + job = constructLaunchJobFlagsJob(launchJobFlags, req, limit) + } + + jobClient := versioned.NewForConfigOrDie(config) + newJob, err := jobClient.BatchV1alpha1().Jobs(launchJobFlags.Namespace).Create(job) + if err != nil { + return err + } + + fmt.Printf("run job %v successfully\n", newJob.Name) + + return nil +} + +func readFile(filename string) (*vkapi.Job, error) { + if filename == "" { + return nil, nil + } + + if !strings.Contains(filename, ".yaml") && !strings.Contains(filename, ".yml") { + return nil, fmt.Errorf("Only support yaml file.") + } + + file, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("Failed to read file, err: %v", err) + } + + var job vkapi.Job + if err := yaml.Unmarshal(file, &job); err != nil { + return nil, fmt.Errorf("Failed to unmarshal file, err: %v", err) + } + + return &job, nil +} + +func constructLaunchJobFlagsJob(launchJobFlags *runFlags, req, limit v1.ResourceList) *vkapi.Job { + return &vkapi.Job{ ObjectMeta: metav1.ObjectMeta{ Name: launchJobFlags.Name, Namespace: launchJobFlags.Namespace, }, Spec: vkapi.JobSpec{ - MinAvailable: int32(launchJobFlags.MinAvailable), + MinAvailable: int32(launchJobFlags.MinAvailable), SchedulerName: launchJobFlags.SchedulerName, Tasks: []vkapi.TaskSpec{ { @@ -111,11 +161,4 @@ func RunJob() error { }, }, } - - jobClient := versioned.NewForConfigOrDie(config) - if _, err := jobClient.BatchV1alpha1().Jobs(launchJobFlags.Namespace).Create(job); err != nil { - return err - } - - return nil } diff --git a/pkg/cli/job/util.go b/pkg/cli/job/util.go index 2654bcd208..de9542e17c 100644 --- a/pkg/cli/job/util.go +++ b/pkg/cli/job/util.go @@ -99,7 +99,6 @@ func createJobCommand(config *rest.Config, ns, name string, action vkbatchv1.Act return nil } - func translateTimestampSince(timestamp metav1.Time) string { if timestamp.IsZero() { return "" diff --git a/pkg/cli/job/view.go b/pkg/cli/job/view.go index 2e7a8033c3..75f2a1bc0c 100644 --- a/pkg/cli/job/view.go +++ b/pkg/cli/job/view.go @@ -17,18 +17,18 @@ limitations under the License. package job import ( + "encoding/json" "fmt" "io" "os" "strings" - "encoding/json" "github.com/spf13/cobra" + coreV1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/rest" "k8s.io/client-go/kubernetes" - coreV1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" "volcano.sh/volcano/pkg/apis/batch/v1alpha1" "volcano.sh/volcano/pkg/client/clientset/versioned" @@ -64,7 +64,7 @@ func ViewJob() error { return err } if viewJobFlags.JobName == "" { - err := fmt.Errorf("job name (specified by --name or -n) is mandatory to view a particular job") + err := fmt.Errorf("job name (specified by --name or -N) is mandaorty to view a particular job") return err } @@ -115,45 +115,45 @@ func PrintJobInfo(job *v1alpha1.Job, writer io.Writer) { WriteLine(writer, LEVEL_2, "Ssh:\t%v\n", job.Spec.Plugins["ssh"]) WriteLine(writer, LEVEL_1, "Scheduler Name: \t%s\n", job.Spec.SchedulerName) WriteLine(writer, LEVEL_1, "Tasks:\n") - for i := 0; i 0 { WriteLine(writer, LEVEL_1, "Controlled Resources:\n") - for key, value := range job.Status.ControlledResources { + for key, value := range job.Status.ControlledResources { WriteLine(writer, LEVEL_2, "%s: \t%s\n", key, value) } } @@ -207,7 +207,7 @@ func PrintEvents(events []coreV1.Event, writer io.Writer) { if len(e.Source.Host) > 0 { EventSourceString = append(EventSourceString, e.Source.Host) } - WriteLine(writer,LEVEL_0, "%-15v\t%-40v\t%-30s\t%-40s\t%v\n", + WriteLine(writer, LEVEL_0, "%-15v\t%-40v\t%-30s\t%-40s\t%v\n", e.Type, e.Reason, interval, @@ -224,13 +224,13 @@ func PrintEvents(events []coreV1.Event, writer io.Writer) { func GetEvents(config *rest.Config, job *v1alpha1.Job) []coreV1.Event { kubernetes, err := kubernetes.NewForConfig(config) if err != nil { - fmt.Printf("%v\n",err) + fmt.Printf("%v\n", err) return nil } events, _ := kubernetes.CoreV1().Events(viewJobFlags.Namespace).List(metav1.ListOptions{}) - var jobEvents []coreV1.Event + var jobEvents []coreV1.Event for _, v := range events.Items { - if strings.HasPrefix(v.ObjectMeta.Name, job.Name + ".") { + if strings.HasPrefix(v.ObjectMeta.Name, job.Name+".") { jobEvents = append(jobEvents, v) } } @@ -242,6 +242,5 @@ func WriteLine(writer io.Writer, spaces int, content string, params ...interface for i := 0; i < spaces; i++ { prefix += " " } - fmt.Fprintf(writer, prefix + content, params...) + fmt.Fprintf(writer, prefix+content, params...) } - From e93d10551003a1634c61d8f25e65e4f40650221d Mon Sep 17 00:00:00 2001 From: lminzhw Date: Fri, 28 Jun 2019 21:29:39 +0800 Subject: [PATCH 7/7] fix golint --- pkg/cli/job/list_test.go | 29 ++++++-- pkg/cli/job/run.go | 4 +- pkg/cli/job/run_test.go | 30 +++++++- pkg/cli/job/util.go | 1 + pkg/cli/job/util_test.go | 109 +++++++++++++++++++++++++++ pkg/cli/job/view.go | 155 ++++++++++++++++++++------------------- pkg/cli/job/view_test.go | 92 ++++++++++++++++++++++- 7 files changed, 331 insertions(+), 89 deletions(-) create mode 100644 pkg/cli/job/util_test.go diff --git a/pkg/cli/job/list_test.go b/pkg/cli/job/list_test.go index 64845c70da..21b9f037a4 100644 --- a/pkg/cli/job/list_test.go +++ b/pkg/cli/job/list_test.go @@ -42,20 +42,33 @@ func TestListJob(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - listJobFlags.Master = server.URL - listJobFlags.Namespace = "test" - testCases := []struct { - Name string - ExpectValue error + Name string + ExpectValue error + AllNamespace bool + Selector string }{ { Name: "ListJob", ExpectValue: nil, }, + { + Name: "ListAllNamespaceJob", + ExpectValue: nil, + AllNamespace: true, + }, } for i, testcase := range testCases { + listJobFlags = &listFlags{ + commonFlags: commonFlags{ + Master: server.URL, + }, + Namespace: "test", + allNamespace: testcase.AllNamespace, + selector: testcase.Selector, + } + err := ListJobs() if err != nil { t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, err) @@ -74,5 +87,11 @@ func TestInitListFlags(t *testing.T) { if cmd.Flag("scheduler") == nil { t.Errorf("Could not find the flag scheduler") } + if cmd.Flag("all-namespaces") == nil { + t.Errorf("Could not find the flag all-namespaces") + } + if cmd.Flag("selector") == nil { + t.Errorf("Could not find the flag selector") + } } diff --git a/pkg/cli/job/run.go b/pkg/cli/job/run.go index d3f9a57094..c171e15747 100644 --- a/pkg/cli/job/run.go +++ b/pkg/cli/job/run.go @@ -108,12 +108,12 @@ func readFile(filename string) (*vkapi.Job, error) { } if !strings.Contains(filename, ".yaml") && !strings.Contains(filename, ".yml") { - return nil, fmt.Errorf("Only support yaml file.") + return nil, fmt.Errorf("only support yaml file") } file, err := ioutil.ReadFile(filename) if err != nil { - return nil, fmt.Errorf("Failed to read file, err: %v", err) + return nil, fmt.Errorf("failed to read file, err: %v", err) } var job vkapi.Job diff --git a/pkg/cli/job/run_test.go b/pkg/cli/job/run_test.go index 26d6285939..8f743ca7b2 100644 --- a/pkg/cli/job/run_test.go +++ b/pkg/cli/job/run_test.go @@ -19,9 +19,12 @@ package job import ( "encoding/json" "github.com/spf13/cobra" + "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" + "time" v1alpha1 "volcano.sh/volcano/pkg/apis/batch/v1alpha1" ) @@ -41,21 +44,42 @@ func TestCreateJob(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - launchJobFlags.Master = server.URL - launchJobFlags.Namespace = "test" - launchJobFlags.Requests = "cpu=1000m,memory=100Mi" + fileName := time.Now().String() + "testCreateJob.yaml" + val, err := json.Marshal(response) + if err != nil { + panic(err) + } + err = ioutil.WriteFile(fileName, val, os.ModePerm) + if err != nil { + panic(err) + } + defer os.Remove(fileName) testCases := []struct { Name string ExpectValue error + FileName string }{ { Name: "CreateJob", ExpectValue: nil, }, + { + Name: "CreateJobWithFile", + FileName: fileName, + ExpectValue: nil, + }, } for i, testcase := range testCases { + launchJobFlags = &runFlags{ + commonFlags: commonFlags{ + Master: server.URL, + }, + Namespace: "test", + Requests: "cpu=1000m,memory=100Mi", + } + err := RunJob() if err != nil { t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, err) diff --git a/pkg/cli/job/util.go b/pkg/cli/job/util.go index de9542e17c..db5ee6290e 100644 --- a/pkg/cli/job/util.go +++ b/pkg/cli/job/util.go @@ -106,6 +106,7 @@ func translateTimestampSince(timestamp metav1.Time) string { return HumanDuration(time.Since(timestamp.Time)) } +// HumanDuration translate time.Duration to human readable time string func HumanDuration(d time.Duration) string { // Allow deviation no more than 2 seconds(excluded) to tolerate machine time // inconsistence, it can be considered as almost now. diff --git a/pkg/cli/job/util_test.go b/pkg/cli/job/util_test.go new file mode 100644 index 0000000000..737f56659e --- /dev/null +++ b/pkg/cli/job/util_test.go @@ -0,0 +1,109 @@ +/* +Copyright 2019 The Volcano Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package job + +import ( + "testing" + + "time" +) + +func TestJobUtil(t *testing.T) { + testCases := []struct { + Name string + Duration time.Duration + ExpectValue string + }{ + { + Name: "InvalidTime", + Duration: -time.Minute, + ExpectValue: "", + }, + { + Name: "SmallInvalieTime", + Duration: -time.Millisecond, + ExpectValue: "0s", + }, + { + Name: "NormalSeconds", + Duration: 62 * time.Second, + ExpectValue: "62s", + }, + { + Name: "NormalMinutes", + Duration: 180 * time.Second, + ExpectValue: "3m", + }, + { + Name: "NormalMinutesWithSecond", + Duration: 190 * time.Second, + ExpectValue: "3m10s", + }, + { + Name: "BiggerMinutesWithoutSecond", + Duration: 121*time.Minute + 56*time.Second, + ExpectValue: "121m", + }, + { + Name: "NormalHours", + Duration: 5*time.Hour + 9*time.Second, + ExpectValue: "5h", + }, + { + Name: "NormalHoursWithMinute", + Duration: 5*time.Hour + 7*time.Minute + 9*time.Second, + ExpectValue: "5h7m", + }, + { + Name: "BiggerHoursWithoutMinute", + Duration: 12*time.Hour + 7*time.Minute + 9*time.Second, + ExpectValue: "12h", + }, + { + Name: "NormalDays", + Duration: 5*24*time.Hour + 7*time.Minute + 9*time.Second, + ExpectValue: "5d", + }, + { + Name: "NormalDaysWithHours", + Duration: 5*24*time.Hour + 9*time.Hour, + ExpectValue: "5d9h", + }, + { + Name: "BiggerDayWithoutHours", + Duration: 531*24*time.Hour + 7*time.Minute + 9*time.Second, + ExpectValue: "531d", + }, + { + Name: "NormalYears", + Duration: (365*5+89)*24*time.Hour + 7*time.Minute + 9*time.Second, + ExpectValue: "5y89d", + }, + { + Name: "BiggerYears", + Duration: (365*12+15)*24*time.Hour + 7*time.Minute + 9*time.Second, + ExpectValue: "12y", + }, + } + + for i, testcase := range testCases { + answer := HumanDuration(testcase.Duration) + if answer != testcase.ExpectValue { + t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, answer) + } + } +} diff --git a/pkg/cli/job/view.go b/pkg/cli/job/view.go index 75f2a1bc0c..d8130f3ea2 100644 --- a/pkg/cli/job/view.go +++ b/pkg/cli/job/view.go @@ -41,10 +41,11 @@ type viewFlags struct { JobName string } +// level of print indent const ( - LEVEL_0 = iota - LEVEL_1 - LEVEL_2 + Level0 = iota + Level1 + Level2 ) var viewJobFlags = &viewFlags{} @@ -82,120 +83,122 @@ func ViewJob() error { return nil } +// PrintJobInfo print the job detailed info into writer func PrintJobInfo(job *v1alpha1.Job, writer io.Writer) { - WriteLine(writer, LEVEL_0, "Name: \t%s\n", job.Name) - WriteLine(writer, LEVEL_0, "Namespace: \t%s\n", job.Namespace) + WriteLine(writer, Level0, "Name: \t%s\n", job.Name) + WriteLine(writer, Level0, "Namespace: \t%s\n", job.Namespace) if len(job.Labels) > 0 { label, _ := json.Marshal(job.Labels) - WriteLine(writer, LEVEL_0, "Labels: \t%s\n", string(label)) + WriteLine(writer, Level0, "Labels: \t%s\n", string(label)) } else { - WriteLine(writer, LEVEL_0, "Labels: \t\n") + WriteLine(writer, Level0, "Labels: \t\n") } if len(job.Annotations) > 0 { annotation, _ := json.Marshal(job.Annotations) - WriteLine(writer, LEVEL_0, "Annotations:\t%s\n", string(annotation)) + WriteLine(writer, Level0, "Annotations:\t%s\n", string(annotation)) } else { - WriteLine(writer, LEVEL_0, "Annotations:\t\n") - } - WriteLine(writer, LEVEL_0, "API Version:\t%s\n", job.APIVersion) - WriteLine(writer, LEVEL_0, "Kind: \t%s\n", job.Kind) - - WriteLine(writer, LEVEL_0, "Metadata:\n") - WriteLine(writer, LEVEL_1, "Creation Timestamp:\t%s\n", job.CreationTimestamp) - WriteLine(writer, LEVEL_1, "Generate Name: \t%s\n", job.GenerateName) - WriteLine(writer, LEVEL_1, "Generation: \t%d\n", job.Generation) - WriteLine(writer, LEVEL_1, "Resource Version: \t%s\n", job.ResourceVersion) - WriteLine(writer, LEVEL_1, "Self Link: \t%s\n", job.SelfLink) - WriteLine(writer, LEVEL_1, "UID: \t%s\n", job.UID) - - WriteLine(writer, LEVEL_0, "Spec:\n") - WriteLine(writer, LEVEL_1, "Min Available: \t%d\n", job.Spec.MinAvailable) - WriteLine(writer, LEVEL_1, "Plugins:\n") - WriteLine(writer, LEVEL_2, "Env:\t%v\n", job.Spec.Plugins["env"]) - WriteLine(writer, LEVEL_2, "Ssh:\t%v\n", job.Spec.Plugins["ssh"]) - WriteLine(writer, LEVEL_1, "Scheduler Name: \t%s\n", job.Spec.SchedulerName) - WriteLine(writer, LEVEL_1, "Tasks:\n") + WriteLine(writer, Level0, "Annotations:\t\n") + } + WriteLine(writer, Level0, "API Version:\t%s\n", job.APIVersion) + WriteLine(writer, Level0, "Kind: \t%s\n", job.Kind) + + WriteLine(writer, Level0, "Metadata:\n") + WriteLine(writer, Level1, "Creation Timestamp:\t%s\n", job.CreationTimestamp) + WriteLine(writer, Level1, "Generate Name: \t%s\n", job.GenerateName) + WriteLine(writer, Level1, "Generation: \t%d\n", job.Generation) + WriteLine(writer, Level1, "Resource Version: \t%s\n", job.ResourceVersion) + WriteLine(writer, Level1, "Self Link: \t%s\n", job.SelfLink) + WriteLine(writer, Level1, "UID: \t%s\n", job.UID) + + WriteLine(writer, Level0, "Spec:\n") + WriteLine(writer, Level1, "Min Available: \t%d\n", job.Spec.MinAvailable) + WriteLine(writer, Level1, "Plugins:\n") + WriteLine(writer, Level2, "Env:\t%v\n", job.Spec.Plugins["env"]) + WriteLine(writer, Level2, "Ssh:\t%v\n", job.Spec.Plugins["ssh"]) + WriteLine(writer, Level1, "Scheduler Name: \t%s\n", job.Spec.SchedulerName) + WriteLine(writer, Level1, "Tasks:\n") for i := 0; i < len(job.Spec.Tasks); i++ { - WriteLine(writer, LEVEL_2, "Name:\t%s\n", job.Spec.Tasks[i].Name) - WriteLine(writer, LEVEL_2, "Replicas:\t%d\n", job.Spec.Tasks[i].Replicas) - WriteLine(writer, LEVEL_2, "Template:\n") - WriteLine(writer, LEVEL_2+1, "Metadata:\n") - WriteLine(writer, LEVEL_2+2, "Annotations:\n") - WriteLine(writer, LEVEL_2+3, "Cri . Cci . Io / Container - Type: \t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.Annotations["cri.cci.io/container-type"]) - WriteLine(writer, LEVEL_2+3, "Kubernetes . Io / Availablezone: \t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.Annotations["kubernetes.io/availablezone"]) - WriteLine(writer, LEVEL_2+3, "Network . Alpha . Kubernetes . Io / Network:\t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.Annotations["network.alpha.kubernetes.io/network"]) - WriteLine(writer, LEVEL_2+2, "Creation Timestamp:\t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.CreationTimestamp) - - WriteLine(writer, LEVEL_2+1, "Spec:\n") - WriteLine(writer, LEVEL_2+2, "Containers:\n") + WriteLine(writer, Level2, "Name:\t%s\n", job.Spec.Tasks[i].Name) + WriteLine(writer, Level2, "Replicas:\t%d\n", job.Spec.Tasks[i].Replicas) + WriteLine(writer, Level2, "Template:\n") + WriteLine(writer, Level2+1, "Metadata:\n") + WriteLine(writer, Level2+2, "Annotations:\n") + WriteLine(writer, Level2+3, "Cri . Cci . Io / Container - Type: \t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.Annotations["cri.cci.io/container-type"]) + WriteLine(writer, Level2+3, "Kubernetes . Io / Availablezone: \t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.Annotations["kubernetes.io/availablezone"]) + WriteLine(writer, Level2+3, "Network . Alpha . Kubernetes . Io / Network:\t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.Annotations["network.alpha.kubernetes.io/network"]) + WriteLine(writer, Level2+2, "Creation Timestamp:\t%s\n", job.Spec.Tasks[i].Template.ObjectMeta.CreationTimestamp) + + WriteLine(writer, Level2+1, "Spec:\n") + WriteLine(writer, Level2+2, "Containers:\n") for j := 0; j < len(job.Spec.Tasks[i].Template.Spec.Containers); j++ { - WriteLine(writer, LEVEL_2+3, "Command:\n") + WriteLine(writer, Level2+3, "Command:\n") for k := 0; k < len(job.Spec.Tasks[i].Template.Spec.Containers[j].Command); k++ { - WriteLine(writer, LEVEL_2+4, "%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Command[k]) + WriteLine(writer, Level2+4, "%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Command[k]) } - WriteLine(writer, LEVEL_2+3, "Image:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Image) - WriteLine(writer, LEVEL_2+3, "Name: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Name) - WriteLine(writer, LEVEL_2+3, "Ports:\n") + WriteLine(writer, Level2+3, "Image:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Image) + WriteLine(writer, Level2+3, "Name: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Name) + WriteLine(writer, Level2+3, "Ports:\n") for k := 0; k < len(job.Spec.Tasks[i].Template.Spec.Containers[j].Ports); k++ { - WriteLine(writer, LEVEL_2+4, "Container Port:\t%d\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Ports[k].ContainerPort) - WriteLine(writer, LEVEL_2+4, "Name: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Ports[k].Name) + WriteLine(writer, Level2+4, "Container Port:\t%d\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Ports[k].ContainerPort) + WriteLine(writer, Level2+4, "Name: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Ports[k].Name) } - WriteLine(writer, LEVEL_2+3, "Resources:\n") - WriteLine(writer, LEVEL_2+4, "Limits:\n") - WriteLine(writer, LEVEL_2+5, "Cpu: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Limits.Cpu()) - WriteLine(writer, LEVEL_2+5, "Memory:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Limits.Memory()) - WriteLine(writer, LEVEL_2+4, "Requests:\n") - WriteLine(writer, LEVEL_2+5, "Cpu: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Requests.Cpu()) - WriteLine(writer, LEVEL_2+5, "Memory:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Requests.Memory()) - WriteLine(writer, LEVEL_2+4, "Working Dir:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].WorkingDir) + WriteLine(writer, Level2+3, "Resources:\n") + WriteLine(writer, Level2+4, "Limits:\n") + WriteLine(writer, Level2+5, "Cpu: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Limits.Cpu()) + WriteLine(writer, Level2+5, "Memory:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Limits.Memory()) + WriteLine(writer, Level2+4, "Requests:\n") + WriteLine(writer, Level2+5, "Cpu: \t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Requests.Cpu()) + WriteLine(writer, Level2+5, "Memory:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].Resources.Requests.Memory()) + WriteLine(writer, Level2+4, "Working Dir:\t%s\n", job.Spec.Tasks[i].Template.Spec.Containers[j].WorkingDir) } - WriteLine(writer, LEVEL_2+2, "Image Pull Secrets:\n") + WriteLine(writer, Level2+2, "Image Pull Secrets:\n") for j := 0; j < len(job.Spec.Tasks[i].Template.Spec.ImagePullSecrets); j++ { - WriteLine(writer, LEVEL_2+3, "Name: \t%s\n", job.Spec.Tasks[i].Template.Spec.ImagePullSecrets[j].Name) + WriteLine(writer, Level2+3, "Name: \t%s\n", job.Spec.Tasks[i].Template.Spec.ImagePullSecrets[j].Name) } - WriteLine(writer, LEVEL_2+2, "Restart Policy: \t%s\n", job.Spec.Tasks[i].Template.Spec.RestartPolicy) + WriteLine(writer, Level2+2, "Restart Policy: \t%s\n", job.Spec.Tasks[i].Template.Spec.RestartPolicy) } - WriteLine(writer, LEVEL_0, "Status:\n") + WriteLine(writer, Level0, "Status:\n") if job.Status.Succeeded > 0 { - WriteLine(writer, LEVEL_1, "Succeeded: \t%d\n", job.Status.Succeeded) + WriteLine(writer, Level1, "Succeeded: \t%d\n", job.Status.Succeeded) } if job.Status.Pending > 0 { - WriteLine(writer, LEVEL_1, "Pending: \t%d\n", job.Status.Pending) + WriteLine(writer, Level1, "Pending: \t%d\n", job.Status.Pending) } if job.Status.Running > 0 { - WriteLine(writer, LEVEL_1, "Running: \t%d\n", job.Status.Running) + WriteLine(writer, Level1, "Running: \t%d\n", job.Status.Running) } if job.Status.Failed > 0 { - WriteLine(writer, LEVEL_1, "Failed: \t%d\n", job.Status.Failed) + WriteLine(writer, Level1, "Failed: \t%d\n", job.Status.Failed) } if job.Status.Terminating > 0 { - WriteLine(writer, LEVEL_1, "Terminating: \t%d\n", job.Status.Terminating) + WriteLine(writer, Level1, "Terminating: \t%d\n", job.Status.Terminating) } if job.Status.RetryCount > 0 { - WriteLine(writer, LEVEL_1, "RetryCount: \t%d\n", job.Status.RetryCount) + WriteLine(writer, Level1, "RetryCount: \t%d\n", job.Status.RetryCount) } if job.Status.MinAvailable > 0 { - WriteLine(writer, LEVEL_1, "Min Available:\t%d\n", job.Status.MinAvailable) + WriteLine(writer, Level1, "Min Available:\t%d\n", job.Status.MinAvailable) } if job.Status.Version > 0 { - WriteLine(writer, LEVEL_1, "Version: \t%d\n", job.Status.Version) + WriteLine(writer, Level1, "Version: \t%d\n", job.Status.Version) } - WriteLine(writer, LEVEL_1, "State:\n") - WriteLine(writer, LEVEL_2, "Phase:\t%s\n", job.Status.State.Phase) + WriteLine(writer, Level1, "State:\n") + WriteLine(writer, Level2, "Phase:\t%s\n", job.Status.State.Phase) if len(job.Status.ControlledResources) > 0 { - WriteLine(writer, LEVEL_1, "Controlled Resources:\n") + WriteLine(writer, Level1, "Controlled Resources:\n") for key, value := range job.Status.ControlledResources { - WriteLine(writer, LEVEL_2, "%s: \t%s\n", key, value) + WriteLine(writer, Level2, "%s: \t%s\n", key, value) } } } +// PrintEvents print event info to writer func PrintEvents(events []coreV1.Event, writer io.Writer) { if len(events) > 0 { - WriteLine(writer, LEVEL_0, "%s:\n%-15s\t%-40s\t%-30s\t%-40s\t%s\n", "Events", "Type", "Reason", "Age", "Form", "Message") - WriteLine(writer, LEVEL_0, "%-15s\t%-40s\t%-30s\t%-40s\t%s\n", "-------", "-------", "-------", "-------", "-------") + WriteLine(writer, Level0, "%s:\n%-15s\t%-40s\t%-30s\t%-40s\t%s\n", "Events", "Type", "Reason", "Age", "Form", "Message") + WriteLine(writer, Level0, "%-15s\t%-40s\t%-30s\t%-40s\t%s\n", "-------", "-------", "-------", "-------", "-------") for _, e := range events { var interval string if e.Count > 1 { @@ -207,7 +210,7 @@ func PrintEvents(events []coreV1.Event, writer io.Writer) { if len(e.Source.Host) > 0 { EventSourceString = append(EventSourceString, e.Source.Host) } - WriteLine(writer, LEVEL_0, "%-15v\t%-40v\t%-30s\t%-40s\t%v\n", + WriteLine(writer, Level0, "%-15v\t%-40v\t%-30s\t%-40s\t%v\n", e.Type, e.Reason, interval, @@ -216,11 +219,12 @@ func PrintEvents(events []coreV1.Event, writer io.Writer) { ) } } else { - WriteLine(writer, LEVEL_0, "Events: \t\n") + WriteLine(writer, Level0, "Events: \t\n") } } +// GetEvents get the job event by config func GetEvents(config *rest.Config, job *v1alpha1.Job) []coreV1.Event { kubernetes, err := kubernetes.NewForConfig(config) if err != nil { @@ -237,6 +241,7 @@ func GetEvents(config *rest.Config, job *v1alpha1.Job) []coreV1.Event { return jobEvents } +// WriteLine write lines with specified indent func WriteLine(writer io.Writer, spaces int, content string, params ...interface{}) { prefix := "" for i := 0; i < spaces; i++ { diff --git a/pkg/cli/job/view_test.go b/pkg/cli/job/view_test.go index 564da3f148..f199ec1001 100644 --- a/pkg/cli/job/view_test.go +++ b/pkg/cli/job/view_test.go @@ -18,24 +18,108 @@ package job import ( "encoding/json" - "github.com/spf13/cobra" + "math" "net/http" "net/http/httptest" + "strings" "testing" + "github.com/spf13/cobra" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1alpha1 "volcano.sh/volcano/pkg/apis/batch/v1alpha1" ) func TestViewJob(t *testing.T) { - response := v1alpha1.Job{} - response.Name = "testJob" + response := v1alpha1.Job{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "testJobWithLongLongLongName", + Labels: map[string]string{ + "LabelWithLongLongLongLongName": "LongLongLongLongLabelValue", + }, + Annotations: map[string]string{ + "AnnotationWithLongLongLongLongName": "LongLongLongLongAnnotationValue", + }, + }, + Spec: v1alpha1.JobSpec{ + Tasks: []v1alpha1.TaskSpec{ + { + Name: "taskWithLongLongLongLongName", + Replicas: math.MaxInt32, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Command: []string{"echo", "123"}, + Ports: []v1.ContainerPort{ + { + Name: "placeholder", + }, + }, + }, + }, + ImagePullSecrets: []v1.LocalObjectReference{ + { + Name: "imagepull-secret", + }, + }, + }, + }, + }, + }, + }, + Status: v1alpha1.JobStatus{ + Succeeded: 1, + Pending: 3, + Running: 1, + Failed: 2, + Terminating: 4, + RetryCount: 5, + MinAvailable: 6, + Version: 7, + ControlledResources: map[string]string{ + "svc": "", + }, + }, + } + + eventList := v1.EventList{ + Items: []v1.Event{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: response.Name + ".123", + }, + Count: 1, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: response.Name + ".456", + }, + Count: 2, + FirstTimestamp: metav1.Now(), + }, + }, + } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - val, err := json.Marshal(response) + + if strings.Contains(r.URL.String(), "job") { + val, err := json.Marshal(response) + if err == nil { + w.Write(val) + } + return + } + + val, err := json.Marshal(eventList) if err == nil { w.Write(val) } + return })