Files
MatchLiveTv/backend/spec/services/streams/node_registry_spec.rb
T
eminuxandCursor 4c5af99efa Allinea slate Cloud a AAC 48k mono e preferisce home in allocate.
Senza questo il telefono (mono 48k) veniva rifiutato sui nodi Hetzner e lo warm spare rubava le sessioni a home.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 18:27:13 +02:00

134 lines
5.0 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
before do
StreamSession.where.not(stream_node_id: nil).update_all(stream_node_id: nil, status: "ended", ended_at: Time.current)
StreamNode.where.not(slug: Streams::NodeRegistry::HOME_SLUG).delete_all
StreamNode.find_by(slug: Streams::NodeRegistry::HOME_SLUG)&.update!(max_publishers: 2, status: "ready")
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 "does not reset home drain/offline/error status on ensure" do
with_env(home_env) do
home = described_class.ensure_home_from_env!
home.update!(status: "draining")
expect(described_class.ensure_home_from_env!.status).to eq("draining")
expect { described_class.allocate! }.to raise_error(Streams::NodeRegistry::NoCapacityError)
end
end
it "prefers home while it still has free slots even if cloud is idle" do
with_env(home_env) do
home = described_class.ensure_home_from_env!
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
)
expect(described_class.allocate!).to eq(home)
end
end
it "allocates the least loaded cloud node when home is full" 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
)
cloud_busy = StreamNode.create!(
slug: "ingest-02",
hostname: "ingest-02.mltv-stream.net",
role: "cloud",
status: "ready",
provider: "hetzner",
rtmp_base_url: "rtmp://ingest-02.mltv-stream.net:1935",
hls_base_url: "https://ingest-02.mltv-stream.net/hls",
api_base_url: "http://10.0.0.3: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)
match2 = team.matches.create!(opponent_name: "Y", scheduled_at: 2.hours.from_now)
# Riempie home (max 2)
StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "live", stream_node: home)
StreamSession.create!(match: match2, user: user, platform: "matchlivetv", status: "live", stream_node: home)
# Un publisher già su ingest-02 → least-loaded = ingest-01
StreamSession.create!(
match: team.matches.create!(opponent_name: "Z", scheduled_at: 3.hours.from_now),
user: user, platform: "matchlivetv", status: "live", stream_node: cloud_busy
)
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