Skip to content

Commit

Permalink
[RTC-405] Resolve FQDNs in DNS cluster strategy (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 authored Jan 3, 2024
1 parent 58d333a commit 99f58dc
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 179 deletions.
87 changes: 0 additions & 87 deletions .env.sample

This file was deleted.

3 changes: 0 additions & 3 deletions config/ci.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,5 @@ config :jellyfish, ip: {127, 0, 0, 1}, port: 4002, server_api_token: "developmen
# you can enable the server option below.
config :jellyfish, JellyfishWeb.Endpoint, server: false

# Print only warnings and errors during test
config :logger, level: :warning

# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
2 changes: 2 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ config :jellyfish,

config :ex_aws, :http_client, Jellyfish.Component.HLS.HTTPoison

config :bundlex, :disable_precompiled_os_deps, apps: [:membrane_h264_ffmpeg_plugin, :ex_libsrtp]

import_config "#{config_env()}.exs"
3 changes: 0 additions & 3 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ config :jellyfish, JellyfishWeb.Endpoint,
debug_errors: true,
watchers: []

# Do not include metadata nor timestamps in development logs
config :logger, :console, level: :info, format: "[$level] $message\n"

# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20
Expand Down
4 changes: 0 additions & 4 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,5 @@ config :jellyfish,
# run the server automatically when using prod release
config :jellyfish, JellyfishWeb.Endpoint, server: true

config :bundlex, :disable_precompiled_os_deps, apps: [:membrane_h264_ffmpeg_plugin]

# Runtime production configuration, including reading
# of environment variables, is done on config/runtime.exs.

config :bundlex, :disable_precompiled_os_deps, apps: [:membrane_h264_ffmpeg_plugin]
37 changes: 3 additions & 34 deletions docker-compose-dns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ x-jellyfish-template: &jellyfish-template
environment: &jellyfish-environment
JF_SERVER_API_TOKEN: "development"
JF_DIST_ENABLED: "true"
JF_DIST_MODE: "name"
JF_DIST_STRATEGY_NAME: "DNS"
restart: on-failure

Expand All @@ -22,66 +23,34 @@ services:
- .:/app
- /app/_build
- /app/deps
networks:
dns-network:
aliases:
- dnsmasq
depends_on:
- app1
- app2
- dnsmasq

app1:
<<: *jellyfish-template
environment:
<<: *jellyfish-environment
JF_HOST: "localhost:4001"
JF_PORT: 4001
JF_DIST_NODE_NAME: [email protected]
JF_DIST_QUERY: app.dns-network
ports:
- 4001:4001
networks:
dns-network:
ipv4_address: 172.28.1.2
default:
aliases:
- app.dns-network
dns:
- 172.28.1.4:5353

app2:
container_name: name
<<: *jellyfish-template
environment:
<<: *jellyfish-environment
JF_HOST: "localhost:4002"
JF_PORT: 4002
JF_DIST_NODE_NAME: [email protected]
JF_DIST_QUERY: app.dns-network
ports:
- 4002:4002
networks:
dns-network:
# Register IP under alias
ipv4_address: 172.28.1.3
default:
aliases:
- app.dns-network
dns:
- 172.28.1.4:5353

dnsmasq:
image: andyshinn/dnsmasq:2.78
volumes:
- /etc/resolv.conf:/etc/resolv.dnsmasq.conf
command: -d -q --no-hosts --no-resolv --no-poll --server 127.0.0.11 --listen-address 0.0.0.0 --port 5353 --log-queries --log-facility=- --address=/./127.0.0.11
networks:
dns-network:
ipv4_address: 172.28.1.4
aliases:
- dnsmasq

networks:
dns-network:
ipam:
config:
- subnet: 172.28.1.0/24
1 change: 0 additions & 1 deletion docker-compose-epmd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ x-jellyfish-template: &jellyfish-template
environment: &jellyfish-environment
JF_SERVER_API_TOKEN: "development"
JF_DIST_ENABLED: "true"
JF_DIST_MODE: "sname"
JF_DIST_NODES: "app@app1 app@app2"
restart: on-failure

Expand Down
8 changes: 4 additions & 4 deletions lib/jellyfish/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ defmodule Jellyfish.Application do
defp config_distribution(dist_config) do
ensure_epmd_started!()

# Release always starts in a distributed mode
# so we have to start a node only in development.
# See env.sh.eex for more information.
# When running JF not in a cluster and using
# mix release, it starts in the distributed mode
# automatically
unless Node.alive?() do
case Node.start(dist_config[:node_name]) do
case Node.start(dist_config[:node_name], dist_config[:mode]) do
{:ok, _} ->
:ok

Expand Down
82 changes: 66 additions & 16 deletions lib/jellyfish/config_reader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,31 @@ defmodule Jellyfish.ConfigReader do
def read_dist_config() do
dist_enabled? = read_boolean("JF_DIST_ENABLED")
dist_strategy = System.get_env("JF_DIST_STRATEGY_NAME")
node_name_value = System.get_env("JF_DIST_NODE_NAME")
mode_value = System.get_env("JF_DIST_MODE", "sname")
cookie_value = System.get_env("JF_DIST_COOKIE", "jellyfish_cookie")

{:ok, hostname} = :inet.gethostname()
node_name_value = System.get_env("JF_DIST_NODE_NAME", "jellyfish@#{hostname}")

cookie = parse_cookie(cookie_value)
mode = parse_mode(mode_value)

cond do
is_nil(dist_enabled?) or not dist_enabled? ->
[enabled: false, strategy: nil, node_name: nil, cookie: nil, strategy_config: nil]
[
enabled: false,
mode: nil,
strategy: nil,
node_name: nil,
cookie: nil,
strategy_config: nil
]

dist_strategy == "NODES_LIST" or is_nil(dist_strategy) ->
do_read_nodes_list_config(node_name_value, cookie_value)
do_read_nodes_list_config(node_name_value, cookie, mode)

dist_strategy == "DNS" ->
do_read_dns_config(node_name_value, cookie_value)
do_read_dns_config(node_name_value, cookie, mode)

true ->
raise """
Expand All @@ -143,15 +156,10 @@ defmodule Jellyfish.ConfigReader do
end
end

defp do_read_nodes_list_config(node_name_value, cookie_value) do
defp do_read_nodes_list_config(node_name_value, cookie, mode) do
nodes_value = System.get_env("JF_DIST_NODES", "")

unless node_name_value do
raise "JF_DIST_ENABLED has been set but JF_DIST_NODE_NAME remains unset."
end

node_name = parse_node_name(node_name_value)
cookie = parse_cookie(cookie_value)
nodes = parse_nodes(nodes_value)

if nodes == [] do
Expand All @@ -163,33 +171,49 @@ defmodule Jellyfish.ConfigReader do

[
enabled: true,
mode: mode,
strategy: Cluster.Strategy.Epmd,
node_name: node_name,
cookie: cookie,
strategy_config: [hosts: nodes]
]
end

defp do_read_dns_config(node_name_value, cookie_value) do
unless node_name_value do
raise "JF_DIST_ENABLED has been set but JF_DIST_NODE_NAME remains unset."
end
defp do_read_dns_config(_node_name_value, _cookie, :shortnames) do
raise "DNS strategy requires `JF_DIST_MODE` to be `name`"
end

defp do_read_dns_config(node_name_value, cookie, mode) do
node_name = parse_node_name(node_name_value)
cookie = parse_cookie(cookie_value)

query_value = System.get_env("JF_DIST_QUERY")

unless query_value do
raise "JF_DIST_QUERY is required by DNS strategy"
end

[node_basename, _ip_addres_or_fqdn | []] = String.split(node_name_value, "@")
[node_basename, hostname | []] = String.split(node_name_value, "@")

node_name =
if is_ip_address(hostname) do
node_name
else
Logger.info(
"Resolving hostname part of JF node name as DNS cluster strategy requires IP address."
)

resolved_hostname = resolve_hostname(hostname)

Logger.info("Resolved #{hostname} as #{resolved_hostname}")

String.to_atom("#{node_basename}@#{resolved_hostname}")
end

polling_interval = parse_polling_interval()

[
enabled: true,
mode: mode,
strategy: Cluster.Strategy.DNSPoll,
node_name: node_name,
cookie: cookie,
Expand Down Expand Up @@ -230,4 +254,30 @@ defmodule Jellyfish.ConfigReader do
raise "`JF_DIST_POLLING_INTERVAL` must be a positivie integer. Got: #{env_value}"
end
end

defp parse_mode("name"), do: :longnames
defp parse_mode("sname"), do: :shortnames
defp parse_mode(other), do: raise("Invalid JF_DIST_MODE. Expected sname or name, got: #{other}")

defp is_ip_address(hostname) do
case :inet.parse_address(String.to_charlist(hostname)) do
{:ok, _} -> true
{:error, _} -> false
end
end

defp resolve_hostname(hostname) do
case :inet.gethostbyname(String.to_charlist(hostname)) do
{:ok, {:hostent, _, _, _, _, h_addr_list}} ->
# Assert there is at least one ip address.
# In other case, this is fatal error
[h | _] = h_addr_list
"#{:inet.ntoa(h)}"

{:error, reason} ->
raise """
Couldn't resolve #{hostname}, reason: #{reason}.
"""
end
end
end
22 changes: 10 additions & 12 deletions rel/env.sh.eex
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
# This is to have a unified way of configuring Jellyfish distribution
# in both development and production environments
if [ "$JF_DIST_ENABLED" == "true" ]; then
export RELEASE_NODE=${JF_DIST_NODE_NAME:-$RELEASE_NODE}
export RELEASE_COOKIE=${JF_DIST_COOKIE:-${RELEASE_COOKIE:-jellyfish_cookie}}

if [ "$RELEASE_NODE" == "" ]; then
echo "JF_DIST_ENABLED has been set but JF_DIST_NODE_NAME remains unset"
exit 1
fi

# Use longnames by default.
# They are required to form a cluster across diffrent machines.
# In development, we also use longnames (when starting a new node).
export RELEASE_DISTRIBUTION=${JF_DIST_MODE:-${RELEASE_DISTRIBUTION:-name}}
# If Jellyfish is meant to be run in a cluster,
# leave node setup to the Elixir code where
# we do extra steps for DNS strategy to determine actual node name.
#
# We also try to read RELEASE_DISTRIBUTION env var
# to allow for calling `remote` command with JF_DIST_ENABLED set.
# In other case, RELEASE_DISTRIBUTION will always be set to none.
# This is a little hack.
# We can get rid of it once we move to the reverse DNS strategy.
export RELEASE_DISTRIBUTION=${RELEASE_DISTRIBUTION:-none}

# We only set min and max ports for start and daemon commands.
# In other case, when someone wants to expose only one port
Expand Down
Loading

0 comments on commit 99f58dc

Please sign in to comment.