mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 15:24:52 +09:00
Refactor instance communication
Change Instance class to be a subclass of Link and refactor the communication to and establisment of instances to use a virtual connection between the slave and the instance. This is simplifies the implementation of plugin communication for instances. Replace InstanceRequest with a forwardTo option to Request which does the task of forwarding in a much cleaner way.
This commit is contained in:
1 parent
9f24d200b7
commit
5d8fa4e4ce
7 files changed
+381
-265
No files matched your search
+120
-113
@@ -9,15 +9,47 @@ const errors = require("lib/errors");
|
||||
* link.
|
||||
*/
|
||||
class Request {
|
||||
|
||||
/**
|
||||
* Creates a request
|
||||
*
|
||||
* @param {string} type -
|
||||
* message type used. Will have suffix '_request' for the request
|
||||
* message and suffix '_response' for the response message.
|
||||
* @param {Array<string>} links -
|
||||
* Links this request is valid on. Takes an array of strings
|
||||
* containing "<source>-<target>" specifications.
|
||||
* @param {?string} forwardTo -
|
||||
* Optional target to forward this request to. Only 'instance' is
|
||||
* currently supported and adds instance_id into the
|
||||
* requestProperties.
|
||||
* @param {Object<string, Object>} requestProperties -
|
||||
* Mapping of property values to JSON schema specifications for the
|
||||
* properties that are valid in the data payload of this requst.
|
||||
* @param {Object<string, Object>} responseProperties -
|
||||
* Mapping of property values to JSON schema specifications for the
|
||||
* properties that are valid in the data payload of the response to
|
||||
* this requst.
|
||||
*/
|
||||
constructor({
|
||||
type, links, requestProperties = {}, responseProperties = {}
|
||||
type, links, forwardTo = null, requestProperties = {}, responseProperties = {}
|
||||
}) {
|
||||
this.type = type;
|
||||
this.links = links;
|
||||
this.forwardTo = forwardTo;
|
||||
|
||||
this.requestType = type + '_request';
|
||||
this.responseType = type + '_response';
|
||||
|
||||
if (forwardTo === 'instance') {
|
||||
requestProperties = Object.assign(
|
||||
{"instance_id": { type: "integer" }}, requestProperties
|
||||
);
|
||||
|
||||
} else if (forwardTo !== null) {
|
||||
throw new Error(`Invalid forwardTo value ${forwardTo}`);
|
||||
}
|
||||
|
||||
this._requestValidator = schema.compile({
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
properties: {
|
||||
@@ -64,6 +96,9 @@ class Request {
|
||||
* Set a the validator for the response on the given link if it's a
|
||||
* source and sets the handler for the request if it is a target.
|
||||
*
|
||||
* If forwardTo was set to 'instance' the `forwardRequestToInstance`
|
||||
* method on the link is used as the default handler.
|
||||
*
|
||||
* Does nothing if the link is neither a source nor a target for this
|
||||
* request.
|
||||
*
|
||||
@@ -81,21 +116,26 @@ class Request {
|
||||
|
||||
// Target side. Note: Source and target is reversed here.
|
||||
if (this.links.includes(`${link.target}-${link.source}`)) {
|
||||
|
||||
// Use forwarder if handler is not present.
|
||||
if (!handler) {
|
||||
if (this.forwardTo === 'instance') {
|
||||
handler = link.forwardRequestToInstance;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
throw new Error(`Missing handler for ${this.requestType} on ${link.source}-${link.target} link`);
|
||||
}
|
||||
|
||||
link.setHandler(this.requestType, message => {
|
||||
handler.call(link, message).then(response => {
|
||||
handler.call(link, message, this).then(response => {
|
||||
if (response === undefined) {
|
||||
// XXX Should we allow implicit responses like this?
|
||||
response = {};
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(response, 'seq')) {
|
||||
throw new errors.RequestError("response contains reserved property 'seq'");
|
||||
}
|
||||
link.connector.send(this.responseType, { seq: message.seq, ...response });
|
||||
link.connector.send(this.responseType, { ...response, seq: message.seq });
|
||||
|
||||
}).catch(err => {
|
||||
if (!(err instanceof errors.RequestError)) {
|
||||
@@ -126,95 +166,6 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a request sent to the master that is forwarded to an instance
|
||||
*
|
||||
* This is a two part request with a master part and a slave part. The
|
||||
* master part is done by the forwardInstanceRequest method on the master
|
||||
* side of link which finds the slave the requuested instance is on and
|
||||
* forwards it there using the foward method.
|
||||
*
|
||||
* The slave part is done by the forwardInstanceRequest on the slave side of
|
||||
* the link and finds the instance and then calls the handler with it and
|
||||
* the message.
|
||||
*/
|
||||
class InstanceRequest {
|
||||
constructor({
|
||||
type, sources, requestProperties = {}, responseProperties = {}
|
||||
}) {
|
||||
this._masterRequest = new Request({
|
||||
type: type + '_master',
|
||||
links: sources.map(source => `${source}-master`),
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
...requestProperties,
|
||||
},
|
||||
responseProperties,
|
||||
});
|
||||
this._slaveRequest = new Request({
|
||||
type: type + '_slave',
|
||||
links: ['master-slave'],
|
||||
requestProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
...requestProperties,
|
||||
},
|
||||
responseProperties,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach to a link
|
||||
*
|
||||
* Attach master/slave part of the instance request. If this is the
|
||||
* slave side the handler argument is required.
|
||||
*
|
||||
* Does nothing if the link is neither a source nor a target for the
|
||||
* slave or master request.
|
||||
*
|
||||
* @param {Link} link - The link to attach to.
|
||||
* @param {Function} handler -
|
||||
* Async function to to handle the instance request. It is passed on
|
||||
* to forwardInstanceRequest on the slave side.
|
||||
* @throws {Error} if the handler is needed and not defined.
|
||||
*/
|
||||
attach(link, handler) {
|
||||
this._masterRequest.attach(link, async (message) => {
|
||||
return await link.forwardInstanceRequest(this, message);
|
||||
});
|
||||
if (handler) {
|
||||
this._slaveRequest.attach(link, async (message) => {
|
||||
return await link.forwardInstanceRequest(handler, message);
|
||||
});
|
||||
} else {
|
||||
this._slaveRequest.attach(link);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the message over the link
|
||||
*
|
||||
* This method is expected to be called by forwardInstanceRequest on the
|
||||
* master with the slave link that the instance is on.
|
||||
*/
|
||||
async forward(link, message) {
|
||||
let response = await this._slaveRequest.send(link, message.data);
|
||||
delete response.seq;
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request over the given link
|
||||
*
|
||||
* Sends the given data over the link to the master and waits for it to
|
||||
* forward the request to the slave with the instance on and return the
|
||||
* result of it.
|
||||
*
|
||||
* @returns {object} response data
|
||||
*/
|
||||
async send(link, data = {}) {
|
||||
return await this._masterRequest.send(link, data);
|
||||
}
|
||||
}
|
||||
|
||||
let messages = {}
|
||||
|
||||
@@ -305,30 +256,34 @@ messages.createInstance = new Request({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
messages.createSave = new InstanceRequest({
|
||||
type: 'create_save',
|
||||
sources: ['control'],
|
||||
});
|
||||
|
||||
messages.startInstance = new InstanceRequest({
|
||||
messages.startInstance = new Request({
|
||||
type: 'start_instance',
|
||||
sources: ['control'],
|
||||
links: ['control-master', 'master-slave', 'slave-instance'],
|
||||
forwardTo: 'instance',
|
||||
});
|
||||
|
||||
messages.stopInstance = new InstanceRequest({
|
||||
messages.createSave = new Request({
|
||||
type: 'create_save',
|
||||
links: ['control-master', 'master-slave', 'slave-instance'],
|
||||
forwardTo: 'instance',
|
||||
});
|
||||
|
||||
messages.stopInstance = new Request({
|
||||
type: 'stop_instance',
|
||||
sources: ['control'],
|
||||
links: ['control-master', 'master-slave', 'slave-instance'],
|
||||
forwardTo: 'instance',
|
||||
});
|
||||
|
||||
messages.deleteInstance = new InstanceRequest({
|
||||
messages.deleteInstance = new Request({
|
||||
type: 'delete_instance',
|
||||
sources: ['control'],
|
||||
links: ['control-master', 'master-slave'],
|
||||
forwardTo: 'instance',
|
||||
});
|
||||
|
||||
messages.sendRcon = new InstanceRequest({
|
||||
messages.sendRcon = new Request({
|
||||
type: 'send_rcon',
|
||||
sources: ['control'],
|
||||
links: ['control-master', 'master-slave', 'slave-instance'],
|
||||
forwardTo: 'instance',
|
||||
requestProperties: {
|
||||
"command": { type: "string" },
|
||||
},
|
||||
@@ -338,10 +293,38 @@ messages.sendRcon = new InstanceRequest({
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Represents an event sent over the link
|
||||
*
|
||||
* A one way message without any response or recipt confirmation that
|
||||
* can be innitiated by either party of the link.
|
||||
*/
|
||||
class Event {
|
||||
constructor({ type, links, eventProperties = {} }) {
|
||||
|
||||
/**
|
||||
* Creates an event
|
||||
*
|
||||
* @param {string} type -
|
||||
* message type used. Will have suffix '_event' appended to it for
|
||||
* the messages sent through this event.
|
||||
* @param {Array<string>} links -
|
||||
* Links this event is valid on. Takes an array of strings
|
||||
* containing "<source>-<target>" specifications.
|
||||
* @param {?string} forwardTo -
|
||||
* Optional target to forward this request to. Only 'master' is
|
||||
* currently supported.
|
||||
* @param {Object<string, Object>} eventProperties -
|
||||
* Mapping of property values to JSON schema specifications for the
|
||||
* properties that are valid in the data payload of this event.
|
||||
*/
|
||||
constructor({ type, links, forwardTo = null, eventProperties = {} }) {
|
||||
this.type = type;
|
||||
this.links = links;
|
||||
this.forwardTo = forwardTo;
|
||||
|
||||
if (forwardTo !== 'master' && forwardTo !== null) {
|
||||
throw new Error(`Invalid forwardTo value ${forwardTo}`);
|
||||
}
|
||||
|
||||
this.eventType = type + '_event';
|
||||
this._eventValidator = schema.compile({
|
||||
@@ -357,16 +340,40 @@ class Event {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach to a link
|
||||
*
|
||||
* Set a the handler for the event on the given link if it's a target
|
||||
* for the event.
|
||||
*
|
||||
* If forwardTo was set to 'master' the `forwardEventToMaster` method on
|
||||
* the link is used as the default handler.
|
||||
*
|
||||
* Does nothing if the link is not a target for this event.
|
||||
*
|
||||
* @param {Link} link - The link to attach to.
|
||||
* @param {Function} handler -
|
||||
* Async function to invoke with link set as this to handle the
|
||||
* event. Only used on the target side.
|
||||
* @throws {Error} if the handler is needed and not defined.
|
||||
*/
|
||||
attach(link, handler) {
|
||||
// Target side. Note: Source and target is reversed here.
|
||||
if (this.links.includes(`${link.target}-${link.source}`)) {
|
||||
// Use forwarder if handler is not present.
|
||||
if (!handler) {
|
||||
if (this.forwardTo === 'master') {
|
||||
handler = link.forwardEventToMaster;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
throw new Error(`Missing handler for ${this.eventType} on ${link.source}-${link.target} link`);
|
||||
}
|
||||
|
||||
link.setHandler(this.eventType, message => {
|
||||
// XXX Should event handlers even be allowed to be async?
|
||||
handler.call(link, message).catch(err => {
|
||||
handler.call(link, message, this).catch(err => {
|
||||
console.error(`Unexpected error while handling ${this.eventType}`, err);
|
||||
});
|
||||
}, this._eventValidator);
|
||||
@@ -404,7 +411,8 @@ messages.updateInstances = new Event({
|
||||
|
||||
messages.instanceOutput = new Event({
|
||||
type: 'instance_output',
|
||||
links: ['slave-master', 'master-control'],
|
||||
links: ['instance-slave', 'slave-master', 'master-control'],
|
||||
forwardTo: 'master',
|
||||
eventProperties: {
|
||||
"instance_id": { type: "integer" },
|
||||
"output": {
|
||||
@@ -444,7 +452,6 @@ function attachAllMessages(link) {
|
||||
|
||||
module.exports = {
|
||||
Request,
|
||||
InstanceRequest,
|
||||
Event,
|
||||
|
||||
attachAllMessages,
|
||||
|
||||
Reference in new issue
Block a user