diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2d7eaeb46..bcc56c1a7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -99,7 +99,7 @@ jobs: QUEUE_CONNECTION: sync HASHIDS_SALT: alittlebitofsalt1234 DB_CONNECTION: sqlite - DB_DATABASE: ${{ github.workspace }}/database/testing.sqlite + DB_DATABASE: testing.sqlite steps: - name: Code Checkout uses: actions/checkout@v4 diff --git a/app/Exceptions/Service/Deployment/NoViableNodeException.php b/app/Exceptions/Service/Deployment/NoViableNodeException.php deleted file mode 100644 index fc2fa47b8..000000000 --- a/app/Exceptions/Service/Deployment/NoViableNodeException.php +++ /dev/null @@ -1,9 +0,0 @@ -validated(); - $nodes = $this->viableNodesService - ->setMemory($data['memory']) - ->setDisk($data['disk']) - ->setCpu($data['cpu'] ?? 0) - ->handle((int) $request->query('per_page'), (int) $request->query('page')); + + $nodes = $this->viableNodesService->handle( + $data['disk'] ?? 0, + $data['memory'] ?? 0, + $data['cpu'] ?? 0, + $data['tags'] ?? $data['location_ids'] ?? [], + ); return $this->fractal->collection($nodes) ->transformWith($this->getTransformer(NodeTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 2d675516b..230d19794 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -50,7 +50,6 @@ class ServerController extends ApplicationApiController * @throws \App\Exceptions\DisplayException * @throws \App\Exceptions\Model\DataValidationException * @throws \App\Exceptions\Service\Deployment\NoViableAllocationException - * @throws \App\Exceptions\Service\Deployment\NoViableNodeException */ public function store(StoreServerRequest $request): JsonResponse { diff --git a/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php index 8f8cd7f05..28dc3afa2 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetDeployableNodesRequest.php @@ -11,6 +11,10 @@ class GetDeployableNodesRequest extends GetNodesRequest 'memory' => 'required|integer|min:0', 'disk' => 'required|integer|min:0', 'cpu' => 'sometimes|integer|min:0', + 'tags' => 'sometimes|array', + + /** @deprecated use tags instead */ + 'location_ids' => 'sometimes|array', ]; } } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index d3343e7a5..fd735edf0 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -123,6 +123,15 @@ class StoreServerRequest extends ApplicationApiRequest return !$input->deploy; }); + /** @deprecated use tags instead */ + $validator->sometimes('deploy.locations', 'present', function ($input) { + return $input->deploy; + }); + + $validator->sometimes('deploy.tags', 'present', function ($input) { + return $input->deploy; + }); + $validator->sometimes('deploy.port_range', 'present', function ($input) { return $input->deploy; }); @@ -139,6 +148,7 @@ class StoreServerRequest extends ApplicationApiRequest $object = new DeploymentObject(); $object->setDedicated($this->input('deploy.dedicated_ip', false)); + $object->setTags($this->input('deploy.tags', $this->input('deploy.locations', []))); $object->setPorts($this->input('deploy.port_range', [])); return $object; diff --git a/app/Models/Objects/DeploymentObject.php b/app/Models/Objects/DeploymentObject.php index d5b02dd83..b7a4ebaf3 100644 --- a/app/Models/Objects/DeploymentObject.php +++ b/app/Models/Objects/DeploymentObject.php @@ -6,6 +6,8 @@ class DeploymentObject { private bool $dedicated = false; + private array $tags = []; + private array $ports = []; public function isDedicated(): bool @@ -31,4 +33,17 @@ class DeploymentObject return $this; } + + public function getTags(): array + { + return $this->tags; + } + + public function setTags(array $tags): self + { + $this->tags = $tags; + + return $this; + } + } diff --git a/app/Services/Deployment/AllocationSelectionService.php b/app/Services/Deployment/AllocationSelectionService.php index 54853c025..3868d0b38 100644 --- a/app/Services/Deployment/AllocationSelectionService.php +++ b/app/Services/Deployment/AllocationSelectionService.php @@ -90,11 +90,9 @@ class AllocationSelectionService */ private function getRandomAllocation(array $nodes = [], array $ports = [], bool $dedicated = false): ?Allocation { - $query = Allocation::query()->whereNull('server_id'); - - if (!empty($nodes)) { - $query->whereIn('node_id', $nodes); - } + $query = Allocation::query() + ->whereNull('server_id') + ->whereIn('node_id', $nodes); if (!empty($ports)) { $query->where(function ($inner) use ($ports) { diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index 1c56135fd..9a51d1c6f 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -3,54 +3,12 @@ namespace App\Services\Deployment; use App\Models\Node; -use Webmozart\Assert\Assert; use Illuminate\Support\Collection; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; -use App\Exceptions\Service\Deployment\NoViableNodeException; class FindViableNodesService { - protected ?int $memory = null; - protected ?int $disk = null; - protected ?int $cpu = null; - /** - * Set the amount of memory that this server will be using. As with disk space, nodes that - * do not have enough free memory will be filtered out. - */ - public function setMemory(int $memory): self - { - $this->memory = $memory; - - return $this; - } - - /** - * Set the amount of disk that will be used by the server being created. Nodes will be - * filtered out if they do not have enough available free disk space for this server - * to be placed on. - */ - public function setDisk(int $disk): self - { - $this->disk = $disk; - - return $this; - } - - /** - * Set the amount of cpu that will be used by the server being created. Nodes will be - * filtered out if they do not have enough available free cpu for this server - * to be placed on. - */ - public function setCpu(int $cpu): self - { - $this->cpu = $cpu; - - return $this; - } - - /** - * Returns an array of nodes that meet the provided requirements and can then + * Returns a collection of nodes that meet the provided requirements and can then * be passed to the AllocationSelectionService to return a single allocation. * * This functionality is used for automatic deployments of servers and will @@ -58,42 +16,20 @@ class FindViableNodesService * and cpu availability requirements. Any nodes not meeting those requirements * are tossed out, as are any nodes marked as non-public, meaning automatic * deployments should not be done against them. - * - * @param int|null $page If provided the results will be paginated by returning - * up to 50 nodes at a time starting at the provided page. - * If "null" is provided as the value no pagination will - * be used. - * - * @throws \App\Exceptions\Service\Deployment\NoViableNodeException */ - public function handle(int $perPage = null, int $page = null): LengthAwarePaginator|Collection + public function handle(int $disk = 0, int $memory = 0, int $cpu = 0, $tags = []): Collection { - Assert::integer($this->memory, 'Memory usage must be an int, got %s'); - Assert::integer($this->disk, 'Disk space must be an int, got %s'); - Assert::integer($this->cpu, 'CPU must be an int, got %s'); + $nodes = Node::query() + ->withSum('servers', 'disk') + ->withSum('servers', 'memory') + ->withSum('servers', 'cpu') + ->where('public', true) + ->get(); - $query = Node::query()->select('nodes.*') - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory') - ->selectRaw('IFNULL(SUM(servers.disk), 0) as sum_disk') - ->selectRaw('IFNULL(SUM(servers.cpu), 0) as sum_cpu') - ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') - ->where('nodes.public', 1); - - $results = $query->groupBy('nodes.id') - ->havingRaw('(IFNULL(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1 + (nodes.memory_overallocate / 100)))', [$this->memory]) - ->havingRaw('(IFNULL(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1 + (nodes.disk_overallocate / 100)))', [$this->disk]) - ->havingRaw('(IFNULL(SUM(servers.cpu), 0) + ?) <= (nodes.cpu * (1 + (nodes.cpu_overallocate / 100)))', [$this->cpu]); - - if (!is_null($page)) { - $results = $results->paginate($perPage ?? 50, ['*'], 'page', $page); - } else { - $results = $results->get()->toBase(); - } - - if ($results->isEmpty()) { - throw new NoViableNodeException(trans('exceptions.deployment.no_viable_nodes')); - } - - return $results; + return $nodes + ->filter(fn (Node $node) => !$tags || collect($node->tags)->intersect($tags)) + ->filter(fn (Node $node) => $node->servers_sum_disk + $disk <= $node->disk * (1 + $node->disk_overallocate / 100)) + ->filter(fn (Node $node) => $node->servers_sum_memory + $memory <= $node->memory * (1 + $node->memory_overallocate / 100)) + ->filter(fn (Node $node) => $node->servers_sum_cpu + $cpu <= $node->cpu * (1 + $node->cpu_overallocate / 100)); } } diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index ab62ddd5f..8953b1325 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -42,7 +42,6 @@ class ServerCreationService * @throws \Throwable * @throws \App\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException - * @throws \App\Exceptions\Service\Deployment\NoViableNodeException * @throws \App\Exceptions\Service\Deployment\NoViableAllocationException */ public function handle(array $data, DeploymentObject $deployment = null): Server @@ -105,16 +104,16 @@ class ServerCreationService * * @throws \App\Exceptions\DisplayException * @throws \App\Exceptions\Service\Deployment\NoViableAllocationException - * @throws \App\Exceptions\Service\Deployment\NoViableNodeException */ private function configureDeployment(array $data, DeploymentObject $deployment): Allocation { - /** @var \Illuminate\Support\Collection $nodes */ - $nodes = $this->findViableNodesService - ->setMemory(Arr::get($data, 'memory')) - ->setDisk(Arr::get($data, 'disk')) - ->setCpu(Arr::get($data, 'cpu')) - ->handle(); + /** @var Collection<\App\Models\Node> $nodes */ + $nodes = $this->findViableNodesService->handle( + Arr::get($data, 'disk', 0), + Arr::get($data, 'memory', 0), + Arr::get($data, 'cpu', 0), + Arr::get($data, 'tags', []), + ); return $this->allocationSelectionService->setDedicated($deployment->isDedicated()) ->setNodes($nodes->pluck('id')->toArray()) diff --git a/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php b/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php deleted file mode 100644 index 5d8d8be32..000000000 --- a/tests/Integration/Services/Deployment/FindViableNodesServiceTest.php +++ /dev/null @@ -1,50 +0,0 @@ -delete(); - Server::query()->delete(); - Node::query()->delete(); - } - - public function testExceptionIsThrownIfNoMemoryHasBeenSet(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Memory usage must be an int, got NULL'); - - $this->getService()->setDisk(10)->setCpu(10)->handle(); - } - - public function testExceptionIsThrownIfNoDiskSpaceHasBeenSet(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Disk space must be an int, got NULL'); - - $this->getService()->setMemory(10)->setCpu(10)->handle(); - } - - public function testExceptionIsThrownIfNoCpuHasBeenSet(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('CPU must be an int, got NULL'); - - $this->getService()->setMemory(10)->setDisk(10)->handle(); - } - - private function getService(): FindViableNodesService - { - return $this->app->make(FindViableNodesService::class); - } -}