Copyright(C) 2026-2026. Huawei Technologies Co.,Ltd. All rights reserved.
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 scaling
import (
"context"
"encoding/json"
"errors"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/smartystreets/goconvey/convey"
autoscalingv2 "k8s.io/api/autoscaling/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"ascend-common/common-utils/hwlog"
apiv1 "infer-operator/pkg/api/v1"
"infer-operator/pkg/common"
)
func init() {
hwlog.InitRunLogger(&hwlog.LogConfig{OnlyToStdout: true}, context.Background())
}
func buildTestScheme() *runtime.Scheme {
scheme := runtime.NewScheme()
apiv1.AddToScheme(scheme)
autoscalingv2.AddToScheme(scheme)
return scheme
}
func buildTestInstanceSet(name, namespace string, scalingPolicy *apiv1.ScalingPolicy) *apiv1.InstanceSet {
is := &apiv1.InstanceSet{
TypeMeta: metav1.TypeMeta{
APIVersion: "mindcluster.huawei.com/v1",
Kind: "InstanceSet",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: apiv1.InstanceSetSpec{
ScalingPolicy: scalingPolicy,
},
}
return is
}
func buildTestHPASpec(minReplicas, maxReplicas int32) *autoscalingv2.HorizontalPodAutoscalerSpec {
min := minReplicas
return &autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &min,
MaxReplicas: maxReplicas,
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: "cpu",
Target: autoscalingv2.MetricTarget{
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptrTo[int32](80),
},
},
},
},
}
}
func ptrTo[T any](v T) *T {
return &v
}
func TestNewScalingManager(t *testing.T) {
convey.Convey("TestNewScalingManager", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
convey.So(mgr, convey.ShouldNotBeNil)
convey.So(mgr.Client, convey.ShouldEqual, fakeClient)
convey.So(mgr.Scheme, convey.ShouldEqual, scheme)
})
}
func TestReconcileScalingResourceNilPolicy(t *testing.T) {
convey.Convey("TestReconcileScalingResource nil scaling policy", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", nil)
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldBeNil)
})
}
func TestReconcileScalingResourceUnsupportedType(t *testing.T) {
convey.Convey("TestReconcileScalingResource unsupported type", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: "Unsupported",
})
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(status.Ready, convey.ShouldBeFalse)
convey.So(status.Type, convey.ShouldEqual, "Unsupported")
convey.So(status.Message, convey.ShouldContainSubstring, "unsupported")
})
}
func TestReconcileHPAUnmarshalError(t *testing.T) {
convey.Convey("TestReconcileHPA unmarshal error", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: []byte("invalid-json")},
})
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(status.Ready, convey.ShouldBeFalse)
convey.So(status.Type, convey.ShouldEqual, common.ScalingPolicyTypeHPA)
convey.So(status.Message, convey.ShouldContainSubstring, "failed to unmarshal")
})
}
func TestReconcileHPAGetError(t *testing.T) {
convey.Convey("TestReconcileHPA get error", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
hpaSpec := buildTestHPASpec(1, 10)
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
patch := gomonkey.ApplyMethodFunc(fakeClient, "Get",
func(_ context.Context, _ types.NamespacedName, _ client.Object, _ ...client.GetOption) error {
return errors.New("get error")
})
defer patch.Reset()
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldNotBeNil)
convey.So(status, convey.ShouldBeNil)
})
}
func TestReconcileHPACreateSuccess(t *testing.T) {
convey.Convey("TestReconcileHPA create success", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
hpaSpec := buildTestHPASpec(1, 10)
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
convey.So(status.Type, convey.ShouldEqual, common.ScalingPolicyTypeHPA)
convey.So(status.Name, convey.ShouldEqual, "test-scaler")
convey.So(status.Message, convey.ShouldEqual, "HPA created successfully")
createdHPA := &autoscalingv2.HorizontalPodAutoscaler{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Name: "test-scaler", Namespace: "default",
}, createdHPA)
convey.So(err, convey.ShouldBeNil)
convey.So(createdHPA.Spec.MaxReplicas, convey.ShouldEqual, 10)
convey.So(createdHPA.Labels[ScalingResourceOwnedByInstanceSet], convey.ShouldEqual, "test")
})
}
func TestReconcileHPACreateControllerRefError(t *testing.T) {
convey.Convey("TestReconcileHPA create controller reference error", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
hpaSpec := buildTestHPASpec(1, 10)
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
patch := gomonkey.ApplyFuncReturn(controllerutil.SetControllerReference, errors.New("reference error"))
defer patch.Reset()
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldNotBeNil)
convey.So(err.Error(), convey.ShouldContainSubstring, "reference error")
convey.So(status, convey.ShouldBeNil)
})
}
func TestReconcileHPACreateError(t *testing.T) {
convey.Convey("TestReconcileHPA create error", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
hpaSpec := buildTestHPASpec(1, 10)
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
patch := gomonkey.ApplyMethodFunc(fakeClient, "Create",
func(_ context.Context, _ client.Object, _ ...client.CreateOption) error {
return errors.New("create error")
})
defer patch.Reset()
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldNotBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(status.Ready, convey.ShouldBeFalse)
convey.So(status.Message, convey.ShouldContainSubstring, "failed to create HPA")
})
}
func TestReconcileHPAUpdateNoChange(t *testing.T) {
convey.Convey("TestReconcileHPA update no change", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
hpaSpec := buildTestHPASpec(1, 10)
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
existingHPA := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler",
Namespace: "default",
Labels: buildScalingResourceLabels(instanceSet),
},
Spec: *hpaSpec,
}
err := fakeClient.Create(context.Background(), existingHPA)
convey.So(err, convey.ShouldBeNil)
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
convey.So(status.Message, convey.ShouldEqual, "HPA updated successfully")
})
}
func TestReconcileHPAUpdateWithChange(t *testing.T) {
convey.Convey("TestReconcileHPA update with change", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
oldSpec := buildTestHPASpec(1, 5)
existingHPA := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler",
Namespace: "default",
},
Spec: *oldSpec,
}
err := fakeClient.Create(context.Background(), existingHPA)
convey.So(err, convey.ShouldBeNil)
newSpec := buildTestHPASpec(2, 10)
raw, _ := json.Marshal(newSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
convey.So(status.Message, convey.ShouldEqual, "HPA updated successfully")
})
}
func TestReconcileHPAUpdateError(t *testing.T) {
convey.Convey("TestReconcileHPA update error", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
oldSpec := buildTestHPASpec(1, 5)
existingHPA := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler",
Namespace: "default",
},
Spec: *oldSpec,
}
err := fakeClient.Create(context.Background(), existingHPA)
convey.So(err, convey.ShouldBeNil)
newSpec := buildTestHPASpec(2, 10)
raw, _ := json.Marshal(newSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
patch := gomonkey.ApplyMethodFunc(fakeClient, "Update",
func(_ context.Context, _ client.Object, _ ...client.UpdateOption) error {
return errors.New("update error")
})
defer patch.Reset()
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldNotBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(status.Ready, convey.ShouldBeFalse)
convey.So(status.Message, convey.ShouldContainSubstring, "failed to update HPA")
})
}
func TestCleanupScalingResourceListError(t *testing.T) {
convey.Convey("TestCleanupScalingResource list error", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", nil)
patch := gomonkey.ApplyMethodFunc(fakeClient, "List",
func(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error {
return errors.New("list error")
})
defer patch.Reset()
status, err := mgr.cleanupScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldNotBeNil)
convey.So(status, convey.ShouldBeNil)
})
}
func TestCleanupScalingResourceDeleteError(t *testing.T) {
convey.Convey("TestCleanupScalingResource delete error", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", nil)
hpa := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler",
Namespace: "default",
Labels: map[string]string{
ScalingResourceOwnedByInstanceSet: "test",
},
},
}
err := fakeClient.Create(context.Background(), hpa)
convey.So(err, convey.ShouldBeNil)
patch := gomonkey.ApplyMethodFunc(fakeClient, "Delete",
func(_ context.Context, _ client.Object, _ ...client.DeleteOption) error {
return errors.New("delete error")
})
defer patch.Reset()
status, err := mgr.cleanupScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldNotBeNil)
convey.So(status, convey.ShouldBeNil)
})
}
func TestCleanupScalingResourceSuccess(t *testing.T) {
convey.Convey("TestCleanupScalingResource success", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", nil)
hpa := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler",
Namespace: "default",
Labels: map[string]string{
ScalingResourceOwnedByInstanceSet: "test",
},
},
}
err := fakeClient.Create(context.Background(), hpa)
convey.So(err, convey.ShouldBeNil)
status, err := mgr.cleanupScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldBeNil)
existingHPA := &autoscalingv2.HorizontalPodAutoscaler{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Name: "test-scaler", Namespace: "default",
}, existingHPA)
convey.So(err, convey.ShouldNotBeNil)
})
}
func TestCleanupScalingResourceNoHPA(t *testing.T) {
convey.Convey("TestCleanupScalingResource no HPA to clean", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", nil)
status, err := mgr.cleanupScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldBeNil)
})
}
func TestBuildScalingResourceName(t *testing.T) {
convey.Convey("TestBuildScalingResourceName", t, func() {
instanceSet := buildTestInstanceSet("myapp", "default", nil)
name := buildScalingResourceName(instanceSet)
convey.So(name, convey.ShouldEqual, "myapp-scaler")
})
}
func TestBuildScalingResourceLabels(t *testing.T) {
convey.Convey("TestBuildScalingResourceLabels", t, func() {
convey.Convey("with instance labels", func() {
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
"app": "myapp",
}
labels := buildScalingResourceLabels(instanceSet)
convey.So(labels[ScalingResourceOwnedByInstanceSet], convey.ShouldEqual, "test")
convey.So(labels[common.OperatorNameKey], convey.ShouldEqual, "")
convey.So(labels["app"], convey.ShouldEqual, "myapp")
})
convey.Convey("without instance labels", func() {
instanceSet := buildTestInstanceSet("test", "default", nil)
labels := buildScalingResourceLabels(instanceSet)
convey.So(labels[ScalingResourceOwnedByInstanceSet], convey.ShouldEqual, "test")
convey.So(labels[common.OperatorNameKey], convey.ShouldEqual, "")
})
})
}
func TestUpdateHPAMinReplicasChanged(t *testing.T) {
convey.Convey("TestUpdateHPA minReplicas changed", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
oldMin := int32(1)
existingHPA := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler",
Namespace: "default",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &oldMin,
MaxReplicas: 10,
},
}
err := fakeClient.Create(context.Background(), existingHPA)
convey.So(err, convey.ShouldBeNil)
newMin := int32(2)
desiredSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &newMin,
MaxReplicas: 10,
}
instanceSet := buildTestInstanceSet("test", "default", nil)
status, err := mgr.updateHPA(context.Background(), instanceSet, existingHPA, desiredSpec)
convey.So(err, convey.ShouldBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
convey.So(status.Message, convey.ShouldEqual, "HPA updated successfully")
})
}
func TestInjectMetricSelectorLabels(t *testing.T) {
convey.Convey("TestInjectMetricSelectorLabels", t, func() {
convey.Convey("no auto labels", func() {
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "qps",
},
},
},
},
}
instanceSet := buildTestInstanceSet("test", "default", nil)
injectMetricSelectorLabels(hpaSpec, instanceSet)
convey.So(hpaSpec.Metrics[0].External.Metric.Selector, convey.ShouldBeNil)
})
convey.Convey("inject labels into external metric", func() {
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "qps",
},
},
},
},
}
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "myservice-0",
common.InstanceSetNameLabelKey: "role1",
}
injectMetricSelectorLabels(hpaSpec, instanceSet)
convey.So(hpaSpec.Metrics[0].External.Metric.Selector, convey.ShouldNotBeNil)
convey.So(hpaSpec.Metrics[0].External.Metric.Selector.MatchLabels[common.InferServiceNameLabelKey], convey.ShouldEqual, "myservice-0")
convey.So(hpaSpec.Metrics[0].External.Metric.Selector.MatchLabels[common.RoleNameLabelKey], convey.ShouldEqual, "role1")
convey.So(hpaSpec.Metrics[0].External.Metric.Selector.MatchLabels[common.InferServiceSetNameLabelKey], convey.ShouldEqual, "myservice")
})
convey.Convey("skip non-external metric", func() {
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: "cpu",
},
},
},
}
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "myservice-0",
}
injectMetricSelectorLabels(hpaSpec, instanceSet)
convey.So(hpaSpec.Metrics[0].Resource, convey.ShouldNotBeNil)
})
convey.Convey("external metric with nil external", func() {
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: nil,
},
},
}
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "myservice-0",
}
injectMetricSelectorLabels(hpaSpec, instanceSet)
convey.So(hpaSpec.Metrics[0].External, convey.ShouldBeNil)
})
convey.Convey("preserve existing selector labels", func() {
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "qps",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
common.InferServiceNameLabelKey: "existing-value",
},
},
},
},
},
},
}
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "new-value",
common.InstanceSetNameLabelKey: "role1",
}
injectMetricSelectorLabels(hpaSpec, instanceSet)
convey.So(hpaSpec.Metrics[0].External.Metric.Selector.MatchLabels[common.InferServiceNameLabelKey], convey.ShouldEqual, "existing-value")
convey.So(hpaSpec.Metrics[0].External.Metric.Selector.MatchLabels[common.RoleNameLabelKey], convey.ShouldEqual, "role1")
})
})
}
func TestBuildAutoInjectedLabels(t *testing.T) {
convey.Convey("TestBuildAutoInjectedLabels", t, func() {
convey.Convey("no relevant labels", func() {
instanceSet := buildTestInstanceSet("test", "default", nil)
labels := buildAutoInjectedLabels(instanceSet)
convey.So(len(labels), convey.ShouldEqual, 0)
})
convey.Convey("with InferServiceNameLabelKey", func() {
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "myservice-0",
}
labels := buildAutoInjectedLabels(instanceSet)
convey.So(labels[common.InferServiceNameLabelKey], convey.ShouldEqual, "myservice-0")
convey.So(labels[common.InferServiceSetNameLabelKey], convey.ShouldEqual, "myservice")
})
convey.Convey("with InferServiceNameLabelKey insufficient split", func() {
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "single",
}
labels := buildAutoInjectedLabels(instanceSet)
convey.So(labels[common.InferServiceNameLabelKey], convey.ShouldEqual, "single")
_, exists := labels[common.InferServiceSetNameLabelKey]
convey.So(exists, convey.ShouldBeFalse)
})
convey.Convey("with InstanceSetNameLabelKey", func() {
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InstanceSetNameLabelKey: "role1",
}
labels := buildAutoInjectedLabels(instanceSet)
convey.So(labels[common.RoleNameLabelKey], convey.ShouldEqual, "role1")
})
convey.Convey("with both labels", func() {
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "myservice-0",
common.InstanceSetNameLabelKey: "role1",
}
labels := buildAutoInjectedLabels(instanceSet)
convey.So(labels[common.InferServiceNameLabelKey], convey.ShouldEqual, "myservice-0")
convey.So(labels[common.InferServiceSetNameLabelKey], convey.ShouldEqual, "myservice")
convey.So(labels[common.RoleNameLabelKey], convey.ShouldEqual, "role1")
})
})
}
func TestCreateHPAWithAnnotations(t *testing.T) {
convey.Convey("TestCreateHPA with annotations", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", nil)
instanceSet.Annotations = map[string]string{
"annotation-key": "annotation-value",
}
hpaSpec := buildTestHPASpec(1, 5)
status, err := mgr.createHPA(context.Background(), instanceSet, "test-scaler", hpaSpec)
convey.So(err, convey.ShouldBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
createdHPA := &autoscalingv2.HorizontalPodAutoscaler{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Name: "test-scaler", Namespace: "default",
}, createdHPA)
convey.So(err, convey.ShouldBeNil)
convey.So(createdHPA.Annotations["annotation-key"], convey.ShouldEqual, "annotation-value")
})
}
func TestReconcileHPAWithExternalMetricAndLabels(t *testing.T) {
convey.Convey("TestReconcileHPA with external metric and labels injection", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
min := int32(1)
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &min,
MaxReplicas: 10,
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "qps",
},
},
},
},
}
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "myservice-0",
common.InstanceSetNameLabelKey: "role1",
}
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
createdHPA := &autoscalingv2.HorizontalPodAutoscaler{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Name: "test-scaler", Namespace: "default",
}, createdHPA)
convey.So(err, convey.ShouldBeNil)
convey.So(createdHPA.Spec.Metrics[0].External.Metric.Selector, convey.ShouldNotBeNil)
convey.So(createdHPA.Spec.Metrics[0].External.Metric.Selector.MatchLabels[common.InferServiceNameLabelKey], convey.ShouldEqual, "myservice-0")
convey.So(createdHPA.Spec.Metrics[0].External.Metric.Selector.MatchLabels[common.RoleNameLabelKey], convey.ShouldEqual, "role1")
})
}
func TestReconcileHPAWithEmptyMetrics(t *testing.T) {
convey.Convey("TestReconcileHPA with empty metrics", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
min := int32(1)
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &min,
MaxReplicas: 10,
Metrics: []autoscalingv2.MetricSpec{},
}
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
})
}
func TestReconcileHPAWithZeroMaxReplicas(t *testing.T) {
convey.Convey("TestReconcileHPA with zero max replicas", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
min := int32(0)
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &min,
MaxReplicas: 0,
Metrics: []autoscalingv2.MetricSpec{},
}
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
createdHPA := &autoscalingv2.HorizontalPodAutoscaler{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Name: "test-scaler", Namespace: "default",
}, createdHPA)
convey.So(err, convey.ShouldBeNil)
convey.So(createdHPA.Spec.MaxReplicas, convey.ShouldEqual, 0)
})
}
func TestReconcileHPAWithLargeMaxReplicas(t *testing.T) {
convey.Convey("TestReconcileHPA with large max replicas", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
min := int32(1)
maxReplicas := int32(100000)
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &min,
MaxReplicas: maxReplicas,
Metrics: []autoscalingv2.MetricSpec{},
}
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
createdHPA := &autoscalingv2.HorizontalPodAutoscaler{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Name: "test-scaler", Namespace: "default",
}, createdHPA)
convey.So(err, convey.ShouldBeNil)
convey.So(createdHPA.Spec.MaxReplicas, convey.ShouldEqual, maxReplicas)
})
}
func TestCleanupScalingResourceWithMultipleHPAs(t *testing.T) {
convey.Convey("TestCleanupScalingResource with multiple HPAs", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
instanceSet := buildTestInstanceSet("test", "default", nil)
hpa1 := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler-1",
Namespace: "default",
Labels: map[string]string{
ScalingResourceOwnedByInstanceSet: "test",
},
},
}
hpa2 := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "test-scaler-2",
Namespace: "default",
Labels: map[string]string{
ScalingResourceOwnedByInstanceSet: "test",
},
},
}
err := fakeClient.Create(context.Background(), hpa1)
convey.So(err, convey.ShouldBeNil)
err = fakeClient.Create(context.Background(), hpa2)
convey.So(err, convey.ShouldBeNil)
status, err := mgr.cleanupScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldBeNil)
hpaList := &autoscalingv2.HorizontalPodAutoscalerList{}
err = fakeClient.List(context.Background(), hpaList)
convey.So(err, convey.ShouldBeNil)
convey.So(len(hpaList.Items), convey.ShouldEqual, 0)
})
}
func TestReconcileHPAWithMultipleMetrics(t *testing.T) {
convey.Convey("TestReconcileHPA with multiple metrics", t, func() {
scheme := buildTestScheme()
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
mgr := NewScalingManager(fakeClient, scheme)
min := int32(1)
hpaSpec := &autoscalingv2.HorizontalPodAutoscalerSpec{
MinReplicas: &min,
MaxReplicas: 10,
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: "cpu",
Target: autoscalingv2.MetricTarget{
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptrTo[int32](80),
},
},
},
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: "memory",
Target: autoscalingv2.MetricTarget{
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptrTo[int32](70),
},
},
},
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "qps",
},
},
},
},
}
raw, _ := json.Marshal(hpaSpec)
instanceSet := buildTestInstanceSet("test", "default", &apiv1.ScalingPolicy{
Type: common.ScalingPolicyTypeHPA,
Spec: runtime.RawExtension{Raw: raw},
})
instanceSet.Labels = map[string]string{
common.InferServiceNameLabelKey: "myservice-0",
common.InstanceSetNameLabelKey: "role1",
}
status, err := mgr.ReconcileScalingResource(context.Background(), instanceSet)
convey.So(err, convey.ShouldBeNil)
convey.So(status.Ready, convey.ShouldBeTrue)
createdHPA := &autoscalingv2.HorizontalPodAutoscaler{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Name: "test-scaler", Namespace: "default",
}, createdHPA)
convey.So(err, convey.ShouldBeNil)
convey.So(len(createdHPA.Spec.Metrics), convey.ShouldEqual, 3)
})
}