Skip to content

Commit 5bfc263

Browse files
test: add tests for dynamic labels
1 parent a1906e3 commit 5bfc263

2 files changed

Lines changed: 200 additions & 0 deletions

File tree

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

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,192 @@ describe('scaleUp with GHES', () => {
405405
10000,
406406
);
407407
});
408+
409+
describe('Dynamic EC2 Configuration', () => {
410+
beforeEach(() => {
411+
process.env.ENABLE_ORGANIZATION_RUNNERS = 'true';
412+
process.env.ENABLE_DYNAMIC_EC2_CONFIG = 'true';
413+
process.env.ENABLE_EPHEMERAL_RUNNERS = 'true';
414+
process.env.ENABLE_JOB_QUEUED_CHECK = 'false';
415+
process.env.RUNNER_LABELS = 'base-label';
416+
process.env.INSTANCE_TYPES = 't3.medium,t3.large';
417+
process.env.RUNNER_NAME_PREFIX = 'unit-test';
418+
expectedRunnerParams = { ...EXPECTED_RUNNER_PARAMS };
419+
mockSSMClient.reset();
420+
});
421+
422+
it('appends EC2 labels to existing runner labels when EC2 labels are present', async () => {
423+
const testDataWithEc2Labels = [
424+
{
425+
...TEST_DATA_SINGLE,
426+
labels: ['ghr-ec2-instance-type:c5.2xlarge', 'ghr-ec2-custom:value'],
427+
messageId: 'test-1',
428+
},
429+
];
430+
431+
await scaleUpModule.scaleUp(testDataWithEc2Labels);
432+
433+
// Verify createRunner was called with EC2 instance type extracted from labels
434+
expect(createRunner).toBeCalledWith(
435+
expect.objectContaining({
436+
ec2instanceCriteria: expect.objectContaining({
437+
instanceTypes: ['c5.2xlarge'],
438+
}),
439+
}),
440+
);
441+
});
442+
443+
it('extracts instance type from EC2 labels and overrides default instance types', async () => {
444+
const testDataWithEc2Labels = [
445+
{
446+
...TEST_DATA_SINGLE,
447+
labels: ['ghr-ec2-instance-type:m5.xlarge', 'ghr-ec2-disk:100'],
448+
messageId: 'test-2',
449+
},
450+
];
451+
452+
await scaleUpModule.scaleUp(testDataWithEc2Labels);
453+
454+
expect(createRunner).toBeCalledWith(
455+
expect.objectContaining({
456+
ec2instanceCriteria: expect.objectContaining({
457+
instanceTypes: ['m5.xlarge'],
458+
}),
459+
}),
460+
);
461+
});
462+
463+
it('uses default instance types when no instance type EC2 label is provided', async () => {
464+
const testDataWithEc2Labels = [
465+
{
466+
...TEST_DATA_SINGLE,
467+
labels: ['ghr-ec2-custom:value'],
468+
messageId: 'test-3',
469+
},
470+
];
471+
472+
await scaleUpModule.scaleUp(testDataWithEc2Labels);
473+
474+
// Should use the default INSTANCE_TYPES from environment
475+
expect(createRunner).toBeCalledWith(
476+
expect.objectContaining({
477+
ec2instanceCriteria: expect.objectContaining({
478+
instanceTypes: ['t3.medium', 't3.large'],
479+
}),
480+
}),
481+
);
482+
});
483+
484+
it('does not modify labels when EC2 labels are not present', async () => {
485+
const testDataWithoutEc2Labels = [
486+
{
487+
...TEST_DATA_SINGLE,
488+
labels: ['regular-label', 'another-label'],
489+
messageId: 'test-4',
490+
},
491+
];
492+
493+
await scaleUpModule.scaleUp(testDataWithoutEc2Labels);
494+
495+
// Should use default instance types
496+
expect(createRunner).toBeCalledWith(
497+
expect.objectContaining({
498+
ec2instanceCriteria: expect.objectContaining({
499+
instanceTypes: ['t3.medium', 't3.large'],
500+
}),
501+
}),
502+
);
503+
});
504+
505+
it('handles messages with no labels gracefully', async () => {
506+
const testDataWithNoLabels = [
507+
{
508+
...TEST_DATA_SINGLE,
509+
labels: undefined,
510+
messageId: 'test-5',
511+
},
512+
];
513+
514+
await scaleUpModule.scaleUp(testDataWithNoLabels);
515+
516+
expect(createRunner).toBeCalledWith(
517+
expect.objectContaining({
518+
ec2instanceCriteria: expect.objectContaining({
519+
instanceTypes: ['t3.medium', 't3.large'],
520+
}),
521+
}),
522+
);
523+
});
524+
525+
it('handles empty labels array', async () => {
526+
const testDataWithEmptyLabels = [
527+
{
528+
...TEST_DATA_SINGLE,
529+
labels: [],
530+
messageId: 'test-6',
531+
},
532+
];
533+
534+
await scaleUpModule.scaleUp(testDataWithEmptyLabels);
535+
536+
expect(createRunner).toBeCalledWith(
537+
expect.objectContaining({
538+
ec2instanceCriteria: expect.objectContaining({
539+
instanceTypes: ['t3.medium', 't3.large'],
540+
}),
541+
}),
542+
);
543+
});
544+
545+
it('does not process EC2 labels when ENABLE_DYNAMIC_EC2_CONFIG is disabled', async () => {
546+
process.env.ENABLE_DYNAMIC_EC2_CONFIG = 'false';
547+
548+
const testDataWithEc2Labels = [
549+
{
550+
...TEST_DATA_SINGLE,
551+
labels: ['ghr-ec2-instance-type:c5.4xlarge'],
552+
messageId: 'test-7',
553+
},
554+
];
555+
556+
await scaleUpModule.scaleUp(testDataWithEc2Labels);
557+
558+
// Should ignore EC2 labels and use default instance types
559+
expect(createRunner).toBeCalledWith(
560+
expect.objectContaining({
561+
ec2instanceCriteria: expect.objectContaining({
562+
instanceTypes: ['t3.medium', 't3.large'],
563+
}),
564+
}),
565+
);
566+
});
567+
568+
it('handles multiple EC2 labels correctly', async () => {
569+
const testDataWithMultipleEc2Labels = [
570+
{
571+
...TEST_DATA_SINGLE,
572+
labels: [
573+
'regular-label',
574+
'ghr-ec2-instance-type:r5.2xlarge',
575+
'ghr-ec2-ami:custom-ami',
576+
'ghr-ec2-disk:200',
577+
],
578+
messageId: 'test-8',
579+
},
580+
];
581+
582+
await scaleUpModule.scaleUp(testDataWithMultipleEc2Labels);
583+
584+
expect(createRunner).toBeCalledWith(
585+
expect.objectContaining({
586+
ec2instanceCriteria: expect.objectContaining({
587+
instanceTypes: ['r5.2xlarge'],
588+
}),
589+
}),
590+
);
591+
});
592+
});
593+
408594
describe('on repo level', () => {
409595
beforeEach(() => {
410596
process.env.ENABLE_ORGANIZATION_RUNNERS = 'false';

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,26 +394,40 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
394394
const queuedMessages: ActionRequestMessageSQS[] = [];
395395

396396
if (messages.length > 0 && dynamicEc2ConfigEnabled) {
397+
logger.info('Dynamic EC2 config enabled, processing labels');
398+
397399
const ec2Labels =
398400
messages[0].labels?.filter(l => l.startsWith('ghr-ec2-')) ?? [];
399401

402+
logger.info('EC2 labels detected', { ec2Labels });
403+
400404
if (ec2Labels.length > 0) {
401405
// Append all EC2 labels to runnerLabels
402406
runnerLabels = runnerLabels
403407
? `${runnerLabels},${ec2Labels.join(',')}`
404408
: ec2Labels.join(',');
405409

410+
logger.info('Updated runner labels', { runnerLabels });
411+
406412
// Extract instance type from EC2 labels
407413
const requestedInstanceType = ec2Labels
408414
.find(l => l.startsWith('ghr-ec2-instance-type:'))
409415
?.replace('ghr-ec2-instance-type:', '');
410416

411417
if (requestedInstanceType) {
412418
instanceTypes = [requestedInstanceType];
419+
logger.info('EC2 instance type requested', {
420+
instanceType: requestedInstanceType,
421+
});
422+
} else {
423+
logger.info('No EC2 instance type label found');
413424
}
425+
} else {
426+
logger.info('No EC2 labels found on message');
414427
}
415428
}
416429

430+
417431
for (const message of messages) {
418432
const messageLogger = logger.createChild({
419433
persistentKeys: {

0 commit comments

Comments
 (0)