mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +09:00
Merge remote-tracking branch 'upstream/master' into fix/host-save-details-ownership
This commit is contained in:
commit
97bab72eb9
16 files changed
+410
-27
No files matched your search
@@ -1,7 +1,44 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { ControlConnection } from "@clusterio/controller";
|
||||
import events from "node:events";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { Controller, ControlConnection, ControllerHooks, InstanceManager } from "@clusterio/controller";
|
||||
import { MockConnector, MockController, MockLogger } from "../mock.js";
|
||||
|
||||
const addr = lib.Address.fromShorthand;
|
||||
|
||||
describe("controller/src/ControlConnection", function() {
|
||||
describe(".handleDebugDumpWsRequest()", function() {
|
||||
function makeConnection() {
|
||||
const controllerConfig = new lib.ControllerConfig("controller");
|
||||
const connector = new lib.VirtualConnector(
|
||||
lib.Address.fromShorthand("controller"),
|
||||
lib.Address.fromShorthand({ controlId: 1 }),
|
||||
);
|
||||
connector._socket = {};
|
||||
const controller = new Controller(lib.logger, [], controllerConfig);
|
||||
const user = controller.users.getOrCreateUser("test");
|
||||
return new ControlConnection({ version: "2.0.0" }, connector, controller, user, 1);
|
||||
}
|
||||
|
||||
it("re-applies clusterio_ignore_dump to the socket on resume", async function() {
|
||||
const connection = makeConnection();
|
||||
await connection.handleDebugDumpWsRequest(new lib.DebugDumpWsRequest());
|
||||
// A resume installs a fresh socket without the flag.
|
||||
const resumedSocket = {};
|
||||
connection.connector._socket = resumedSocket;
|
||||
connection.connector.emit("resume");
|
||||
assert.equal(resumedSocket.clusterio_ignore_dump, true);
|
||||
});
|
||||
|
||||
it("leaves the flag off on resume without a dumper", function() {
|
||||
const connection = makeConnection();
|
||||
const resumedSocket = {};
|
||||
connection.connector._socket = resumedSocket;
|
||||
connection.connector.emit("resume");
|
||||
assert.equal(resumedSocket.clusterio_ignore_dump, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".handleControllerRestartRequest()", function() {
|
||||
let mockController;
|
||||
|
||||
@@ -38,4 +75,138 @@ describe("controller/src/ControlConnection", function() {
|
||||
assert.equal(mockController.stopped, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".handleInstanceCreateRequest()", function() {
|
||||
let mockController;
|
||||
|
||||
beforeEach(function() {
|
||||
mockController = new MockController();
|
||||
mockController.mockConfigEntries.set("controller.name", "Test");
|
||||
mockController.instances = new InstanceManager(new lib.SubscribableDatastore(), mockController);
|
||||
});
|
||||
|
||||
async function create(config, cloneFromId) {
|
||||
await ControlConnection.prototype.handleInstanceCreateRequest.call(
|
||||
{ _controller: mockController },
|
||||
new lib.InstanceCreateRequest(config, cloneFromId),
|
||||
);
|
||||
}
|
||||
|
||||
it("rejects instance.assigned_host in the config", async function() {
|
||||
await assert.rejects(
|
||||
create({ "instance.id": 4001, "instance.name": "c1", "instance.assigned_host": 10 }),
|
||||
new lib.RequestError("instance.assigned_host must be set through the assign-host interface"),
|
||||
);
|
||||
assert.equal(mockController.instances.has(4001), false);
|
||||
});
|
||||
|
||||
it("creates an unassigned clone of an assigned instance", async function() {
|
||||
await create({ "instance.id": 4001, "instance.name": "base" });
|
||||
mockController.instances.getMutable(4001).config.set("instance.assigned_host", 10);
|
||||
|
||||
await create({ "instance.id": 4002, "instance.name": "clone", "instance.assigned_host": null }, 4001);
|
||||
const clone = mockController.instances.get(4002);
|
||||
assert.equal(clone.config.get("instance.name"), "clone");
|
||||
assert.equal(clone.config.get("instance.assigned_host"), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("close cleanup", function() {
|
||||
let connector;
|
||||
let mockController;
|
||||
let connection;
|
||||
|
||||
beforeEach(function() {
|
||||
connector = new MockConnector(addr("controller"), addr({ controlId: 1 }));
|
||||
connector._socket = {};
|
||||
const transports = new Set();
|
||||
mockController = {
|
||||
hooks: new ControllerHooks(new MockLogger()),
|
||||
router: null,
|
||||
_registeredRequests: new Map(),
|
||||
_fallbackedRequests: new Map(),
|
||||
_registeredEvents: new Map(),
|
||||
_snoopedEvents: new Map(),
|
||||
subscriptions: { unsubscribeLink() {} },
|
||||
clusterLogger: {
|
||||
transports,
|
||||
add(transport) { transports.add(transport); },
|
||||
remove(transport) { transports.delete(transport); },
|
||||
},
|
||||
debugEvents: new events.EventEmitter(),
|
||||
sendRequestToHostByInstanceId() {},
|
||||
};
|
||||
connection = new ControlConnection({ version: "test" }, connector, mockController, {}, 1);
|
||||
});
|
||||
|
||||
it("removes the log transport from clusterLogger", async function() {
|
||||
await connection.handleLogSetSubscriptionsRequest(
|
||||
new lib.LogSetSubscriptionsRequest(true, false, [], [])
|
||||
);
|
||||
assert.equal(mockController.clusterLogger.transports.size, 1);
|
||||
connector.emit("close");
|
||||
assert.equal(mockController.clusterLogger.transports.size, 0);
|
||||
assert.equal(connection.logTransport, null);
|
||||
});
|
||||
|
||||
it("removes the debug dump listener after repeated requests", async function() {
|
||||
await connection.handleDebugDumpWsRequest(new lib.DebugDumpWsRequest());
|
||||
await connection.handleDebugDumpWsRequest(new lib.DebugDumpWsRequest());
|
||||
assert.equal(mockController.debugEvents.listenerCount("message"), 1);
|
||||
assert.equal(connector._socket.clusterio_ignore_dump, true);
|
||||
connector.emit("close");
|
||||
assert.equal(mockController.debugEvents.listenerCount("message"), 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".handleUserDeleteRequest()", function() {
|
||||
let mockController;
|
||||
let nextId;
|
||||
|
||||
beforeEach(function() {
|
||||
mockController = new MockController();
|
||||
mockController.sendTo = () => {};
|
||||
mockController.wsServer.controlConnections = new Map();
|
||||
nextId = 1;
|
||||
});
|
||||
|
||||
function connect(name) {
|
||||
const connection = {
|
||||
_controller: mockController,
|
||||
user: mockController.users.getByName(name),
|
||||
connector: {
|
||||
terminated: false,
|
||||
terminate() { this.terminated = true; },
|
||||
},
|
||||
};
|
||||
mockController.wsServer.controlConnections.set(nextId, connection);
|
||||
nextId += 1;
|
||||
return connection;
|
||||
}
|
||||
|
||||
async function deleteUser(connection, name) {
|
||||
await ControlConnection.prototype.handleUserDeleteRequest.call(
|
||||
connection, new lib.UserDeleteRequest(name)
|
||||
);
|
||||
}
|
||||
|
||||
it("terminates connections of the deleted user", async function() {
|
||||
const admin = connect("test");
|
||||
const player1 = connect("player");
|
||||
const player2 = connect("player");
|
||||
|
||||
await deleteUser(admin, "player");
|
||||
assert.equal(mockController.users.getByName("player"), undefined);
|
||||
assert.equal(player1.connector.terminated, true);
|
||||
assert.equal(player2.connector.terminated, true);
|
||||
assert.equal(admin.connector.terminated, false);
|
||||
});
|
||||
|
||||
it("terminates its own connection when deleting itself", async function() {
|
||||
const player = connect("player");
|
||||
|
||||
await deleteUser(player, "player");
|
||||
assert.equal(player.connector.terminated, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -298,6 +298,45 @@ describe("controller/InstanceManager", function () {
|
||||
assert(assignRequest instanceof lib.InstanceAssignInternalRequest);
|
||||
assert.equal(assignRequest.instanceId, 1);
|
||||
});
|
||||
|
||||
it("should leave instance unassigned if the new host fails the assign", async function () {
|
||||
let unassignRequest;
|
||||
controller.wsServer.hostConnections.set(5, {
|
||||
send: async (request) => {
|
||||
unassignRequest = request;
|
||||
},
|
||||
connector: { closing: false },
|
||||
});
|
||||
let failAssign = true;
|
||||
let assignRequest;
|
||||
controller.wsServer.hostConnections.set(6, {
|
||||
send: async (request) => {
|
||||
if (failAssign) {
|
||||
throw new lib.RequestError("EACCES: permission denied");
|
||||
}
|
||||
assignRequest = request;
|
||||
},
|
||||
connector: { closing: false },
|
||||
});
|
||||
|
||||
instance.config.set("instance.assigned_host", 5);
|
||||
instance.status = "stopped";
|
||||
await assert.rejects(
|
||||
() => instances.assignInstance(1, 6),
|
||||
{ message: "EACCES: permission denied" }
|
||||
);
|
||||
|
||||
assert(unassignRequest instanceof lib.InstanceUnassignInternalRequest);
|
||||
const stored = instances.records.get(1);
|
||||
assert.equal(stored.config.get("instance.assigned_host"), null);
|
||||
assert.equal(stored.status, "unassigned");
|
||||
|
||||
failAssign = false;
|
||||
await instances.assignInstance(1, 6);
|
||||
assert.equal(instance.config.get("instance.assigned_host"), 6);
|
||||
assert(assignRequest instanceof lib.InstanceAssignInternalRequest);
|
||||
assert.equal(assignRequest.instanceId, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".unassignInstance()", function () {
|
||||
|
||||
@@ -79,6 +79,23 @@ describe("controller/UserManager", function () {
|
||||
const user = userManager.createUser("Eve");
|
||||
assert(user.roleIds.has(1));
|
||||
});
|
||||
|
||||
it("should set tokenValidAfter to the current time", function () {
|
||||
const before = Math.floor(Date.now() / 1000);
|
||||
const user = userManager.createUser("Mallory");
|
||||
assert(user.tokenValidAfter >= before, "tokenValidAfter predates the call");
|
||||
assert(user.tokenValidAfter <= Math.floor(Date.now() / 1000), "tokenValidAfter is in the future");
|
||||
});
|
||||
|
||||
it("should invalidate tokens issued before a user of the same name was recreated", function () {
|
||||
const user = userManager.createUser("Niaj");
|
||||
const oldTokenIat = user.tokenValidAfter - 60;
|
||||
userManager.deleteUser(user);
|
||||
|
||||
// WsServer.registerControl rejects the token when iat < tokenValidAfter
|
||||
const recreated = userManager.createUser("Niaj");
|
||||
assert(oldTokenIat < recreated.tokenValidAfter, "token from before the deletion is still valid");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".getOrCreateUser()", function () {
|
||||
|
||||
Reference in new issue
Block a user