Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

295 lines
8.6 KiB
Go

// Copyright 2025 Alibaba Group Holding Ltd.
//
// 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 assign
import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
sandboxv1alpha1 "github.com/alibaba/OpenSandbox/sandbox-k8s/apis/sandbox/v1alpha1"
)
func makeSBX(name string, image string) *sandboxv1alpha1.BatchSandbox {
return &sandboxv1alpha1.BatchSandbox{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: sandboxv1alpha1.BatchSandboxSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: image}},
},
},
},
}
}
func makePool(name string, image string, total, allocated int32) *sandboxv1alpha1.Pool {
return &sandboxv1alpha1.Pool{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: sandboxv1alpha1.PoolSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: image}},
},
},
CapacitySpec: sandboxv1alpha1.CapacitySpec{
PoolMax: total,
},
},
Status: sandboxv1alpha1.PoolStatus{
Total: total,
Allocated: allocated,
},
}
}
func TestDefaultAssigner_AssignPool(t *testing.T) {
ctx := context.Background()
t.Run("single pool passes all predicates", func(t *testing.T) {
assigner := NewDefaultAssigner(DefaultProfile())
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{makePool("pool-1", "nginx", 10, 5)}
name, err := assigner.AssignPool(ctx, sbx, pools)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-1" {
t.Errorf("AssignPool() = %q, want %q", name, "pool-1")
}
})
t.Run("multiple pools - higher score wins", func(t *testing.T) {
profile := &Profile{
Name: "most-allocated",
Plugins: PluginsSpec{
Predicate: []string{"image"},
Score: []ScoreSpec{{Name: "resbalance", Weight: 100}},
},
PluginConf: []PluginConf{
{Name: "resbalance", Args: map[string]interface{}{"strategy": "MostAllocated"}},
},
}
assigner := NewDefaultAssigner(profile)
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{
makePool("pool-a", "nginx", 10, 2),
makePool("pool-b", "nginx", 10, 8),
}
name, err := assigner.AssignPool(ctx, sbx, pools)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-b" {
t.Errorf("AssignPool() = %q, want %q", name, "pool-b")
}
})
t.Run("all pools filtered out - error", func(t *testing.T) {
assigner := NewDefaultAssigner(DefaultProfile())
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{makePool("pool-1", "redis", 10, 5)}
_, err := assigner.AssignPool(ctx, sbx, pools)
if err == nil {
t.Fatal("expected error, got nil")
}
})
t.Run("tie-breaking by name", func(t *testing.T) {
profile := &Profile{
Name: "tie-break",
Plugins: PluginsSpec{
Predicate: []string{"image"},
Score: []ScoreSpec{{Name: "resbalance", Weight: 100}},
},
PluginConf: []PluginConf{
{Name: "resbalance", Args: map[string]interface{}{"strategy": "MostAllocated"}},
},
}
assigner := NewDefaultAssigner(profile)
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{
makePool("pool-b", "nginx", 10, 5),
makePool("pool-a", "nginx", 10, 5),
}
name, err := assigner.AssignPool(ctx, sbx, pools)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-a" {
t.Errorf("AssignPool() = %q, want %q (lexicographic first)", name, "pool-a")
}
})
t.Run("no scorers - pick by name", func(t *testing.T) {
profile := &Profile{
Name: "no-scorers",
Plugins: PluginsSpec{Predicate: []string{"image"}},
}
assigner := NewDefaultAssigner(profile)
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{
makePool("pool-b", "nginx", 10, 5),
makePool("pool-a", "nginx", 10, 5),
}
name, err := assigner.AssignPool(ctx, sbx, pools)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-a" {
t.Errorf("AssignPool() = %q, want %q", name, "pool-a")
}
})
t.Run("empty pool list - error", func(t *testing.T) {
assigner := NewDefaultAssigner(DefaultProfile())
sbx := makeSBX("sbx-1", "nginx")
_, err := assigner.AssignPool(ctx, sbx, nil)
if err == nil {
t.Fatal("expected error, got nil")
}
})
t.Run("fully allocated pool filtered by capacity predicate", func(t *testing.T) {
assigner := NewDefaultAssigner(DefaultProfile())
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{
makePool("pool-full", "nginx", 10, 10),
makePool("pool-avail", "nginx", 10, 5),
}
name, err := assigner.AssignPool(ctx, sbx, pools)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-avail" {
t.Errorf("AssignPool() = %q, want %q (full pool should be filtered)", name, "pool-avail")
}
})
t.Run("MostAllocated skips fully allocated pool", func(t *testing.T) {
profile := &Profile{
Name: "most-allocated-cap",
Plugins: PluginsSpec{
Predicate: []string{"capacity", "image"},
Score: []ScoreSpec{{Name: "resbalance", Weight: 100}},
},
PluginConf: []PluginConf{
{Name: "resbalance", Args: map[string]interface{}{"strategy": "MostAllocated"}},
},
}
assigner := NewDefaultAssigner(profile)
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{
makePool("pool-full", "nginx", 10, 10),
makePool("pool-partial", "nginx", 10, 8),
}
name, err := assigner.AssignPool(ctx, sbx, pools)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-partial" {
t.Errorf("AssignPool() = %q, want %q (full pool should be filtered even with MostAllocated)", name, "pool-partial")
}
})
t.Run("all pools fully allocated - error", func(t *testing.T) {
assigner := NewDefaultAssigner(DefaultProfile())
sbx := makeSBX("sbx-1", "nginx")
pools := []*sandboxv1alpha1.Pool{
makePool("pool-a", "nginx", 10, 10),
makePool("pool-b", "nginx", 5, 5),
}
_, err := assigner.AssignPool(ctx, sbx, pools)
if err == nil {
t.Fatal("expected error when all pools are full, got nil")
}
})
t.Run("scale-from-zero pool is eligible", func(t *testing.T) {
assigner := NewDefaultAssigner(DefaultProfile())
sbx := makeSBX("sbx-1", "nginx")
zeroPool := &sandboxv1alpha1.Pool{
ObjectMeta: metav1.ObjectMeta{Name: "pool-zero"},
Spec: sandboxv1alpha1.PoolSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: "nginx"}},
},
},
CapacitySpec: sandboxv1alpha1.CapacitySpec{PoolMax: 5},
},
Status: sandboxv1alpha1.PoolStatus{Total: 0, Allocated: 0},
}
pools := []*sandboxv1alpha1.Pool{makePool("pool-full", "nginx", 3, 3), zeroPool}
name, err := assigner.AssignPool(ctx, sbx, pools)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-zero" {
t.Errorf("AssignPool() = %q, want %q (scale-from-zero pool should be selected)", name, "pool-zero")
}
})
t.Run("capacity requires room for all desired replicas", func(t *testing.T) {
assigner := NewDefaultAssigner(DefaultProfile())
sbx := makeSBX("sbx-1", "nginx")
sbx.Spec.Replicas = int32Ptr(2)
smallPool := &sandboxv1alpha1.Pool{
ObjectMeta: metav1.ObjectMeta{Name: "pool-small"},
Spec: sandboxv1alpha1.PoolSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: "nginx"}},
},
},
CapacitySpec: sandboxv1alpha1.CapacitySpec{PoolMax: 1},
},
Status: sandboxv1alpha1.PoolStatus{Total: 1, Allocated: 0},
}
bigPool := &sandboxv1alpha1.Pool{
ObjectMeta: metav1.ObjectMeta{Name: "pool-big"},
Spec: sandboxv1alpha1.PoolSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: "nginx"}},
},
},
CapacitySpec: sandboxv1alpha1.CapacitySpec{PoolMax: 10},
},
Status: sandboxv1alpha1.PoolStatus{Total: 10, Allocated: 5},
}
name, err := assigner.AssignPool(ctx, sbx, []*sandboxv1alpha1.Pool{smallPool, bigPool})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "pool-big" {
t.Errorf("AssignPool() = %q, want %q (pool with PoolMax<replicas must be filtered)", name, "pool-big")
}
})
}