Skip to content

Commit efbaa6f

Browse files
authored
feat(runner): add source parameter to distinguish between scale-up and pool lambda (#5054)
1 parent 6d3b7db commit efbaa6f

File tree

7 files changed

+92
-6
lines changed

7 files changed

+92
-6
lines changed

lambdas/functions/control-plane/src/aws/runners.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DefaultTargetCapacityType, SpotAllocationStrategy } from '@aws-sdk/client-ec2';
2+
import { LambdaRunnerSource } from '../scale-runners/scale-up';
23

34
export type RunnerType = 'Org' | 'Repo';
45

@@ -42,6 +43,7 @@ export interface RunnerInputParameters {
4243
instanceAllocationStrategy: SpotAllocationStrategy;
4344
};
4445
numberOfRunners: number;
46+
source: LambdaRunnerSource;
4547
amiIdSsmParameterName?: string;
4648
tracingEnabled?: boolean;
4749
onDemandFailoverOnError?: string[];

lambdas/functions/control-plane/src/aws/runners.test.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
2121
import ScaleError from './../scale-runners/ScaleError';
2222
import { createRunner, listEC2Runners, tag, terminateRunner, untag } from './runners';
2323
import type { RunnerInfo, RunnerInputParameters, RunnerType } from './runners.d';
24+
import { LambdaRunnerSource } from '../scale-runners/scale-up';
2425

2526
process.env.AWS_REGION = 'eu-east-1';
2627
const mockEC2Client = mockClient(EC2Client);
@@ -318,13 +319,16 @@ describe('create runner', () => {
318319
allocationStrategy: SpotAllocationStrategy.CAPACITY_OPTIMIZED,
319320
capacityType: 'spot',
320321
type: 'Org',
322+
scaleErrors: [],
323+
source: 'scale-up-lambda',
321324
};
322325

323326
const defaultExpectedFleetRequestValues: ExpectedFleetRequestValues = {
324327
type: 'Org',
325328
capacityType: 'spot',
326329
allocationStrategy: SpotAllocationStrategy.CAPACITY_OPTIMIZED,
327330
totalTargetCapacity: 1,
331+
source: 'scale-up-lambda',
328332
};
329333

330334
beforeEach(() => {
@@ -365,6 +369,25 @@ describe('create runner', () => {
365369
});
366370
});
367371

372+
it('calls create fleet of multiple instances with pool-lambda source when specified', async () => {
373+
const instances = [{ InstanceIds: ['i-1234', 'i-5678', 'i-9012'] }];
374+
375+
mockEC2Client.on(CreateFleetCommand).resolves({ Instances: instances });
376+
377+
await createRunner({
378+
...createRunnerConfig({ ...defaultRunnerConfig, source: 'pool-lambda' }),
379+
numberOfRunners: 3,
380+
});
381+
382+
expect(mockEC2Client).toHaveReceivedCommandWith(CreateFleetCommand, {
383+
...expectedCreateFleetRequest({
384+
...defaultExpectedFleetRequestValues,
385+
totalTargetCapacity: 3,
386+
source: 'pool-lambda',
387+
}),
388+
});
389+
});
390+
368391
it('calls create fleet of 1 instance with the on-demand capacity', async () => {
369392
await createRunner(createRunnerConfig({ ...defaultRunnerConfig, capacityType: 'on-demand' }));
370393
expect(mockEC2Client).toHaveReceivedCommandWith(CreateFleetCommand, {
@@ -425,6 +448,28 @@ describe('create runner', () => {
425448
}),
426449
});
427450
});
451+
452+
it('calls create fleet with source set to scale-up-lambda when source is specified', async () => {
453+
await createRunner(createRunnerConfig({ ...defaultRunnerConfig, source: 'scale-up-lambda' }));
454+
455+
expect(mockEC2Client).toHaveReceivedCommandWith(CreateFleetCommand, {
456+
...expectedCreateFleetRequest({
457+
...defaultExpectedFleetRequestValues,
458+
source: 'scale-up-lambda',
459+
}),
460+
});
461+
});
462+
463+
it('calls create fleet with source set to pool-lambda when source is specified', async () => {
464+
await createRunner(createRunnerConfig({ ...defaultRunnerConfig, source: 'pool-lambda' }));
465+
466+
expect(mockEC2Client).toHaveReceivedCommandWith(CreateFleetCommand, {
467+
...expectedCreateFleetRequest({
468+
...defaultExpectedFleetRequestValues,
469+
source: 'pool-lambda',
470+
}),
471+
});
472+
});
428473
});
429474

430475
describe('create runner with errors', () => {
@@ -433,12 +478,14 @@ describe('create runner with errors', () => {
433478
capacityType: 'spot',
434479
type: 'Repo',
435480
scaleErrors: ['UnfulfillableCapacity', 'MaxSpotInstanceCountExceeded'],
481+
source: 'scale-up-lambda',
436482
};
437483
const defaultExpectedFleetRequestValues: ExpectedFleetRequestValues = {
438484
type: 'Repo',
439485
capacityType: 'spot',
440486
allocationStrategy: SpotAllocationStrategy.CAPACITY_OPTIMIZED,
441487
totalTargetCapacity: 1,
488+
source: 'scale-up-lambda',
442489
};
443490
beforeEach(() => {
444491
vi.clearAllMocks();
@@ -546,12 +593,15 @@ describe('create runner with errors fail over to OnDemand', () => {
546593
capacityType: 'spot',
547594
type: 'Repo',
548595
onDemandFailoverOnError: ['InsufficientInstanceCapacity'],
596+
scaleErrors: [],
597+
source: 'scale-up-lambda',
549598
};
550599
const defaultExpectedFleetRequestValues: ExpectedFleetRequestValues = {
551600
type: 'Repo',
552601
capacityType: 'spot',
553602
allocationStrategy: SpotAllocationStrategy.CAPACITY_OPTIMIZED,
554603
totalTargetCapacity: 1,
604+
source: 'scale-up-lambda',
555605
};
556606
beforeEach(() => {
557607
vi.clearAllMocks();
@@ -704,6 +754,7 @@ interface RunnerConfig {
704754
tracingEnabled?: boolean;
705755
onDemandFailoverOnError?: string[];
706756
scaleErrors: string[];
757+
source: LambdaRunnerSource;
707758
}
708759

709760
function createRunnerConfig(runnerConfig: RunnerConfig): RunnerInputParameters {
@@ -724,6 +775,7 @@ function createRunnerConfig(runnerConfig: RunnerConfig): RunnerInputParameters {
724775
tracingEnabled: runnerConfig.tracingEnabled,
725776
onDemandFailoverOnError: runnerConfig.onDemandFailoverOnError,
726777
scaleErrors: runnerConfig.scaleErrors,
778+
source: runnerConfig.source,
727779
};
728780
}
729781

@@ -735,14 +787,15 @@ interface ExpectedFleetRequestValues {
735787
totalTargetCapacity: number;
736788
imageId?: string;
737789
tracingEnabled?: boolean;
790+
source: LambdaRunnerSource;
738791
}
739792

740793
function expectedCreateFleetRequest(expectedValues: ExpectedFleetRequestValues): CreateFleetCommandInput {
741794
const tags = [
742795
{ Key: 'ghr:Application', Value: 'github-action-runner' },
743796
{
744797
Key: 'ghr:created_by',
745-
Value: expectedValues.totalTargetCapacity > 1 ? 'pool-lambda' : 'scale-up-lambda',
798+
Value: expectedValues.source,
746799
},
747800
{ Key: 'ghr:Type', Value: expectedValues.type },
748801
{ Key: 'ghr:Owner', Value: REPO_NAME },

lambdas/functions/control-plane/src/aws/runners.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ async function createInstances(
241241
) {
242242
const tags = [
243243
{ Key: 'ghr:Application', Value: 'github-action-runner' },
244-
{ Key: 'ghr:created_by', Value: runnerParameters.numberOfRunners === 1 ? 'scale-up-lambda' : 'pool-lambda' },
244+
{ Key: 'ghr:created_by', Value: runnerParameters.source },
245245
{ Key: 'ghr:Type', Value: runnerParameters.runnerType },
246246
{ Key: 'ghr:Owner', Value: runnerParameters.runnerOwner },
247247
];

lambdas/functions/control-plane/src/pool/pool.test.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,13 @@ describe('Test simple pool.', () => {
192192
it('Top up pool with pool size 2 registered.', async () => {
193193
await adjust({ poolSize: 3 });
194194
expect(createRunners).toHaveBeenCalledTimes(1);
195-
expect(createRunners).toHaveBeenCalledWith(expect.anything(), expect.anything(), 1, expect.anything());
195+
expect(createRunners).toHaveBeenCalledWith(
196+
expect.anything(),
197+
expect.anything(),
198+
1,
199+
expect.anything(),
200+
'pool-lambda',
201+
);
196202
});
197203

198204
it('Should not top up if pool size is reached.', async () => {
@@ -268,7 +274,13 @@ describe('Test simple pool.', () => {
268274
it('Top up if the pool size is set to 5', async () => {
269275
await adjust({ poolSize: 5 });
270276
// 2 idle, top up with 3 to match a pool of 5
271-
expect(createRunners).toHaveBeenCalledWith(expect.anything(), expect.anything(), 3, expect.anything());
277+
expect(createRunners).toHaveBeenCalledWith(
278+
expect.anything(),
279+
expect.anything(),
280+
3,
281+
expect.anything(),
282+
'pool-lambda',
283+
);
272284
});
273285
});
274286

@@ -283,7 +295,13 @@ describe('Test simple pool.', () => {
283295
it('Top up if the pool size is set to 5', async () => {
284296
await adjust({ poolSize: 5 });
285297
// 2 idle, top up with 3 to match a pool of 5
286-
expect(createRunners).toHaveBeenCalledWith(expect.anything(), expect.anything(), 3, expect.anything());
298+
expect(createRunners).toHaveBeenCalledWith(
299+
expect.anything(),
300+
expect.anything(),
301+
3,
302+
expect.anything(),
303+
'pool-lambda',
304+
);
287305
});
288306
});
289307

@@ -333,7 +351,13 @@ describe('Test simple pool.', () => {
333351

334352
await adjust({ poolSize: 5 });
335353
// 2 idle, 2 prefixed idle top up with 1 to match a pool of 5
336-
expect(createRunners).toHaveBeenCalledWith(expect.anything(), expect.anything(), 1, expect.anything());
354+
expect(createRunners).toHaveBeenCalledWith(
355+
expect.anything(),
356+
expect.anything(),
357+
1,
358+
expect.anything(),
359+
'pool-lambda',
360+
);
337361
});
338362
});
339363
});

lambdas/functions/control-plane/src/pool/pool.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export async function adjust(event: PoolEvent): Promise<void> {
106106
},
107107
topUp,
108108
githubInstallationClient,
109+
'pool-lambda',
109110
);
110111
} else {
111112
logger.info(`Pool will not be topped up. Found ${numberOfRunnersInPool} managed idle runners.`);

lambdas/functions/control-plane/src/scale-runners/scale-up.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ const EXPECTED_RUNNER_PARAMS: RunnerInputParameters = {
113113
tracingEnabled: false,
114114
onDemandFailoverOnError: [],
115115
scaleErrors: ['UnfulfillableCapacity', 'MaxSpotInstanceCountExceeded', 'TargetCapacityLimitExceededException'],
116+
source: 'scale-up-lambda',
116117
};
117118
let expectedRunnerParams: RunnerInputParameters;
118119

lambdas/functions/control-plane/src/scale-runners/scale-up.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { publishRetryMessage } from './job-retry';
1111

1212
const logger = createChildLogger('scale-up');
1313

14+
export type LambdaRunnerSource = 'scale-up-lambda' | 'pool-lambda';
15+
1416
export interface RunnerGroup {
1517
name: string;
1618
id: number;
@@ -248,11 +250,13 @@ export async function createRunners(
248250
ec2RunnerConfig: CreateEC2RunnerConfig,
249251
numberOfRunners: number,
250252
ghClient: Octokit,
253+
source: LambdaRunnerSource = 'scale-up-lambda',
251254
): Promise<string[]> {
252255
const instances = await createRunner({
253256
runnerType: githubRunnerConfig.runnerType,
254257
runnerOwner: githubRunnerConfig.runnerOwner,
255258
numberOfRunners,
259+
source,
256260
...ec2RunnerConfig,
257261
});
258262
if (instances.length !== 0) {
@@ -507,6 +511,7 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
507511
},
508512
newRunners,
509513
githubInstallationClient,
514+
'scale-up-lambda',
510515
);
511516

512517
// Not all runners we wanted were created, let's reject enough items so that

0 commit comments

Comments
 (0)