Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scheduler predictor #64

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ $(LOCALBIN):
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
ENVTEST ?= $(LOCALBIN)/setup-envtest

GOLINT_VERSION ?= 1.56.2
GOLINT_VERSION ?= 1.61.0

.PHONY: envtest
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
Expand Down
6 changes: 2 additions & 4 deletions cmd/kubectl-k8ssandra/k8ssandra/k8ssandra.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/nodetool"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/operate"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/register"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/tools"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/users"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -40,21 +41,18 @@ func NewCmd(streams genericclioptions.IOStreams) *cobra.Command {
}

// Add subcommands
// cmd.AddCommand(nodetool.NewCmd(streams))
// cmd.AddCommand(cqlsh.NewCmd(streams))
// cmd.AddCommand(cleaner.NewCmd(streams))
// cmd.AddCommand(crds.NewCmd(streams))
// cmd.AddCommand(edit.NewCmd(streams))
cmd.AddCommand(operate.NewStartCmd(streams))
cmd.AddCommand(operate.NewRestartCmd(streams))
cmd.AddCommand(operate.NewStopCmd(streams))
// cmd.AddCommand(list.NewCmd(streams))
// cmd.AddCommand(migrate.NewCmd(streams))
cmd.AddCommand(users.NewCmd(streams))
// cmd.AddCommand(migrate.NewInstallCmd(streams))
cmd.AddCommand(config.NewCmd(streams))
cmd.AddCommand(helm.NewHelmCmd(streams))
cmd.AddCommand(nodetool.NewCmd(streams))
cmd.AddCommand(tools.NewToolsCmd(streams))
register.SetupRegisterClusterCmd(cmd, streams)

// cmd.Flags().BoolVar(&o.listNamespaces, "list", o.listNamespaces, "if true, print the list of all namespaces in the current KUBECONFIG")
Expand Down
171 changes: 171 additions & 0 deletions cmd/kubectl-k8ssandra/tools/estimate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package tools

import (
"context"
"fmt"

"github.com/pkg/errors"

"github.com/k8ssandra/k8ssandra-client/pkg/kubernetes"
"github.com/k8ssandra/k8ssandra-client/pkg/scheduler"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

var (
estimateExample = `
# Estimate if pods will fit the cluster
%[1]s estimate [<args>]

# Estimate if 4 pods each with 2Gi of memory and 2 vCPUs will be able to run on the cluster.
# All CPU values and memory use Kubernetes notation
%[1]s estimate --count 4 --memory 2Gi --cpu 2000m
`
errInvalidCount = errors.New("Count of pods must be higher than 0")
)

type estimateOptions struct {
configFlags *genericclioptions.ConfigFlags
genericclioptions.IOStreams

count int
memory string
cpu string

cpuQuantity resource.Quantity
memoryQuantity resource.Quantity
}

func newEstimateOptions(streams genericclioptions.IOStreams) *estimateOptions {
return &estimateOptions{
configFlags: genericclioptions.NewConfigFlags(true),
IOStreams: streams,
}
}

// NewCmd provides a cobra command wrapping newAddOptions
func NewEstimateCmd(streams genericclioptions.IOStreams) *cobra.Command {
o := newEstimateOptions(streams)

cmd := &cobra.Command{
Use: "estimate [flags]",
Short: "Estimate if datacenter can be expanded",
Example: fmt.Sprintf(estimateExample, "kubectl k8ssandra tools estimate"),
RunE: func(c *cobra.Command, args []string) error {
if err := o.Complete(c, args); err != nil {
return err
}
if err := o.Validate(); err != nil {
return err
}
if err := o.Run(); err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: doing things this way always spits out the help text for the command in the console, along with the actual error:

k8ssandra-client % ./kubectl-k8ssandra tools estimate --count 25 --memory 2Gi --cpu 2000m 
Error: Unable to schedule the pods: unable to schedule all the pods, requested: 25, schedulable: 5
Usage:
  k8ssandra tools estimate [flags]

Examples:

        # Estimate if pods will fit the cluster
        kubectl k8ssandra tools estimate estimate [<args>]

        # Estimate if 4 pods each with 2Gi of memory and 2 vCPUs will be able to run on the cluster.
        # All CPU values and memory use Kubernetes notation
        kubectl k8ssandra tools estimate estimate --count 4 --memory 2Gi --cpu 2000m
        

Flags:
      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
      --as-uid string                  UID to impersonate for the operation.
      --cache-dir string               Default cache directory (default "/Users/adejanovski/.kube/cache")
      --certificate-authority string   Path to a cert file for the certificate authority
      --client-certificate string      Path to a client certificate file for TLS
      --client-key string              Path to a client key file for TLS
      --cluster string                 The name of the kubeconfig cluster to use
      --context string                 The name of the kubeconfig context to use
      --count int                      new nodes to create
...

It makes it a little challenging to figure out what's going on.
Also we don't get a message if the pods can successfully be scheduled.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to this:

k8ssandra-client git:(add_scheduler_predictor) ✗ ./kubectl-k8ssandra tools estimate --count 6 --memory 2Gi --cpu 2000m    
2024/12/03 13:24:32 Pods can be scheduled to current cluster kind-kindk8ssandra-client git:(add_scheduler_predictor) ✗ ./kubectl-k8ssandra tools estimate --count 16 --memory 2Gi --cpu 2000m
2024/12/03 13:24:36 Error: Unable to schedule the pods to current cluster kind-kind: unable to schedule all the pods, requested: 16, schedulable: 6

}

return nil
},
}

fl := cmd.Flags()
fl.IntVar(&o.count, "count", 0, "new nodes to create")
fl.StringVar(&o.cpu, "cpu", "0", "how many cores per node")
fl.StringVar(&o.memory, "memory", "0", "how much memory per node")

if err := cmd.MarkFlagRequired("memory"); err != nil {
panic(err)
}

if err := cmd.MarkFlagRequired("cpu"); err != nil {
panic(err)
}

if err := cmd.MarkFlagRequired("count"); err != nil {
panic(err)
}

o.configFlags.AddFlags(fl)
return cmd
}

// Complete parses the arguments and necessary flags to options
func (c *estimateOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}

// Validate ensures that all required arguments and flag values are provided
func (c *estimateOptions) Validate() error {
if c.count < 0 {
burmanm marked this conversation as resolved.
Show resolved Hide resolved
return errInvalidCount
}

var err error
cpuQuantity, err := resource.ParseQuantity(c.cpu)
if err != nil {
return err
}

memoryQuantity, err := resource.ParseQuantity(c.memory)
if err != nil {
return err
}

c.memoryQuantity = memoryQuantity
c.cpuQuantity = cpuQuantity

return nil
}

// Run processes the input, creates a connection to Kubernetes and processes a secret to add the users
func (c *estimateOptions) Run() error {
restConfig, err := c.configFlags.ToRESTConfig()
if err != nil {
return err
}

kubeClient, err := kubernetes.GetClient(restConfig)
if err != nil {
return err
}
ctx := context.Background()

proposedPods := makePods(c.count, makeResources(c.cpuQuantity.MilliValue(), c.memoryQuantity.Value()))

if err := scheduler.TryScheduling(ctx, kubeClient, proposedPods); err != nil {
return errors.Wrap(err, "Unable to schedule the pods")
}

return nil
}

func makePods(count int, resources corev1.ResourceList) []*corev1.Pod {
pods := make([]*corev1.Pod, count)
for i := 0; i < count; i++ {
pods[i] = &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "a",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Resources: corev1.ResourceRequirements{
Requests: resources,
},
},
},
},
}
}

return pods
}

func makeResources(milliCPU, memory int64) corev1.ResourceList {
return corev1.ResourceList{
corev1.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
corev1.ResourceMemory: *resource.NewQuantity(memory, resource.BinarySI),
}
}
35 changes: 35 additions & 0 deletions cmd/kubectl-k8ssandra/tools/tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package tools

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

type ClientOptions struct {
configFlags *genericclioptions.ConfigFlags
genericclioptions.IOStreams
}

// NewToolsOptions provides an instance of NamespaceOptions with default values
func NewToolsOptions(streams genericclioptions.IOStreams) *ClientOptions {
return &ClientOptions{
configFlags: genericclioptions.NewConfigFlags(true),
IOStreams: streams,
}
}

// NewToolsCmd provides a cobra command wrapping ClientOptions
func NewToolsCmd(streams genericclioptions.IOStreams) *cobra.Command {
o := NewToolsOptions(streams)

cmd := &cobra.Command{
Use: "tools [subcommand] [flags]",
}

// Add subcommands
cmd.AddCommand(NewEstimateCmd(streams))

o.configFlags.AddFlags(cmd.Flags())

return cmd
}
Loading
Loading