Skip to content

Commit

Permalink
[kueuectl] Initialized kubectl kueue plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
mbobrovskyi committed May 2, 2024
1 parent b5d3b4d commit 4bdcdcf
Show file tree
Hide file tree
Showing 17 changed files with 1,009 additions and 1 deletion.
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GINKGO_VERSION ?= $(shell $(GO_CMD) list -m -f '{{.Version}}' github.com/onsi/gi
GIT_TAG ?= $(shell git describe --tags --dirty --always)
# Image URL to use all building/pushing image targets
PLATFORMS ?= linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
CLI_PLATFORMS ?= linux/amd64,linux/arm64,darwin/amd64,darwin/arm64
DOCKER_BUILDX_CMD ?= docker buildx
IMAGE_BUILD_CMD ?= $(DOCKER_BUILDX_CMD) build
IMAGE_BUILD_EXTRA_OPTS ?=
Expand Down Expand Up @@ -190,7 +191,7 @@ test-integration: gomod-download envtest ginkgo mpi-operator-crd ray-operator-cr

CREATE_KIND_CLUSTER ?= true
.PHONY: test-e2e
test-e2e: kustomize ginkgo yq gomod-download jobset-operator-crd run-test-e2e-$(E2E_KIND_VERSION:kindest/node:v%=%)
test-e2e: kustomize ginkgo yq gomod-download jobset-operator-crd kueuectl run-test-e2e-$(E2E_KIND_VERSION:kindest/node:v%=%)

.PHONY: test-multikueue-e2e
test-multikueue-e2e: kustomize ginkgo yq gomod-download jobset-operator-crd run-test-multikueue-e2e-$(E2E_KIND_VERSION:kindest/node:v%=%)
Expand Down Expand Up @@ -374,6 +375,7 @@ artifacts: kustomize yq helm
mv artifacts/kueue-$(GIT_TAG).tgz artifacts/kueue-chart-$(GIT_TAG).tgz
# Revert the image changes
$(YQ) e '.controllerManager.manager.image.repository = "$(IMAGE_REGISTRY)/$(IMAGE_NAME)" | .controllerManager.manager.image.tag = "main" | .controllerManager.manager.image.pullPolicy = "Always"' -i charts/kueue/values.yaml
#GO_BUILD_ENV="$(GO_BUILD_ENV)" GO_CMD="$(GO_CMD)" LD_FLAGS="$(LD_FLAGS)" BUILD_DIR="artifacts/bin" BUILD_NAME=kubectl-kueue PLATFORMS="$(CLI_PLATFORMS)" ./hack/multiplatform-build.sh ./cmd/kueuectl/main.go

.PHONY: prepare-release-branch
prepare-release-branch: yq kustomize
Expand Down Expand Up @@ -418,6 +420,10 @@ importer-image: PLATFORMS=linux/amd64
importer-image: PUSH=--load
importer-image: importer-image-build

.PHONY: kueuectl
kueuectl:
$(GO_BUILD_ENV) $(GO_CMD) build -ldflags="$(LD_FLAGS)" -o bin/kubectl-kueue cmd/kueuectl/main.go

GOLANGCI_LINT = $(PROJECT_DIR)/bin/golangci-lint
.PHONY: golangci-lint
golangci-lint: ## Download golangci-lint locally if necessary.
Expand Down
64 changes: 64 additions & 0 deletions cmd/kueuectl/app/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright 2024 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 app

import (
"os"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"

"sigs.k8s.io/kueue/cmd/kueuectl/app/create"
)

type KueuectlOptions struct {
ConfigFlags *genericclioptions.ConfigFlags

genericiooptions.IOStreams
}

func defaultConfigFlags() *genericclioptions.ConfigFlags {
return genericclioptions.NewConfigFlags(true).WithDiscoveryQPS(50.0)
}

func NewDefaultKueuectlCmd() *cobra.Command {
ioStreams := genericiooptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
return NewKueuectlCmd(KueuectlOptions{
ConfigFlags: defaultConfigFlags().WithWarningPrinter(ioStreams),
IOStreams: ioStreams,
})
}

func NewKueuectlCmd(o KueuectlOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "kueue",
Short: "Controls Kueue queueing manager",
}

flags := cmd.PersistentFlags()

configFlags := o.ConfigFlags
if configFlags == nil {
configFlags = defaultConfigFlags().WithWarningPrinter(o.IOStreams)
}
configFlags.AddFlags(flags)

cmd.AddCommand(create.NewCreateCmd(configFlags, o.IOStreams))

return cmd
}
44 changes: 44 additions & 0 deletions cmd/kueuectl/app/create/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2024 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 create

import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"

"sigs.k8s.io/kueue/cmd/kueuectl/app/util"
)

const (
createExample = ` # Create local queue
kueuectl create localqueue my-local-queue -c my-cluster-queue`
)

func NewCreateCmd(clientGetter genericclioptions.RESTClientGetter, streams genericiooptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Create a resource",
Example: createExample,
}

util.AddDryRunFlag(cmd)

cmd.AddCommand(NewLocalQueueCmd(clientGetter, streams))

return cmd
}
178 changes: 178 additions & 0 deletions cmd/kueuectl/app/create/create_localqueue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
Copyright 2024 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 create

import (
"context"
"fmt"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/cli-runtime/pkg/printers"

"sigs.k8s.io/kueue/apis/kueue/v1beta1"
"sigs.k8s.io/kueue/client-go/clientset/versioned"
"sigs.k8s.io/kueue/client-go/clientset/versioned/scheme"
kueuev1beta1 "sigs.k8s.io/kueue/client-go/clientset/versioned/typed/kueue/v1beta1"
"sigs.k8s.io/kueue/cmd/kueuectl/app/util"
)

const (
lqLong = `Create a local queue with the given name in the specified namespace.`
lqExample = ` # Create a local queue
kueuectl create localqueue my-local-queue -c my-cluster-queue`
)

type LocalQueueOptions struct {
PrintFlags *genericclioptions.PrintFlags

DryRunStrategy util.DryRunStrategy
Name string
Namespace string
EnforceNamespace bool
ClusterQueue v1beta1.ClusterQueueReference

UserSpecifiedClusterQueue string

Client kueuev1beta1.KueueV1beta1Interface

PrintObj printers.ResourcePrinterFunc

genericiooptions.IOStreams
}

func NewLocalQueueOptions(streams genericiooptions.IOStreams) *LocalQueueOptions {
return &LocalQueueOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: streams,
}
}

func NewLocalQueueCmd(clientGetter genericclioptions.RESTClientGetter, streams genericiooptions.IOStreams) *cobra.Command {
o := NewLocalQueueOptions(streams)

cmd := &cobra.Command{
Use: "localqueue NAME -c CLUSTER_QUEUE_NAME [--dry-run STRATEGY]",
// To do not add "[flags]" suffix on the end of usage line
DisableFlagsInUseLine: true,
Aliases: []string{"lq"},
Short: "Creates a localqueue",
Long: lqLong,
Example: lqExample,
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
cobra.CheckErr(o.Complete(clientGetter, cmd, args))
cobra.CheckErr(o.Validate())
cobra.CheckErr(o.Run(cmd.Context()))
},
}

o.PrintFlags.AddFlags(cmd)

cmd.Flags().StringVarP(&o.UserSpecifiedClusterQueue, "clusterqueue", "c", "",
"The cluster queue name which will be associated with the local queue (required).")
_ = cmd.MarkFlagRequired("clusterqueue")

return cmd
}

// Complete completes all the required options
func (o *LocalQueueOptions) Complete(clientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command, args []string) error {
o.Name = args[0]

var err error
o.Namespace, o.EnforceNamespace, err = clientGetter.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}

o.ClusterQueue = v1beta1.ClusterQueueReference(o.UserSpecifiedClusterQueue)

config, err := clientGetter.ToRESTConfig()
if err != nil {
return err
}

clientset, err := versioned.NewForConfig(config)
if err != nil {
return err
}

o.Client = clientset.KueueV1beta1()

o.DryRunStrategy, err = util.GetDryRunStrategy(cmd)
if err != nil {
return err
}

err = util.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
if err != nil {
return err
}

printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}

o.PrintObj = printer.PrintObj

return nil
}

// Validate validates required fields are set to support structured generation
func (o *LocalQueueOptions) Validate() error {
if len(o.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(o.ClusterQueue) == 0 {
return fmt.Errorf("clusterqueue must be specified")
}
if len(o.Namespace) == 0 {
return fmt.Errorf("namespace must be specified")
}
return nil
}

// Run create localqueue
func (o *LocalQueueOptions) Run(ctx context.Context) error {
lq := o.createLocalQueue()
if o.DryRunStrategy != util.DryRunClient {
var (
createOptions metav1.CreateOptions
err error
)
if o.DryRunStrategy == util.DryRunServer {
createOptions.DryRun = []string{metav1.DryRunAll}
}
lq, err = o.Client.LocalQueues(o.Namespace).Create(ctx, lq, createOptions)
if err != nil {
return err
}
}
return o.PrintObj(lq, o.Out)
}

func (o *LocalQueueOptions) createLocalQueue() *v1beta1.LocalQueue {
return &v1beta1.LocalQueue{
TypeMeta: metav1.TypeMeta{APIVersion: v1beta1.SchemeGroupVersion.String(), Kind: "LocalQueue"},
ObjectMeta: metav1.ObjectMeta{Name: o.Name, Namespace: o.Namespace},
Spec: v1beta1.LocalQueueSpec{ClusterQueue: o.ClusterQueue},
}
}
54 changes: 54 additions & 0 deletions cmd/kueuectl/app/create/create_localqueue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright 2024 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 create

import (
"testing"

"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"sigs.k8s.io/kueue/apis/kueue/v1beta1"
)

func TestCreateLocalQueue(t *testing.T) {
testCases := map[string]struct {
options *LocalQueueOptions
expected *v1beta1.LocalQueue
}{
"success_create": {
options: &LocalQueueOptions{
Name: "lq1",
Namespace: "ns1",
ClusterQueue: "cq1",
},
expected: &v1beta1.LocalQueue{
TypeMeta: metav1.TypeMeta{APIVersion: "kueue.x-k8s.io/v1beta1", Kind: "LocalQueue"},
ObjectMeta: metav1.ObjectMeta{Name: "lq1", Namespace: "ns1"},
Spec: v1beta1.LocalQueueSpec{ClusterQueue: "cq1"},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
lq := tc.options.createLocalQueue()
if diff := cmp.Diff(tc.expected, lq); diff != "" {
t.Errorf("Unexpected result (-want,+got):\n%s", diff)
}
})
}
}
Loading

0 comments on commit 4bdcdcf

Please sign in to comment.