mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 07:04:52 +09:00
Refactor actions out of ControlConnection
Move implementation of instance related actions out of ControlConnection and into Controller to allow plugins to easily make use of these methods.
This commit is contained in:
1 parent
496ebd18a5
commit
f662437b3f
2 files changed
+170
-109
No files matched your search
@@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
const events = require("events");
|
||||
const fs = require("fs-extra");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const path = require("path");
|
||||
|
||||
const libConfig = require("@clusterio/lib/config");
|
||||
@@ -11,12 +10,10 @@ const libHelpers = require("@clusterio/lib/helpers");
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const { logFilter, logger } = require("@clusterio/lib/logging");
|
||||
const libLoggingUtils = require("@clusterio/lib/logging_utils");
|
||||
const libPlugin = require("@clusterio/lib/plugin");
|
||||
const libPrometheus = require("@clusterio/lib/prometheus");
|
||||
const libUsers = require("@clusterio/lib/users");
|
||||
|
||||
const BaseConnection = require("./BaseConnection");
|
||||
const InstanceInfo = require("./InstanceInfo");
|
||||
const routes = require("./routes");
|
||||
|
||||
const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare;
|
||||
@@ -147,19 +144,12 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
}
|
||||
|
||||
generateHostToken(hostId) {
|
||||
return jwt.sign(
|
||||
{ aud: "host", host: hostId },
|
||||
Buffer.from(this._controller.config.get("controller.auth_secret"), "base64")
|
||||
);
|
||||
}
|
||||
|
||||
async generateHostTokenRequestHandler(message) {
|
||||
let hostId = message.data.host_id;
|
||||
if (hostId === null) {
|
||||
hostId = Math.random() * 2**31 | 0;
|
||||
}
|
||||
return { token: this.generateHostToken(hostId) };
|
||||
return { token: this._controller.generateHostToken(hostId) };
|
||||
}
|
||||
|
||||
async createHostConfigRequestHandler(message) {
|
||||
@@ -175,7 +165,7 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
if (message.data.generate_token) {
|
||||
this.user.checkPermission("core.host.generate_token");
|
||||
hostConfig.set("host.controller_token", this.generateHostToken(hostConfig.get("host.id")));
|
||||
hostConfig.set("host.controller_token", this._controller.generateHostToken(hostConfig.get("host.id")));
|
||||
}
|
||||
return { serialized_config: hostConfig.serialize() };
|
||||
}
|
||||
@@ -230,55 +220,11 @@ class ControlConnection extends BaseConnection {
|
||||
async createInstanceRequestHandler(message) {
|
||||
let instanceConfig = new libConfig.InstanceConfig("controller");
|
||||
await instanceConfig.load(message.data.serialized_config);
|
||||
|
||||
let instanceId = instanceConfig.get("instance.id");
|
||||
if (this._controller.instances.has(instanceId)) {
|
||||
throw new libErrors.RequestError(`Instance with ID ${instanceId} already exists`);
|
||||
}
|
||||
|
||||
// Add common settings for the Factorio server
|
||||
let settings = {
|
||||
"name": `${this._controller.config.get("controller.name")} - ${instanceConfig.get("instance.name")}`,
|
||||
"description": `Clusterio instance for ${this._controller.config.get("controller.name")}`,
|
||||
"tags": ["clusterio"],
|
||||
"max_players": 0,
|
||||
"visibility": { "public": true, "lan": true },
|
||||
"username": "",
|
||||
"token": "",
|
||||
"game_password": "",
|
||||
"require_user_verification": true,
|
||||
"max_upload_in_kilobytes_per_second": 0,
|
||||
"max_upload_slots": 5,
|
||||
"ignore_player_limit_for_returning_players": false,
|
||||
"allow_commands": "admins-only",
|
||||
"autosave_interval": 10,
|
||||
"autosave_slots": 5,
|
||||
"afk_autokick_interval": 0,
|
||||
"auto_pause": false,
|
||||
"only_admins_can_pause_the_game": true,
|
||||
"autosave_only_on_server": true,
|
||||
|
||||
...instanceConfig.get("factorio.settings"),
|
||||
};
|
||||
instanceConfig.set("factorio.settings", settings);
|
||||
|
||||
let instance = new InstanceInfo({ config: instanceConfig, status: "unassigned" });
|
||||
this._controller.instances.set(instanceId, instance);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, null);
|
||||
this._controller.addInstanceHooks(instance);
|
||||
await this._controller.instanceCreate(instanceConfig);
|
||||
}
|
||||
|
||||
async deleteInstanceRequestHandler(message, request) {
|
||||
let instance = this._controller.getRequestInstance(message.data.instance_id);
|
||||
if (instance.config.get("instance.assigned_host") !== null) {
|
||||
await this.forwardRequestToInstance(message, request);
|
||||
}
|
||||
this._controller.instances.delete(message.data.instance_id);
|
||||
|
||||
let prev = instance.status;
|
||||
instance.status = "deleted";
|
||||
this._controller.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
await this._controller.instanceDelete(message.data.instance_id);
|
||||
}
|
||||
|
||||
async getInstanceConfigRequestHandler(message) {
|
||||
@@ -288,19 +234,6 @@ class ControlConnection extends BaseConnection {
|
||||
};
|
||||
}
|
||||
|
||||
async updateInstanceConfig(instance) {
|
||||
let hostId = instance.config.get("instance.assigned_host");
|
||||
if (hostId !== null) {
|
||||
let connection = this._controller.wsServer.hostConnections.get(hostId);
|
||||
if (connection) {
|
||||
await libLink.messages.assignInstance.send(connection, {
|
||||
instance_id: instance.config.get("instance.id"),
|
||||
serialized_config: instance.config.serialize("host"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setInstanceConfigFieldRequestHandler(message) {
|
||||
let instance = this._controller.getRequestInstance(message.data.instance_id);
|
||||
if (message.data.field === "instance.assigned_host") {
|
||||
@@ -313,54 +246,19 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
instance.config.set(message.data.field, message.data.value, "control");
|
||||
await this.updateInstanceConfig(instance);
|
||||
await this._controller.instanceConfigUpdated(instance);
|
||||
}
|
||||
|
||||
async setInstanceConfigPropRequestHandler(message) {
|
||||
let instance = this._controller.getRequestInstance(message.data.instance_id);
|
||||
let { field, prop, value } = message.data;
|
||||
instance.config.setProp(field, prop, value, "control");
|
||||
await this.updateInstanceConfig(instance);
|
||||
await this._controller.instanceConfigUpdated(instance);
|
||||
}
|
||||
|
||||
async assignInstanceCommandRequestHandler(message, request) {
|
||||
let { host_id, instance_id } = message.data;
|
||||
let instance = this._controller.getRequestInstance(instance_id);
|
||||
|
||||
// Check if target host is connected
|
||||
let newHostConnection;
|
||||
if (host_id !== null) {
|
||||
newHostConnection = this._controller.wsServer.hostConnections.get(host_id);
|
||||
if (!newHostConnection) {
|
||||
// The case of the host not getting the assign instance message
|
||||
// still have to be handled, so it's not a requirement that the
|
||||
// target host be connected to the controller while doing the
|
||||
// assignment, but it is IMHO a better user experience if this
|
||||
// is the case.
|
||||
throw new libErrors.RequestError("Target host is not connected to the controller");
|
||||
}
|
||||
}
|
||||
|
||||
// Unassign from currently assigned host if it is connected.
|
||||
let currentAssignedHost = instance.config.get("instance.assigned_host");
|
||||
if (currentAssignedHost !== null && host_id !== currentAssignedHost) {
|
||||
let oldHostConnection = this._controller.wsServer.hostConnections.get(currentAssignedHost);
|
||||
if (oldHostConnection && !oldHostConnection.connector.closing) {
|
||||
await libLink.messages.unassignInstance.send(oldHostConnection, { instance_id });
|
||||
}
|
||||
}
|
||||
|
||||
// Assign to target
|
||||
instance.config.set("instance.assigned_host", host_id);
|
||||
if (host_id !== null) {
|
||||
await libLink.messages.assignInstance.send(newHostConnection, {
|
||||
instance_id,
|
||||
serialized_config: instance.config.serialize("host"),
|
||||
});
|
||||
} else {
|
||||
instance.status = "unassigned";
|
||||
this._controller.instanceUpdated(instance);
|
||||
}
|
||||
await this._controller.instanceAssign(instance_id, host_id);
|
||||
}
|
||||
|
||||
async setSaveListSubscriptionsRequestHandler(message) {
|
||||
|
||||
@@ -5,6 +5,7 @@ const express = require("express");
|
||||
const fs = require("fs-extra");
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const path = require("path");
|
||||
const stream = require("stream");
|
||||
|
||||
@@ -544,6 +545,19 @@ class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate access token for host
|
||||
*
|
||||
* @param {number} hostId - ID of host to generate a token for.
|
||||
* @returns {string} access token for host
|
||||
*/
|
||||
generateHostToken(hostId) {
|
||||
return jwt.sign(
|
||||
{ aud: "host", host: hostId },
|
||||
Buffer.from(this.config.get("controller.auth_secret"), "base64")
|
||||
);
|
||||
}
|
||||
|
||||
hostUpdated(host) {
|
||||
let update = {
|
||||
agent: host.agent,
|
||||
@@ -578,6 +592,155 @@ class Controller {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* Adds common Factorio settings to the provided instance config then
|
||||
* creates an instance using that config in the cluster.
|
||||
*
|
||||
* @example
|
||||
* let instanceConfig = new libConfig.InstanceConfig("controller");
|
||||
* await instanceConfig.init();
|
||||
* instanceConfig.set("instance.name", "My instance");
|
||||
* await controller.createInstance(instanceConfig);
|
||||
*
|
||||
* @param {module:lib/config.InstanceConfig} instanceConfig -
|
||||
* Config to base newly created instance on.
|
||||
* @returns {module:controller/src/InstanceInfo} The created instance
|
||||
*/
|
||||
async instanceCreate(instanceConfig) {
|
||||
let instanceId = instanceConfig.get("instance.id");
|
||||
if (this.instances.has(instanceId)) {
|
||||
throw new libErrors.RequestError(`Instance with ID ${instanceId} already exists`);
|
||||
}
|
||||
|
||||
// Add common settings for the Factorio server
|
||||
let settings = {
|
||||
"name": `${this.config.get("controller.name")} - ${instanceConfig.get("instance.name")}`,
|
||||
"description": `Clusterio instance for ${this.config.get("controller.name")}`,
|
||||
"tags": ["clusterio"],
|
||||
"max_players": 0,
|
||||
"visibility": { "public": true, "lan": true },
|
||||
"username": "",
|
||||
"token": "",
|
||||
"game_password": "",
|
||||
"require_user_verification": true,
|
||||
"max_upload_in_kilobytes_per_second": 0,
|
||||
"max_upload_slots": 5,
|
||||
"ignore_player_limit_for_returning_players": false,
|
||||
"allow_commands": "admins-only",
|
||||
"autosave_interval": 10,
|
||||
"autosave_slots": 5,
|
||||
"afk_autokick_interval": 0,
|
||||
"auto_pause": false,
|
||||
"only_admins_can_pause_the_game": true,
|
||||
"autosave_only_on_server": true,
|
||||
|
||||
...instanceConfig.get("factorio.settings"),
|
||||
};
|
||||
instanceConfig.set("factorio.settings", settings);
|
||||
|
||||
let instance = new InstanceInfo({ config: instanceConfig, status: "unassigned" });
|
||||
this.instances.set(instanceId, instance);
|
||||
await libPlugin.invokeHook(this.plugins, "onInstanceStatusChanged", instance, null);
|
||||
this.addInstanceHooks(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change assigned host of an instance
|
||||
*
|
||||
* Unassigns instance from existing host if already assigned to one and
|
||||
* then assigns it to the given host. This is the only supported way of
|
||||
* changing the instance.assigned_host config entry.
|
||||
*
|
||||
* Note: this will not transfer any files or saves stored on the host
|
||||
* the instance was previously assgined to the new one.
|
||||
*
|
||||
* @param {number} instanceId - ID of Instance to assign.
|
||||
* @param {number} hostId - ID of host to assign instance to.
|
||||
*/
|
||||
async instanceAssign(instanceId, hostId) {
|
||||
let instance = this.getRequestInstance(instanceId);
|
||||
|
||||
// Check if target host is connected
|
||||
let newHostConnection;
|
||||
if (hostId !== null) {
|
||||
newHostConnection = this.wsServer.hostConnections.get(hostId);
|
||||
if (!newHostConnection) {
|
||||
// The case of the host not getting the assign instance message
|
||||
// still have to be handled, so it's not a requirement that the
|
||||
// target host be connected to the controller while doing the
|
||||
// assignment, but it is IMHO a better user experience if this
|
||||
// is the case.
|
||||
throw new libErrors.RequestError("Target host is not connected to the controller");
|
||||
}
|
||||
}
|
||||
|
||||
// Unassign from currently assigned host if it is connected.
|
||||
let currentAssignedHost = instance.config.get("instance.assigned_host");
|
||||
if (currentAssignedHost !== null && hostId !== currentAssignedHost) {
|
||||
let oldHostConnection = this.wsServer.hostConnections.get(currentAssignedHost);
|
||||
if (oldHostConnection && !oldHostConnection.connector.closing) {
|
||||
await libLink.messages.unassignInstance.send(oldHostConnection, { instance_id: instanceId });
|
||||
}
|
||||
}
|
||||
|
||||
// Assign to target
|
||||
instance.config.set("instance.assigned_host", hostId);
|
||||
if (hostId !== null) {
|
||||
await libLink.messages.assignInstance.send(newHostConnection, {
|
||||
instance_id: instanceId,
|
||||
serialized_config: instance.config.serialize("host"),
|
||||
});
|
||||
} else {
|
||||
instance.status = "unassigned";
|
||||
this.instanceUpdated(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an instance
|
||||
*
|
||||
* Permanently deletes the instance from its assigned host (if assigned)
|
||||
* and the controller. This action cannot be undone.
|
||||
*
|
||||
* @param {number} instanceId - ID of instance to delete.
|
||||
*/
|
||||
async instanceDelete(instanceId) {
|
||||
let instance = this.getRequestInstance(instanceId);
|
||||
if (instance.config.get("instance.assigned_host") !== null) {
|
||||
await this.forwardRequestToInstance(libLink.messages.deleteInstance, { instance_id: instanceId });
|
||||
}
|
||||
this.instances.delete(instanceId);
|
||||
|
||||
let prev = instance.status;
|
||||
instance.status = "deleted";
|
||||
this.instanceUpdated(instance);
|
||||
await libPlugin.invokeHook(this.plugins, "onInstanceStatusChanged", instance, prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the config of an instance updated
|
||||
*
|
||||
* Used to push config changes to the assigned host when the config of
|
||||
* an instance has changed.
|
||||
*
|
||||
* @param {module:controller/src/InstanceInfo} instance -
|
||||
* Instance to nodify the config updated.
|
||||
*/
|
||||
async instanceConfigUpdated(instance) {
|
||||
let hostId = instance.config.get("instance.assigned_host");
|
||||
if (hostId !== null) {
|
||||
let connection = this.wsServer.hostConnections.get(hostId);
|
||||
if (connection) {
|
||||
await libLink.messages.assignInstance.send(connection, {
|
||||
instance_id: instance.config.get("instance.id"),
|
||||
serialized_config: instance.config.serialize("host"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addInstanceHooks(instance) {
|
||||
instance.config.on("fieldChanged", (group, field, prev) => {
|
||||
if (group.name === "instance" && field === "name") {
|
||||
|
||||
Reference in new issue
Block a user