Accedi
IT
<?phpnamespace App\Model;/** * The objects of one pool as XAPI gives them (records by class and ref), kept up to date by event.from, with the questions the * windows ask: the VMs of a host, the storage of a VM, the pool master... */final class Cache{    public const NULL = 'OpaqueRef:NULL';    /** @var array<string, array<string, array<string, mixed>>> class => ref => record */    public array $records = [];    /** @var array<string, string> ref => class */    private array $classOf = [];    /** bumped at each change: the windows redraw what depends on it */    public int $version = 0;    /** @param array<string, mixed> $record */    public function put(string $class, string $ref, array $record): void    {        $class = strtolower($class);        $this->records[$class][$ref] = $record;        $this->classOf[$ref] = $class;        $this->version++;    }    public function remove(string $class, string $ref): void    {        unset($this->records[strtolower($class)][$ref], $this->classOf[$ref]);        $this->version++;    }    /** @return array<string, mixed>|null */    public function get(string $ref): ?array    {        $class = $this->classOf[$ref] ?? null;        return $class === null ? null : ($this->records[$class][$ref] ?? null);    }    public function classOf(string $ref): ?string    {        return $this->classOf[$ref] ?? null;    }    /** @return array<string, array<string, mixed>> */    public function all(string $class): array    {        return $this->records[$class] ?? [];    }    public function field(string $ref, string $field, mixed $default = null): mixed    {        return $this->get($ref)[$field] ?? $default;    }    public function byName(string $class, string $name): ?string    {        foreach ($this->all($class) as $ref => $record) {            if (($record['name_label'] ?? '') === $name) {                return $ref;            }        }        return null;    }    public function byUuid(string $class, string $uuid): ?string    {        foreach ($this->all($class) as $ref => $record) {            if (($record['uuid'] ?? '') === $uuid) {                return $ref;            }        }        return null;    }    // the pool --------------------------------------------------------------------------------------------------------    public function poolRef(): ?string    {        return array_key_first($this->all('pool'));    }    public function master(): ?string    {        $pool = $this->poolRef();        return $pool === null ? null : (string)$this->field($pool, 'master');    }    /** A pool of one host shows as the host itself (as in XenCenter): the pool has no name. */    public function isRealPool(): bool    {        $pool = $this->poolRef();        return $pool !== null && (count($this->all('host')) > 1 || trim((string)$this->field($pool, 'name_label')) !== '');    }    /** @return array<string, array<string, mixed>> hosts sorted by name */    public function hosts(): array    {        $hosts = $this->all('host');        uasort($hosts, fn ($a, $b) => strnatcasecmp((string)$a['name_label'], (string)$b['name_label']));        return $hosts;    }    // VMs ---------------------------------------------------------------------------------------------------------------    /** @param array<string, mixed> $vm */    public static function isRealVm(array $vm): bool    {        return empty($vm['is_a_template']) && empty($vm['is_a_snapshot']) && empty($vm['is_control_domain']);    }    /** @return array<string, array<string, mixed>> the VMs (no template, snapshot or dom0), sorted by name */    public function vms(): array    {        $vms = array_filter($this->all('vm'), [self::class, 'isRealVm']);        uasort($vms, fn ($a, $b) => strnatcasecmp((string)$a['name_label'], (string)$b['name_label']));        return $vms;    }    /** @return array<string, array<string, mixed>> VMs running (or paused) on a host */    public function vmsOn(string $host): array    {        return array_filter($this->vms(), fn ($vm) => ($vm['resident_on'] ?? '') === $host);    }    /**     * Halted / suspended VMs: under the host they are bound to (affinity, or local storage), else under the pool.     *     * @return array<string, array<string, mixed>>     */    public function vmsHomedOn(?string $host): array    {        return array_filter($this->vms(), function (array $vm, string $ref) use ($host): bool {            if (isset($this->all('host')[(string)($vm['resident_on'] ?? '')])) {                return false;            }            return $this->homeOf($ref) === $host;        }, ARRAY_FILTER_USE_BOTH);    }    /** The host a halted VM belongs to: the one of its local disks, else none (pool level). */    public function homeOf(string $vm): ?string    {        foreach ($this->disksOf($vm) as $disk) {            $sr = (string)($disk['vdi']['SR'] ?? '');            if ($sr !== '' && !$this->field($sr, 'shared', true)) {                $host = $this->hostOfLocalSr($sr);                if ($host !== null) {                    return $host;                }            }        }        if (!$this->isRealPool()) {            return $this->master();        }        return null;    }    public function hostOfLocalSr(string $sr): ?string    {        foreach ((array)$this->field($sr, 'PBDs', []) as $pbd) {            $host = (string)$this->field((string)$pbd, 'host', '');            if ($host !== '' && $this->get($host) !== null) {                return $host;            }        }        return null;    }    /** @return array<string, array<string, mixed>> */    public function templates(): array    {        $out = array_filter($this->all('vm'), fn ($vm) => !empty($vm['is_a_template']) && empty($vm['is_a_snapshot']));        uasort($out, fn ($a, $b) => strnatcasecmp((string)$a['name_label'], (string)$b['name_label']));        return $out;    }    /** @return array<string, array<string, mixed>> newest first */    public function snapshotsOf(string $vm): array    {        $out = [];        foreach ((array)$this->field($vm, 'snapshots', []) as $ref) {            if (($record = $this->get((string)$ref)) !== null) {                $out[(string)$ref] = $record;            }        }        uasort($out, fn ($a, $b) => strcmp((string)$b['snapshot_time'], (string)$a['snapshot_time']));        return $out;    }    /** @return list<array{ref: string, vbd: array<string, mixed>, vdi: array<string, mixed>|null}> by position */    public function disksOf(string $vm, bool $withCd = false): array    {        $out = [];        foreach ((array)$this->field($vm, 'VBDs', []) as $ref) {            $vbd = $this->get((string)$ref);            if ($vbd === null || (!$withCd && ($vbd['type'] ?? '') === 'CD')) {                continue;            }            $vdi = $this->get((string)($vbd['VDI'] ?? ''));            $out[] = ['ref' => (string)$ref, 'vbd' => $vbd, 'vdi' => $vdi];        }        usort($out, fn ($a, $b) => (int)$a['vbd']['userdevice'] <=> (int)$b['vbd']['userdevice']);        return $out;    }    /** @return list<array{ref: string, vif: array<string, mixed>}> */    public function vifsOf(string $vm): array    {        $out = [];        foreach ((array)$this->field($vm, 'VIFs', []) as $ref) {            if (($vif = $this->get((string)$ref)) !== null) {                $out[] = ['ref' => (string)$ref, 'vif' => $vif];            }        }        usort($out, fn ($a, $b) => (int)$a['vif']['device'] <=> (int)$b['vif']['device']);        return $out;    }    /** @return array<string, array<string, mixed>> the SRs a host sees (PBDs) */    public function srsOf(string $host): array    {        $out = [];        foreach ((array)$this->field($host, 'PBDs', []) as $pbd) {            $sr = (string)$this->field((string)$pbd, 'SR', '');            if (($record = $this->get($sr)) !== null) {                $out[$sr] = $record;            }        }        uasort($out, fn ($a, $b) => strnatcasecmp((string)$a['name_label'], (string)$b['name_label']));        return $out;    }    /** @return array<string, array<string, mixed>> all the SRs, shared ones first */    public function srs(): array    {        $out = array_filter($this->all('sr'), fn ($sr) => ($sr['type'] ?? '') !== 'udev' || !empty($sr['VDIs']));        uasort($out, fn ($a, $b) => [(int)!$a['shared'], strtolower((string)$a['name_label'])] <=> [(int)!$b['shared'], strtolower((string)$b['name_label'])]);        return $out;    }    /** @return array<string, array<string, mixed>> the networks guests can use */    public function networks(): array    {        $out = array_filter($this->all('network'), fn ($n) => ($n['other_config']['is_host_internal_management_network'] ?? '') !== 'true');        uasort($out, fn ($a, $b) => strnatcasecmp((string)$a['name_label'], (string)$b['name_label']));        return $out;    }    /** @return array<string, array<string, mixed>> the PIFs (network cards) of a host, physical first */    public function pifsOf(string $host): array    {        $out = [];        foreach ((array)$this->field($host, 'PIFs', []) as $ref) {            if (($pif = $this->get((string)$ref)) !== null) {                $out[(string)$ref] = $pif;            }        }        uasort($out, fn ($a, $b) => [(int)$a['VLAN'] !== -1, $a['device']] <=> [(int)$b['VLAN'] !== -1, $b['device']]);        return $out;    }    /** @return array<string, array<string, mixed>> newest first */    public function tasks(): array    {        $out = $this->all('task');        uasort($out, fn ($a, $b) => strcmp((string)$b['created'], (string)$a['created']));        return $out;    }    /** @return array<string, array<string, mixed>> alerts, newest first */    public function messages(): array    {        $out = $this->all('message');        uasort($out, fn ($a, $b) => strcmp((string)$b['timestamp'], (string)$a['timestamp']));        return $out;    }    /** The object an alert is about (obj_uuid + cls). */    public function objectOfMessage(array $message): ?string    {        $class = strtolower((string)($message['cls'] ?? ''));        return $this->byUuid($class === 'vm' ? 'vm' : $class, (string)($message['obj_uuid'] ?? ''));    }    // figures -----------------------------------------------------------------------------------------------------------    /** @return array{total: float, free: float, used: float} bytes of a host */    public function hostMemory(string $host): array    {        $metrics = $this->get((string)$this->field($host, 'metrics', '')) ?? [];        $total = (float)($metrics['memory_total'] ?? 0);        $free = (float)($metrics['memory_free'] ?? 0);        return ['total' => $total, 'free' => $free, 'used' => max(0, $total - $free)];    }    public function vmMetrics(string $vm): array    {        return $this->get((string)$this->field($vm, 'metrics', '')) ?? [];    }    public function guestMetrics(string $vm): array    {        return $this->get((string)$this->field($vm, 'guest_metrics', '')) ?? [];    }    /** The IP addresses the guest tools report. @return list<string> */    public function ipsOf(string $vm): array    {        $networks = (array)($this->guestMetrics($vm)['networks'] ?? []);        $out = [];        foreach ($networks as $key => $ip) {            if (preg_match('#^\d+/(ip|ipv4/\d+|ipv6/\d+)$#', (string)$key) && !in_array($ip, $out, true)) {                $out[] = (string)$ip;            }        }        return $out;    }    public function name(string $ref): string    {        $record = $this->get($ref);        if ($record === null) {            return '';        }        $name = (string)($record['name_label'] ?? $record['device'] ?? $record['name'] ?? '');        if (($this->classOf($ref) === 'sr') && ($record['other_config']['i18n-key'] ?? '') === 'local-storage') {            $host = $this->hostOfLocalSr($ref);            return $host !== null && count($this->all('host')) > 1 ? 'Local storage on ' . $this->name($host) : 'Local storage';        }        return $name;    }}