Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
table:
name: game_plugin_cvars
schema: public
object_relationships:
- name: plugin
using:
foreign_key_constraint_on: plugin_slug
select_permissions:
- role: administrator
permission:
columns:
- plugin_slug
- name
- runtime
- version
- kind
- default_value
- description
- flags
- reported_at
filter: {}
comment: ""
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ select_permissions:
- enabled
- disable_server_guidelines
- cfg
- config
- load_ranked
- load_tournaments
- load_custom
Expand All @@ -34,6 +35,7 @@ insert_permissions:
- enabled
- disable_server_guidelines
- cfg
- config
- load_ranked
- load_tournaments
- load_custom
Expand All @@ -48,6 +50,7 @@ update_permissions:
- enabled
- disable_server_guidelines
- cfg
- config
- load_ranked
- load_tournaments
- load_custom
Expand Down
19 changes: 19 additions & 0 deletions hasura/metadata/databases/default/tables/public_game_plugins.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ array_relationships:
table:
name: game_mode_plugins
schema: public
- name: reported_cvars
using:
foreign_key_constraint_on:
column: plugin_slug
table:
name: game_plugin_cvars
schema: public
- name: node_installs
using:
manual_configuration:
Expand Down Expand Up @@ -60,7 +67,11 @@ select_permissions:
- source
- config_schema
- config_path
- config_cvar
- config_default
- config_shipped
- cvars
- forced_cvars
- map_rotation
- pairs_with
- panel
Expand Down Expand Up @@ -92,7 +103,11 @@ insert_permissions:
- source
- config_schema
- config_path
- config_cvar
- config_default
- config_shipped
- cvars
- forced_cvars
- map_rotation
- pairs_with
- panel
Expand Down Expand Up @@ -121,7 +136,11 @@ update_permissions:
- source
- config_schema
- config_path
- config_cvar
- config_default
- config_shipped
- cvars
- forced_cvars
- map_rotation
- pairs_with
- panel
Expand Down
1 change: 1 addition & 0 deletions hasura/metadata/databases/default/tables/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
- "!include public_friends.yaml"
- "!include public_game_mode_plugins.yaml"
- "!include public_game_modes.yaml"
- "!include public_game_plugin_cvars.yaml"
- "!include public_game_plugin_installs.yaml"
- "!include public_game_plugin_versions.yaml"
- "!include public_game_plugins.yaml"
Expand Down
10 changes: 10 additions & 0 deletions hasura/migrations/default/1889000001700_plugin_settings/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
DROP TABLE IF EXISTS public.game_plugin_cvars;

ALTER TABLE public.game_plugin_installs
DROP COLUMN IF EXISTS config;

ALTER TABLE public.game_plugins
DROP COLUMN IF EXISTS config_cvar,
DROP COLUMN IF EXISTS config_default,
DROP COLUMN IF EXISTS config_shipped,
DROP COLUMN IF EXISTS forced_cvars;
28 changes: 28 additions & 0 deletions hasura/migrations/default/1889000001700_plugin_settings/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
ALTER TABLE public.game_plugins
ADD COLUMN IF NOT EXISTS config_cvar text,
ADD COLUMN IF NOT EXISTS config_default jsonb,
ADD COLUMN IF NOT EXISTS config_shipped jsonb,
ADD COLUMN IF NOT EXISTS forced_cvars text[] NOT NULL DEFAULT '{}';

ALTER TABLE public.game_plugin_installs
ADD COLUMN IF NOT EXISTS config jsonb;

-- One row per cvar the catalog lists, as a server running the plugin reported
-- it. default_value is only ever read from a server where none of the panel's
-- configs set that cvar, so it stays NULL until such a server reports.
CREATE TABLE IF NOT EXISTS public.game_plugin_cvars (
plugin_slug text NOT NULL,
name text NOT NULL,
runtime text NOT NULL,
version text NOT NULL,
kind text NOT NULL,
default_value text,
description text NOT NULL DEFAULT '',
flags text NOT NULL DEFAULT '',
reported_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (plugin_slug, name),
CONSTRAINT game_plugin_cvars_plugin_slug_fkey FOREIGN KEY (plugin_slug)
REFERENCES public.game_plugins (slug) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT game_plugin_cvars_kind_check
CHECK (kind IN ('bool', 'int', 'float', 'string'))
);
60 changes: 59 additions & 1 deletion src/dedicated-servers/dedicated-servers.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe("DedicatedServersService.rebuildDedicatedServer", () => {
null as never,
null as never,
postgres as never,
null as never,
);

const steps: Array<string> = [];
Expand Down Expand Up @@ -188,6 +189,8 @@ describe("DedicatedServersService.pingDedicatedServer", () => {
let reachable: boolean;
let hasura: { query: jest.Mock; mutation: jest.Mock };
let notifications: { send: jest.Mock };
let pluginCvars: { harvest: jest.Mock };
let rcon: Record<string, jest.Mock>;
let service: DedicatedServersService;

beforeEach(() => {
Expand All @@ -210,7 +213,8 @@ describe("DedicatedServersService.pingDedicatedServer", () => {
}),
};
notifications = { send: jest.fn().mockResolvedValue(undefined) };
const rcon = {
pluginCvars = { harvest: jest.fn().mockResolvedValue(undefined) };
rcon = {
connect: jest.fn(async () =>
reachable
? {
Expand All @@ -226,6 +230,7 @@ describe("DedicatedServersService.pingDedicatedServer", () => {
: null,
),
disconnect: jest.fn(),
listCvars: jest.fn(async () => []),
};

service = new DedicatedServersService(
Expand All @@ -241,6 +246,7 @@ describe("DedicatedServersService.pingDedicatedServer", () => {
null as never,
notifications as never,
null as never,
pluginCvars as never,
);
});

Expand Down Expand Up @@ -280,6 +286,56 @@ describe("DedicatedServersService.pingDedicatedServer", () => {
expect(row.connected).toBe(true);
});

it("reads plugin cvars over the open connection before closing it", async () => {
const order: Array<string> = [];
pluginCvars.harvest.mockImplementation(
async (_: string, list: (name: string) => Promise<unknown>) => {
await list("dm_replenish_health");
order.push("harvest");
},
);
rcon.disconnect.mockImplementation(async () => {
order.push("disconnect");
});

await service.pingDedicatedServer("server-1");

expect(rcon.listCvars).toHaveBeenCalledWith(
"server-1",
"dm_replenish_health",
);
expect(order).toEqual(["harvest", "disconnect"]);
});

it("tells the harvest a server that just came up has restarted", async () => {
row.connected = false;

await service.pingDedicatedServer("server-1");

expect(pluginCvars.harvest).toHaveBeenCalledWith(
"server-1",
expect.any(Function),
{ restarted: true },
);
});

it("does not ask an unreachable server about its plugins", async () => {
reachable = false;

await service.pingDedicatedServer("server-1");

expect(pluginCvars.harvest).not.toHaveBeenCalled();
});

it("still marks the server connected when reading its cvars fails", async () => {
row.connected = false;
pluginCvars.harvest.mockRejectedValue(new Error("rcon dropped"));

await service.pingDedicatedServer("server-1");

expect(row.connected).toBe(true);
});

it("stays quiet about a server that just stopped answering", async () => {
reachable = false;

Expand Down Expand Up @@ -425,6 +481,7 @@ describe("DedicatedServersService.getServerPlayerList", () => {
null as never,
null as never,
null as never,
null as never,
);

return { service, rcon };
Expand Down Expand Up @@ -711,6 +768,7 @@ describe("DedicatedServersService.setupDedicatedServer", () => {
{ forServer: jest.fn().mockResolvedValue({ maps: [] }) } as never,
null as never,
null as never,
null as never,
);

Object.assign(service, { apps: { createNamespacedDeployment } });
Expand Down
23 changes: 23 additions & 0 deletions src/dedicated-servers/dedicated-servers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ResolvedGameMode,
} from "../game-plugins/game-modes.service";
import { MapRotationService } from "../game-plugins/map-rotation.service";
import { PluginCvarsService } from "../game-plugins/plugin-cvars.service";
import { NotificationsService } from "src/notifications/notifications.service";
import { DISCORD_COLORS } from "src/notifications/utilities/constants";
import { MarkDedicatedServerOffline } from "src/game-server-node/jobs/MarkDedicatedServerOffline";
Expand Down Expand Up @@ -78,6 +79,7 @@ export class DedicatedServersService {
private readonly mapRotationService: MapRotationService,
private readonly notifications: NotificationsService,
private readonly postgres: PostgresService,
private readonly pluginCvars: PluginCvarsService,
) {
this.redis = this.redisManager.getConnection();

Expand Down Expand Up @@ -1112,9 +1114,30 @@ export class DedicatedServersService {
});
}

// Not connected before this ping means it just came up, possibly with a
// different plugin set than the last time it was asked.
await this.readPluginCvars(serverId, !server.connected);

await this.RconService.disconnect(serverId);
}

private async readPluginCvars(
serverId: string,
restarted: boolean,
): Promise<void> {
try {
await this.pluginCvars.harvest(
serverId,
(name) => this.RconService.listCvars(serverId, name),
{ restarted },
);
} catch (error) {
this.logger.warn(
`[${serverId}] could not read plugin cvars: ${error?.message ?? error}`,
);
}
}

public async expectRestart(serverId: string): Promise<void> {
await MarkDedicatedServerOffline.expectRestart(this.redis, serverId);
}
Expand Down
Loading
Loading