Mehrere Docker-Hosts: Matches verteilt auf die Hosts starten
CS2_DOCKER_HOSTS nimmt eine Liste von Docker-Hosts. Jedes Match läuft auf dem Host mit den meisten freien Ports und merkt sich seinen Host, damit Status, Konfiguration und Stopp dort landen. Ohne die Variable bleibt alles beim bisherigen einzelnen Host. deploy/docker-host-einrichten.sh richtet einen Host per SSH ein.
This commit is contained in:
1 parent
1a304cb398
commit
eda15bc585
9 files changed
+297
-5
No files matched your search
@@ -0,0 +1,82 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { DockerHostPool } from '../src/servers/dockerAdapter.js';
|
||||
|
||||
const secrets = { rconPassword: 'Rcon123', serverPassword: 'Pw123', tvPassword: 'Tv123' };
|
||||
|
||||
const hostA = { name: 'a', publicHost: '10.0.0.1', portRangeStart: 27100, portRangeEnd: 27103 }; // 2 Paare
|
||||
const hostB = { name: 'b', publicHost: '10.0.0.2', portRangeStart: 27100, portRangeEnd: 27139 }; // 20 Paare
|
||||
|
||||
function options(hosts) {
|
||||
return {
|
||||
bin: 'docker', image: 'joedwards32/cs2:latest', host: '10.0.0.9', rconHost: '10.0.0.9',
|
||||
portRangeStart: 27015, portRangeEnd: 27115, gslt: '', volume: 'cs2-mapveto-gamefiles', extraArgs: '',
|
||||
startTimeoutMinutes: 1, hosts,
|
||||
};
|
||||
}
|
||||
|
||||
/** Docker-Aufrufe merken, Container gelten sofort als fertig. */
|
||||
function fakeDocker() {
|
||||
const calls = [];
|
||||
const run = async (args) => {
|
||||
calls.push(args);
|
||||
if (args[0] === 'inspect') return 'running';
|
||||
if (args[0] === 'logs') return 'Starting CS2 Dedicated Server';
|
||||
return '';
|
||||
};
|
||||
return { run, calls };
|
||||
}
|
||||
|
||||
/** Nur die Konfiguration der Hosts, ohne auf echte Container zu warten. */
|
||||
function pool(hosts, used = {}) {
|
||||
const docker = fakeDocker();
|
||||
const p = new DockerHostPool(options(hosts), {
|
||||
getUsedPorts: (publicHost) => new Set(used[publicHost] ?? []),
|
||||
runDocker: docker.run,
|
||||
});
|
||||
for (const a of p.adapters.values()) a.waitForUpdateCheck = async () => {};
|
||||
return { pool: p, calls: docker.calls };
|
||||
}
|
||||
|
||||
test('Ohne CS2_DOCKER_HOSTS gibt es genau einen Host, wie bisher', () => {
|
||||
const { pool: p } = pool(null);
|
||||
assert.equal(p.hosts.length, 1);
|
||||
assert.equal(p.hosts[0].publicHost, '10.0.0.9');
|
||||
});
|
||||
|
||||
test('Ein Match landet auf dem Host mit den meisten freien Ports', async () => {
|
||||
const { pool: p } = pool([hostA, hostB], { '10.0.0.1': [27100] }); // A hat ein Paar belegt, B ist frei
|
||||
const r = await p.start({ matchId: 'm1', mode: 'competitive', secrets });
|
||||
assert.equal(r.handle.hostName, 'b');
|
||||
assert.equal(r.host, '10.0.0.2');
|
||||
});
|
||||
|
||||
test('Ein voller Host wird übersprungen', async () => {
|
||||
const fullA = Array.from({ length: 4 }, (_, i) => 27100 + i); // alle Ports von A belegt
|
||||
const { pool: p } = pool([hostA, hostB], { '10.0.0.1': fullA });
|
||||
const r = await p.start({ matchId: 'm2', mode: 'wingman', secrets });
|
||||
assert.equal(r.handle.hostName, 'b');
|
||||
});
|
||||
|
||||
test('Sind alle Hosts voll, gibt es einen klaren Fehler', async () => {
|
||||
const fullA = Array.from({ length: 4 }, (_, i) => 27100 + i);
|
||||
const fullB = Array.from({ length: 40 }, (_, i) => 27100 + i);
|
||||
const { pool: p } = pool([hostA, hostB], { '10.0.0.1': fullA, '10.0.0.2': fullB });
|
||||
await assert.rejects(p.start({ matchId: 'm3', mode: 'wingman', secrets }), /Kein Docker-Host mit freien Ports/);
|
||||
});
|
||||
|
||||
test('Status, Stopp und Befehle gehen an den Host des Matches', async () => {
|
||||
const { pool: p, calls } = pool([hostA, hostB], { '10.0.0.1': [27100] });
|
||||
const r = await p.start({ matchId: 'm4', mode: 'competitive', secrets });
|
||||
calls.length = 0;
|
||||
await p.stop(r.handle);
|
||||
assert.deepEqual(calls.at(-1)[0], 'rm');
|
||||
assert.equal(calls.at(-1).at(-1), r.handle.container);
|
||||
// Ein Handle von einem Host, der nicht mehr konfiguriert ist, wird abgelehnt
|
||||
await assert.rejects(p.status({ ...r.handle, hostName: 'gelöscht' }), /nicht \(mehr\) konfiguriert/);
|
||||
});
|
||||
|
||||
test('Alte Handles ohne Host-Namen laufen auf dem ersten Host', () => {
|
||||
const { pool: p } = pool([hostA, hostB]);
|
||||
assert.equal(p.adapterFor({ container: 'x' }), p.adapters.get('a'));
|
||||
});
|
||||
Reference in new issue
Block a user