fix(hosts): retry a failed DNS registration before advancing without a name
The failure may be a lost response to a request that did create the record. Advancing straight away stored no id, so PurgeHost could never remove it and the host's management address stayed published. The upsert is idempotent, so a retry recovers the id; only after the attempts run out does the onboarding continue without a name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
952e8e5d2f
commit
7b57ab21cb
|
|
@ -47,6 +47,17 @@ class RegisterHostDns extends HostStep
|
|||
try {
|
||||
$recordId = $this->dns->upsertRecord($fqdn, 'A', $host->wg_ip);
|
||||
} catch (Throwable $e) {
|
||||
// Retried first, because the failure may be a lost response to a
|
||||
// request that DID create the record: advancing then leaves a
|
||||
// published address with no id, so PurgeHost could never remove it.
|
||||
// The upsert is idempotent, so a retry recovers the id.
|
||||
$allowed = min(3, (int) $run->max_attempts);
|
||||
if ($run->attempt + 1 < $allowed) {
|
||||
return StepResult::retry(30, 'DNS unavailable: '.$e->getMessage());
|
||||
}
|
||||
|
||||
// Out of attempts: a name is convenience, and a host that is
|
||||
// otherwise ready should not be stranded for the want of one.
|
||||
$run->events()->create([
|
||||
'step' => $this->key(),
|
||||
'outcome' => 'info',
|
||||
|
|
|
|||
|
|
@ -287,15 +287,22 @@ it('numbers hosts per datacenter and never reuses a number', function () {
|
|||
expect($host->fresh()->dns_name)->toBe('fsn-03');
|
||||
});
|
||||
|
||||
it('does not strand an onboarding because DNS was unreachable', function () {
|
||||
it('retries a failed DNS registration before giving up on the name', function () {
|
||||
$s = fakeServices();
|
||||
$s['dns']->failUpsert = true;
|
||||
$host = App\Models\Host::factory()->active()->create(['datacenter' => 'fsn', 'wg_ip' => '10.66.0.40', 'dns_name' => null]);
|
||||
$run = App\Models\ProvisioningRun::factory()->forHost($host)->create(["pipeline" => "host"]);
|
||||
$run = App\Models\ProvisioningRun::factory()->forHost($host)->create(['pipeline' => 'host', 'attempt' => 0]);
|
||||
|
||||
// A name is convenience; the host works without it.
|
||||
expect((new App\Provisioning\Steps\Host\RegisterHostDns($s['dns']))->execute($run)->type)->toBe("advance");
|
||||
expect($run->events()->where('step', 'register_host_dns')->exists())->toBeTrue();
|
||||
$step = new App\Provisioning\Steps\Host\RegisterHostDns($s['dns']);
|
||||
|
||||
// The failure may be a lost response to a request that did create the
|
||||
// record — advancing straight away would orphan it.
|
||||
expect($step->execute($run)->type)->toBe('retry');
|
||||
|
||||
// Out of attempts, the host still finishes: a name is convenience.
|
||||
$run->update(['attempt' => 5]);
|
||||
expect($step->execute($run)->type)->toBe('advance')
|
||||
->and($run->events()->where('step', 'register_host_dns')->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
it('takes the host DNS record with it when the host is purged', function () {
|
||||
|
|
|
|||
Loading…
Reference in New Issue