Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions cmd/cli/jobtemplate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"github.com/spf13/cobra"
"volcano.sh/volcano/pkg/cli/job"
)

func buildJobTemplateCmd() *cobra.Command {
jobTemplateCmd := &cobra.Command{
Use: "template",
Short: "vcctl command line operation job template",
}

jobTemplateRunCmd := &cobra.Command{
Use: "run",
Short: "run job by parameters from the command line",
Run: func(cmd *cobra.Command, args []string) {
checkError(cmd, job.RunJobTemplate())
},
}
job.InitTemplateRunFlags(jobTemplateRunCmd)
jobTemplateCmd.AddCommand(jobTemplateRunCmd)

jobTemplateGenerateCmd := &cobra.Command{
Use: "generate",
Short: "generate jobTemplate by parameters from the command line",
Run: func(cmd *cobra.Command, args []string) {
checkError(cmd, job.GenerateJobTemplate())
},
}
job.InitTemplateGenerateFlags(jobTemplateGenerateCmd)
jobTemplateCmd.AddCommand(jobTemplateGenerateCmd)

return jobTemplateCmd
}
1 change: 1 addition & 0 deletions cmd/cli/vcctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func main() {
rootCmd.CompletionOptions.DisableDefaultCmd = true

rootCmd.AddCommand(buildJobCmd())
rootCmd.AddCommand(buildJobTemplateCmd())
rootCmd.AddCommand(buildQueueCmd())
rootCmd.AddCommand(versionCommand())

Expand Down
118 changes: 118 additions & 0 deletions pkg/cli/job/template_generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package job

import (
"context"
"fmt"
"github.com/spf13/cobra"
"io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"sigs.k8s.io/yaml"
"strings"
vcbatch "volcano.sh/apis/pkg/apis/batch/v1alpha1"
"volcano.sh/apis/pkg/client/clientset/versioned"
"volcano.sh/volcano/pkg/cli/util"
)

type generateTemplateFlags struct {
commonFlags
FileName string
JobName string
Namespace string
GenerateName string
}

var generateJobTemplateFlags = &generateTemplateFlags{}

// InitTemplateGenerateFlags init the generate flags.
func InitTemplateGenerateFlags(cmd *cobra.Command) {
initFlags(cmd, &generateJobTemplateFlags.commonFlags)

cmd.Flags().StringVarP(&generateJobTemplateFlags.FileName, "filename", "f", "", "the yaml file of job")
cmd.Flags().StringVarP(&generateJobTemplateFlags.Namespace, "namespace", "n", "default", "the namespace of job")
cmd.Flags().StringVarP(&generateJobTemplateFlags.JobName, "name", "N", "", "the name of job")
cmd.Flags().StringVarP(&generateJobTemplateFlags.GenerateName, "generateName", "g", "", "the name for new job template")
}

func GenerateJobTemplate() error {
config, err := util.BuildConfig(generateJobTemplateFlags.Master, generateJobTemplateFlags.Kubeconfig)
if err != nil {
return err
}

if generateJobTemplateFlags.JobName == "" && generateJobTemplateFlags.FileName == "" {
err = fmt.Errorf("the filename and job name cannot both be left blank")
return err
}

var job *vcbatch.Job
if generateJobTemplateFlags.FileName != "" {
job, err = readJobFile(generateJobTemplateFlags.FileName)
if err != nil {
return err
}
}

if job == nil {
jobClient := versioned.NewForConfigOrDie(config)
job, err = jobClient.BatchV1alpha1().Jobs(generateJobTemplateFlags.Namespace).Get(context.TODO(), generateJobTemplateFlags.JobName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("fail to get job <%s/%s>", generateJobTemplateFlags.Namespace, generateJobTemplateFlags.JobName)
}
}

unstructuredContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(job)
if err != nil {
return fmt.Errorf("fail to convert job <%s/%s> for:%v", job.Namespace, job.Name, err)
}
jobTemplate := &unstructured.Unstructured{Object: unstructuredContent}
if generateJobTemplateFlags.GenerateName != "" {
jobTemplate.SetName(generateJobTemplateFlags.GenerateName)
}
jobTemplate.SetKind("JobTemplate")
if jobTemplate.GetNamespace() == "" {
jobTemplate.SetNamespace("default")
}
jobTemplate.SetAPIVersion("batch.volcano.sh/v1alpha1")
jobTemplate.SetManagedFields(nil)
jobTemplate.SetAnnotations(nil)
jobTemplate.SetGeneration(1)
jobTemplate.SetResourceVersion("")
jobTemplate.SetUID("")
myDynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return err
}
myDynamicResourceClient := myDynamicClient.
Resource(schema.GroupVersionResource{Group: "batch.volcano.sh", Version: "v1alpha1", Resource: "jobtemplates"}).
Namespace(jobTemplate.GetNamespace())
createdTemplate, err := myDynamicResourceClient.Create(context.TODO(), jobTemplate, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("fail to create jobTemplate <%s/%s> for:%v", jobTemplate.GetNamespace(), jobTemplate.GetName(), err)
}
fmt.Printf("%s/%s created\n", createdTemplate.GetKind(), createdTemplate.GetName())

return nil
}

func readJobFile(filename string) (*vcbatch.Job, error) {

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 vcbatch.Job
if err := yaml.Unmarshal(file, &job); err != nil {
return nil, fmt.Errorf("failed to unmarshal file, err: %v", err)
}

return &job, nil
}
100 changes: 100 additions & 0 deletions pkg/cli/job/template_generate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package job

import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"

"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"volcano.sh/apis/pkg/apis/batch/v1alpha1"
)

func TestGenerateJobTemplate(t *testing.T) {
response := v1alpha1.Job{}
jobTemplate := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "JobTemplate",
"apiVersion": "batch.volcano.sh/v1alpha1",
},
}

handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

if strings.Contains(r.URL.String(), "jobtemplates") {
val, err := json.Marshal(jobTemplate)
if err == nil {
w.Write(val)
}
return
}
val, err := json.Marshal(response)
if err == nil {
w.Write(val)
}
})

server := httptest.NewServer(handler)
defer server.Close()

fileName := "testJob.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: "GenerateTemplate",
ExpectValue: nil,
},
{
Name: "GenerateTemplateWithFile",
FileName: fileName,
ExpectValue: nil,
},
}

for i, testcase := range testCases {
generateJobTemplateFlags = &generateTemplateFlags{
commonFlags: commonFlags{
Master: server.URL,
},
JobName: "test",
Namespace: "test",
}
if testcase.FileName != "" {
generateJobTemplateFlags.FileName = testcase.FileName
}

err := GenerateJobTemplate()
if err != nil {
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, err)
}
}

}

func TestInitTemplateGenerateFlags(t *testing.T) {
var cmd cobra.Command
InitTemplateGenerateFlags(&cmd)

if cmd.Flag("namespace") == nil {
t.Errorf("Could not find the flag namespace")
}
}
125 changes: 125 additions & 0 deletions pkg/cli/job/template_run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package job

import (
"context"
"fmt"
"github.com/spf13/cobra"
"io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"sigs.k8s.io/yaml"
"strings"
vcbatch "volcano.sh/apis/pkg/apis/batch/v1alpha1"
"volcano.sh/apis/pkg/client/clientset/versioned"
"volcano.sh/volcano/pkg/cli/util"
)

type runTemplateFlags struct {
commonFlags
FileName string
TemplateName string
TemplateNamespace string
GenerateName string
}

const createSourceKey = "volcano.sh/createByJobTemplate"

var launchJobTemplateFlags = &runTemplateFlags{}

// InitTemplateRunFlags init the run flags.
func InitTemplateRunFlags(cmd *cobra.Command) {
initFlags(cmd, &launchJobTemplateFlags.commonFlags)

cmd.Flags().StringVarP(&launchJobTemplateFlags.FileName, "filename", "f", "", "the yaml file of jobTemplate")
cmd.Flags().StringVarP(&launchJobTemplateFlags.TemplateNamespace, "namespace", "n", "default", "the namespace of job template")
cmd.Flags().StringVarP(&launchJobTemplateFlags.TemplateName, "name", "N", "", "the name of job template")
cmd.Flags().StringVarP(&launchJobTemplateFlags.GenerateName, "generateName", "g", "", "the name for new job")
}

func RunJobTemplate() error {
config, err := util.BuildConfig(launchJobTemplateFlags.Master, launchJobTemplateFlags.Kubeconfig)
if err != nil {
return err
}
jobTemplate, err := readTemplateFile(launchJobTemplateFlags.FileName)
if err != nil {
return err
}
if jobTemplate == nil {
if launchJobTemplateFlags.TemplateName == "" {
return fmt.Errorf("the filename and template name cannot both be empty")
}
myDynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return err
}
myDynamicResourceClient := myDynamicClient.
Resource(schema.GroupVersionResource{Group: "batch.volcano.sh", Version: "v1alpha1", Resource: "jobtemplates"}).
Namespace(launchJobTemplateFlags.TemplateNamespace)
jobTemplate, err = myDynamicResourceClient.Get(context.TODO(), launchJobTemplateFlags.TemplateName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("fail to get unstructed jobTemplate for: %v", err)
}
}
jobTemplate.SetKind("Job")
jobTemplate.SetManagedFields(nil)
jobTemplate.SetGeneration(1)
jobTemplate.SetResourceVersion("")
jobTemplate.SetUID("")
if jobTemplate.GetNamespace() == "" {
jobTemplate.SetNamespace("default")
}
annotations := jobTemplate.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations[createSourceKey] = fmt.Sprintf("%s.%s", jobTemplate.GetNamespace(), jobTemplate.GetName())
jobTemplate.SetAnnotations(annotations)
var job vcbatch.Job
result, err := yaml.Marshal(jobTemplate.Object)
if err != nil {
return err
}
if err := yaml.Unmarshal(result, &job); err != nil {
return err
}
if launchJobTemplateFlags.GenerateName != "" {
job.Name = launchJobTemplateFlags.GenerateName
}
jobClient := versioned.NewForConfigOrDie(config)
newJob, err := jobClient.BatchV1alpha1().Jobs(launchJobTemplateFlags.TemplateNamespace).Create(context.TODO(), &job, metav1.CreateOptions{})
if err != nil {
return err
}
if newJob.Spec.Queue == "" {
newJob.Spec.Queue = "default"
}

fmt.Printf("run job %v successfully\n", newJob.Name)

return nil
}

func readTemplateFile(filename string) (*unstructured.Unstructured, 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)
}

jobTemplate := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(file, &jobTemplate.Object); err != nil {
return nil, fmt.Errorf("failed to unmarshal file, err: %v", err)
}

return jobTemplate, nil
}
Loading