diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index 0f0fc309..f805070d 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -55,7 +55,7 @@ class ServerTransferController extends Controller * ServerTransferController constructor. * * @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\LocationRepository $locationRepository * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository @@ -104,9 +104,11 @@ 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)) { - //$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->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->new_additional_allocations = json_encode($additional_allocations); - $transfer->save();*/ + $transfer->save(); - // Suspend the server and request an archive to be created. - // $this->suspensionService->toggle($server, 'suspend'); + // 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->transferService->requestArchive($server); $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); } + /** + * 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) { $allocations = $additional_allocations; diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index cd8ceb85..ddaa0849 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; use Cake\Chronos\Chronos; +use Illuminate\Database\ConnectionInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -10,19 +11,32 @@ use Illuminate\Support\Facades\Log; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Key; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Models\Server; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Wings\DaemonTransferRepository; +use Pterodactyl\Services\Servers\ServerConfigurationStructureService; +use Pterodactyl\Services\Servers\SuspensionService; class ServerTransferController extends Controller { + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; + /** * @var \Pterodactyl\Repositories\Eloquent\ServerRepository */ private $repository; + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + private $allocationRepository; + /** * @var \Pterodactyl\Repositories\Eloquent\NodeRepository */ @@ -33,21 +47,43 @@ class ServerTransferController extends Controller */ private $daemonTransferRepository; + /** + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + private $configurationStructureService; + + /** + * @var \Pterodactyl\Services\Servers\SuspensionService + */ + private $suspensionService; + /** * ServerTransferController constructor. * + * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository * @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( + ConnectionInterface $connection, ServerRepository $repository, + AllocationRepositoryInterface $allocationRepository, NodeRepository $nodeRepository, - DaemonTransferRepository $daemonTransferRepository + DaemonTransferRepository $daemonTransferRepository, + ServerConfigurationStructureService $configurationStructureService, + SuspensionService $suspensionService ) { + $this->connection = $connection; $this->repository = $repository; + $this->allocationRepository = $allocationRepository; $this->nodeRepository = $nodeRepository; $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\Repository\RecordNotFoundException + * @throws \Throwable */ public function archive(Request $request, string $uuid) { @@ -66,10 +103,20 @@ class ServerTransferController extends Controller // Unsuspend the server and don't continue the transfer. if (!$request->input('successful')) { - // $this->suspensionService->toggle($server, 'unsuspend'); + $this->suspensionService->toggle($server, 'unsuspend'); 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(); $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 // because setServer() tells the repository to use the server's node and not the one // we want to specify. - $this->daemonTransferRepository - ->setServer($server) - ->setNode($this->nodeRepository->find($server->transfer->new_node)) - ->notify($server, $server->node, $token->__toString()); + try { + $this->daemonTransferRepository + ->setServer($server) + ->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); } diff --git a/app/Models/Server.php b/app/Models/Server.php index 12873fcd..936e6946 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -184,7 +184,7 @@ class Server extends Validable */ 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'); })->toArray(); } diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index 15000043..94072bf0 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -11,6 +11,7 @@ namespace Pterodactyl\Models; * @property int $new_allocation * @property string $old_additional_allocations * @property string $new_additional_allocations + * @property bool $successful * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @@ -51,6 +52,7 @@ class ServerTransfer extends Validable 'new_allocation' => 'int', 'old_additional_allocations' => 'string', 'new_additional_allocations' => 'string', + 'successful' => 'bool', ]; /** @@ -64,10 +66,11 @@ class ServerTransfer extends Validable 'new_allocation' => 'required|numeric', 'old_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 */ diff --git a/app/Repositories/Wings/DaemonTransferRepository.php b/app/Repositories/Wings/DaemonTransferRepository.php index c6331283..9c47478d 100644 --- a/app/Repositories/Wings/DaemonTransferRepository.php +++ b/app/Repositories/Wings/DaemonTransferRepository.php @@ -11,18 +11,20 @@ class DaemonTransferRepository extends DaemonRepository { /** * @param Server $server + * @param array $data * @param Node $node * @param string $token * * @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 { $this->getHttpClient()->post('/api/transfer', [ 'json' => [ 'server_id' => $server->uuid, 'url' => $node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid), 'token' => 'Bearer ' . $token, + 'server' => $data, ], ]); } catch(TransferException $exception) { diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index 9fb95645..b2d4cb4e 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Services\Servers; +use Illuminate\Support\Facades\Log; use Psr\Log\LoggerInterface; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; @@ -73,10 +74,14 @@ class SuspensionService return; } + Log::debug('SuspensionService: ' . $action); + $this->connection->transaction(function () use ($action, $server) { $this->repository->withoutFreshModel()->update($server->id, [ '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); }); diff --git a/database/migrations/2020_04_04_172331_add_successful_column_to_server_transfers.php b/database/migrations/2020_04_04_172331_add_successful_column_to_server_transfers.php new file mode 100644 index 00000000..e65ad7aa --- /dev/null +++ b/database/migrations/2020_04_04_172331_add_successful_column_to_server_transfers.php @@ -0,0 +1,32 @@ +tinyInteger('successful')->unsigned()->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('server_transfers', function (Blueprint $table) { + $table->dropColumn('successful'); + }); + } +} diff --git a/routes/api-remote.php b/routes/api-remote.php index 6fff25c6..b214ccb4 100644 --- a/routes/api-remote.php +++ b/routes/api-remote.php @@ -12,6 +12,8 @@ Route::group(['prefix' => '/servers/{uuid}'], function () { Route::get('/install', 'Servers\ServerInstallController@index'); Route::post('/install', 'Servers\ServerInstallController@store'); Route::post('/archive', 'Servers\ServerTransferController@archive'); + Route::get('/transfer/failure', 'Servers\ServerTransferController@failure'); + Route::get('/transfer/success', 'Servers\ServerTransferController@success'); });