forInstance( $subscription->instance ?? $subscription->customer?->instances()->latest('id')->first() ); } /** * Start a storage run for this instance, or return null when there is * nothing to start. * * Null is the ordinary answer and not a failure: a machine still being * built, one whose VM has gone, one whose service has ended, or one that * already has a run in flight all deliver the allowance from that run * instead — every pipeline that touches storage reads the allowance when it * gets there, which is this state or newer. */ public function forInstance(?Instance $instance): ?ProvisioningRun { if ($instance === null || ! $instance->hasLiveMachine()) { return null; } // The same lock ReapplyInstanceAddress and RestartInstance take, for the // same reason: two bookings can land in one instant — a double click, a // webhook delivered twice, an operator granting a pack while the // customer buys one — and two runs against one machine would resize the // same disk and race each other's occ calls. Nobody waits for it: a run // that lost the race has nothing to add, because the one that won reads // the same allowance. $lock = Cache::lock('instance-storage:'.$instance->uuid, 30); if (! $lock->get()) { return null; } try { return $this->start($instance); } finally { $lock->release(); } } private function start(Instance $instance): ?ProvisioningRun { $order = $instance->order; // Stand aside only for a run that will deliver the whole allowance: the // disk, the filesystem inside it and the figure Nextcloud enforces. A // plan change does all three and there is nothing to add. A `quota` run // does only the last of them, and letting it stand for the booking would // enforce a quota over a filesystem that never grew — space the machine // does not have. `address` and `restart` do none of them, and standing // aside for one of those was how a pack could be charged monthly and // never delivered. See App\Provisioning\WorkInFlight. if (app(WorkInFlight::class)->covers($order, 'storage')) { return null; } $run = ProvisioningRun::create([ 'subject_type' => Order::class, 'subject_id' => $order->id, 'pipeline' => 'storage', 'status' => ProvisioningRun::STATUS_PENDING, 'current_step' => 0, // Everything the three steps read: instance_id is how CustomerStep // finds the machine, node and vmid are how it reaches inside it. 'context' => [ 'instance_id' => $instance->id, 'host_id' => $instance->host_id, 'node' => $instance->host?->node, 'vmid' => $instance->vmid, 'subdomain' => $instance->subdomain, ], ]); AdvanceRunJob::dispatch($run->uuid); Log::info('Delivering a changed storage allowance.', [ 'instance' => $instance->uuid, 'run' => $run->uuid, ]); return $run; } }