Streaming Transfers (#4548)
This commit is contained in:
parent
032e4f2e31
commit
df2402b54f
6 changed files with 38 additions and 106 deletions
|
@ -2,15 +2,17 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
||||||
|
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
use Prologue\Alerts\AlertsMessageBag;
|
||||||
use Pterodactyl\Models\ServerTransfer;
|
use Pterodactyl\Models\ServerTransfer;
|
||||||
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
use Pterodactyl\Services\Servers\TransferService;
|
use Pterodactyl\Services\Nodes\NodeJWTService;
|
||||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
|
||||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
||||||
|
|
||||||
class ServerTransferController extends Controller
|
class ServerTransferController extends Controller
|
||||||
|
@ -21,9 +23,10 @@ class ServerTransferController extends Controller
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private AlertsMessageBag $alert,
|
private AlertsMessageBag $alert,
|
||||||
private AllocationRepositoryInterface $allocationRepository,
|
private AllocationRepositoryInterface $allocationRepository,
|
||||||
private NodeRepository $nodeRepository,
|
private ConnectionInterface $connection,
|
||||||
private TransferService $transferService,
|
private DaemonTransferRepository $daemonTransferRepository,
|
||||||
private DaemonConfigurationRepository $daemonConfigurationRepository
|
private NodeJWTService $nodeJWTService,
|
||||||
|
private NodeRepository $nodeRepository
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,12 +49,15 @@ class ServerTransferController extends Controller
|
||||||
|
|
||||||
// Check if the node is viable for the transfer.
|
// Check if the node is viable for the transfer.
|
||||||
$node = $this->nodeRepository->getNodeWithResourceUsage($node_id);
|
$node = $this->nodeRepository->getNodeWithResourceUsage($node_id);
|
||||||
if ($node->isViable($server->memory, $server->disk)) {
|
if (!$node->isViable($server->memory, $server->disk)) {
|
||||||
// Check if the selected daemon is online.
|
$this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
|
||||||
$this->daemonConfigurationRepository->setNode($node)->getSystemInformation();
|
|
||||||
|
|
||||||
$server->validateTransferState();
|
return redirect()->route('admin.servers.view.manage', $server->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server->validateTransferState();
|
||||||
|
|
||||||
|
$this->connection->transaction(function () use ($server, $node_id, $allocation_id, $additional_allocations) {
|
||||||
// Create a new ServerTransfer entry.
|
// Create a new ServerTransfer entry.
|
||||||
$transfer = new ServerTransfer();
|
$transfer = new ServerTransfer();
|
||||||
|
|
||||||
|
@ -68,13 +74,19 @@ class ServerTransferController extends Controller
|
||||||
// Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress.
|
// Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress.
|
||||||
$this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations);
|
$this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations);
|
||||||
|
|
||||||
// Request an archive from the server's current daemon. (this also checks if the daemon is online)
|
// Generate a token for the destination node that the source node can use to authenticate with.
|
||||||
$this->transferService->requestArchive($server);
|
$token = $this->nodeJWTService
|
||||||
|
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||||
|
->setSubject($server->uuid)
|
||||||
|
->handle($transfer->newNode, $server->uuid, 'sha256');
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.transfer_started'))->flash();
|
// Notify the source node of the pending outgoing transfer.
|
||||||
} else {
|
$this->daemonTransferRepository->setServer($server)->notify($transfer->newNode, $token);
|
||||||
$this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
|
|
||||||
}
|
return $transfer;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->alert->success(trans('admin/server.alerts.transfer_started'))->flash();
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
return redirect()->route('admin.servers.view.manage', $server->id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
|
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
|
||||||
|
|
||||||
use Carbon\CarbonImmutable;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Pterodactyl\Models\Allocation;
|
use Pterodactyl\Models\Allocation;
|
||||||
|
@ -11,10 +9,8 @@ use Illuminate\Support\Facades\Log;
|
||||||
use Pterodactyl\Models\ServerTransfer;
|
use Pterodactyl\Models\ServerTransfer;
|
||||||
use Illuminate\Database\ConnectionInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
use Pterodactyl\Services\Nodes\NodeJWTService;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
|
|
||||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||||
|
|
||||||
class ServerTransferController extends Controller
|
class ServerTransferController extends Controller
|
||||||
|
@ -25,52 +21,10 @@ class ServerTransferController extends Controller
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private ConnectionInterface $connection,
|
private ConnectionInterface $connection,
|
||||||
private ServerRepository $repository,
|
private ServerRepository $repository,
|
||||||
private DaemonServerRepository $daemonServerRepository,
|
private DaemonServerRepository $daemonServerRepository
|
||||||
private DaemonTransferRepository $daemonTransferRepository,
|
|
||||||
private NodeJWTService $jwtService
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The daemon notifies us about the archive status.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function archive(Request $request, string $uuid): JsonResponse
|
|
||||||
{
|
|
||||||
$server = $this->repository->getByUuid($uuid);
|
|
||||||
|
|
||||||
// Unsuspend the server and don't continue the transfer.
|
|
||||||
if (!$request->input('successful')) {
|
|
||||||
return $this->processFailedTransfer($server->transfer);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->connection->transaction(function () use ($server) {
|
|
||||||
// This token is used by the new node the server is being transferred to. It allows
|
|
||||||
// that node to communicate with the old node during the process to initiate the
|
|
||||||
// actual file transfer.
|
|
||||||
$token = $this->jwtService
|
|
||||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
|
||||||
->setSubject($server->uuid)
|
|
||||||
->handle($server->node, $server->uuid, 'sha256');
|
|
||||||
|
|
||||||
// Update the archived field on the transfer to make clients connect to the websocket
|
|
||||||
// on the new node to be able to receive transfer logs.
|
|
||||||
$server->transfer->forceFill(['archived' => true])->saveOrFail();
|
|
||||||
|
|
||||||
// On the daemon transfer repository, make sure to set the node after the server
|
|
||||||
// because setServer() tells the repository to use the server's node and not the one
|
|
||||||
// we want to specify.
|
|
||||||
$this->daemonTransferRepository
|
|
||||||
->setServer($server)
|
|
||||||
->setNode($server->transfer->newNode)
|
|
||||||
->notify($server, $token);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new JsonResponse([], Response::HTTP_NO_CONTENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The daemon notifies us about a transfer failure.
|
* The daemon notifies us about a transfer failure.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Wings;
|
namespace Pterodactyl\Repositories\Wings;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Node;
|
||||||
use Lcobucci\JWT\Token\Plain;
|
use Lcobucci\JWT\Token\Plain;
|
||||||
use Pterodactyl\Models\Server;
|
|
||||||
use GuzzleHttp\Exception\GuzzleException;
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||||
|
|
||||||
|
@ -12,16 +12,16 @@ class DaemonTransferRepository extends DaemonRepository
|
||||||
/**
|
/**
|
||||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||||
*/
|
*/
|
||||||
public function notify(Server $server, Plain $token): void
|
public function notify(Node $targetNode, Plain $token): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->getHttpClient()->post('/api/transfer', [
|
$this->getHttpClient()->post(sprintf('/api/servers/%s/transfer', $this->server->uuid), [
|
||||||
'json' => [
|
'json' => [
|
||||||
'server_id' => $server->uuid,
|
'server_id' => $this->server->uuid,
|
||||||
'url' => $server->node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid),
|
'url' => $targetNode->getConnectionAddress() . '/api/transfers',
|
||||||
'token' => 'Bearer ' . $token->toString(),
|
'token' => 'Bearer ' . $token->toString(),
|
||||||
'server' => [
|
'server' => [
|
||||||
'uuid' => $server->uuid,
|
'uuid' => $this->server->uuid,
|
||||||
'start_on_completion' => false,
|
'start_on_completion' => false,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Services\Servers;
|
|
||||||
|
|
||||||
use Pterodactyl\Models\Server;
|
|
||||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
|
||||||
|
|
||||||
class TransferService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* TransferService constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private DaemonServerRepository $daemonServerRepository
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests an archive from the daemon.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
|
||||||
*/
|
|
||||||
public function requestArchive(Server $server): void
|
|
||||||
{
|
|
||||||
$this->daemonServerRepository->setServer($server)->requestArchive();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,19 +7,19 @@ const TransferListener = () => {
|
||||||
const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer);
|
const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer);
|
||||||
const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState);
|
const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState);
|
||||||
|
|
||||||
// Listen for the transfer status event so we can update the state of the server.
|
// Listen for the transfer status event, so we can update the state of the server.
|
||||||
useWebsocketEvent(SocketEvent.TRANSFER_STATUS, (status: string) => {
|
useWebsocketEvent(SocketEvent.TRANSFER_STATUS, (status: string) => {
|
||||||
if (status === 'starting') {
|
if (status === 'pending' || status === 'processing') {
|
||||||
setServerFromState((s) => ({ ...s, isTransferring: true }));
|
setServerFromState((s) => ({ ...s, isTransferring: true }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'failure') {
|
if (status === 'failed') {
|
||||||
setServerFromState((s) => ({ ...s, isTransferring: false }));
|
setServerFromState((s) => ({ ...s, isTransferring: false }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status !== 'success') {
|
if (status !== 'completed') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,13 +76,6 @@ export default () => {
|
||||||
case 'failure':
|
case 'failure':
|
||||||
terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m');
|
terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m');
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Sent by the source node whenever the server was archived successfully.
|
|
||||||
case 'archive':
|
|
||||||
terminal.writeln(
|
|
||||||
TERMINAL_PRELUDE +
|
|
||||||
'Server has been archived successfully, attempting connection to target node..\u001b[0m'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue