Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php
14056 views
1
<?php
2
3
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
4
5
use Illuminate\Http\Request;
6
use Pterodactyl\Models\Node;
7
use Webmozart\Assert\Assert;
8
use Illuminate\Http\Response;
9
use Illuminate\Http\JsonResponse;
10
use Pterodactyl\Models\Allocation;
11
use Illuminate\Support\Facades\Log;
12
use Pterodactyl\Models\ServerTransfer;
13
use Illuminate\Database\ConnectionInterface;
14
use Pterodactyl\Http\Controllers\Controller;
15
use Pterodactyl\Exceptions\Http\HttpForbiddenException;
16
use Pterodactyl\Repositories\Eloquent\ServerRepository;
17
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
18
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
19
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
20
21
class ServerTransferController extends Controller
22
{
23
/**
24
* ServerTransferController constructor.
25
*/
26
public function __construct(
27
private ConnectionInterface $connection,
28
private ServerRepository $repository,
29
private DaemonServerRepository $daemonServerRepository,
30
) {
31
}
32
33
/**
34
* The daemon notifies us about a transfer failure.
35
*
36
* @throws \Throwable
37
*/
38
public function failure(Request $request, string $uuid): JsonResponse
39
{
40
$server = $this->repository->getByUuid($uuid);
41
$transfer = $server->transfer;
42
if (is_null($transfer)) {
43
throw new ConflictHttpException('Server is not being transferred.');
44
}
45
46
/* @var Node $node */
47
Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class);
48
49
// Either node can tell the panel that the transfer has failed. Only the new node
50
// can tell the panel that it was successful.
51
if (! $node->is($transfer->newNode) && ! $node->is($transfer->oldNode)) {
52
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
53
}
54
55
return $this->processFailedTransfer($transfer);
56
}
57
58
/**
59
* The daemon notifies us about a transfer success.
60
*
61
* @throws \Throwable
62
*/
63
public function success(Request $request, string $uuid): JsonResponse
64
{
65
$server = $this->repository->getByUuid($uuid);
66
$transfer = $server->transfer;
67
if (is_null($transfer)) {
68
throw new ConflictHttpException('Server is not being transferred.');
69
}
70
71
/* @var Node $node */
72
Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class);
73
74
// Only the new node communicates a successful state to the panel, so we should
75
// not allow the old node to hit this endpoint.
76
if (! $node->is($transfer->newNode)) {
77
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
78
}
79
80
/** @var \Pterodactyl\Models\Server $server */
81
$server = $this->connection->transaction(function () use ($server, $transfer) {
82
$allocations = array_merge([$transfer->old_allocation], $transfer->old_additional_allocations);
83
84
// Remove the old allocations for the server and re-assign the server to the new
85
// primary allocation and node.
86
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
87
$server->update([
88
'allocation_id' => $transfer->new_allocation,
89
'node_id' => $transfer->new_node,
90
]);
91
92
$server = $server->fresh();
93
$server->transfer->update(['successful' => true]);
94
95
return $server;
96
});
97
98
// Delete the server from the old node making sure to point it to the old node so
99
// that we do not delete it from the new node the server was transferred to.
100
try {
101
$this->daemonServerRepository
102
->setServer($server)
103
->setNode($transfer->oldNode)
104
->delete();
105
} catch (DaemonConnectionException $exception) {
106
Log::warning($exception, ['transfer_id' => $server->transfer->id]);
107
}
108
109
return new JsonResponse([], Response::HTTP_NO_CONTENT);
110
}
111
112
/**
113
* Release all the reserved allocations for this transfer and mark it as failed in
114
* the database.
115
*
116
* @throws \Throwable
117
*/
118
protected function processFailedTransfer(ServerTransfer $transfer): JsonResponse
119
{
120
$this->connection->transaction(function () use (&$transfer) {
121
$transfer->forceFill(['successful' => false])->saveOrFail();
122
123
$allocations = array_merge([$transfer->new_allocation], $transfer->new_additional_allocations);
124
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
125
});
126
127
return new JsonResponse([], Response::HTTP_NO_CONTENT);
128
}
129
}
130
131