github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/api/autoscaling_test.go (about)

     1  package api
     2  
     3  import (
     4  	"os"
     5  	"sync/atomic"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/in4it/ecs-deploy/provider/ecs"
    10  	"github.com/in4it/ecs-deploy/service"
    11  	"github.com/juju/loggo"
    12  )
    13  
    14  type MockECS struct {
    15  	ecs.ECSIf
    16  	GetInstanceResourcesOutputFree       []ecs.FreeInstanceResource
    17  	GetInstanceResourcesOutputRegistered []ecs.RegisteredInstanceResource
    18  }
    19  
    20  type MockService struct {
    21  	GetClusterInfoOutput  *service.DynamoCluster
    22  	GetClusterInfoCounter uint64
    23  	IsDeployRunningOutput bool
    24  	PutClusterInfoOutput  *service.DynamoCluster
    25  	PutClusterInfoCounter uint64
    26  	service.ServiceIf
    27  }
    28  
    29  type MockAutoScaling struct {
    30  	ecs.AutoScalingIf
    31  	GetAutoScalingGroupByTagOutput string
    32  }
    33  
    34  func (m *MockECS) GetInstanceResources(clusterName string) ([]ecs.FreeInstanceResource, []ecs.RegisteredInstanceResource, error) {
    35  	return m.GetInstanceResourcesOutputFree, m.GetInstanceResourcesOutputRegistered, nil
    36  }
    37  
    38  func (m *MockAutoScaling) GetAutoScalingGroupByTag(clusterName string) (string, error) {
    39  	return m.GetAutoScalingGroupByTagOutput, nil
    40  }
    41  
    42  func (m *MockAutoScaling) ScaleClusterNodes(autoScalingGroupName string, change int64) error {
    43  	return nil
    44  
    45  }
    46  func (m *MockService) PutClusterInfo(dc service.DynamoCluster, clusterName string, action string, pendingAction string) (*service.DynamoCluster, error) {
    47  	atomic.AddUint64(&m.PutClusterInfoCounter, 1)
    48  	m.GetClusterInfoOutput.ScalingOperation.PendingAction = pendingAction
    49  	return m.PutClusterInfoOutput, nil
    50  }
    51  func (m *MockService) GetClusterInfo() (*service.DynamoCluster, error) {
    52  	atomic.AddUint64(&m.GetClusterInfoCounter, 1)
    53  	return m.GetClusterInfoOutput, nil
    54  }
    55  
    56  func (m *MockService) IsDeployRunning() (bool, error) {
    57  	return m.IsDeployRunningOutput, nil
    58  }
    59  
    60  func TestAreAllTasksRunningInCluster(t *testing.T) {
    61  	mc1 := &MockController{
    62  		runningServices: []service.RunningService{
    63  			{
    64  				ServiceName:  "test-service",
    65  				RunningCount: 1,
    66  				PendingCount: 0,
    67  				DesiredCount: 1,
    68  			},
    69  			{
    70  				ServiceName:  "test-service2",
    71  				RunningCount: 2,
    72  				PendingCount: 0,
    73  				DesiredCount: 2,
    74  			},
    75  			{
    76  				ServiceName:  "test-service3",
    77  				RunningCount: 0,
    78  				PendingCount: 0,
    79  				DesiredCount: 0,
    80  			},
    81  		},
    82  	}
    83  	mc2 := &MockController{
    84  		runningServices: []service.RunningService{
    85  			{
    86  				ServiceName:  "test-service",
    87  				RunningCount: 0,
    88  				PendingCount: 0,
    89  				DesiredCount: 1,
    90  			},
    91  		},
    92  	}
    93  	as := AutoscalingController{}
    94  
    95  	if !as.areAllTasksRunningInCluster("clustername", mc1) {
    96  		t.Errorf("Expected that all tasks are running in the cluster. Got false")
    97  	}
    98  	if as.areAllTasksRunningInCluster("clustername", mc2) {
    99  		t.Errorf("Expected that all tasks are not running in the cluster. Got true")
   100  	}
   101  }
   102  
   103  func TestLaunchProcessPendingScalingOpWithLocking(t *testing.T) {
   104  	// configuration
   105  	os.Setenv("AUTOSCALING_DOWN_PERIOD", "2")
   106  	os.Setenv("AUTOSCALING_DOWN_INTERVAL", "1")
   107  	asAutoscalingControllerLogger.SetLogLevel(loggo.DEBUG)
   108  	// mock
   109  	am := &MockAutoScaling{
   110  		GetAutoScalingGroupByTagOutput: "ecs-deploy",
   111  	}
   112  	s := &MockService{
   113  		IsDeployRunningOutput: false,
   114  		GetClusterInfoOutput: &service.DynamoCluster{
   115  			Identifier: "myService",
   116  			Time:       time.Now(),
   117  			ScalingOperation: service.DynamoClusterScalingOperation{
   118  				ClusterName:   "testCluster",
   119  				Action:        "down",
   120  				PendingAction: "down",
   121  			},
   122  			ContainerInstances: []service.DynamoClusterContainerInstance{
   123  				{
   124  					ClusterName:         "testCluster",
   125  					ContainerInstanceId: "1-2-3-4",
   126  					FreeMemory:          int64(2048),
   127  					FreeCpu:             int64(1024),
   128  					Status:              "ACTIVE",
   129  				},
   130  				{
   131  					ClusterName:         "testCluster",
   132  					ContainerInstanceId: "1-2-3-5",
   133  					FreeMemory:          int64(2048),
   134  					FreeCpu:             int64(1024),
   135  					Status:              "ACTIVE",
   136  				},
   137  				{
   138  					ClusterName:         "testCluster",
   139  					ContainerInstanceId: "1-2-3-6",
   140  					FreeMemory:          int64(2048),
   141  					FreeCpu:             int64(1024),
   142  					Status:              "ACTIVE",
   143  				},
   144  			},
   145  		},
   146  		PutClusterInfoOutput: &service.DynamoCluster{
   147  			Identifier: "myService",
   148  			Time:       time.Now(),
   149  		},
   150  	}
   151  	mc1 := &MockController{
   152  		runningServices: []service.RunningService{
   153  			{
   154  				ServiceName:  "myService",
   155  				RunningCount: 1,
   156  				PendingCount: 0,
   157  				DesiredCount: 1,
   158  			},
   159  		},
   160  		getServicesOutput: []*service.DynamoServicesElement{
   161  			{
   162  				C:                 "testCluster",
   163  				S:                 "myService",
   164  				MemoryReservation: int64(2048),
   165  				CpuReservation:    int64(1024),
   166  			},
   167  		},
   168  	}
   169  	// test
   170  	as := AutoscalingController{}
   171  	clusterName := "testCluster"
   172  	pendingScalingOp := "down"
   173  	registeredInstanceCpu := int64(1024)
   174  	registeredInstanceMemory := int64(2048)
   175  	var (
   176  		err1 error
   177  		err2 error
   178  	)
   179  
   180  	wait1 := make(chan struct{})
   181  	wait2 := make(chan struct{})
   182  
   183  	go func() {
   184  		err1 = as.launchProcessPendingScalingOpWithLocking(clusterName, pendingScalingOp, registeredInstanceCpu, registeredInstanceMemory, s, mc1, am)
   185  		if err1 != nil {
   186  			t.Errorf("Error: %s", err1)
   187  		}
   188  		close(wait1)
   189  
   190  	}()
   191  	go func() {
   192  		err2 = as.launchProcessPendingScalingOpWithLocking(clusterName, pendingScalingOp, registeredInstanceCpu, registeredInstanceMemory, s, mc1, am)
   193  		if err2 != nil {
   194  			t.Errorf("Error: %s", err2)
   195  		}
   196  		close(wait2)
   197  
   198  	}()
   199  	<-wait1
   200  	<-wait2
   201  
   202  	if s.PutClusterInfoCounter != 1 {
   203  		t.Errorf("PutClusterInfoCounter is %d (expected 1)", s.PutClusterInfoCounter)
   204  	}
   205  	if s.GetClusterInfoCounter != 3 {
   206  		t.Errorf("GetClusterInfoCounter is %d (expected 3)", s.GetClusterInfoCounter)
   207  	}
   208  }
   209  
   210  func TestGetClusterInfoWithExpiredCache(t *testing.T) {
   211  	scalingOp := service.DynamoClusterScalingOperation{
   212  		ClusterName:   "testCluster",
   213  		Action:        "down",
   214  		PendingAction: "down",
   215  	}
   216  	e := &MockECS{
   217  		GetInstanceResourcesOutputFree: []ecs.FreeInstanceResource{
   218  			{
   219  				InstanceId:       "i-123",
   220  				AvailabilityZone: "eu-west-1a",
   221  				Status:           "ACTIVE",
   222  			},
   223  			{
   224  				InstanceId:       "i-456",
   225  				AvailabilityZone: "eu-west-1b",
   226  				Status:           "ACTIVE",
   227  			},
   228  		},
   229  		GetInstanceResourcesOutputRegistered: []ecs.RegisteredInstanceResource{},
   230  	}
   231  	s := &MockService{
   232  		IsDeployRunningOutput: false,
   233  		GetClusterInfoOutput: &service.DynamoCluster{
   234  			Identifier:       "myService",
   235  			Time:             time.Now().Truncate(10 * time.Minute),
   236  			ScalingOperation: scalingOp,
   237  			ContainerInstances: []service.DynamoClusterContainerInstance{
   238  				{
   239  					ClusterName:         "testCluster",
   240  					ContainerInstanceId: "1-2-3-4",
   241  					FreeMemory:          int64(2048),
   242  					FreeCpu:             int64(1024),
   243  					Status:              "ACTIVE",
   244  				},
   245  				{
   246  					ClusterName:         "testCluster",
   247  					ContainerInstanceId: "1-2-3-5",
   248  					FreeMemory:          int64(2048),
   249  					FreeCpu:             int64(1024),
   250  					Status:              "ACTIVE",
   251  				},
   252  				{
   253  					ClusterName:         "testCluster",
   254  					ContainerInstanceId: "1-2-3-6",
   255  					FreeMemory:          int64(2048),
   256  					FreeCpu:             int64(1024),
   257  					Status:              "ACTIVE",
   258  				},
   259  			},
   260  		},
   261  	}
   262  	as := AutoscalingController{}
   263  
   264  	res, err := as.getClusterInfo("myCluster", true, s, e)
   265  
   266  	if err != nil {
   267  		t.Errorf("Error getClusterInfo: %s", err)
   268  	}
   269  	if res.ScalingOperation.PendingAction != scalingOp.PendingAction {
   270  		t.Errorf("Scaling Operation not found in result: expected %s, got %s", scalingOp.PendingAction, res.ScalingOperation.PendingAction)
   271  	}
   272  	if len(res.ContainerInstances) != 2 {
   273  		t.Errorf("wrong number of container instances, got: %d", len(res.ContainerInstances))
   274  	}
   275  	if res.ContainerInstances[0].ContainerInstanceId != "i-123" {
   276  		t.Errorf("wrong container instance returned")
   277  	}
   278  }