Skip to content

Commit fda50bb

Browse files
authored
Merge pull request pterodactyl#2434 from pressstartearly/develop
Added Autoallocation Button
2 parents 31ad238 + 49c29aa commit fda50bb

File tree

24 files changed

+842
-140
lines changed

24 files changed

+842
-140
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Pterodactyl\Exceptions\Service\Allocation;
4+
5+
use Pterodactyl\Exceptions\DisplayException;
6+
7+
class AutoAllocationNotEnabledException extends DisplayException
8+
{
9+
/**
10+
* AutoAllocationNotEnabledException constructor.
11+
*/
12+
public function __construct()
13+
{
14+
parent::__construct(
15+
'Server auto-allocation is not enabled for this instance.'
16+
);
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Pterodactyl\Exceptions\Service\Allocation;
4+
5+
use Pterodactyl\Exceptions\DisplayException;
6+
7+
class NoAutoAllocationSpaceAvailableException extends DisplayException
8+
{
9+
/**
10+
* NoAutoAllocationSpaceAvailableException constructor.
11+
*/
12+
public function __construct()
13+
{
14+
parent::__construct(
15+
'Cannot assign additional allocation: no more space available on node.'
16+
);
17+
}
18+
}

app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
1111
use Pterodactyl\Transformers\Api\Client\AllocationTransformer;
1212
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
13+
use Pterodactyl\Services\Allocations\FindAssignableAllocationService;
1314
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest;
15+
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest;
1416
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\DeleteAllocationRequest;
1517
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest;
1618
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest;
@@ -27,20 +29,28 @@ class NetworkAllocationController extends ClientApiController
2729
*/
2830
private $serverRepository;
2931

32+
/**
33+
* @var \Pterodactyl\Services\Allocations\FindAssignableAllocationService
34+
*/
35+
private $assignableAllocationService;
36+
3037
/**
3138
* NetworkController constructor.
3239
*
3340
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $repository
3441
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository
42+
* @param \Pterodactyl\Services\Allocations\FindAssignableAllocationService $assignableAllocationService
3543
*/
3644
public function __construct(
3745
AllocationRepository $repository,
38-
ServerRepository $serverRepository
46+
ServerRepository $serverRepository,
47+
FindAssignableAllocationService $assignableAllocationService
3948
) {
4049
parent::__construct();
4150

4251
$this->repository = $repository;
4352
$this->serverRepository = $serverRepository;
53+
$this->assignableAllocationService = $assignableAllocationService;
4454
}
4555

4656
/**
@@ -100,6 +110,31 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server,
100110
->toArray();
101111
}
102112

113+
/**
114+
* Set the notes for the allocation for a server.
115+
*s
116+
*
117+
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest $request
118+
* @param \Pterodactyl\Models\Server $server
119+
* @return array
120+
*
121+
* @throws \Pterodactyl\Exceptions\DisplayException
122+
*/
123+
public function store(NewAllocationRequest $request, Server $server): array
124+
{
125+
if ($server->allocations()->count() >= $server->allocation_limit) {
126+
throw new DisplayException(
127+
'Cannot assign additional allocations to this server: limit has been reached.'
128+
);
129+
}
130+
131+
$allocation = $this->assignableAllocationService->handle($server);
132+
133+
return $this->fractal->item($allocation)
134+
->transformWith($this->getTransformer(AllocationTransformer::class))
135+
->toArray();
136+
}
137+
103138
/**
104139
* Delete an allocation from a server.
105140
*
@@ -109,18 +144,19 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server,
109144
* @return \Illuminate\Http\JsonResponse
110145
*
111146
* @throws \Pterodactyl\Exceptions\DisplayException
112-
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
113-
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
114147
*/
115148
public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation)
116149
{
117150
if ($allocation->id === $server->allocation_id) {
118151
throw new DisplayException(
119-
'Cannot delete the primary allocation for a server.'
152+
'You cannot delete the primary allocation for this server.'
120153
);
121154
}
122155

123-
$this->repository->update($allocation->id, ['server_id' => null, 'notes' => null]);
156+
Allocation::query()->where('id', $allocation->id)->update([
157+
'notes' => null,
158+
'server_id' => null,
159+
]);
124160

125161
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
126162
}

app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ public function rules()
1919
'recaptcha:website_key' => 'required|string|max:191',
2020
'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60',
2121
'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60',
22+
'pterodactyl:console:count' => 'required|integer|min:1',
23+
'pterodactyl:console:frequency' => 'required|integer|min:10',
24+
'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false',
25+
'pterodactyl:client_features:allocations:range_start' => 'required|integer|between:1024,65535',
26+
'pterodactyl:client_features:allocations:range_end' => 'required|integer|between:1024,65535',
2227
];
2328
}
2429

@@ -33,6 +38,11 @@ public function attributes()
3338
'recaptcha:website_key' => 'reCAPTCHA Website Key',
3439
'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout',
3540
'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout',
41+
'pterodactyl:console:count' => 'Console Message Count',
42+
'pterodactyl:console:frequency' => 'Console Frequency Tick',
43+
'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled',
44+
'pterodactyl:client_features:allocations:range_start' => 'Starting Port',
45+
'pterodactyl:client_features:allocations:range_end' => 'Ending Port',
3646
];
3747
}
3848
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Network;
4+
5+
use Illuminate\Support\Collection;
6+
use Pterodactyl\Models\Allocation;
7+
use Pterodactyl\Models\Permission;
8+
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
9+
10+
class NewAllocationRequest extends ClientApiRequest
11+
{
12+
/**
13+
* @return string
14+
*/
15+
public function permission(): string
16+
{
17+
return Permission::ACTION_ALLOCATION_CREATE;
18+
}
19+
20+
}

app/Providers/SettingsServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class SettingsServiceProvider extends ServiceProvider
3030
'pterodactyl:console:count',
3131
'pterodactyl:console:frequency',
3232
'pterodactyl:auth:2fa_required',
33+
'pterodactyl:client_features:allocations:enabled',
34+
'pterodactyl:client_features:allocations:range_start',
35+
'pterodactyl:client_features:allocations:range_end',
3336
];
3437

3538
/**
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Pterodactyl\Services\Allocations;
4+
5+
use Webmozart\Assert\Assert;
6+
use Pterodactyl\Models\Server;
7+
use Pterodactyl\Models\Allocation;
8+
use Pterodactyl\Exceptions\Service\Allocation\AutoAllocationNotEnabledException;
9+
use Pterodactyl\Exceptions\Service\Allocation\NoAutoAllocationSpaceAvailableException;
10+
11+
class FindAssignableAllocationService
12+
{
13+
/**
14+
* @var \Pterodactyl\Services\Allocations\AssignmentService
15+
*/
16+
private $service;
17+
18+
/**
19+
* FindAssignableAllocationService constructor.
20+
*
21+
* @param \Pterodactyl\Services\Allocations\AssignmentService $service
22+
*/
23+
public function __construct(AssignmentService $service)
24+
{
25+
$this->service = $service;
26+
}
27+
28+
/**
29+
* Finds an existing unassigned allocation and attempts to assign it to the given server. If
30+
* no allocation can be found, a new one will be created with a random port between the defined
31+
* range from the configuration.
32+
*
33+
* @param \Pterodactyl\Models\Server $server
34+
* @return \Pterodactyl\Models\Allocation
35+
*
36+
* @throws \Pterodactyl\Exceptions\DisplayException
37+
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
38+
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
39+
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
40+
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
41+
*/
42+
public function handle(Server $server)
43+
{
44+
if (! config('pterodactyl.client_features.allocations.enabled')) {
45+
throw new AutoAllocationNotEnabledException;
46+
}
47+
48+
// Attempt to find a given available allocation for a server. If one cannot be found
49+
// we will fall back to attempting to create a new allocation that can be used for the
50+
// server.
51+
/** @var \Pterodactyl\Models\Allocation|null $allocation */
52+
$allocation = $server->node->allocations()
53+
->where('ip', $server->allocation->ip)
54+
->whereNull('server_id')
55+
->inRandomOrder()
56+
->first();
57+
58+
$allocation = $allocation ?? $this->createNewAllocation($server);
59+
60+
$allocation->update(['server_id' => $server->id]);
61+
62+
return $allocation->refresh();
63+
}
64+
65+
/**
66+
* Create a new allocation on the server's node with a random port from the defined range
67+
* in the settings. If there are no matches in that range, or something is wrong with the
68+
* range information provided an exception will be raised.
69+
*
70+
* @param \Pterodactyl\Models\Server $server
71+
* @return \Pterodactyl\Models\Allocation
72+
*
73+
* @throws \Pterodactyl\Exceptions\DisplayException
74+
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
75+
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
76+
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
77+
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
78+
*/
79+
protected function createNewAllocation(Server $server): Allocation
80+
{
81+
$start = config('pterodactyl.client_features.allocations.range_start', null);
82+
$end = config('pterodactyl.client_features.allocations.range_end', null);
83+
84+
if (! $start || ! $end) {
85+
throw new NoAutoAllocationSpaceAvailableException;
86+
}
87+
88+
Assert::integerish($start);
89+
Assert::integerish($end);
90+
91+
// Get all of the currently allocated ports for the node so that we can figure out
92+
// which port might be available.
93+
$ports = $server->node->allocations()
94+
->where('ip', $server->allocation->ip)
95+
->whereBetween('port', [$start, $end])
96+
->pluck('port');
97+
98+
// Compute the difference of the range and the currently created ports, finding
99+
// any port that does not already exist in the database. We will then use this
100+
// array of ports to create a new allocation to assign to the server.
101+
$available = array_diff(range($start, $end), $ports->toArray());
102+
103+
// If we've already allocated all of the ports, just abort.
104+
if (empty($available)) {
105+
throw new NoAutoAllocationSpaceAvailableException;
106+
}
107+
108+
// Pick a random port out of the remaining available ports.
109+
/** @var int $port */
110+
$port = $available[array_rand($available)];
111+
112+
$this->service->handle($server->node, [
113+
'allocation_ip' => $server->allocation->ip,
114+
'allocation_ports' => [$port],
115+
]);
116+
117+
/** @var \Pterodactyl\Models\Allocation $allocation */
118+
$allocation = $server->node->allocations()
119+
->where('ip', $server->allocation->ip)
120+
->where('port', $port)
121+
->firstOrFail();
122+
123+
return $allocation;
124+
}
125+
}

config/app.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
| change this value if you are not maintaining your own internal versions.
1010
*/
1111

12-
'version' => 'canary',
12+
'version' => '1.0.1',
1313

1414
/*
1515
|--------------------------------------------------------------------------

config/pterodactyl.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@
143143
// The total number of tasks that can exist for any given schedule at once.
144144
'per_schedule_task_limit' => 10,
145145
],
146+
147+
'allocations' => [
148+
'enabled' => env('PTERODACTYL_CLIENT_ALLOCATIONS_ENABLED', false),
149+
'range_start' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_START'),
150+
'range_end' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_END'),
151+
],
146152
],
147153

148154
/*
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Allocation } from '@/api/server/getServer';
2+
import http from '@/api/http';
3+
import { rawDataToServerAllocation } from '@/api/transformers';
4+
5+
export default async (uuid: string): Promise<Allocation> => {
6+
const { data } = await http.post(`/api/client/servers/${uuid}/network/allocations`);
7+
8+
return rawDataToServerAllocation(data);
9+
};

0 commit comments

Comments
 (0)