diff --git a/app/api/server/v1/instances.ts b/app/api/server/v1/instances.ts index 68efb9345d704..e6586a7c12a73 100644 --- a/app/api/server/v1/instances.ts +++ b/app/api/server/v1/instances.ts @@ -1,6 +1,8 @@ -import { getInstances } from '../../../../server/stream/streamBroadcast'; +import { getInstanceConnection } from '../../../../server/stream/streamBroadcast'; import { hasPermission } from '../../../authorization/server'; import { API } from '../api'; +import InstanceStatus from '../../../models/server/models/InstanceStatus'; +import { IInstanceStatus } from '../../../../definition/IInstanceStatus'; API.v1.addRoute('instances.get', { authRequired: true }, { get() { @@ -8,6 +10,19 @@ API.v1.addRoute('instances.get', { authRequired: true }, { return API.v1.unauthorized(); } - return API.v1.success({ instances: getInstances() }); + const instances = InstanceStatus.find().fetch(); + + return API.v1.success({ + instances: instances.map((instance: IInstanceStatus) => { + const connection = getInstanceConnection(instance); + if (connection) { + delete connection.instanceRecord; + } + return { + ...instance, + connection, + }; + }), + }); }, }); diff --git a/server/main.js b/server/main.js index 2c1155f6e4d76..d00699b9735db 100644 --- a/server/main.js +++ b/server/main.js @@ -10,6 +10,7 @@ import './startup/migrations'; import './startup/appcache'; import './startup/cron'; import './startup/initialData'; +import './startup/instance'; import './startup/presence'; import './startup/serverRunning'; import './configuration/accounts_meld'; diff --git a/server/startup/instance.js b/server/startup/instance.js new file mode 100644 index 0000000000000..5dcebcb2be5b7 --- /dev/null +++ b/server/startup/instance.js @@ -0,0 +1,33 @@ +import os from 'os'; + +import { Meteor } from 'meteor/meteor'; +import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; + +import { startStreamBroadcast } from '../stream/streamBroadcast'; + +export function getInstances() { + InstanceStatus.find().fetch(); +} + +Meteor.startup(function() { + const instance = { + host: process.env.INSTANCE_IP ? String(process.env.INSTANCE_IP).trim() : 'localhost', + port: String(process.env.PORT).trim(), + os: { + type: os.type(), + platform: os.platform(), + arch: os.arch(), + release: os.release(), + uptime: os.uptime(), + loadavg: os.loadavg(), + totalmem: os.totalmem(), + freemem: os.freemem(), + cpus: os.cpus().length, + }, + nodeVersion: process.version, + }; + + InstanceStatus.registerInstance('rocket.chat', instance); + + return startStreamBroadcast(); +}); diff --git a/server/startup/presence.js b/server/startup/presence.js index 193dfc3bb1176..bcad81c80bb36 100644 --- a/server/startup/presence.js +++ b/server/startup/presence.js @@ -1,22 +1,10 @@ import { Meteor } from 'meteor/meteor'; -import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; import { UserPresence } from 'meteor/konecty:user-presence'; import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; import UsersSessionsModel from '../../app/models/server/models/UsersSessions'; Meteor.startup(function() { - const instance = { - host: 'localhost', - port: String(process.env.PORT).trim(), - }; - - if (process.env.INSTANCE_IP) { - instance.host = String(process.env.INSTANCE_IP).trim(); - } - - InstanceStatus.registerInstance('rocket.chat', instance); - UserPresence.start(); const startMonitor = typeof process.env.DISABLE_PRESENCE_MONITOR === 'undefined' diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index 27f45c7b1a361..3496519828217 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { UserPresence } from 'meteor/konecty:user-presence'; import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; import { check } from 'meteor/check'; -import _ from 'underscore'; import { DDP } from 'meteor/ddp'; import { DDPCommon } from 'meteor/ddp-common'; @@ -97,6 +96,9 @@ function startMatrixBroadcast() { _dontPrintErrors: LoggerManager.logLevel < 2, }); + // remove not relevant info from instance record + delete record.extraInformation.os; + connections[instance].instanceRecord = record; connections[instance].instanceId = record._id; @@ -147,43 +149,6 @@ function startMatrixBroadcast() { InstanceStatusModel.find(query, options).fetch().forEach(matrixBroadCastActions.added); } -Meteor.methods({ - broadcastAuth(remoteId, selfId) { - check(selfId, String); - check(remoteId, String); - - const query = { - _id: remoteId, - }; - - if (selfId === InstanceStatus.id() && remoteId !== InstanceStatus.id() && InstanceStatus.getCollection().findOne(query)) { - this.connection.broadcastAuth = true; - } - - return this.connection.broadcastAuth === true; - }, - - stream(streamName, eventName, args) { - if (!this.connection) { - return 'self-not-authorized'; - } - - if (this.connection.broadcastAuth !== true) { - return 'not-authorized'; - } - - const instance = StreamerCentral.instances[streamName]; - if (!instance) { - return 'stream-not-exists'; - } - - if (instance.serverOnly) { - instance.__emit(eventName, ...args); - } else { - StreamerCentral.instances[streamName]._emit(eventName, args); - } - }, -}); function startStreamCastBroadcast(value) { const instance = 'StreamCast'; @@ -237,7 +202,7 @@ function startStreamCastBroadcast(value) { return connection.subscribe('stream'); } -function startStreamBroadcast() { +export function startStreamBroadcast() { if (!process.env.INSTANCE_IP) { process.env.INSTANCE_IP = 'localhost'; } @@ -314,24 +279,75 @@ function startStreamBroadcast() { }); } -export function getInstances() { - return Object.keys(connections).map((address) => { - const conn = connections[address]; - return Object.assign({ address, currentStatus: conn._stream.currentStatus }, _.pick(conn, 'instanceRecord', 'broadcastAuth')); - }); +function getConnection(address) { + const conn = connections[address]; + if (!conn) { + return; + } + + const { + instanceRecord, + broadcastAuth, + } = conn; + + return { + address, + currentStatus: conn._stream.currentStatus, + instanceRecord, + broadcastAuth, + }; } -Meteor.startup(function() { - return startStreamBroadcast(); -}); +export function getInstanceConnection(instance) { + const subPath = getURL('', { cdn: false, full: false }); + const address = `${ instance.extraInformation.host }:${ instance.extraInformation.port }${ subPath }`; + + return getConnection(address); +} Meteor.methods({ + broadcastAuth(remoteId, selfId) { + check(selfId, String); + check(remoteId, String); + + const query = { + _id: remoteId, + }; + + if (selfId === InstanceStatus.id() && remoteId !== InstanceStatus.id() && InstanceStatus.getCollection().findOne(query)) { + this.connection.broadcastAuth = true; + } + + return this.connection.broadcastAuth === true; + }, + + stream(streamName, eventName, args) { + if (!this.connection) { + return 'self-not-authorized'; + } + + if (this.connection.broadcastAuth !== true) { + return 'not-authorized'; + } + + const instance = StreamerCentral.instances[streamName]; + if (!instance) { + return 'stream-not-exists'; + } + + if (instance.serverOnly) { + instance.__emit(eventName, ...args); + } else { + StreamerCentral.instances[streamName]._emit(eventName, args); + } + }, + 'instances/get'() { if (!hasPermission(Meteor.userId(), 'view-statistics')) { throw new Meteor.Error('error-action-not-allowed', 'List instances is not allowed', { method: 'instances/get', }); } - return getInstances(); + return Object.keys(connections).map(getConnection); }, }); diff --git a/tests/end-to-end/api/00-miscellaneous.js b/tests/end-to-end/api/00-miscellaneous.js index bca5fbd432ef2..e6bd9293b7a71 100644 --- a/tests/end-to-end/api/00-miscellaneous.js +++ b/tests/end-to-end/api/00-miscellaneous.js @@ -446,4 +446,31 @@ describe('miscellaneous', function() { .end(done); }); }); + + describe('/instances.get', () => { + it('should return available instances', (done) => { + request.get(api('instances.get')) + .set(credentials) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('instances').and.to.be.an('array').with.lengthOf(1); + + const { instances: [instance] } = res.body; + + expect(instance).to.have.property('_id'); + expect(instance).to.have.property('extraInformation'); + expect(instance).to.have.property('name'); + expect(instance).to.have.property('pid'); + + const { extraInformation } = instance; + + expect(extraInformation).to.have.property('host'); + expect(extraInformation).to.have.property('port'); + expect(extraInformation).to.have.property('os').and.to.have.property('cpus').to.be.a('number'); + expect(extraInformation).to.have.property('nodeVersion'); + }) + .end(done); + }); + }); });