Skip to content
Open
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
4 changes: 2 additions & 2 deletions lambdas/functions/control-plane/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"@vercel/ncc": "^0.38.4",
"aws-sdk-client-mock": "^4.1.0",
"aws-sdk-client-mock-jest": "^4.1.0",
"moment-timezone": "^0.6.0",
"nock": "^14.0.10",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0"
Expand All @@ -41,7 +40,8 @@
"@octokit/plugin-retry": "8.0.3",
"@octokit/plugin-throttling": "11.0.3",
"@octokit/rest": "22.0.1",
"cron-parser": "^5.4.0"
"cron-parser": "^5.4.0",
"temporal-polyfill": "^0.3.2"
},
"nx": {
"includedScripts": [
Expand Down
13 changes: 9 additions & 4 deletions lambdas/functions/control-plane/src/aws/runners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { createChildLogger } from '@aws-github-runner/aws-powertools-util';
import { getTracedAWSV3Client, tracer } from '@aws-github-runner/aws-powertools-util';
import { getParameter } from '@aws-github-runner/aws-ssm-util';
import moment from 'moment';
import { Temporal } from 'temporal-polyfill';

import ScaleError from './../scale-runners/ScaleError';
import * as Runners from './runners.d';
Expand Down Expand Up @@ -380,7 +380,12 @@ async function createInstancesWithRunInstances(

// If launchTime is undefined, this will return false
export function bootTimeExceeded(ec2Runner: { launchTime?: Date }): boolean {
const runnerBootTimeInMinutes = process.env.RUNNER_BOOT_TIME_IN_MINUTES;
const launchTimePlusBootTime = moment(ec2Runner.launchTime).utc().add(runnerBootTimeInMinutes, 'minutes');
return launchTimePlusBootTime < moment(new Date()).utc();
const runnerBootTimeInMinutes = Number(process.env.RUNNER_BOOT_TIME_IN_MINUTES);
if (ec2Runner.launchTime === undefined || !Number.isFinite(runnerBootTimeInMinutes)) {
return false;
}
const launchTimePlusBootTime = Temporal.Instant.fromEpochMilliseconds(ec2Runner.launchTime.getTime()).add({
minutes: runnerBootTimeInMinutes,
});
return Temporal.Instant.compare(launchTimePlusBootTime, Temporal.Now.instant()) < 0;
}
26 changes: 10 additions & 16 deletions lambdas/functions/control-plane/src/pool/pool.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Octokit } from '@octokit/rest';
import moment from 'moment-timezone';
import { Temporal } from 'temporal-polyfill';
import * as nock from 'nock';

import { listEC2Runners } from '../aws/runners';
Expand Down Expand Up @@ -57,6 +57,10 @@ const cleanEnv = process.env;
const ORG = 'my-org';
const MINIMUM_TIME_RUNNING = 15;

function minutesAgo(minutes: number): Date {
return new Date(Temporal.Now.instant().subtract({ minutes }).epochMilliseconds);
}

const ec2InstancesRegistered = [
{
instanceId: 'i-1-idle',
Expand All @@ -78,9 +82,7 @@ const ec2InstancesRegistered = [
},
{
instanceId: 'i-4-idle-older-than-minimum-time-running',
launchTime: moment(new Date())
.subtract(MINIMUM_TIME_RUNNING + 3, 'minutes')
.toDate(),
launchTime: minutesAgo(MINIMUM_TIME_RUNNING + 3),
type: 'Org',
owner: ORG,
},
Expand Down Expand Up @@ -211,17 +213,13 @@ describe('Test simple pool.', () => {
...ec2InstancesRegistered,
{
instanceId: 'i-4-still-booting',
launchTime: moment(new Date())
.subtract(MINIMUM_TIME_RUNNING - 3, 'minutes')
.toDate(),
launchTime: minutesAgo(MINIMUM_TIME_RUNNING - 3),
type: 'Org',
owner: ORG,
},
{
instanceId: 'i-5-orphan',
launchTime: moment(new Date())
.subtract(MINIMUM_TIME_RUNNING + 3, 'minutes')
.toDate(),
launchTime: minutesAgo(MINIMUM_TIME_RUNNING + 3),
type: 'Org',
owner: ORG,
},
Expand All @@ -242,17 +240,13 @@ describe('Test simple pool.', () => {
...ec2InstancesRegistered,
{
instanceId: 'i-4-still-booting',
launchTime: moment(new Date())
.subtract(MINIMUM_TIME_RUNNING - 3, 'minutes')
.toDate(),
launchTime: minutesAgo(MINIMUM_TIME_RUNNING - 3),
type: 'Org',
owner: ORG,
},
{
instanceId: 'i-5-orphan',
launchTime: moment(new Date())
.subtract(MINIMUM_TIME_RUNNING + 3, 'minutes')
.toDate(),
launchTime: minutesAgo(MINIMUM_TIME_RUNNING + 3),
type: 'Org',
owner: ORG,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import moment from 'moment-timezone';
import { Temporal } from 'temporal-polyfill';

import { EvictionStrategy, ScalingDownConfigList, getEvictionStrategy, getIdleRunnerCount } from './scale-down-config';
import { describe, it, expect } from 'vitest';

const DEFAULT_TIMEZONE = 'America/Los_Angeles';
const DEFAULT_IDLE_COUNT = 1;
const DEFAULT_EVICTION_STRATEGY: EvictionStrategy = 'oldest_first';
const now = moment.tz(new Date(), 'America/Los_Angeles');
const now = Temporal.Now.zonedDateTimeISO('America/Los_Angeles');
const nowDay = now.dayOfWeek % 7;

function getConfig(
cronTabs: string[],
Expand All @@ -28,12 +29,12 @@ describe('scaleDownConfig', () => {
});

it('No active cron configuration', async () => {
const scaleDownConfig = getConfig(['* * * * * ' + ((now.day() + 1) % 7)]);
const scaleDownConfig = getConfig(['* * * * * ' + ((nowDay + 1) % 7)]);
expect(getIdleRunnerCount(scaleDownConfig)).toEqual(0);
});

it('1 of 2 cron configurations be active', async () => {
const scaleDownConfig = getConfig(['* * * * * ' + ((now.day() + 1) % 7), '* * * * * ' + (now.day() % 7)]);
const scaleDownConfig = getConfig(['* * * * * ' + ((nowDay + 1) % 7), '* * * * * ' + (nowDay % 7)]);
expect(getIdleRunnerCount(scaleDownConfig)).toEqual(DEFAULT_IDLE_COUNT);
});
});
Expand All @@ -50,7 +51,7 @@ describe('scaleDownConfig', () => {
});

it('No active cron configuration', async () => {
const scaleDownConfig = getConfig(['* * * * * ' + ((now.day() + 1) % 7)]);
const scaleDownConfig = getConfig(['* * * * * ' + ((nowDay + 1) % 7)]);
expect(getEvictionStrategy(scaleDownConfig)).toEqual(DEFAULT_EVICTION_STRATEGY);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createChildLogger } from '@aws-github-runner/aws-powertools-util';
import parser from 'cron-parser';
import moment from 'moment';
import { Temporal } from 'temporal-polyfill';

export type ScalingDownConfigList = ScalingDownConfig[];
export type EvictionStrategy = 'newest_first' | 'oldest_first';
Expand All @@ -14,12 +14,13 @@ export interface ScalingDownConfig {
const logger = createChildLogger('scale-down-config.ts');

function inPeriod(period: ScalingDownConfig): boolean {
const now = moment(new Date());
const now = Temporal.Now.instant();
const expr = parser.parse(period.cron, {
tz: period.timeZone,
});
const next = moment(expr.next().toDate());
return Math.abs(next.diff(now, 'seconds')) < 5; // we keep a range of 5 seconds
const next = Temporal.Instant.fromEpochMilliseconds(expr.next().toDate().getTime());
const diffInSeconds = Math.trunc(now.until(next).total('seconds'));
return Math.abs(diffInSeconds) < 5; // we keep a range of 5 seconds
}

export function getIdleRunnerCount(scalingDownConfigs: ScalingDownConfigList): number {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Octokit } from '@octokit/rest';
import { RequestError } from '@octokit/request-error';
import moment from 'moment';
import { Temporal } from 'temporal-polyfill';
import nock from 'nock';

import { RunnerInfo, RunnerList } from '../aws/runners.d';
Expand Down Expand Up @@ -76,6 +76,10 @@ export interface TestData {

const cleanEnv = process.env;

function minutesAgo(minutes: number): Date {
return new Date(Temporal.Now.instant().subtract({ minutes }).epochMilliseconds);
}

const ENVIRONMENT = 'unit-test-environment';
const MINIMUM_TIME_RUNNING_IN_MINUTES = 30;
const MINIMUM_BOOT_TIME = 5;
Expand Down Expand Up @@ -680,25 +684,25 @@ describe('Scale down runners', () => {
const runners: RunnerInfo[] = [
{
instanceId: '1',
launchTime: moment(new Date()).subtract(1, 'minute').toDate(),
launchTime: minutesAgo(1),
owner: 'owner',
type: 'type',
},
{
instanceId: '3',
launchTime: moment(new Date()).subtract(3, 'minute').toDate(),
launchTime: minutesAgo(3),
owner: 'owner',
type: 'type',
},
{
instanceId: '2',
launchTime: moment(new Date()).subtract(2, 'minute').toDate(),
launchTime: minutesAgo(2),
owner: 'owner',
type: 'type',
},
{
instanceId: '0',
launchTime: moment(new Date()).subtract(0, 'minute').toDate(),
launchTime: minutesAgo(0),
owner: 'owner',
type: 'type',
},
Expand All @@ -722,7 +726,7 @@ describe('Scale down runners', () => {

it('Should sort runners with equal launch time.', () => {
const runnersTest = [...runners];
const same = moment(new Date()).subtract(4, 'minute').toDate();
const same = minutesAgo(4);
runnersTest.push({
instanceId: '4',
launchTime: same,
Expand Down Expand Up @@ -756,7 +760,7 @@ describe('Scale down runners', () => {
},
{
instanceId: '1',
launchTime: moment(new Date()).subtract(3, 'minute').toDate(),
launchTime: minutesAgo(3),
owner: 'owner',
type: 'type',
},
Expand Down Expand Up @@ -821,7 +825,7 @@ function createRunnerTestData(
): RunnerTestItem {
return {
instanceId: `i-${name}-${type.toLowerCase()}`,
launchTime: moment(new Date()).subtract(minutesLaunchedAgo, 'minutes').toDate(),
launchTime: minutesAgo(minutesLaunchedAgo),
type,
owner: owner
? owner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Octokit } from '@octokit/rest';
import { Endpoints } from '@octokit/types';
import { RequestError } from '@octokit/request-error';
import { createChildLogger } from '@aws-github-runner/aws-powertools-util';
import moment from 'moment';
import { Temporal } from 'temporal-polyfill';

import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth';
import { bootTimeExceeded, listEC2Runners, tag, untag, terminateRunner } from './../aws/runners';
Expand Down Expand Up @@ -121,10 +121,14 @@ async function listGitHubRunners(runner: RunnerInfo): Promise<GhRunners> {
}

function runnerMinimumTimeExceeded(runner: RunnerInfo): boolean {
const minimumRunningTimeInMinutes = process.env.MINIMUM_RUNNING_TIME_IN_MINUTES;
const launchTimePlusMinimum = moment(runner.launchTime).utc().add(minimumRunningTimeInMinutes, 'minutes');
const now = moment(new Date()).utc();
return launchTimePlusMinimum < now;
const minimumRunningTimeInMinutes = Number(process.env.MINIMUM_RUNNING_TIME_IN_MINUTES);
if (runner.launchTime === undefined || !Number.isFinite(minimumRunningTimeInMinutes)) {
return false;
}
const launchTimePlusMinimum = Temporal.Instant.fromEpochMilliseconds(runner.launchTime.getTime()).add({
minutes: minimumRunningTimeInMinutes,
});
return Temporal.Instant.compare(launchTimePlusMinimum, Temporal.Now.instant()) < 0;
}

async function removeRunner(ec2runner: RunnerInfo, ghRunnerIds: number[]): Promise<void> {
Expand Down
34 changes: 17 additions & 17 deletions lambdas/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ __metadata:
aws-sdk-client-mock: "npm:^4.1.0"
aws-sdk-client-mock-jest: "npm:^4.1.0"
cron-parser: "npm:^5.4.0"
moment-timezone: "npm:^0.6.0"
nock: "npm:^14.0.10"
temporal-polyfill: "npm:^0.3.2"
ts-node: "npm:^10.9.2"
ts-node-dev: "npm:^2.0.0"
languageName: unknown
Expand Down Expand Up @@ -8943,22 +8943,6 @@ __metadata:
languageName: node
linkType: hard

"moment-timezone@npm:^0.6.0":
version: 0.6.0
resolution: "moment-timezone@npm:0.6.0"
dependencies:
moment: "npm:^2.29.4"
checksum: 10c0/16164cf321d8be0bf7d43855286b426c94c8200e0634f2e42cf469f591c6a230ac43f37d3826d76b05ac221f69a571400323fb8625e3d4e8669f4d9ab00fe779
languageName: node
linkType: hard

"moment@npm:^2.29.4":
version: 2.29.4
resolution: "moment@npm:2.29.4"
checksum: 10c0/844c6f3ce42862ac9467c8ca4f5e48a00750078682cc5bda1bc0e50cc7ca88e2115a0f932d65a06e4a90e26cb78892be9b3ca3dd6546ca2c4d994cebb787fc2b
languageName: node
linkType: hard

"ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
Expand Down Expand Up @@ -10651,6 +10635,22 @@ __metadata:
languageName: node
linkType: hard

"temporal-polyfill@npm:^0.3.2":
version: 0.3.2
resolution: "temporal-polyfill@npm:0.3.2"
dependencies:
temporal-spec: "npm:0.3.1"
checksum: 10c0/762795c94e1b6cb0574541d0b09f805ff23b0a2c9d0356ab3fc0205e390a3bce73e047de2fdc54c7d149d6e6d781aa1d95aa62afa5de4c83cb79c129b0e502da
languageName: node
linkType: hard

"temporal-spec@npm:0.3.1":
version: 0.3.1
resolution: "temporal-spec@npm:0.3.1"
checksum: 10c0/e38eae391bd59ae675f1925a65c8ef302933cf9ce98efedaf2e69269e8c36f2b5d486cd3084367f04a6c1ea0289b925690c0df20a73f0c4629b531775924e850
languageName: node
linkType: hard

"tinybench@npm:^2.9.0":
version: 2.9.0
resolution: "tinybench@npm:2.9.0"
Expand Down