Skip to content

Commit cfb7415

Browse files
committed
Fix data integrity exception, closes pterodactyl#922
1 parent e1d6980 commit cfb7415

File tree

4 files changed

+116
-5
lines changed

4 files changed

+116
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
88
* `[rc.2]` — Fixes bad API behavior on `/user` routes.
99
* `[rc.2]` — Fixes Admin CP user editing resetting a password on users unintentionally.
1010
* `[rc.2]` — Fixes bug with server creation API endpoint that would fail to validate `allocation.default` correctly.
11+
* `[rc.2]` — Fix data integrity exception occuring due to invalid data being passed to server creation service on the API.
1112

1213
### Added
1314
* Added ability to search the following API endpoints: list users, list servers, and list locations.

app/Http/Requests/Api/Application/Servers/StoreServerRequest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public function rules(): array
4949
'limits.io' => $rules['io'],
5050
'limits.cpu' => $rules['cpu'],
5151

52+
// Placeholders for rules added in withValidator() function.
53+
'allocation.default' => '',
54+
'allocation.additional.*' => '',
55+
5256
// Automatic deployment rules
5357
'deploy' => 'sometimes|required|array',
5458
'deploy.locations' => 'array',

app/Services/Servers/ServerCreationService.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ public function __construct(
112112

113113
/**
114114
* Create a server on the Panel and trigger a request to the Daemon to begin the server
115-
* creation process.
115+
* creation process. This function will attempt to set as many additional values
116+
* as possible given the input data. For example, if an allocation_id is passed with
117+
* no node_id the node_is will be picked from the allocation.
116118
*
117119
* @param array $data
118120
* @param \Pterodactyl\Models\Objects\DeploymentObject|null $deployment
@@ -138,6 +140,12 @@ public function handle(array $data, DeploymentObject $deployment = null): Server
138140
$data['node_id'] = $allocation->node_id;
139141
}
140142

143+
// Auto-configure the node based on the selected allocation
144+
// if no node was defined.
145+
if (is_null(array_get($data, 'node_id'))) {
146+
$data['node_id'] = $this->getNodeFromAllocation($data['allocation_id']);
147+
}
148+
141149
if (is_null(array_get($data, 'nest_id'))) {
142150
$egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(array_get($data, 'egg_id'));
143151
$data['nest_id'] = $egg->nest_id;
@@ -263,4 +271,19 @@ private function storeEggVariables(Server $server, Collection $variables)
263271
$this->serverVariableRepository->insert($records);
264272
}
265273
}
274+
275+
/**
276+
* Get the node that an allocation belongs to.
277+
*
278+
* @param int $allocation
279+
* @return int
280+
*
281+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
282+
*/
283+
private function getNodeFromAllocation(int $allocation): int
284+
{
285+
$allocation = $this->allocationRepository->setColumns(['id', 'node_id'])->find($allocation);
286+
287+
return $allocation->node_id;
288+
}
266289
}

tests/Unit/Services/Servers/ServerCreationServiceTest.php

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
use Mockery as m;
66
use Tests\TestCase;
7-
use Pterodactyl\Models\Node;
7+
use Pterodactyl\Models\Egg;
88
use Pterodactyl\Models\User;
99
use Tests\Traits\MocksUuids;
1010
use Pterodactyl\Models\Server;
11+
use Pterodactyl\Models\Allocation;
1112
use Tests\Traits\MocksRequestException;
1213
use Illuminate\Database\ConnectionInterface;
14+
use Pterodactyl\Models\Objects\DeploymentObject;
1315
use Pterodactyl\Services\Servers\ServerCreationService;
1416
use Pterodactyl\Services\Servers\VariableValidatorService;
1517
use Pterodactyl\Services\Deployment\FindViableNodesService;
@@ -35,7 +37,7 @@ class ServerCreationServiceTest extends TestCase
3537
private $allocationRepository;
3638

3739
/**
38-
* @var \Pterodactyl\Services\Deployment\AllocationSelectionService
40+
* @var \Pterodactyl\Services\Deployment\AllocationSelectionService|\Mockery\Mock
3941
*/
4042
private $allocationSelectionService;
4143

@@ -55,12 +57,12 @@ class ServerCreationServiceTest extends TestCase
5557
private $daemonServerRepository;
5658

5759
/**
58-
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
60+
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
5961
*/
6062
private $eggRepository;
6163

6264
/**
63-
* @var \Pterodactyl\Services\Deployment\FindViableNodesService
65+
* @var \Pterodactyl\Services\Deployment\FindViableNodesService|\Mockery\Mock
6466
*/
6567
private $findViableNodesService;
6668

@@ -117,6 +119,7 @@ public function testCreateShouldHitAllOfTheNecessaryServicesAndStoreTheServer()
117119
$this->repository->shouldReceive('create')->with(m::subset([
118120
'uuid' => $this->getKnownUuid(),
119121
'node_id' => $model->node_id,
122+
'allocation_id' => $model->allocation_id,
120123
'owner_id' => $model->owner_id,
121124
'nest_id' => $model->nest_id,
122125
'egg_id' => $model->egg_id,
@@ -147,6 +150,86 @@ public function testCreateShouldHitAllOfTheNecessaryServicesAndStoreTheServer()
147150
$this->assertSame($model, $response);
148151
}
149152

153+
/**
154+
* Test that optional parameters get auto-filled correctly on the model.
155+
*/
156+
public function testDataIsAutoFilled()
157+
{
158+
$model = factory(Server::class)->make(['uuid' => $this->getKnownUuid()]);
159+
$allocationModel = factory(Allocation::class)->make(['node_id' => $model->node_id]);
160+
$eggModel = factory(Egg::class)->make(['nest_id' => $model->nest_id]);
161+
162+
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs();
163+
$this->allocationRepository->shouldReceive('setColumns->find')->once()->with($model->allocation_id)->andReturn($allocationModel);
164+
$this->eggRepository->shouldReceive('setColumns->find')->once()->with($model->egg_id)->andReturn($eggModel);
165+
166+
$this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([]));
167+
$this->repository->shouldReceive('create')->once()->with(m::subset([
168+
'uuid' => $this->getKnownUuid(),
169+
'node_id' => $model->node_id,
170+
'allocation_id' => $model->allocation_id,
171+
'nest_id' => $model->nest_id,
172+
'egg_id' => $model->egg_id,
173+
]))->andReturn($model);
174+
175+
$this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->with($model->id, [$model->allocation_id]);
176+
$this->configurationStructureService->shouldReceive('handle')->once()->with($model)->andReturn([]);
177+
178+
$this->daemonServerRepository->shouldReceive('setServer->create')->once();
179+
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
180+
181+
$this->getService()->handle(
182+
collect($model->toArray())->except(['node_id', 'nest_id'])->toArray()
183+
);
184+
}
185+
186+
/**
187+
* Test that an auto-deployment object is used correctly if passed.
188+
*/
189+
public function testAutoDeploymentObject()
190+
{
191+
$model = factory(Server::class)->make(['uuid' => $this->getKnownUuid()]);
192+
$deploymentObject = new DeploymentObject();
193+
$deploymentObject->setPorts(['25565']);
194+
$deploymentObject->setDedicated(false);
195+
$deploymentObject->setLocations([1]);
196+
197+
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs();
198+
199+
$this->findViableNodesService->shouldReceive('setLocations')->once()->with($deploymentObject->getLocations())->andReturnSelf();
200+
$this->findViableNodesService->shouldReceive('setDisk')->once()->with($model->disk)->andReturnSelf();
201+
$this->findViableNodesService->shouldReceive('setMemory')->once()->with($model->memory)->andReturnSelf();
202+
$this->findViableNodesService->shouldReceive('handle')->once()->withNoArgs()->andReturn([1, 2]);
203+
204+
$allocationModel = factory(Allocation::class)->make([
205+
'id' => $model->allocation_id,
206+
'node_id' => $model->node_id,
207+
]);
208+
$this->allocationSelectionService->shouldReceive('setDedicated')->once()->with($deploymentObject->isDedicated())->andReturnSelf();
209+
$this->allocationSelectionService->shouldReceive('setNodes')->once()->with([1, 2])->andReturnSelf();
210+
$this->allocationSelectionService->shouldReceive('setPorts')->once()->with($deploymentObject->getPorts())->andReturnSelf();
211+
$this->allocationSelectionService->shouldReceive('handle')->once()->withNoArgs()->andReturn($allocationModel);
212+
213+
$this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([]));
214+
$this->repository->shouldReceive('create')->once()->with(m::subset([
215+
'uuid' => $this->getKnownUuid(),
216+
'node_id' => $model->node_id,
217+
'allocation_id' => $model->allocation_id,
218+
'nest_id' => $model->nest_id,
219+
'egg_id' => $model->egg_id,
220+
]))->andReturn($model);
221+
222+
$this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->with($model->id, [$model->allocation_id]);
223+
$this->configurationStructureService->shouldReceive('handle')->once()->with($model)->andReturn([]);
224+
225+
$this->daemonServerRepository->shouldReceive('setServer->create')->once();
226+
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
227+
228+
$this->getService()->handle(
229+
collect($model->toArray())->except(['allocation_id', 'node_id'])->toArray(), $deploymentObject
230+
);
231+
}
232+
150233
/**
151234
* Test handling of node timeout or other daemon error.
152235
*

0 commit comments

Comments
 (0)