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
93 changes: 77 additions & 16 deletions cli/kthena/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@ If NAME is provided, only models containing the specified name will be displayed

// getModelServingsCmd represents the get model-servings command
var getModelServingsCmd = &cobra.Command{
Use: "model-servings",
Use: "model-servings [NAME]",
Aliases: []string{"ms", "model-serving"},
Short: "List model serving workloads",
Long: `List ModelServing resources in the cluster.`,
Long: `List ModelServing resources in the cluster. Optionally filter by name.`,
Args: cobra.MaximumNArgs(1),
Comment on lines +94 to +98
RunE: runGetModelServings,
}

Expand Down Expand Up @@ -363,11 +364,30 @@ func runGetModelServings(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to list ModelServings: %v", err)
}

if len(modelServingList.Items) == 0 {
if getAllNamespaces {
fmt.Println("No ModelServings found across all namespaces.")
// Get name filter if provided
var nameFilter string
if len(args) > 0 {
nameFilter = args[0]
}
Comment on lines +367 to +371
filterLower := strings.ToLower(nameFilter)

// Filter matching items
var filtered []workload.ModelServing
for _, ms := range modelServingList.Items {
if nameFilter == "" || strings.Contains(strings.ToLower(ms.Name), filterLower) {
filtered = append(filtered, ms)
}
}
Comment thread
anirudh240 marked this conversation as resolved.
Comment on lines +367 to +380

if len(filtered) == 0 {
if nameFilter != "" {
fmt.Printf("No ModelServings found matching '%s'.\n", nameFilter)
} else {
fmt.Printf("No ModelServings found in namespace %s.\n", namespace)
if getAllNamespaces {
fmt.Println("No ModelServings found across all namespaces.")
} else {
fmt.Printf("No ModelServings found in namespace %s.\n", namespace)
}
}
return nil
}
Comment thread
anirudh240 marked this conversation as resolved.
Expand All @@ -380,8 +400,8 @@ func runGetModelServings(cmd *cobra.Command, args []string) error {
fmt.Fprintln(w, "NAME\tREADY\tSTATUS\tAGE")
}

// Print ModelServings
for _, ms := range modelServingList.Items {
// Print matching ModelServings
for _, ms := range filtered {
age := time.Since(ms.CreationTimestamp.Time).Truncate(time.Second)
ready := fmt.Sprintf("%d/%d", ms.Status.AvailableReplicas, ms.Status.Replicas)
status := getModelServingStatus(ms.Status.Conditions)
Expand All @@ -394,6 +414,42 @@ func runGetModelServings(cmd *cobra.Command, args []string) error {
return w.Flush()
}

func getAutoscalingPolicyBindingTarget(binding workload.AutoscalingPolicyBinding) string {
if binding.Spec.HomogeneousTarget != nil {
if binding.Spec.HomogeneousTarget.Target.TargetRef.Name != "" {
return binding.Spec.HomogeneousTarget.Target.TargetRef.Name
}
}
if binding.Spec.HeterogeneousTarget != nil {
var names []string
for _, p := range binding.Spec.HeterogeneousTarget.Params {
if p.Target.TargetRef.Name != "" {
names = append(names, p.Target.TargetRef.Name)
}
}
if len(names) > 0 {
return strings.Join(names, ",")
}
}
return "<none>"
}
Comment thread
anirudh240 marked this conversation as resolved.
Comment on lines +417 to +435

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If binding.Spec.HomogeneousTarget.Target.TargetRef.Name is empty, the function currently returns an empty string "" instead of "<none>". We should check if the name is non-empty before returning it, allowing it to fall back to "<none>" for consistency with HeterogeneousTarget.

func getAutoscalingPolicyBindingTarget(binding workload.AutoscalingPolicyBinding) string {
	if binding.Spec.HomogeneousTarget != nil {
		if name := binding.Spec.HomogeneousTarget.Target.TargetRef.Name; name != "" {
			return name
		}
	}
	if binding.Spec.HeterogeneousTarget != nil {
		var names []string
		for _, p := range binding.Spec.HeterogeneousTarget.Params {
			if p.Target.TargetRef.Name != "" {
				names = append(names, p.Target.TargetRef.Name)
			}
		}
		if len(names) > 0 {
			return strings.Join(names, ",")
		}
	}
	return "<none>"
}


func getAutoscalingPolicyBindingMinMax(binding workload.AutoscalingPolicyBinding) (string, string) {
if binding.Spec.HomogeneousTarget != nil {
Comment thread
anirudh240 marked this conversation as resolved.
return fmt.Sprintf("%d", binding.Spec.HomogeneousTarget.MinReplicas),
fmt.Sprintf("%d", binding.Spec.HomogeneousTarget.MaxReplicas)
}
if binding.Spec.HeterogeneousTarget != nil {
var totalMin, totalMax int32
for _, p := range binding.Spec.HeterogeneousTarget.Params {
totalMin += p.MinReplicas
totalMax += p.MaxReplicas
}
return fmt.Sprintf("%d", totalMin), fmt.Sprintf("%d", totalMax)
}
return "-", "-"
}
Comment thread
anirudh240 marked this conversation as resolved.
Comment on lines +417 to +451

func runGetAutoscalingPolicies(cmd *cobra.Command, args []string) error {
client, err := getKthenaClient()
if err != nil {
Expand All @@ -420,18 +476,20 @@ func runGetAutoscalingPolicies(cmd *cobra.Command, args []string) error {
// Print header
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
if getAllNamespaces {
fmt.Fprintln(w, "NAMESPACE\tNAME\tAGE")
fmt.Fprintln(w, "NAMESPACE\tNAME\tMETRICS\tTOLERANCE\tAGE")
} else {
fmt.Fprintln(w, "NAME\tAGE")
fmt.Fprintln(w, "NAME\tMETRICS\tTOLERANCE\tAGE")
}

// Print AutoscalingPolicies
for _, policy := range policies.Items {
age := time.Since(policy.CreationTimestamp.Time).Truncate(time.Second)
metrics := len(policy.Spec.Metrics)
tolerance := fmt.Sprintf("%d%%", policy.Spec.TolerancePercent)
if getAllNamespaces {
fmt.Fprintf(w, "%s\t%s\t%s\n", policy.Namespace, policy.Name, age)
fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\n", policy.Namespace, policy.Name, metrics, tolerance, age)
} else {
fmt.Fprintf(w, "%s\t%s\n", policy.Name, age)
fmt.Fprintf(w, "%s\t%d\t%s\t%s\n", policy.Name, metrics, tolerance, age)
}
}

Expand Down Expand Up @@ -464,18 +522,21 @@ func runGetAutoscalingPolicyBindings(cmd *cobra.Command, args []string) error {
// Print header
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
if getAllNamespaces {
fmt.Fprintln(w, "NAMESPACE\tNAME\tAGE")
fmt.Fprintln(w, "NAMESPACE\tNAME\tPOLICY\tTARGET\tMIN\tMAX\tAGE")
} else {
fmt.Fprintln(w, "NAME\tAGE")
fmt.Fprintln(w, "NAME\tPOLICY\tTARGET\tMIN\tMAX\tAGE")
}

// Print AutoscalingPolicyBindings
for _, binding := range bindings.Items {
age := time.Since(binding.CreationTimestamp.Time).Truncate(time.Second)
policy := binding.Spec.PolicyRef.Name
target := getAutoscalingPolicyBindingTarget(binding)
min, max := getAutoscalingPolicyBindingMinMax(binding)
if getAllNamespaces {
fmt.Fprintf(w, "%s\t%s\t%s\n", binding.Namespace, binding.Name, age)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", binding.Namespace, binding.Name, policy, target, min, max, age)
} else {
fmt.Fprintf(w, "%s\t%s\n", binding.Name, age)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", binding.Name, policy, target, min, max, age)
}
}

Expand Down
107 changes: 107 additions & 0 deletions cli/kthena/cmd/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

workload "github.com/volcano-sh/kthena/pkg/apis/workload/v1alpha1"
Expand Down Expand Up @@ -202,3 +203,109 @@ func TestGetModelBoosterStatus(t *testing.T) {
})
}
}

func TestGetAutoscalingPolicyBindingTarget(t *testing.T) {
tests := []struct {
name string
binding workload.AutoscalingPolicyBinding
expected string
}{
{
name: "NoTarget",
binding: workload.AutoscalingPolicyBinding{},
expected: "<none>",
},
{
name: "HomogeneousTarget",
binding: workload.AutoscalingPolicyBinding{
Spec: workload.AutoscalingPolicyBindingSpec{
HomogeneousTarget: &workload.HomogeneousTarget{
Target: workload.Target{
TargetRef: corev1.ObjectReference{Name: "my-serving"},
},
},
},
},
expected: "my-serving",
},
{
name: "HomogeneousTargetEmptyName",
binding: workload.AutoscalingPolicyBinding{
Spec: workload.AutoscalingPolicyBindingSpec{
HomogeneousTarget: &workload.HomogeneousTarget{},
},
},
expected: "<none>",
},
{
name: "HeterogeneousTarget",
binding: workload.AutoscalingPolicyBinding{
Spec: workload.AutoscalingPolicyBindingSpec{
HeterogeneousTarget: &workload.HeterogeneousTarget{
Params: []workload.HeterogeneousTargetParam{
{Target: workload.Target{TargetRef: corev1.ObjectReference{Name: "serving-a"}}},
{Target: workload.Target{TargetRef: corev1.ObjectReference{Name: "serving-b"}}},
},
},
},
},
expected: "serving-a,serving-b",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, getAutoscalingPolicyBindingTarget(tt.binding))
})
}
}

func TestGetAutoscalingPolicyBindingMinMax(t *testing.T) {
tests := []struct {
name string
binding workload.AutoscalingPolicyBinding
expectedMin string
expectedMax string
}{
{
name: "NoTarget",
binding: workload.AutoscalingPolicyBinding{},
expectedMin: "-",
expectedMax: "-",
},
{
name: "HomogeneousTarget",
binding: workload.AutoscalingPolicyBinding{
Spec: workload.AutoscalingPolicyBindingSpec{
HomogeneousTarget: &workload.HomogeneousTarget{
MinReplicas: 2,
MaxReplicas: 10,
},
},
},
expectedMin: "2",
expectedMax: "10",
},
{
name: "HeterogeneousTarget",
binding: workload.AutoscalingPolicyBinding{
Spec: workload.AutoscalingPolicyBindingSpec{
HeterogeneousTarget: &workload.HeterogeneousTarget{
Params: []workload.HeterogeneousTargetParam{
{MinReplicas: 1, MaxReplicas: 5},
{MinReplicas: 2, MaxReplicas: 10},
},
},
},
},
expectedMin: "3",
expectedMax: "15",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
min, max := getAutoscalingPolicyBindingMinMax(tt.binding)
assert.Equal(t, tt.expectedMin, min)
assert.Equal(t, tt.expectedMax, max)
})
}
}
Loading