mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-11-04 06:26:52 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			112 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			112 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
namespace App\Services\Allocations;
 | 
						|
 | 
						|
use Webmozart\Assert\Assert;
 | 
						|
use App\Models\Server;
 | 
						|
use App\Models\Allocation;
 | 
						|
use App\Exceptions\Service\Allocation\AutoAllocationNotEnabledException;
 | 
						|
use App\Exceptions\Service\Allocation\NoAutoAllocationSpaceAvailableException;
 | 
						|
 | 
						|
class FindAssignableAllocationService
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * FindAssignableAllocationService constructor.
 | 
						|
     */
 | 
						|
    public function __construct(private AssignmentService $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.
 | 
						|
     *
 | 
						|
     * @throws \App\Exceptions\DisplayException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\CidrOutOfRangeException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\InvalidPortMappingException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException
 | 
						|
     */
 | 
						|
    public function handle(Server $server): Allocation
 | 
						|
    {
 | 
						|
        if (!config('panel.client_features.allocations.enabled')) {
 | 
						|
            throw new AutoAllocationNotEnabledException();
 | 
						|
        }
 | 
						|
 | 
						|
        // 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 \App\Models\Allocation|null $allocation */
 | 
						|
        $allocation = $server->node->allocations()
 | 
						|
            ->when($server->allocation, function ($query) use ($server) {
 | 
						|
                $query->where('ip', $server->allocation->ip);
 | 
						|
            })
 | 
						|
            ->whereNull('server_id')
 | 
						|
            ->inRandomOrder()
 | 
						|
            ->first();
 | 
						|
 | 
						|
        $allocation = $allocation ?? $this->createNewAllocation($server);
 | 
						|
 | 
						|
        $allocation->update(['server_id' => $server->id]);
 | 
						|
 | 
						|
        return $allocation->refresh();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 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.
 | 
						|
     *
 | 
						|
     * @throws \App\Exceptions\DisplayException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\CidrOutOfRangeException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\InvalidPortMappingException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException
 | 
						|
     * @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException
 | 
						|
     */
 | 
						|
    protected function createNewAllocation(Server $server): Allocation
 | 
						|
    {
 | 
						|
        $start = config('panel.client_features.allocations.range_start', null);
 | 
						|
        $end = config('panel.client_features.allocations.range_end', null);
 | 
						|
 | 
						|
        if (!$start || !$end) {
 | 
						|
            throw new NoAutoAllocationSpaceAvailableException();
 | 
						|
        }
 | 
						|
 | 
						|
        Assert::integerish($start);
 | 
						|
        Assert::integerish($end);
 | 
						|
 | 
						|
        // Get all 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 the ports, just abort.
 | 
						|
        if (empty($available)) {
 | 
						|
            throw new NoAutoAllocationSpaceAvailableException();
 | 
						|
        }
 | 
						|
 | 
						|
        // Pick a random port out of the remaining available ports.
 | 
						|
        /** @var int $port */
 | 
						|
        $port = $available[array_rand($available)];
 | 
						|
 | 
						|
        $this->service->handle($server->node, [
 | 
						|
            'allocation_ip' => $server->allocation->ip,
 | 
						|
            'allocation_ports' => [$port],
 | 
						|
        ]);
 | 
						|
 | 
						|
        /** @var \App\Models\Allocation $allocation */
 | 
						|
        $allocation = $server->node->allocations()
 | 
						|
            ->where('ip', $server->allocation->ip)
 | 
						|
            ->where('port', $port)
 | 
						|
            ->firstOrFail();
 | 
						|
 | 
						|
        return $allocation;
 | 
						|
    }
 | 
						|
}
 |