Packages
altworx_runbox
0.1.2
25.0.0
24.0.0
23.1.0
23.0.0
22.2.0
22.1.0
22.0.0
21.2.0
21.1.2
21.1.1
21.1.0
21.0.0
20.0.0
19.0.0
18.0.0
17.2.0
17.1.0
17.0.1
17.0.0
16.2.0
16.1.0
16.0.0
15.0.0
14.1.0
14.0.1
14.0.0
13.0.3
13.0.2
13.0.1
13.0.0
12.1.0
12.0.0
11.0.1
11.0.0
10.0.0
9.0.0
8.0.0
7.0.1
7.0.0
6.0.0
5.0.0
4.0.0
3.0.0
2.1.0
2.0.0
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.1.3
0.1.2
0.1.1
0.1.0
Runbox is a library for running Altworx scenarios.
Current section
Files
Jump to
Current section
Files
lib/runbox/slave.ex
defmodule Runbox.Slave do
@moduledoc """
Utilities for starting a slave node with a scenario release and communicating
with the node.
"""
@doc """
Starts a slave node.
The `release_dir` is a directory with scenario release.
If `app_env` is set, given app environment variables are set on slave.
"""
@spec start(String.t(), [{Application.app(), [{Application.key(), Application.value()}]}]) ::
{:ok, node()} | {:error, term()}
def start(release_dir, app_env \\ []) do
host = slave_host() |> to_charlist()
slave_name = UUID.uuid4() |> to_charlist()
args =
[
"-setcookie #{Node.get_cookie()}",
~s(-boot "#{boot_file(release_dir)}"),
~s(-boot_var RELEASE_LIB "#{release_lib(release_dir)}"),
~s(-config "#{config_file(release_dir)}"),
"-mode embedded",
"-runbox mode slave",
erl_opt_toolbox_scenario_config_dir(),
erl_opt_logger_console()
]
|> Enum.join(" ")
|> to_charlist()
link_to = self()
# The progname has to be stated explicitly, otherwise it can be wrongly set
# to the name of the release script.
progname = 'erl'
case :slave.start(host, slave_name, args, link_to, progname) do
{:ok, slave} ->
with :ok <- check_runbox_started(slave),
:ok <- check_runbox_same_version(slave),
:ok <- check_runbox_scenario_app_configured(slave),
:ok <- put_app_env(slave, app_env) do
{:ok, slave}
else
{:error, _} = error ->
:ok = :slave.stop(slave)
error
end
{:error, _} = error ->
error
end
end
@doc """
Spawns a slave and executes the `fun`.
`fun` recives the slave node as a parameter. The slave is terminated after
the `fun` finishes.
"""
@spec with_slave(String.t(), (node() -> result)) :: {:ok, result} | {:error, term}
when result: any()
def with_slave(release_dir, fun) do
with {:ok, slave} <- start(release_dir) do
try do
{:ok, fun.(slave)}
after
:ok = :slave.stop(slave)
end
end
end
@doc """
Calls a function on the slave.
Calls MFA on the `slave`. See `:erpc.call/4` for details.
"""
@spec call(node(), module(), atom(), list()) :: term()
def call(slave, m, f, a) do
:erpc.call(slave, m, f, a)
end
defp check_runbox_started(slave) do
apps = call(slave, Application, :started_applications, [])
if Enum.any?(apps, &match?({:runbox, _, _}, &1)) do
:ok
else
{:error, :runbox_not_running_on_slave}
end
end
defp check_runbox_same_version(slave) do
{:ok, runbox_master} = :application.get_key(:runbox, :vsn)
{:ok, runbox_slave} = call(slave, :application, :get_key, [:runbox, :vsn])
if runbox_master == runbox_slave do
:ok
else
{:error, {:runbox_version_mismatch_on_slave, %{master: runbox_master, slave: runbox_slave}}}
end
end
defp check_runbox_scenario_app_configured(slave) do
case call(slave, Application, :fetch_env, [:runbox, :scenario_app]) do
{:ok, _} -> :ok
:error -> {:error, {:runbox_misconfigured, "Missing app config key :scenario_app"}}
end
end
defp put_app_env(slave, app_env) do
if app_env == [], do: :ok, else: call(slave, Application, :put_all_env, [app_env])
end
defp release_lib(release_dir) do
Path.join([release_dir, "lib"])
end
defp boot_file(release_dir) do
Path.join([release_dir, "releases", release_version(release_dir), "start"])
end
defp config_file(release_dir) do
Path.join([release_dir, "releases", release_version(release_dir), "sys"])
end
defp release_version(release_dir) do
data_file = Path.join([release_dir, "releases", "start_erl.data"])
[_, release_version] =
data_file
|> File.read!()
|> String.split(" ")
release_version
end
defp slave_host, do: this_host()
# The name of the host has to be derived from the node name. If taken from
# `:inet.gethostname`, it does not work inside docker.
defp this_host do
[_name, host] =
node()
|> Atom.to_string()
|> String.split("@")
host
end
defp erl_opt_toolbox_scenario_config_dir do
scenario_config_dir = Application.fetch_env!(:toolbox, :scenario_config_dir)
~s(-toolbox scenario_config_dir "#{erl_format_term(scenario_config_dir)}")
end
defp erl_opt_logger_console do
case Application.fetch_env(:logger, :console) do
{:ok, conf} -> ~s(-logger console "#{erl_format_term(conf)}")
:error -> ""
end
end
defp erl_format_term(term) do
IO.chardata_to_string(:io_lib.format("~tw", [term]))
end
end