Improve logic handle auto-allocation of ports for a server
This commit is contained in:
parent
7638ffccde
commit
c6bd7ff661
10 changed files with 201 additions and 79 deletions
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Exceptions\Service\Allocation;
|
||||
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
|
||||
class AutoAllocationNotEnabledException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* AutoAllocationNotEnabledException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'Server auto-allocation is not enabled for this instance.'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Exceptions\Service\Allocation;
|
||||
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
|
||||
class NoAutoAllocationSpaceAvailableException extends DisplayException
|
||||
{
|
||||
/**
|
||||
* NoAutoAllocationSpaceAvailableException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'Cannot assign additional allocation: no more space available on node.'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Models\Allocation;
|
||||
|
@ -11,13 +10,12 @@ use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
|||
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
|
||||
use Pterodactyl\Transformers\Api\Client\AllocationTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||
use Pterodactyl\Services\Allocations\FindAssignableAllocationService;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\DeleteAllocationRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest;
|
||||
use Pterodactyl\Services\Allocations\AssignmentService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class NetworkAllocationController extends ClientApiController
|
||||
{
|
||||
|
@ -32,37 +30,27 @@ class NetworkAllocationController extends ClientApiController
|
|||
private $serverRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Allocations\AssignmentService
|
||||
* @var \Pterodactyl\Services\Allocations\FindAssignableAllocationService
|
||||
*/
|
||||
protected $assignmentService;
|
||||
private $assignableAllocationService;
|
||||
|
||||
/**
|
||||
* NetworkController constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $repository
|
||||
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository
|
||||
* @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService
|
||||
* @param \Illuminate\Contracts\Config\Repository $config
|
||||
* @param \Pterodactyl\Services\Allocations\FindAssignableAllocationService $assignableAllocationService
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
private $config;
|
||||
|
||||
public function __construct(
|
||||
AllocationRepository $repository,
|
||||
ServerRepository $serverRepository,
|
||||
AssignmentService $assignmentService,
|
||||
Repository $config
|
||||
|
||||
FindAssignableAllocationService $assignableAllocationService
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->repository = $repository;
|
||||
$this->serverRepository = $serverRepository;
|
||||
$this->assignmentService = $assignmentService;
|
||||
$this->config = $config;
|
||||
$this->assignableAllocationService = $assignableAllocationService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,52 +113,16 @@ class NetworkAllocationController extends ClientApiController
|
|||
/**
|
||||
* Set the notes for the allocation for a server.
|
||||
*s
|
||||
*
|
||||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @param \Pterodactyl\Models\Allocation $allocation
|
||||
* @return array
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function addNew(NewAllocationRequest $request, Server $server): array
|
||||
public function store(NewAllocationRequest $request, Server $server): array
|
||||
{
|
||||
$topRange = config('pterodactyl.allocation.stop',0);
|
||||
$bottomRange = config('pterodactyl.allocation.start',0);
|
||||
|
||||
if($server->allocation_limit <= $server->allocations->count()) {
|
||||
throw new DisplayException(
|
||||
'You have created the maximum number of allocations!'
|
||||
);
|
||||
}
|
||||
|
||||
$allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->whereNull('server_id')->first();
|
||||
|
||||
if(!$allocation) {
|
||||
if($server->node->allocations()->where('ip',$server->allocation->ip)->where([['port', '>=', $bottomRange ], ['port', '<=', $topRange],])->count() >= $topRange-$bottomRange+1 || !config('allocation.enabled', false)) {
|
||||
throw new DisplayException(
|
||||
'No more allocations available!'
|
||||
);
|
||||
}
|
||||
|
||||
$allPorts = $server->node->allocations()->select(['port'])->where('ip',$server->allocation->ip)->get()->pluck('port')->toArray();
|
||||
|
||||
do {
|
||||
$port = rand($bottomRange, $topRange);
|
||||
} while(array_search($port, $allPorts));
|
||||
|
||||
$this->assignmentService->handle($server->node,[
|
||||
'allocation_ip'=>$server->allocation->ip,
|
||||
'allocation_ports'=>[$port],
|
||||
'server_id'=>$server->id
|
||||
]);
|
||||
|
||||
$allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->where('port', $port)->first();
|
||||
|
||||
}
|
||||
|
||||
$allocation->update(['server_id' => $server->id]);
|
||||
$allocation = $this->assignableAllocationService->handle($server);
|
||||
|
||||
return $this->fractal->item($allocation)
|
||||
->transformWith($this->getTransformer(AllocationTransformer::class))
|
||||
|
|
|
@ -21,9 +21,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest
|
|||
'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60',
|
||||
'pterodactyl:console:count' => 'required|integer|min:1',
|
||||
'pterodactyl:console:frequency' => 'required|integer|min:10',
|
||||
'allocation:enabled' => 'required|in:true,false',
|
||||
'pterodactyl:allocation:start' => 'required|integer|between:2000,65535',
|
||||
'pterodactyl:allocation:stop' => 'required|integer|between:2000,65535',
|
||||
'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false',
|
||||
'pterodactyl:client_features:allocations:range_start' => 'required|integer|between:1024,65535',
|
||||
'pterodactyl:client_features:allocations:range_end' => 'required|integer|between:1024,65535',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -40,9 +40,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest
|
|||
'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout',
|
||||
'pterodactyl:console:count' => 'Console Message Count',
|
||||
'pterodactyl:console:frequency' => 'Console Frequency Tick',
|
||||
'allocation:enabled' => 'Auto Create Allocations Enabled',
|
||||
'pterodactyl:allocation:start' => 'Starting Port',
|
||||
'pterodactyl:allocation:stop' => 'Ending Port',
|
||||
'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled',
|
||||
'pterodactyl:client_features:allocations:range_start' => 'Starting Port',
|
||||
'pterodactyl:client_features:allocations:range_end' => 'Ending Port',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,9 +30,9 @@ class SettingsServiceProvider extends ServiceProvider
|
|||
'pterodactyl:console:count',
|
||||
'pterodactyl:console:frequency',
|
||||
'pterodactyl:auth:2fa_required',
|
||||
'allocation:enabled',
|
||||
'pterodactyl:allocation:stop',
|
||||
'pterodactyl:allocation:start',
|
||||
'pterodactyl:client_features:allocations:enabled',
|
||||
'pterodactyl:client_features:allocations:range_start',
|
||||
'pterodactyl:client_features:allocations:range_end',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
130
app/Services/Allocations/FindAssignableAllocationService.php
Normal file
130
app/Services/Allocations/FindAssignableAllocationService.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
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;
|
||||
|
||||
class FindAssignableAllocationService
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Allocations\AssignmentService
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* FindAssignableAllocationService constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Services\Allocations\AssignmentService $service
|
||||
*/
|
||||
public function __construct(AssignmentService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an existing unassigned allocation and attempts to assign it to the given server. If
|
||||
* no allocation can be found, a new one will be created with a random port between the defined
|
||||
* range from the configuration.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Pterodactyl\Models\Allocation
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
||||
*/
|
||||
public function handle(Server $server)
|
||||
{
|
||||
if (!config('pterodactyl.client_features.allocations.enabled')) {
|
||||
throw new AutoAllocationNotEnabledException;
|
||||
}
|
||||
|
||||
if ($server->allocations()->count() >= $server->allocation_limit) {
|
||||
throw new DisplayException(
|
||||
'Cannot assign additional allocations to this server: limit has been reached.'
|
||||
);
|
||||
}
|
||||
|
||||
// Attempt to find a given available allocation for a server. If one cannot be found
|
||||
// we will fall back to attempting to create a new allocation that can be used for the
|
||||
// server.
|
||||
/** @var \Pterodactyl\Models\Allocation|null $allocation */
|
||||
$allocation = $server->node->allocations()
|
||||
->where('ip', $server->allocation->ip)
|
||||
->whereNull('server_id')
|
||||
->inRandomOrder()
|
||||
->first();
|
||||
|
||||
$allocation = $allocation ?? $this->createNewAllocation($server);
|
||||
|
||||
$allocation->update(['server_id' => $server->id]);
|
||||
|
||||
return $allocation->refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Pterodactyl\Models\Allocation
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
||||
*/
|
||||
protected function createNewAllocation(Server $server): Allocation
|
||||
{
|
||||
$start = config('pterodactyl.client_features.allocations.range_start', null);
|
||||
$end = config('pterodactyl.client_features.allocations.range_end', null);
|
||||
|
||||
if (!$start || !$end) {
|
||||
throw new NoAutoAllocationSpaceAvailableException;
|
||||
}
|
||||
|
||||
Assert::integerish($start);
|
||||
Assert::integerish($end);
|
||||
|
||||
// Get all of the currently allocated ports for the node so that we can figure out
|
||||
// which port might be available.
|
||||
$ports = $server->node->allocations()
|
||||
->where('ip', $server->allocation->ip)
|
||||
->whereBetween('port', [$start, $end])
|
||||
->pluck('port');
|
||||
|
||||
// Compute the difference of the range and the currently created ports, finding
|
||||
// any port that does not already exist in the database. We will then use this
|
||||
// array of ports to create a new allocation to assign to the server.
|
||||
$available = array_diff(range($start, $end), $ports->toArray());
|
||||
|
||||
// If we've already allocated all of the ports, just abort.
|
||||
if (empty($available)) {
|
||||
throw new NoAutoAllocationSpaceAvailableException;
|
||||
}
|
||||
|
||||
// dd($available, array_rand($available));
|
||||
// Pick a random port out of the remaining available ports.
|
||||
/** @var int $port */
|
||||
$port = $available[array_rand($available)];
|
||||
|
||||
$this->service->handle($server->node, [
|
||||
'server_id' => $server->id,
|
||||
'allocation_ip' => $server->allocation->ip,
|
||||
'allocation_ports' => [$port],
|
||||
]);
|
||||
|
||||
/** @var \Pterodactyl\Models\Allocation $allocation */
|
||||
$allocation = $server->node->allocations()
|
||||
->where('ip', $server->allocation->ip)
|
||||
->where('port', $port)
|
||||
->firstOrFail();
|
||||
|
||||
return $allocation;
|
||||
}
|
||||
}
|
|
@ -143,6 +143,12 @@ return [
|
|||
// The total number of tasks that can exist for any given schedule at once.
|
||||
'per_schedule_task_limit' => 10,
|
||||
],
|
||||
|
||||
'allocations' => [
|
||||
'enabled' => env('PTERODACTYL_CLIENT_ALLOCATIONS_ENABLED', false),
|
||||
'range_start' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_START'),
|
||||
'range_end' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_END'),
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -2,10 +2,8 @@ import { Allocation } from '@/api/server/getServer';
|
|||
import http from '@/api/http';
|
||||
import { rawDataToServerAllocation } from '@/api/transformers';
|
||||
|
||||
export default (uuid: string): Promise<Allocation> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(`/api/client/servers/${uuid}/network/allocations/new`)
|
||||
.then(({ data }) => resolve(rawDataToServerAllocation(data)))
|
||||
.catch(reject);
|
||||
});
|
||||
export default async (uuid: string): Promise<Allocation> => {
|
||||
const { data } = await http.post(`/api/client/servers/${uuid}/network/allocations`);
|
||||
|
||||
return rawDataToServerAllocation(data);
|
||||
};
|
||||
|
|
|
@ -114,24 +114,24 @@
|
|||
<div class="form-group col-md-4">
|
||||
<label class="control-label">Status</label>
|
||||
<div>
|
||||
<select class="form-control" name="allocation:enabled">
|
||||
<select class="form-control" name="pterodactyl:client_features:allocations:enabled">
|
||||
<option value="false">Disabled</option>
|
||||
<option value="true" @if(old('allocation:enabled', config('allocation.enabled'))) selected @endif>Enabled</option>
|
||||
<option value="true" @if(old('pterodactyl:client_features:allocations:enabled', config('pterodactyl.client_features.allocations.enabled'))) selected @endif>Enabled</option>
|
||||
</select>
|
||||
<p class="text-muted small">If enabled, the panel will attempt to auto create a new allocation in the range specified if there are no more allocations already created on the node.</p>
|
||||
<p class="text-muted small">If enabled users will have the option to automatically create new allocations for their server via the frontend.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label class="control-label">Starting Port</label>
|
||||
<div>
|
||||
<input type="number" required class="form-control" name="pterodactyl:allocation:start" value="{{ old('pterodactyl:allocation:start', config('pterodactyl.allocation.start')) }}">
|
||||
<input type="number" required class="form-control" name="pterodactyl:client_features:allocations:range_start" value="{{ old('pterodactyl:client_features:allocations:range_start', config('pterodactyl.client_features.allocations.range_start')) }}">
|
||||
<p class="text-muted small">The starting port in the range that can be automatically allocated.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label class="control-label">Ending Port</label>
|
||||
<div>
|
||||
<input type="number" required class="form-control" name="pterodactyl:allocation:stop" value="{{ old('pterodactyl:allocation:stop', config('pterodactyl.allocation.stop')) }}">
|
||||
<input type="number" required class="form-control" name="pterodactyl:client_features:allocations:range_end" value="{{ old('pterodactyl:client_features:allocations:range_end', config('pterodactyl.client_features.allocations.range_end')) }}">
|
||||
<p class="text-muted small">The ending port in the range that can be automatically allocated.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -82,9 +82,9 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ
|
|||
|
||||
Route::group(['prefix' => '/network', 'middleware' => [AllocationBelongsToServer::class]], function () {
|
||||
Route::get('/allocations', 'Servers\NetworkAllocationController@index');
|
||||
Route::post('/allocations', 'Servers\NetworkAllocationController@store');
|
||||
Route::post('/allocations/{allocation}', 'Servers\NetworkAllocationController@update');
|
||||
Route::post('/allocations/{allocation}/primary', 'Servers\NetworkAllocationController@setPrimary');
|
||||
Route::get('/allocations/new', 'Servers\NetworkAllocationController@addNew');
|
||||
Route::delete('/allocations/{allocation}', 'Servers\NetworkAllocationController@delete');
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue