From e56b18c700471a4a67dcf9f771f93f1f926894de Mon Sep 17 00:00:00 2001 From: Zhang Jinghui Date: Fri, 6 Dec 2019 15:48:17 +0800 Subject: [PATCH] support queue action by vcctl --- cmd/cli/queue.go | 34 +++++++-- pkg/apis/helpers/helpers.go | 4 ++ pkg/cli/queue/create.go | 10 ++- pkg/cli/queue/delete.go | 58 +++++++++++++++ pkg/cli/queue/delete_test.go | 87 ++++++++++++++++++++++ pkg/cli/queue/get.go | 9 +-- pkg/cli/queue/list.go | 15 ++-- pkg/cli/queue/operate.go | 105 +++++++++++++++++++++++++++ pkg/cli/queue/operate_test.go | 131 ++++++++++++++++++++++++++++++++++ pkg/cli/queue/queue_test.go | 2 +- pkg/cli/queue/util.go | 35 +++++++++ 11 files changed, 471 insertions(+), 19 deletions(-) create mode 100644 pkg/cli/queue/delete.go create mode 100644 pkg/cli/queue/delete_test.go create mode 100644 pkg/cli/queue/operate.go create mode 100644 pkg/cli/queue/operate_test.go diff --git a/cmd/cli/queue.go b/cmd/cli/queue.go index 039c8672b7..46f199b747 100644 --- a/cmd/cli/queue.go +++ b/cmd/cli/queue.go @@ -23,20 +23,40 @@ import ( ) func buildQueueCmd() *cobra.Command { - jobCmd := &cobra.Command{ + queueCmd := &cobra.Command{ Use: "queue", Short: "Queue Operations", } - jobRunCmd := &cobra.Command{ + queueCreateCmd := &cobra.Command{ Use: "create", Short: "creates queue", Run: func(cmd *cobra.Command, args []string) { checkError(cmd, queue.CreateQueue()) }, } - queue.InitRunFlags(jobRunCmd) - jobCmd.AddCommand(jobRunCmd) + queue.InitCreateFlags(queueCreateCmd) + queueCmd.AddCommand(queueCreateCmd) + + queueDeleteCmd := &cobra.Command{ + Use: "delete", + Short: "delete queue", + Run: func(cmd *cobra.Command, args []string) { + checkError(cmd, queue.DeleteQueue()) + }, + } + queue.InitDeleteFlags(queueDeleteCmd) + queueCmd.AddCommand(queueDeleteCmd) + + queueOperateCmd := &cobra.Command{ + Use: "operate queue", + Short: "operate queue", + Run: func(cmd *cobra.Command, args []string) { + checkError(cmd, queue.OperateQueue()) + }, + } + queue.InitOperateFlags(queueOperateCmd) + queueCmd.AddCommand(queueOperateCmd) queueListCmd := &cobra.Command{ Use: "list", @@ -46,7 +66,7 @@ func buildQueueCmd() *cobra.Command { }, } queue.InitListFlags(queueListCmd) - jobCmd.AddCommand(queueListCmd) + queueCmd.AddCommand(queueListCmd) queueGetCmd := &cobra.Command{ Use: "get", @@ -56,7 +76,7 @@ func buildQueueCmd() *cobra.Command { }, } queue.InitGetFlags(queueGetCmd) - jobCmd.AddCommand(queueGetCmd) + queueCmd.AddCommand(queueGetCmd) - return jobCmd + return queueCmd } diff --git a/pkg/apis/helpers/helpers.go b/pkg/apis/helpers/helpers.go index c42601a891..b5df3faf1d 100644 --- a/pkg/apis/helpers/helpers.go +++ b/pkg/apis/helpers/helpers.go @@ -40,6 +40,7 @@ import ( vcbatch "volcano.sh/volcano/pkg/apis/batch/v1alpha1" vcbus "volcano.sh/volcano/pkg/apis/bus/v1alpha1" + schedulerv1alpha2 "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2" ) // JobKind creates job GroupVersionKind @@ -48,6 +49,9 @@ var JobKind = vcbatch.SchemeGroupVersion.WithKind("Job") // CommandKind creates command GroupVersionKind var CommandKind = vcbus.SchemeGroupVersion.WithKind("Command") +// V1alpha2QueueKind is queue kind with v1alpha2 version +var V1alpha2QueueKind = schedulerv1alpha2.SchemeGroupVersion.WithKind("Queue") + // GetController returns the controller uid func GetController(obj interface{}) types.UID { accessor, err := meta.Accessor(obj) diff --git a/pkg/cli/queue/create.go b/pkg/cli/queue/create.go index 01df533243..68bac301d9 100644 --- a/pkg/cli/queue/create.go +++ b/pkg/cli/queue/create.go @@ -30,20 +30,23 @@ type createFlags struct { Name string Weight int32 + // State is state of Queue + State string } var createQueueFlags = &createFlags{} -// InitRunFlags is used to init all run flags -func InitRunFlags(cmd *cobra.Command) { +// InitCreateFlags is used to init all flags during queue creating +func InitCreateFlags(cmd *cobra.Command) { initFlags(cmd, &createQueueFlags.commonFlags) cmd.Flags().StringVarP(&createQueueFlags.Name, "name", "n", "test", "the name of queue") cmd.Flags().Int32VarP(&createQueueFlags.Weight, "weight", "w", 1, "the weight of the queue") + cmd.Flags().StringVarP(&createQueueFlags.State, "state", "S", "Open", "the state of queue") } -// CreateQueue creates queue +// CreateQueue create queue func CreateQueue() error { config, err := buildConfig(createQueueFlags.Master, createQueueFlags.Kubeconfig) if err != nil { @@ -56,6 +59,7 @@ func CreateQueue() error { }, Spec: schedulingV1alpha2.QueueSpec{ Weight: int32(createQueueFlags.Weight), + State: schedulingV1alpha2.QueueState(createQueueFlags.State), }, } diff --git a/pkg/cli/queue/delete.go b/pkg/cli/queue/delete.go new file mode 100644 index 0000000000..1ec9366325 --- /dev/null +++ b/pkg/cli/queue/delete.go @@ -0,0 +1,58 @@ +/* +Copyright 2017 The Kubernetes 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 queue + +import ( + "fmt" + + "volcano.sh/volcano/pkg/client/clientset/versioned" + + "github.com/spf13/cobra" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type deleteFlags struct { + commonFlags + + // Name is name of queue + Name string +} + +var deleteQueueFlags = &deleteFlags{} + +// InitDeleteFlags is used to init all flags during queue deleting +func InitDeleteFlags(cmd *cobra.Command) { + initFlags(cmd, &deleteQueueFlags.commonFlags) + + cmd.Flags().StringVarP(&deleteQueueFlags.Name, "name", "n", "", "the name of queue") +} + +// DeleteQueue delete queue +func DeleteQueue() error { + config, err := buildConfig(deleteQueueFlags.Master, deleteQueueFlags.Kubeconfig) + if err != nil { + return err + } + + if len(deleteQueueFlags.Name) == 0 { + return fmt.Errorf("Queue name must be specified") + } + + queueClient := versioned.NewForConfigOrDie(config) + return queueClient.SchedulingV1alpha2().Queues().Delete(deleteQueueFlags.Name, &metav1.DeleteOptions{}) +} diff --git a/pkg/cli/queue/delete_test.go b/pkg/cli/queue/delete_test.go new file mode 100644 index 0000000000..5a37394310 --- /dev/null +++ b/pkg/cli/queue/delete_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2017 The Kubernetes 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 queue + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2" + + "github.com/spf13/cobra" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestDeleteQueue(t *testing.T) { + response := v1alpha2.Queue{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-queue", + }, + } + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + val, err := json.Marshal(response) + if err == nil { + w.Write(val) + } + }) + + server := httptest.NewServer(handler) + defer server.Close() + + deleteQueueFlags.Master = server.URL + testCases := []struct { + Name string + QueueName string + ExpectValue error + }{ + { + Name: "Normal Case Delete Queue Succeed", + QueueName: "normal-case", + ExpectValue: nil, + }, + { + Name: "Abnormal Case Delete Queue Failed For Name Not Specified", + QueueName: "", + ExpectValue: fmt.Errorf("Queue name must be specified"), + }, + } + + for _, testCase := range testCases { + deleteQueueFlags.Name = testCase.QueueName + + err := DeleteQueue() + if false == reflect.DeepEqual(err, testCase.ExpectValue) { + t.Errorf("Case '%s' failed, expected: '%v', got '%v'", testCase.Name, testCase.ExpectValue, err) + } + } +} + +func TestInitDeleteFlags(t *testing.T) { + var cmd cobra.Command + InitDeleteFlags(&cmd) + + if cmd.Flag("name") == nil { + t.Errorf("Could not find the flag name") + } +} diff --git a/pkg/cli/queue/get.go b/pkg/cli/queue/get.go index 919df10c6f..da46d1909d 100644 --- a/pkg/cli/queue/get.go +++ b/pkg/cli/queue/get.go @@ -70,13 +70,14 @@ func GetQueue() error { // PrintQueue prints queue information func PrintQueue(queue *v1alpha2.Queue, writer io.Writer) { - _, err := fmt.Fprintf(writer, "%-25s%-8s%-8s%-8s%-8s\n", - Name, Weight, Pending, Running, Unknown) + _, err := fmt.Fprintf(writer, "%-25s%-8s%-8s%-8s%-8s%-8s%-8s\n", + Name, Weight, State, Inqueue, Pending, Running, Unknown) if err != nil { fmt.Printf("Failed to print queue command result: %s.\n", err) } - _, err = fmt.Fprintf(writer, "%-25s%-8d%-8d%-8d%-8d\n", - queue.Name, queue.Spec.Weight, queue.Status.Pending, queue.Status.Running, queue.Status.Unknown) + _, err = fmt.Fprintf(writer, "%-25s%-8d%-8s%-8d%-8d%-8d%-8d\n", + queue.Name, queue.Spec.Weight, queue.Status.State, queue.Status.Inqueue, + queue.Status.Pending, queue.Status.Running, queue.Status.Unknown) if err != nil { fmt.Printf("Failed to print queue command result: %s.\n", err) } diff --git a/pkg/cli/queue/list.go b/pkg/cli/queue/list.go index 33bd1b7d8a..b961320d2b 100644 --- a/pkg/cli/queue/list.go +++ b/pkg/cli/queue/list.go @@ -48,6 +48,12 @@ const ( // Unknown status of the queue Unknown string = "Unknown" + + // Inqueue status of queue + Inqueue string = "Inqueue" + + // State is state of queue + State string = "State" ) var listQueueFlags = &listFlags{} @@ -81,14 +87,15 @@ func ListQueue() error { // PrintQueues prints queue information func PrintQueues(queues *v1alpha2.QueueList, writer io.Writer) { - _, err := fmt.Fprintf(writer, "%-25s%-8s%-8s%-8s%-8s\n", - Name, Weight, Pending, Running, Unknown) + _, err := fmt.Fprintf(writer, "%-25s%-8s%-8s%-8s%-8s%-8s%-8s\n", + Name, Weight, State, Inqueue, Pending, Running, Unknown) if err != nil { fmt.Printf("Failed to print queue command result: %s.\n", err) } for _, queue := range queues.Items { - _, err = fmt.Fprintf(writer, "%-25s%-8d%-8d%-8d%-8d\n", - queue.Name, queue.Spec.Weight, queue.Status.Pending, queue.Status.Running, queue.Status.Unknown) + _, err = fmt.Fprintf(writer, "%-25s%-8d%-8s%-8d%-8d%-8d%-8d\n", + queue.Name, queue.Spec.Weight, queue.Status.State, queue.Status.Inqueue, + queue.Status.Pending, queue.Status.Running, queue.Status.Unknown) if err != nil { fmt.Printf("Failed to print queue command result: %s.\n", err) } diff --git a/pkg/cli/queue/operate.go b/pkg/cli/queue/operate.go new file mode 100644 index 0000000000..ac37f979b2 --- /dev/null +++ b/pkg/cli/queue/operate.go @@ -0,0 +1,105 @@ +/* +Copyright 2017 The Kubernetes 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 queue + +import ( + "fmt" + + schedulingv1alpha2 "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2" + "volcano.sh/volcano/pkg/client/clientset/versioned" + + "github.com/spf13/cobra" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // ActionOpen is `open` action + ActionOpen = "open" + // ActionClose is `close` action + ActionClose = "close" + // ActionUpdate is `update` action + ActionUpdate = "update" +) + +type operateFlags struct { + commonFlags + + // Name is name of queue + Name string + // Weight is weight of queue + Weight int32 + // Action is operation action of queue + Action string +} + +var operateQueueFlags = &operateFlags{} + +// InitOperateFlags is used to init all flags during queue operating +func InitOperateFlags(cmd *cobra.Command) { + initFlags(cmd, &operateQueueFlags.commonFlags) + + cmd.Flags().StringVarP(&operateQueueFlags.Name, "name", "n", "", "the name of queue") + cmd.Flags().Int32VarP(&operateQueueFlags.Weight, "weight", "w", 0, "the weight of the queue") + cmd.Flags().StringVarP(&operateQueueFlags.Action, "action", "a", "", + "operate action to queue, valid actions are open, close, update") +} + +// OperateQueue operates queue +func OperateQueue() error { + config, err := buildConfig(operateQueueFlags.Master, operateQueueFlags.Kubeconfig) + if err != nil { + return err + } + + if len(operateQueueFlags.Name) == 0 { + return fmt.Errorf("Queue name must be specified") + } + + var action schedulingv1alpha2.QueueAction + + switch operateQueueFlags.Action { + case ActionOpen: + action = schedulingv1alpha2.OpenQueueAction + case ActionClose: + action = schedulingv1alpha2.CloseQueueAction + case ActionUpdate: + if operateQueueFlags.Weight == 0 { + return fmt.Errorf("When %s queue %s, weight must be specified, "+ + "the value must be greater than 0", ActionUpdate, operateQueueFlags.Name) + } + + queueClient := versioned.NewForConfigOrDie(config) + queue, err := queueClient.SchedulingV1alpha2().Queues().Get(operateQueueFlags.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + queue.Spec.Weight = int32(operateQueueFlags.Weight) + + _, err = queueClient.SchedulingV1alpha2().Queues().Update(queue) + + return err + case "": + return fmt.Errorf("Action can not be null") + default: + return fmt.Errorf("Action %s invalid, valid actions are %s, %s and %s", + operateQueueFlags.Action, ActionOpen, ActionClose, ActionUpdate) + } + + return createQueueCommand(config, action) +} diff --git a/pkg/cli/queue/operate_test.go b/pkg/cli/queue/operate_test.go new file mode 100644 index 0000000000..a3265ce7be --- /dev/null +++ b/pkg/cli/queue/operate_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2017 The Kubernetes 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 queue + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2" + + "github.com/spf13/cobra" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestOperateQueue(t *testing.T) { + response := v1alpha2.Queue{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-queue", + }, + } + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + val, err := json.Marshal(response) + if err == nil { + w.Write(val) + } + }) + + server := httptest.NewServer(handler) + defer server.Close() + + operateQueueFlags.Master = server.URL + testCases := []struct { + Name string + QueueName string + Weight int32 + Action string + ExpectValue error + }{ + { + Name: "Normal Case Operate Queue Succeed, Action close", + QueueName: "normal-case-action-close", + Action: ActionClose, + ExpectValue: nil, + }, + { + Name: "Normal Case Operate Queue Succeed, Action open", + QueueName: "normal-case-action-open", + Action: ActionOpen, + ExpectValue: nil, + }, + { + Name: "Normal Case Operate Queue Succeed, Update Weight", + QueueName: "normal-case-update-weight", + Action: ActionUpdate, + Weight: 3, + ExpectValue: nil, + }, + { + Name: "Abnormal Case Update Queue Failed For Invalid Weight", + QueueName: "abnormal-case-invalid-weight", + Action: ActionUpdate, + ExpectValue: fmt.Errorf("When %s queue %s, weight must be specified, "+ + "the value must be greater than 0", ActionUpdate, "abnormal-case-invalid-weight"), + }, + { + Name: "Abnormal Case Operate Queue Failed For Name Not Specified", + QueueName: "", + ExpectValue: fmt.Errorf("Queue name must be specified"), + }, + { + Name: "Abnormal Case Operate Queue Failed For Action null", + QueueName: "abnormal-case-null-action", + Action: "", + ExpectValue: fmt.Errorf("Action can not be null"), + }, + { + Name: "Abnormal Case Operate Queue Failed For Action Invalid", + QueueName: "abnormal-case-invalid-action", + Action: "invalid", + ExpectValue: fmt.Errorf("Action %s invalid, valid actions are %s, %s and %s", + "invalid", ActionOpen, ActionClose, ActionUpdate), + }, + } + + for _, testCase := range testCases { + operateQueueFlags.Name = testCase.QueueName + operateQueueFlags.Action = testCase.Action + operateQueueFlags.Weight = testCase.Weight + + err := OperateQueue() + if false == reflect.DeepEqual(err, testCase.ExpectValue) { + t.Errorf("Case '%s' failed, expected: '%v', got '%v'", testCase.Name, testCase.ExpectValue, err) + } + } +} + +func TestInitOperateFlags(t *testing.T) { + var cmd cobra.Command + InitOperateFlags(&cmd) + + if cmd.Flag("name") == nil { + t.Errorf("Could not find the flag name") + } + if cmd.Flag("weight") == nil { + t.Errorf("Could not find the flag weight") + } + if cmd.Flag("action") == nil { + t.Errorf("Could not find the flag action") + } +} diff --git a/pkg/cli/queue/queue_test.go b/pkg/cli/queue/queue_test.go index 126487eca2..c49e3ad48d 100644 --- a/pkg/cli/queue/queue_test.go +++ b/pkg/cli/queue/queue_test.go @@ -80,7 +80,7 @@ func getCommonFlags(master string) commonFlags { } func TestCreateQueue(t *testing.T) { - InitRunFlags(&cobra.Command{}) + InitCreateFlags(&cobra.Command{}) server := getTestQueueHTTPServer(t) defer server.Close() diff --git a/pkg/cli/queue/util.go b/pkg/cli/queue/util.go index f6617f287d..f862ac642d 100644 --- a/pkg/cli/queue/util.go +++ b/pkg/cli/queue/util.go @@ -17,8 +17,16 @@ limitations under the License. package queue import ( + "fmt" "os" + "strings" + busv1alpha1 "volcano.sh/volcano/pkg/apis/bus/v1alpha1" + "volcano.sh/volcano/pkg/apis/helpers" + schedulingv1alpha2 "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2" + "volcano.sh/volcano/pkg/client/clientset/versioned" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" // Initialize client auth plugin. @@ -35,3 +43,30 @@ func homeDir() string { func buildConfig(master, kubeconfig string) (*rest.Config, error) { return clientcmd.BuildConfigFromFlags(master, kubeconfig) } + +func createQueueCommand(config *rest.Config, action schedulingv1alpha2.QueueAction) error { + queueClient := versioned.NewForConfigOrDie(config) + queue, err := queueClient.SchedulingV1alpha2().Queues().Get(operateQueueFlags.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + ctrlRef := metav1.NewControllerRef(queue, helpers.V1alpha2QueueKind) + cmd := &busv1alpha1.Command{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s-%s-", + queue.Name, strings.ToLower(string(action))), + OwnerReferences: []metav1.OwnerReference{ + *ctrlRef, + }, + }, + TargetObject: ctrlRef, + Action: string(action), + } + + if _, err := queueClient.BusV1alpha1().Commands("default").Create(cmd); err != nil { + return err + } + + return nil +}