fix: start sub app in cluster (#5530)

* fix: start sub app

* fix: test

* fix: test

* fix: test

---------

Co-authored-by: CHENGLEI SHAO <Chareice>
This commit is contained in:
ChengLei Shao 2024-11-03 22:31:12 +08:00 committed by GitHub
parent 746b54e9c1
commit f81b942967
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 80 additions and 52 deletions

View File

@ -37,7 +37,11 @@ export class SyncMessageManager {
if (transaction) {
return await new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Publish message to ${channel} timeout, message: ${JSON.stringify(message)}`));
reject(
new Error(
`Publish message to ${channel} timeout, channel: ${channel}, message: ${JSON.stringify(message)}`,
),
);
}, 50000);
transaction.afterCommit(async () => {
@ -46,6 +50,7 @@ export class SyncMessageManager {
skipSelf: true,
...others,
});
resolve(r);
} catch (error) {
reject(error);

View File

@ -8,7 +8,7 @@
*/
import { Database } from '@nocobase/database';
import { MockServer, createMockServer } from '@nocobase/test';
import { createMockServer, MockServer } from '@nocobase/test';
import compose from 'koa-compose';
import { parseBuilder, parseFieldAndAssociations, queryData } from '../actions/query';
@ -19,7 +19,7 @@ describe('formatter', () => {
beforeAll(async () => {
app = await createMockServer({
acl: true,
plugins: ['users', 'auth', 'data-visualization'],
plugins: ['users', 'auth', 'field-sort', 'data-visualization'],
});
db = app.db;
});

View File

@ -0,0 +1,46 @@
import { createMockCluster, waitSecond } from '@nocobase/test';
import { uid } from '@nocobase/utils';
describe('cluster', () => {
let cluster;
beforeEach(async () => {
cluster = await createMockCluster({
plugins: ['nocobase', 'field-sort', 'multi-app-manager'],
acl: false,
});
});
afterEach(async () => {
await cluster.destroy();
});
it('should start sub app after app created between nodes', async () => {
const [app1, app2] = cluster.nodes;
const name = `td_${uid()}`;
const fn = vi.fn();
app2.on('subAppStarted', async (subApp) => {
fn(subApp.name);
});
await app1.db.getRepository('applications').create({
values: {
name,
options: {
skipSupervisor: true,
plugins: [],
database: {
underscored: true,
},
},
},
context: {
waitSubAppInstall: true,
},
});
await waitSecond(5000);
expect(fn).toBeCalledWith(name);
});
});

View File

@ -129,43 +129,6 @@ describe('multiple apps', () => {
expect(await db.getRepository('applications').count()).toBe(0);
});
it('should upgrade sub app', async () => {
await db.getRepository('applications').create({
values: {
name: 'test1',
options: {
plugins: ['nocobase'],
},
},
context: {
waitSubAppInstall: true,
},
});
await db.getRepository('applications').create({
values: {
name: 'test2',
options: {
plugins: ['nocobase'],
},
},
context: {
waitSubAppInstall: true,
},
});
await app.runCommand('restart');
await app.runCommand('upgrade');
// const subAppStatus = AppSupervisor.getInstance().getAppStatus(name);
// expect(subAppStatus).toEqual('running');
//
// const subApp = await AppSupervisor.getInstance().getApp(name);
// await subApp.runCommand('upgrade');
//
// await AppSupervisor.getInstance().removeApp(name);
// expect(await db.getRepository('applications').count()).toBe(1);
});
it('should list application with status', async () => {
const sub1 = `td_${uid()}`;
await db.getRepository('applications').create({

View File

@ -29,6 +29,12 @@ export class ApplicationModel extends Model {
name: appName,
};
return new Application(subAppOptions);
const subApp = new Application(subAppOptions);
subApp.on('afterStart', () => {
mainApp.emit('subAppStarted', subApp);
});
return subApp;
}
}

View File

@ -146,7 +146,7 @@ export class PluginMultiAppManagerServer extends Plugin {
async handleSyncMessage(message) {
const { type } = message;
if (type === 'startApp') {
if (type === 'subAppStarted') {
const { appName } = message;
const model = await this.app.db.getRepository('applications').findOne({
filter: {
@ -158,6 +158,10 @@ export class PluginMultiAppManagerServer extends Plugin {
return;
}
if (AppSupervisor.getInstance().hasApp(appName)) {
return;
}
const subApp = model.registerToSupervisor(this.app, {
appOptionsFactory: this.appOptionsFactory,
});
@ -208,22 +212,19 @@ export class PluginMultiAppManagerServer extends Plugin {
appOptionsFactory: this.appOptionsFactory,
});
subApp.on('afterStart', async () => {
this.sendSyncMessage({
type: 'subAppStarted',
appName: name,
});
});
// create database
await this.appDbCreator(subApp, {
transaction,
context: options.context,
});
this.sendSyncMessage(
{
type: 'startApp',
appName: name,
},
{
transaction,
},
);
const startPromise = subApp.runCommand('start', '--quickstart');
if (options?.context?.waitSubAppInstall) {
@ -289,6 +290,13 @@ export class PluginMultiAppManagerServer extends Plugin {
appOptionsFactory: self.appOptionsFactory,
});
subApp.on('afterStart', async () => {
this.sendSyncMessage({
type: 'subAppStarted',
appName: name,
});
});
// must skip load on upgrade
if (!loadButNotStart) {
await subApp.runCommand('start', '--quickstart');