Skip to content

Commit

Permalink
[RTC-526] Add env vars to configure allowed components (#189)
Browse files Browse the repository at this point in the history
* [RTC-526] Add env vars to configure allowed components
* lint
* Changes after review
* Print allowed components at JF startup
  • Loading branch information
sgfn authored May 16, 2024
1 parent 582b21f commit d3fb68f
Show file tree
Hide file tree
Showing 22 changed files with 283 additions and 114 deletions.
7 changes: 5 additions & 2 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ host =
other -> other
end

components_used = ConfigReader.read_components_used()
sip_used? = Jellyfish.Component.SIP in components_used

config :jellyfish,
jwt_max_age: 24 * 3600,
media_files_path:
Expand All @@ -34,8 +37,8 @@ config :jellyfish,
metrics_port: ConfigReader.read_port("JF_METRICS_PORT") || 9568,
dist_config: ConfigReader.read_dist_config(),
webrtc_config: ConfigReader.read_webrtc_config(),
sip_config: ConfigReader.read_sip_config(),
recording_config: ConfigReader.read_recording_config(),
components_used: components_used,
sip_config: ConfigReader.read_sip_config(sip_used?),
s3_config: ConfigReader.read_s3_config(),
git_commit: ConfigReader.read_git_commit()

Expand Down
2 changes: 2 additions & 0 deletions lib/jellyfish/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ defmodule Jellyfish.Application do
dist_config = Application.fetch_env!(:jellyfish, :dist_config)
webrtc_config = Application.fetch_env!(:jellyfish, :webrtc_config)
git_commit = Application.get_env(:jellyfish, :git_commit)
components_used = Application.get_env(:jellyfish, :components_used)

Logger.info("Starting Jellyfish v#{Jellyfish.version()} (#{git_commit})")
Logger.info("Distribution config: #{inspect(Keyword.delete(dist_config, :cookie))}")
Logger.info("WebRTC config: #{inspect(webrtc_config)}")
Logger.info("Allowed components: #{inspect(components_used)}")

children =
[
Expand Down
7 changes: 0 additions & 7 deletions lib/jellyfish/component/recording.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,8 @@ defmodule Jellyfish.Component.Recording do

@impl true
def config(%{engine_pid: engine} = options) do
recording_config = Application.fetch_env!(:jellyfish, :recording_config)
sink_config = Application.fetch_env!(:jellyfish, :s3_config)

unless recording_config[:recording_used?],
do:
raise("""
Recording components can only be used if JF_RECORDING_USED environmental variable is set to \"true\"
""")

with {:ok, serialized_opts} <- serialize_options(options, Options.schema()),
result_opts <- parse_subscribe_mode(serialized_opts),
{:ok, credentials} <- get_credentials(serialized_opts, sink_config),
Expand Down
11 changes: 1 addition & 10 deletions lib/jellyfish/component/sip.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,7 @@ defmodule Jellyfish.Component.SIP do

@impl true
def config(%{engine_pid: engine} = options) do
sip_config = Application.fetch_env!(:jellyfish, :sip_config)

external_ip =
if sip_config[:sip_used?] do
Application.fetch_env!(:jellyfish, :sip_config)[:sip_external_ip]
else
raise """
SIP components can only be used if JF_SIP_USED environmental variable is set to \"true\"
"""
end
external_ip = Application.fetch_env!(:jellyfish, :sip_config)[:sip_external_ip]

with {:ok, serialized_opts} <- serialize_options(options, Options.schema()) do
endpoint_spec = %SIP{
Expand Down
31 changes: 20 additions & 11 deletions lib/jellyfish/config_reader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -128,36 +128,45 @@ defmodule Jellyfish.ConfigReader do
end
end

def read_sip_config() do
sip_used? = read_boolean("JF_SIP_USED")
def read_components_used() do
components_used = System.get_env("JF_COMPONENTS_USED") || ""

components_used
|> String.split(" ", trim: true)
|> Enum.map(fn type ->
case Jellyfish.Component.parse_type(type) do
{:ok, component} ->
component

{:error, :invalid_type} ->
raise(
"Invalid value in JF_COMPONENTS_USED. Expected a lowercase component name, got: #{type}"
)
end
end)
end

def read_sip_config(sip_used?) do
sip_ip = System.get_env("JF_SIP_IP") || ""

cond do
sip_used? != true ->
[
sip_used?: false,
sip_external_ip: nil
]

ip_address?(sip_ip) ->
[
sip_used?: true,
sip_external_ip: sip_ip
]

true ->
raise """
JF_SIP_USED has been set to true but incorrect IP address was provided as `JF_SIP_IP`
SIP components are allowed, but incorrect IP address was provided as `JF_SIP_IP`
"""
end
end

def read_recording_config() do
[
recording_used?: read_boolean("JF_RECORDING_USED") != false
]
end

def read_s3_config() do
credentials = [
bucket: System.get_env("JF_S3_BUCKET"),
Expand Down
8 changes: 8 additions & 0 deletions lib/jellyfish/peer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ defmodule Jellyfish.Peer do
end
end

@spec to_string!(module()) :: String.t()
def to_string!(peer) do
case peer do
WebRTC -> "webrtc"
_other -> raise "Invalid peer"
end
end

@spec new(peer(), map()) :: {:ok, t()} | {:error, term()}
def new(type, options) do
id = UUID.uuid4()
Expand Down
6 changes: 0 additions & 6 deletions lib/jellyfish/peer/webrtc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ defmodule Jellyfish.Peer.WebRTC do

@impl true
def config(options) do
if not Application.fetch_env!(:jellyfish, :webrtc_config)[:webrtc_used?] do
raise(
"WebRTC peers can only be used if JF_WEBRTC_USED environmental variable is not set to \"false\""
)
end

with {:ok, valid_opts} <- OpenApiSpex.cast_value(options, ApiSpec.Peer.WebRTC.schema()) do
handshake_options = [
client_mode: false,
Expand Down
47 changes: 36 additions & 11 deletions lib/jellyfish/room.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ defmodule Jellyfish.Room do
end

@spec add_peer(id(), Peer.peer(), map()) ::
{:ok, Peer.t()} | :error | {:error, :reached_peers_limit}
{:ok, Peer.t()}
| :error
| {:error, {:peer_disabled_globally, String.t()} | {:reached_peers_limit, String.t()}}
def add_peer(room_id, peer_type, options \\ %{}) do
GenServer.call(registry_id(room_id), {:add_peer, peer_type, options})
end
Expand All @@ -92,7 +94,19 @@ defmodule Jellyfish.Room do
@spec add_component(id(), Component.component(), map()) ::
{:ok, Component.t()}
| :error
| {:error, :incompatible_codec | :reached_components_limit_hls}
| {:error,
{:component_disabled_globally, String.t()}
| :incompatible_codec
| {:reached_components_limit, String.t()}
| :file_does_not_exist
| :bad_parameter_framerate_for_audio
| :invalid_framerate
| :invalid_file_path
| :unsupported_file_type
| {:missing_parameter, term()}
| :missing_s3_credentials
| :overriding_credentials
| :overriding_path_prefix}
def add_component(room_id, component_type, options \\ %{}) do
GenServer.call(registry_id(room_id), {:add_component, component_type, options})
end
Expand Down Expand Up @@ -142,15 +156,22 @@ defmodule Jellyfish.Room do

@impl true
def handle_call({:add_peer, peer_type, override_options}, _from, state) do
with false <- State.reached_peers_limit?(state),
with :ok <- State.check_peer_allowed(peer_type, state),
options <- State.generate_peer_options(state, override_options),
{:ok, peer} <- Peer.new(peer_type, options) do
state = State.add_peer(state, peer)

{:reply, {:ok, peer}, state}
else
true ->
{:reply, {:error, :reached_peers_limit}, state}
{:error, :peer_disabled_globally} ->
type = Peer.to_string!(peer_type)
Logger.warning("Unable to add peer: #{type} peers are disabled globally")
{:reply, {:error, {:peer_disabled_globally, type}}, state}

{:error, :reached_peers_limit} ->
type = Peer.to_string!(peer_type)
Logger.warning("Unable to add peer: Reached #{type} peers limit")
{:reply, {:error, {:reached_peers_limit, type}}, state}

{:error, reason} ->
Logger.warning("Unable to add peer: #{inspect(reason)}")
Expand Down Expand Up @@ -214,7 +235,7 @@ defmodule Jellyfish.Room do
options
)

with :ok <- check_component_allowed(component_type, state),
with :ok <- State.check_component_allowed(component_type, state),
{:ok, component} <- Component.new(component_type, options) do
state = State.put_component(state, component)

Expand All @@ -226,27 +247,31 @@ defmodule Jellyfish.Room do

{:reply, {:ok, component}, state}
else
{:error, :component_disabled_globally} ->
type = Component.to_string!(component_type)
Logger.warning("Unable to add component: #{type} components are disabled globally")
{:reply, {:error, {:component_disabled_globally, type}}, state}

{:error, :incompatible_codec} ->
Logger.warning("Unable to add component: incompatible codec")
{:reply, {:error, :incompatible_codec}, state}

{:error, :reached_components_limit} ->
type = Component.to_string!(component_type)
Logger.warning("Unable to add component: reached components limit #{type}")
Logger.warning("Unable to add component: reached #{type} components limit")
{:reply, {:error, {:reached_components_limit, type}}, state}

{:error, :file_does_not_exist} ->
Logger.warning("Unable to add component: file does not exist")
{:reply, {:error, :file_does_not_exist}, state}

{:error, :bad_parameter_framerate_for_audio} ->
Logger.warning("Attempted to set framerate for audio component which is not supported.")

Logger.warning("Unable to add component: attempted to set framerate for audio component")
{:reply, {:error, :bad_parameter_framerate_for_audio}, state}

{:error, {:invalid_framerate, passed_framerate}} ->
Logger.warning(
"Invalid framerate value: #{passed_framerate}. It has to be a positivie integer."
"Unable to add component: expected framerate to be a positive integer, got: #{passed_framerate}"
)

{:reply, {:error, :invalid_framerate}, state}
Expand All @@ -256,7 +281,7 @@ defmodule Jellyfish.Room do
{:reply, {:error, :invalid_file_path}, state}

{:error, :unsupported_file_type} ->
Logger.warning("Unable to add component: unsupported file path")
Logger.warning("Unable to add component: unsupported file type")
{:reply, {:error, :unsupported_file_type}, state}

{:error, {:missing_parameter, name}} ->
Expand Down
61 changes: 44 additions & 17 deletions lib/jellyfish/room/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -382,33 +382,33 @@ defmodule Jellyfish.Room.State do
)
end

def check_component_allowed(type, %{
config: %{video_codec: video_codec},
components: components
})
when type in [HLS, Recording] do
@spec check_peer_allowed(Peer.peer(), t()) ::
:ok | {:error, :peer_disabled_globally | :reached_peers_limit}
def check_peer_allowed(Peer.WebRTC, state) do
cond do
video_codec != :h264 ->
{:error, :incompatible_codec}
not Application.fetch_env!(:jellyfish, :webrtc_config)[:webrtc_used?] ->
{:error, :peer_disabled_globally}

component_already_present?(type, components) ->
{:error, :reached_components_limit}
Enum.count(state.peers) >= state.config.max_peers ->
{:error, :reached_peers_limit}

true ->
:ok
end
end

def check_component_allowed(RTSP, %{config: %{video_codec: video_codec}}) do
# Right now, RTSP component can only publish H264, so there's no point adding it
# to a room which allows another video codec, e.g. VP8
if video_codec == :h264,
do: :ok,
else: {:error, :incompatible_codec}
@spec check_component_allowed(Component.component(), t()) ::
:ok
| {:error,
:component_disabled_globally | :incompatible_codec | :reached_components_limit}
def check_component_allowed(type, state) do
if type in Application.fetch_env!(:jellyfish, :components_used) do
check_component_allowed_in_room(type, state)
else
{:error, :component_disabled_globally}
end
end

def check_component_allowed(_component_type, _state), do: :ok

@spec get_endpoint_id_type(state :: t(), endpoint_id :: endpoint_id()) ::
:peer_id | :component_id
def get_endpoint_id_type(state, endpoint_id) do
Expand All @@ -435,6 +435,33 @@ defmodule Jellyfish.Room.State do
def validate_subscription_mode(%{properties: %{subscribe_mode: :manual}}), do: :ok
def validate_subscription_mode(_not_properties), do: {:error, :invalid_component_type}

defp check_component_allowed_in_room(type, %{
config: %{video_codec: video_codec},
components: components
})
when type in [HLS, Recording] do
cond do
video_codec != :h264 ->
{:error, :incompatible_codec}

component_already_present?(type, components) ->
{:error, :reached_components_limit}

true ->
:ok
end
end

defp check_component_allowed_in_room(RTSP, %{config: %{video_codec: video_codec}}) do
# Right now, RTSP component can only publish H264, so there's no point adding it
# to a room which allows another video codec, e.g. VP8
if video_codec == :h264,
do: :ok,
else: {:error, :incompatible_codec}
end

defp check_component_allowed_in_room(_component_type, _state), do: :ok

defp component_already_present?(type, components),
do: components |> Map.values() |> Enum.any?(&(&1.type == type))

Expand Down
Loading

0 comments on commit d3fb68f

Please sign in to comment.