mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-29 01:15:48 +02:00
feat: maintenance mode disables scheduler (#3854)
This commit is contained in:
parent
ab4ff29407
commit
5ac575389e
@ -43,7 +43,7 @@ async function createApp(
|
|||||||
const db = createDb(config);
|
const db = createDb(config);
|
||||||
const stores = createStores(config, db);
|
const stores = createStores(config, db);
|
||||||
const services = createServices(stores, config, db);
|
const services = createServices(stores, config, db);
|
||||||
scheduleServices(services);
|
await scheduleServices(services);
|
||||||
|
|
||||||
const metricsMonitor = createMetricsMonitor();
|
const metricsMonitor = createMetricsMonitor();
|
||||||
const unleashSession = sessionDb(config, db);
|
const unleashSession = sessionDb(config, db);
|
||||||
|
@ -60,7 +60,9 @@ import ConfigurationRevisionService from '../features/feature-toggle/configurati
|
|||||||
import { createFeatureToggleService } from '../features';
|
import { createFeatureToggleService } from '../features';
|
||||||
|
|
||||||
// TODO: will be moved to scheduler feature directory
|
// TODO: will be moved to scheduler feature directory
|
||||||
export const scheduleServices = (services: IUnleashServices): void => {
|
export const scheduleServices = async (
|
||||||
|
services: IUnleashServices,
|
||||||
|
): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
schedulerService,
|
schedulerService,
|
||||||
apiTokenService,
|
apiTokenService,
|
||||||
@ -69,8 +71,13 @@ export const scheduleServices = (services: IUnleashServices): void => {
|
|||||||
projectService,
|
projectService,
|
||||||
projectHealthService,
|
projectHealthService,
|
||||||
configurationRevisionService,
|
configurationRevisionService,
|
||||||
|
maintenanceService,
|
||||||
} = services;
|
} = services;
|
||||||
|
|
||||||
|
if (await maintenanceService.isMaintenanceMode()) {
|
||||||
|
schedulerService.pause();
|
||||||
|
}
|
||||||
|
|
||||||
schedulerService.schedule(
|
schedulerService.schedule(
|
||||||
apiTokenService.fetchActiveTokens.bind(apiTokenService),
|
apiTokenService.fetchActiveTokens.bind(apiTokenService),
|
||||||
minutesToMilliseconds(1),
|
minutesToMilliseconds(1),
|
||||||
@ -224,14 +231,15 @@ export const createServices = (
|
|||||||
versionService,
|
versionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const schedulerService = new SchedulerService(config.getLogger);
|
||||||
|
|
||||||
const maintenanceService = new MaintenanceService(
|
const maintenanceService = new MaintenanceService(
|
||||||
stores,
|
stores,
|
||||||
config,
|
config,
|
||||||
settingService,
|
settingService,
|
||||||
|
schedulerService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const schedulerService = new SchedulerService(config.getLogger);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accessService,
|
accessService,
|
||||||
accountService,
|
accountService,
|
||||||
|
@ -5,6 +5,7 @@ import { IEventStore } from '../types/stores/event-store';
|
|||||||
import SettingService from './setting-service';
|
import SettingService from './setting-service';
|
||||||
import { maintenanceSettingsKey } from '../types/settings/maintenance-settings';
|
import { maintenanceSettingsKey } from '../types/settings/maintenance-settings';
|
||||||
import { MaintenanceSchema } from '../openapi/spec/maintenance-schema';
|
import { MaintenanceSchema } from '../openapi/spec/maintenance-schema';
|
||||||
|
import { SchedulerService } from './scheduler-service';
|
||||||
|
|
||||||
export default class MaintenanceService {
|
export default class MaintenanceService {
|
||||||
private config: IUnleashConfig;
|
private config: IUnleashConfig;
|
||||||
@ -17,6 +18,8 @@ export default class MaintenanceService {
|
|||||||
|
|
||||||
private settingService: SettingService;
|
private settingService: SettingService;
|
||||||
|
|
||||||
|
private schedulerService: SchedulerService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
patStore,
|
patStore,
|
||||||
@ -24,12 +27,14 @@ export default class MaintenanceService {
|
|||||||
}: Pick<IUnleashStores, 'patStore' | 'eventStore'>,
|
}: Pick<IUnleashStores, 'patStore' | 'eventStore'>,
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
settingService: SettingService,
|
settingService: SettingService,
|
||||||
|
schedulerService: SchedulerService,
|
||||||
) {
|
) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.logger = config.getLogger('services/pat-service.ts');
|
this.logger = config.getLogger('services/pat-service.ts');
|
||||||
this.patStore = patStore;
|
this.patStore = patStore;
|
||||||
this.eventStore = eventStore;
|
this.eventStore = eventStore;
|
||||||
this.settingService = settingService;
|
this.settingService = settingService;
|
||||||
|
this.schedulerService = schedulerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
async isMaintenanceMode(): Promise<boolean> {
|
async isMaintenanceMode(): Promise<boolean> {
|
||||||
@ -51,6 +56,11 @@ export default class MaintenanceService {
|
|||||||
setting: MaintenanceSchema,
|
setting: MaintenanceSchema,
|
||||||
user: string,
|
user: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (setting.enabled) {
|
||||||
|
this.schedulerService.pause();
|
||||||
|
} else {
|
||||||
|
this.schedulerService.resume();
|
||||||
|
}
|
||||||
return this.settingService.insert(
|
return this.settingService.insert(
|
||||||
maintenanceSettingsKey,
|
maintenanceSettingsKey,
|
||||||
setting,
|
setting,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { SchedulerService } from './scheduler-service';
|
import { SchedulerService } from './scheduler-service';
|
||||||
|
import { LogProvider } from '../logger';
|
||||||
|
|
||||||
function ms(timeMs) {
|
function ms(timeMs) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, timeMs));
|
return new Promise((resolve) => setTimeout(resolve, timeMs));
|
||||||
@ -6,7 +7,7 @@ function ms(timeMs) {
|
|||||||
|
|
||||||
const getLogger = () => {
|
const getLogger = () => {
|
||||||
const records: any[] = [];
|
const records: any[] = [];
|
||||||
const logger = () => ({
|
const logger: LogProvider = () => ({
|
||||||
error(...args: any[]) {
|
error(...args: any[]) {
|
||||||
records.push(args);
|
records.push(args);
|
||||||
},
|
},
|
||||||
@ -14,17 +15,15 @@ const getLogger = () => {
|
|||||||
info() {},
|
info() {},
|
||||||
warn() {},
|
warn() {},
|
||||||
fatal() {},
|
fatal() {},
|
||||||
getRecords() {
|
|
||||||
return records;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
logger.getRecords = () => records;
|
const getRecords = () => records;
|
||||||
|
|
||||||
return logger;
|
return { logger, getRecords };
|
||||||
};
|
};
|
||||||
|
|
||||||
test('Schedules job immediately', async () => {
|
test('Schedules job immediately', async () => {
|
||||||
const schedulerService = new SchedulerService(getLogger());
|
const { logger } = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
const job = jest.fn();
|
const job = jest.fn();
|
||||||
|
|
||||||
schedulerService.schedule(job, 10);
|
schedulerService.schedule(job, 10);
|
||||||
@ -33,8 +32,21 @@ test('Schedules job immediately', async () => {
|
|||||||
schedulerService.stop();
|
schedulerService.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Does not schedule job immediately when paused', async () => {
|
||||||
|
const { logger } = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
|
const job = jest.fn();
|
||||||
|
|
||||||
|
schedulerService.pause();
|
||||||
|
schedulerService.schedule(job, 10);
|
||||||
|
|
||||||
|
expect(job).toBeCalledTimes(0);
|
||||||
|
schedulerService.stop();
|
||||||
|
});
|
||||||
|
|
||||||
test('Can schedule a single regular job', async () => {
|
test('Can schedule a single regular job', async () => {
|
||||||
const schedulerService = new SchedulerService(getLogger());
|
const { logger } = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
const job = jest.fn();
|
const job = jest.fn();
|
||||||
|
|
||||||
schedulerService.schedule(job, 50);
|
schedulerService.schedule(job, 50);
|
||||||
@ -44,8 +56,36 @@ test('Can schedule a single regular job', async () => {
|
|||||||
schedulerService.stop();
|
schedulerService.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Scheduled job ignored in a paused mode', async () => {
|
||||||
|
const { logger } = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
|
const job = jest.fn();
|
||||||
|
|
||||||
|
schedulerService.pause();
|
||||||
|
schedulerService.schedule(job, 50);
|
||||||
|
await ms(75);
|
||||||
|
|
||||||
|
expect(job).toBeCalledTimes(0);
|
||||||
|
schedulerService.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can resume paused job', async () => {
|
||||||
|
const { logger } = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
|
const job = jest.fn();
|
||||||
|
|
||||||
|
schedulerService.pause();
|
||||||
|
schedulerService.schedule(job, 50);
|
||||||
|
schedulerService.resume();
|
||||||
|
await ms(75);
|
||||||
|
|
||||||
|
expect(job).toBeCalledTimes(1);
|
||||||
|
schedulerService.stop();
|
||||||
|
});
|
||||||
|
|
||||||
test('Can schedule multiple jobs at the same interval', async () => {
|
test('Can schedule multiple jobs at the same interval', async () => {
|
||||||
const schedulerService = new SchedulerService(getLogger());
|
const { logger } = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
const job = jest.fn();
|
const job = jest.fn();
|
||||||
const anotherJob = jest.fn();
|
const anotherJob = jest.fn();
|
||||||
|
|
||||||
@ -59,7 +99,8 @@ test('Can schedule multiple jobs at the same interval', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can schedule multiple jobs at the different intervals', async () => {
|
test('Can schedule multiple jobs at the different intervals', async () => {
|
||||||
const schedulerService = new SchedulerService(getLogger());
|
const { logger } = getLogger();
|
||||||
|
const schedulerService = new SchedulerService(logger);
|
||||||
const job = jest.fn();
|
const job = jest.fn();
|
||||||
const anotherJob = jest.fn();
|
const anotherJob = jest.fn();
|
||||||
|
|
||||||
@ -73,7 +114,7 @@ test('Can schedule multiple jobs at the different intervals', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can handle crash of a async job', async () => {
|
test('Can handle crash of a async job', async () => {
|
||||||
const logger = getLogger();
|
const { logger, getRecords } = getLogger();
|
||||||
const schedulerService = new SchedulerService(logger);
|
const schedulerService = new SchedulerService(logger);
|
||||||
const job = async () => {
|
const job = async () => {
|
||||||
await Promise.reject('async reason');
|
await Promise.reject('async reason');
|
||||||
@ -83,14 +124,14 @@ test('Can handle crash of a async job', async () => {
|
|||||||
await ms(75);
|
await ms(75);
|
||||||
|
|
||||||
schedulerService.stop();
|
schedulerService.stop();
|
||||||
expect(logger.getRecords()).toEqual([
|
expect(getRecords()).toEqual([
|
||||||
['scheduled job failed', 'async reason'],
|
['scheduled job failed', 'async reason'],
|
||||||
['scheduled job failed', 'async reason'],
|
['scheduled job failed', 'async reason'],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can handle crash of a sync job', async () => {
|
test('Can handle crash of a sync job', async () => {
|
||||||
const logger = getLogger();
|
const { logger, getRecords } = getLogger();
|
||||||
const schedulerService = new SchedulerService(logger);
|
const schedulerService = new SchedulerService(logger);
|
||||||
const job = () => {
|
const job = () => {
|
||||||
throw new Error('sync reason');
|
throw new Error('sync reason');
|
||||||
@ -100,7 +141,7 @@ test('Can handle crash of a sync job', async () => {
|
|||||||
await ms(75);
|
await ms(75);
|
||||||
|
|
||||||
schedulerService.stop();
|
schedulerService.stop();
|
||||||
expect(logger.getRecords()).toEqual([
|
expect(getRecords()).toEqual([
|
||||||
['scheduled job failed', new Error('sync reason')],
|
['scheduled job failed', new Error('sync reason')],
|
||||||
['scheduled job failed', new Error('sync reason')],
|
['scheduled job failed', new Error('sync reason')],
|
||||||
]);
|
]);
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import { Logger, LogProvider } from '../logger';
|
import { Logger, LogProvider } from '../logger';
|
||||||
|
|
||||||
|
export type SchedulerMode = 'active' | 'paused';
|
||||||
|
|
||||||
export class SchedulerService {
|
export class SchedulerService {
|
||||||
private intervalIds: NodeJS.Timer[] = [];
|
private intervalIds: NodeJS.Timer[] = [];
|
||||||
|
|
||||||
|
private mode: SchedulerMode;
|
||||||
|
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(getLogger: LogProvider) {
|
constructor(getLogger: LogProvider) {
|
||||||
this.logger = getLogger('/services/scheduler-service.ts');
|
this.logger = getLogger('/services/scheduler-service.ts');
|
||||||
|
this.mode = 'active';
|
||||||
}
|
}
|
||||||
|
|
||||||
async schedule(
|
async schedule(
|
||||||
@ -16,14 +21,18 @@ export class SchedulerService {
|
|||||||
this.intervalIds.push(
|
this.intervalIds.push(
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
await scheduledFunction();
|
if (this.mode === 'active') {
|
||||||
|
await scheduledFunction();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error('scheduled job failed', e);
|
this.logger.error('scheduled job failed', e);
|
||||||
}
|
}
|
||||||
}, timeMs).unref(),
|
}, timeMs).unref(),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await scheduledFunction();
|
if (this.mode === 'active') {
|
||||||
|
await scheduledFunction();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error('scheduled job failed', e);
|
this.logger.error('scheduled job failed', e);
|
||||||
}
|
}
|
||||||
@ -32,4 +41,16 @@ export class SchedulerService {
|
|||||||
stop(): void {
|
stop(): void {
|
||||||
this.intervalIds.forEach(clearInterval);
|
this.intervalIds.forEach(clearInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pause(): void {
|
||||||
|
this.mode = 'paused';
|
||||||
|
}
|
||||||
|
|
||||||
|
resume(): void {
|
||||||
|
this.mode = 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
getMode(): SchedulerMode {
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user