Skip to content

Commit

Permalink
Always run distribution in long names mode (#2646)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatanklosko authored Jun 13, 2024
1 parent a3baf2a commit ba4e59f
Show file tree
Hide file tree
Showing 19 changed files with 60 additions and 192 deletions.
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,6 @@ The following environment variables can be used to configure Livebook on boot:
"standalone" (Elixir standalone), "attached:NODE:COOKIE" (Attached node)
or "embedded" (Embedded). Defaults to "standalone".

* `LIVEBOOK_DISTRIBUTION` - sets the node distribution for running Livebook in a
cluster. Must be "name" (long names) or "sname" (short names). Note that this
sets RELEASE_DISTRIBUTION if present when creating a release. Defaults to "sname".

* `LIVEBOOK_EPMDLESS` - if set to "true", it disables the usage of EPMD. This is
only supported within releases and defaults to true for the Desktop app.

Expand Down Expand Up @@ -308,9 +304,6 @@ such as:

* [the `PATH` environment variable](https://en.wikipedia.org/wiki/PATH_(variable))

* set `LIVEBOOK_DISTRIBUTION=name` to enable notebooks to communicate
with nodes in other machines

* or to configure the Erlang VM, for instance, by setting
`ERL_AFLAGS="-proto_dist inet6_tcp"` if you need Livebook to run over IPv6

Expand Down
6 changes: 0 additions & 6 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,3 @@ config :livebook,
agent_name: "chonky-cat"

config :livebook, Livebook.Apps.Manager, retry_backoff_base_ms: 0

# Use longnames when running tests in CI, so that no host resolution is required,
# see https://github.com/livebook-dev/livebook/pull/173#issuecomment-819468549
if System.get_env("CI") == "true" do
config :livebook, :node, {:longnames, :"[email protected]"}
end
2 changes: 1 addition & 1 deletion docs/deployment/clustering.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Clustering

If you plan to run several Livebook instances behind a load balancer, you need to enable clustering via the `LIVEBOOK_CLUSTER` environment variable in your Docker image. `LIVEBOOK_DISTRIBUTION` is automatically set to `name` if clustering is enabled.
If you plan to run several Livebook instances behind a load balancer, you need to enable clustering via the `LIVEBOOK_CLUSTER` environment variable in your Docker image.

Depending on the clustering strategy of your choice, you must set additional environment variables, oftentimes, at runtime. When using the Livebook Docker image, you can create a file at `/app/user/env.sh` that exports the necessary environment variables. This file is invoked right before booting Livebook.

Expand Down
2 changes: 1 addition & 1 deletion docs/use_cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ system you want to connect to. For example, to connect to a
you may start it as follows:

```shell
$ iex --sname phoenix-app --cookie secret -S mix phx.server
$ iex --name phoenix-app@127.0.0.1 --cookie secret -S mix phx.server
```

With this information in hand, you can query and automate tasks within
Expand Down
14 changes: 10 additions & 4 deletions lib/livebook.ex
Original file line number Diff line number Diff line change
Expand Up @@ -199,17 +199,23 @@ defmodule Livebook do
config :livebook, :cacertfile, cacertfile
end

if rewrite_on = Livebook.Config.rewrite_on!("LIVEBOOK_PROXY_HEADERS") do
config :livebook, :rewrite_on, rewrite_on
end
config :livebook, :rewrite_on, Livebook.Config.rewrite_on!("LIVEBOOK_PROXY_HEADERS")

config :livebook,
:cookie,
Livebook.Config.cookie!("LIVEBOOK_COOKIE") ||
Livebook.Config.cookie!("RELEASE_COOKIE") ||
Livebook.Utils.random_cookie()

if node = Livebook.Config.node!("LIVEBOOK_NODE", "LIVEBOOK_DISTRIBUTION") do
# TODO: remove in v1.0
if System.get_env("LIVEBOOK_DISTRIBUTION") == "sname" do
IO.warn(
~s/Ignoring LIVEBOOK_DISTRIBUTION=sname, because short names are no longer supported./,
[]
)
end

if node = Livebook.Config.node!("LIVEBOOK_NODE") do
config :livebook, :node, node
end

Expand Down
91 changes: 13 additions & 78 deletions lib/livebook/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ defmodule Livebook.Application do
else
ensure_epmd!()
ensure_distribution!()
validate_hostname_resolution!()
end

set_cookie()
Expand Down Expand Up @@ -164,9 +163,9 @@ defmodule Livebook.Application do

defp ensure_distribution!() do
unless Node.alive?() do
{type, name} = get_node_type_and_name()
node = get_node_name()

case Node.start(name, type) do
case Node.start(node, :longnames) do
{:ok, _} ->
:ok

Expand All @@ -179,88 +178,24 @@ defmodule Livebook.Application do
import Record
defrecordp :hostent, Record.extract(:hostent, from_lib: "kernel/include/inet.hrl")

# See https://github.com/livebook-dev/livebook/issues/302
defp validate_hostname_resolution!() do
unless Livebook.Config.longname() do
[nodename, hostname] = node() |> Atom.to_charlist() |> :string.split(~c"@")

# erl_epmd names do not support ipv6 resolution by default,
# unless inet6 is configured, so we attempt both.
gethostbyname =
with {:error, _} <- :inet.gethostbyname(hostname, :inet, :infinity),
{:error, _} <- :inet.gethostbyname(hostname, :inet6, :infinity),
do: :error

with {:ok, hostent(h_addr_list: [epmd_addr | _])} <- gethostbyname,
{:ok, nodenames} <- :erl_epmd.names(epmd_addr),
true <- List.keymember?(nodenames, nodename, 0) do
:ok
else
_ ->
hint =
cond do
is_nil(System.get_env("LIVEBOOK_DESKTOP")) ->
"""
* If you are using Livebook's CLI or from source, consider using longnames:
livebook server --name [email protected]
elixir --name [email protected] -S mix phx.server
"""

match?({:win32, _}, :os.type()) ->
path =
Path.join(
System.get_env("USERPROFILE", "%USERPROFILE%"),
".livebookdesktop.bat"
)

"""
* Configure your Livebook Desktop to use long names by creating a file at #{path} with:
set LIVEBOOK_DISTRIBUTION=name
set [email protected]
"""

true ->
path = Path.join(System.get_env("HOME", "~"), ".livebookdesktop.sh")

"""
* Configure your Livebook Desktop to use long names by creating a file at #{path} with:
export LIVEBOOK_DISTRIBUTION=name
export [email protected]
"""
end

Livebook.Config.abort!("""
Your hostname \"#{hostname}\" does not resolve to a loopback address (127.0.0.0/8), \
which indicates something wrong in your OS configuration, or EPMD is not running.
To address this issue, you might:
* Consult our Installation FAQ:
https://github.com/livebook-dev/livebook/wiki/Installation-FAQ
#{hint}\
* If the issue persists, please file a bug report
""")
end
end
end

defp set_cookie() do
cookie = Application.fetch_env!(:livebook, :cookie)
Node.set_cookie(cookie)
end

defp get_node_type_and_name() do
Application.get_env(:livebook, :node) || {:shortnames, random_short_name()}
defp get_node_name() do
Application.get_env(:livebook, :node) || random_long_name()
end

defp random_short_name() do
:"livebook_#{Livebook.Utils.random_short_id()}"
defp random_long_name() do
host =
if Livebook.Utils.proto_dist() == :inet6_tcp do
"::1"
else
"127.0.0.1"
end

:"livebook_#{Livebook.Utils.random_short_id()}@#{host}"
end

defp display_startup_info() do
Expand Down
30 changes: 4 additions & 26 deletions lib/livebook/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,6 @@ defmodule Livebook.Config do
]
end

@doc """
Returns the longname if the distribution mode is configured to use long names.
"""
@spec longname() :: binary() | nil
def longname() do
host = Livebook.Utils.node_host()

if host =~ "." do
host
end
end

@doc """
Returns the default runtime.
"""
Expand Down Expand Up @@ -532,21 +520,11 @@ defmodule Livebook.Config do
end

@doc """
Parses node and distribution type from env.
Parses node from env.
"""
def node!(node_env, distribution_env) do
case {System.get_env(node_env), System.get_env(distribution_env, "sname")} do
{nil, _} ->
nil

{name, "name"} ->
{:longnames, String.to_atom(name)}

{sname, "sname"} ->
{:shortnames, String.to_atom(sname)}

{_, other} ->
abort!(~s(#{distribution_env} must be one of "name" or "sname", got "#{other}"))
def node!(env) do
if node = System.get_env(env) do
String.to_atom(node)
end
end

Expand Down
9 changes: 4 additions & 5 deletions lib/livebook/runtime/elixir_standalone.ex
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ defmodule Livebook.Runtime.ElixirStandalone do
# Also note that we explicitly halt, just in case `System.no_halt(true)` is
# called within the runtime.
@child_node_eval_string """
{:ok, [[mode, node]]} = :init.get_argument(:livebook_current);\
{:ok, _} = :net_kernel.start(List.to_atom(node), %{name_domain: List.to_atom(mode)});\
{:ok, [[node]]} = :init.get_argument(:livebook_current);\
{:ok, _} = :net_kernel.start(List.to_atom(node), %{name_domain: :longnames});\
{:ok, [[parent_node, _port]]} = :init.get_argument(:livebook_parent);\
dist_port = :persistent_term.get(:livebook_dist_port, 0);\
init_ref = make_ref();\
Expand All @@ -164,8 +164,6 @@ defmodule Livebook.Runtime.ElixirStandalone do
parent_name = node()
parent_port = Livebook.EPMD.dist_port()

mode = if Livebook.Config.longname(), do: :longnames, else: :shortnames

epmdless_flags =
if parent_port != 0 do
"-epmd_module Elixir.Livebook.EPMD -start_epmd false -erl_epmd_port 0 "
Expand All @@ -181,8 +179,9 @@ defmodule Livebook.Runtime.ElixirStandalone do
# Enable ANSI escape codes as we handle them with HTML.
# Disable stdin, so that the system process never tries to read terminal input.
"+sbwt none +sbwtdcpu none +sbwtdio none +sssdio 128 -elixir ansi_enabled true -noinput " <>
"-proto_dist #{Livebook.Utils.proto_dist()} " <>
epmdless_flags <>
"-livebook_parent #{parent_name} #{parent_port} -livebook_current #{mode} #{node_name}",
"-livebook_parent #{parent_name} #{parent_port} -livebook_current #{node_name}",
# Add the location of Livebook.EPMD
"-pa",
Application.app_dir(:livebook, "priv/epmd"),
Expand Down
12 changes: 12 additions & 0 deletions lib/livebook/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ defmodule Livebook.Utils do
host
end

@doc """
Returns the protocol for Erlang distribution used by the current node.
"""
@spec proto_dist() :: :inet_tcp | :inet6_tcp | :inet_tls
def proto_dist() do
case :init.get_argument(:proto_dist) do
{:ok, [[~c"inet6_tcp"]]} -> :inet6_tcp
{:ok, [[~c"inet_tls"]]} -> :inet_tls
_ -> :inet_tcp
end
end

@doc """
Registers the given process under `name` for the time of `fun` evaluation.
"""
Expand Down
21 changes: 3 additions & 18 deletions lib/livebook_cli/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ defmodule LivebookCLI.Server do
Must be a valid IPv4 or IPv6 address
--name Sets a name for the app distributed node
-p, --port The port to start the web application on, defaults to 8080
--sname Sets a short name for the app distributed node
The --help option can be given to print this notice.
Expand Down Expand Up @@ -195,24 +194,15 @@ defmodule LivebookCLI.Server do
ip: :string,
name: :string,
port: :integer,
home: :string,
sname: :string
home: :string
]

@aliases [
p: :port
]

defp args_to_options(args) do
{opts, extra_args} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
validate_options!(opts)
{opts, extra_args}
end

defp validate_options!(opts) do
if Keyword.has_key?(opts, :name) and Keyword.has_key?(opts, :sname) do
raise "the provided --sname and --name options are mutually exclusive, please specify only one of them"
end
OptionParser.parse!(args, strict: @switches, aliases: @aliases)
end

defp opts_to_config([], config), do: config
Expand All @@ -230,14 +220,9 @@ defmodule LivebookCLI.Server do
])
end

defp opts_to_config([{:sname, sname} | opts], config) do
sname = String.to_atom(sname)
opts_to_config(opts, [{:livebook, :node, {:shortnames, sname}} | config])
end

defp opts_to_config([{:name, name} | opts], config) do
name = String.to_atom(name)
opts_to_config(opts, [{:livebook, :node, {:longnames, name}} | config])
opts_to_config(opts, [{:livebook, :node, name} | config])
end

defp opts_to_config([{:cookie, cookie} | opts], config) do
Expand Down
12 changes: 4 additions & 8 deletions lib/livebook_web/live/session_live/attached_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@ defmodule LivebookWeb.SessionLive.AttachedLive do
Make sure to give the node a name and a cookie, for example:
</p>
<div class="text-gray-700 markdown">
<%= if longname = Livebook.Config.longname() do %>
<pre><code>iex --name test@<%= longname %> --cookie mycookie -S mix</code></pre>
<% else %>
<pre><code>iex --sname test --cookie mycookie -S mix</code></pre>
<% end %>
<pre><code>iex --name <%= test_node() %> --cookie mycookie -S mix</code></pre>
</div>
<p class="text-gray-700">
Then enter the connection information below:
Expand All @@ -79,7 +75,7 @@ defmodule LivebookWeb.SessionLive.AttachedLive do
spellcheck="false"
>
<div class="flex flex-col space-y-4 mb-5">
<.text_field field={f[:name]} label="Name" placeholder={name_placeholder()} />
<.text_field field={f[:name]} label="Name" placeholder={test_node()} />
<.text_field field={f[:cookie]} label="Cookie" placeholder="mycookie" />
</div>
<.button type="submit" disabled={not @changeset.valid?}>
Expand Down Expand Up @@ -138,7 +134,7 @@ defmodule LivebookWeb.SessionLive.AttachedLive do
changeset.valid? and changeset.data == apply_changes(changeset)
end

defp name_placeholder do
if longname = Livebook.Config.longname(), do: "test@#{longname}", else: "test"
defp test_node() do
"test@#{Livebook.Utils.node_host()}"
end
end
3 changes: 1 addition & 2 deletions rel/app/env.bat.eex
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ set ELIXIR_ERL_OPTIONS=!ELIXIR_ERL_OPTIONS! -epmd_module Elixir.Livebook.EPMD -s
:continue
for /f "skip=1" %%X in ('wmic os get localdatetime') do if not defined TIMESTAMP set TIMESTAMP=%%X

if defined LIVEBOOK_DISTRIBUTION set RELEASE_DISTRIBUTION=!LIVEBOOK_DISTRIBUTION!
if not defined RELEASE_DISTRIBUTION set RELEASE_DISTRIBUTION=sname
set RELEASE_DISTRIBUTION="name"

if defined LIVEBOOK_NODE set RELEASE_NODE=!LIVEBOOK_NODE!
if not defined RELEASE_NODE set RELEASE_NODE=livebook-app-!TIMESTAMP:~8,6!-!RANDOM!
Expand Down
2 changes: 1 addition & 1 deletion rel/app/env.sh.eex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ if [ -f "$HOME/.livebookdesktop.sh" ]; then
. "$HOME/.livebookdesktop.sh"
fi

export RELEASE_DISTRIBUTION=${LIVEBOOK_DISTRIBUTION:-${RELEASE_DISTRIBUTION:-"sname"}}
export RELEASE_DISTRIBUTION="name"
export RELEASE_NODE=${LIVEBOOK_NODE:-${RELEASE_NODE:-"livebook-app-$(cat /dev/urandom | env LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)"}}
export RELEASE_MODE=interactive
export MIX_ARCHIVES="${RELEASE_ROOT}/vendor/archives"
Expand Down
Loading

0 comments on commit ba4e59f

Please sign in to comment.