Provided by: erlang-manpages_25.2.3+dfsg-1_all bug

NAME

       peer - Start and control linked Erlang nodes.

DESCRIPTION

       This  module  provides  functions  for starting linked Erlang nodes. The node spawning new
       nodes is called origin, and newly started nodes are peer nodes,  or  peers.  A  peer  node
       automatically  terminates  when  it  loses  the  control  connection  to  the origin. This
       connection could be an Erlang distribution connection, or an alternative - TCP or standard
       I/O. The alternative connection provides a way to execute remote procedure calls even when
       Erlang Distribution is not available, allowing to test the distribution itself.

       Peer node terminal  input/output  is  relayed  through  the  origin.  If  a  standard  I/O
       alternative  connection  is  requested,  console output also goes via the origin, allowing
       debugging of node startup and boot script execution (see -init_debug).  File  I/O  is  not
       redirected, contrary to slave(3erl) behaviour.

       The  peer  node  can  start  on  the  same  or a different host (via ssh) or in a separate
       container (for example Docker). When the peer starts on the same host as  the  origin,  it
       inherits the current directory and environment variables from the origin.

   Note:
       This  module  is  designed  to  facilitate  multi-node  testing  with Common Test. Use the
       ?CT_PEER() macro to start a linked peer node according to Common Test  conventions:  crash
       dumps written to specific location, node name prefixed with module name, calling function,
       and origin OS process ID). Use random_name/1 to create sufficiently unique node  names  if
       you need more control.

       A  peer node started without alternative connection behaves similarly to slave(3erl). When
       an   alternative   connection   is   requested,    the    behaviour    is    similar    to
       test_server:start_node(Name, peer, Args).

EXAMPLE

       The  following  example  implements a test suite starting extra Erlang nodes. It employs a
       number of techniques to speed up testing and reliably shut down peer nodes:

         * peers start linked to test runner process. If the test case fails, the  peer  node  is
           stopped automatically, leaving no rogue nodes running in the background

         * arguments  used  to  start  the peer are saved in the control process state for manual
           analysis. If the test case fails, the CRASH REPORT contains these arguments

         * multiple test cases can run concurrently speeding up  overall  testing  process,  peer
           node  names  are  unique even when there are multiple instances of the same test suite
           running in parallel

             -module(my_SUITE).
             -behaviour(ct_suite).
             -export([all/0, groups/0]).
             -export([basic/1, args/1, named/1, restart_node/1, multi_node/1]).

             -include_lib("common_test/include/ct.hrl").

             groups() ->
                 [{quick, [parallel],
                     [basic, args, named, restart_node, multi_node]}].

             all() ->
                 [{group, quick}].

             basic(Config) when is_list(Config) ->
                 {ok, Peer, _Node} = ?CT_PEER(),
                 peer:stop(Peer).

             args(Config) when is_list(Config) ->
                 %% specify additional arguments to the new node
                 {ok, Peer, _Node} = ?CT_PEER(["-emu_flavor", "smp"]),
                 peer:stop(Peer).

             named(Config) when is_list(Config) ->
                 %% pass test case name down to function starting nodes
                 Peer = start_node_impl(named_test),
                 peer:stop(Peer).

             start_node_impl(ActualTestCase) ->
                 {ok, Peer, Node} = ?CT_PEER(#{name => ?CT_PEER_NAME(ActualTestCase)}),
                 %% extra setup needed for multiple test cases
                 ok = rpc:call(Node, application, set_env, [kernel, key, value]),
                 Peer.

             restart_node(Config) when is_list(Config) ->
                 Name = ?CT_PEER_NAME(),
                 {ok, Peer, Node} = ?CT_PEER(#{name => Name}),
                 peer:stop(Peer),
                 %% restart the node with the same name as before
                 {ok, Peer2, Node} = ?CT_PEER(#{name => Name, args => ["+fnl"]}),
                 peer:stop(Peer2).

       The next example demonstrates how to start multiple nodes concurrently:

             multi_node(Config) when is_list(Config) ->
                 Peers = [?CT_PEER(#{wait_boot => {self(), tag}})
                     || _ <- lists:seq(1, 4)],
                 %% wait for all nodes to complete boot process, get their names:
                 _Nodes = [receive {tag, {started, Node, Peer}} -> Node end
                     || {ok, Peer} <- Peers],
                 [peer:stop(Peer) || {ok, Peer} <- Peers].

       Start a peer on a different host. Requires ssh key-based authentication set  up,  allowing
       "another_host" connection without password prompt.

             Ssh = os:find_executable("ssh"),
             peer:start_link(#{exec => {Ssh, ["another_host", "erl"]},
                 connection => standard_io}),

       The  following  Common  Test case demonstrates Docker integration, starting two containers
       with hostnames "one" and "two". In this example Erlang  nodes  running  inside  containers
       form an Erlang cluster.

             docker(Config) when is_list(Config) ->
                 Docker = os:find_executable("docker"),
                 PrivDir = proplists:get_value(priv_dir, Config),
                 build_release(PrivDir),
                 build_image(PrivDir),

                 %% start two Docker containers
                 {ok, Peer, Node} = peer:start_link(#{name => lambda,
                     connection => standard_io,
                     exec => {Docker, ["run", "-h", "one", "-i", "lambda"]}}),
                 {ok, Peer2, Node2} = peer:start_link(#{name => lambda,
                     connection => standard_io,
                     exec => {Docker, ["run", "-h", "two", "-i", "lambda"]}}),

                 %% find IP address of the second node using alternative connection RPC
                 {ok, Ips} = peer:call(Peer2, inet, getifaddrs, []),
                 {"eth0", Eth0} = lists:keyfind("eth0", 1, Ips),
                 {addr, Ip} = lists:keyfind(addr, 1, Eth0),

                 %% make first node to discover second one
                 ok = peer:call(Peer, inet_db, set_lookup, [[file]]),
                 ok = peer:call(Peer, inet_db, add_host, [Ip, ["two"]]),

                 %% join a cluster
                 true = peer:call(Peer, net_kernel, connect_node, [Node2]),
                 %% verify that second peer node has only the first node visible
                 [Node] = peer:call(Peer2, erlang, nodes, []),

                 %% stop peers, causing containers to also stop
                 peer:stop(Peer2),
                 peer:stop(Peer).

             build_release(Dir) ->
                 %% load sasl.app file, otherwise application:get_key will fail
                 application:load(sasl),
                 %% create *.rel - release file
                 RelFile = filename:join(Dir, "lambda.rel"),
                 Release = {release, {"lambda", "1.0.0"},
                     {erts, erlang:system_info(version)},
                     [{App, begin {ok, Vsn} = application:get_key(App, vsn), Vsn end}
                         || App <- [kernel, stdlib, sasl]]},
                 ok = file:write_file(RelFile, list_to_binary(lists:flatten(
                     io_lib:format("~tp.", [Release])))),
                 RelFileNoExt = filename:join(Dir, "lambda"),

                 %% create boot script
                 {ok, systools_make, []} = systools:make_script(RelFileNoExt,
                     [silent, {outdir, Dir}]),
                 %% package release into *.tar.gz
                 ok = systools:make_tar(RelFileNoExt, [{erts, code:root_dir()}]).

             build_image(Dir) ->
                 %% Create Dockerfile example, working only for Ubuntu 20.04
                 %% Expose port 4445, and make Erlang distribution to listen
                 %%  on this port, and connect to it without EPMD
                 %% Set cookie on both nodes to be the same.
                 BuildScript = filename:join(Dir, "Dockerfile"),
                 Dockerfile =
                   "FROM ubuntu:20.04 as runner\n"
                   "EXPOSE 4445\n"
                   "WORKDIR /opt/lambda\n"
                   "COPY lambda.tar.gz /tmp\n"
                   "RUN tar -zxvf /tmp/lambda.tar.gz -C /opt/lambda\n"
                   "ENTRYPOINT [\"/opt/lambda/erts-" ++ erlang:system_info(version) ++
                   "/bin/dyn_erl\", \"-boot\", \"/opt/lambda/releases/1.0.0/start\","
                   " \"-kernel\", \"inet_dist_listen_min\", \"4445\","
                   " \"-erl_epmd_port\", \"4445\","
                   " \"-setcookie\", \"secret\"]\n",
                 ok = file:write_file(BuildScript, Dockerfile),
                 os:cmd("docker build -t lambda " ++ Dir).

DATA TYPES

       server_ref() = pid()

              Identifies the controlling process of a peer node.

       start_options() =
           #{name => atom() | string(),
             longnames => boolean(),
             host => string(),
             peer_down => stop | continue | crash,
             exec => exec(),
             connection => connection(),
             args => [string()],
             env => [{string(), string()}],
             wait_boot => wait_boot(),
             shutdown =>
                 close | halt |
                 {halt, disconnect_timeout()} |
                 disconnect_timeout()}

              Options   that  can  be  used  when  starting  a  peer  node  through  start/1  and
              start_link/0,1.

                name:
                  Node name (the part before "@"). When name is not specified, but host is,  peer
                  follows compatibility behaviour and uses the origin node name.

                host:
                  Enforces  a  specific  host name. Can be used to override the default behaviour
                  and start "node@localhost" instead of "node@realhostname".

                longnames:
                  Use long names to start  a  node.  Default  is  taken  from  the  origin  using
                  net_kernel:longnames().  If  the  origin is not distributed, short names is the
                  default.

                peer_down:
                  Defines the peer control process  behaviour  when  the  control  connection  is
                  closed  from  the  peer  node  side (for example when the peer crashes or dumps
                  core). When set to stop (default), a lost control connection causes the control
                  process  to  exit  normally.  Setting  peer_down  to continue keeps the control
                  process  running,  and  crash  will  cause  the  controlling  process  to  exit
                  abnormally.

                exec:
                  Alternative mechanism to start peer nodes with, for example, ssh instead of the
                  default bash.

                connection:
                  Alternative connection specification. See the connection datatype.

                args:
                  Extra command line arguments to append to  the  "erl"  command.  Arguments  are
                  passed as is, no escaping or quoting is needed or accepted.

                env:
                  List  of  environment  variables  with  their values. This list is applied to a
                  locally started executable. If you need to change the environment of the remote
                  peer, adjust args to contain -env ENV_KEY ENV_VALUE.

                wait_boot:
                  Specifies the start/start_link timeout. See wait_boot datatype.

                shutdown:
                  Specifies the peer node stopping behaviour. See stop().

       peer_state() = booting | running | {down, Reason :: term()}

              Peer node state.

       connection() =
           0..65535 | {inet:ip_address(), 0..65535} | standard_io

              Alternative connection between the origin and the peer. When the connection closes,
              the peer node terminates automatically. If the peer_down startup  flag  is  set  to
              crash,  the controlling process on the origin node exits with corresponding reason,
              effectively providing a two-way link.

              When connection is set to a  port  number,  the  origin  starts  listening  on  the
              requested  TCP  port,  and the peer node connects to the port. When it is set to an
              {IP, Port} tuple, the origin listens only on the specified IP. The port number  can
              be set to 0 for automatic selection.

              Using the standard_io alternative connection starts the peer attached to the origin
              (other connections use -detached flag  to  erl).  In  this  mode  peer  and  origin
              communicate via stdin/stdout.

       exec() = file:name() | {file:name(), [string()]}

              Overrides  executable to start peer nodes with. By default it is the path to "erl",
              taken from init:get_argument(progname). If progname is not known, peer  makes  best
              guess given the current ERTS version.

              When a tuple is passed, the first element is the path to executable, and the second
              element is prepended to the final command line. This can be used to start peers  on
              a remote host or in a Docker container. See the examples above.

              This  option  is useful for testing backwards compatibility with previous releases,
              installed at specific paths, or when the Erlang installation  location  is  missing
              from the PATH.

       wait_boot() = timeout() | {pid(), Tag :: term()} | false

              Specifies  start/start_link  timeout in milliseconds. Can be set to false, allowing
              the peer to start asynchronously. If {Pid, Tag} is specified instead of a  timeout,
              the peer will send Tag to the requested process.

       disconnect_timeout() = 1000..4294967295 | infinity

              Disconnect timeout. See stop().

EXPORTS

       call(Dest :: server_ref(),
            Module :: module(),
            Function :: atom(),
            Args :: [term()]) ->
               Result :: term()

       call(Dest :: server_ref(),
            Module :: module(),
            Function :: atom(),
            Args :: [term()],
            Timeout :: timeout()) ->
               Result :: term()

              Uses  the  alternative  connection to evaluate apply(Module, Function, Args) on the
              peer node and returns  the  corresponding  value  Result.  Timeout  is  an  integer
              representing  the  timeout  in milliseconds or the atom infinity which prevents the
              operation from ever timing out.

              When an alternative connection is not requested,  this  function  will  raise  exit
              signal  with  the  noconnection  reason. Use erpc module to communicate over Erlang
              distribution.

       cast(Dest :: server_ref(),
            Module :: module(),
            Function :: atom(),
            Args :: [term()]) ->
               ok

              Uses the alternative connection to evaluate apply(Module, Function,  Args)  on  the
              peer node. No response is delivered to the calling process.

              peer:cast/4  fails  silently when the alternative connection is not configured. Use
              erpc module to communicate over Erlang distribution.

       send(Dest :: server_ref(),
            To :: pid() | atom(),
            Message :: term()) ->
               ok

              Uses the alternative connection to send Message to a process on the the peer  node.
              Silently  fails  if  no  alternative  connection  is configured. The process can be
              referenced by process ID or registered name.

       get_state(Dest :: server_ref()) -> peer_state()

              Returns the peer node state. Th initial state is booting; the node  stays  in  that
              state  until then boot script is complete, and then the node progresses to running.
              If the node stops (gracefully or not), the state changes to down.

       random_name() -> string()

              The same as random_name(peer).

       random_name(Prefix :: string() | atom()) -> string()

              Creates a sufficiently unique node name for the current host, combining a prefix, a
              unique number, and the current OS process ID.

          Note:
              Use     the     ?CT_PEER(["erl_arg1"])    macro    provided    by    Common    Test
              -include_lib("common_test/include/ct.hrl") for convenience. It starts  a  new  peer
              using  Erlang  distribution  as the control channel, supplies thes calling module's
              code path to the peer, and uses the calling function name for the name prefix.

       start(Options :: start_options()) ->
                {ok, pid()} | {ok, pid(), node()} | {error, Reason}

              Types:

                 Reason = term()

              Starts a peer node with the  specified  start_options().  Returns  the  controlling
              process and the full peer node name, unless wait_boot is not requested and the host
              name is not known in advance.

       start_link() -> {ok, pid(), node()} | {error, Reason :: term()}

              The same as start_link(#{name => random_name()}).

       start_link(Options :: start_options()) ->
                     {ok, pid()} | {ok, pid(), node()} | {error, Reason}

              Types:

                 Reason = term()

              Starts a peer node in the same way as start/1, except that the peer node is  linked
              to  the currently executing process. If that process terminates, the peer node also
              terminates.

              Accepts start_options(). Returns the controlling process and  the  full  peer  node
              name, unless wait_boot is not requested and host name is not known in advance.

              When  the standard_io alternative connection is requested, and wait_boot is not set
              to false, a  failed  peer  boot  sequence  causes  the  caller  to  exit  with  the
              {boot_failed, {exit_status, ExitCode}} reason.

       stop(Dest :: server_ref()) -> ok

              Types:

                 disconnect_timeout() = 1000..4294967295 | infinity

              Stops  a  peer  node. How the node is stopped depends on the shutdown option passed
              when  starting  the  peer  node.  Currently  the  following  shutdown  options  are
              supported:

                halt:
                  This  is  the  default  shutdown behavior. It behaves as shutdown option {halt,
                  DefaultTimeout} where DefaultTimeout currently equals 5000.

                {halt, Timeout :: disconnect_timeout()}:
                  Triggers a call to erlang:halt() on the peer node and then waits for the Erlang
                  distribution  connection  to the peer node to be taken down. If this connection
                  has not been taken down after Timeout milliseconds, it will forcefully be taken
                  down by peer:stop/1. See the warning below for more info about this.

                Timeout :: disconnect_timeout():
                  Triggers  a  call to init:stop() on the peer node and then waits for the Erlang
                  distribution connection to the peer node to be taken down. If  this  connection
                  has not been taken down after Timeout milliseconds, it will forcefully be taken
                  down by peer:stop/1. See the warning below for more info about this.

                close:
                  Close the control connection  to the peer node and return. This is the  fastest
                  way for the caller of peer:stop/1 to stop a peer node.

                  Note  that  if  the  Erlang  distribution  connection  is  not  used as control
                  connection it might not have been taken down  when  peer:stop/1  returns.  Also
                  note  that the warning below applies when the Erlang distribution connection is
                  used as control connection.

          Warning:
              In the cases where the Erlang distribution connection is taken down by peer:stop/1,
              other  code  independent of the peer code might react to the connection loss before
              the peer node is stopped which might cause undesirable effects. For example, global
              might  trigger even more Erlang distribution connections to other nodes to be taken
              down. The potential undesirable effects are, however, not limited to  this.  It  is
              hard  to say what the effects will be since these effects can be caused by any code
              with links or monitors to something on the origin  node,  or  code  monitoring  the
              connection to the origin node.