PL
games/xencenter-xenadmin-xcp-jumphp / src / Feature / Pools / PoolActions.php · commit fb1b7c9
<?phpnamespace App\Feature\Pools;use App\Form\HostAddressesForm;use App\Form\MainForm;use App\Form\NewPoolForm;use App\Model\Chain;use App\Model\Connection;use App\Model\LogEntry;use App\Model\Session;use JumPHP\Async\Task;use JumPHP\Kernel\Engine;/** * What the pool commands do, with XAPI's calls as XenCenter makes them: * - New Pool: pool.set_name_label (and _description) on the chosen master's own pool, then each other server joins it; * - a join: Async.pool.join(master_address, user, password) called ON THE JOINING SERVER, through its own connection; once done, *   that connection is left (the server answers HOST_IS_SLAVE now: it shows under the pool); * - Remove Server from Pool: Async.pool.eject(host) on the pool; Make this server the pool master: Async.pool.designate_new_master. */final class PoolActions{    /** A command of PoolCommands: a dialog to open ([class, params]), or null when it ran. @return array{0: class-string, 1: array<string, mixed>}|null */    public static function command(MainForm $form, string $id, ?Connection $tc, string $target): ?array    {        $session = $form->session;        if ($id === 'pool.new') {            if (PoolCommands::standalone($session) === []) {                $session->log('New Pool', 'XenCenter', null, LogEntry::FAILED, 'Connect a server that is in no pool first.');                return null;            }            return [NewPoolForm::class, NewPoolForm::paramsForNew($session)];        }        if ($tc === null || !$tc->isConnected()) {            return null;        }        $name = $tc->cache->name($target);        switch (true) {            case $id === 'pool.addServer':                return [NewPoolForm::class, NewPoolForm::paramsForAdd($session, $tc)];            case $id === 'pool.addresses':                return [HostAddressesForm::class, HostAddressesForm::paramsFor($tc)];            case str_starts_with($id, 'pool.joinTo:'):                $pool = $session->connections[(int)substr($id, 12)] ?? null;                if ($pool !== null) {                    self::join($form, $pool, $tc)->then(fn () => null, fn () => null);                }                return null;            case $id === 'host.removeFromPool':                $tc->run("Removing server '$name' from pool '" . $tc->name() . "'", 'pool.eject', [$target], $target)->then(fn () => null, fn () => null);                return null;            case $id === 'host.designateMaster':                $tc->run("Making '$name' the master of pool '" . $tc->name() . "'", 'pool.designate_new_master', [$target], (string)$tc->cache->poolRef())->then(function () use ($form, $tc): void {                    if ($tc->profile->method !== 'demo') {                        // the old master answers HOST_IS_SLAVE now: connecting again follows it to the new one                        Engine::get()->gameLoop->addTimer(8.0, fn () => $form->connect($tc));                    }                }, fn () => null);                return null;        }        return null;    }    /** What NewPoolForm and HostAddressesForm answered: true when it was theirs. */    public static function resume(MainForm $form, array $result): bool    {        $session = $form->session;        if (isset($result['newPool'])) {            $p = (array)$result['newPool'];            $master = self::byKey($session, (string)$p['master']);            if ($master !== null) {                self::create($form, $master, (string)$p['name'], (string)($p['description'] ?? ''), self::members($session, (array)$p['members']));            }            return true;        }        if (isset($result['joinPool'])) {            $p = (array)$result['joinPool'];            $pool = self::byKey($session, (string)$p['pool']);            if ($pool !== null) {                Chain::run(array_map(fn (Connection $s) => fn () => self::join($form, $pool, $s), self::members($session, (array)$p['members'])))->then(fn () => null, fn () => null);            }            return true;        }        if (isset($result['hostAddresses'])) {            $p = (array)$result['hostAddresses'];            $c = self::byKey($session, (string)$p['connection']);            if ($c !== null) {                $c->profile->hostAddresses = HostAddresses::clean((array)$p['addresses']);                $c->hostLinks()->close();           // the roads to the hosts are made again at the new addresses                $session->save();                $session->log('Connection addresses of ' . $c->name() . ' saved', $c->name());                $form->refreshFigures();            }            return true;        }        return false;    }    /** New Pool: the master's pool gets its name, then the other servers join it one after the other. @param list<Connection> $members */    public static function create(MainForm $form, Connection $master, string $name, string $description, array $members): Task    {        $pool = (string)$master->cache->poolRef();        $steps = [fn () => $master->call('pool.set_name_label', $pool, $name)];        if (trim($description) !== '') {            $steps[] = fn () => $master->call('pool.set_name_description', $pool, $description);        }        $steps[] = function () use ($form, $name): void {            $form->session->log("Created pool '$name'", $name);        };        foreach ($members as $s) {            $steps[] = fn () => self::join($form, $master, $s, $name);        }        return Chain::run($steps)->then(fn () => null, fn () => null);    }    /** A server joins a pool: Async.pool.join on the server's own connection, given the master's address and credentials. */    public static function join(MainForm $form, Connection $pool, Connection $server, ?string $poolName = null): Task    {        $poolName ??= $pool->name();        $host = (string)$server->cache->master();        $serverName = $server->name();        $done = new Task();        $server->run("Adding server '$serverName' to pool '$poolName'", 'pool.join', [$pool->profile->host, $pool->profile->user, $pool->profile->password], $host)->then(            function () use ($form, $server, $pool, $serverName, $poolName, $done): void {                $form->session->log("Server '$serverName' joined pool '$poolName'", $poolName);                // it is a pool member now: the pool's connection shows it, its own connection goes (as in XenCenter)                $form->forgetConnection($server);                $form->navigate((string)$pool->cache->poolRef());                $done->settle(true);            },            fn (\Throwable $e) => $done->settle(null, $e),        );        return $done;    }    private static function byKey(Session $session, string $key): ?Connection    {        foreach ($session->connections as $c) {            if ($c->profile->key() === $key) {                return $c;            }        }        return null;    }    /** @param list<mixed> $keys @return list<Connection> */    private static function members(Session $session, array $keys): array    {        return array_values(array_filter(array_map(fn ($k) => self::byKey($session, (string)$k), $keys)));    }}