@@ -17,8 +17,7 @@ limitations under the License.
1717package aws
1818
1919import (
20- "testing"
21-
20+ "fmt"
2221 "github.com/stretchr/testify/assert"
2322 "github.com/stretchr/testify/mock"
2423 apiv1 "k8s.io/api/core/v1"
@@ -27,6 +26,7 @@ import (
2726 "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/aws/aws-sdk-go/aws"
2827 "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/aws/aws-sdk-go/service/autoscaling"
2928 "k8s.io/autoscaler/cluster-autoscaler/config"
29+ "testing"
3030)
3131
3232var testAwsManager = & AwsManager {
@@ -550,7 +550,7 @@ func TestDeleteNodesWithPlaceholder(t *testing.T) {
550550 err = asgs [0 ].DeleteNodes ([]* apiv1.Node {node })
551551 assert .NoError (t , err )
552552 a .AssertNumberOfCalls (t , "SetDesiredCapacity" , 1 )
553- a .AssertNumberOfCalls (t , "DescribeAutoScalingGroupsPages" , 1 )
553+ a .AssertNumberOfCalls (t , "DescribeAutoScalingGroupsPages" , 2 )
554554
555555 newSize , err := asgs [0 ].TargetSize ()
556556 assert .NoError (t , err )
@@ -595,6 +595,115 @@ func TestDeleteNodesAfterMultipleRefreshes(t *testing.T) {
595595 assert .NoError (t , err )
596596}
597597
598+ func TestDeleteNodesWithPlaceholderAndStaleCache (t * testing.T ) {
599+ // This test validates the scenario where ASG cache is not in sync with Autoscaling configuration.
600+ // we are taking an example where ASG size is 10, cache as 3 instances "i-0000", "i-0001" and "i-0002
601+ // But ASG has 6 instances i-0000 to i-10005. When DeleteInstances is called with 2 instances ("i-0000", "i-0001" )
602+ // and placeholders, CAS will terminate only these 2 instances after reducing ASG size by the count of placeholders
603+
604+ a := & autoScalingMock {}
605+ provider := testProvider (t , newTestAwsManagerWithAsgs (t , a , nil , []string {"1:10:test-asg" }))
606+ asgs := provider .NodeGroups ()
607+ commonAsg := & asg {
608+ AwsRef : AwsRef {Name : asgs [0 ].Id ()},
609+ minSize : asgs [0 ].MinSize (),
610+ maxSize : asgs [0 ].MaxSize (),
611+ }
612+
613+ // desired capacity will be set as 6 as ASG has 4 placeholders
614+ a .On ("SetDesiredCapacity" , & autoscaling.SetDesiredCapacityInput {
615+ AutoScalingGroupName : aws .String (asgs [0 ].Id ()),
616+ DesiredCapacity : aws .Int64 (6 ),
617+ HonorCooldown : aws .Bool (false ),
618+ }).Return (& autoscaling.SetDesiredCapacityOutput {})
619+
620+ // Look up the current number of instances...
621+ var expectedInstancesCount int64 = 10
622+ a .On ("DescribeAutoScalingGroupsPages" ,
623+ & autoscaling.DescribeAutoScalingGroupsInput {
624+ AutoScalingGroupNames : aws .StringSlice ([]string {"test-asg" }),
625+ MaxRecords : aws .Int64 (maxRecordsReturnedByAPI ),
626+ },
627+ mock .AnythingOfType ("func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool" ),
628+ ).Run (func (args mock.Arguments ) {
629+ fn := args .Get (1 ).(func (* autoscaling.DescribeAutoScalingGroupsOutput , bool ) bool )
630+ fn (testNamedDescribeAutoScalingGroupsOutput ("test-asg" , expectedInstancesCount , "i-0000" , "i-0001" , "i-0002" , "i-0003" , "i-0004" , "i-0005" ), false )
631+
632+ expectedInstancesCount = 4
633+ }).Return (nil )
634+
635+ a .On ("DescribeScalingActivities" ,
636+ & autoscaling.DescribeScalingActivitiesInput {
637+ AutoScalingGroupName : aws .String ("test-asg" ),
638+ },
639+ ).Return (& autoscaling.DescribeScalingActivitiesOutput {}, nil )
640+
641+ provider .Refresh ()
642+
643+ initialSize , err := asgs [0 ].TargetSize ()
644+ assert .NoError (t , err )
645+ assert .Equal (t , 10 , initialSize )
646+
647+ var awsInstanceRefs []AwsInstanceRef
648+ instanceToAsg := make (map [AwsInstanceRef ]* asg )
649+
650+ var nodes []* apiv1.Node
651+ for i := 3 ; i <= 9 ; i ++ {
652+ providerId := fmt .Sprintf ("aws:///us-east-1a/i-placeholder-test-asg-%d" , i )
653+ node := & apiv1.Node {
654+ Spec : apiv1.NodeSpec {
655+ ProviderID : providerId ,
656+ },
657+ }
658+ nodes = append (nodes , node )
659+ awsInstanceRef := AwsInstanceRef {
660+ ProviderID : providerId ,
661+ Name : fmt .Sprintf ("i-placeholder-test-asg-%d" , i ),
662+ }
663+ awsInstanceRefs = append (awsInstanceRefs , awsInstanceRef )
664+ instanceToAsg [awsInstanceRef ] = commonAsg
665+ }
666+
667+ for i := 0 ; i <= 2 ; i ++ {
668+ providerId := fmt .Sprintf ("aws:///us-east-1a/i-000%d" , i )
669+ node := & apiv1.Node {
670+ Spec : apiv1.NodeSpec {
671+ ProviderID : providerId ,
672+ },
673+ }
674+ // only setting 2 instances to be terminated out of 3 active instances
675+ if i < 2 {
676+ nodes = append (nodes , node )
677+ a .On ("TerminateInstanceInAutoScalingGroup" , & autoscaling.TerminateInstanceInAutoScalingGroupInput {
678+ InstanceId : aws .String (fmt .Sprintf ("i-000%d" , i )),
679+ ShouldDecrementDesiredCapacity : aws .Bool (true ),
680+ }).Return (& autoscaling.TerminateInstanceInAutoScalingGroupOutput {
681+ Activity : & autoscaling.Activity {Description : aws .String ("Deleted instance" )},
682+ })
683+ }
684+ awsInstanceRef := AwsInstanceRef {
685+ ProviderID : providerId ,
686+ Name : fmt .Sprintf ("i-000%d" , i ),
687+ }
688+ awsInstanceRefs = append (awsInstanceRefs , awsInstanceRef )
689+ instanceToAsg [awsInstanceRef ] = commonAsg
690+ }
691+
692+ // modifying provider to bring disparity between ASG and cache
693+ provider .awsManager .asgCache .asgToInstances [AwsRef {Name : "test-asg" }] = awsInstanceRefs
694+ provider .awsManager .asgCache .instanceToAsg = instanceToAsg
695+
696+ // calling delete nodes 2 nodes and remaining placeholders
697+ err = asgs [0 ].DeleteNodes (nodes )
698+ assert .NoError (t , err )
699+ a .AssertNumberOfCalls (t , "SetDesiredCapacity" , 1 )
700+ a .AssertNumberOfCalls (t , "DescribeAutoScalingGroupsPages" , 2 )
701+
702+ // This ensures only 2 instances are terminated which are mocked in this unit test
703+ a .AssertNumberOfCalls (t , "TerminateInstanceInAutoScalingGroup" , 2 )
704+
705+ }
706+
598707func TestGetResourceLimiter (t * testing.T ) {
599708 mockAutoScaling := & autoScalingMock {}
600709 mockEC2 := & ec2Mock {}
0 commit comments