Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions lambdas/functions/webhook/src/runners/dispatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,54 @@ describe('Dispatcher', () => {
});
});

it('should dispatch to lowest priority pool when multiple pools match the same labels', async () => {
config = await createConfig(undefined, [
{
...runnerConfig[0],
id: 'large-ondemand',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux', 'x64', 'large', 'ondemand']],
exactMatch: true,
priority: 100,
},
},
{
...runnerConfig[0],
id: 'medium-spot',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux', 'x64', 'medium', 'spot']],
exactMatch: true,
priority: 5,
},
},
{
...runnerConfig[0],
id: 'small-spot',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux', 'x64', 'small', 'spot']],
exactMatch: true,
priority: 1,
},
},
]);

// Job requests only the common subset — all pools match
const event = {
...workFlowJobEvent,
workflow_job: {
...workFlowJobEvent.workflow_job,
labels: ['self-hosted', 'linux', 'x64'],
},
} as unknown as WorkflowJobEvent;
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toHaveBeenCalledWith(
expect.objectContaining({
queueId: 'small-spot',
}),
);
});

it('should not accept jobs where not all labels are supported (single matcher).', async () => {
config = await createConfig(undefined, [
{
Expand Down
7 changes: 5 additions & 2 deletions lambdas/functions/webhook/src/runners/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ async function handleWorkflowJob(
`Job ID: ${body.workflow_job.id}, Job Name: ${body.workflow_job.name}, ` +
`Run ID: ${body.workflow_job.run_id}, Labels: ${JSON.stringify(body.workflow_job.labels)}`,
);
// sort the queuesConfig by order of matcher config exact match, with all true matches lined up ahead.
// Sort: exact-match first, then by ascending priority (lower = preferred/cheaper).
matcherConfig.sort((a, b) => {
return a.matcherConfig.exactMatch === b.matcherConfig.exactMatch ? 0 : a.matcherConfig.exactMatch ? -1 : 1;
if (a.matcherConfig.exactMatch !== b.matcherConfig.exactMatch) {
return a.matcherConfig.exactMatch ? -1 : 1;
}
return (a.matcherConfig.priority ?? 999) - (b.matcherConfig.priority ?? 999);
});
for (const queue of matcherConfig) {
if (canRunJob(body.workflow_job.labels, queue.matcherConfig.labelMatchers, queue.matcherConfig.exactMatch)) {
Expand Down
1 change: 1 addition & 0 deletions lambdas/functions/webhook/src/sqs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ActionRequestMessage {
export interface MatcherConfig {
labelMatchers: string[][];
exactMatch: boolean;
priority?: number;
}

export type RunnerConfig = RunnerMatcherConfig[];
Expand Down