authorize('hosts.manage'); // Through the same query that FINDS them, so "look again" can never // mean a different set of runs than the table above it is showing. app(HostCapacity::class)->parkedRuns()->update(['next_attempt_at' => now()]); $this->dispatch('notify', message: __('capacity.rechecked')); } /** * Decide which parked order goes on which host. * * The queue is read by somebody who knows things the placement rule does * not — that this customer belongs beside that one, that a machine is about * to be taken out of service, that the big order should have the new box * and the small ones can share. The rule stays in charge when nobody says * otherwise; an empty choice hands it back. * * Only the preference is written. The reservation still happens in the * provisioning step, under the placement lock, and still checks the host * has room — a console that created the instance itself would be a second * placement path with no lock and its own arithmetic. */ public function pin(int $runId, string $hostId): void { $this->authorize('hosts.manage'); // Only a run that is actually parked. Writing a host preference onto a // run that has moved on would sit in its context for ever and take // effect on a re-run months later, on a host chosen for a queue that no // longer exists. $run = app(HostCapacity::class)->parkedRuns()->whereKey($runId)->first(); if ($run === null) { return; } if ($hostId === '') { $run->forgetContext('preferred_host_id'); $this->dispatch('notify', message: __('capacity.pin_cleared')); return; } // Nur ein Host, auf dem diese Bestellung auch landen dürfte: // unreserviert oder ihrem eigenen Kunden reserviert, über denselben // Scope, den die Platzierung im Schritt benutzt. Eine reservierte // Maschine gehört ihrem Kunden — sie hier anheften zu lassen hieße, die // Zusage über die Konsole wieder aufzumachen, und der Schritt lehnte // die Bestellung danach ohnehin ab. $host = Host::query() ->where('status', 'active') ->unreserved($run->subject?->customer_id) ->find((int) $hostId); if ($host === null) { return; } $run->mergeContext(['preferred_host_id' => $host->id]); // Taken up now rather than at the next tick: an operator who has just // said where it goes is watching for it to go there. $run->forceFill(['next_attempt_at' => now()])->save(); $this->dispatch('notify', message: __('capacity.pinned', ['host' => $host->name])); } public function render() { $this->authorize('hosts.manage'); $capacity = app(HostCapacity::class); $demand = $capacity->queueDemand(); $parked = $capacity->parked(); // Die Auswahl je Bestellung, nicht eine Liste für alle: welcher Host in // Frage kommt, hängt am Kunden dieser Bestellung. Gefragt wird über // denselben Scope wie in der Platzierung — im eigenen Rechenzentrum // (der Schritt lehnt einen Pin von anderswo ab) und nicht jemand // anderem reserviert. Ein Auswahlfeld, das anbietet, was die Aktion // danach ablehnt, ist eine Falle. // // Eine Abfrage je Rechenzentrum/Kunde-Paar statt je Zeile: die // Warteschlange ist die Liste bezahlter Bestellungen ohne Maschine, also // kurz, und mehrere davon gehören meist demselben Rechenzentrum. $choices = []; foreach ($parked as $index => $entry) { $customerId = $entry['order']->customer_id; $key = $entry['datacenter'].'|'.$customerId; $choices[$key] ??= Host::query() ->where('status', 'active') ->where('datacenter', $entry['datacenter']) ->unreserved($customerId) ->orderBy('name') ->get(); $parked[$index]['choices'] = $choices[$key]; } return view('livewire.admin.capacity', [ 'parked' => $parked, 'demand' => $demand, 'unplaceable' => $capacity->unplaceablePlans(), // reservedFor eager-loaded: die Tafel "was die Hosts noch // aufnehmen" weist eine reservierte Maschine als solche aus, // statt sie kommentarlos neben dem allgemeinen Bestand zu zeigen. 'hosts' => Host::query()->where('status', 'active')->orderBy('name')->with('reservedFor')->get(), // Only when something is actually waiting. A shopping list on an // idle estate is an invitation to spend money for no reason. 'offers' => $demand['count'] > 0 ? app(ServerMarket::class)->offers($demand['largest']) : [], ]); } }