172 lines
6.4 KiB
PHP
172 lines
6.4 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
use App\Models\LabSession;
|
||
|
|
use App\Models\LabSessionMachine;
|
||
|
|
use App\Services\Labs\ClientCorrecteur;
|
||
|
|
use App\Services\Labs\StockChaud;
|
||
|
|
use App\Services\Labs\SurveillanceVm;
|
||
|
|
use App\Services\OpenStack\NovaClient;
|
||
|
|
use Illuminate\Support\Facades\Redis;
|
||
|
|
|
||
|
|
// Redis en mémoire : juste les commandes dont SurveillanceVm a besoin.
|
||
|
|
beforeEach(function () {
|
||
|
|
Redis::swap(new class
|
||
|
|
{
|
||
|
|
/** @var array<string, mixed> */
|
||
|
|
public array $d = [];
|
||
|
|
|
||
|
|
public function incr($k)
|
||
|
|
{
|
||
|
|
return $this->d[$k] = (int) ($this->d[$k] ?? 0) + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function get($k)
|
||
|
|
{
|
||
|
|
return $this->d[$k] ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function del($k)
|
||
|
|
{
|
||
|
|
unset($this->d[$k]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function expire($k, $s) {}
|
||
|
|
|
||
|
|
public function hincrby($k, $c, $n)
|
||
|
|
{
|
||
|
|
$this->d[$k][$c] = ($this->d[$k][$c] ?? 0) + $n;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function hgetall($k)
|
||
|
|
{
|
||
|
|
return $this->d[$k] ?? [];
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Session d'élève active avec ses VM, créées il y a $ilYa secondes.
|
||
|
|
*
|
||
|
|
* @param array<string, string> $instances nom => uuid
|
||
|
|
* @return array<string, LabSessionMachine>
|
||
|
|
*/
|
||
|
|
function sessionActive(array $instances, int $ilYa = 600): array
|
||
|
|
{
|
||
|
|
$session = sessionEnAttente(LabSession::ACTIF);
|
||
|
|
$machines = [];
|
||
|
|
foreach ($instances as $nom => $uuid) {
|
||
|
|
$m = $session->machines()->create(['nom' => $nom, 'instance_id' => $uuid]);
|
||
|
|
$m->forceFill(['created_at' => now()->subSeconds($ilYa)])->save();
|
||
|
|
$machines[$nom] = $m;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $machines;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array<string, array{repond: bool, etat: ?string}> $etats uuid => état
|
||
|
|
*/
|
||
|
|
function simuler(string $classe, Closure $attentes): void
|
||
|
|
{
|
||
|
|
app()->instance($classe, Mockery::mock($classe, $attentes));
|
||
|
|
}
|
||
|
|
|
||
|
|
function surveillance(array $etats, array $pretes = [], ?Closure $nova = null, ?Closure $stock = null): SurveillanceVm
|
||
|
|
{
|
||
|
|
simuler(ClientCorrecteur::class, fn ($c) => $c->shouldReceive('sante')->andReturnUsing(
|
||
|
|
fn (string $id) => ($etats[$id] ?? ['repond' => true, 'etat' => 'running']) + ['erreur' => null]
|
||
|
|
));
|
||
|
|
simuler(NovaClient::class, $nova ?? fn ($n) => $n->shouldNotReceive('redemarrerForce', 'demarrer'));
|
||
|
|
simuler(StockChaud::class, function ($s) use ($pretes, $stock) {
|
||
|
|
$s->shouldReceive('pretes')->andReturn($pretes);
|
||
|
|
$stock ? $stock($s) : $s->shouldNotReceive('retirer');
|
||
|
|
});
|
||
|
|
|
||
|
|
return app(SurveillanceVm::class);
|
||
|
|
}
|
||
|
|
|
||
|
|
$muette = ['repond' => false, 'etat' => 'running'];
|
||
|
|
|
||
|
|
test('une VM d\'élève muette 3 minutes de suite est redémarrée de force, une seule fois', function () use ($muette) {
|
||
|
|
$m = sessionActive(['pc-a' => 'uuid-a', 'pc-b' => 'uuid-b', 'routeur' => 'uuid-r']);
|
||
|
|
$s = surveillance(['uuid-a' => $muette], nova: fn ($n) => $n->shouldReceive('redemarrerForce')->once()->with('uuid-a'));
|
||
|
|
|
||
|
|
expect($s->surveiller()['relancees'])->toBe(0)
|
||
|
|
->and($s->surveiller()['relancees'])->toBe(0)
|
||
|
|
->and($s->surveiller()['relancees'])->toBe(1);
|
||
|
|
|
||
|
|
expect($m['pc-a']->refresh()->sante)->toBe(LabSessionMachine::RELANCEE)
|
||
|
|
->and($m['pc-b']->refresh()->sante)->toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('une VM éteinte par l\'élève est redémarrée, pas réinitialisée', function () {
|
||
|
|
sessionActive(['pc-a' => 'uuid-a', 'pc-b' => 'uuid-b', 'routeur' => 'uuid-r']);
|
||
|
|
$s = surveillance(['uuid-a' => ['repond' => false, 'etat' => 'shut off']], nova: fn ($n) => $n->shouldReceive('demarrer')->once()->with('uuid-a'));
|
||
|
|
|
||
|
|
foreach (range(1, 3) as $_) {
|
||
|
|
$s->surveiller();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('après deux relances dans l\'heure, la VM est déclarée irréparable et le prof le voit', function () use ($muette) {
|
||
|
|
$m = sessionActive(['pc-a' => 'uuid-a', 'pc-b' => 'uuid-b', 'routeur' => 'uuid-r']);
|
||
|
|
$s = surveillance(['uuid-a' => $muette], nova: fn ($n) => $n->shouldReceive('redemarrerForce')->twice());
|
||
|
|
|
||
|
|
foreach (range(1, 9) as $_) {
|
||
|
|
$r = $s->surveiller();
|
||
|
|
}
|
||
|
|
|
||
|
|
expect($r['irreparables'])->toBe(1)
|
||
|
|
->and($m['pc-a']->refresh()->sante)->toBe(LabSessionMachine::IRREPARABLE)
|
||
|
|
->and(Redis::hgetall('sante:compteurs'))->toBe(['relances_session' => 2, 'abandons' => 1]);
|
||
|
|
|
||
|
|
$session = $m['pc-a']->labSession;
|
||
|
|
$prof = $session->seance->classe->prof;
|
||
|
|
$this->withSession(['jeton_session_web' => $prof->demarreNouvelleSession()])
|
||
|
|
->actingAs($prof, 'web')
|
||
|
|
->get(route('seances.attente', $session->seance))
|
||
|
|
->assertSee('pc-a ne répond plus');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('toutes les VM muettes à la fois : panne de l\'hôte, aucune relance', function () use ($muette) {
|
||
|
|
sessionActive(['pc-a' => 'uuid-a', 'pc-b' => 'uuid-b', 'routeur' => 'uuid-r']);
|
||
|
|
$s = surveillance(['uuid-a' => $muette, 'uuid-b' => $muette, 'uuid-r' => $muette]);
|
||
|
|
|
||
|
|
foreach (range(1, 4) as $_) {
|
||
|
|
$r = $s->surveiller();
|
||
|
|
}
|
||
|
|
|
||
|
|
expect($r['panne_generale'])->toBeTrue()->and($r['relancees'])->toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('une VM qui vient d\'être créée n\'est pas sondée (elle démarre)', function () use ($muette) {
|
||
|
|
sessionActive(['pc-a' => 'uuid-a'], ilYa: 60);
|
||
|
|
$s = surveillance(['uuid-a' => $muette]);
|
||
|
|
|
||
|
|
foreach (range(1, 4) as $_) {
|
||
|
|
$r = $s->surveiller();
|
||
|
|
}
|
||
|
|
|
||
|
|
expect($r['sondees'])->toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('une stack du stock dont une VM est muette 2 minutes est retirée du stock', function () use ($muette) {
|
||
|
|
$pretes = [
|
||
|
|
['lab' => '08-deux-lan-routeur', 'nom' => 'stock-a-e1', 'id' => 'h1', 'instances' => ['pc-a' => 's1', 'routeur' => 's2', 'pc-b' => 's3'], 'cree_a' => time() - 600, 'brut' => 'A'],
|
||
|
|
['lab' => '08-deux-lan-routeur', 'nom' => 'stock-b-e1', 'id' => 'h2', 'instances' => ['pc-a' => 's4', 'routeur' => 's5', 'pc-b' => 's6'], 'cree_a' => time() - 600, 'brut' => 'B'],
|
||
|
|
];
|
||
|
|
$s = surveillance(['s2' => $muette], $pretes, stock: fn ($st) => $st->shouldReceive('retirer')->once()->with('08-deux-lan-routeur', 'A')->andReturn(true));
|
||
|
|
|
||
|
|
expect($s->surveiller()['retirees_stock'])->toBe(0)
|
||
|
|
->and($s->surveiller()['retirees_stock'])->toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('correcteur injoignable : aucune action', function () {
|
||
|
|
sessionActive(['pc-a' => 'uuid-a']);
|
||
|
|
$this->mock(ClientCorrecteur::class, fn ($c) => $c->shouldReceive('sante')->andThrow(new RuntimeException('Correcteur injoignable')));
|
||
|
|
$this->mock(NovaClient::class, fn ($n) => $n->shouldNotReceive('redemarrerForce', 'demarrer'));
|
||
|
|
$this->mock(StockChaud::class, fn ($s) => $s->shouldReceive('pretes')->andReturn([]));
|
||
|
|
|
||
|
|
expect(app(SurveillanceVm::class)->surveiller()['correcteur_injoignable'])->toBeTrue();
|
||
|
|
});
|