Fix data integrity exception, closes #922

This commit is contained in:
Dane Everitt 2018-02-10 14:01:49 -06:00
parent e1d6980c0b
commit cfb7415e2a
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
4 changed files with 116 additions and 5 deletions

View file

@ -8,6 +8,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
* `[rc.2]` — Fixes bad API behavior on `/user` routes. * `[rc.2]` — Fixes bad API behavior on `/user` routes.
* `[rc.2]` — Fixes Admin CP user editing resetting a password on users unintentionally. * `[rc.2]` — Fixes Admin CP user editing resetting a password on users unintentionally.
* `[rc.2]` — Fixes bug with server creation API endpoint that would fail to validate `allocation.default` correctly. * `[rc.2]` — Fixes bug with server creation API endpoint that would fail to validate `allocation.default` correctly.
* `[rc.2]` — Fix data integrity exception occuring due to invalid data being passed to server creation service on the API.
### Added ### Added
* Added ability to search the following API endpoints: list users, list servers, and list locations. * Added ability to search the following API endpoints: list users, list servers, and list locations.

View file

@ -49,6 +49,10 @@ class StoreServerRequest extends ApplicationApiRequest
'limits.io' => $rules['io'], 'limits.io' => $rules['io'],
'limits.cpu' => $rules['cpu'], 'limits.cpu' => $rules['cpu'],
// Placeholders for rules added in withValidator() function.
'allocation.default' => '',
'allocation.additional.*' => '',
// Automatic deployment rules // Automatic deployment rules
'deploy' => 'sometimes|required|array', 'deploy' => 'sometimes|required|array',
'deploy.locations' => 'array', 'deploy.locations' => 'array',

View file

@ -112,7 +112,9 @@ class ServerCreationService
/** /**
* Create a server on the Panel and trigger a request to the Daemon to begin the server * Create a server on the Panel and trigger a request to the Daemon to begin the server
* creation process. * creation process. This function will attempt to set as many additional values
* as possible given the input data. For example, if an allocation_id is passed with
* no node_id the node_is will be picked from the allocation.
* *
* @param array $data * @param array $data
* @param \Pterodactyl\Models\Objects\DeploymentObject|null $deployment * @param \Pterodactyl\Models\Objects\DeploymentObject|null $deployment
@ -138,6 +140,12 @@ class ServerCreationService
$data['node_id'] = $allocation->node_id; $data['node_id'] = $allocation->node_id;
} }
// Auto-configure the node based on the selected allocation
// if no node was defined.
if (is_null(array_get($data, 'node_id'))) {
$data['node_id'] = $this->getNodeFromAllocation($data['allocation_id']);
}
if (is_null(array_get($data, 'nest_id'))) { if (is_null(array_get($data, 'nest_id'))) {
$egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(array_get($data, 'egg_id')); $egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(array_get($data, 'egg_id'));
$data['nest_id'] = $egg->nest_id; $data['nest_id'] = $egg->nest_id;
@ -263,4 +271,19 @@ class ServerCreationService
$this->serverVariableRepository->insert($records); $this->serverVariableRepository->insert($records);
} }
} }
/**
* Get the node that an allocation belongs to.
*
* @param int $allocation
* @return int
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
private function getNodeFromAllocation(int $allocation): int
{
$allocation = $this->allocationRepository->setColumns(['id', 'node_id'])->find($allocation);
return $allocation->node_id;
}
} }

View file

@ -4,12 +4,14 @@ namespace Tests\Unit\Services\Servers;
use Mockery as m; use Mockery as m;
use Tests\TestCase; use Tests\TestCase;
use Pterodactyl\Models\Node; use Pterodactyl\Models\Egg;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Tests\Traits\MocksUuids; use Tests\Traits\MocksUuids;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\Allocation;
use Tests\Traits\MocksRequestException; use Tests\Traits\MocksRequestException;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Models\Objects\DeploymentObject;
use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerCreationService;
use Pterodactyl\Services\Servers\VariableValidatorService; use Pterodactyl\Services\Servers\VariableValidatorService;
use Pterodactyl\Services\Deployment\FindViableNodesService; use Pterodactyl\Services\Deployment\FindViableNodesService;
@ -35,7 +37,7 @@ class ServerCreationServiceTest extends TestCase
private $allocationRepository; private $allocationRepository;
/** /**
* @var \Pterodactyl\Services\Deployment\AllocationSelectionService * @var \Pterodactyl\Services\Deployment\AllocationSelectionService|\Mockery\Mock
*/ */
private $allocationSelectionService; private $allocationSelectionService;
@ -55,12 +57,12 @@ class ServerCreationServiceTest extends TestCase
private $daemonServerRepository; private $daemonServerRepository;
/** /**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/ */
private $eggRepository; private $eggRepository;
/** /**
* @var \Pterodactyl\Services\Deployment\FindViableNodesService * @var \Pterodactyl\Services\Deployment\FindViableNodesService|\Mockery\Mock
*/ */
private $findViableNodesService; private $findViableNodesService;
@ -117,6 +119,7 @@ class ServerCreationServiceTest extends TestCase
$this->repository->shouldReceive('create')->with(m::subset([ $this->repository->shouldReceive('create')->with(m::subset([
'uuid' => $this->getKnownUuid(), 'uuid' => $this->getKnownUuid(),
'node_id' => $model->node_id, 'node_id' => $model->node_id,
'allocation_id' => $model->allocation_id,
'owner_id' => $model->owner_id, 'owner_id' => $model->owner_id,
'nest_id' => $model->nest_id, 'nest_id' => $model->nest_id,
'egg_id' => $model->egg_id, 'egg_id' => $model->egg_id,
@ -147,6 +150,86 @@ class ServerCreationServiceTest extends TestCase
$this->assertSame($model, $response); $this->assertSame($model, $response);
} }
/**
* Test that optional parameters get auto-filled correctly on the model.
*/
public function testDataIsAutoFilled()
{
$model = factory(Server::class)->make(['uuid' => $this->getKnownUuid()]);
$allocationModel = factory(Allocation::class)->make(['node_id' => $model->node_id]);
$eggModel = factory(Egg::class)->make(['nest_id' => $model->nest_id]);
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs();
$this->allocationRepository->shouldReceive('setColumns->find')->once()->with($model->allocation_id)->andReturn($allocationModel);
$this->eggRepository->shouldReceive('setColumns->find')->once()->with($model->egg_id)->andReturn($eggModel);
$this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([]));
$this->repository->shouldReceive('create')->once()->with(m::subset([
'uuid' => $this->getKnownUuid(),
'node_id' => $model->node_id,
'allocation_id' => $model->allocation_id,
'nest_id' => $model->nest_id,
'egg_id' => $model->egg_id,
]))->andReturn($model);
$this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->with($model->id, [$model->allocation_id]);
$this->configurationStructureService->shouldReceive('handle')->once()->with($model)->andReturn([]);
$this->daemonServerRepository->shouldReceive('setServer->create')->once();
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle(
collect($model->toArray())->except(['node_id', 'nest_id'])->toArray()
);
}
/**
* Test that an auto-deployment object is used correctly if passed.
*/
public function testAutoDeploymentObject()
{
$model = factory(Server::class)->make(['uuid' => $this->getKnownUuid()]);
$deploymentObject = new DeploymentObject();
$deploymentObject->setPorts(['25565']);
$deploymentObject->setDedicated(false);
$deploymentObject->setLocations([1]);
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs();
$this->findViableNodesService->shouldReceive('setLocations')->once()->with($deploymentObject->getLocations())->andReturnSelf();
$this->findViableNodesService->shouldReceive('setDisk')->once()->with($model->disk)->andReturnSelf();
$this->findViableNodesService->shouldReceive('setMemory')->once()->with($model->memory)->andReturnSelf();
$this->findViableNodesService->shouldReceive('handle')->once()->withNoArgs()->andReturn([1, 2]);
$allocationModel = factory(Allocation::class)->make([
'id' => $model->allocation_id,
'node_id' => $model->node_id,
]);
$this->allocationSelectionService->shouldReceive('setDedicated')->once()->with($deploymentObject->isDedicated())->andReturnSelf();
$this->allocationSelectionService->shouldReceive('setNodes')->once()->with([1, 2])->andReturnSelf();
$this->allocationSelectionService->shouldReceive('setPorts')->once()->with($deploymentObject->getPorts())->andReturnSelf();
$this->allocationSelectionService->shouldReceive('handle')->once()->withNoArgs()->andReturn($allocationModel);
$this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([]));
$this->repository->shouldReceive('create')->once()->with(m::subset([
'uuid' => $this->getKnownUuid(),
'node_id' => $model->node_id,
'allocation_id' => $model->allocation_id,
'nest_id' => $model->nest_id,
'egg_id' => $model->egg_id,
]))->andReturn($model);
$this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->with($model->id, [$model->allocation_id]);
$this->configurationStructureService->shouldReceive('handle')->once()->with($model)->andReturn([]);
$this->daemonServerRepository->shouldReceive('setServer->create')->once();
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle(
collect($model->toArray())->except(['allocation_id', 'node_id'])->toArray(), $deploymentObject
);
}
/** /**
* Test handling of node timeout or other daemon error. * Test handling of node timeout or other daemon error.
* *