Skip to content

Commit

Permalink
Merge pull request #787 from gettakaro/keep-track-of-cached-reachabil…
Browse files Browse the repository at this point in the history
…ity-status-for-servers
  • Loading branch information
niekcandaele authored Dec 24, 2023
2 parents 19b65ad + 5929917 commit ea45eda
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 81 deletions.
26 changes: 18 additions & 8 deletions packages/app-api/src/db/gameserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class GameServerModel extends TakaroModel {
name!: string;

connectionInfo!: Record<string, unknown>;
reachable!: boolean;

type!: GAME_SERVER_TYPE;

Expand Down Expand Up @@ -150,16 +151,25 @@ export class GameServerRepo extends ITakaroRepo<

async update(id: string, item: GameServerUpdateDTO): Promise<GameServerOutputDTO> {
const { query } = await this.getModel();
const encryptedConnectionInfo = await encrypt(item.connectionInfo);
const data = {

const updateData: Record<string, unknown> = {
...item.toJSON(),
connectionInfo: encryptedConnectionInfo,
} as unknown as Partial<GameServerModel>;
const res = await query.updateAndFetchById(id, data).returning('*');
return new GameServerOutputDTO().construct({
...res,
connectionInfo: JSON.parse(item.connectionInfo),
};

if (item.connectionInfo) {
const encryptedConnectionInfo = await encrypt(item.connectionInfo);
updateData.connectionInfo = encryptedConnectionInfo as unknown as Record<string, unknown>;
}

// Remove all undefined values
Object.keys(updateData).forEach((key) => {
if (updateData[key] === undefined) {
delete updateData[key];
}
});

await query.updateAndFetchById(id, updateData);
return this.findOne(id);
}

async getModuleInstallation(gameserverId: string, moduleId: string) {
Expand Down
17 changes: 15 additions & 2 deletions packages/app-api/src/service/GameServerService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TakaroService } from './Base.js';

import { GameServerModel, GameServerRepo } from '../db/gameserver.js';
import { IsEnum, IsJSON, IsObject, IsOptional, IsString, IsUUID, Length } from 'class-validator';
import { IsBoolean, IsEnum, IsJSON, IsObject, IsOptional, IsString, IsUUID, Length } from 'class-validator';
import {
IMessageOptsDTO,
IGameServer,
Expand Down Expand Up @@ -52,6 +52,8 @@ export class GameServerOutputDTO extends TakaroModelDTO<GameServerOutputDTO> {
@IsString()
@IsEnum(GAME_SERVER_TYPE)
type: GAME_SERVER_TYPE;
@IsBoolean()
reachable: boolean;
}

export class GameServerCreateDTO extends TakaroDTO<GameServerCreateDTO> {
Expand All @@ -74,6 +76,9 @@ export class GameServerUpdateDTO extends TakaroDTO<GameServerUpdateDTO> {
@IsString()
@IsEnum(GAME_SERVER_TYPE)
type: GAME_SERVER_TYPE;
@IsBoolean()
@IsOptional()
reachable: boolean;
}

export class ModuleInstallDTO extends TakaroDTO<ModuleInstallDTO> {
Expand Down Expand Up @@ -177,7 +182,15 @@ export class GameServerService extends TakaroService<
async testReachability(id?: string, connectionInfo?: Record<string, unknown>, type?: GAME_SERVER_TYPE) {
if (id) {
const instance = await this.getGame(id);
return instance.testReachability();
const reachability = await instance.testReachability();

if (reachability.connectable) {
await this.repo.update(id, await new GameServerUpdateDTO().construct({ reachable: true }));
} else {
await this.repo.update(id, await new GameServerUpdateDTO().construct({ reachable: false }));
}

return reachability;
} else if (connectionInfo && type) {
const instance = await getGame(type, connectionInfo, {});
return instance.testReachability();
Expand Down
68 changes: 36 additions & 32 deletions packages/app-api/src/workers/playerSyncWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,42 +39,46 @@ export async function processJob(job: Job<IGameServerQueueData>) {
const domainsService = new DomainService();
const domains = await domainsService.find({});

for (const domain of domains.results) {
const promises = [];

const playerService = new PlayerService(domain.id);
promises.push(
playerService.handleSteamSync().then(() => job.log(`Synced steam players for domain: ${domain.id}`))
);

const gameserverService = new GameServerService(domain.id);
const gameServers = await gameserverService.find({});
promises.push(
...gameServers.results.map(async (gs) => {
const reachable = await gameserverService.testReachability(gs.id);
if (reachable.connectable) {
await queueService.queues.playerSync.queue.add(
{ domainId: domain.id, gameServerId: gs.id },
{ jobId: `playerSync-${domain.id}-${gs.id}-${Date.now()}` }
);
await job.log(`Added playerSync job for domain: ${domain.id} and game server: ${gs.id}`);
}
})
);
const domainPromises = [];

domainPromises.push(
...domains.results.map(async (domain) => {
const promises = [];

const playerService = new PlayerService(domain.id);
promises.push(
playerService.handleSteamSync().then(() => job.log(`Synced steam players for domain: ${domain.id}`))
);

const gameserverService = new GameServerService(domain.id);
const gameServers = await gameserverService.find({});
promises.push(
...gameServers.results.map(async (gs) => {
const reachable = await gameserverService.testReachability(gs.id);
if (reachable.connectable) {
await queueService.queues.playerSync.queue.add(
{ domainId: domain.id, gameServerId: gs.id },
{ jobId: `playerSync-${domain.id}-${gs.id}-${Date.now()}` }
);
await job.log(`Added playerSync job for domain: ${domain.id} and game server: ${gs.id}`);
}
})
);

const res = await Promise.allSettled(promises);
const res = await Promise.allSettled(promises);

for (const r of res) {
if (r.status === 'rejected') {
log.error(r.reason);
await job.log(r.reason);
for (const r of res) {
if (r.status === 'rejected') {
log.error(r.reason);
await job.log(r.reason);
}
}
}

if (res.some((r) => r.status === 'rejected')) {
throw new Error('Some promises failed');
}
}
if (res.some((r) => r.status === 'rejected')) {
throw new Error('Some promises failed');
}
})
);

return;
}
Expand Down
18 changes: 12 additions & 6 deletions packages/lib-apiclient/src/generated/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2084,6 +2084,12 @@ export interface GameServerOutputDTO {
* @memberof GameServerOutputDTO
*/
type: GameServerOutputDTOTypeEnum;
/**
*
* @type {boolean}
* @memberof GameServerOutputDTO
*/
reachable: boolean;
/**
*
* @type {string}
Expand Down Expand Up @@ -2357,6 +2363,12 @@ export interface GameServerUpdateDTO {
* @memberof GameServerUpdateDTO
*/
type: GameServerUpdateDTOTypeEnum;
/**
*
* @type {boolean}
* @memberof GameServerUpdateDTO
*/
reachable?: boolean;
}

export const GameServerUpdateDTOTypeEnum = {
Expand Down Expand Up @@ -4690,12 +4702,6 @@ export interface PlayerOnGameServerUpdateDTO {
* @memberof PlayerOnGameServerUpdateDTO
*/
currency?: number;
/**
*
* @type {boolean}
* @memberof PlayerOnGameServerUpdateDTO
*/
online?: boolean;
}
/**
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('gameservers', (table) => {
table.boolean('reachable').defaultTo(true);
});
}

export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('gameservers', (table) => {
table.dropColumn('reachable');
});
}
14 changes: 7 additions & 7 deletions packages/test/src/__snapshots__/GameServerController/Create.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
{
"body": {
"meta": {
"serverTime": "2022-09-13T19:27:39.651Z"
},
"meta": {},
"data": {
"id": "59f2cdbf-ef7d-495f-be9e-531d4fd6ee29",
"createdAt": "2023-12-24T15:22:57.559Z",
"updatedAt": "2023-12-24T15:22:57.559Z",
"name": "Test gameserver",
"connectionInfo": {
"host": "http://takaro:3002"
},
"type": "MOCK",
"createdAt": "2022-09-13T19:27:39.649Z",
"updatedAt": "2022-09-13T19:27:39.650Z",
"id": "abc10e29-6aee-48c0-b0cf-4ff1f6017770"
"reachable": true
}
},
"status": 200,
"test": {
"group": "GameServerController",
"snapshot": true,
"name": "Create",
"expectedStatus": 200,
"filteredFields": [
"connectionInfo"
],
"expectedStatus": 200,
"standardEnvironment": true
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
{
"body": {
"meta": {
"serverTime": "2022-09-13T19:27:39.353Z"
},
"meta": {},
"data": {
"createdAt": "2022-09-13T19:27:39.342Z",
"updatedAt": "2022-09-13T19:27:39.343Z",
"id": "4bca3c37-b82a-47fc-80e4-096e27758804",
"id": "2600aac2-d5b8-455d-bf6a-12a242f5ee0a",
"createdAt": "2023-12-24T15:22:57.034Z",
"updatedAt": "2023-12-24T15:22:57.034Z",
"name": "Test gameserver",
"type": "MOCK",
"connectionInfo": {
"host": "http://takaro:3002"
}
},
"type": "MOCK",
"reachable": true
}
},
"status": 200,
"test": {
"group": "GameServerController",
"snapshot": true,
"name": "Get by ID",
"expectedStatus": 200,
"filteredFields": [
"connectionInfo"
],
"expectedStatus": 200,
"standardEnvironment": true
}
}
12 changes: 6 additions & 6 deletions packages/test/src/__snapshots__/GameServerController/Update.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
{
"body": {
"meta": {
"serverTime": "2022-09-13T19:27:39.990Z"
},
"meta": {},
"data": {
"id": "4218a6e4-001a-4175-a346-f9b8a920c3f4",
"createdAt": "2023-12-24T15:22:57.938Z",
"updatedAt": "2023-12-24T15:22:58.000Z",
"name": "Test gameserver 2",
"connectionInfo": {
"host": "somewhere.else",
"port": 9876
},
"type": "MOCK",
"updatedAt": "2022-09-13T19:27:39.988Z",
"createdAt": "2022-09-13T19:27:39.979Z",
"id": "0ada19e8-710d-4e68-9ff1-ffb7a3c1405f"
"reachable": true
}
},
"status": 200,
"test": {
"group": "GameServerController",
"snapshot": true,
"name": "Update",
"expectedStatus": 200,
"filteredFields": [],
Expand Down
17 changes: 6 additions & 11 deletions packages/web-main/src/components/GameServerCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { FC, MouseEvent, useState } from 'react';
import { Button, Chip, Dialog, Dropdown, Skeleton, IconButton, Tooltip, PERMISSIONS } from '@takaro/lib-components';
import { Button, Chip, Dialog, Dropdown, IconButton, Tooltip, PERMISSIONS } from '@takaro/lib-components';
import { Body, Header, Container, EmptyContainer, TitleContainer, StyledDialogBody } from './style';
import { GameServerOutputDTO } from '@takaro/apiclient';
import { useNavigate } from 'react-router-dom';

import { AiOutlineMenu as MenuIcon, AiOutlinePlus as PlusIcon } from 'react-icons/ai';
import { PATHS } from 'paths';
import { useGameServerRemove, useGameServerReachabilityById } from 'queries/gameservers';
import { useGameServerRemove } from 'queries/gameservers';
import { useSelectedGameServer } from 'hooks/useSelectedGameServerContext';
import { PermissionsGuard } from 'components/PermissionsGuard';

export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type }) => {
export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type, reachable }) => {
const [openDialog, setOpenDialog] = useState<boolean>(false);
const navigate = useNavigate();

const { isLoading, data } = useGameServerReachabilityById(id);
const { mutateAsync, isLoading: isDeleting } = useGameServerRemove();

const { selectedGameServerId, setSelectedGameServerId } = useSelectedGameServer();
Expand All @@ -38,8 +37,6 @@ export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type }) => {
await mutateAsync({ id });
};

const status = data?.connectable ? 'online' : 'offline';

return (
<>
<Container
Expand All @@ -49,12 +46,10 @@ export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type }) => {
>
<Body>
<Header>
{isLoading || !data ? (
<Skeleton variant="text" width="50px" height="15px" />
) : status === 'online' ? (
<>{status}</>
{reachable ? (
<Chip label={'online'} color="success" variant="outline" />
) : (
<Chip label={status} color="error" variant="outline" />
<Chip label={'offline'} color="error" variant="outline" />
)}
<PermissionsGuard requiredPermissions={[[PERMISSIONS.READ_GAMESERVERS, PERMISSIONS.MANAGE_GAMESERVERS]]}>
<Dropdown>
Expand Down

0 comments on commit ea45eda

Please sign in to comment.