mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-31 19:03:43 +01:00
Phase 5 Task 1. A loopback stream (streams_servers.parent_id > 0) on a serve-only target is now classified kind "loopback" carrying only the parent id — not left "other". buildSupervisorSpec signals loopback in portable mode (it already detects parent_id > 0 and must not compose the relay URL, which carries live_streaming_pass) and StreamReplica::runtimeUpgrade emits StreamRuntimeDirective::loopback(parent_id). The Go agent builds the node-local relay URL http://127.0.0.1:31290/relay/<k>/<id>.ts itself. No relay secret reaches the directive (source stays null); a non-serve-only target is untouched (the composer runs only on the serve_only-gated page).
133 lines
5.2 KiB
PHP
133 lines
5.2 KiB
PHP
<?php
|
|
|
|
use XcVm\Core\Cluster\StreamRuntimeDirective;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* @covers \XcVm\Core\Cluster\StreamRuntimeDirective
|
|
*
|
|
* The runtime directive MAIN embeds in a stream's replica record so a
|
|
* serve-only LB can hand the stream to its fanout without PHP (Phase 4a). It
|
|
* is the cross-repo contract the Go agent applies verbatim: kind="proxy" with
|
|
* a node-agnostic source for a direct-proxy restream, kind="other" otherwise.
|
|
* The source is FanoutClient::buildSource WITHOUT the node-local ffmpeg path —
|
|
* the agent fills that from its own config (spec D3).
|
|
*/
|
|
final class StreamRuntimeDirectiveTest extends TestCase {
|
|
|
|
public function testBuildsProxyDirectiveForDirectProxyStream() {
|
|
$row = [
|
|
'direct_proxy' => 1,
|
|
'stream_source' => json_encode(['http://a/1.ts', 'http://b/2.m3u8']),
|
|
];
|
|
$args = [
|
|
'user_agent' => ['value' => 'MyPlayer/1.0', 'argument_default_value' => 'VLC/3.0'],
|
|
'proxy' => ['value' => '1.2.3.4:8080'],
|
|
'cookie' => ['value' => 'sid=xyz'],
|
|
];
|
|
|
|
$directive = StreamRuntimeDirective::build($row, $args);
|
|
|
|
$want = json_decode(file_get_contents(__DIR__ . '/fixtures/runtime_directive_proxy.json'), true);
|
|
$this->assertSame($want, $directive);
|
|
}
|
|
|
|
public function testProxySourceCarriesNoFfmpegPath() {
|
|
$row = ['direct_proxy' => 1, 'stream_source' => json_encode(['http://a/1.ts'])];
|
|
|
|
$directive = StreamRuntimeDirective::build($row, []);
|
|
|
|
// The node-local ffmpeg path is the agent's to fill, never MAIN's (D3).
|
|
$this->assertArrayNotHasKey('ffmpeg', $directive['source']);
|
|
}
|
|
|
|
public function testNonDirectProxyStreamIsOther() {
|
|
$row = ['direct_proxy' => 0, 'stream_source' => json_encode(['http://a/1.ts'])];
|
|
|
|
$directive = StreamRuntimeDirective::build($row, []);
|
|
|
|
$this->assertSame('other', $directive['kind']);
|
|
$this->assertNull($directive['source']);
|
|
}
|
|
|
|
public function testDirectProxyWithNoSourceStaysProxyWithEmptyUrls() {
|
|
// A direct-proxy stream whose source is unset is still kind=proxy; the
|
|
// agent skips it (empty urls) rather than MAIN guessing a different kind.
|
|
$row = ['direct_proxy' => 1, 'stream_source' => ''];
|
|
|
|
$directive = StreamRuntimeDirective::build($row, []);
|
|
|
|
$this->assertSame('proxy', $directive['kind']);
|
|
$this->assertSame([], $directive['source']['urls']);
|
|
}
|
|
|
|
public function testTranscodeDirectiveWhenPortableSpecGiven() {
|
|
// A non-proxy stream the target node can supervise: the portable Spec the
|
|
// caller built rides in `spec`, kind=transcode, source null (4b).
|
|
$row = ['direct_proxy' => 0, 'stream_source' => json_encode(['http://a/1.ts'])];
|
|
$spec = [
|
|
'sources' => [['label' => 's', 'cmd' => '{ffmpeg_cpu} -i x {streams}7_.m3u8']],
|
|
'pid_path' => '{streams}7_.pid', 'server_id' => '{server_id}', 'encrypt' => false,
|
|
];
|
|
|
|
$directive = StreamRuntimeDirective::build($row, [], $spec);
|
|
|
|
$this->assertSame('transcode', $directive['kind']);
|
|
$this->assertNull($directive['source']);
|
|
$this->assertSame($spec, $directive['spec']);
|
|
}
|
|
|
|
public function testNonProxyWithoutSpecIsOther() {
|
|
// null Spec (delay/created_live/resolver — buildSupervisorSpec returned null): "other", no spec key.
|
|
$directive = StreamRuntimeDirective::build(['direct_proxy' => 0, 'stream_source' => json_encode(['http://a/1.ts'])], [], null);
|
|
|
|
$this->assertSame('other', $directive['kind']);
|
|
$this->assertArrayNotHasKey('spec', $directive);
|
|
}
|
|
|
|
public function testProxyNeverCarriesATranscodeSpec() {
|
|
// direct_proxy wins even if a Spec is passed: a proxy is served by the
|
|
// puller, never the supervisor — no spec key.
|
|
$directive = StreamRuntimeDirective::build(['direct_proxy' => 1, 'stream_source' => json_encode(['http://a/1.ts'])], [], ['sources' => []]);
|
|
|
|
$this->assertSame('proxy', $directive['kind']);
|
|
$this->assertArrayNotHasKey('spec', $directive);
|
|
}
|
|
|
|
public function testUpgradeOtherWithSpecBecomesTranscode() {
|
|
// A serve-only target composes a portable Spec after the directive was
|
|
// first classified "other" (StreamReplica::page); upgrade attaches it.
|
|
$spec = ['sources' => [['cmd' => '{ffmpeg_cpu} -i x']], 'encrypt' => false];
|
|
|
|
$this->assertSame(
|
|
['kind' => 'transcode', 'source' => null, 'spec' => $spec],
|
|
StreamRuntimeDirective::upgrade(['kind' => 'other', 'source' => null], $spec)
|
|
);
|
|
}
|
|
|
|
public function testUpgradeOtherWithNullSpecStaysOther() {
|
|
// buildSupervisorSpec returned null (not supervisable) → directive unchanged.
|
|
$directive = ['kind' => 'other', 'source' => null];
|
|
|
|
$this->assertSame($directive, StreamRuntimeDirective::upgrade($directive, null));
|
|
}
|
|
|
|
public function testUpgradeLeavesProxyUntouched() {
|
|
// A proxy is served by the puller; a Spec must never displace it.
|
|
$directive = ['kind' => 'proxy', 'source' => ['urls' => ['http://x']]];
|
|
|
|
$this->assertSame($directive, StreamRuntimeDirective::upgrade($directive, ['sources' => []]));
|
|
}
|
|
|
|
public function testLoopbackDirectiveCarriesTheParentId() {
|
|
// A serve-only target whose streams_servers.parent_id > 0 relays the stream
|
|
// from that parent node. MAIN cannot compose the relay URL (it carries the
|
|
// node-local relay key), so the directive only names the kind and parent;
|
|
// the Go agent builds http://127.0.0.1:31290/relay/<k>/<id>.ts itself.
|
|
$this->assertSame(
|
|
['kind' => 'loopback', 'source' => null, 'parent_id' => 7],
|
|
StreamRuntimeDirective::loopback(7)
|
|
);
|
|
}
|
|
}
|