Paillasse/portail/app/Jobs/CreerStackLab.php

100 lines
3.2 KiB
PHP
Raw Normal View History

<?php
namespace App\Jobs;
use App\Models\LabSession;
use App\Services\Labs\DefinitionLab;
use App\Services\OpenStack\FabriqueStack;
use App\Services\OpenStack\HeatClient;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Throwable;
/**
* Construit la stack d'un élève admis quand le stock chaud du lab est vide
* (chemin lent : 20 à 40 s). Un essai raté supprime sa stack et lève une
* exception : Laravel relance le job (2 nouveaux essais, après 15 puis 45 s).
* Après le 3ᵉ échec, failed() passe la session en « echec », visible du prof.
*/
class CreerStackLab implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
/** @var array<int, int> */
public array $backoff = [15, 45];
// Doit rester sous le timeout du superviseur Horizon (300 s), lui-même
// sous retry_after (330 s, voir .env) : sinon un job encore en cours
// serait redonné à un autre processus.
public int $timeout = 280;
public function __construct(private readonly LabSession $labSession)
{
$this->onQueue('sessions');
}
public function handle(FabriqueStack $fabrique, HeatClient $heat): void
{
$session = $this->labSession->fresh();
// Expirée ou relancée autrement entre-temps : plus rien à construire.
if ($session === null || $session->etat !== LabSession::ADMIS) {
return;
}
$base = "lab-session-{$session->id}";
$fabrique->nettoyerEssais($base);
$stack = $fabrique->fabriquer(
DefinitionLab::depuis($session->lab->identifiant),
"{$base}-e{$this->attempts()}",
function (string $nom, string $id) use ($session): void {
$session->forceFill(['heat_stack_name' => $nom, 'heat_stack_id' => $id])->save();
},
);
if ($session->fresh()->etat !== LabSession::ADMIS) {
$heat->supprimerStack($stack['nom'], $stack['id']);
return;
}
$session->attribuerStack($stack);
}
public function failed(?Throwable $exception): void
{
$session = $this->labSession->fresh();
if ($session === null) {
return;
}
$session->forceFill([
'etat' => LabSession::ECHEC,
'raison_echec' => Str::limit($exception?->getMessage() ?? 'raison inconnue', 250),
'heat_stack_name' => null,
'heat_stack_id' => null,
])->save();
Log::error("CreerStackLab : session {$session->id} en échec après {$this->tries} essais", [
'raison' => $exception?->getMessage(),
]);
// Le dernier essai a normalement supprimé sa stack ; pas si son
// processus a été tué (timeout) : on repasse derrière.
try {
app(FabriqueStack::class)->nettoyerEssais("lab-session-{$session->id}");
} catch (Throwable $e) {
Log::error("CreerStackLab : nettoyage impossible pour la session {$session->id}", ['raison' => $e->getMessage()]);
}
}
}