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 }