2038 lines
68 KiB
Go
2038 lines
68 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")
|
|
errFetchListManagedInstancesResults = errors.New("fetch ListManagedInstancesResults error")
|
|
errFetchMachineType = errors.New("fetch machine type error")
|
|
|
|
mig = &gceMig{
|
|
gceRef: GceRef{
|
|
Project: "project",
|
|
Zone: "us-test1",
|
|
Name: "mig",
|
|
},
|
|
}
|
|
mig1 = &gceMig{
|
|
gceRef: GceRef{
|
|
Project: "myprojid",
|
|
Zone: "myzone1",
|
|
Name: "mig1",
|
|
},
|
|
}
|
|
mig2 = &gceMig{
|
|
gceRef: GceRef{
|
|
Project: "myprojid",
|
|
Zone: "myzone2",
|
|
Name: "mig2",
|
|
},
|
|
}
|
|
|
|
instance1 = GceInstance{
|
|
Instance: cloudprovider.Instance{
|
|
Id: "gce://myprojid/myzone1/test-instance-1",
|
|
Status: &cloudprovider.InstanceStatus{State: cloudprovider.InstanceRunning},
|
|
},
|
|
Igm: mig1.GceRef(),
|
|
}
|
|
instance2 = GceInstance{
|
|
Instance: cloudprovider.Instance{
|
|
Id: "gce://myprojid/myzone1/test-instance-2",
|
|
Status: &cloudprovider.InstanceStatus{State: cloudprovider.InstanceCreating},
|
|
},
|
|
Igm: mig1.GceRef(),
|
|
}
|
|
instance3 = GceInstance{
|
|
Instance: cloudprovider.Instance{
|
|
Id: "gce://myprojid/myzone2/test-instance-3",
|
|
Status: &cloudprovider.InstanceStatus{State: cloudprovider.InstanceRunning},
|
|
},
|
|
Igm: mig2.GceRef(),
|
|
}
|
|
|
|
instance4 = GceInstance{
|
|
Instance: cloudprovider.Instance{
|
|
Id: "gce://myprojid/myzone2/test-instance-4",
|
|
Status: &cloudprovider.InstanceStatus{State: cloudprovider.InstanceRunning},
|
|
},
|
|
Igm: GceRef{},
|
|
}
|
|
|
|
instance5 = GceInstance{
|
|
Instance: cloudprovider.Instance{
|
|
Id: "gce://myprojid/myzone2/test-instance-5",
|
|
Status: &cloudprovider.InstanceStatus{State: cloudprovider.InstanceRunning},
|
|
},
|
|
Igm: GceRef{},
|
|
}
|
|
|
|
instance6 = GceInstance{
|
|
Instance: cloudprovider.Instance{
|
|
Id: "gce://myprojid/myzone2/test-instance-6",
|
|
Status: &cloudprovider.InstanceStatus{State: cloudprovider.InstanceRunning},
|
|
},
|
|
Igm: mig2.GceRef(),
|
|
}
|
|
)
|
|
|
|
type mockAutoscalingGceClient struct {
|
|
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
|
|
fetchAllInstances func(project, zone string, filter string) ([]GceInstance, error)
|
|
fetchMigTargetSize func(GceRef) (int64, error)
|
|
fetchMigBasename func(GceRef) (string, error)
|
|
fetchMigInstances func(GceRef) ([]GceInstance, error)
|
|
fetchMigTemplateName func(GceRef) (InstanceTemplateName, error)
|
|
fetchMigTemplate func(GceRef, string, bool) (*gce.InstanceTemplate, error)
|
|
fetchMachineType func(string, string) (*gce.MachineType, error)
|
|
fetchListManagedInstancesResults func(GceRef) (string, 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) FetchAllInstances(project, zone string, filter string) ([]GceInstance, error) {
|
|
return client.fetchAllInstances(project, zone, filter)
|
|
}
|
|
|
|
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) FetchListManagedInstancesResults(migRef GceRef) (string, error) {
|
|
return client.fetchListManagedInstancesResults(migRef)
|
|
}
|
|
|
|
func (client *mockAutoscalingGceClient) FetchMigInstances(migRef GceRef) ([]GceInstance, error) {
|
|
return client.fetchMigInstances(migRef)
|
|
}
|
|
|
|
func (client *mockAutoscalingGceClient) FetchMigTemplateName(migRef GceRef) (InstanceTemplateName, error) {
|
|
return client.fetchMigTemplateName(migRef)
|
|
}
|
|
|
|
func (client *mockAutoscalingGceClient) FetchMigTemplate(migRef GceRef, templateName string, regional bool) (*gce.InstanceTemplate, error) {
|
|
return client.fetchMigTemplate(migRef, templateName, regional)
|
|
}
|
|
|
|
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) FetchAvailableDiskTypes(_ string) ([]string, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (client *mockAutoscalingGceClient) FetchReservations() ([]*gce.Reservation, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (client *mockAutoscalingGceClient) FetchReservationsInProject(_ string) ([]*gce.Reservation, 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 (client *mockAutoscalingGceClient) WaitForOperation(_, _, _, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
func TestFillMigInstances(t *testing.T) {
|
|
migRef := GceRef{Project: "test", Zone: "zone-A", Name: "some-mig"}
|
|
oldInstances := []GceInstance{
|
|
{Instance: cloudprovider.Instance{Id: "gce://test/zone-A/some-mig-old-instance-1"}, NumericId: 1},
|
|
{Instance: cloudprovider.Instance{Id: "gce://test/zone-A/some-mig-old-instance-2"}, NumericId: 2},
|
|
}
|
|
newInstances := []GceInstance{
|
|
{Instance: cloudprovider.Instance{Id: "gce://test/zone-A/some-mig-new-instance-1"}, NumericId: 3},
|
|
{Instance: cloudprovider.Instance{Id: "gce://test/zone-A/some-mig-new-instance-2"}, NumericId: 4},
|
|
}
|
|
|
|
timeNow := time.Now()
|
|
timeRecent := timeNow.Add(-30 * time.Minute)
|
|
timeOld := timeNow.Add(-90 * time.Minute)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
cache *GceCache
|
|
wantClientCalls int
|
|
wantInstances []GceInstance
|
|
wantUpdateTime time.Time
|
|
}{
|
|
{
|
|
name: "No instances in cache",
|
|
cache: &GceCache{
|
|
instances: map[GceRef][]GceInstance{},
|
|
instancesUpdateTime: map[GceRef]time.Time{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
},
|
|
wantClientCalls: 1,
|
|
wantInstances: newInstances,
|
|
wantUpdateTime: timeNow,
|
|
},
|
|
{
|
|
name: "Old instances in cache",
|
|
cache: &GceCache{
|
|
instances: map[GceRef][]GceInstance{migRef: oldInstances},
|
|
instancesUpdateTime: map[GceRef]time.Time{migRef: timeOld},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
},
|
|
wantClientCalls: 1,
|
|
wantInstances: newInstances,
|
|
wantUpdateTime: timeNow,
|
|
},
|
|
{
|
|
name: "Recently updated instances in cache",
|
|
cache: &GceCache{
|
|
instances: map[GceRef][]GceInstance{migRef: oldInstances},
|
|
instancesUpdateTime: map[GceRef]time.Time{migRef: timeRecent},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
},
|
|
wantClientCalls: 0,
|
|
wantInstances: oldInstances,
|
|
wantUpdateTime: timeRecent,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
callCounter := make(map[GceRef]int)
|
|
client := &mockAutoscalingGceClient{
|
|
fetchMigInstances: fetchMigInstancesWithCounter(newInstances, callCounter),
|
|
}
|
|
|
|
provider, ok := NewCachingMigInfoProvider(tc.cache, NewMigLister(tc.cache), client, mig.GceRef().Project, 1, time.Hour, false).(*cachingMigInfoProvider)
|
|
assert.True(t, ok)
|
|
provider.timeProvider = &fakeTime{now: timeNow}
|
|
|
|
assert.NoError(t, provider.fillMigInstances(migRef))
|
|
assert.Equal(t, tc.wantClientCalls, callCounter[migRef])
|
|
|
|
updateTime, updateTimeFound := tc.cache.GetMigInstancesUpdateTime(migRef)
|
|
assert.True(t, updateTimeFound)
|
|
assert.Equal(t, tc.wantUpdateTime, updateTime)
|
|
|
|
instances, instancesFound := tc.cache.GetMigInstances(migRef)
|
|
assert.True(t, instancesFound)
|
|
assert.ElementsMatch(t, tc.wantInstances, instances)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMigInfoProviderGetMigForInstance(t *testing.T) {
|
|
instance := GceInstance{
|
|
Instance: cloudprovider.Instance{Id: "gce://project/us-test1/base-instance-name-abcd"},
|
|
NumericId: 777,
|
|
}
|
|
instanceRef, err := GceRefFromProviderId(instance.Id)
|
|
assert.Nil(t, err)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
instanceRef GceRef
|
|
cache *GceCache
|
|
fetchMigInstances func(GceRef) ([]GceInstance, 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][]GceInstance{},
|
|
instancesUpdateTime: map[GceRef]time.Time{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
migBaseNameCache: map[GceRef]string{mig.GceRef(): "base-instance-name"},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst([]GceInstance{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][]GceInstance{},
|
|
instancesUpdateTime: map[GceRef]time.Time{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
migBaseNameCache: map[GceRef]string{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst([]GceInstance{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][]GceInstance{},
|
|
instancesUpdateTime: map[GceRef]time.Time{},
|
|
instancesFromUnknownMig: map[GceRef]bool{},
|
|
migBaseNameCache: map[GceRef]string{mig.GceRef(): "base-instance-name"},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst([]GceInstance{}),
|
|
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([]GceInstance{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, false)
|
|
|
|
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) {
|
|
oldRefreshTime := time.Now().Add(-time.Hour)
|
|
newRefreshTime := time.Now()
|
|
instances := []GceInstance{
|
|
{Instance: cloudprovider.Instance{Id: "gce://project/us-test1/base-instance-name-abcd"}, NumericId: 7},
|
|
{Instance: cloudprovider.Instance{Id: "gce://project/us-test1/base-instance-name-efgh"}, NumericId: 88},
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
cache *GceCache
|
|
fetchMigInstances func(GceRef) ([]GceInstance, error)
|
|
expectedInstances []GceInstance
|
|
expectedErr error
|
|
expectedCachedInstances []GceInstance
|
|
expectedCached bool
|
|
expectedRefreshTime time.Time
|
|
expectedRefreshed bool
|
|
}{
|
|
{
|
|
name: "instances in cache",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instances: map[GceRef][]GceInstance{mig.GceRef(): instances},
|
|
instancesUpdateTime: map[GceRef]time.Time{mig.GceRef(): oldRefreshTime},
|
|
},
|
|
expectedInstances: instances,
|
|
expectedCachedInstances: instances,
|
|
expectedCached: true,
|
|
expectedRefreshTime: oldRefreshTime,
|
|
expectedRefreshed: true,
|
|
},
|
|
{
|
|
name: "instances cache fill",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instances: map[GceRef][]GceInstance{},
|
|
instancesUpdateTime: map[GceRef]time.Time{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst(instances),
|
|
expectedInstances: instances,
|
|
expectedCachedInstances: instances,
|
|
expectedCached: true,
|
|
expectedRefreshTime: newRefreshTime,
|
|
expectedRefreshed: true,
|
|
},
|
|
{
|
|
name: "error during instances cache fill",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instances: map[GceRef][]GceInstance{},
|
|
instancesUpdateTime: map[GceRef]time.Time{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesFail,
|
|
expectedErr: errFetchMigInstances,
|
|
expectedCachedInstances: []GceInstance{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
client := &mockAutoscalingGceClient{
|
|
fetchMigInstances: tc.fetchMigInstances,
|
|
}
|
|
migLister := NewMigLister(tc.cache)
|
|
provider, ok := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second, false).(*cachingMigInfoProvider)
|
|
assert.True(t, ok)
|
|
provider.timeProvider = &fakeTime{now: newRefreshTime}
|
|
|
|
instances, err := provider.GetMigInstances(mig.GceRef())
|
|
cachedInstances, cached := tc.cache.GetMigInstances(mig.GceRef())
|
|
refreshTime, refreshed := tc.cache.GetMigInstancesUpdateTime(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)
|
|
|
|
assert.Equal(t, tc.expectedRefreshTime, refreshTime)
|
|
assert.Equal(t, tc.expectedRefreshed, refreshed)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegenerateMigInstancesCache(t *testing.T) {
|
|
otherMig := &gceMig{
|
|
gceRef: GceRef{
|
|
Project: "project",
|
|
Zone: "us-test1",
|
|
Name: "other-mig",
|
|
},
|
|
}
|
|
|
|
instances := []GceInstance{
|
|
{Instance: cloudprovider.Instance{Id: "gce://project/us-test1/base-instance-name-abcd"}, NumericId: 1},
|
|
{Instance: cloudprovider.Instance{Id: "gce://project/us-test1/base-instance-name-efgh"}, NumericId: 2},
|
|
}
|
|
mig1Instances := []GceInstance{instance1, instance2}
|
|
mig2Instances := []GceInstance{instance3, instance6}
|
|
otherInstances := []GceInstance{
|
|
{Instance: cloudprovider.Instance{Id: "gce://project/us-test1/other-base-instance-name-abcd"}},
|
|
{Instance: cloudprovider.Instance{Id: "gce://project/us-test1/other-base-instance-name-efgh"}},
|
|
}
|
|
|
|
mig1Igm := &gce.InstanceGroupManager{
|
|
Zone: mig1.GceRef().Zone,
|
|
Name: mig1.GceRef().Name,
|
|
TargetSize: 2,
|
|
CurrentActions: &gce.InstanceGroupManagerActionsSummary{
|
|
Creating: 1,
|
|
},
|
|
}
|
|
mig2Igm := &gce.InstanceGroupManager{
|
|
Zone: mig2.GceRef().Zone,
|
|
Name: mig2.GceRef().Name,
|
|
TargetSize: 2,
|
|
CurrentActions: &gce.InstanceGroupManagerActionsSummary{},
|
|
}
|
|
|
|
instancesRefs := toInstancesRefs(t, instances)
|
|
mig1InstancesRefs := toInstancesRefs(t, mig1Instances)
|
|
mig2InstancesRefs := toInstancesRefs(t, mig2Instances)
|
|
otherInstancesRefs := toInstancesRefs(t, otherInstances)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
cache *GceCache
|
|
fetchMigInstances func(GceRef) ([]GceInstance, error)
|
|
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
|
|
fetchAllInstances func(string, string, string) ([]GceInstance, error)
|
|
bulkGceMigInstancesListingEnabled bool
|
|
projectId string
|
|
expectedErr error
|
|
expectedMigInstances map[GceRef][]GceInstance
|
|
expectedInstancesToMig map[GceRef]GceRef
|
|
}{
|
|
{
|
|
name: "fill empty cache for one mig",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instances: map[GceRef][]GceInstance{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst(instances),
|
|
projectId: mig.GceRef().Project,
|
|
expectedMigInstances: map[GceRef][]GceInstance{
|
|
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][]GceInstance{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesMapping(map[GceRef][]GceInstance{
|
|
mig.GceRef(): instances,
|
|
otherMig.GceRef(): otherInstances,
|
|
}),
|
|
projectId: mig.GceRef().Project,
|
|
expectedMigInstances: map[GceRef][]GceInstance{
|
|
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][]GceInstance{
|
|
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][]GceInstance{
|
|
mig.GceRef(): instances,
|
|
otherMig.GceRef(): otherInstances,
|
|
}),
|
|
projectId: mig.GceRef().Project,
|
|
expectedMigInstances: map[GceRef][]GceInstance{
|
|
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][]GceInstance{
|
|
mig.GceRef(): instances,
|
|
},
|
|
instancesToMig: map[GceRef]GceRef{
|
|
instancesRefs[0]: mig.GceRef(),
|
|
instancesRefs[1]: mig.GceRef(),
|
|
},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst(otherInstances),
|
|
projectId: mig.GceRef().Project,
|
|
expectedMigInstances: map[GceRef][]GceInstance{
|
|
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][]GceInstance{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesFail,
|
|
projectId: mig.GceRef().Project,
|
|
expectedErr: errFetchMigInstances,
|
|
},
|
|
{
|
|
name: "bulkGceMigInstancesListingEnabled - fill empty cache for one mig - instances in creating/deleting state",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1},
|
|
instances: map[GceRef][]GceInstance{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
migTargetSizeCache: map[GceRef]int64{},
|
|
migBaseNameCache: map[GceRef]string{},
|
|
listManagedInstancesResultsCache: map[GceRef]string{},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{},
|
|
migInstancesStateCountCache: map[GceRef]map[cloudprovider.InstanceState]int64{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst(mig1Instances),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{mig1Igm}),
|
|
fetchAllInstances: fetchAllInstancesInZone(map[string][]GceInstance{"myzone1": {instance1, instance2}}),
|
|
bulkGceMigInstancesListingEnabled: true,
|
|
projectId: mig1.GceRef().Project,
|
|
expectedMigInstances: map[GceRef][]GceInstance{
|
|
mig1.GceRef(): mig1Instances,
|
|
},
|
|
expectedInstancesToMig: map[GceRef]GceRef{
|
|
mig1InstancesRefs[0]: mig1.GceRef(),
|
|
mig1InstancesRefs[1]: mig1.GceRef(),
|
|
},
|
|
},
|
|
{
|
|
name: "bulkGceMigInstancesListingEnabled - fill empty cache for one mig - number of instances are inconsistent in bulk listing result",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig2.GceRef(): mig2},
|
|
instances: map[GceRef][]GceInstance{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
migTargetSizeCache: map[GceRef]int64{},
|
|
migBaseNameCache: map[GceRef]string{},
|
|
listManagedInstancesResultsCache: map[GceRef]string{},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{},
|
|
migInstancesStateCountCache: map[GceRef]map[cloudprovider.InstanceState]int64{},
|
|
},
|
|
fetchMigInstances: fetchMigInstancesConst(mig2Instances),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{mig2Igm}),
|
|
// one instance is missing from the instances of igm2 in myzone2
|
|
fetchAllInstances: fetchAllInstancesInZone(map[string][]GceInstance{"myzone2": {instance3}}),
|
|
bulkGceMigInstancesListingEnabled: true,
|
|
projectId: mig2.GceRef().Project,
|
|
expectedMigInstances: map[GceRef][]GceInstance{
|
|
mig2.GceRef(): mig2Instances,
|
|
},
|
|
expectedInstancesToMig: map[GceRef]GceRef{
|
|
mig2InstancesRefs[0]: mig2.GceRef(),
|
|
mig2InstancesRefs[1]: mig2.GceRef(),
|
|
},
|
|
},
|
|
{
|
|
name: "bulkGceMigInstancesListingEnabled - fill empty cache for one mig - all instances in running state",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig2.GceRef(): mig2},
|
|
instances: map[GceRef][]GceInstance{},
|
|
instancesToMig: map[GceRef]GceRef{},
|
|
migTargetSizeCache: map[GceRef]int64{},
|
|
migBaseNameCache: map[GceRef]string{},
|
|
listManagedInstancesResultsCache: map[GceRef]string{},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{},
|
|
migInstancesStateCountCache: map[GceRef]map[cloudprovider.InstanceState]int64{},
|
|
},
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{mig2Igm}),
|
|
fetchAllInstances: fetchAllInstancesInZone(map[string][]GceInstance{"myzone2": {instance3, instance6}}),
|
|
bulkGceMigInstancesListingEnabled: true,
|
|
projectId: mig2.GceRef().Project,
|
|
expectedMigInstances: map[GceRef][]GceInstance{
|
|
mig2.GceRef(): mig2Instances,
|
|
},
|
|
expectedInstancesToMig: map[GceRef]GceRef{
|
|
mig2InstancesRefs[0]: mig2.GceRef(),
|
|
mig2InstancesRefs[1]: mig2.GceRef(),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
client := &mockAutoscalingGceClient{
|
|
fetchMigInstances: tc.fetchMigInstances,
|
|
fetchMigs: tc.fetchMigs,
|
|
fetchAllInstances: tc.fetchAllInstances,
|
|
}
|
|
migLister := NewMigLister(tc.cache)
|
|
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, tc.projectId, 1, 0*time.Second, tc.bulkGceMigInstancesListingEnabled)
|
|
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 toInstancesRefs(t *testing.T, instances []GceInstance) []GceRef {
|
|
var refs []GceRef
|
|
for _, instance := range instances {
|
|
instanceRef, err := GceRefFromProviderId(instance.Id)
|
|
assert.Nil(t, err)
|
|
refs = append(refs, instanceRef)
|
|
}
|
|
return refs
|
|
}
|
|
|
|
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, false)
|
|
|
|
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: "basename 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, false)
|
|
|
|
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 TestGetListManagedInstancesResults(t *testing.T) {
|
|
results := "PAGELESS"
|
|
instanceGroupManager := &gce.InstanceGroupManager{
|
|
Zone: mig.GceRef().Zone,
|
|
Name: mig.GceRef().Name,
|
|
ListManagedInstancesResults: results,
|
|
}
|
|
testCases := []struct {
|
|
name string
|
|
cache *GceCache
|
|
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
|
|
fetchResults func(GceRef) (string, error)
|
|
expectedResults string
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "results in cache",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
listManagedInstancesResultsCache: map[GceRef]string{mig.GceRef(): results},
|
|
},
|
|
expectedResults: results,
|
|
},
|
|
{
|
|
name: "results from cache fill",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{instanceGroupManager}),
|
|
expectedResults: results,
|
|
},
|
|
{
|
|
name: "cache fill without mig, fallback success",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
|
|
fetchResults: fetchListManagedInstancesResultsConst(results),
|
|
expectedResults: results,
|
|
},
|
|
{
|
|
name: "cache fill failure, fallback success",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsFail,
|
|
fetchResults: fetchListManagedInstancesResultsConst(results),
|
|
expectedResults: results,
|
|
},
|
|
{
|
|
name: "cache fill without mig, fallback failure",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
|
|
fetchResults: fetchListManagedInstancesResultsFail,
|
|
expectedErr: errFetchListManagedInstancesResults,
|
|
},
|
|
{
|
|
name: "cache fill failure, fallback failure",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsFail,
|
|
fetchResults: fetchListManagedInstancesResultsFail,
|
|
expectedErr: errFetchListManagedInstancesResults,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
client := &mockAutoscalingGceClient{
|
|
fetchMigs: tc.fetchMigs,
|
|
fetchListManagedInstancesResults: tc.fetchResults,
|
|
}
|
|
migLister := NewMigLister(tc.cache)
|
|
provider := NewCachingMigInfoProvider(tc.cache, migLister, client, mig.GceRef().Project, 1, 0*time.Second, false)
|
|
|
|
results, err := provider.GetListManagedInstancesResults(mig.GceRef())
|
|
cachedResults, found := tc.cache.GetListManagedInstancesResults(mig.GceRef())
|
|
|
|
assert.Equal(t, tc.expectedErr, err)
|
|
assert.Equal(t, tc.expectedErr == nil, found)
|
|
if tc.expectedErr == nil {
|
|
assert.Equal(t, tc.expectedResults, results)
|
|
assert.Equal(t, tc.expectedResults, cachedResults)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetMigInstanceTemplateName(t *testing.T) {
|
|
templateName := "template-name"
|
|
instanceGroupManager := &gce.InstanceGroupManager{
|
|
Zone: mig.GceRef().Zone,
|
|
Name: mig.GceRef().Name,
|
|
InstanceTemplate: "https://www.googleapis.com/compute/v1/projects/test-project/global/instanceTemplates/template-name",
|
|
}
|
|
|
|
instanceGroupManagerRegional := &gce.InstanceGroupManager{
|
|
Zone: mig.GceRef().Zone,
|
|
Name: mig.GceRef().Name,
|
|
InstanceTemplate: "https://www.googleapis.com/compute/v1/projects/test-project/regions/us-central1/instanceTemplates/template-name",
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
cache *GceCache
|
|
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
|
|
fetchMigTemplateName func(GceRef) (InstanceTemplateName, error)
|
|
expectedTemplateName string
|
|
expectedRegion bool
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "template name in cache",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
},
|
|
expectedTemplateName: templateName,
|
|
},
|
|
{
|
|
name: "template name from cache fill",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{instanceGroupManager}),
|
|
expectedTemplateName: templateName,
|
|
},
|
|
{
|
|
name: "target size from cache fill, regional",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{instanceGroupManagerRegional}),
|
|
expectedTemplateName: templateName,
|
|
},
|
|
{
|
|
name: "cache fill without mig, fallback success",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsConst([]*gce.InstanceGroupManager{}),
|
|
fetchMigTemplateName: fetchMigTemplateNameConst(InstanceTemplateName{templateName, false}),
|
|
expectedTemplateName: templateName,
|
|
},
|
|
{
|
|
name: "cache fill failure, fallback success",
|
|
cache: emptyCache(),
|
|
fetchMigs: fetchMigsFail,
|
|
fetchMigTemplateName: fetchMigTemplateNameConst(InstanceTemplateName{templateName, false}),
|
|
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, false)
|
|
|
|
instanceTemplateName, err := provider.GetMigInstanceTemplateName(mig.GceRef())
|
|
cachedInstanceTemplateName, 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, instanceTemplateName.Name)
|
|
assert.Equal(t, tc.expectedTemplateName, cachedInstanceTemplateName.Name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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) (InstanceTemplateName, error)
|
|
fetchMigTemplate func(GceRef, string, bool) (*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]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
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]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
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]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
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]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
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]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
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, false)
|
|
|
|
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 TestCreateInstancesState(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
targetSize int64
|
|
actionSummary *gce.InstanceGroupManagerActionsSummary
|
|
want map[cloudprovider.InstanceState]int64
|
|
}{
|
|
{
|
|
name: "actionSummary is nil",
|
|
targetSize: 10,
|
|
actionSummary: nil,
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "actionSummary is empty",
|
|
targetSize: 10,
|
|
actionSummary: &gce.InstanceGroupManagerActionsSummary{},
|
|
want: map[cloudprovider.InstanceState]int64{
|
|
cloudprovider.InstanceCreating: 0,
|
|
cloudprovider.InstanceDeleting: 0,
|
|
cloudprovider.InstanceRunning: 10,
|
|
},
|
|
},
|
|
{
|
|
name: "actionSummary with data",
|
|
targetSize: 30,
|
|
actionSummary: &gce.InstanceGroupManagerActionsSummary{
|
|
Abandoning: 2,
|
|
Creating: 3,
|
|
CreatingWithoutRetries: 5,
|
|
Deleting: 7,
|
|
Recreating: 11,
|
|
},
|
|
want: map[cloudprovider.InstanceState]int64{
|
|
cloudprovider.InstanceCreating: 19,
|
|
cloudprovider.InstanceDeleting: 9,
|
|
cloudprovider.InstanceRunning: 11,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
stateCount := createInstancesStateCount(tc.targetSize, tc.actionSummary)
|
|
assert.Equal(t, tc.want, stateCount)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetMigInstanceKubeEnv(t *testing.T) {
|
|
templateName := "template-name"
|
|
kubeEnvValue := "VAR1: VALUE1\nVAR2: VALUE2"
|
|
kubeEnv, err := ParseKubeEnv(templateName, kubeEnvValue)
|
|
assert.NoError(t, err)
|
|
template := &gce.InstanceTemplate{
|
|
Name: templateName,
|
|
Description: "instance template",
|
|
Properties: &gce.InstanceProperties{
|
|
Metadata: &gce.Metadata{
|
|
Items: []*gce.MetadataItems{
|
|
{Key: "kube-env", Value: &kubeEnvValue},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
oldTemplateName := "old-template-name"
|
|
oldKubeEnvValue := "VAR3: VALUE3\nVAR4: VALUE4"
|
|
oldKubeEnv, err := ParseKubeEnv(oldTemplateName, oldKubeEnvValue)
|
|
assert.NoError(t, err)
|
|
oldTemplate := &gce.InstanceTemplate{
|
|
Name: oldTemplateName,
|
|
Description: "old instance template",
|
|
Properties: &gce.InstanceProperties{
|
|
Metadata: &gce.Metadata{
|
|
Items: []*gce.MetadataItems{
|
|
{Key: "kube-env", Value: &oldKubeEnvValue},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
cache *GceCache
|
|
fetchMigs func(string) ([]*gce.InstanceGroupManager, error)
|
|
fetchMigTemplateName func(GceRef) (InstanceTemplateName, error)
|
|
fetchMigTemplate func(GceRef, string, bool) (*gce.InstanceTemplate, error)
|
|
expectedKubeEnv KubeEnv
|
|
expectedCachedKubeEnv KubeEnv
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "kube-env in cache",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
kubeEnvCache: map[GceRef]KubeEnv{mig.GceRef(): kubeEnv},
|
|
},
|
|
expectedKubeEnv: kubeEnv,
|
|
expectedCachedKubeEnv: kubeEnv,
|
|
},
|
|
{
|
|
name: "cache without kube-env, template in cache",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{mig.GceRef(): template},
|
|
kubeEnvCache: make(map[GceRef]KubeEnv),
|
|
},
|
|
expectedKubeEnv: kubeEnv,
|
|
expectedCachedKubeEnv: kubeEnv,
|
|
},
|
|
{
|
|
name: "cache without kube-env, fetch success",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
instanceTemplatesCache: make(map[GceRef]*gce.InstanceTemplate),
|
|
kubeEnvCache: make(map[GceRef]KubeEnv),
|
|
},
|
|
fetchMigTemplate: fetchMigTemplateConst(template),
|
|
expectedKubeEnv: kubeEnv,
|
|
expectedCachedKubeEnv: kubeEnv,
|
|
},
|
|
{
|
|
name: "cache with old kube-env, new template cached",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{mig.GceRef(): template},
|
|
kubeEnvCache: map[GceRef]KubeEnv{mig.GceRef(): oldKubeEnv},
|
|
},
|
|
expectedKubeEnv: kubeEnv,
|
|
expectedCachedKubeEnv: kubeEnv,
|
|
},
|
|
{
|
|
name: "cache with old kube-env, fetch success",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{mig.GceRef(): oldTemplate},
|
|
kubeEnvCache: map[GceRef]KubeEnv{mig.GceRef(): oldKubeEnv},
|
|
},
|
|
fetchMigTemplate: fetchMigTemplateConst(template),
|
|
expectedKubeEnv: kubeEnv,
|
|
expectedCachedKubeEnv: kubeEnv,
|
|
},
|
|
{
|
|
name: "cache without kube-env, fetch failure",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
instanceTemplatesCache: make(map[GceRef]*gce.InstanceTemplate),
|
|
kubeEnvCache: make(map[GceRef]KubeEnv),
|
|
},
|
|
fetchMigTemplate: fetchMigTemplateFail,
|
|
expectedErr: errFetchMigTemplate,
|
|
},
|
|
{
|
|
name: "cache with old kube-env, fetch failure",
|
|
cache: &GceCache{
|
|
migs: map[GceRef]Mig{mig.GceRef(): mig},
|
|
instanceTemplateNameCache: map[GceRef]InstanceTemplateName{mig.GceRef(): {templateName, false}},
|
|
instanceTemplatesCache: map[GceRef]*gce.InstanceTemplate{mig.GceRef(): oldTemplate},
|
|
kubeEnvCache: map[GceRef]KubeEnv{mig.GceRef(): oldKubeEnv},
|
|
},
|
|
fetchMigTemplate: fetchMigTemplateFail,
|
|
expectedCachedKubeEnv: oldKubeEnv,
|
|
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, false)
|
|
|
|
kubeEnv, err := provider.GetMigKubeEnv(mig.GceRef())
|
|
cachedKubeEnv, found := tc.cache.GetMigKubeEnv(mig.GceRef())
|
|
|
|
assert.Equal(t, tc.expectedErr, err)
|
|
if tc.expectedErr == nil {
|
|
assert.Equal(t, tc.expectedKubeEnv, kubeEnv)
|
|
}
|
|
|
|
assert.Equal(t, tc.expectedCachedKubeEnv.env != nil, found)
|
|
if tc.expectedCachedKubeEnv.env != nil {
|
|
assert.Equal(t, tc.expectedCachedKubeEnv, cachedKubeEnv)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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]InstanceTemplateName{mig.GceRef(): {"template", false}},
|
|
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, false)
|
|
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 := GceInstance{
|
|
Instance: cloudprovider.Instance{Id: "gce://project/zone/base-instance-name-abcd"}, NumericId: 1111,
|
|
}
|
|
instanceRef, err := GceRefFromProviderId(instance.Id)
|
|
assert.Nil(t, err)
|
|
instance2 := GceInstance{
|
|
Instance: cloudprovider.Instance{Id: "gce://project/zone/base-instance-name-abcd2"}, NumericId: 222,
|
|
}
|
|
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,
|
|
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()])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListInstancesInAllZonesWithMigs(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
migs map[GceRef]Mig
|
|
fetchAllInstances func(string, string, string) ([]GceInstance, error)
|
|
wantInstances []GceInstance
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "instance fetching failed",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1},
|
|
fetchAllInstances: fetchAllInstancesInZone(map[string][]GceInstance{}),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "Successfully list mig instances in a single zone",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1},
|
|
fetchAllInstances: fetchAllInstancesInZone(map[string][]GceInstance{"myzone1": {instance1, instance2}, "myzone2": {instance3}}),
|
|
wantInstances: []GceInstance{instance1, instance2},
|
|
},
|
|
{
|
|
name: "Successfully list mig instances in multiple zones",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1, mig2.GceRef(): mig2},
|
|
fetchAllInstances: fetchAllInstancesInZone(map[string][]GceInstance{"myzone1": {instance1, instance2}, "myzone2": {instance3}}),
|
|
wantInstances: []GceInstance{instance1, instance2, instance3},
|
|
},
|
|
{
|
|
name: "Successfully list mig instances in one zones and got errors in another",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1, mig2.GceRef(): mig2},
|
|
fetchAllInstances: fetchAllInstancesInZone(map[string][]GceInstance{"myzone1": {instance1, instance2}}),
|
|
wantInstances: []GceInstance{instance1, instance2},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cache := GceCache{
|
|
migs: tc.migs,
|
|
}
|
|
client := &mockAutoscalingGceClient{
|
|
fetchAllInstances: tc.fetchAllInstances,
|
|
}
|
|
migLister := NewMigLister(&cache)
|
|
provider := &cachingMigInfoProvider{
|
|
cache: &cache,
|
|
migLister: migLister,
|
|
gceClient: client,
|
|
concurrentGceRefreshes: 1,
|
|
}
|
|
instances, err := provider.listInstancesInAllZonesWithMigs()
|
|
|
|
if tc.wantErr {
|
|
assert.NotNil(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.ElementsMatch(t, tc.wantInstances, instances)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupInstancesToMigs(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
instances []GceInstance
|
|
want map[GceRef][]GceInstance
|
|
}{
|
|
{
|
|
name: "no instances",
|
|
want: map[GceRef][]GceInstance{},
|
|
},
|
|
{
|
|
name: "instances from multiple migs including unknown migs",
|
|
instances: []GceInstance{instance1, instance2, instance3, instance4, instance5},
|
|
want: map[GceRef][]GceInstance{
|
|
mig1.GceRef(): {instance1, instance2},
|
|
mig2.GceRef(): {instance3},
|
|
{}: {instance4, instance5},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
groupedInstances := groupInstancesToMigs(tc.instances)
|
|
assert.Equal(t, tc.want, groupedInstances)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsMigInstancesConsistent(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
mig Mig
|
|
migToInstances map[GceRef][]GceInstance
|
|
migInstancesStateCache map[GceRef]map[cloudprovider.InstanceState]int64
|
|
want bool
|
|
}{
|
|
{
|
|
name: "instance not found",
|
|
mig: mig1,
|
|
migToInstances: map[GceRef][]GceInstance{},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "instanceState not found",
|
|
mig: mig1,
|
|
migToInstances: map[GceRef][]GceInstance{mig1.GceRef(): {instance1, instance2}},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "inconsistent number of instances",
|
|
mig: mig1,
|
|
migToInstances: map[GceRef][]GceInstance{mig1.GceRef(): {instance1, instance2}},
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {
|
|
cloudprovider.InstanceCreating: 2,
|
|
cloudprovider.InstanceDeleting: 3,
|
|
cloudprovider.InstanceRunning: 4,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "consistent number of instances",
|
|
mig: mig1,
|
|
migToInstances: map[GceRef][]GceInstance{mig1.GceRef(): {instance1, instance2}},
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {
|
|
cloudprovider.InstanceCreating: 1,
|
|
cloudprovider.InstanceDeleting: 0,
|
|
cloudprovider.InstanceRunning: 1,
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cache := GceCache{
|
|
migInstancesStateCountCache: tc.migInstancesStateCache,
|
|
}
|
|
provider := &cachingMigInfoProvider{
|
|
cache: &cache,
|
|
}
|
|
got := provider.isMigInstancesConsistent(tc.mig, tc.migToInstances)
|
|
assert.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsMigInCreatingOrDeletingInstanceState(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
mig Mig
|
|
migInstancesStateCache map[GceRef]map[cloudprovider.InstanceState]int64
|
|
want bool
|
|
}{
|
|
{
|
|
name: "instanceState not found",
|
|
mig: mig1,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "in creating state",
|
|
mig: mig1,
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {
|
|
cloudprovider.InstanceCreating: 2,
|
|
cloudprovider.InstanceDeleting: 0,
|
|
cloudprovider.InstanceRunning: 1,
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "in deleting state",
|
|
mig: mig1,
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {
|
|
cloudprovider.InstanceCreating: 0,
|
|
cloudprovider.InstanceDeleting: 1,
|
|
cloudprovider.InstanceRunning: 0,
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "not in creating or deleting states",
|
|
mig: mig1,
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {
|
|
cloudprovider.InstanceCreating: 0,
|
|
cloudprovider.InstanceDeleting: 0,
|
|
cloudprovider.InstanceRunning: 1,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cache := GceCache{
|
|
migInstancesStateCountCache: tc.migInstancesStateCache,
|
|
}
|
|
provider := &cachingMigInfoProvider{
|
|
cache: &cache,
|
|
}
|
|
got := provider.isMigCreatingOrDeletingInstances(tc.mig)
|
|
assert.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateMigInstancesCache(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
migs map[GceRef]Mig
|
|
migToInstances map[GceRef][]GceInstance
|
|
fetchMigInstances []GceInstance
|
|
wantInstances map[GceRef][]GceInstance
|
|
migInstancesStateCache map[GceRef]map[cloudprovider.InstanceState]int64
|
|
}{
|
|
{
|
|
name: "inconsistent mig instance state",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1},
|
|
migToInstances: map[GceRef][]GceInstance{
|
|
mig1.GceRef(): {instance1},
|
|
},
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {cloudprovider.InstanceRunning: 2, cloudprovider.InstanceDeleting: 0, cloudprovider.InstanceCreating: 0},
|
|
},
|
|
fetchMigInstances: []GceInstance{instance1, instance2},
|
|
wantInstances: map[GceRef][]GceInstance{mig1.GceRef(): {instance1, instance2}},
|
|
},
|
|
{
|
|
name: "mig with instance in creating or deleting state",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1},
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {cloudprovider.InstanceRunning: 0, cloudprovider.InstanceDeleting: 0, cloudprovider.InstanceCreating: 2},
|
|
},
|
|
fetchMigInstances: []GceInstance{instance1, instance2},
|
|
wantInstances: map[GceRef][]GceInstance{mig1.GceRef(): {instance1, instance2}},
|
|
},
|
|
{
|
|
name: "consistent mig instance state",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1},
|
|
migToInstances: map[GceRef][]GceInstance{
|
|
mig1.GceRef(): {instance1, instance2},
|
|
},
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {cloudprovider.InstanceRunning: 2, cloudprovider.InstanceDeleting: 0, cloudprovider.InstanceCreating: 0},
|
|
},
|
|
wantInstances: map[GceRef][]GceInstance{mig1.GceRef(): {instance1, instance2}},
|
|
},
|
|
{
|
|
name: "mix of consistent and inconsistent states",
|
|
migs: map[GceRef]Mig{mig1.GceRef(): mig1, mig2.GceRef(): mig2},
|
|
migToInstances: map[GceRef][]GceInstance{
|
|
mig1.GceRef(): {instance1, instance2},
|
|
},
|
|
migInstancesStateCache: map[GceRef]map[cloudprovider.InstanceState]int64{
|
|
mig1.GceRef(): {cloudprovider.InstanceRunning: 2, cloudprovider.InstanceDeleting: 0, cloudprovider.InstanceCreating: 0},
|
|
mig2.GceRef(): {cloudprovider.InstanceRunning: 1, cloudprovider.InstanceDeleting: 0, cloudprovider.InstanceCreating: 0},
|
|
},
|
|
fetchMigInstances: []GceInstance{instance3},
|
|
wantInstances: map[GceRef][]GceInstance{
|
|
mig1.GceRef(): {instance1, instance2},
|
|
mig2.GceRef(): {instance3},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cache := GceCache{
|
|
migs: tc.migs,
|
|
instances: make(map[GceRef][]GceInstance),
|
|
instancesUpdateTime: make(map[GceRef]time.Time),
|
|
migBaseNameCache: make(map[GceRef]string),
|
|
migInstancesStateCountCache: tc.migInstancesStateCache,
|
|
instancesToMig: make(map[GceRef]GceRef),
|
|
}
|
|
migLister := NewMigLister(&cache)
|
|
client := &mockAutoscalingGceClient{
|
|
fetchMigInstances: fetchMigInstancesConst(tc.fetchMigInstances),
|
|
}
|
|
provider := &cachingMigInfoProvider{
|
|
cache: &cache,
|
|
migLister: migLister,
|
|
gceClient: client,
|
|
timeProvider: &realTime{},
|
|
}
|
|
err := provider.updateMigInstancesCache(tc.migToInstances)
|
|
assert.NoError(t, err)
|
|
for migRef, want := range tc.wantInstances {
|
|
instances, found := cache.GetMigInstances(migRef)
|
|
assert.True(t, found)
|
|
assert.Equal(t, want, instances)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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][]GceInstance),
|
|
instancesUpdateTime: make(map[GceRef]time.Time),
|
|
migTargetSizeCache: make(map[GceRef]int64),
|
|
migBaseNameCache: make(map[GceRef]string),
|
|
migInstancesStateCountCache: make(map[GceRef]map[cloudprovider.InstanceState]int64),
|
|
listManagedInstancesResultsCache: make(map[GceRef]string),
|
|
instanceTemplateNameCache: make(map[GceRef]InstanceTemplateName),
|
|
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) ([]GceInstance, error) {
|
|
return nil, errFetchMigInstances
|
|
}
|
|
|
|
func fetchMigInstancesConst(instances []GceInstance) func(GceRef) ([]GceInstance, error) {
|
|
return func(GceRef) ([]GceInstance, error) {
|
|
return instances, nil
|
|
}
|
|
}
|
|
|
|
func fetchMigInstancesWithCounter(instances []GceInstance, migCounter map[GceRef]int) func(GceRef) ([]GceInstance, error) {
|
|
return func(ref GceRef) ([]GceInstance, error) {
|
|
migCounter[ref] = migCounter[ref] + 1
|
|
return instances, nil
|
|
}
|
|
}
|
|
|
|
func fetchMigInstancesMapping(instancesMapping map[GceRef][]GceInstance) func(GceRef) ([]GceInstance, error) {
|
|
return func(migRef GceRef) ([]GceInstance, 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) (InstanceTemplateName, error) {
|
|
return InstanceTemplateName{}, errFetchMigTemplateName
|
|
}
|
|
|
|
func fetchMigTemplateNameConst(instanceTemplateName InstanceTemplateName) func(GceRef) (InstanceTemplateName, error) {
|
|
return func(GceRef) (InstanceTemplateName, error) {
|
|
return instanceTemplateName, nil
|
|
}
|
|
}
|
|
|
|
func fetchMigTemplateFail(_ GceRef, _ string, _ bool) (*gce.InstanceTemplate, error) {
|
|
return nil, errFetchMigTemplate
|
|
}
|
|
|
|
func fetchMigTemplateConst(template *gce.InstanceTemplate) func(GceRef, string, bool) (*gce.InstanceTemplate, error) {
|
|
return func(GceRef, string, bool) (*gce.InstanceTemplate, error) {
|
|
return template, nil
|
|
}
|
|
}
|
|
|
|
func fetchListManagedInstancesResultsFail(_ GceRef) (string, error) {
|
|
return "", errFetchListManagedInstancesResults
|
|
}
|
|
|
|
func fetchListManagedInstancesResultsConst(listManagedInstancesResults string) func(GceRef) (string, error) {
|
|
return func(GceRef) (string, error) {
|
|
return listManagedInstancesResults, 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
|
|
}
|
|
}
|
|
|
|
func fetchAllInstancesInZone(allInstances map[string][]GceInstance) func(string, string, string) ([]GceInstance, error) {
|
|
return func(project, zone, filter string) ([]GceInstance, error) {
|
|
instances, found := allInstances[zone]
|
|
if !found {
|
|
return nil, errors.New("")
|
|
}
|
|
return instances, nil
|
|
}
|
|
}
|