76 lines
3.2 KiB
PHP
76 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning;
|
|
|
|
use App\Models\Order;
|
|
use App\Models\ProvisioningRun;
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* Is the work somebody is about to start already going to happen anyway?
|
|
*
|
|
* Every customer pipeline shares one subject — the instance's own purchase Order
|
|
* — so an action that wants to start a run has to look at what is already in
|
|
* flight against it. Two runs writing one router, or resizing one disk from two
|
|
* different figures, is the failure this guards.
|
|
*
|
|
* It used to ask whether ANYTHING was in flight, and stand aside if it was, on
|
|
* the reasoning that whatever is running reads the current state when it gets
|
|
* there. That is true only of a run that actually contains the steps in question.
|
|
* It was false for `restart`, which has no address steps, and for `address`, which
|
|
* has no storage steps — with two consequences that both cost the customer
|
|
* something real:
|
|
*
|
|
* - a domain proven while a restart was running was never routed, and nothing
|
|
* looked again: the nightly proof check only re-applies an address on the
|
|
* FLIP from unproven to proven (see VerifyCustomDomains), and that flip had
|
|
* already happened.
|
|
* - a storage pack booked while an address run was going was charged every
|
|
* month and never delivered, because nothing else ever grows a disk.
|
|
*
|
|
* So the question is now what it always meant: will the run in flight carry out
|
|
* this work? Where the answer is no, the new run starts beside it. That is safe
|
|
* for the pairs it allows, and only for those: the caller has already established
|
|
* that the machine is LIVE (Instance::hasLiveMachine()), which rules out standing
|
|
* beside the build; a run whose pipeline covers the work still wins; and what is
|
|
* left writes different things — a router file and a DNS record on one side, a
|
|
* disk and a filesystem on the other, with Nextcloud's config.php and its
|
|
* appconfig table apart from each other. The one genuinely awkward pair is a
|
|
* `restart`, where the guest goes away underneath the newcomer; those steps throw
|
|
* on an unreachable guest, which the runner turns into a retry, and the backoff
|
|
* outlasts a reboot.
|
|
*/
|
|
class WorkInFlight
|
|
{
|
|
public function __construct(private PipelineRegistry $registry) {}
|
|
|
|
/**
|
|
* Will a run already going against this order carry out every step of
|
|
* `$pipeline`?
|
|
*/
|
|
public function covers(Order $order, string $pipeline): bool
|
|
{
|
|
$runs = ProvisioningRun::query()
|
|
->where('subject_type', Order::class)
|
|
->where('subject_id', $order->id)
|
|
->inFlight()
|
|
->get(['pipeline', 'current_step']);
|
|
|
|
foreach ($runs as $run) {
|
|
try {
|
|
if ($this->registry->stillCovers($run->pipeline, $run->current_step, $pipeline)) {
|
|
return true;
|
|
}
|
|
} catch (InvalidArgumentException) {
|
|
// A pipeline that no longer exists (renamed in a deploy while a
|
|
// run was in flight) covers nothing. The run will fail at its
|
|
// next advance for the same reason; refusing to start the work
|
|
// over it would lose the work as well.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|