Skip to content

Commit 788bd93

Browse files
committed
vcctl add command for job template
Signed-off-by: HaoJie Liu <[email protected]>
1 parent c238353 commit 788bd93

File tree

9 files changed

+975
-0
lines changed

9 files changed

+975
-0
lines changed

cmd/cli/jobtemplate.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package main
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"volcano.sh/volcano/pkg/cli/job"
6+
)
7+
8+
func buildJobTemplateCmd() *cobra.Command {
9+
jobTemplateCmd := &cobra.Command{
10+
Use: "template",
11+
Short: "vcctl command line operation job template",
12+
}
13+
14+
jobTemplateRunCmd := &cobra.Command{
15+
Use: "run",
16+
Short: "run job by parameters from the command line",
17+
Run: func(cmd *cobra.Command, args []string) {
18+
checkError(cmd, job.RunJobTemplate())
19+
},
20+
}
21+
job.InitTemplateRunFlags(jobTemplateRunCmd)
22+
jobTemplateCmd.AddCommand(jobTemplateRunCmd)
23+
24+
jobTemplateGenerateCmd := &cobra.Command{
25+
Use: "generate",
26+
Short: "generate jobTemplate by parameters from the command line",
27+
Run: func(cmd *cobra.Command, args []string) {
28+
checkError(cmd, job.GenerateJobTemplate())
29+
},
30+
}
31+
job.InitTemplateGenerateFlags(jobTemplateGenerateCmd)
32+
jobTemplateCmd.AddCommand(jobTemplateGenerateCmd)
33+
34+
return jobTemplateCmd
35+
}

cmd/cli/vcctl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func main() {
4848
rootCmd.CompletionOptions.DisableDefaultCmd = true
4949

5050
rootCmd.AddCommand(buildJobCmd())
51+
rootCmd.AddCommand(buildJobTemplateCmd())
5152
rootCmd.AddCommand(buildQueueCmd())
5253
rootCmd.AddCommand(versionCommand())
5354

pkg/cli/job/template_generate.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package job
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/spf13/cobra"
7+
"io/ioutil"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/apimachinery/pkg/runtime/schema"
12+
"k8s.io/client-go/dynamic"
13+
"sigs.k8s.io/yaml"
14+
"strings"
15+
vcbatch "volcano.sh/apis/pkg/apis/batch/v1alpha1"
16+
"volcano.sh/apis/pkg/client/clientset/versioned"
17+
"volcano.sh/volcano/pkg/cli/util"
18+
)
19+
20+
type generateTemplateFlags struct {
21+
commonFlags
22+
FileName string
23+
JobName string
24+
Namespace string
25+
GenerateName string
26+
}
27+
28+
var generateJobTemplateFlags = &generateTemplateFlags{}
29+
30+
// InitTemplateGenerateFlags init the generate flags.
31+
func InitTemplateGenerateFlags(cmd *cobra.Command) {
32+
initFlags(cmd, &generateJobTemplateFlags.commonFlags)
33+
34+
cmd.Flags().StringVarP(&generateJobTemplateFlags.FileName, "filename", "f", "", "the yaml file of job")
35+
cmd.Flags().StringVarP(&generateJobTemplateFlags.Namespace, "namespace", "n", "default", "the namespace of job")
36+
cmd.Flags().StringVarP(&generateJobTemplateFlags.JobName, "name", "N", "", "the name of job")
37+
cmd.Flags().StringVarP(&generateJobTemplateFlags.GenerateName, "generateName", "g", "", "the name for new job template")
38+
}
39+
40+
func GenerateJobTemplate() error {
41+
config, err := util.BuildConfig(generateJobTemplateFlags.Master, generateJobTemplateFlags.Kubeconfig)
42+
if err != nil {
43+
return err
44+
}
45+
46+
if generateJobTemplateFlags.JobName == "" && generateJobTemplateFlags.FileName == "" {
47+
err = fmt.Errorf("the filename and job name cannot both be left blank")
48+
return err
49+
}
50+
51+
var job *vcbatch.Job
52+
if generateJobTemplateFlags.FileName != "" {
53+
job, err = readJobFile(generateJobTemplateFlags.FileName)
54+
if err != nil {
55+
return err
56+
}
57+
}
58+
59+
if job == nil {
60+
jobClient := versioned.NewForConfigOrDie(config)
61+
job, err = jobClient.BatchV1alpha1().Jobs(generateJobTemplateFlags.Namespace).Get(context.TODO(), generateJobTemplateFlags.JobName, metav1.GetOptions{})
62+
if err != nil {
63+
return fmt.Errorf("fail to get job <%s/%s>", generateJobTemplateFlags.Namespace, generateJobTemplateFlags.JobName)
64+
}
65+
}
66+
67+
unstructuredContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(job)
68+
if err != nil {
69+
return fmt.Errorf("fail to convert job <%s/%s> for:%v", job.Namespace, job.Name, err)
70+
}
71+
jobTemplate := &unstructured.Unstructured{Object: unstructuredContent}
72+
if generateJobTemplateFlags.GenerateName != "" {
73+
jobTemplate.SetName(generateJobTemplateFlags.GenerateName)
74+
}
75+
jobTemplate.SetKind("JobTemplate")
76+
if jobTemplate.GetNamespace() == "" {
77+
jobTemplate.SetNamespace("default")
78+
}
79+
jobTemplate.SetAPIVersion("batch.volcano.sh/v1alpha1")
80+
jobTemplate.SetManagedFields(nil)
81+
jobTemplate.SetAnnotations(nil)
82+
jobTemplate.SetGeneration(1)
83+
jobTemplate.SetResourceVersion("")
84+
jobTemplate.SetUID("")
85+
myDynamicClient, err := dynamic.NewForConfig(config)
86+
if err != nil {
87+
return err
88+
}
89+
myDynamicResourceClient := myDynamicClient.
90+
Resource(schema.GroupVersionResource{Group: "batch.volcano.sh", Version: "v1alpha1", Resource: "jobtemplates"}).
91+
Namespace(jobTemplate.GetNamespace())
92+
createdTemplate, err := myDynamicResourceClient.Create(context.TODO(), jobTemplate, metav1.CreateOptions{})
93+
if err != nil {
94+
return fmt.Errorf("fail to create jobTemplate <%s/%s> for:%v", jobTemplate.GetNamespace(), jobTemplate.GetName(), err)
95+
}
96+
fmt.Printf("%s/%s created\n", createdTemplate.GetKind(), createdTemplate.GetName())
97+
98+
return nil
99+
}
100+
101+
func readJobFile(filename string) (*vcbatch.Job, error) {
102+
103+
if !strings.Contains(filename, ".yaml") && !strings.Contains(filename, ".yml") {
104+
return nil, fmt.Errorf("only support yaml file")
105+
}
106+
107+
file, err := ioutil.ReadFile(filename)
108+
if err != nil {
109+
return nil, fmt.Errorf("failed to read file, err: %v", err)
110+
}
111+
112+
var job vcbatch.Job
113+
if err := yaml.Unmarshal(file, &job); err != nil {
114+
return nil, fmt.Errorf("failed to unmarshal file, err: %v", err)
115+
}
116+
117+
return &job, nil
118+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package job
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"net/http"
7+
"net/http/httptest"
8+
"os"
9+
"strings"
10+
"testing"
11+
12+
"github.com/spf13/cobra"
13+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14+
15+
"volcano.sh/apis/pkg/apis/batch/v1alpha1"
16+
)
17+
18+
func TestGenerateJobTemplate(t *testing.T) {
19+
response := v1alpha1.Job{}
20+
jobTemplate := &unstructured.Unstructured{
21+
Object: map[string]interface{}{
22+
"kind": "JobTemplate",
23+
"apiVersion": "batch.volcano.sh/v1alpha1",
24+
},
25+
}
26+
27+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28+
w.Header().Set("Content-Type", "application/json")
29+
30+
if strings.Contains(r.URL.String(), "jobtemplates") {
31+
val, err := json.Marshal(jobTemplate)
32+
if err == nil {
33+
w.Write(val)
34+
}
35+
return
36+
}
37+
val, err := json.Marshal(response)
38+
if err == nil {
39+
w.Write(val)
40+
}
41+
})
42+
43+
server := httptest.NewServer(handler)
44+
defer server.Close()
45+
46+
fileName := "testJob.yaml"
47+
val, err := json.Marshal(response)
48+
if err != nil {
49+
panic(err)
50+
}
51+
err = ioutil.WriteFile(fileName, val, os.ModePerm)
52+
if err != nil {
53+
panic(err)
54+
}
55+
defer os.Remove(fileName)
56+
57+
testCases := []struct {
58+
Name string
59+
ExpectValue error
60+
FileName string
61+
}{
62+
{
63+
Name: "GenerateTemplate",
64+
ExpectValue: nil,
65+
},
66+
{
67+
Name: "GenerateTemplateWithFile",
68+
FileName: fileName,
69+
ExpectValue: nil,
70+
},
71+
}
72+
73+
for i, testcase := range testCases {
74+
generateJobTemplateFlags = &generateTemplateFlags{
75+
commonFlags: commonFlags{
76+
Master: server.URL,
77+
},
78+
JobName: "test",
79+
Namespace: "test",
80+
}
81+
if testcase.FileName != "" {
82+
generateJobTemplateFlags.FileName = testcase.FileName
83+
}
84+
85+
err := GenerateJobTemplate()
86+
if err != nil {
87+
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, err)
88+
}
89+
}
90+
91+
}
92+
93+
func TestInitTemplateGenerateFlags(t *testing.T) {
94+
var cmd cobra.Command
95+
InitTemplateGenerateFlags(&cmd)
96+
97+
if cmd.Flag("namespace") == nil {
98+
t.Errorf("Could not find the flag namespace")
99+
}
100+
}

pkg/cli/job/template_run.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package job
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/spf13/cobra"
7+
"io/ioutil"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
"k8s.io/apimachinery/pkg/runtime/schema"
11+
"k8s.io/client-go/dynamic"
12+
"sigs.k8s.io/yaml"
13+
"strings"
14+
vcbatch "volcano.sh/apis/pkg/apis/batch/v1alpha1"
15+
"volcano.sh/apis/pkg/client/clientset/versioned"
16+
"volcano.sh/volcano/pkg/cli/util"
17+
)
18+
19+
type runTemplateFlags struct {
20+
commonFlags
21+
FileName string
22+
TemplateName string
23+
TemplateNamespace string
24+
GenerateName string
25+
}
26+
27+
const createSourceKey = "volcano.sh/createByJobTemplate"
28+
29+
var launchJobTemplateFlags = &runTemplateFlags{}
30+
31+
// InitTemplateRunFlags init the run flags.
32+
func InitTemplateRunFlags(cmd *cobra.Command) {
33+
initFlags(cmd, &launchJobTemplateFlags.commonFlags)
34+
35+
cmd.Flags().StringVarP(&launchJobTemplateFlags.FileName, "filename", "f", "", "the yaml file of jobTemplate")
36+
cmd.Flags().StringVarP(&launchJobTemplateFlags.TemplateNamespace, "namespace", "n", "default", "the namespace of job template")
37+
cmd.Flags().StringVarP(&launchJobTemplateFlags.TemplateName, "name", "N", "", "the name of job template")
38+
cmd.Flags().StringVarP(&launchJobTemplateFlags.GenerateName, "generateName", "g", "", "the name for new job")
39+
}
40+
41+
func RunJobTemplate() error {
42+
config, err := util.BuildConfig(launchJobTemplateFlags.Master, launchJobTemplateFlags.Kubeconfig)
43+
if err != nil {
44+
return err
45+
}
46+
jobTemplate, err := readTemplateFile(launchJobTemplateFlags.FileName)
47+
if err != nil {
48+
return err
49+
}
50+
if jobTemplate == nil {
51+
if launchJobTemplateFlags.TemplateName == "" {
52+
return fmt.Errorf("the filename and template name cannot both be empty")
53+
}
54+
myDynamicClient, err := dynamic.NewForConfig(config)
55+
if err != nil {
56+
return err
57+
}
58+
myDynamicResourceClient := myDynamicClient.
59+
Resource(schema.GroupVersionResource{Group: "batch.volcano.sh", Version: "v1alpha1", Resource: "jobtemplates"}).
60+
Namespace(launchJobTemplateFlags.TemplateNamespace)
61+
jobTemplate, err = myDynamicResourceClient.Get(context.TODO(), launchJobTemplateFlags.TemplateName, metav1.GetOptions{})
62+
if err != nil {
63+
return fmt.Errorf("fail to get unstructed jobTemplate for: %v", err)
64+
}
65+
}
66+
jobTemplate.SetKind("Job")
67+
jobTemplate.SetManagedFields(nil)
68+
jobTemplate.SetGeneration(1)
69+
jobTemplate.SetResourceVersion("")
70+
jobTemplate.SetUID("")
71+
if jobTemplate.GetNamespace() == "" {
72+
jobTemplate.SetNamespace("default")
73+
}
74+
annotations := jobTemplate.GetAnnotations()
75+
if annotations == nil {
76+
annotations = make(map[string]string)
77+
}
78+
annotations[createSourceKey] = fmt.Sprintf("%s.%s", jobTemplate.GetNamespace(), jobTemplate.GetName())
79+
jobTemplate.SetAnnotations(annotations)
80+
var job vcbatch.Job
81+
result, err := yaml.Marshal(jobTemplate.Object)
82+
if err != nil {
83+
return err
84+
}
85+
if err := yaml.Unmarshal(result, &job); err != nil {
86+
return err
87+
}
88+
if launchJobTemplateFlags.GenerateName != "" {
89+
job.Name = launchJobTemplateFlags.GenerateName
90+
}
91+
jobClient := versioned.NewForConfigOrDie(config)
92+
newJob, err := jobClient.BatchV1alpha1().Jobs(launchJobTemplateFlags.TemplateNamespace).Create(context.TODO(), &job, metav1.CreateOptions{})
93+
if err != nil {
94+
return err
95+
}
96+
if newJob.Spec.Queue == "" {
97+
newJob.Spec.Queue = "default"
98+
}
99+
100+
fmt.Printf("run job %v successfully\n", newJob.Name)
101+
102+
return nil
103+
}
104+
105+
func readTemplateFile(filename string) (*unstructured.Unstructured, error) {
106+
if filename == "" {
107+
return nil, nil
108+
}
109+
110+
if !strings.Contains(filename, ".yaml") && !strings.Contains(filename, ".yml") {
111+
return nil, fmt.Errorf("only support yaml file")
112+
}
113+
114+
file, err := ioutil.ReadFile(filename)
115+
if err != nil {
116+
return nil, fmt.Errorf("failed to read file, err: %v", err)
117+
}
118+
119+
jobTemplate := &unstructured.Unstructured{Object: map[string]interface{}{}}
120+
if err := yaml.Unmarshal(file, &jobTemplate.Object); err != nil {
121+
return nil, fmt.Errorf("failed to unmarshal file, err: %v", err)
122+
}
123+
124+
return jobTemplate, nil
125+
}

0 commit comments

Comments
 (0)