Prepara l'architettura multi-nodo (MediaMTX+ffmpeg) con assignment URL per sessione, admin di provision/drain e provider Cloud/DNS astratti verso mltv-stream.net. Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
2.8 KiB
Ruby
80 lines
2.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "rails_helper"
|
|
|
|
RSpec.describe Streams::NodeRegistry do
|
|
def with_env(vars)
|
|
previous = vars.keys.index_with { |k| ENV[k] }
|
|
vars.each { |k, v| ENV[k] = v }
|
|
yield
|
|
ensure
|
|
previous.each { |k, v| v.nil? ? ENV.delete(k) : ENV[k] = v }
|
|
end
|
|
|
|
let(:home_env) do
|
|
{
|
|
"MEDIAMTX_API_URL" => "http://mtx-home:9997",
|
|
"MEDIAMTX_RTMP_URL" => "rtmp://ingest-home.example:1935",
|
|
"HLS_PUBLIC_URL" => "https://ingest-home.example/hls",
|
|
"MEDIAMTX_INTERNAL_RTMP_URL" => "rtmp://mtx-home:1935",
|
|
"MEDIAMTX_HLS_URL" => "http://mtx-home:8888",
|
|
"STREAM_NODE_HOME_MAX_PUBLISHERS" => "2"
|
|
}
|
|
end
|
|
|
|
it "creates home node from ENV" do
|
|
with_env(home_env) do
|
|
node = described_class.ensure_home_from_env!
|
|
expect(node.slug).to eq("home")
|
|
expect(node.rtmp_base_url).to eq("rtmp://ingest-home.example:1935")
|
|
expect(node.api_base_url).to eq("http://mtx-home:9997")
|
|
expect(node.max_publishers).to eq(2)
|
|
expect(node.status).to eq("ready")
|
|
end
|
|
end
|
|
|
|
it "allocates the least loaded ready node" do
|
|
with_env(home_env) do
|
|
home = described_class.ensure_home_from_env!
|
|
cloud = StreamNode.create!(
|
|
slug: "ingest-01",
|
|
hostname: "ingest-01.mltv-stream.net",
|
|
role: "cloud",
|
|
status: "ready",
|
|
provider: "hetzner",
|
|
rtmp_base_url: "rtmp://ingest-01.mltv-stream.net:1935",
|
|
hls_base_url: "https://ingest-01.mltv-stream.net/hls",
|
|
api_base_url: "http://10.0.0.2:9997",
|
|
max_publishers: 2,
|
|
max_relays: 2
|
|
)
|
|
|
|
user = User.create!(email: "n@example.com", name: "N", password: "Password123", role: "coach")
|
|
club = Club.create!(name: "C", sport: "volleyball")
|
|
team = club.teams.create!(name: "T", sport: "volleyball", slug: "t-node")
|
|
match = team.matches.create!(opponent_name: "X", scheduled_at: 1.hour.from_now)
|
|
|
|
StreamSession.create!(
|
|
match: match, user: user, platform: "matchlivetv", status: "live", stream_node: home
|
|
)
|
|
|
|
expect(described_class.allocate!).to eq(cloud)
|
|
end
|
|
end
|
|
|
|
it "raises when no capacity remains" do
|
|
with_env(home_env.merge("STREAM_NODE_HOME_MAX_PUBLISHERS" => "1")) do
|
|
home = described_class.ensure_home_from_env!
|
|
user = User.create!(email: "full@example.com", name: "F", password: "Password123", role: "coach")
|
|
club = Club.create!(name: "Full", sport: "volleyball")
|
|
team = club.teams.create!(name: "T", sport: "volleyball", slug: "t-full")
|
|
match = team.matches.create!(opponent_name: "X", scheduled_at: 1.hour.from_now)
|
|
StreamSession.create!(
|
|
match: match, user: user, platform: "matchlivetv", status: "live", stream_node: home
|
|
)
|
|
|
|
expect { described_class.allocate! }.to raise_error(Streams::NodeRegistry::NoCapacityError)
|
|
end
|
|
end
|
|
end
|