102 lines
4.0 KiB
PHP
102 lines
4.0 KiB
PHP
<?php
|
|
|
|
use App\Actions\StartHostOnboarding;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Models\RunResource;
|
|
use App\Provisioning\RunRunner;
|
|
use App\Provisioning\Steps\Host\ConfigureWireguard;
|
|
use App\Services\Ssh\CommandResult;
|
|
use Illuminate\Support\Facades\Queue;
|
|
|
|
it('drives a fresh host all the way to active (mocked)', function () {
|
|
Queue::fake(); // drive the runner manually, one step per advance
|
|
$s = fakeServices();
|
|
$s['shell']->script('wg pubkey', CommandResult::success('HOSTPUBKEY0000='));
|
|
$s['shell']->script('uname -r', CommandResult::success('6.8.12-4-pve'));
|
|
$s['shell']->script('pveum user token add', CommandResult::success(
|
|
json_encode(['full-tokenid' => 'automation@pve!clupilot', 'value' => 'tok-secret-123'])
|
|
));
|
|
|
|
$host = app(StartHostOnboarding::class)->run([
|
|
'name' => 'pve-fsn-9',
|
|
'datacenter' => 'fsn',
|
|
'public_ip' => '203.0.113.9',
|
|
'root_password' => 'rootpw',
|
|
]);
|
|
|
|
$run = ProvisioningRun::query()->where('subject_id', $host->id)->firstOrFail();
|
|
$runner = app(RunRunner::class);
|
|
|
|
for ($i = 0; $i < 40; $i++) {
|
|
$run->refresh();
|
|
if (in_array($run->status, ['completed', 'failed'], true)) {
|
|
break;
|
|
}
|
|
if ($run->next_attempt_at?->isFuture()) {
|
|
$run->update(['next_attempt_at' => now()]); // simulate the scheduler waking a due run
|
|
}
|
|
$runner->advance($run);
|
|
}
|
|
|
|
$run->refresh();
|
|
$host->refresh();
|
|
|
|
expect($run->status)->toBe('completed')
|
|
->and($run->error)->toBeNull()
|
|
->and($host->status)->toBe('active')
|
|
->and($host->wg_ip)->not->toBeNull()
|
|
->and($host->total_gb)->toBe(1024)
|
|
->and($host->cpu_cores)->toBe(16)
|
|
->and($host->api_token_ref)->toContain('automation@pve')
|
|
// external resources created exactly once
|
|
->and(RunResource::where('run_id', $run->id)->where('kind', 'wg_peer')->count())->toBe(1)
|
|
->and(RunResource::where('run_id', $run->id)->where('kind', 'pve_token')->count())->toBe(1)
|
|
->and(RunResource::where('run_id', $run->id)->where('kind', 'host_firewall')->count())->toBe(1)
|
|
// one-time root password scrubbed from state
|
|
->and($run->context('root_password'))->toBeNull();
|
|
|
|
// The firewall step ran last, over the tunnel, and left its rules + the
|
|
// manual emergency-release script on the host.
|
|
expect($s['shell']->files())->toHaveKey('/etc/nftables.conf')
|
|
->and($s['shell']->files())->toHaveKey('/usr/local/sbin/clupilot-emergency-open-firewall.sh');
|
|
});
|
|
|
|
it('does not duplicate external resources when a step re-runs after a crash', function () {
|
|
Queue::fake();
|
|
$s = fakeServices();
|
|
$s['shell']->script('wg pubkey', CommandResult::success('HOSTPUBKEY0000='));
|
|
$s['shell']->script('uname -r', CommandResult::success('6.8.12-4-pve'));
|
|
$s['shell']->script('pveum user token add', CommandResult::success(
|
|
json_encode(['full-tokenid' => 'automation@pve!clupilot', 'value' => 'tok-secret-123'])
|
|
));
|
|
|
|
$host = app(StartHostOnboarding::class)->run([
|
|
'name' => 'pve-fsn-10',
|
|
'datacenter' => 'fsn',
|
|
'public_ip' => '203.0.113.10',
|
|
'root_password' => 'rootpw',
|
|
]);
|
|
$run = ProvisioningRun::query()->where('subject_id', $host->id)->firstOrFail();
|
|
$runner = app(RunRunner::class);
|
|
|
|
// Advance a few steps, then simulate a crash by rewinding the run to the
|
|
// WireGuard step and replaying it — the breadcrumb must prevent duplicates.
|
|
for ($i = 0; $i < 6; $i++) {
|
|
$run->refresh();
|
|
if ($run->next_attempt_at?->isFuture()) {
|
|
$run->update(['next_attempt_at' => now()]);
|
|
}
|
|
$runner->advance($run);
|
|
}
|
|
$run->refresh();
|
|
$wgIndex = array_search(
|
|
ConfigureWireguard::class,
|
|
config('provisioning.pipelines.host'),
|
|
true,
|
|
);
|
|
$run->update(['current_step' => $wgIndex, 'status' => 'running']);
|
|
$runner->advance($run->fresh());
|
|
|
|
expect(RunResource::where('run_id', $run->id)->where('kind', 'wg_peer')->count())->toBe(1);
|
|
});
|