-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce room_id generator (#206)
- Loading branch information
1 parent
9b57b35
commit 07a89a9
Showing
15 changed files
with
258 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
defmodule Fishjam.FeatureFlags do | ||
@moduledoc """ | ||
Module to resolve any feature flags, since we are not using database we can't use fun_with_flags. | ||
Because of that we base feature flags on the environment variables mainly. | ||
""" | ||
|
||
@doc """ | ||
Flag for disabling custom room names, which will be replaced by the generated based on the node name. | ||
Introduced: 28/05/2024 | ||
Removal: Once we move on to generated room_ids permanently. | ||
""" | ||
def custom_room_name_disabled?, | ||
do: Application.get_env(:fishjam, :feature_flags)[:custom_room_name_disabled] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
defmodule Fishjam.Room.ID do | ||
@moduledoc """ | ||
This module allows to generate room_id with the node name in it. | ||
""" | ||
|
||
@type id :: String.t() | ||
|
||
@doc """ | ||
Based on the Room ID determines to which node it belongs to. | ||
Returns an error if the node isn't present in the cluster. | ||
DISCLAIMER: | ||
It should be used only with room_ids generated by generate/0, otherwise it can raise. | ||
""" | ||
@spec determine_node(id()) :: | ||
{:ok, node()} | {:error, :invalid_room_id} | {:error, :invalid_node} | ||
def determine_node(room_id) do | ||
with {:ok, room_id} <- validate_room_id(room_id), | ||
node_name <- decode_node_name(room_id), | ||
true <- node_present_in_cluster?(node_name) do | ||
{:ok, node_name} | ||
else | ||
{:error, :invalid_room_id} -> {:error, :invalid_room_id} | ||
false -> {:error, :invalid_node} | ||
end | ||
end | ||
|
||
@doc """ | ||
Room ID structure resembles the one of the UUID, although the last part is replaced by encoded node name. | ||
## Example: | ||
For node_name: "fishjam@10.0.0.1" | ||
iex> Fishjam.Room.ID.generate() | ||
"da2e-4a75-95ff-776bad2caf04-666973686a616d4031302e302e302e31" | ||
""" | ||
@spec generate() :: id() | ||
def generate do | ||
UUID.uuid4() | ||
|> String.split("-") | ||
|> Enum.take(-4) | ||
|> Enum.concat([encoded_node_name()]) | ||
|> Enum.join("-") | ||
end | ||
|
||
@doc """ | ||
Depending on feature flag "custom_room_name_disabled" | ||
- uses `generate/0` to generate room_id | ||
or | ||
- parses the `room_id` provided by the client | ||
""" | ||
@spec generate(nil | String.t()) :: {:ok, id()} | {:error, :invalid_room_id} | ||
def generate(nil), do: generate(UUID.uuid4()) | ||
|
||
def generate(room_id) do | ||
if Fishjam.FeatureFlags.custom_room_name_disabled?() do | ||
{:ok, generate()} | ||
else | ||
validate_room_id(room_id) | ||
end | ||
end | ||
|
||
defp decode_node_name(room_id) do | ||
room_id | ||
|> String.split("-") | ||
|> Enum.take(-1) | ||
|> Enum.at(0) | ||
|> Base.decode16!(case: :lower) | ||
|> String.to_existing_atom() | ||
end | ||
|
||
defp encoded_node_name do | ||
Node.self() | ||
|> Atom.to_string() | ||
|> Base.encode16(case: :lower) | ||
end | ||
|
||
defp node_present_in_cluster?(node) do | ||
node in [Node.self() | Node.list()] | ||
end | ||
|
||
defp validate_room_id(room_id) when is_binary(room_id) do | ||
if Regex.match?(~r/^[a-zA-Z0-9-_]+$/, room_id) do | ||
{:ok, room_id} | ||
else | ||
{:error, :invalid_room_id} | ||
end | ||
end | ||
|
||
defp validate_room_id(_room_id), do: {:error, :invalid_room_id} | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
defmodule Fishjam.RPCClient do | ||
@moduledoc """ | ||
This modules serves as simple RPC client to communicate with other nodes in cluster. | ||
It utilizes the Enhanced version of Erlang `rpc` called `erpc`. | ||
Enhanced version allows to distinguish between returned value, raised exceptions, and other errors. | ||
`erpc` also has better performance and scalability than the original rpc implementation. | ||
""" | ||
require Logger | ||
|
||
@doc """ | ||
Executes mfa on a remote node. | ||
Function returns {:ok, result} tuple only if the execution succeeded. | ||
In case of any exceptions we are catching them logging and returning simple :error atom. | ||
""" | ||
@spec call(node(), module(), atom(), term(), timeout()) :: {:ok, term()} | :error | ||
def call(node, module, function, args, timeout \\ :infinity) do | ||
try do | ||
{:ok, :erpc.call(node, module, function, args, timeout)} | ||
rescue | ||
e -> | ||
Logger.warning("RPC call to node #{node} failed with exception: #{inspect(e)}") | ||
:error | ||
end | ||
end | ||
|
||
@doc """ | ||
Multicall to all nodes in the cluster, including this node. | ||
It filters out any errors or exceptions from return so you may end up with empty list. | ||
""" | ||
@spec multicall(module(), atom(), term(), timeout()) :: list(term) | ||
def multicall(module, function, args, timeout \\ :infinity) do | ||
nodes() | ||
|> :erpc.multicall(module, function, args, timeout) | ||
|> handle_result() | ||
end | ||
|
||
defp handle_result(result) when is_list(result) do | ||
result | ||
|> Enum.reduce([], fn | ||
{:ok, res}, acc -> | ||
[res | acc] | ||
|
||
{status, res}, acc -> | ||
Logger.warning( | ||
"RPC multicall to one of the nodes failed with status: #{inspect(status)} because of: #{inspect(res)}" | ||
) | ||
|
||
acc | ||
end) | ||
end | ||
|
||
defp nodes, do: [Node.self() | Node.list()] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
defmodule Fishjam.Room.IDTest do | ||
use ExUnit.Case | ||
|
||
alias Fishjam.Room.ID, as: Subject | ||
|
||
describe "determine_node/1" do | ||
test "resolves node name from the provided room_id" do | ||
node_name = Node.self() | ||
room_id = Subject.generate() | ||
|
||
assert {:ok, node_name} == Subject.determine_node(room_id) | ||
end | ||
|
||
test "returns error if node is not detected in cluster" do | ||
invalid_node = :invalid_node |> Atom.to_string() |> Base.encode16(case: :lower) | ||
invalid_room_id = "room-id-#{invalid_node}" | ||
assert {:error, :invalid_node} == Subject.determine_node(invalid_room_id) | ||
end | ||
end | ||
|
||
describe "generate/0" do | ||
test "room_id last part is based on the node name" do | ||
room1_id = Subject.generate() | ||
room2_id = Subject.generate() | ||
|
||
node_part_from_room1 = room1_id |> String.split("-") |> Enum.take(-1) | ||
node_part_from_room2 = room2_id |> String.split("-") |> Enum.take(-1) | ||
|
||
assert node_part_from_room1 == node_part_from_room2 | ||
end | ||
|
||
test "generated room_id has 5 parts" do | ||
room_id = Subject.generate() | ||
assert room_id |> String.split("-") |> length() == 5 | ||
end | ||
end | ||
|
||
describe "generate/1" do | ||
setup do | ||
Application.delete_env(:fishjam, :feature_flags) | ||
end | ||
|
||
test "executes generate/0 when feature flag is enabled and generates random id" do | ||
Application.put_env(:fishjam, :feature_flags, custom_room_name_disabled: true) | ||
refute {:ok, "custom_room_name"} == Subject.generate("custom_room_name") | ||
end | ||
|
||
test "parses custom room name when feature flag is disabled" do | ||
assert {:ok, "custom_room_name"} == Subject.generate("custom_room_name") | ||
end | ||
|
||
test "returns error when custom room doesn't meet naming criteria" do | ||
assert {:error, :invalid_room_id} = Subject.generate("invalid_characters//??$@!") | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.