refactor(core): Make instances more resilient to license sync issues (#16627)

This commit is contained in:
Iván Ovejero
2025-06-24 12:23:14 +02:00
committed by GitHub
parent 1917082de8
commit 88af45f71a
2 changed files with 78 additions and 0 deletions

View File

@@ -214,6 +214,64 @@ describe('License', () => {
const mainPlan = license.getMainPlan();
expect(mainPlan).toBeUndefined();
});
describe('onExpirySoon', () => {
it.each([
{
instanceType: 'main' as const,
isLeader: true,
shouldReload: false,
description: 'Leader main should not reload',
},
{
instanceType: 'main' as const,
isLeader: false,
shouldReload: true,
description: 'Follower main should reload',
},
{
instanceType: 'worker' as const,
isLeader: false,
shouldReload: true,
description: 'Worker should reload',
},
{
instanceType: 'webhook' as const,
isLeader: false,
shouldReload: true,
description: 'Webhook should reload',
},
])('$description', async ({ instanceType, isLeader, shouldReload }) => {
const logger = mockLogger();
const reloadSpy = jest.spyOn(License.prototype, 'reload').mockResolvedValueOnce();
const instanceSettings = mock<InstanceSettings>({ instanceType });
Object.defineProperty(instanceSettings, 'isLeader', { get: () => isLeader });
license = new License(
logger,
instanceSettings,
mock(),
mock(),
mock<GlobalConfig>({ license: licenseConfig }),
);
await license.init();
const licenseManager = LicenseManager as jest.MockedClass<typeof LicenseManager>;
const calls = licenseManager.mock.calls;
const licenseManagerCall = calls[calls.length - 1][0];
const onExpirySoon = licenseManagerCall.onExpirySoon;
if (shouldReload) {
expect(onExpirySoon).toBeDefined();
onExpirySoon!();
expect(reloadSpy).toHaveBeenCalled();
} else {
expect(onExpirySoon).toBeUndefined();
expect(reloadSpy).not.toHaveBeenCalled();
}
});
});
});
describe('License', () => {

View File

@@ -78,6 +78,8 @@ export class License implements LicenseProvider {
const collectPassthroughData = isMainInstance
? async () => await this.licenseMetricsService.collectPassthroughData()
: async () => ({});
const onExpirySoon = !this.instanceSettings.isLeader ? () => this.onExpirySoon() : undefined;
const expirySoonOffsetMins = !this.instanceSettings.isLeader ? 120 : undefined;
const { isLeader } = this.instanceSettings;
const { autoRenewalEnabled } = this.globalConfig.license;
@@ -107,6 +109,8 @@ export class License implements LicenseProvider {
collectPassthroughData,
onFeatureChange,
onLicenseRenewed,
onExpirySoon,
expirySoonOffsetMins,
});
await this.manager.initialize();
@@ -433,4 +437,20 @@ export class License implements LicenseProvider {
disableAutoRenewals() {
this.manager?.disableAutoRenewals();
}
private onExpirySoon() {
this.logger.info('License is about to expire soon, reloading license...');
// reload in background to avoid blocking SDK
void this.reload()
.then(() => {
this.logger.info('Reloaded license on expiry soon');
})
.catch((error) => {
this.logger.error('Failed to reload license on expiry soon', {
error: error instanceof Error ? error.message : error,
});
});
}
}