Streaming Transfers (#4548)

This commit is contained in:
Matthew Penner 2022-11-14 18:25:07 -07:00 committed by GitHub
parent 032e4f2e31
commit df2402b54f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 38 additions and 106 deletions

View file

@ -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);
} }

View file

@ -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.
* *

View file

@ -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,
], ],
], ],

View file

@ -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();
}
}

View file

@ -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;
} }

View file

@ -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'
);
} }
}; };