autoscaler/cluster-autoscaler/cloudprovider/brightbox/brightbox_node_group_test.go

457 lines
15 KiB
Go

/*
Copyright 2020 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 brightbox
import (
"errors"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/brightbox/k8ssdk"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/brightbox/k8ssdk/mocks"
schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework"
//schedulerframework "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
)
const (
fakeMaxSize = 4
fakeMinSize = 1
fakeNodeGroupDescription = "1:4"
fakeDefaultSize = 3
fakeNodeGroupID = "grp-sda44"
fakeNodeGroupName = "auto.workers.k8s_fake.cluster.local"
fakeNodeGroupImageID = "img-testy"
fakeNodeGroupServerTypeID = "typ-zx45f"
fakeNodeGroupServerTypeHandle = "small"
fakeNodeGroupZoneID = "zon-testy"
fakeNodeGroupMainGroupID = "grp-y6cai"
fakeNodeGroupUserData = "fake userdata"
)
var (
fakeMapData = map[string]string{
"min": strconv.Itoa(fakeMinSize),
"max": strconv.Itoa(fakeMaxSize),
"server_group": fakeNodeGroupID,
"default_group": fakeNodeGroupMainGroupID,
"image": fakeNodeGroupImageID,
"type": fakeNodeGroupServerTypeID,
"zone": fakeNodeGroupZoneID,
"user_data": fakeNodeGroupUserData,
}
fakeMainGroupList = []string{fakeNodeGroupID, fakeNodeGroupMainGroupID}
fakeAdditionalGroupList = []string{"grp-abcde", "grp-testy", "grp-winga"}
fakeFullGroupList = append(fakeMainGroupList, fakeAdditionalGroupList...)
fakeAdditionalGroups = strings.Join(fakeAdditionalGroupList, ",")
fakeDefaultAdditionalGroups = fakeNodeGroupMainGroupID + "," + fakeAdditionalGroups
ErrFake = errors.New("fake API Error")
fakeInstances = []cloudprovider.Instance{
{
Id: "brightbox://srv-rp897",
Status: &cloudprovider.InstanceStatus{
State: cloudprovider.InstanceRunning,
},
},
{
Id: "brightbox://srv-lv426",
Status: &cloudprovider.InstanceStatus{
State: cloudprovider.InstanceRunning,
},
},
}
fakeTransitionInstances = []cloudprovider.Instance{
{
Id: "brightbox://srv-rp897",
Status: &cloudprovider.InstanceStatus{
State: cloudprovider.InstanceDeleting,
},
},
{
Id: "brightbox://srv-lv426",
Status: &cloudprovider.InstanceStatus{
State: cloudprovider.InstanceCreating,
},
},
}
ErrFakeInstances = []cloudprovider.Instance{
{
Id: "brightbox://srv-rp897",
Status: &cloudprovider.InstanceStatus{
ErrorInfo: &cloudprovider.InstanceErrorInfo{
ErrorClass: cloudprovider.OtherErrorClass,
ErrorCode: "unavailable",
ErrorMessage: "unavailable",
},
},
},
{
Id: "brightbox://srv-lv426",
Status: &cloudprovider.InstanceStatus{
ErrorInfo: &cloudprovider.InstanceErrorInfo{
ErrorClass: cloudprovider.OtherErrorClass,
ErrorCode: "inactive",
ErrorMessage: "inactive",
},
},
},
}
)
func TestMaxSize(t *testing.T) {
assert.Equal(t, makeFakeNodeGroup(t, nil).MaxSize(), fakeMaxSize)
}
func TestMinSize(t *testing.T) {
assert.Equal(t, makeFakeNodeGroup(t, nil).MinSize(), fakeMinSize)
}
func TestSize(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
nodeGroup := makeFakeNodeGroup(t, testclient)
fakeServerGroup := &fakeGroups()[0]
t.Run("TargetSize", func(t *testing.T) {
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil).Once()
size, err := nodeGroup.TargetSize()
assert.Equal(t, 2, size)
assert.NoError(t, err)
})
t.Run("TargetSizeFail", func(t *testing.T) {
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(nil, ErrFake).Once()
size, err := nodeGroup.TargetSize()
assert.Error(t, err)
assert.Zero(t, size)
})
t.Run("CurrentSize", func(t *testing.T) {
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil).Once()
size, err := nodeGroup.CurrentSize()
assert.Equal(t, 2, size)
assert.NoError(t, err)
})
t.Run("CurrentSizeFail", func(t *testing.T) {
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(nil, ErrFake).Once()
size, err := nodeGroup.CurrentSize()
assert.Error(t, err)
assert.Zero(t, size)
})
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil)
t.Run("DecreaseTargetSizePositive", func(t *testing.T) {
err := nodeGroup.DecreaseTargetSize(0)
assert.Error(t, err)
})
t.Run("DecreaseTargetSizeFail", func(t *testing.T) {
err := nodeGroup.DecreaseTargetSize(-1)
assert.Error(t, err)
})
mockclient.AssertExpectations(t)
}
func TestIncreaseSize(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
nodeGroup := makeFakeNodeGroup(t, testclient)
t.Run("Creating details set properly", func(t *testing.T) {
assert.Equal(t, fakeNodeGroupID, nodeGroup.id)
assert.Equal(t, fakeNodeGroupName, *nodeGroup.serverOptions.Name)
assert.Equal(t, fakeNodeGroupServerTypeID, nodeGroup.serverOptions.ServerType)
assert.Equal(t, fakeNodeGroupImageID, nodeGroup.serverOptions.Image)
assert.Equal(t, fakeNodeGroupZoneID, nodeGroup.serverOptions.Zone)
assert.ElementsMatch(t, []string{fakeNodeGroupMainGroupID, fakeNodeGroupID}, nodeGroup.serverOptions.ServerGroups)
assert.Equal(t, fakeNodeGroupUserData, *nodeGroup.serverOptions.UserData)
})
t.Run("Require positive delta", func(t *testing.T) {
err := nodeGroup.IncreaseSize(0)
assert.Error(t, err)
})
fakeServerGroup := &fakeGroups()[0]
t.Run("Don't exceed max size", func(t *testing.T) {
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil).Once()
err := nodeGroup.IncreaseSize(4)
assert.Error(t, err)
})
t.Run("Fail to create one new server", func(t *testing.T) {
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil).Once()
mockclient.On("CreateServer", mock.Anything).
Return(nil, ErrFake).Once()
err := nodeGroup.IncreaseSize(1)
assert.Error(t, err)
})
t.Run("Create one new server", func(t *testing.T) {
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil).Once()
mockclient.On("CreateServer", mock.Anything).
Return(nil, nil).Once()
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(&fakeServerGroupsPlusOne()[0], nil).Once()
err := nodeGroup.IncreaseSize(1)
assert.NoError(t, err)
})
}
func TestDeleteNodes(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
nodeGroup := makeFakeNodeGroup(t, testclient)
fakeServerGroup := &fakeGroups()[0]
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil).
On("Server", fakeServer).
Return(fakeServertesty(), nil)
t.Run("Empty Nodes", func(t *testing.T) {
err := nodeGroup.DeleteNodes(nil)
assert.NoError(t, err)
})
t.Run("Foreign Node", func(t *testing.T) {
err := nodeGroup.DeleteNodes([]*v1.Node{makeNode(fakeServer)})
assert.Error(t, err)
})
t.Run("Delete Node", func(t *testing.T) {
mockclient.On("Server", "srv-rp897").
Return(fakeServerrp897(), nil).Once().
On("Server", "srv-rp897").
Return(deletedFakeServer(fakeServerrp897()), nil).
Once().
On("DestroyServer", "srv-rp897").
Return(nil).Once()
err := nodeGroup.DeleteNodes([]*v1.Node{makeNode("srv-rp897")})
assert.NoError(t, err)
})
t.Run("Delete All Nodes", func(t *testing.T) {
truncateServers := mocks.ServerListReducer(fakeServerGroup)
mockclient.On("Server", "srv-rp897").
Return(fakeServerrp897(), nil).Once().
On("Server", "srv-rp897").
Return(deletedFakeServer(fakeServerrp897()), nil).
Once().
On("DestroyServer", "srv-rp897").
Return(nil).Once().Run(truncateServers)
err := nodeGroup.DeleteNodes([]*v1.Node{
makeNode("srv-rp897"),
makeNode("srv-lv426"),
})
assert.Error(t, err)
})
}
func TestExist(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
nodeGroup := makeFakeNodeGroup(t, testclient)
fakeServerGroup := &fakeGroups()[0]
t.Run("Find Group", func(t *testing.T) {
mockclient.On("ServerGroup", nodeGroup.Id()).
Return(fakeServerGroup, nil).Once()
assert.True(t, nodeGroup.Exist())
})
t.Run("Fail to Find Group", func(t *testing.T) {
mockclient.On("ServerGroup", nodeGroup.Id()).
Return(nil, serverNotFoundError(nodeGroup.Id()))
assert.False(t, nodeGroup.Exist())
})
mockclient.AssertExpectations(t)
}
func TestNodes(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
nodeGroup := makeFakeNodeGroup(t, testclient)
fakeServerGroup := &fakeGroups()[0]
mockclient.On("ServerGroup", fakeNodeGroupID).
Return(fakeServerGroup, nil)
t.Run("Both Active", func(t *testing.T) {
fakeServerGroup.Servers[0].Status = "active"
fakeServerGroup.Servers[1].Status = "active"
nodes, err := nodeGroup.Nodes()
require.NoError(t, err)
assert.ElementsMatch(t, fakeInstances, nodes)
})
t.Run("Creating and Deleting", func(t *testing.T) {
fakeServerGroup.Servers[0].Status = "creating"
fakeServerGroup.Servers[1].Status = "deleting"
nodes, err := nodeGroup.Nodes()
require.NoError(t, err)
assert.ElementsMatch(t, fakeTransitionInstances, nodes)
})
t.Run("Inactive and Unavailable", func(t *testing.T) {
fakeServerGroup.Servers[0].Status = "inactive"
fakeServerGroup.Servers[1].Status = "unavailable"
nodes, err := nodeGroup.Nodes()
require.NoError(t, err)
assert.ElementsMatch(t, ErrFakeInstances, nodes)
})
}
func TestTemplateNodeInfo(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
mockclient.On("ServerType", fakeNodeGroupServerTypeID).
Return(fakeServerTypezx45f(), nil)
obj, err := makeFakeNodeGroup(t, testclient).TemplateNodeInfo()
require.NoError(t, err)
assert.Equal(t, fakeResource(), obj.ToScheduler().Allocatable)
}
func TestNodeGroupErrors(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
emptyMapData := map[string]string{}
obj, err := makeNodeGroupFromAPIDetails(
fakeNodeGroupName,
emptyMapData,
fakeMinSize,
fakeMaxSize,
testclient,
)
assert.Equal(t, cloudprovider.ErrIllegalConfiguration, err)
assert.Nil(t, obj)
}
func TestMultipleGroups(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
t.Run("server group and multi default group", func(t *testing.T) {
multigroupData := map[string]string{
"min": strconv.Itoa(fakeMinSize),
"max": strconv.Itoa(fakeMaxSize),
"image": fakeNodeGroupImageID,
"type": fakeNodeGroupServerTypeID,
"zone": fakeNodeGroupZoneID,
"user_data": fakeNodeGroupUserData,
"server_group": fakeNodeGroupID,
"default_group": fakeDefaultAdditionalGroups,
}
obj, err := makeFakeNodeGroupFromMap(
testclient,
multigroupData,
)
require.NoError(t, err)
assert.ElementsMatch(t, fakeFullGroupList, obj.serverOptions.ServerGroups)
})
t.Run("server group and multi additional group", func(t *testing.T) {
multigroupData := map[string]string{
"min": strconv.Itoa(fakeMinSize),
"max": strconv.Itoa(fakeMaxSize),
"image": fakeNodeGroupImageID,
"type": fakeNodeGroupServerTypeID,
"zone": fakeNodeGroupZoneID,
"user_data": fakeNodeGroupUserData,
"server_group": fakeNodeGroupID,
"additional_groups": fakeDefaultAdditionalGroups,
}
obj, err := makeFakeNodeGroupFromMap(
testclient,
multigroupData,
)
require.NoError(t, err)
assert.ElementsMatch(t, fakeFullGroupList, obj.serverOptions.ServerGroups)
})
t.Run("server group, default group and multi additional group", func(t *testing.T) {
multigroupData := map[string]string{
"min": strconv.Itoa(fakeMinSize),
"max": strconv.Itoa(fakeMaxSize),
"image": fakeNodeGroupImageID,
"type": fakeNodeGroupServerTypeID,
"zone": fakeNodeGroupZoneID,
"user_data": fakeNodeGroupUserData,
"server_group": fakeNodeGroupID,
"default_group": fakeNodeGroupMainGroupID,
"additional_groups": fakeAdditionalGroups,
}
obj, err := makeFakeNodeGroupFromMap(
testclient,
multigroupData,
)
require.NoError(t, err)
assert.ElementsMatch(t, fakeFullGroupList, obj.serverOptions.ServerGroups)
})
t.Run("server group, default group and multi additional group with duplicates", func(t *testing.T) {
multigroupData := map[string]string{
"min": strconv.Itoa(fakeMinSize),
"max": strconv.Itoa(fakeMaxSize),
"image": fakeNodeGroupImageID,
"type": fakeNodeGroupServerTypeID,
"zone": fakeNodeGroupZoneID,
"user_data": fakeNodeGroupUserData,
"server_group": fakeNodeGroupID,
"default_group": fakeNodeGroupMainGroupID,
"additional_groups": fakeDefaultAdditionalGroups,
}
obj, err := makeFakeNodeGroupFromMap(
testclient,
multigroupData,
)
require.NoError(t, err)
assert.ElementsMatch(t, fakeFullGroupList, obj.serverOptions.ServerGroups)
})
}
func TestCreate(t *testing.T) {
obj, err := makeFakeNodeGroup(t, nil).Create()
assert.Equal(t, cloudprovider.ErrNotImplemented, err)
assert.Nil(t, obj)
}
func TestDelete(t *testing.T) {
assert.Equal(t, cloudprovider.ErrNotImplemented, makeFakeNodeGroup(t, nil).Delete())
}
func TestAutoprovisioned(t *testing.T) {
assert.False(t, makeFakeNodeGroup(t, nil).Autoprovisioned())
}
func fakeResource() *schedulerframework.Resource {
return &schedulerframework.Resource{
MilliCPU: 2000,
Memory: 1979711488,
EphemeralStorage: 80530636800,
AllowedPodNumber: 110,
}
}
func makeFakeNodeGroup(t *testing.T, brightboxCloudClient *k8ssdk.Cloud) *brightboxNodeGroup {
obj, err := makeFakeNodeGroupFromMap(
brightboxCloudClient,
fakeMapData,
)
require.NoError(t, err)
return obj
}
func makeFakeNodeGroupFromMap(brightboxCloudClient *k8ssdk.Cloud, mapData map[string]string) (*brightboxNodeGroup, error) {
return makeNodeGroupFromAPIDetails(
fakeNodeGroupName,
mapData,
fakeMinSize,
fakeMaxSize,
brightboxCloudClient,
)
}