autoscaler/cluster-autoscaler/cloudprovider/gce/mig_info_provider_test.go

1115 lines
35 KiB
Go

/*
Copyright 2021 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 gce
import (
"errors"
"fmt"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/assert"
gce "google.golang.org/api/compute/v1"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
"k8s.io/autoscaler/cluster-autoscaler/utils/units"
)
var (
errFetchMig = errors.New("fetch migs error")
errFetchMigInstances = errors.New("fetch mig instances error")
errFetchMigTargetSize = errors.New("fetch mig target size error")
errFetchMigBaseName = errors.New("fetch mig basename error")
errFetchMigTemplateName = errors.New("fetch mig template name error")
errFetchMigTemplate = errors.New("fetch mig template error")
errFetchMachineType = errors.New("fetch machine type error")
mig = &gceMig{
gceRef: GceRef{
Project: "project",
Zone: "us-test1",
Name: "mig",
},
}
)
type mockAutoscalingGceClient struct {
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
fetchMigTargetSize func(GceRef) (int64, error)
fetchMigBasename func(GceRef) (string, error)
fetchMigInstances func(GceRef) ([]cloudprovider.Instance, error)
fetchMigTemplateName func(GceRef) (string, error)
fetchMigTemplate func(GceRef, string) (*gce.InstanceTemplate, error)
fetchMachineType func(string, string) (*gce.MachineType, error)
}
func (client *mockAutoscalingGceClient) FetchMachineType(zone, machineName string) (*gce.MachineType, error) {
return client.fetchMachineType(zone, machineName)
}
func (client *mockAutoscalingGceClient) FetchMachineTypes(_ string) ([]*gce.MachineType, error) {
return nil, nil
}
func (client *mockAutoscalingGceClient) FetchAllMigs(zone string) ([]*gce.InstanceGroupManager, error) {
return client.fetchMigs(zone)
}
func (client *mockAutoscalingGceClient) FetchMigTargetSize(migRef GceRef) (int64, error) {
return client.fetchMigTargetSize(migRef)
}
func (client *mockAutoscalingGceClient) FetchMigBasename(migRef GceRef) (string, error) {
return client.fetchMigBasename(migRef)
}
func (client *mockAutoscalingGceClient) FetchMigInstances(migRef GceRef) ([]cloudprovider.Instance, error) {
return client.fetchMigInstances(migRef)
}
func (client *mockAutoscalingGceClient) FetchMigTemplateName(migRef GceRef) (string, error) {
return client.fetchMigTemplateName(migRef)
}
func (client *mockAutoscalingGceClient) FetchMigTemplate(migRef GceRef, templateName string) (*gce.InstanceTemplate, error) {
return client.fetchMigTemplate(migRef, templateName)
}
func (client *mockAutoscalingGceClient) FetchMigsWithName(_ string, _ *regexp.Regexp) ([]string, error) {
return nil, nil
}
func (client *mockAutoscalingGceClient) FetchZones(_ string) ([]string, error) {
return nil, nil
}
func (client *mockAutoscalingGceClient) FetchAvailableCpuPlatforms() (map[string][]string, error) {
return nil, nil
}
func (client *mockAutoscalingGceClient) ResizeMig(_ GceRef, _ int64) error {
return nil
}
func (client *mockAutoscalingGceClient) DeleteInstances(_ GceRef, _ []GceRef) error {
return nil
}
func (client *mockAutoscalingGceClient) CreateInstances(_ GceRef, _ string, _ int64, _ []string) error {
return nil
}
func TestMigInfoProviderGetMigForInstance(t *testing.T) {
instance := cloudprovider.Instance{
Id: "gce://project/us-test1/base-instance-name-abcd",
}
instanceRef, err := GceRefFromProviderId(instance.Id)
assert.Nil(t, err)
testCases := []struct {
name string
instanceRef GceRef
cache *GceCache
fetchMigInstances func(GceRef) ([]cloudprovider.Instance, error)
fetchMigBasename func(GceRef) (string, error)
expectedMig Mig
expectedErr error
expectedCachedMigRef GceRef
expectedCached bool
expectedCachedMigUnknown bool
}{
{
name: "instance mig ref and mig in cache",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instancesToMig: map[GceRef]GceRef{instanceRef: mig.GceRef()},
},
expectedMig: mig,
expectedCachedMigRef: mig.GceRef(),
expectedCached: true,
},
{
name: "only instance mig ref in cache",
cache: &GceCache{
instancesToMig: map[GceRef]GceRef{instanceRef: mig.GceRef()},
},
expectedMig: nil,
expectedErr: fmt.Errorf("instance %v belongs to unregistered mig %v", instanceRef, mig.GceRef()),
expectedCachedMigRef: mig.GceRef(),
expectedCached: true,
},
{
name: "instance mig unknown in cache",
cache: &GceCache{
instancesFromUnknownMig: map[GceRef]bool{instanceRef: true},
},
expectedCachedMigUnknown: true,
},
{
name: "mig from cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: map[GceRef][]cloudprovider.Instance{},
instancesToMig: map[GceRef]GceRef{},
migBaseNameCache: map[GceRef]string{mig.GceRef(): "base-instance-name"},
},
fetchMigInstances: fetchMigInstancesConst([]cloudprovider.Instance{instance}),
expectedMig: mig,
expectedCachedMigRef: mig.GceRef(),
expectedCached: true,
},
{
name: "mig and basename from cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: map[GceRef][]cloudprovider.Instance{},
instancesToMig: map[GceRef]GceRef{},
migBaseNameCache: map[GceRef]string{},
},
fetchMigInstances: fetchMigInstancesConst([]cloudprovider.Instance{instance}),
fetchMigBasename: fetchMigBasenameConst("base-instance-name"),
expectedMig: mig,
expectedCachedMigRef: mig.GceRef(),
expectedCached: true,
},
{
name: "unknown mig from cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: map[GceRef][]cloudprovider.Instance{},
instancesFromUnknownMig: map[GceRef]bool{},
migBaseNameCache: map[GceRef]string{mig.GceRef(): "base-instance-name"},
},
fetchMigInstances: fetchMigInstancesConst([]cloudprovider.Instance{}),
expectedCachedMigUnknown: true,
},
{
name: "no candidate mig during cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
migBaseNameCache: map[GceRef]string{mig.GceRef(): "different-base-instance-name"},
instancesToMig: map[GceRef]GceRef{},
},
fetchMigInstances: fetchMigInstancesConst([]cloudprovider.Instance{instance}),
},
{
name: "fetch instances error during cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instancesToMig: map[GceRef]GceRef{},
migBaseNameCache: map[GceRef]string{mig.GceRef(): "base-instance-name"},
},
fetchMigInstances: fetchMigInstancesFail,
expectedErr: errFetchMigInstances,
},
{
name: "fetch basename error during cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instancesToMig: map[GceRef]GceRef{},
migBaseNameCache: map[GceRef]string{},
},
fetchMigBasename: fetchMigBasenameFail,
expectedErr: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := &mockAutoscalingGceClient{
fetchMigBasename: tc.fetchMigBasename,
fetchMigInstances: tc.fetchMigInstances,
fetchMigs: fetchMigsConst(nil),
}
migLister := NewMigLister(tc.cache)
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
mig, err := provider.GetMigForInstance(instanceRef)
cachedMigRef, cached := tc.cache.GetMigForInstance(instanceRef)
cachedMigUnknown := tc.cache.IsMigUnknownForInstance(instanceRef)
assert.Equal(t, tc.expectedMig, mig)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedCachedMigRef, cachedMigRef)
assert.Equal(t, tc.expectedCached, cached)
assert.Equal(t, tc.expectedCachedMigUnknown, cachedMigUnknown)
})
}
}
func TestGetMigInstances(t *testing.T) {
instances := []cloudprovider.Instance{
{Id: "gce://project/us-test1/base-instance-name-abcd"},
{Id: "gce://project/us-test1/base-instance-name-efgh"},
}
testCases := []struct {
name string
cache *GceCache
fetchMigInstances func(GceRef) ([]cloudprovider.Instance, error)
expectedInstances []cloudprovider.Instance
expectedErr error
expectedCachedInstances []cloudprovider.Instance
expectedCached bool
}{
{
name: "instances in cache",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: map[GceRef][]cloudprovider.Instance{mig.GceRef(): instances},
},
expectedInstances: instances,
expectedCachedInstances: instances,
expectedCached: true,
},
{
name: "instances cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: map[GceRef][]cloudprovider.Instance{},
instancesToMig: map[GceRef]GceRef{},
},
fetchMigInstances: fetchMigInstancesConst(instances),
expectedInstances: instances,
expectedCachedInstances: instances,
expectedCached: true,
},
{
name: "error during instances cache fill",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: map[GceRef][]cloudprovider.Instance{},
},
fetchMigInstances: fetchMigInstancesFail,
expectedErr: errFetchMigInstances,
expectedCachedInstances: []cloudprovider.Instance{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := &mockAutoscalingGceClient{
fetchMigInstances: tc.fetchMigInstances,
}
migLister := NewMigLister(tc.cache)
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
instances, err := provider.GetMigInstances(mig.GceRef())
cachedInstances, cached := tc.cache.GetMigInstances(mig.GceRef())
assert.Equal(t, tc.expectedInstances, instances)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedCachedInstances, cachedInstances)
assert.Equal(t, tc.expectedCached, cached)
})
}
}
func TestRegenerateMigInstancesCache(t *testing.T) {
otherMig := &gceMig{
gceRef: GceRef{
Project: "project",
Zone: "us-test1",
Name: "other-mig",
},
}
instances := []cloudprovider.Instance{
{Id: "gce://project/us-test1/base-instance-name-abcd"},
{Id: "gce://project/us-test1/base-instance-name-efgh"},
}
otherInstances := []cloudprovider.Instance{
{Id: "gce://project/us-test1/other-base-instance-name-abcd"},
{Id: "gce://project/us-test1/other-base-instance-name-efgh"},
}
var instancesRefs, otherInstancesRefs []GceRef
for _, instance := range instances {
instanceRef, err := GceRefFromProviderId(instance.Id)
assert.Nil(t, err)
instancesRefs = append(instancesRefs, instanceRef)
}
for _, instance := range otherInstances {
instanceRef, err := GceRefFromProviderId(instance.Id)
assert.Nil(t, err)
otherInstancesRefs = append(otherInstancesRefs, instanceRef)
}
testCases := []struct {
name string
cache *GceCache
fetchMigInstances func(GceRef) ([]cloudprovider.Instance, error)
expectedErr error
expectedMigInstances map[GceRef][]cloudprovider.Instance
expectedInstancesToMig map[GceRef]GceRef
}{
{
name: "fill empty cache for one mig",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: map[GceRef][]cloudprovider.Instance{},
instancesToMig: map[GceRef]GceRef{},
},
fetchMigInstances: fetchMigInstancesConst(instances),
expectedMigInstances: map[GceRef][]cloudprovider.Instance{
mig.GceRef(): instances,
},
expectedInstancesToMig: map[GceRef]GceRef{
instancesRefs[0]: mig.GceRef(),
instancesRefs[1]: mig.GceRef(),
},
},
{
name: "fill empty cache for two migs",
cache: &GceCache{
migs: map[GceRef]Mig{
mig.GceRef(): mig,
otherMig.GceRef(): otherMig,
},
instances: map[GceRef][]cloudprovider.Instance{},
instancesToMig: map[GceRef]GceRef{},
},
fetchMigInstances: fetchMigInstancesMapping(map[GceRef][]cloudprovider.Instance{
mig.GceRef(): instances,
otherMig.GceRef(): otherInstances,
}),
expectedMigInstances: map[GceRef][]cloudprovider.Instance{
mig.GceRef(): instances,
otherMig.GceRef(): otherInstances,
},
expectedInstancesToMig: map[GceRef]GceRef{
instancesRefs[0]: mig.GceRef(),
instancesRefs[1]: mig.GceRef(),
otherInstancesRefs[0]: otherMig.GceRef(),
otherInstancesRefs[1]: otherMig.GceRef(),
},
},
{
name: "clear cache for removed mig",
cache: &GceCache{
migs: map[GceRef]Mig{
mig.GceRef(): mig,
},
instances: map[GceRef][]cloudprovider.Instance{
mig.GceRef(): instances,
otherMig.GceRef(): otherInstances,
},
instancesToMig: map[GceRef]GceRef{
instancesRefs[0]: mig.GceRef(),
instancesRefs[1]: mig.GceRef(),
otherInstancesRefs[0]: otherMig.GceRef(),
otherInstancesRefs[1]: otherMig.GceRef(),
},
},
fetchMigInstances: fetchMigInstancesMapping(map[GceRef][]cloudprovider.Instance{
mig.GceRef(): instances,
otherMig.GceRef(): otherInstances,
}),
expectedMigInstances: map[GceRef][]cloudprovider.Instance{
mig.GceRef(): instances,
},
expectedInstancesToMig: map[GceRef]GceRef{
instancesRefs[0]: mig.GceRef(),
instancesRefs[1]: mig.GceRef(),
},
},
{
name: "override cache for changed instances",
cache: &GceCache{
migs: map[GceRef]Mig{
mig.GceRef(): mig,
},
instances: map[GceRef][]cloudprovider.Instance{
mig.GceRef(): instances,
},
instancesToMig: map[GceRef]GceRef{
instancesRefs[0]: mig.GceRef(),
instancesRefs[1]: mig.GceRef(),
},
},
fetchMigInstances: fetchMigInstancesConst(otherInstances),
expectedMigInstances: map[GceRef][]cloudprovider.Instance{
mig.GceRef(): otherInstances,
},
expectedInstancesToMig: map[GceRef]GceRef{
otherInstancesRefs[0]: mig.GceRef(),
otherInstancesRefs[1]: mig.GceRef(),
},
},
{
name: "refill mig instances error",
cache: &GceCache{
migs: map[GceRef]Mig{
mig.GceRef(): mig,
},
instances: map[GceRef][]cloudprovider.Instance{},
instancesToMig: map[GceRef]GceRef{},
},
fetchMigInstances: fetchMigInstancesFail,
expectedErr: errFetchMigInstances,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := &mockAutoscalingGceClient{
fetchMigInstances: tc.fetchMigInstances,
}
migLister := NewMigLister(tc.cache)
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
err := provider.RegenerateMigInstancesCache()
assert.Equal(t, tc.expectedErr, err)
if tc.expectedErr == nil {
assert.Equal(t, tc.expectedMigInstances, tc.cache.instances)
assert.Equal(t, tc.expectedInstancesToMig, tc.cache.instancesToMig)
}
})
}
}
func TestGetMigTargetSize(t *testing.T) {
targetSize := int64(42)
instanceGroupManager := &gce.InstanceGroupManager{
Zone: mig.GceRef().Zone,
Name: mig.GceRef().Name,
TargetSize: targetSize,
}
testCases := []struct {
name string
cache *GceCache
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
fetchMigTargetSize func(GceRef) (int64, error)
expectedTargetSize int64
expectedErr error
}{
{
name: "target size in cache",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
migTargetSizeCache: map[GceRef]int64{mig.GceRef(): targetSize},
},
expectedTargetSize: targetSize,
},
{
name: "target size from cache fill",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{instanceGroupManager}),
expectedTargetSize: targetSize,
},
{
name: "cache fill without mig, fallback success",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
fetchMigTargetSize: fetchMigTargetSizeConst(targetSize),
expectedTargetSize: targetSize,
},
{
name: "cache fill failure, fallback success",
cache: emptyCache(),
fetchMigs: fetchMigsFail,
fetchMigTargetSize: fetchMigTargetSizeConst(targetSize),
expectedTargetSize: targetSize,
},
{
name: "cache fill without mig, fallback failure",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
fetchMigTargetSize: fetchMigTargetSizeFail,
expectedErr: errFetchMigTargetSize,
},
{
name: "cache fill failure, fallback failure",
cache: emptyCache(),
fetchMigs: fetchMigsFail,
fetchMigTargetSize: fetchMigTargetSizeFail,
expectedErr: errFetchMigTargetSize,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := &mockAutoscalingGceClient{
fetchMigs: tc.fetchMigs,
fetchMigTargetSize: tc.fetchMigTargetSize,
}
migLister := NewMigLister(tc.cache)
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
targetSize, err := provider.GetMigTargetSize(mig.GceRef())
cachedTargetSize, found := tc.cache.GetMigTargetSize(mig.GceRef())
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedErr == nil, found)
if tc.expectedErr == nil {
assert.Equal(t, tc.expectedTargetSize, targetSize)
assert.Equal(t, tc.expectedTargetSize, cachedTargetSize)
}
})
}
}
func TestGetMigBasename(t *testing.T) {
basename := "base-instance-name"
instanceGroupManager := &gce.InstanceGroupManager{
Zone: mig.GceRef().Zone,
Name: mig.GceRef().Name,
BaseInstanceName: basename,
}
testCases := []struct {
name string
cache *GceCache
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
fetchMigBasename func(GceRef) (string, error)
expectedBasename string
expectedErr error
}{
{
name: "basename in cache",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
migBaseNameCache: map[GceRef]string{mig.GceRef(): basename},
},
expectedBasename: basename,
},
{
name: "target size from cache fill",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{instanceGroupManager}),
expectedBasename: basename,
},
{
name: "cache fill without mig, fallback success",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
fetchMigBasename: fetchMigBasenameConst(basename),
expectedBasename: basename,
},
{
name: "cache fill failure, fallback success",
cache: emptyCache(),
fetchMigs: fetchMigsFail,
fetchMigBasename: fetchMigBasenameConst(basename),
expectedBasename: basename,
},
{
name: "cache fill without mig, fallback failure",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
fetchMigBasename: fetchMigBasenameFail,
expectedErr: errFetchMigBaseName,
},
{
name: "cache fill failure, fallback failure",
cache: emptyCache(),
fetchMigs: fetchMigsFail,
fetchMigBasename: fetchMigBasenameFail,
expectedErr: errFetchMigBaseName,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := &mockAutoscalingGceClient{
fetchMigs: tc.fetchMigs,
fetchMigBasename: tc.fetchMigBasename,
}
migLister := NewMigLister(tc.cache)
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
basename, err := provider.GetMigBasename(mig.GceRef())
cachedBasename, found := tc.cache.GetMigBasename(mig.GceRef())
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedErr == nil, found)
if tc.expectedErr == nil {
assert.Equal(t, tc.expectedBasename, basename)
assert.Equal(t, tc.expectedBasename, cachedBasename)
}
})
}
}
func TestGetMigInstanceTemplateName(t *testing.T) {
templateName := "template-name"
instanceGroupManager := &gce.InstanceGroupManager{
Zone: mig.GceRef().Zone,
Name: mig.GceRef().Name,
InstanceTemplate: templateName,
}
testCases := []struct {
name string
cache *GceCache
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
fetchMigTemplateName func(GceRef) (string, error)
expectedTemplateName string
expectedErr error
}{
{
name: "template name in cache",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instanceTemplateNameCache: map[GceRef]string{mig.GceRef(): templateName},
},
expectedTemplateName: templateName,
},
{
name: "target size from cache fill",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{instanceGroupManager}),
expectedTemplateName: templateName,
},
{
name: "cache fill without mig, fallback success",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
fetchMigTemplateName: fetchMigTemplateNameConst(templateName),
expectedTemplateName: templateName,
},
{
name: "cache fill failure, fallback success",
cache: emptyCache(),
fetchMigs: fetchMigsFail,
fetchMigTemplateName: fetchMigTemplateNameConst(templateName),
expectedTemplateName: templateName,
},
{
name: "cache fill without mig, fallback failure",
cache: emptyCache(),
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
fetchMigTemplateName: fetchMigTemplateNameFail,
expectedErr: errFetchMigTemplateName,
},
{
name: "cache fill failure, fallback failure",
cache: emptyCache(),
fetchMigs: fetchMigsFail,
fetchMigTemplateName: fetchMigTemplateNameFail,
expectedErr: errFetchMigTemplateName,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := &mockAutoscalingGceClient{
fetchMigs: tc.fetchMigs,
fetchMigTemplateName: tc.fetchMigTemplateName,
}
migLister := NewMigLister(tc.cache)
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
templateName, err := provider.GetMigInstanceTemplateName(mig.GceRef())
cachedTemplateName, found := tc.cache.GetMigInstanceTemplateName(mig.GceRef())
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedErr == nil, found)
if tc.expectedErr == nil {
assert.Equal(t, tc.expectedTemplateName, templateName)
assert.Equal(t, tc.expectedTemplateName, cachedTemplateName)
}
})
}
}
func TestGetMigInstanceTemplate(t *testing.T) {
templateName := "template-name"
template := &gce.InstanceTemplate{
Name: templateName,
Description: "instance template",
}
oldTemplate := &gce.InstanceTemplate{
Name: "old-template-name",
Description: "old instance template",
}
testCases := []struct {
name string
cache *GceCache
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
fetchMigTemplateName func(GceRef) (string, error)
fetchMigTemplate func(GceRef, string) (*gce.InstanceTemplate, error)
expectedTemplate *gce.InstanceTemplate
expectedCachedTemplate *gce.InstanceTemplate
expectedErr error
}{
{
name: "template in cache",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instanceTemplateNameCache: map[GceRef]string{mig.GceRef(): templateName},
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{mig.GceRef(): template},
},
expectedTemplate: template,
expectedCachedTemplate: template,
},
{
name: "cache without template, fetch success",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instanceTemplateNameCache: map[GceRef]string{mig.GceRef(): templateName},
instanceTemplatesCache: make(map[GceRef]*gce.InstanceTemplate),
},
fetchMigTemplate: fetchMigTemplateConst(template),
expectedTemplate: template,
expectedCachedTemplate: template,
},
{
name: "cache with old template, fetch success",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instanceTemplateNameCache: map[GceRef]string{mig.GceRef(): templateName},
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{mig.GceRef(): oldTemplate},
},
fetchMigTemplate: fetchMigTemplateConst(template),
expectedTemplate: template,
expectedCachedTemplate: template,
},
{
name: "cache without template, fetch failure",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instanceTemplateNameCache: map[GceRef]string{mig.GceRef(): templateName},
instanceTemplatesCache: make(map[GceRef]*gce.InstanceTemplate),
},
fetchMigTemplate: fetchMigTemplateFail,
expectedErr: errFetchMigTemplate,
},
{
name: "cache with old template, fetch failure",
cache: &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instanceTemplateNameCache: map[GceRef]string{mig.GceRef(): templateName},
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{mig.GceRef(): oldTemplate},
},
fetchMigTemplate: fetchMigTemplateFail,
expectedCachedTemplate: oldTemplate,
expectedErr: errFetchMigTemplate,
},
{
name: "template name fetch failure",
cache: emptyCache(),
fetchMigs: fetchMigsFail,
fetchMigTemplateName: fetchMigTemplateNameFail,
expectedErr: errFetchMigTemplateName,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := &mockAutoscalingGceClient{
fetchMigs: tc.fetchMigs,
fetchMigTemplateName: tc.fetchMigTemplateName,
fetchMigTemplate: tc.fetchMigTemplate,
}
migLister := NewMigLister(tc.cache)
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
template, err := provider.GetMigInstanceTemplate(mig.GceRef())
cachedTemplate, found := tc.cache.GetMigInstanceTemplate(mig.GceRef())
assert.Equal(t, tc.expectedErr, err)
if tc.expectedErr == nil {
assert.Equal(t, tc.expectedTemplate, template)
}
assert.Equal(t, tc.expectedCachedTemplate != nil, found)
if tc.expectedCachedTemplate != nil {
assert.Equal(t, tc.expectedCachedTemplate, cachedTemplate)
}
})
}
}
func TestGetMigMachineType(t *testing.T) {
knownZone := "us-cache1-a"
unknownZone := "us-nocache42-c"
testCases := []struct {
name string
machine string
zone string
fetchMachineType func(string, string) (*gce.MachineType, error)
cpu int64
memory int64
expectCpu int64
expectMemory int64
expectError bool
}{
{
name: "custom machine",
machine: "custom-8-2",
zone: unknownZone,
expectCpu: 8,
expectMemory: 2 * units.MiB,
},
{
name: "machine in cache",
machine: "n1-standard-1",
zone: knownZone,
cpu: 1,
memory: 2,
expectCpu: 1,
expectMemory: 2 * units.MiB,
},
{
name: "machine not in cache",
machine: "n1-standard-2",
zone: unknownZone,
fetchMachineType: fetchMachineTypeConst("n1-standard-2", 2, 3840),
expectCpu: 2,
expectMemory: 3840 * units.MiB,
},
{
name: "machine not in cache, request error",
machine: "n1-standard-1",
zone: unknownZone,
fetchMachineType: fetchMachineTypeFail,
expectError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mig := &gceMig{
gceRef: GceRef{
Project: "project",
Zone: tc.zone,
Name: "mig",
},
}
cache := &GceCache{
instanceTemplateNameCache: map[GceRef]string{mig.GceRef(): "template"},
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{
mig.GceRef(): {
Name: "template",
Properties: &gce.InstanceProperties{
MachineType: tc.machine,
},
},
},
machinesCache: map[MachineTypeKey]MachineType{
{knownZone, tc.machine}: {
Name: tc.machine,
CPU: tc.cpu,
Memory: tc.memory * units.MiB,
},
},
}
client := &mockAutoscalingGceClient{
fetchMachineType: tc.fetchMachineType,
}
migLister := NewMigLister(cache)
provider := NewCachingMigInfoProvider(cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second)
machine, err := provider.GetMigMachineType(mig.GceRef())
if tc.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.expectCpu, machine.CPU)
assert.Equal(t, tc.expectMemory, machine.Memory)
}
})
}
}
func TestMultipleGetMigInstanceCallsLimited(t *testing.T) {
mig := &gceMig{
gceRef: GceRef{
Project: "project",
Zone: "zone",
Name: "base-instance-name",
},
}
instance := cloudprovider.Instance{
Id: "gce://project/zone/base-instance-name-abcd",
}
instanceRef, err := GceRefFromProviderId(instance.Id)
assert.Nil(t, err)
instance2 := cloudprovider.Instance{
Id: "gce://project/zone/base-instance-name-abcd2",
}
instanceRef2, err := GceRefFromProviderId(instance2.Id)
assert.Nil(t, err)
now := time.Now()
for name, tc := range map[string]struct {
refreshRateDuration time.Duration
firstCallTime time.Time
secondCallTime time.Time
expectedCallsToFetchMigInstances int
}{
"0s refresh rate duration, refetch expected": {
refreshRateDuration: 0 * time.Second,
firstCallTime: now,
secondCallTime: now,
expectedCallsToFetchMigInstances: 2,
},
"5s refresh rate duration, 0.01s between calls, no refetch expected": {
refreshRateDuration: 5 * time.Second,
firstCallTime: now,
secondCallTime: now.Add(10 * time.Millisecond),
expectedCallsToFetchMigInstances: 1,
},
"0.01s refresh rate duration, 0.01s between calls, refetch expected": {
refreshRateDuration: 10 * time.Millisecond,
firstCallTime: now,
secondCallTime: now.Add(11 * time.Millisecond),
expectedCallsToFetchMigInstances: 2,
},
} {
t.Run(name, func(t *testing.T) {
cache := emptyCache()
cache.migs = map[GceRef]Mig{
mig.gceRef: mig,
}
cache.migBaseNameCache = map[GceRef]string{mig.GceRef(): "base-instance-name"}
callCounter := make(map[GceRef]int)
client := &mockAutoscalingGceClient{
fetchMigInstances: fetchMigInstancesWithCounter(nil, callCounter),
}
migLister := NewMigLister(cache)
ft := &fakeTime{}
provider := &cachingMigInfoProvider{
cache: cache,
migLister: migLister,
gceClient: client,
projectId: projectId,
concurrentGceRefreshes: 1,
migInstancesMinRefreshWaitTime: tc.refreshRateDuration,
migInstancesLastRefreshedInfo: make(map[string]time.Time),
timeProvider: ft,
}
ft.now = tc.firstCallTime
_, err = provider.GetMigForInstance(instanceRef)
assert.NoError(t, err)
ft.now = tc.secondCallTime
_, err = provider.GetMigForInstance(instanceRef2)
assert.NoError(t, err)
assert.Equal(t, tc.expectedCallsToFetchMigInstances, callCounter[mig.GceRef()])
})
}
}
type fakeTime struct {
now time.Time
}
func (f *fakeTime) Now() time.Time {
return f.now
}
func emptyCache() *GceCache {
return &GceCache{
migs: map[GceRef]Mig{mig.GceRef(): mig},
instances: make(map[GceRef][]cloudprovider.Instance),
migTargetSizeCache: make(map[GceRef]int64),
migBaseNameCache: make(map[GceRef]string),
instanceTemplateNameCache: make(map[GceRef]string),
instanceTemplatesCache: make(map[GceRef]*gce.InstanceTemplate),
instancesFromUnknownMig: make(map[GceRef]bool),
}
}
func fetchMigsFail(_ string) ([]*gce.InstanceGroupManager, error) {
return nil, errFetchMig
}
func fetchMigsConst(migs []*gce.InstanceGroupManager) func(string) ([]*gce.InstanceGroupManager, error) {
return func(string) ([]*gce.InstanceGroupManager, error) {
return migs, nil
}
}
func fetchMigInstancesFail(_ GceRef) ([]cloudprovider.Instance, error) {
return nil, errFetchMigInstances
}
func fetchMigInstancesConst(instances []cloudprovider.Instance) func(GceRef) ([]cloudprovider.Instance, error) {
return func(GceRef) ([]cloudprovider.Instance, error) {
return instances, nil
}
}
func fetchMigInstancesWithCounter(instances []cloudprovider.Instance, migCounter map[GceRef]int) func(GceRef) ([]cloudprovider.Instance, error) {
return func(ref GceRef) ([]cloudprovider.Instance, error) {
migCounter[ref] = migCounter[ref] + 1
return instances, nil
}
}
func fetchMigInstancesMapping(instancesMapping map[GceRef][]cloudprovider.Instance) func(GceRef) ([]cloudprovider.Instance, error) {
return func(migRef GceRef) ([]cloudprovider.Instance, error) {
return instancesMapping[migRef], nil
}
}
func fetchMigTargetSizeFail(_ GceRef) (int64, error) {
return 0, errFetchMigTargetSize
}
func fetchMigTargetSizeConst(targetSize int64) func(GceRef) (int64, error) {
return func(GceRef) (int64, error) {
return targetSize, nil
}
}
func fetchMigBasenameFail(_ GceRef) (string, error) {
return "", errFetchMigBaseName
}
func fetchMigBasenameConst(basename string) func(GceRef) (string, error) {
return func(GceRef) (string, error) {
return basename, nil
}
}
func fetchMigTemplateNameFail(_ GceRef) (string, error) {
return "", errFetchMigTemplateName
}
func fetchMigTemplateNameConst(templateName string) func(GceRef) (string, error) {
return func(GceRef) (string, error) {
return templateName, nil
}
}
func fetchMigTemplateFail(_ GceRef, _ string) (*gce.InstanceTemplate, error) {
return nil, errFetchMigTemplate
}
func fetchMigTemplateConst(template *gce.InstanceTemplate) func(GceRef, string) (*gce.InstanceTemplate, error) {
return func(GceRef, string) (*gce.InstanceTemplate, error) {
return template, nil
}
}
func fetchMachineTypeFail(_, _ string) (*gce.MachineType, error) {
return nil, errFetchMachineType
}
func fetchMachineTypeConst(name string, cpu int64, mem int64) func(string, string) (*gce.MachineType, error) {
return func(string, string) (*gce.MachineType, error) {
return &gce.MachineType{
Name: name,
GuestCpus: cpu,
MemoryMb: mem,
}, nil
}
}