Wer einen Server aus der Ping-Ueberwachung nehmen wollte, musste sich bisher auf den Rechner mit check_fds.sh einloggen und die hosts.conf editieren - und die Kachel blieb danach trotzdem im System-Monitor stehen, weil ein einmal gemeldetes System nirgends wieder geloescht werden konnte. Die Liste steht jetzt in der Datenbank und ist ueber ein Zahnrad neben der Ueberschrift "Systeme" pflegbar, analog zum Zahnrad am PO-Platz (Issue #27). Jeder Host gehoert dem OPs-Agenten (Issue #66), der ihn anpingt: API und Ping-Check laufen nicht zwangslaeufig auf demselben Rechner, die API kann die hosts.conf also nicht selbst schreiben. check_fds.sh holt sie stattdessen bei jedem Lauf mit seinem Agenten-Key ab und schreibt daraus die lokale Datei; ist die API nicht erreichbar, pingt es unveraendert nach der zuletzt geschriebenen Liste weiter. Beim ersten Lauf laedt der Check seine bestehende hosts.conf hoch, damit die bereits ueberwachten Server nicht von Hand nachgetragen werden muessen. Dass die API das nur einmal annimmt (Marker in host_config_imports) und "noch nie konfiguriert" per 204 von "im Frontend geleert" per 200 unterscheidet, ist noetig, damit ein Ping-Rechner mit alter lokaler Datei die dort geloeschten Hosts nicht wieder anlegt. Loeschen raeumt mit auf: Ein entfernter Host nimmt seine Kachel mit - beide Ids, denn der status.txt-Import benennt ihn nach dem Hostnamen, die REST-Meldung nach dessen Slug. Kacheln ohne zugehoerigen Host (Selbstmelder oder Reste laengst entfernter Hosts) stehen im selben Dialog unter "Weitere Kacheln im System-Monitor" und sind dort einzeln loeschbar - dafuer gibt es neu DELETE /api/v1/systems/{id}. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vNjbfr8M3HSfWMee92GTE
162 lines
5.8 KiB
PHP
162 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Monitor\Api\Tests\Controller;
|
|
|
|
use Monitor\Api\Controller\HostConfigController;
|
|
use Monitor\Api\Db\Database;
|
|
use Monitor\Api\Http\Request;
|
|
use Monitor\Api\Repository\AgentRepository;
|
|
use Monitor\Api\Repository\HostConfigRepository;
|
|
use Monitor\Api\Repository\SystemRepository;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class HostConfigControllerTest extends TestCase
|
|
{
|
|
private HostConfigController $controller;
|
|
private HostConfigRepository $hosts;
|
|
private SystemRepository $systems;
|
|
/** @var array<string, mixed> */
|
|
private array $opsAgent;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$db = Database::inMemory();
|
|
$agents = new AgentRepository($db);
|
|
$this->opsAgent = $agents->register('OPs', 'ops')['agent'];
|
|
$this->hosts = new HostConfigRepository($db);
|
|
$this->systems = new SystemRepository($db);
|
|
$this->controller = new HostConfigController($this->hosts, $this->systems);
|
|
}
|
|
|
|
public function testIndexReturnsDerivedSystemIds(): void
|
|
{
|
|
$this->hosts->create([
|
|
'agent_id' => $this->opsAgent['id'],
|
|
'hostname' => 'Nextcloud(lokal)',
|
|
'address' => '192.168.2.7',
|
|
]);
|
|
|
|
$body = $this->controller->index(Request::create('GET', '/api/v1/hosts'))->body();
|
|
|
|
self::assertSame(
|
|
['Nextcloud(lokal)', 'nextcloud-lokal'],
|
|
$body['hosts'][0]['system_ids']
|
|
);
|
|
self::assertSame('OPs', $body['hosts'][0]['agent_name']);
|
|
}
|
|
|
|
public function testDeleteAlsoRemovesTilesOfBothReportingPaths(): void
|
|
{
|
|
$host = $this->hosts->create([
|
|
'agent_id' => $this->opsAgent['id'],
|
|
'hostname' => 'Nextcloud(lokal)',
|
|
'address' => '192.168.2.7',
|
|
]);
|
|
// So wuerde derselbe Host in den Monitor kommen: per REST unter dem Slug,
|
|
// per status.txt-Import unter dem Namen.
|
|
$this->systems->reportStatus('nextcloud-lokal', ['status' => 'error']);
|
|
$this->systems->importLegacyRows([[
|
|
'id' => 'Nextcloud(lokal)', 'name' => 'Nextcloud(lokal)', 'status' => 'error',
|
|
'detail' => '192.168.2.7', 'last_check_at' => '2026-01-01 00:00',
|
|
]]);
|
|
|
|
$this->controller->delete(Request::create('DELETE', '/api/v1/hosts/1'), ['id' => (string) $host['id']]);
|
|
|
|
self::assertNull($this->systems->find('nextcloud-lokal'));
|
|
self::assertNull($this->systems->find('Nextcloud(lokal)'));
|
|
}
|
|
|
|
public function testUpdateRemovesTileOfPreviousHostname(): void
|
|
{
|
|
$host = $this->hosts->create([
|
|
'agent_id' => $this->opsAgent['id'],
|
|
'hostname' => 'alt',
|
|
'address' => '10.0.0.1',
|
|
]);
|
|
$this->systems->reportStatus('alt', ['status' => 'ok']);
|
|
|
|
$this->controller->update(
|
|
Request::create('PATCH', '/api/v1/hosts/1', [], ['hostname' => 'neu', 'address' => '10.0.0.1']),
|
|
['id' => (string) $host['id']]
|
|
);
|
|
|
|
self::assertNull($this->systems->find('alt'));
|
|
}
|
|
|
|
public function testConfReturnsPlainHostsFileForAuthenticatedAgent(): void
|
|
{
|
|
$this->hosts->create(['agent_id' => $this->opsAgent['id'], 'hostname' => 'Web', 'address' => '10.0.0.1']);
|
|
|
|
$response = $this->controller->conf(Request::create('GET', '/api/v1/hosts/conf'), [], $this->opsAgent);
|
|
|
|
self::assertSame(200, $response->status);
|
|
self::assertStringContainsString("Web 10.0.0.1\n", (string) $response->body());
|
|
}
|
|
|
|
public function testConfAnswers204BeforeTheFirstImport(): void
|
|
{
|
|
$response = $this->controller->conf(Request::create('GET', '/api/v1/hosts/conf'), [], $this->opsAgent);
|
|
|
|
self::assertSame(204, $response->status);
|
|
}
|
|
|
|
public function testConfAnswers200WithAnEmptyListAfterTheImport(): void
|
|
{
|
|
// Alle Hosts im Frontend entfernt: der Ping-Rechner soll seine Datei leeren, nicht
|
|
// die alte Liste behalten - also 200 mit leerer Liste statt 204.
|
|
$this->controller->import(
|
|
Request::create('POST', '/api/v1/hosts/import', [], [], [], "Web 10.0.0.1\n"),
|
|
[],
|
|
$this->opsAgent
|
|
);
|
|
foreach ($this->hosts->allForAgent((int) $this->opsAgent['id']) as $host) {
|
|
$this->hosts->delete((int) $host['id']);
|
|
}
|
|
|
|
$response = $this->controller->conf(Request::create('GET', '/api/v1/hosts/conf'), [], $this->opsAgent);
|
|
|
|
self::assertSame(200, $response->status);
|
|
self::assertStringNotContainsString('Web', (string) $response->body());
|
|
}
|
|
|
|
public function testConfRejectsAgentWithoutOpsRole(): void
|
|
{
|
|
$response = $this->controller->conf(
|
|
Request::create('GET', '/api/v1/hosts/conf'),
|
|
[],
|
|
['id' => 99, 'role' => 'entwickler']
|
|
);
|
|
|
|
self::assertSame(403, $response->status);
|
|
}
|
|
|
|
public function testImportFillsListFromUploadedHostsConf(): void
|
|
{
|
|
$conf = "# Kommentar\nWeb 10.0.0.1\nMail 10.0.0.2\n";
|
|
|
|
$response = $this->controller->import(
|
|
Request::create('POST', '/api/v1/hosts/import', [], [], [], $conf),
|
|
[],
|
|
$this->opsAgent
|
|
);
|
|
|
|
self::assertSame(2, $response->body()['imported']);
|
|
self::assertCount(2, $this->hosts->allForAgent((int) $this->opsAgent['id']));
|
|
}
|
|
|
|
public function testImportDoesNotOverwriteExistingList(): void
|
|
{
|
|
$this->hosts->create(['agent_id' => $this->opsAgent['id'], 'hostname' => 'Web', 'address' => '10.0.0.1']);
|
|
|
|
$response = $this->controller->import(
|
|
Request::create('POST', '/api/v1/hosts/import', [], [], [], "Anders 10.0.0.9\n"),
|
|
[],
|
|
$this->opsAgent
|
|
);
|
|
|
|
self::assertSame(0, $response->body()['imported']);
|
|
self::assertCount(1, $this->hosts->allForAgent((int) $this->opsAgent['id']));
|
|
}
|
|
}
|