93 lines
2.7 KiB
PHP
93 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Steps\Customer;
|
|
|
|
use App\Models\Host;
|
|
use App\Models\Instance;
|
|
use App\Models\Order;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\StepResult;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Str;
|
|
|
|
class ReserveResources extends CustomerStep
|
|
{
|
|
public function key(): string
|
|
{
|
|
return 'reserve_resources';
|
|
}
|
|
|
|
public function maxDuration(): int
|
|
{
|
|
return 60;
|
|
}
|
|
|
|
public function execute(ProvisioningRun $run): StepResult
|
|
{
|
|
$order = $this->order($run);
|
|
|
|
// Idempotent: an instance already exists for this order.
|
|
$existing = Instance::query()->where('order_id', $order->id)->first();
|
|
if ($existing !== null) {
|
|
$this->putContext($run, $existing);
|
|
|
|
return StepResult::advance();
|
|
}
|
|
|
|
$plan = $this->plan($run);
|
|
|
|
// Place + create under a per-datacenter lock so concurrent reservations
|
|
// can't both pick the same host and overcommit its capacity.
|
|
$instance = Cache::lock('placement:'.$order->datacenter, 30)->block(10, function () use ($order, $plan) {
|
|
$host = Host::placeableIn($order->datacenter, (int) $plan['disk_gb']);
|
|
if ($host === null) {
|
|
return null;
|
|
}
|
|
|
|
return Instance::create([
|
|
'customer_id' => $order->customer_id,
|
|
'order_id' => $order->id,
|
|
'host_id' => $host->id,
|
|
'plan' => $order->plan,
|
|
'quota_gb' => $plan['quota_gb'],
|
|
'disk_gb' => $plan['disk_gb'],
|
|
'ram_mb' => $plan['ram_mb'],
|
|
'cores' => $plan['cores'],
|
|
'subdomain' => $this->uniqueSubdomain($order),
|
|
'status' => 'reserving',
|
|
]);
|
|
});
|
|
|
|
if ($instance === null) {
|
|
return StepResult::fail('no_capacity');
|
|
}
|
|
|
|
$this->putContext($run, $instance);
|
|
$this->recordResource($run, $instance->host, 'instance_id', (string) $instance->id);
|
|
|
|
return StepResult::advance();
|
|
}
|
|
|
|
private function putContext(ProvisioningRun $run, Instance $instance): void
|
|
{
|
|
$run->mergeContext([
|
|
'instance_id' => $instance->id,
|
|
'host_id' => $instance->host_id,
|
|
'node' => $instance->host?->node ?? 'pve',
|
|
'subdomain' => $instance->subdomain,
|
|
'plan' => $instance->plan,
|
|
]);
|
|
}
|
|
|
|
private function uniqueSubdomain(Order $order): string
|
|
{
|
|
$base = Str::slug($order->customer->name) ?: 'cloud';
|
|
|
|
do {
|
|
$subdomain = $base.'-'.Str::lower(Str::random(5));
|
|
} while (Instance::query()->where('subdomain', $subdomain)->exists());
|
|
|
|
return $subdomain;
|
|
}
|
|
}
|