Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMPROVE][ENTERPRISE] Improve how micro services are loaded #24388

Merged
merged 14 commits into from
Feb 12, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
7 changes: 2 additions & 5 deletions app/livechat/server/roomAccessValidator.internalService.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ServiceClass } from '../../../server/sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../../server/sdk/types/ServiceClass';
import { IAuthorizationLivechat } from '../../../server/sdk/types/IAuthorizationLivechat';
import { validators } from './roomAccessValidator.compatibility';
import { api } from '../../../server/sdk/api';
import { IRoom } from '../../../definition/IRoom';
import { IUser } from '../../../definition/IUser';

class AuthorizationLivechat extends ServiceClass implements IAuthorizationLivechat {
export class AuthorizationLivechat extends ServiceClassInternal implements IAuthorizationLivechat {
protected name = 'authorization-livechat';

protected internal = true;
Expand All @@ -20,5 +19,3 @@ class AuthorizationLivechat extends ServiceClass implements IAuthorizationLivech
return false;
}
}

api.registerService(new AuthorizationLivechat());
4 changes: 2 additions & 2 deletions app/search/server/search.internalService.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Users } from '../../models/server';
import { settings } from '../../settings/server';
import { searchProviderService } from './service/providerService';
import { ServiceClass } from '../../../server/sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../../server/sdk/types/ServiceClass';
import { api } from '../../../server/sdk/api';
import { searchEventService } from './events/events';

class Search extends ServiceClass {
class Search extends ServiceClassInternal {
protected name = 'search';

protected internal = true;
Expand Down
7 changes: 2 additions & 5 deletions app/tokenpass/server/roomAccessValidator.internalService.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ServiceClass } from '../../../server/sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../../server/sdk/types/ServiceClass';
import { validators } from './roomAccessValidator.compatibility';
import { api } from '../../../server/sdk/api';
import { IAuthorizationTokenpass } from '../../../server/sdk/types/IAuthorizationTokenpass';
import { IRoom } from '../../../definition/IRoom';
import { IUser } from '../../../definition/IUser';

class AuthorizationTokenpass extends ServiceClass implements IAuthorizationTokenpass {
export class AuthorizationTokenpass extends ServiceClassInternal implements IAuthorizationTokenpass {
protected name = 'authorization-tokenpass';

protected internal = true;
Expand All @@ -20,5 +19,3 @@ class AuthorizationTokenpass extends ServiceClass implements IAuthorizationToken
return false;
}
}

api.registerService(new AuthorizationTokenpass());
4 changes: 0 additions & 4 deletions ee/app/license/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import './settings';
import './methods';
import './startup';
import { LicenseService } from './license.internalService';
import { api } from '../../../../server/sdk/api';

export { onLicense, overwriteClassOnLicense, isEnterprise, getMaxGuestUsers } from './license';

export { getStatistics } from './getStatistics';

api.registerService(new LicenseService());
92 changes: 48 additions & 44 deletions ee/app/license/server/license.internalService.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { ServiceClass } from '../../../../server/sdk/types/ServiceClass';
import { debounce } from 'underscore';

import { Authorization } from '../../../../server/sdk';
import { api } from '../../../../server/sdk/api';
import { ILicense } from '../../../../server/sdk/types/ILicense';
import { hasLicense, isEnterprise, getModules, onValidateLicenses, onModule } from './license';
import { resetEnterprisePermissions } from '../../authorization/server/resetEnterprisePermissions';
import { Authorization } from '../../../../server/sdk';
import { ServiceClassInternal } from '../../../../server/sdk/types/ServiceClass';
import { guestPermissions } from '../../authorization/lib/guestPermissions';
import { resetEnterprisePermissions } from '../../authorization/server/resetEnterprisePermissions';
import { getModules, hasLicense, isEnterprise, onModule, onValidateLicenses } from './license';

export class LicenseService extends ServiceClass implements ILicense {
export class LicenseService extends ServiceClassInternal implements ILicense {
protected name = 'license';

protected internal = true;

constructor() {
super();

Expand All @@ -27,43 +27,47 @@ export class LicenseService extends ServiceClass implements ILicense {
api.broadcast('license.module', licenseModule);
});

this.onEvent('$services.changed', async () => {
// if (hasModule('scalability')) {
// return;
// }

const services: {
name: string;
nodes: string[];
}[] = await api.call('$node.services');

/* The main idea is if there is no scalability module enabled,
* then we should not allow more than one service per environment.
* So we list the services and the nodes, and if there is more than
* one, we inform the service that it should be disabled.
*/

// Filter only the services are duplicated
const duplicated = services.filter((service) => {
return service.name !== '$node' && service.nodes.length > 1;
});

if (!duplicated.length) {
return;
}

const brokers: Record<string, string[]> = Object.fromEntries(
duplicated.map((service) => {
const [, ...nodes] = service.nodes;
return [service.name, nodes];
}),
);

// Just inform the service that it should be disabled

const duplicatedServicesNames = duplicated.map((service) => service.name);
api.broadcastToServices(duplicatedServicesNames, 'shutdown', brokers);
});
/**
* The main idea is if there is no scalability module enabled,
* then we should not allow more than one service per environment.
* So we list the services and nodes, and if there is more than
* one, we inform the service that it should be disabled.
*/
this.onEvent(
'$services.changed',
debounce(async () => {
if (hasLicense('scalability')) {
return;
}

const services: {
name: string;
nodes: string[];
}[] = await api.call('$node.services');

// Filter only the duplicated services
const duplicated = services.filter((service) => {
return service.name !== '$node' && service.nodes.length > 1;
});

if (!duplicated.length) {
return;
}

const brokers: Record<string, string[]> = Object.fromEntries(
duplicated.map((service) => {
// remove the first node from the list
const [, ...nodes] = service.nodes;
return [service.name, nodes];
}),
);

const duplicatedServicesNames = duplicated.map((service) => service.name);

// send shutdown signal to the duplicated services
api.broadcastToServices(duplicatedServicesNames, 'shutdown', brokers);
}, 1000),
);
}

async started(): Promise<void> {
Expand Down
4 changes: 0 additions & 4 deletions ee/app/license/server/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,6 @@ export function onValidFeature(feature: BundleFeature, cb: () => void): () => vo
};
}

export const hasModule = (module: string): boolean => {
return License.hasModule(module);
};

export function onInvalidFeature(feature: BundleFeature, cb: () => void): () => void {
EnterpriseLicenses.on(`invalid:${feature}`, cb);

Expand Down
7 changes: 2 additions & 5 deletions ee/app/settings/server/settings.internalService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ServiceClass } from '../../../../server/sdk/types/ServiceClass';
import { api } from '../../../../server/sdk/api';
import { ServiceClassInternal } from '../../../../server/sdk/types/ServiceClass';
import { IEnterpriseSettings } from '../../../../server/sdk/types/IEnterpriseSettings';
import { changeSettingValue } from './settings';
import { ISetting } from '../../../../definition/ISetting';

class EnterpriseSettings extends ServiceClass implements IEnterpriseSettings {
export class EnterpriseSettings extends ServiceClassInternal implements IEnterpriseSettings {
protected name = 'ee-settings';

protected internal = true;
Expand All @@ -13,5 +12,3 @@ class EnterpriseSettings extends ServiceClass implements IEnterpriseSettings {
return changeSettingValue(record);
}
}

api.registerService(new EnterpriseSettings());
29 changes: 4 additions & 25 deletions ee/server/NetworkBroker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ const lifecycle: { [k: string]: string } = {
};

const {
INTERNAL_SERVICES_ONLY = 'false',
// SERVICES_ALLOWED = '',
WAIT_FOR_SERVICES_TIMEOUT = '10000', // 10 seconds
WAIT_FOR_SERVICES_WHITELIST_TIMEOUT = '600000', // 10 minutes
} = process.env;
Expand All @@ -38,9 +36,6 @@ export class NetworkBroker implements IBroker {
actions: ['license.hasLicense'],
};

// whether only internal services are allowed to be registered
private internalOnly = ['true', 'yes'].includes(INTERNAL_SERVICES_ONLY.toLowerCase());

metrics: IServiceMetrics;

constructor(broker: ServiceBroker) {
Expand Down Expand Up @@ -103,18 +98,15 @@ export class NetworkBroker implements IBroker {
}

createService(instance: ServiceClass): void {
if (!this.isServiceAllowed(instance)) {
return;
}

const name = instance.getName();

if (!this.isServiceInternal(instance)) {
if (!instance.isInternal()) {
instance.onEvent('shutdown', async (services) => {
const service = services[name];
if (!service) {
if (!services[name]?.includes(this.broker.nodeID)) {
this.broker.logger.debug({ msg: 'Not shutting down, different node.', nodeID: this.broker.nodeID });
return;
}
this.broker.logger.info({ msg: 'Received shutdown event, destroying service.', nodeID: this.broker.nodeID });
this.destroyService(instance);
});
}
Expand Down Expand Up @@ -209,17 +201,4 @@ export class NetworkBroker implements IBroker {
async nodeList(): Promise<IBrokerNode[]> {
return this.broker.call('$node.list');
}

private isServiceInternal(instance: ServiceClass): boolean {
return !(this.internalOnly && !instance.isInternal());
}

private isServiceAllowed(instance: ServiceClass): boolean {
// allow only internal services if internalOnly is true
if (this.internalOnly && !instance.isInternal()) {
return false;
}

return true;
}
}
3 changes: 1 addition & 2 deletions ee/server/broker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const {
MS_METRICS = 'false',
MS_METRICS_PORT = '9458',
TRACING_ENABLED = 'false',
SKIP_PROCESS_EVENT_REGISTRATION = 'true',
SKIP_PROCESS_EVENT_REGISTRATION = 'false',
} = process.env;

// only starts network broker if transporter properly configured
Expand All @@ -44,7 +44,6 @@ if (TRANSPORTER.match(/^(?:nats|TCP)/)) {
}

const network = new ServiceBroker({
// TODO: Reevaluate, without this setting it was preventing the process to stop
skipProcessEventRegistration: SKIP_PROCESS_EVENT_REGISTRATION === 'true',
transporter: TRANSPORTER,
metrics: {
Expand Down
7 changes: 2 additions & 5 deletions ee/server/local-services/ldap/service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import '../../broker';

import { api } from '../../../../server/sdk/api';
import { LDAPEEManager } from '../../lib/ldap/Manager';
import { ILDAPEEService } from '../../sdk/types/ILDAPEEService';
import { ServiceClass } from '../../../../server/sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../../../server/sdk/types/ServiceClass';

export class LDAPEEService extends ServiceClass implements ILDAPEEService {
export class LDAPEEService extends ServiceClassInternal implements ILDAPEEService {
protected name = 'ldap-enterprise';

async sync(): Promise<void> {
Expand All @@ -20,5 +19,3 @@ export class LDAPEEService extends ServiceClass implements ILDAPEEService {
return LDAPEEManager.syncLogout();
}
}

api.registerService(new LDAPEEService());
1 change: 1 addition & 0 deletions ee/server/startup/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import './engagementDashboard';
import './seatsCap';
import './services';
9 changes: 9 additions & 0 deletions ee/server/startup/services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { api } from '../../../server/sdk/api';
import { EnterpriseSettings } from '../../app/settings/server/settings.internalService';
import { LDAPEEService } from '../local-services/ldap/service';
import { LicenseService } from '../../app/license/server/license.internalService';

// TODO consider registering these services only after a valid license is added
api.registerService(new EnterpriseSettings());
api.registerService(new LDAPEEService());
api.registerService(new LicenseService());
8 changes: 8 additions & 0 deletions server/sdk/types/ServiceClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,11 @@ export abstract class ServiceClass implements IServiceClass {
this.events.emit(event, ...args);
}
}

/**
* An internal service is a service that is registered only on monolith node.
* Services that run on their own node should use @ServiceClass instead.
*/
export abstract class ServiceClassInternal extends ServiceClass {
protected internal = true;
}
4 changes: 2 additions & 2 deletions server/services/analytics/service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Db } from 'mongodb';

import { ServiceClass } from '../../sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../sdk/types/ServiceClass';
import { IAnalyticsService } from '../../sdk/types/IAnalyticsService';
import { AnalyticsRaw } from '../../../app/models/server/raw/Analytics';
import { IAnalyticsSeatRequest } from '../../../definition/IAnalytic';

export class AnalyticsService extends ServiceClass implements IAnalyticsService {
export class AnalyticsService extends ServiceClassInternal implements IAnalyticsService {
protected name = 'analytics';

private Analytics: AnalyticsRaw;
Expand Down
4 changes: 2 additions & 2 deletions server/services/banner/service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Db } from 'mongodb';
import { v4 as uuidv4 } from 'uuid';

import { ServiceClass } from '../../sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../sdk/types/ServiceClass';
import { BannersRaw } from '../../../app/models/server/raw/Banners';
import { BannersDismissRaw } from '../../../app/models/server/raw/BannersDismiss';
import { UsersRaw } from '../../../app/models/server/raw/Users';
Expand All @@ -11,7 +11,7 @@ import { api } from '../../sdk/api';
import { IUser } from '../../../definition/IUser';
import { Optional } from '../../../definition/utils';

export class BannerService extends ServiceClass implements IBannerService {
export class BannerService extends ServiceClassInternal implements IBannerService {
protected name = 'banner';

private Banners: BannersRaw;
Expand Down
4 changes: 2 additions & 2 deletions server/services/image/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import fileType from 'file-type';
import sharp from 'sharp';
import isSvg from 'is-svg';

import { ServiceClass } from '../../sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../sdk/types/ServiceClass';
import { IMediaService, ResizeResult } from '../../sdk/types/IMediaService';

/* eslint-disable @typescript-eslint/no-var-requires */
const ExifTransformer = require('exif-be-gone');
/* eslint-enable @typescript-eslint/no-var-requires */

export class MediaService extends ServiceClass implements IMediaService {
export class MediaService extends ServiceClassInternal implements IMediaService {
protected name = 'media';

private imageExts = new Set([
Expand Down
4 changes: 2 additions & 2 deletions server/services/ldap/service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { LDAPManager } from '../../lib/ldap/Manager';
import { ILDAPService } from '../../sdk/types/ILDAPService';
import { ServiceClass } from '../../sdk/types/ServiceClass';
import { ServiceClassInternal } from '../../sdk/types/ServiceClass';
import { LDAPLoginResult } from '../../../definition/ldap/ILDAPLoginResult';

export class LDAPService extends ServiceClass implements ILDAPService {
export class LDAPService extends ServiceClassInternal implements ILDAPService {
protected name = 'ldap';

async loginRequest(username: string, password: string): Promise<LDAPLoginResult> {
Expand Down
Loading