autoscaler/cluster-autoscaler/cloudprovider/externalgrpc/externalgrpc_cloud_provider...

508 lines
12 KiB
Go

/*
Copyright 2022 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 externalgrpc
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/protobuf/types/known/anypb"
apiv1 "k8s.io/api/core/v1"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/externalgrpc/protos"
)
func TestCloudProvider_NodeGroups(t *testing.T) {
client, m, teardown := setupTest(t)
defer teardown()
c := newExternalGrpcCloudProvider(client, nil)
m.On("Refresh", mock.Anything, mock.Anything).Return(&protos.RefreshResponse{}, nil)
// test answer with multiple node groups
m.On(
"NodeGroups", mock.Anything, mock.Anything,
).Return(
&protos.NodeGroupsResponse{
NodeGroups: []*protos.NodeGroup{
{Id: "1", MinSize: 10, MaxSize: 20, Debug: "test1"},
{Id: "2", MinSize: 30, MaxSize: 40, Debug: "test2"},
},
}, nil,
).Times(2)
ngs := c.NodeGroups()
assert.Equal(t, 2, len(ngs))
for _, ng := range ngs {
if ng.Id() == "1" {
assert.Equal(t, 10, ng.MinSize())
assert.Equal(t, 20, ng.MaxSize())
assert.Equal(t, "test1", ng.Debug())
} else if ng.Id() == "2" {
assert.Equal(t, 30, ng.MinSize())
assert.Equal(t, 40, ng.MaxSize())
assert.Equal(t, "test2", ng.Debug())
} else {
assert.Fail(t, "node group id not recognized")
}
}
// test cached answer
m.AssertNumberOfCalls(t, "NodeGroups", 1)
ngs = c.NodeGroups()
assert.Equal(t, 2, len(ngs))
m.AssertNumberOfCalls(t, "NodeGroups", 1)
// test answer after refresh to clear cached answer
err := c.Refresh()
assert.NoError(t, err)
ngs = c.NodeGroups()
assert.Equal(t, 2, len(ngs))
m.AssertNumberOfCalls(t, "NodeGroups", 2)
// test empty answer
err = c.Refresh()
assert.NoError(t, err)
m.On(
"NodeGroups", mock.Anything, mock.Anything,
).Return(
&protos.NodeGroupsResponse{
NodeGroups: make([]*protos.NodeGroup, 0),
}, nil,
).Once()
ngs = c.NodeGroups()
assert.NotNil(t, ngs)
assert.Equal(t, 0, len(ngs))
// test grpc error
err = c.Refresh()
assert.NoError(t, err)
m.On(
"NodeGroups", mock.Anything, mock.Anything,
).Return(
&protos.NodeGroupsResponse{
NodeGroups: make([]*protos.NodeGroup, 0),
},
fmt.Errorf("mock error"),
).Once()
ngs = c.NodeGroups()
assert.NotNil(t, ngs)
assert.Equal(t, 0, len(ngs))
}
func TestCloudProvider_NodeGroupForNode(t *testing.T) {
client, m, teardown := setupTest(t)
defer teardown()
c := newExternalGrpcCloudProvider(client, nil)
m.On("Refresh", mock.Anything, mock.Anything).Return(&protos.RefreshResponse{}, nil)
// test correct answer
m.On(
"NodeGroupForNode", mock.Anything, mock.MatchedBy(func(req *protos.NodeGroupForNodeRequest) bool {
return req.Node.Name == "node1"
}),
).Return(
&protos.NodeGroupForNodeResponse{
NodeGroup: &protos.NodeGroup{Id: "1", MinSize: 10, MaxSize: 20, Debug: "test1"},
}, nil,
)
m.On(
"NodeGroupForNode", mock.Anything, mock.MatchedBy(func(req *protos.NodeGroupForNodeRequest) bool {
return req.Node.Name == "node2"
}),
).Return(
&protos.NodeGroupForNodeResponse{
NodeGroup: &protos.NodeGroup{Id: "2", MinSize: 30, MaxSize: 40, Debug: "test2"},
}, nil,
)
apiv1Node1 := &apiv1.Node{}
apiv1Node1.Name = "node1"
apiv1Node1.Spec.ProviderID = "providerId://node1"
ng1, err := c.NodeGroupForNode(apiv1Node1)
assert.NoError(t, err)
assert.Equal(t, "1", ng1.Id())
assert.Equal(t, 10, ng1.MinSize())
assert.Equal(t, 20, ng1.MaxSize())
assert.Equal(t, "test1", ng1.Debug())
apiv1Node2 := &apiv1.Node{}
apiv1Node2.Name = "node2"
apiv1Node2.Spec.ProviderID = "providerId://node2"
ng2, err := c.NodeGroupForNode(apiv1Node2)
assert.NoError(t, err)
assert.Equal(t, "2", ng2.Id())
assert.Equal(t, 30, ng2.MinSize())
assert.Equal(t, 40, ng2.MaxSize())
assert.Equal(t, "test2", ng2.Debug())
// test cached answer
ng1, err = c.NodeGroupForNode(apiv1Node1)
assert.NoError(t, err)
assert.Equal(t, "1", ng1.Id())
m.AssertNumberOfCalls(t, "NodeGroupForNode", 2)
// test clear cache
err = c.Refresh()
assert.NoError(t, err)
ng1, err = c.NodeGroupForNode(apiv1Node1)
assert.NoError(t, err)
assert.Equal(t, "1", ng1.Id())
m.AssertNumberOfCalls(t, "NodeGroupForNode", 3)
//test no node group for node
err = c.Refresh()
assert.NoError(t, err)
m.On(
"NodeGroupForNode", mock.Anything, mock.MatchedBy(func(req *protos.NodeGroupForNodeRequest) bool {
return req.Node.Name == "node3"
}),
).Return(
&protos.NodeGroupForNodeResponse{
NodeGroup: &protos.NodeGroup{Id: ""},
}, nil,
)
apiv1Node3 := &apiv1.Node{}
apiv1Node3.Name = "node3"
apiv1Node3.Spec.ProviderID = "providerId://node3"
ng3, err := c.NodeGroupForNode(apiv1Node3)
assert.NoError(t, err)
assert.Equal(t, nil, ng3)
//test grpc error
err = c.Refresh()
assert.NoError(t, err)
m.On(
"NodeGroupForNode", mock.Anything, mock.MatchedBy(func(req *protos.NodeGroupForNodeRequest) bool {
return req.Node.Name == "node4"
}),
).Return(
&protos.NodeGroupForNodeResponse{
NodeGroup: &protos.NodeGroup{Id: ""},
},
fmt.Errorf("mock error"),
)
apiv1Node4 := &apiv1.Node{}
apiv1Node4.Name = "node4"
apiv1Node4.Spec.ProviderID = "providerId://node4"
_, err = c.NodeGroupForNode(apiv1Node4)
assert.Error(t, err)
//test error is not cached
_, err = c.NodeGroupForNode(apiv1Node4)
assert.Error(t, err)
m.AssertNumberOfCalls(t, "NodeGroupForNode", 6)
//test nil node param
_, err = c.NodeGroupForNode(nil)
assert.Error(t, err)
}
func TestCloudProvider_Pricing(t *testing.T) {
client, m, teardown := setupTest(t)
defer teardown()
c := newExternalGrpcCloudProvider(client, nil)
model, errPricing := c.Pricing()
assert.NoError(t, errPricing)
assert.NotNil(t, model)
// test correct NodePrice call
m.On(
"PricingNodePrice", mock.Anything, mock.MatchedBy(func(req *protos.PricingNodePriceRequest) bool {
return req.Node.Name == "node1"
}),
).Return(
&protos.PricingNodePriceResponse{Price: 100},
nil,
)
m.On(
"PricingNodePrice", mock.Anything, mock.MatchedBy(func(req *protos.PricingNodePriceRequest) bool {
return req.Node.Name == "node2"
}),
).Return(
&protos.PricingNodePriceResponse{Price: 200},
nil,
)
apiv1Node1 := &apiv1.Node{}
apiv1Node1.Name = "node1"
price, err := model.NodePrice(apiv1Node1, time.Time{}, time.Time{})
assert.NoError(t, err)
assert.Equal(t, float64(100), price)
apiv1Node2 := &apiv1.Node{}
apiv1Node2.Name = "node2"
price, err = model.NodePrice(apiv1Node2, time.Time{}, time.Time{})
assert.NoError(t, err)
assert.Equal(t, float64(200), price)
// test grpc error for NodePrice
m.On(
"PricingNodePrice", mock.Anything, mock.MatchedBy(func(req *protos.PricingNodePriceRequest) bool {
return req.Node.Name == "node3"
}),
).Return(
&protos.PricingNodePriceResponse{},
fmt.Errorf("mock error"),
)
apiv1Node3 := &apiv1.Node{}
apiv1Node3.Name = "node3"
_, err = model.NodePrice(apiv1Node3, time.Time{}, time.Time{})
assert.Error(t, err)
// test correct PodPrice call
m.On(
"PricingPodPrice", mock.Anything, mock.MatchedBy(func(req *protos.PricingPodPriceRequest) bool {
return req.Pod.Name == "pod1"
}),
).Return(
&protos.PricingPodPriceResponse{Price: 100},
nil,
)
m.On(
"PricingPodPrice", mock.Anything, mock.MatchedBy(func(req *protos.PricingPodPriceRequest) bool {
return req.Pod.Name == "pod2"
}),
).Return(
&protos.PricingPodPriceResponse{Price: 200},
nil,
)
apiv1Pod1 := &apiv1.Pod{}
apiv1Pod1.Name = "pod1"
price, err = model.PodPrice(apiv1Pod1, time.Time{}, time.Time{})
assert.NoError(t, err)
assert.Equal(t, float64(100), price)
apiv1Pod2 := &apiv1.Pod{}
apiv1Pod2.Name = "pod2"
price, err = model.PodPrice(apiv1Pod2, time.Time{}, time.Time{})
assert.NoError(t, err)
assert.Equal(t, float64(200), price)
// test grpc error for PodPrice
m.On(
"PricingPodPrice", mock.Anything, mock.MatchedBy(func(req *protos.PricingPodPriceRequest) bool {
return req.Pod.Name == "pod3"
}),
).Return(
&protos.PricingPodPriceResponse{},
fmt.Errorf("mock error"),
)
apiv1Pod3 := &apiv1.Pod{}
apiv1Pod3.Name = "pod3"
_, err = model.PodPrice(apiv1Pod3, time.Time{}, time.Time{})
assert.Error(t, err)
}
func TestCloudProvider_GPULabel(t *testing.T) {
client, m, teardown := setupTest(t)
defer teardown()
c := newExternalGrpcCloudProvider(client, nil)
m.On("Refresh", mock.Anything, mock.Anything).Return(&protos.RefreshResponse{}, nil)
// test correct call
m.On(
"GPULabel", mock.Anything, mock.Anything,
).Return(
&protos.GPULabelResponse{Label: "gpu_label"},
nil,
)
label := c.GPULabel()
assert.Equal(t, "gpu_label", label)
// test cache
label = c.GPULabel()
assert.Equal(t, "gpu_label", label)
m.AssertNumberOfCalls(t, "GPULabel", 1)
// test grpc error
client2, m2, teardown2 := setupTest(t)
defer teardown2()
c2 := newExternalGrpcCloudProvider(client2, nil)
m2.On("Refresh", mock.Anything, mock.Anything).Return(&protos.RefreshResponse{}, nil)
m2.On(
"GPULabel", mock.Anything, mock.Anything,
).Return(
&protos.GPULabelResponse{Label: "gpu_label"},
fmt.Errorf("mock error"),
)
label = c2.GPULabel()
assert.Equal(t, "", label)
//test error is not cached
label = c2.GPULabel()
assert.Equal(t, "", label)
m2.AssertNumberOfCalls(t, "GPULabel", 2)
}
func TestCloudProvider_GetAvailableGPUTypes(t *testing.T) {
client, m, teardown := setupTest(t)
defer teardown()
c := newExternalGrpcCloudProvider(client, nil)
m.On("Refresh", mock.Anything, mock.Anything).Return(&protos.RefreshResponse{}, nil)
// test correct call
pbGpuTypes := make(map[string]*anypb.Any)
pbGpuTypes["type1"] = &anypb.Any{}
pbGpuTypes["type2"] = &anypb.Any{}
m.On(
"GetAvailableGPUTypes", mock.Anything, mock.Anything,
).Return(
&protos.GetAvailableGPUTypesResponse{GpuTypes: pbGpuTypes},
nil,
)
gpuTypes := c.GetAvailableGPUTypes()
assert.NotNil(t, gpuTypes)
assert.NotNil(t, gpuTypes["type1"])
assert.NotNil(t, gpuTypes["type2"])
// test cache
gpuTypes = c.GetAvailableGPUTypes()
assert.NotNil(t, gpuTypes)
assert.NotNil(t, gpuTypes["type1"])
assert.NotNil(t, gpuTypes["type2"])
m.AssertNumberOfCalls(t, "GetAvailableGPUTypes", 1)
// test no gpu types
client2, m2, teardown2 := setupTest(t)
defer teardown2()
c2 := newExternalGrpcCloudProvider(client2, nil)
m2.On(
"GetAvailableGPUTypes", mock.Anything, mock.Anything,
).Return(
&protos.GetAvailableGPUTypesResponse{GpuTypes: nil},
nil,
)
gpuTypes = c2.GetAvailableGPUTypes()
assert.NotNil(t, gpuTypes)
assert.Equal(t, 0, len(gpuTypes))
// test grpc error
client3, m3, teardown3 := setupTest(t)
defer teardown3()
c3 := newExternalGrpcCloudProvider(client3, nil)
m3.On(
"GetAvailableGPUTypes", mock.Anything, mock.Anything,
).Return(
&protos.GetAvailableGPUTypesResponse{GpuTypes: nil},
fmt.Errorf("mock error"),
)
gpuTypes = c3.GetAvailableGPUTypes()
assert.Nil(t, gpuTypes)
// test error is not cahced
gpuTypes = c3.GetAvailableGPUTypes()
assert.Nil(t, gpuTypes)
m3.AssertNumberOfCalls(t, "GetAvailableGPUTypes", 2)
}
func TestCloudProvider_Cleanup(t *testing.T) {
client, m, teardown := setupTest(t)
defer teardown()
c := newExternalGrpcCloudProvider(client, nil)
// test correct call
m.On(
"Cleanup", mock.Anything, mock.Anything,
).Return(
&protos.CleanupResponse{},
nil,
).Once()
err := c.Cleanup()
assert.NoError(t, err)
// test grpc error
m.On(
"Cleanup", mock.Anything, mock.Anything,
).Return(
&protos.CleanupResponse{},
fmt.Errorf("mock error"),
).Once()
err = c.Cleanup()
assert.Error(t, err)
}
func TestCloudProvider_Refresh(t *testing.T) {
client, m, teardown := setupTest(t)
defer teardown()
c := newExternalGrpcCloudProvider(client, nil)
// test correct call
m.On(
"Refresh", mock.Anything, mock.Anything,
).Return(
&protos.RefreshResponse{},
nil,
).Once()
err := c.Refresh()
assert.NoError(t, err)
// test grpc error
m.On(
"Refresh", mock.Anything, mock.Anything,
).Return(
&protos.RefreshResponse{},
fmt.Errorf("mock error"),
).Once()
err = c.Refresh()
assert.Error(t, err)
}