diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index ed3638265..6f523663a 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -5,7 +5,6 @@ namespace Pterodactyl\Services\Allocations; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\Service\Allocation\AutoAllocationNotEnabledException; use Pterodactyl\Exceptions\Service\Allocation\NoAutoAllocationSpaceAvailableException; @@ -42,7 +41,7 @@ class FindAssignableAllocationService */ public function handle(Server $server) { - if (!config('pterodactyl.client_features.allocations.enabled')) { + if (! config('pterodactyl.client_features.allocations.enabled')) { throw new AutoAllocationNotEnabledException; } @@ -64,6 +63,10 @@ class FindAssignableAllocationService } /** + * Create a new allocation on the server's node with a random port from the defined range + * in the settings. If there are no matches in that range, or something is wrong with the + * range information provided an exception will be raised. + * * @param \Pterodactyl\Models\Server $server * @return \Pterodactyl\Models\Allocation * @@ -78,7 +81,7 @@ class FindAssignableAllocationService $start = config('pterodactyl.client_features.allocations.range_start', null); $end = config('pterodactyl.client_features.allocations.range_end', null); - if (!$start || !$end) { + if (! $start || ! $end) { throw new NoAutoAllocationSpaceAvailableException; } diff --git a/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php new file mode 100644 index 000000000..ce49a1873 --- /dev/null +++ b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php @@ -0,0 +1,179 @@ +set('pterodactyl.client_features.allocations.enabled', true); + config()->set('pterodactyl.client_features.allocations.range_start', 0); + config()->set('pterodactyl.client_features.allocations.range_end', 0); + } + + /** + * Test that an unassigned allocation is prefered rather than creating an entirely new + * allocation for the server. + */ + public function testExistingAllocationIsPreferred() + { + $server = $this->createServerModel(); + + $created = factory(Allocation::class)->create([ + 'node_id' => $server->node_id, + 'ip' => $server->allocation->ip, + ]); + + $response = $this->getService()->handle($server); + + $this->assertSame($created->id, $response->id); + $this->assertSame($server->allocation->ip, $response->ip); + $this->assertSame($server->node_id, $response->node_id); + $this->assertSame($server->id, $response->server_id); + $this->assertNotSame($server->allocation_id, $response->id); + } + + /** + * Test that a new allocation is created if there is not a free one available. + */ + public function testNewAllocationIsCreatedIfOneIsNotFound() + { + $server = $this->createServerModel(); + config()->set('pterodactyl.client_features.allocations.range_start', 5000); + config()->set('pterodactyl.client_features.allocations.range_end', 5005); + + $response = $this->getService()->handle($server); + $this->assertSame($server->id, $response->server_id); + $this->assertSame($server->allocation->ip, $response->ip); + $this->assertSame($server->node_id, $response->node_id); + $this->assertNotSame($server->allocation_id, $response->id); + $this->assertTrue($response->port >= 5000 && $response->port <= 5005); + } + + /** + * Test that a currently assigned port is never assigned to a server. + */ + public function testOnlyPortNotInUseIsCreated() + { + $server = $this->createServerModel(); + $server2 = $this->createServerModel(['node_id' => $server->node_id]); + + config()->set('pterodactyl.client_features.allocations.range_start', 5000); + config()->set('pterodactyl.client_features.allocations.range_end', 5001); + + factory(Allocation::class)->create([ + 'server_id' => $server2->id, + 'node_id' => $server->node_id, + 'ip' => $server->allocation->ip, + 'port' => 5000, + ]); + + $response = $this->getService()->handle($server); + $this->assertSame(5001, $response->port); + } + + public function testExceptionIsThrownIfNoMoreAllocationsCanBeCreatedInRange() + { + $server = $this->createServerModel(); + $server2 = $this->createServerModel(['node_id' => $server->node_id]); + config()->set('pterodactyl.client_features.allocations.range_start', 5000); + config()->set('pterodactyl.client_features.allocations.range_end', 5005); + + for ($i = 5000; $i <= 5005; $i++) { + factory(Allocation::class)->create([ + 'ip' => $server->allocation->ip, + 'port' => $i, + 'node_id' => $server->node_id, + 'server_id' => $server2->id, + ]); + } + + $this->expectException(NoAutoAllocationSpaceAvailableException::class); + $this->expectExceptionMessage('Cannot assign additional allocation: no more space available on node.'); + + $this->getService()->handle($server); + } + + /** + * Test that we only auto-allocate from the current server's IP address space, and not a random + * IP address available on that node. + */ + public function testExceptionIsThrownIfOnlyFreePortIsOnADifferentIp() + { + $server = $this->createServerModel(); + + factory(Allocation::class)->times(5)->create(['node_id' => $server->node_id]); + + $this->expectException(NoAutoAllocationSpaceAvailableException::class); + $this->expectExceptionMessage('Cannot assign additional allocation: no more space available on node.'); + + $this->getService()->handle($server); + } + + public function testExceptionIsThrownIfStartOrEndRangeIsNotDefined() + { + $server = $this->createServerModel(); + + $this->expectException(NoAutoAllocationSpaceAvailableException::class); + $this->expectExceptionMessage('Cannot assign additional allocation: no more space available on node.'); + + $this->getService()->handle($server); + } + + public function testExceptionIsThrownIfStartOrEndRangeIsNotNumeric() + { + $server = $this->createServerModel(); + config()->set('pterodactyl.client_features.allocations.range_start', 'hodor'); + config()->set('pterodactyl.client_features.allocations.range_end', 10); + + try { + $this->getService()->handle($server); + $this->assertTrue(false, 'This assertion should not be reached.'); + } catch (Exception $exception) { + $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); + } + + config()->set('pterodactyl.client_features.allocations.range_start', 10); + config()->set('pterodactyl.client_features.allocations.range_end', 'hodor'); + + try { + $this->getService()->handle($server); + $this->assertTrue(false, 'This assertion should not be reached.'); + } catch (Exception $exception) { + $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); + } + } + + public function testExceptionIsThrownIfFeatureIsNotEnabled() + { + config()->set('pterodactyl.client_features.allocations.enabled', false); + $server = $this->createServerModel(); + + $this->expectException(AutoAllocationNotEnabledException::class); + + $this->getService()->handle($server); + } + + /** + * @return \Pterodactyl\Services\Allocations\FindAssignableAllocationService + */ + private function getService() + { + return $this->app->make(FindAssignableAllocationService::class); + } +}