-
Notifications
You must be signed in to change notification settings - Fork 103
/
activityScalingSpec.js
251 lines (223 loc) · 10.6 KB
/
activityScalingSpec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
'use strict';
const find = require('lodash.find');
const { autoscaling, ecs } = require('@cumulus/common/aws');
const {
buildAndStartWorkflow,
waitForCompletedExecution,
getAutoScalingGroupName,
getClusterArn,
getClusterStats,
getExecutionStatus,
getNewScalingActivity
} = require('@cumulus/integration-tests');
const { sleep } = require('@cumulus/common/util');
const { loadConfig, loadCloudformationTemplate } = require('../helpers/testUtils');
const config = loadConfig();
const stackName = config.stackName;
let cloudformationResources;
let activitiesWaitingAlarm;
let memoryReservationHighAlarm;
let memoryReservationLowAlarm;
let alarmEvaluationPeriods;
let alarmPeriodSeconds;
let sleepMs;
let numActivityTasks;
const workflowName = 'HelloWorldActivityWorkflow';
const serviceScaleOutPolicyName = 'HelloWorldServiceScaleOutScalingPolicy';
const activiitesWaitingAlarmName = 'HelloWorldServiceActivitiesWaitingAlarm';
const targetTrackingScalingPolicy = 'HelloWorldServiceScalingPolicy';
describe('scaling for step function activities', () => {
beforeAll(async () => {
cloudformationResources = (await loadCloudformationTemplate(config)).Resources;
activitiesWaitingAlarm = cloudformationResources[activiitesWaitingAlarmName];
alarmEvaluationPeriods = activitiesWaitingAlarm.Properties.EvaluationPeriods;
const alarmPeriod = activitiesWaitingAlarm.Properties.Metrics[1].MetricStat.Period;
alarmPeriodSeconds = alarmPeriod / alarmEvaluationPeriods;
sleepMs = 2 * alarmPeriodSeconds * 1000;
numActivityTasks = Object.values(cloudformationResources).filter((resource) => resource.Type === 'AWS::StepFunctions::Activity').length;
memoryReservationHighAlarm = cloudformationResources.MemoryReservationHighAlarm;
memoryReservationLowAlarm = cloudformationResources.MemoryReservationLowAlarm;
});
it('HelloWorld ECS Service is a scalable target', () => {
const helloWorldScalableTarget = cloudformationResources.HelloWorldServiceECSServiceScalableTarget;
expect(helloWorldScalableTarget.Type).toEqual('AWS::ApplicationAutoScaling::ScalableTarget');
});
describe('ActivitesWaiting alarm', () => {
it('cloudformation stack has an alarm for ActivitiesWaiting ', () => {
expect(activitiesWaitingAlarm.Type).toEqual('AWS::CloudWatch::Alarm');
});
it('ActivitiesWaitingAlarm is configured to scale out the ECSService', () => {
const alarmAction = activitiesWaitingAlarm.Properties.AlarmActions[0].Ref;
expect(alarmAction).toEqual(serviceScaleOutPolicyName);
});
it('ScaleOutTasks scaling policy scales out % when ActivitiesWaiting Alarm triggers', () => {
const scaleOutTasksPolicy = cloudformationResources[serviceScaleOutPolicyName].Properties;
expect(scaleOutTasksPolicy.StepScalingPolicyConfiguration.AdjustmentType).toEqual('PercentChangeInCapacity');
});
});
describe('ECS Service TargetTracking Policy', () => {
it('triggers at 20% of CPUUtilization', () => {
const targetTrackingConfiguration = cloudformationResources[targetTrackingScalingPolicy].Properties.TargetTrackingScalingPolicyConfiguration;
expect(targetTrackingConfiguration.TargetValue).toEqual(50);
expect(targetTrackingConfiguration.PredefinedMetricSpecification.PredefinedMetricType).toEqual('ECSServiceAverageCPUUtilization');
});
});
describe('MemoryReservation alarms', () => {
it('Cloudformation stack has an alarm for High and Low MemoryReservation', () => {
expect(memoryReservationHighAlarm.Type).toEqual('AWS::CloudWatch::Alarm');
expect(memoryReservationLowAlarm.Type).toEqual('AWS::CloudWatch::Alarm');
});
it('MemoryReservation alarms triggers ec2 scale in or out policies', () => {
let alarmAction = memoryReservationHighAlarm.Properties.AlarmActions[0].Ref;
expect(alarmAction).toEqual('ScaleOutEc2ScalingPolicy');
alarmAction = memoryReservationLowAlarm.Properties.AlarmActions[0].Ref;
expect(alarmAction).toEqual('ScaleInEc2ScalingPolicy');
});
it('MemoryReservationHigh alarm triggers at 75%', () => {
expect(memoryReservationHighAlarm.Properties.Threshold).toEqual(75);
});
it('MemoryReservationLow alarm triggers at 50%', () => {
expect(memoryReservationHighAlarm.Properties.Threshold).toEqual(75);
});
});
// This test is skipped because it is a timely operation to scale in and
// scale out EC2 instances.
//
// The test kicks off 10 workflow executions and asserts an increase in the number of
// running tasks for the hello world service. It also asserts that (eventually) the
// number of EC2 instances scales out (and back in).
xdescribe('scaling the service\'s desired tasks', () => {
let workflowExecutionArns = [];
const numExecutions = 10;
beforeAll(async () => {
const workflowExecutionPromises = [];
for (let i = 0; i < numExecutions; i += 1) {
workflowExecutionPromises.push(buildAndStartWorkflow(
stackName,
config.bucket,
workflowName,
null,
null,
{
sleep: sleepMs
}
));
}
workflowExecutionArns = await Promise.all(workflowExecutionPromises);
});
describe('when activities Waiting are greater than the threshold', () => {
it('the number of tasks the service is running should increase', async () => {
await sleep(sleepMs);
const clusterStats = await getClusterStats(stackName);
const runningEC2TasksCount = parseInt(find(clusterStats, ['name', 'runningEC2TasksCount']).value, 10);
expect(runningEC2TasksCount).toBeGreaterThan(numActivityTasks);
});
});
describe('when activities scheduled are below the threshold', () => {
beforeAll(async () => {
const completions = workflowExecutionArns.map((executionArn) => waitForCompletedExecution(executionArn));
await Promise.all(completions);
});
it('all executions succeeded', async () => {
const results = await Promise.all(workflowExecutionArns.map((arn) => getExecutionStatus(arn)));
expect(results).toEqual(new Array(numExecutions).fill('SUCCEEDED'));
});
});
});
xdescribe('when scale in takes affect', () => {
let workflowExecutionArns = [];
const numExecutions = 2;
beforeAll(async (done) => {
try {
console.log('in before block');
const workflowExecutionPromises = [];
const clusterArn = await getClusterArn(stackName);
console.log(`clusterArn ${clusterArn}`);
const asgName = await getAutoScalingGroupName(stackName);
console.log(`asgName ${asgName}`);
// set desired instances to 2 so scale in will take affect
const setDesiredCapacityParams = {
AutoScalingGroupName: asgName,
DesiredCapacity: 2,
HonorCooldown: true
};
await autoscaling().setDesiredCapacity(setDesiredCapacityParams).promise()
.catch((err) => {
console.log(`err ${JSON.stringify(err, null, 2)}`);
if (err.code === 'ScalingActivityInProgress') {
console.log('ScalingActivityInProgress. Cannot make update.');
}
else {
throw (err);
}
});
// wait for instances to be active
const listContainerInstancesParams = { cluster: clusterArn };
let containerInstanceIds = (await ecs().listContainerInstances(listContainerInstancesParams).promise()).containerInstanceArns;
const waitTime = 30000;
/* eslint-disable no-await-in-loop */
while (containerInstanceIds.length < 2) {
console.log('waiting for instances to become active');
await sleep(waitTime);
containerInstanceIds = (await ecs().listContainerInstances(listContainerInstancesParams).promise()).containerInstanceArns;
}
/* eslint-enable no-await-in-loop */
// set desired tasks to 2
const services = await ecs().listServices({ cluster: clusterArn }).promise();
const serviceName = services.serviceArns[0].split('/').pop();
const updateServiceParams = {
desiredCount: 2,
cluster: clusterArn,
service: serviceName
};
await ecs().updateService(updateServiceParams).promise();
// Check there is a task running on each instance
const describeContainerInstanceParams = {
cluster: clusterArn,
containerInstances: containerInstanceIds
};
let instanceData = await ecs().describeContainerInstances(describeContainerInstanceParams).promise();
let firstInstanceRunningTasks = instanceData.containerInstances[0].runningTasksCount;
let secondInstanceRunningTasks = instanceData.containerInstances[1].runningTasksCount;
/* eslint-disable no-await-in-loop */
while (!(firstInstanceRunningTasks === 1 && secondInstanceRunningTasks === 1)) {
await sleep(waitTime);
instanceData = await ecs().describeContainerInstances(describeContainerInstanceParams).promise();
firstInstanceRunningTasks = instanceData.containerInstances[0].runningTasksCount;
secondInstanceRunningTasks = instanceData.containerInstances[1].runningTasksCount;
}
/* eslint-enable no-await-in-loop */
for (let i = 0; i < numExecutions; i += 1) {
workflowExecutionPromises.push(buildAndStartWorkflow(
stackName,
config.bucket,
workflowName,
null,
null,
{
sleep: 4 * sleepMs // sleep for long enough for scale in to take affect (about 7 minutes)
}
));
}
// set desired tasks to 1 so memory reservation low alarm will be triggered
workflowExecutionArns = await Promise.all(workflowExecutionPromises);
done();
}
catch (e) {
console.log(e);
throw (e);
}
}, 20 * 60 * 1000);
it('all tasks should complete before instance is terminated', async () => {
const newScalingActivity = await getNewScalingActivity({ stackName });
expect(newScalingActivity.Description).toMatch(/Terminating EC2 instance: i-*/);
// expect instance not to be terminated until running tasks complete.
// So all should complete within sleep
const completions = workflowExecutionArns.map((executionArn) => waitForCompletedExecution(executionArn));
console.log('waiting for completed executions');
await Promise.all(completions);
const results = await Promise.all(workflowExecutionArns.map((arn) => getExecutionStatus(arn)));
expect(results).toEqual(new Array(numExecutions).fill('SUCCEEDED'));
});
});
});