Add successful column to server_transfers table, get server transfers working properly :)

This commit is contained in:
Matthew Penner 2020-04-04 16:16:18 -06:00
parent 6ba6c34252
commit 454ce6ce45
8 changed files with 202 additions and 17 deletions

View File

@ -55,7 +55,7 @@ class ServerTransferController extends Controller
* ServerTransferController constructor. * ServerTransferController constructor.
* *
* @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository, * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
* @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository * @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository
@ -104,9 +104,11 @@ 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)) {
//$this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); // Suspend the server and request an archive to be created.
$this->suspensionService->toggle($server, 'suspend');
/*$transfer = new ServerTransfer; // Create a new ServerTransfer entry.
$transfer = new ServerTransfer;
$transfer->server_id = $server->id; $transfer->server_id = $server->id;
$transfer->old_node = $server->node_id; $transfer->old_node = $server->node_id;
@ -116,10 +118,12 @@ class ServerTransferController extends Controller
$transfer->old_additional_allocations = json_encode($server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')); $transfer->old_additional_allocations = json_encode($server->allocations->where('id', '!=', $server->allocation_id)->pluck('id'));
$transfer->new_additional_allocations = json_encode($additional_allocations); $transfer->new_additional_allocations = json_encode($additional_allocations);
$transfer->save();*/ $transfer->save();
// Suspend the server and request an archive to be created. // Add the allocations to the server so they cannot be automatically assigned while the transfer is in progress.
// $this->suspensionService->toggle($server, 'suspend'); $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations);
// Request an archive from the server's current daemon.
$this->transferService->requestArchive($server); $this->transferService->requestArchive($server);
$this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash();
@ -130,6 +134,14 @@ class ServerTransferController extends Controller
return redirect()->route('admin.servers.view.manage', $server->id); return redirect()->route('admin.servers.view.manage', $server->id);
} }
/**
* Assigns the specified allocations to the specified server.
*
* @param Server $server
* @param int $node_id
* @param int $allocation_id
* @param array $additional_allocations
*/
private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations) private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations)
{ {
$allocations = $additional_allocations; $allocations = $additional_allocations;

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
use Cake\Chronos\Chronos; use Cake\Chronos\Chronos;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
@ -10,19 +11,32 @@ use Illuminate\Support\Facades\Log;
use Lcobucci\JWT\Builder; use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Key;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Models\Server;
use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
use Pterodactyl\Services\Servers\SuspensionService;
class ServerTransferController extends Controller class ServerTransferController extends Controller
{ {
/**
* @var \Illuminate\Database\ConnectionInterface
*/
private $connection;
/** /**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository * @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/ */
private $repository; private $repository;
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
*/
private $allocationRepository;
/** /**
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository * @var \Pterodactyl\Repositories\Eloquent\NodeRepository
*/ */
@ -33,21 +47,43 @@ class ServerTransferController extends Controller
*/ */
private $daemonTransferRepository; private $daemonTransferRepository;
/**
* @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
*/
private $configurationStructureService;
/**
* @var \Pterodactyl\Services\Servers\SuspensionService
*/
private $suspensionService;
/** /**
* ServerTransferController constructor. * ServerTransferController constructor.
* *
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository
* @param DaemonTransferRepository $daemonTransferRepository * @param \Pterodactyl\Repositories\Wings\DaemonTransferRepository $daemonTransferRepository
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService
* @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService
*/ */
public function __construct( public function __construct(
ConnectionInterface $connection,
ServerRepository $repository, ServerRepository $repository,
AllocationRepositoryInterface $allocationRepository,
NodeRepository $nodeRepository, NodeRepository $nodeRepository,
DaemonTransferRepository $daemonTransferRepository DaemonTransferRepository $daemonTransferRepository,
ServerConfigurationStructureService $configurationStructureService,
SuspensionService $suspensionService
) { ) {
$this->connection = $connection;
$this->repository = $repository; $this->repository = $repository;
$this->allocationRepository = $allocationRepository;
$this->nodeRepository = $nodeRepository; $this->nodeRepository = $nodeRepository;
$this->daemonTransferRepository = $daemonTransferRepository; $this->daemonTransferRepository = $daemonTransferRepository;
$this->configurationStructureService = $configurationStructureService;
$this->suspensionService = $suspensionService;
} }
/** /**
@ -59,6 +95,7 @@ class ServerTransferController extends Controller
* *
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/ */
public function archive(Request $request, string $uuid) public function archive(Request $request, string $uuid)
{ {
@ -66,10 +103,20 @@ class ServerTransferController extends Controller
// Unsuspend the server and don't continue the transfer. // Unsuspend the server and don't continue the transfer.
if (!$request->input('successful')) { if (!$request->input('successful')) {
// $this->suspensionService->toggle($server, 'unsuspend'); $this->suspensionService->toggle($server, 'unsuspend');
return JsonResponse::create([], Response::HTTP_NO_CONTENT); return JsonResponse::create([], Response::HTTP_NO_CONTENT);
} }
$server->node_id = $server->transfer->new_node;
$data = $this->configurationStructureService->handle($server);
$data['suspended'] = false;
$data['service']['skip_scripts'] = true;
$allocations = $server->getAllocationMappings();
$data['allocations']['default']['ip'] = array_key_first($allocations);
$data['allocations']['default']['port'] = $allocations[$data['allocations']['default']['ip']][0];
$now = Chronos::now(); $now = Chronos::now();
$signer = new Sha256; $signer = new Sha256;
@ -85,10 +132,92 @@ class ServerTransferController extends Controller
// On the daemon transfer repository, make sure to set the node after the server // 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 // because setServer() tells the repository to use the server's node and not the one
// we want to specify. // we want to specify.
$this->daemonTransferRepository try {
->setServer($server) $this->daemonTransferRepository
->setNode($this->nodeRepository->find($server->transfer->new_node)) ->setServer($server)
->notify($server, $server->node, $token->__toString()); ->setNode($this->nodeRepository->find($server->transfer->new_node))
->notify($server, $data, $server->node, $token->__toString());
} catch (DaemonConnectionException $exception) {
throw $exception;
}
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
}
/**
* The daemon notifies us about a transfer failure.
*
* @param \Illuminate\Http\Request $request
* @param string $uuid
* @return \Illuminate\Http\JsonResponse
*
* @throws \Throwable
*/
public function failure(string $uuid)
{
$server = $this->repository->getByUuid($uuid);
$transfer = $server->transfer;
$allocationIds = json_decode($transfer->new_additional_allocations);
array_push($allocationIds, $transfer->new_allocation);
// Begin a transaction.
$this->connection->beginTransaction();
// Remove the new allocations.
$this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]);
// Commit the transaction.
$this->connection->commit();
// Unsuspend the server.
$this->suspensionService->toggle($server, 'unsuspend');
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
}
/**
* The daemon notifies us about a transfer success.
*
* @param string $uuid
* @return \Illuminate\Http\JsonResponse
*
* @throws \Throwable
*/
public function success(string $uuid)
{
$server = $this->repository->getByUuid($uuid);
$transfer = $server->transfer;
// TODO: Notify old daemon about transfer and get it to remove server files.
$allocationIds = json_decode($transfer->old_additional_allocations);
array_push($allocationIds, $transfer->old_allocation);
// Begin a transaction.
$this->connection->beginTransaction();
// Remove the old allocations.
$this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]);
// Update the server's allocation_id and node_id.
$server->allocation_id = $transfer->new_allocation;
$server->node_id = $transfer->new_node;
$server->save();
// Mark the transfer as successful.
$transfer->successful = true;
$transfer->save();
// Commit the transaction.
$this->connection->commit();
// Unsuspend the server
$server->load('node');
Log::debug(json_encode($server));
Log::debug(json_encode($server->node_id));
Log::debug(json_encode($server->node));
$this->suspensionService->toggle($server, $this->suspensionService::ACTION_UNSUSPEND);
return JsonResponse::create([], Response::HTTP_NO_CONTENT); return JsonResponse::create([], Response::HTTP_NO_CONTENT);
} }

View File

@ -184,7 +184,7 @@ class Server extends Validable
*/ */
public function getAllocationMappings(): array public function getAllocationMappings(): array
{ {
return $this->allocations->groupBy('ip')->map(function ($item) { return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) {
return $item->pluck('port'); return $item->pluck('port');
})->toArray(); })->toArray();
} }

View File

@ -11,6 +11,7 @@ namespace Pterodactyl\Models;
* @property int $new_allocation * @property int $new_allocation
* @property string $old_additional_allocations * @property string $old_additional_allocations
* @property string $new_additional_allocations * @property string $new_additional_allocations
* @property bool $successful
* @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at * @property \Carbon\Carbon $updated_at
* *
@ -51,6 +52,7 @@ class ServerTransfer extends Validable
'new_allocation' => 'int', 'new_allocation' => 'int',
'old_additional_allocations' => 'string', 'old_additional_allocations' => 'string',
'new_additional_allocations' => 'string', 'new_additional_allocations' => 'string',
'successful' => 'bool',
]; ];
/** /**
@ -64,10 +66,11 @@ class ServerTransfer extends Validable
'new_allocation' => 'required|numeric', 'new_allocation' => 'required|numeric',
'old_additional_allocations' => 'nullable', 'old_additional_allocations' => 'nullable',
'new_additional_allocations' => 'nullable', 'new_additional_allocations' => 'nullable',
'successful' => 'sometimes|boolean',
]; ];
/** /**
* Gets the server associated with a subuser. * Gets the server associated with a server transfer.
* *
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */

View File

@ -11,18 +11,20 @@ class DaemonTransferRepository extends DaemonRepository
{ {
/** /**
* @param Server $server * @param Server $server
* @param array $data
* @param Node $node * @param Node $node
* @param string $token * @param string $token
* *
* @throws DaemonConnectionException * @throws DaemonConnectionException
*/ */
public function notify(Server $server, Node $node, string $token): void { public function notify(Server $server, array $data, Node $node, string $token): void {
try { try {
$this->getHttpClient()->post('/api/transfer', [ $this->getHttpClient()->post('/api/transfer', [
'json' => [ 'json' => [
'server_id' => $server->uuid, 'server_id' => $server->uuid,
'url' => $node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid), 'url' => $node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid),
'token' => 'Bearer ' . $token, 'token' => 'Bearer ' . $token,
'server' => $data,
], ],
]); ]);
} catch(TransferException $exception) { } catch(TransferException $exception) {

View File

@ -2,6 +2,7 @@
namespace Pterodactyl\Services\Servers; namespace Pterodactyl\Services\Servers;
use Illuminate\Support\Facades\Log;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
@ -73,10 +74,14 @@ class SuspensionService
return; return;
} }
Log::debug('SuspensionService: ' . $action);
$this->connection->transaction(function () use ($action, $server) { $this->connection->transaction(function () use ($action, $server) {
$this->repository->withoutFreshModel()->update($server->id, [ $this->repository->withoutFreshModel()->update($server->id, [
'suspended' => $action === self::ACTION_SUSPEND, 'suspended' => $action === self::ACTION_SUSPEND,
]); ]);
Log::debug('Server suspended: ' . ($action === self::ACTION_SUSPEND) ? 'true' : 'false');
Log::debug('Daemon unsuspended: ' . ($action === self::ACTION_UNSUSPEND) ? 'true' : 'false');
$this->daemonServerRepository->setServer($server)->suspend($action === self::ACTION_UNSUSPEND); $this->daemonServerRepository->setServer($server)->suspend($action === self::ACTION_UNSUSPEND);
}); });

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddSuccessfulColumnToServerTransfers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('server_transfers', function (Blueprint $table) {
$table->tinyInteger('successful')->unsigned()->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('server_transfers', function (Blueprint $table) {
$table->dropColumn('successful');
});
}
}

View File

@ -12,6 +12,8 @@ Route::group(['prefix' => '/servers/{uuid}'], function () {
Route::get('/install', 'Servers\ServerInstallController@index'); Route::get('/install', 'Servers\ServerInstallController@index');
Route::post('/install', 'Servers\ServerInstallController@store'); Route::post('/install', 'Servers\ServerInstallController@store');
Route::post('/archive', 'Servers\ServerTransferController@archive'); Route::post('/archive', 'Servers\ServerTransferController@archive');
Route::get('/transfer/failure', 'Servers\ServerTransferController@failure');
Route::get('/transfer/success', 'Servers\ServerTransferController@success');
}); });