Files
XC_VM/tests/Unit/ServerSyncOpensslExtraCommandTest.php
T
Claude 33d1aa7188 fix(servers): add server:sync-openssl-extra to repair mismatched LBs
Load balancers added before provisioning shipped config/openssl_extra
run on the built-in OPENSSL_EXTRA while their main holds a random one,
so they reject every token the main mints for them. server:diagnose now
shows this; nothing could fix it short of a manual copy.

server:sync-openssl-extra <server_id>|--all [--force], run on the main,
queues one root signal carrying the main's value for each streaming LB
whose published fingerprint differs. Nodes that publish none (not
updated), offline nodes and nodes already in sync need --force; proxies
and the main are never sent it. A main without config/openssl_extra
runs on the default like its LBs, and the command sends nothing.

The LB's cron:root_signals handles 'set_openssl_extra' in its own case
(ignored on the main, whose value keys hmac_keys and image names) with
OpensslExtra::install: it writes the value it replaces to
openssl_extra.prev, accepted for 600 s by Encryption::readToken, then
swaps openssl_extra in through a 0600 temporary file owned like
config/, so php-fpm never sees a file it cannot read. php-fpm picks the
new value up on its next request. Installing the value already in use
keeps an earlier .prev. server:install now also clears a stale .prev,
and server:diagnose names the command in its mismatch report.

The value sits in the signals table until the LB's root cron deletes the
row, about a minute; a legacy LB reads that database in full anyway.
The command is MAIN-only: it is stripped from the LB archive and listed
in verify-lb-archive.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQHeypQpfAXTdCysmi7b6a
2026-09-25 00:34:59 +00:00

57 lines
2.2 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
use XcVm\Cli\Commands\ServerSyncOpensslExtraCommand;
use XcVm\Core\Config\OpensslExtra;
/**
* server:sync-openssl-extra — queues MAIN's OPENSSL_EXTRA for the streaming
* LBs that report another one. A node that does not report a fingerprint runs
* a build whose root cron cannot apply the value, and an offline node would
* leave it waiting in the signals table, so both need --force; proxies and the
* MAIN never get it.
*/
final class ServerSyncOpensslExtraCommandTest extends TestCase {
private function lb(array $rOverride = []): array {
return array_merge([
'id' => 2,
'is_main' => 0,
'server_type' => 0,
'server_online' => true,
'server_hardware' => json_encode(['cores' => 4, OpensslExtra::HARDWARE_KEY => OpensslExtra::fingerprint('lb-extra')]),
], $rOverride);
}
private function skip(array $rServer, bool $rForce): ?string {
return ServerSyncOpensslExtraCommand::skipReason($rServer, OpensslExtra::fingerprint('main-extra'), $rForce);
}
public function testAnLbThatReportsAnotherValueIsSynced(): void {
$this->assertNull($this->skip($this->lb(), false));
$this->assertNull($this->skip($this->lb(), true));
}
public function testAnLbInSyncIsSyncedOnlyWhenForced(): void {
$rInSync = $this->lb(['server_hardware' => json_encode([OpensslExtra::HARDWARE_KEY => OpensslExtra::fingerprint('main-extra')])]);
$this->assertSame('already in sync', $this->skip($rInSync, false));
$this->assertNull($this->skip($rInSync, true));
}
public function testAnOutdatedOrOfflineLbIsSyncedOnlyWhenForced(): void {
$rOutdated = $this->lb(['server_hardware' => json_encode(['cores' => 4])]);
$rOffline = $this->lb(['server_online' => false]);
$this->assertStringStartsWith('unknown (node not updated)', (string) $this->skip($rOutdated, false));
$this->assertNull($this->skip($rOutdated, true));
$this->assertStringStartsWith('offline', (string) $this->skip($rOffline, false));
$this->assertNull($this->skip($rOffline, true));
}
public function testProxiesAndTheMainAreNeverSynced(): void {
$this->assertNotNull($this->skip($this->lb(['server_type' => 1]), true));
$this->assertNotNull($this->skip($this->lb(['is_main' => 1]), true));
}
}