Skip to content
Draft
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
5 changes: 4 additions & 1 deletion app/Http/Controllers/api/v1/ServerPoolController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ public function __construct()
public function index(ServerPoolIndexRequest $request)
{
$additionalMeta = [];
$resource = ServerPool::withCount('servers');
$resource = ServerPool::withCount(['servers', 'backupServers']);

// Sort by column, fallback/default is id
$sortBy = match ($request->query('sort_by')) {
'servers_count' => 'servers_count',
'backup_servers_count' => 'backup_servers_count',
'name' => 'LOWER(name)',
default => 'id',
};
Expand Down Expand Up @@ -89,6 +90,7 @@ public function update(ServerPoolRequest $request, ServerPool $serverPool)
$serverPool->name = $request->name;
$serverPool->save();
$serverPool->servers()->sync($request->servers);
$serverPool->backupServers()->sync($request->backup_servers);

return (new ServerPoolResource($serverPool))->withServers();
}
Expand All @@ -105,6 +107,7 @@ public function store(ServerPoolRequest $request)
$serverPool->name = $request->name;
$serverPool->save();
$serverPool->servers()->sync($request->servers);
$serverPool->backupServers()->sync($request->backup_servers);

return (new ServerPoolResource($serverPool))->withServers();
}
Expand Down
5 changes: 5 additions & 0 deletions app/Http/Requests/ServerPoolRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ public function rules()
'name' => ['required', 'string', 'max:255', Rule::unique('server_pools', 'name')],
'description' => ['nullable', 'string', 'max:255'],
'servers' => 'array',
// Backup servers are optional
// but if provided, they must be distinct and exist in the database
'backup_servers' => 'array',
'servers.*' => ['distinct', 'integer', 'exists:App\Models\Server,id'],
'backup_servers.*' => ['distinct', 'integer', 'exists:App\Models\Server,id'],
];

if ($this->serverPool) {
$rules['name'] = ['required', 'string', 'max:255', Rule::unique('server_pools', 'name')->ignore($this->serverPool->id)];

}

return $rules;
Expand Down
4 changes: 4 additions & 0 deletions app/Http/Resources/ServerPoolResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public function toArray($request)
'servers' => $this->when($this->withServers, function () {
return ServerResource::collection($this->servers);
}),
'backup_servers_count' => $this->backupServers()->count(),
'backup_servers' => $this->when($this->withServers, function () {
return ServerResource::collection($this->backupServers);
}),
'model_name' => $this->model_name,
'updated_at' => $this->updated_at,
];
Expand Down
8 changes: 8 additions & 0 deletions app/Models/ServerPool.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ public function servers(): BelongsToMany
return $this->belongsToMany(Server::class);
}

/**
* Backup-Servers that are part of this server pool
*/
public function backupServers(): BelongsToMany
{
return $this->belongsToMany(Server::class, 'backup_server_server_pool');
}

/**
* RoomTypes that are using this server pool
*
Expand Down
27 changes: 25 additions & 2 deletions app/Services/LoadBalancingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,40 @@ class LoadBalancingService
{
private $servers;

private $backupServers;

public function setServerPool(ServerPool $serverPool)
{
$this->servers = $serverPool->servers;
$this->backupServers = $serverPool->backupServers;

return $this;
}

/**
* Find server in the pool with the lowest usage
*
* @return array{server:? Server, isPrefered:bool}
*/
public function getLowestUsageServer(): ?Server
public function getLowestUsageServer(): array
{
return $this->servers
$preferredServer = $this->servers
->where('status', ServerStatus::ENABLED)
->where('recover_count', '>=', config('bigbluebutton.server_online_threshold'))
->where('error_count', '=', 0)
->whereNotNull('load')
->sortBy(function (Server $server) {
return $server->load / $server->strength;
})
->first();

// Check if the preferred server is available
if ($preferredServer) {
return ['server' => $preferredServer, 'isPreferred' => true];
}

// If no server is found, check backup servers
$backupServer = $this->backupServers
->where('status', ServerStatus::ENABLED)
->where('recover_count', '>=', config('bigbluebutton.server_online_threshold'))
->where('error_count', '=', 0)
Expand All @@ -33,5 +54,7 @@ public function getLowestUsageServer(): ?Server
return $server->load / $server->strength;
})
->first();

return ['server' => $backupServer, 'isPreferred' => false];
}
}
8 changes: 7 additions & 1 deletion app/Services/MeetingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function getCallbackUrl(): string
/**
* Start meeting with the properties saved for this meeting and room
*/
public function start(): ?CreateMeetingResponse
public function start(bool $isPreferredServer): ?CreateMeetingResponse
{
// Set meeting parameters
$meetingParams = new CreateMeetingParameters($this->meeting->id, $this->meeting->room->name);
Expand Down Expand Up @@ -107,6 +107,12 @@ public function start(): ?CreateMeetingResponse
$meetingParams->addMeta('bbb-origin', 'PILOS');
$meetingParams->addMeta('pilos-sub-spool-dir', config('recording.spool-sub-directory'));

/* @TODO, optionally show banner on backup-server
if (! $isPreferredServer) {
$meetingParams->setBannerText('Test');
$meetingParams->setBannerColor('#DF2721');
}*/

// get files that should be used in this meeting and add links to the files
$files = $this->meeting->room->files()->where('use_in_meeting', true)->orderBy('default', 'desc')->get();
foreach ($files as $file) {
Expand Down
16 changes: 11 additions & 5 deletions app/Services/RoomService.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,15 @@ public function start(): MeetingService

// Basic load balancing: get server with the lowest usage
$loadBalancingService = new LoadBalancingService;
$server = $loadBalancingService
['server' => $server, 'isPreferred' => $isPreferredServer] = $loadBalancingService
->setServerPool($this->room->roomType->serverPool)
->getLowestUsageServer();

// No preferred Server found, but backup server found
if (! $isPreferredServer && $server != null) {
Log::warning('No preferred server found! Fallback to backup server', ['room' => $this->room->getLogLabel()]);
}

// If no server found, throw error
if ($server == null) {
$lock->release();
Expand All @@ -84,20 +89,21 @@ public function start(): MeetingService
$meeting->save();

$meetingService = new MeetingService($meeting);
$serverType = $isPreferredServer ? 'server' : 'backup server';

Log::info('Starting new meeting for room {room} on server {server}', ['room' => $this->room->getLogLabel(), 'server' => $server->getLogLabel()]);
if (! $meetingService->start()) {
Log::info('Starting new meeting for room {room} on {serverType} {server}', ['room' => $this->room->getLogLabel(), 'serverType' => $serverType, 'server' => $server->getLogLabel()]);
if (! $meetingService->start($isPreferredServer)) {
// Creating Meeting failed, remove meeting
$meeting->forceDelete();

$lock->release();
Log::error('Failed to start meeting for room {room} on server {server}', ['room' => $this->room->getLogLabel(), 'server' => $server->getLogLabel()]);
Log::error('Failed to start meeting for room {room} on {serverType} {server}', ['room' => $this->room->getLogLabel(), 'serverType' => $serverType, 'server' => $server->getLogLabel()]);
Counter::get('room_start_errors_total')->inc('start_failed');
abort(CustomStatusCodes::ROOM_START_FAILED->value, __('app.errors.room_start'));
}

Counter::get('room_started_total')->inc();
Log::info('Successfully started new meeting for room {room} on server {server}', ['room' => $this->room->getLogLabel(), 'server' => $server->getLogLabel()]);
Log::info('Successfully started new meeting for room {room} on {serverType} {server}', ['room' => $this->room->getLogLabel(), 'server' => $server->getLogLabel()]);

// Set start time after successful api call, prevents server poller from ending a meeting that has been started
// but the api call has not been completed yet therefore the meeting will not be found on the server
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('backup_server_server_pool', function (Blueprint $table) {
$table->foreignId('server_id')->constrained()->onDelete('cascade');
$table->foreignId('server_pool_id')->constrained()->onDelete('cascade');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('backup_server_server_pool');
}
};
4 changes: 4 additions & 0 deletions lang/en/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@
'view_all' => 'Show all rooms',
],
'server_pools' => [
'backup_servers' => 'Backup servers',
'create' => 'Create server pools',
'delete' => 'Delete server pools',
'select_server' => 'Select server',
'title' => 'Server pools',
'update' => 'Edit server pools',
'view' => 'Show server pools',
Expand Down Expand Up @@ -191,6 +193,8 @@
'view' => 'Detailed information for the room type :name',
],
'server_pools' => [
'backup_servers' => 'Backup servers',
'backup_server_count' => 'Number of backup servers',
'delete' => [
'confirm' => 'Do you really want to delete the server pool :name?',
'failed' => 'Server pool can\'t be deleted because the following room types still use it:',
Expand Down
5 changes: 5 additions & 0 deletions resources/js/views/AdminServerPoolsIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
field="servers_count"
sortable
></Column>
<Column
:header="$t('admin.server_pools.backup_server_count')"
field="backup_servers_count"
sortable
></Column>
<Column
v-if="actionColumn.visible"
:header="$t('app.actions')"
Expand Down
98 changes: 98 additions & 0 deletions resources/js/views/AdminServerPoolsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
<div class="col-span-12 md:col-span-8">
<InputGroup>
<multiselect
id="servers"
ref="serversMultiselectRef"
v-model="model.servers"
data-test="server-dropdown"
Expand Down Expand Up @@ -179,6 +180,97 @@
<FormError :errors="formErrors.fieldError('servers', true)" />
</div>
</div>
<!-- Begin server field for backup server -->
<div
class="field grid grid-cols-12 gap-4"
data-test="backup-server-field"
>
<label
id="backup-server-label"
class="col-span-12 md:col-span-4 md:mb-0"
>{{ $t("admin.server_pools.backup_servers") }}</label
>
<div class="col-span-12 md:col-span-8">
<InputGroup>
<multiselect
id="servers"
ref="backupServersMultiselectRef"
v-model="model.backup_servers"
data-test="backup-server-dropdown"
aria-labelledby="backup-servers-label"
:placeholder="$t('admin.server_pools.select_servers')"
track-by="id"
open-direction="bottom"
:multiple="true"
:searchable="false"
:internal-search="false"
:clear-on-select="false"
:close-on-select="false"
:show-no-results="false"
:show-labels="false"
:options="servers"
:disabled="
isBusy ||
modelLoadingError ||
serversLoading ||
serversLoadingError ||
viewOnly
"
:loading="serversLoading"
:allow-empty="true"
:class="{
'is-invalid': formErrors.fieldInvalid('backup_servers', true),
}"
>
<template #noOptions>
{{ $t("admin.servers.no_data") }}
</template>
<template #option="{ option }">
{{ option.name }}
</template>
<template #tag="{ option, remove }">
<Chip :label="option.name" data-test="server-chip">
<span>{{ option.name }}</span>
<Button
v-if="!viewOnly"
severity="contrast"
class="h-5 w-5 rounded-full text-sm"
icon="fas fa-xmark"
:aria-label="
$t('admin.server_pools.remove_server', {
name: option.name,
})
"
data-test="remove-server-button"
@click="remove(option)"
/>
</Chip>
</template>
<template #afterList>
<Button
:disabled="serversLoading || serversCurrentPage === 1"
outlined
severity="secondary"
icon="fa-solid fa-arrow-left"
:label="$t('app.previous_page')"
data-test="previous-page-button"
@click="loadServers(Math.max(1, serversCurrentPage - 1))"
/>
<Button
:disabled="serversLoading || !serversHasNextPage"
outlined
severity="secondary"
icon="fa-solid fa-arrow-right"
:label="$t('app.next_page')"
data-test="next-page-button"
@click="loadServers(serversCurrentPage + 1)"
/>
</template>
</multiselect>
</InputGroup>
</div>
</div>
<!-- End server field for backup server -->
<div v-if="!viewOnly">
<div class="flex justify-end">
<Button
Expand Down Expand Up @@ -256,6 +348,7 @@ const props = defineProps({

const model = ref({
servers: [],
backup_servers: [],
});
const name = ref("");

Expand All @@ -277,6 +370,7 @@ const serversCurrentPage = ref(1);
const serversHasNextPage = ref(false);
const serversLoadingError = ref(false);
const serversMultiselectRef = ref(false);
const backupServersMultiselectRef = ref(false);

/**
* Loads the server pool and servers from the backend
Expand Down Expand Up @@ -341,6 +435,7 @@ function loadServers(page = 1) {
})
.catch((error) => {
serversMultiselectRef.value.deactivate();
backupServersMultiselectRef.value.deactivate();
serversLoadingError.value = true;
api.error(error);
})
Expand All @@ -363,6 +458,9 @@ function saveServerPool() {
};

config.data.servers = config.data.servers.map((server) => server.id);
config.data.backup_servers = config.data.backup_servers.map(
(backupServer) => backupServer.id,
);

api
.call(
Expand Down
Loading