99 lines
3.2 KiB
PHP
99 lines
3.2 KiB
PHP
<?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()]);
|
|
}
|
|
}
|
|
}
|