Skip to content

Commit 5a953b3

Browse files
committed
Move service shutdown logic to each service
1 parent 3f5c0ad commit 5a953b3

File tree

3 files changed

+61
-60
lines changed

3 files changed

+61
-60
lines changed

apps/meteor/ee/app/license/server/license.internalService.ts

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { debounce } from 'underscore';
21
import type { ILicense } from '@rocket.chat/core-services';
32
import { api, ServiceClassInternal } from '@rocket.chat/core-services';
43

@@ -24,54 +23,6 @@ export class LicenseService extends ServiceClassInternal implements ILicense {
2423
onModule((licenseModule) => {
2524
api.broadcast('license.module', licenseModule);
2625
});
27-
28-
/**
29-
* The main idea is if there is no scalability module enabled,
30-
* then we should not allow more than one service per environment.
31-
* So we list the services and nodes, and if there is more than
32-
* one, we inform the service that it should be disabled.
33-
*/
34-
const shutdownServices = debounce(async () => {
35-
if (hasLicense('scalability')) {
36-
return;
37-
}
38-
39-
const services: {
40-
name: string;
41-
nodes: string[];
42-
}[] = await api.call('$node.services');
43-
44-
// Filter only the duplicated services
45-
const duplicated = services.filter((service) => {
46-
return service.name !== '$node' && service.nodes.length > 1;
47-
});
48-
49-
if (!duplicated.length) {
50-
return;
51-
}
52-
53-
const brokers: Record<string, string[]> = Object.fromEntries(
54-
duplicated.map((service) => {
55-
// remove the first node from the list
56-
const [, ...nodes] = service.nodes;
57-
return [service.name, nodes];
58-
}),
59-
);
60-
61-
const duplicatedServicesNames = duplicated.map((service) => service.name);
62-
63-
// send shutdown signal to the duplicated services
64-
api.broadcastToServices(duplicatedServicesNames, 'shutdown', brokers);
65-
}, 1000);
66-
67-
this.onEvent('$services.changed', async ({ localService }) => {
68-
// since this is an internal service, we can ignore this event if the service that triggered is a local service.
69-
// this will also prevent race conditions if a license is not in place when this process is starting up.
70-
if (localService) {
71-
return;
72-
}
73-
shutdownServices();
74-
});
7526
}
7627

7728
async started(): Promise<void> {

apps/meteor/ee/server/NetworkBroker.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { ServiceBroker, Context, ServiceSchema } from 'moleculer';
22
import { asyncLocalStorage } from '@rocket.chat/core-services';
33
import type { IBroker, IBrokerNode, IServiceMetrics, IServiceClass, EventSignatures } from '@rocket.chat/core-services';
44

5+
import { EnterpriseCheck } from './lib/EnterpriseCheck';
6+
57
const events: { [k: string]: string } = {
68
onNodeConnected: '$node.connected',
79
onNodeUpdated: '$node.updated',
@@ -90,17 +92,6 @@ export class NetworkBroker implements IBroker {
9092
return;
9193
}
9294

93-
if (!instance.isInternal()) {
94-
instance.onEvent('shutdown', async (services) => {
95-
if (!services[name]?.includes(this.broker.nodeID)) {
96-
this.broker.logger.info({ msg: 'Not shutting down, different node.', nodeID: this.broker.nodeID });
97-
return;
98-
}
99-
this.broker.logger.warn({ msg: 'Received shutdown event, destroying service.', nodeID: this.broker.nodeID });
100-
this.destroyService(instance);
101-
});
102-
}
103-
10495
const instanceEvents = instance.getEvents();
10596
if (!instanceEvents && !methods.length) {
10697
return;
@@ -111,6 +102,7 @@ export class NetworkBroker implements IBroker {
111102
const service: ServiceSchema = {
112103
name,
113104
actions: {},
105+
mixins: !instance.isInternal() ? [EnterpriseCheck] : [],
114106
...dependencies,
115107
events: instanceEvents.reduce<Record<string, (ctx: Context) => void>>((map, eventName) => {
116108
map[eventName] = /^\$/.test(eventName)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { ServiceSchema } from 'moleculer';
2+
3+
const { LICENSE_CHECK_INTERVAL = '20', MAX_FAILS = '2' } = process.env;
4+
5+
let checkFails = 0;
6+
const maxFails = parseInt(MAX_FAILS) || 2;
7+
8+
export const EnterpriseCheck: ServiceSchema = {
9+
name: 'EnterpriseCheck',
10+
methods: {
11+
/**
12+
* The main idea is if there is no scalability module enabled,
13+
* then we should not allow more than one service per environment.
14+
* So we list the services and nodes, and if there is more than
15+
* one, we say it should be shutdown.
16+
*/
17+
async shouldShutdown(): Promise<boolean> {
18+
const services: {
19+
name: string;
20+
nodes: string[];
21+
}[] = await this.broker.call('$node.services');
22+
23+
const currentService = services.find((service) => {
24+
return service.name === this.name;
25+
});
26+
27+
// if current service is not on the list maybe it is already shut down?
28+
if (!currentService) {
29+
return true;
30+
}
31+
32+
const { nodes } = currentService;
33+
34+
const firstNode = nodes.sort().shift();
35+
36+
// if the first node is the current node and there are others nodes running the same service, then it should shutdown
37+
return firstNode === this.broker.nodeID && nodes.length > 0;
38+
},
39+
},
40+
async started(): Promise<void> {
41+
setInterval(async () => {
42+
const hasLicense = await this.broker.call('license.hasLicense', ['scalability']);
43+
if (hasLicense) {
44+
checkFails = 0;
45+
return;
46+
}
47+
48+
if (++checkFails < maxFails) {
49+
return;
50+
}
51+
52+
const shouldShutdown = await this.shouldShutdown();
53+
if (shouldShutdown) {
54+
this.broker.fatal('Enterprise license not found. Shutting down...');
55+
}
56+
}, (parseInt(LICENSE_CHECK_INTERVAL) || 20) * 1000);
57+
},
58+
};

0 commit comments

Comments
 (0)