Streaming Transfers (#4548)
This commit is contained in:
		
							parent
							
								
									032e4f2e31
								
							
						
					
					
						commit
						df2402b54f
					
				@ -2,15 +2,17 @@
 | 
			
		||||
 | 
			
		||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
 | 
			
		||||
 | 
			
		||||
use Carbon\CarbonImmutable;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Pterodactyl\Models\Server;
 | 
			
		||||
use Illuminate\Http\RedirectResponse;
 | 
			
		||||
use Prologue\Alerts\AlertsMessageBag;
 | 
			
		||||
use Pterodactyl\Models\ServerTransfer;
 | 
			
		||||
use Illuminate\Database\ConnectionInterface;
 | 
			
		||||
use Pterodactyl\Http\Controllers\Controller;
 | 
			
		||||
use Pterodactyl\Services\Servers\TransferService;
 | 
			
		||||
use Pterodactyl\Services\Nodes\NodeJWTService;
 | 
			
		||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
 | 
			
		||||
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
 | 
			
		||||
use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
 | 
			
		||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
 | 
			
		||||
 | 
			
		||||
class ServerTransferController extends Controller
 | 
			
		||||
@ -21,9 +23,10 @@ class ServerTransferController extends Controller
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private AlertsMessageBag $alert,
 | 
			
		||||
        private AllocationRepositoryInterface $allocationRepository,
 | 
			
		||||
        private NodeRepository $nodeRepository,
 | 
			
		||||
        private TransferService $transferService,
 | 
			
		||||
        private DaemonConfigurationRepository $daemonConfigurationRepository
 | 
			
		||||
        private ConnectionInterface $connection,
 | 
			
		||||
        private DaemonTransferRepository $daemonTransferRepository,
 | 
			
		||||
        private NodeJWTService $nodeJWTService,
 | 
			
		||||
        private NodeRepository $nodeRepository
 | 
			
		||||
    ) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -46,12 +49,15 @@ class ServerTransferController extends Controller
 | 
			
		||||
 | 
			
		||||
        // Check if the node is viable for the transfer.
 | 
			
		||||
        $node = $this->nodeRepository->getNodeWithResourceUsage($node_id);
 | 
			
		||||
        if ($node->isViable($server->memory, $server->disk)) {
 | 
			
		||||
            // Check if the selected daemon is online.
 | 
			
		||||
            $this->daemonConfigurationRepository->setNode($node)->getSystemInformation();
 | 
			
		||||
        if (!$node->isViable($server->memory, $server->disk)) {
 | 
			
		||||
            $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
 | 
			
		||||
 | 
			
		||||
            $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.
 | 
			
		||||
            $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.
 | 
			
		||||
            $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)
 | 
			
		||||
            $this->transferService->requestArchive($server);
 | 
			
		||||
            // Generate a token for the destination node that the source node can use to authenticate with.
 | 
			
		||||
            $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();
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
 | 
			
		||||
        }
 | 
			
		||||
            // Notify the source node of the pending outgoing transfer.
 | 
			
		||||
            $this->daemonTransferRepository->setServer($server)->notify($transfer->newNode, $token);
 | 
			
		||||
 | 
			
		||||
            return $transfer;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash();
 | 
			
		||||
 | 
			
		||||
        return redirect()->route('admin.servers.view.manage', $server->id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,6 @@
 | 
			
		||||
 | 
			
		||||
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
 | 
			
		||||
 | 
			
		||||
use Carbon\CarbonImmutable;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Pterodactyl\Models\Allocation;
 | 
			
		||||
@ -11,10 +9,8 @@ use Illuminate\Support\Facades\Log;
 | 
			
		||||
use Pterodactyl\Models\ServerTransfer;
 | 
			
		||||
use Illuminate\Database\ConnectionInterface;
 | 
			
		||||
use Pterodactyl\Http\Controllers\Controller;
 | 
			
		||||
use Pterodactyl\Services\Nodes\NodeJWTService;
 | 
			
		||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
			
		||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
 | 
			
		||||
use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
 | 
			
		||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
 | 
			
		||||
 | 
			
		||||
class ServerTransferController extends Controller
 | 
			
		||||
@ -25,52 +21,10 @@ class ServerTransferController extends Controller
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private ConnectionInterface $connection,
 | 
			
		||||
        private ServerRepository $repository,
 | 
			
		||||
        private DaemonServerRepository $daemonServerRepository,
 | 
			
		||||
        private DaemonTransferRepository $daemonTransferRepository,
 | 
			
		||||
        private NodeJWTService $jwtService
 | 
			
		||||
        private DaemonServerRepository $daemonServerRepository
 | 
			
		||||
    ) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,8 @@
 | 
			
		||||
 | 
			
		||||
namespace Pterodactyl\Repositories\Wings;
 | 
			
		||||
 | 
			
		||||
use Pterodactyl\Models\Node;
 | 
			
		||||
use Lcobucci\JWT\Token\Plain;
 | 
			
		||||
use Pterodactyl\Models\Server;
 | 
			
		||||
use GuzzleHttp\Exception\GuzzleException;
 | 
			
		||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
 | 
			
		||||
 | 
			
		||||
@ -12,16 +12,16 @@ class DaemonTransferRepository extends DaemonRepository
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
			
		||||
     */
 | 
			
		||||
    public function notify(Server $server, Plain $token): void
 | 
			
		||||
    public function notify(Node $targetNode, Plain $token): void
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->getHttpClient()->post('/api/transfer', [
 | 
			
		||||
            $this->getHttpClient()->post(sprintf('/api/servers/%s/transfer', $this->server->uuid), [
 | 
			
		||||
                'json' => [
 | 
			
		||||
                    'server_id' => $server->uuid,
 | 
			
		||||
                    'url' => $server->node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid),
 | 
			
		||||
                    'server_id' => $this->server->uuid,
 | 
			
		||||
                    'url' => $targetNode->getConnectionAddress() . '/api/transfers',
 | 
			
		||||
                    'token' => 'Bearer ' . $token->toString(),
 | 
			
		||||
                    'server' => [
 | 
			
		||||
                        'uuid' => $server->uuid,
 | 
			
		||||
                        'uuid' => $this->server->uuid,
 | 
			
		||||
                        '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 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) => {
 | 
			
		||||
        if (status === 'starting') {
 | 
			
		||||
        if (status === 'pending' || status === 'processing') {
 | 
			
		||||
            setServerFromState((s) => ({ ...s, isTransferring: true }));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (status === 'failure') {
 | 
			
		||||
        if (status === 'failed') {
 | 
			
		||||
            setServerFromState((s) => ({ ...s, isTransferring: false }));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (status !== 'success') {
 | 
			
		||||
        if (status !== 'completed') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,13 +76,6 @@ export default () => {
 | 
			
		||||
            case 'failure':
 | 
			
		||||
                terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m');
 | 
			
		||||
                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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user