<?phpnamespace App\Feature\Pools;use App\Model\Connection;use App\Xapi\Fake\FakeHostTransport;use App\Xapi\Rrd;use App\Xapi\StreamTransport;use App\Xapi\TlsStream;use App\Xapi\Transport;use App\Xapi\XapiClient;use JumPHP\Kernel\Engine;/** * The roads of one connection to the hosts of its pool other than the master. Each XAPI host only has the performance figures of * itself and of the VMs it runs (/rrd_updates?host=true), so the Performance and Search tabs ask each host, at the address * HostAddresses::reachable() gives, by the connection's own method (HTTP, HTTPS, an SSH tunnel through the master: `ssh -W * <host>:80`, or the demo pool). The pool's session is valid on every member. One transport per host, closed on disconnect. */final class HostLinks{ /** @var array<string, array{address: string, transport: Transport}> host ref => its road */ private array $links = []; /** @var array<string, Rrd> host ref => its last figures */ private array $rrd = []; /** @var array<string, bool> a request running */ private array $busy = []; private ?Rrd $master = null; /** Asks every other host of the pool for its figures; $changed runs when an answer arrived. */ public function fetch(Connection $c, \Closure $changed): void { $session = $c->client?->session; $master = (string)$c->cache->master(); if ($session === null || !$c->isConnected()) { return; } foreach (array_keys($c->cache->hosts()) as $host) { if ($host === $master || !empty($this->busy[$host])) { continue; } try { $transport = $this->transport($c, $host); } catch (\Throwable) { continue; } $client = new XapiClient($transport); $client->session = $session; $this->busy[$host] = true; $client->rrdUpdates(time() - 600, true)->then(function (Rrd $rrd) use ($host, $changed): void { $this->busy[$host] = false; $this->rrd[$host] = $rrd; $changed(); }, function (\Throwable $e) use ($host): void { $this->busy[$host] = false; }); } } /** The master's figures (when given) and every host's, in one: the columns hold uuids, they do not overlap. */ public function merged(?Rrd $master = null): Rrd { if ($master !== null) { $this->master = $master; } $m = $this->master; $all = $m === null ? new Rrd() : new Rrd($m->columns, $m->end, $m->step); foreach ($this->rrd as $rrd) { $all->columns += $rrd->columns; $all->end = max($all->end, $rrd->end); } return $all; } /** @return array<string, string> host ref => the address its figures come from (tests, the status) */ public function addresses(): array { return array_map(fn (array $l) => $l['address'], $this->links); } /** The road to a host: made once, again when its address changed. */ public function transport(Connection $c, string $host): Transport { $address = HostAddresses::reachable($c, $host); if (isset($this->links[$host]) && $this->links[$host]['address'] === $address) { return $this->links[$host]['transport']; } if (isset($this->links[$host])) { $this->links[$host]['transport']->close(); } $p = $c->profile; $engine = Engine::get(); $transport = match ($p->method) { 'demo' => new FakeHostTransport($c->fake() ?? throw new \RuntimeException('No demo pool.'), (string)$c->cache->field($host, 'uuid', ''), $address), 'https' => TlsStream::available() ? StreamTransport::tls($address, $p->port()) : throw new \RuntimeException('No TLS here.'), 'ssh' => StreamTransport::tcp($engine->ssh()->via(array_filter(['sshHost' => $p->host, 'sshPort' => $p->sshPort, 'sshUser' => $p->sshUser, 'sshKey' => $p->sshKey !== '' ? $p->sshKey : null, 'sshPassword' => $p->sshKey === '' ? $p->password : null, 'sshKeys' => $engine->sshKeyring()->all() ?: null], fn ($v) => $v !== null && $v !== '')), $address, $p->port(), "SSH {$p->host} → $address"), default => StreamTransport::tcp($engine->tcp(), $address, $p->port(), "HTTP $address:{$p->port()}"), }; $this->links[$host] = ['address' => $address, 'transport' => $transport]; return $transport; } public function poll(float $dt): void { foreach ($this->links as $link) { $link['transport']->poll($dt); } } /** The roads close (disconnect, addresses changed); the figures go with them. */ public function close(): void { foreach ($this->links as $link) { $link['transport']->close(); } $this->links = []; $this->rrd = []; $this->busy = []; $this->master = null; }}