diff options
Diffstat (limited to 'lib/stdlib/doc/src/peer.xml')
-rw-r--r-- | lib/stdlib/doc/src/peer.xml | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/lib/stdlib/doc/src/peer.xml b/lib/stdlib/doc/src/peer.xml new file mode 100644 index 0000000000..d8ac800605 --- /dev/null +++ b/lib/stdlib/doc/src/peer.xml @@ -0,0 +1,616 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<!-- %ExternalCopyright% --> + +<erlref> + <header> + <copyright> + <year>2021</year><year>2021</year> + <holder>Maxim Fedorov, WhatsApp Inc.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>peer</title> + <prepared>maximfca@gmail.com</prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>peer.xml</file> + </header> + <module since="OTP 25.0">peer</module> + <modulesummary>Start and control linked Erlang nodes. + </modulesummary> + <description> + <p> + This module provides functions for starting linked Erlang nodes. + The node spawning new nodes is called <em>origin</em>, and newly started + nodes are <em>peer</em> nodes, or peers. A peer node automatically + terminates when it loses the <em>control connection</em> 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. + </p> + + <p> + 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 <seecom marker="erts:erl#init_debug"> + <c>-init_debug</c></seecom>). File I/O is not redirected, contrary to + <seeerl marker="slave"><c>slave(3)</c></seeerl> behaviour. + </p> + + <p> + The peer node can start on the same or a different host (via <c>ssh</c>) + 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. + </p> + + <note> + <p> + This module is designed to facilitate multi-node testing with Common Test. + Use the <c>?CT_PEER()</c> 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 <seemfa marker="#random_name/1"><c>random_name/1</c></seemfa> to + create sufficiently unique node names if you need more control. + </p> + <p> + A peer node started without alternative connection behaves similarly + to <seeerl marker="slave"><c>slave(3)</c></seeerl>. When an alternative + connection is requested, the behaviour is similar to + <c>test_server:start_node(Name, peer, Args).</c> + </p> </note> + + </description> + + <section> + <title>Example</title> + <p> + 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: + </p> + <list> + <item>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</item> + <item>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</item> + <item>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</item> + </list> + <code type="erl"> + -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). + </code> + + <p> + The next example demonstrates how to start multiple nodes concurrently: + </p> + <code type="erl"> + 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]. + </code> + + <p> + Start a peer on a different host. Requires <c>ssh</c> key-based + authentication set up, allowing "another_host" connection without password + prompt. + </p> + <code type="erl"> + Ssh = os:find_executable("ssh"), + peer:start_link(#{exec => {Ssh, ["another_host", "erl"]}, + connection => standard_io}), + </code> + + <p> + 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. + </p> + <code type="erl"> + 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). + </code> + </section> + + <datatypes> + <datatype> + <name name="server_ref"/> + <desc> + <p> + Identifies the controlling process of a peer node. + </p> + </desc> + </datatype> + <datatype> + <name name="start_options"/> + <desc> + <p> + Options that can be used when starting + a <c>peer</c> node through <seemfa marker="#start/1"><c>start/1</c></seemfa> + and <seemfa marker="#start_link/0"><c>start_link/0,1</c></seemfa>. + </p> + <taglist> + <tag><c>name</c></tag> + <item> + <p> + Node name (the part before "@"). When <c>name</c> is not specified, but <c>host</c> + is, <c>peer</c> follows compatibility behaviour and uses the origin node name. + </p> + </item> + <tag><c>host</c></tag> + <item> + <p> + Enforces a specific host name. Can be used to override the default + behaviour and start "node@localhost" instead of "node@realhostname". + </p> + </item> + <tag><c>longnames</c></tag> + <item> + <p> + Use long names to start a node. Default is taken from the origin + using <c>net_kernel:longnames()</c>. If the origin is not distributed, + short names is the default. + </p> + </item> + <tag><c>peer_down</c></tag> + <item> + <p> + 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 <c>stop</c> (default), a lost control connection causes + the control process to exit normally. Setting <c>peer_down</c> to <c>continue</c> + keeps the control process running, and <c>crash</c> will cause + the controlling process to exit abnormally. + </p> + </item> + <tag><c>exec</c></tag> + <item> + <p> + Alternative mechanism to start peer nodes with, for example, ssh instead of the + default bash. + </p> + </item> + <tag><c>connection</c></tag> + <item> + <p>Alternative connection specification. See the + <seetype marker="#connection"><c>connection</c> datatype</seetype>.</p> + </item> + <tag><c>args</c></tag> + <item> + <p>Extra command line arguments to append to the "erl" command. Arguments are + passed as is, no escaping or quoting is needed or accepted.</p> + </item> + <tag><c>env</c></tag> + <item> + <p> + 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 <c>args</c> to contain + <c>-env ENV_KEY ENV_VALUE</c>. + </p> + </item> + <tag><c>wait_boot</c></tag> + <item> + <p>Specifies the start/start_link timeout. + See <seetype marker="#wait_boot"><c>wait_boot</c> datatype</seetype>. + </p> + </item> + <tag><c>shutdown</c></tag> + <item> + <p>Specifies the peer node stopping behaviour. See + <seemfa marker="#stop/1"><c>stop()</c></seemfa>.</p> + </item> + </taglist> + </desc> + </datatype> + <datatype> + <name name="peer_state"/> + <desc><p>Peer node state.</p></desc> + </datatype> + <datatype> + <name name="connection"/> + <desc><p>Alternative connection between the origin and the peer. When the + connection closes, the peer node terminates automatically. If + the <c>peer_down</c> startup flag is set to <c>crash</c>, the controlling + process on the origin node exits with corresponding reason, effectively + providing a two-way link. </p> + <p>When <c>connection</c> 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 <c>{IP, Port}</c> tuple, the origin listens only on the specified IP. The port + number can be set to 0 for automatic selection. + </p> + <p>Using the <c>standard_io</c> alternative connection starts the peer attached to + the origin (other connections use <c>-detached</c> flag to erl). In this mode + peer and origin communicate via stdin/stdout. + </p> + </desc> + </datatype> + <datatype> + <name name="exec"/> + <desc> + <p> + Overrides executable to start peer nodes with. By default it is + the path to "erl", taken from <c>init:get_argument(progname)</c>. + If <c>progname</c> is not known, <c>peer</c> makes best guess given the current + ERTS version. + </p> + <p> + 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. + </p> + <p> + 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 <c>PATH</c>. + </p> + </desc> + </datatype> + <datatype> + <name name="wait_boot"/> + <desc><p>Specifies start/start_link timeout in milliseconds. Can be set to + <c>false</c>, allowing the peer to start asynchronously. If <c>{Pid, Tag}</c> + is specified instead of a timeout, the peer will send <c>Tag</c> to the + requested process.</p></desc> + </datatype> + <datatype> + <name name="disconnect_timeout"/> + <desc><p>Disconnect timeout. See + <seemfa marker="#stop/1"><c>stop()</c></seemfa>.</p></desc> + </datatype> + </datatypes> + + <funcs> + + <func> + <name name="call" arity="4" since="OTP 25.0"/> + <name name="call" arity="5" since="OTP 25.0"/> + <fsummary>Evaluates a function call on a peer node.</fsummary> + <desc> + <p> + Uses the alternative connection to + evaluate <c>apply(<anno>Module</anno>, <anno>Function</anno>, + <anno>Args</anno>)</c> on the peer node and returns + the corresponding value <c><anno>Result</anno></c>. + <c><anno>Timeout</anno></c> is an integer representing + the timeout in milliseconds or the atom <c>infinity</c> + which prevents the operation from ever timing out. + </p> + <p> + When an alternative connection is not requested, this + function will raise <c>exit</c> signal with the <c>noconnection</c> + reason. Use <seeerl marker="kernel:erpc"><c>erpc</c></seeerl> module + to communicate over Erlang distribution. + </p> + </desc> + </func> + + <func> + <name name="cast" arity="4" since="OTP 25.0"/> + <fsummary>Evaluates a function call on a peer node ignoring the result.</fsummary> + <desc> + <p> + Uses the alternative connection to + evaluate <c>apply(<anno>Module</anno>, <anno>Function</anno>, + <anno>Args</anno>)</c> on the peer node. No response is delivered to the + calling process. + </p> + <p> + <c>peer:cast/4</c> fails silently when the alternative connection is not + configured. Use <seeerl marker="kernel:erpc"><c>erpc</c></seeerl> module + to communicate over Erlang distribution. + </p> + </desc> + </func> + + <func> + <name name="send" arity="3" since="OTP 25.0"/> + <fsummary>Sends a message to a process on the peer node.</fsummary> + <desc> + <p> + Uses the alternative connection to send <anno>Message</anno> 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. + </p> + </desc> + </func> + + <func> + <name name="get_state" arity="1" since="OTP 25.0"/> + <fsummary>Returns peer node state.</fsummary> + <desc> + <p>Returns the peer node state. Th initial state is <c>booting</c>; the node stays in that + state until then boot script is complete, and then the node progresses to <c>running</c>. + If the node stops (gracefully or not), the state changes to <c>down</c>. + </p> + </desc> + </func> + + <func> + <name name="random_name" arity="0" since="OTP 25.0"/> + <fsummary>Creates a sufficiently unique node name.</fsummary> + <desc> + <p> + The same as <seemfa marker="#random_name/1"><c>random_name(peer)</c></seemfa>. + </p> + </desc> + </func> + + <func> + <name name="random_name" arity="1" since="OTP 25.0"/> + <fsummary>Creates a sufficiently unique node name given a prefix.</fsummary> + <desc> + <p> + Creates a sufficiently unique node name for the current host, + combining a prefix, a unique number, and the current OS process ID. + </p> + <note> + <p> + Use the <c>?CT_PEER(["erl_arg1"])</c> macro provided by Common Test + <c>-include_lib("common_test/include/ct.hrl")</c> 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. + </p> + </note> + </desc> + </func> + + <func> + <name name="start" arity="1" since="OTP 25.0"/> + <fsummary>Starts a peer node.</fsummary> + <desc> + <p> + Starts a peer node with the specified + <seetype marker="#start_options"><c>start_options()</c></seetype>. + Returns the controlling process and the full peer node name, unless + <c>wait_boot</c> is not requested and the host name is not known in advance. + </p> + </desc> + </func> + + <func> + <name name="start_link" arity="0" since="OTP 25.0"/> + <fsummary>Starts a peer node, and links controlling process to caller process.</fsummary> + <desc> + <p> + The same as + <seemfa marker="#start_link/1"><c>start_link(#{name => random_name()})</c></seemfa>. + </p> + </desc> + </func> + + <func> + <name name="start_link" arity="1" since="OTP 25.0"/> + <fsummary>Starts a peer node, and links controlling process to caller process.</fsummary> + <desc> + <p>Starts a peer node in the same way as <seemfa marker="#start/1"><c>start/1</c></seemfa>, + except that the peer node is linked to the currently + executing process. If that process terminates, the peer node + also terminates.</p> + <p> + Accepts <seetype marker="#start_options"><c>start_options()</c></seetype>. + Returns the controlling process and the full peer node name, unless <c>wait_boot</c> is not + requested and host name is not known in advance. + </p> + <p> + When the <c>standard_io</c> alternative connection is requested, and <c>wait_boot</c> is + not set to <c>false</c>, a failed peer boot sequence causes the caller to exit with + the <c>{boot_failed, {exit_status, ExitCode}}</c> reason. + </p> + </desc> + </func> + + <func> + <name name="stop" arity="1" since="OTP 25.0"/> + <fsummary>Stop controlling process and terminate peer node.</fsummary> + <type name="disconnect_timeout"/> + <desc> + <p> + Stops a peer node. How the node is stopped depends on the + <seetype marker="#start_options"><c>shutdown</c></seetype> + option passed when starting the peer node. Currently the + following <c>shutdown</c> options are supported: + </p> + <taglist> + <tag><c>halt</c></tag> + <item><p> + This is the default shutdown behavior. It behaves as <c>shutdown</c> + option <c>{halt, DefaultTimeout}</c> where <c>DefaultTimeout</c> + currently equals <c>5000</c>. + </p></item> + <tag><c>{halt, Timeout :: disconnect_timeout()}</c></tag> + <item><p> + Triggers a call to + <seemfa marker="erts:erlang#halt/0"><c>erlang:halt()</c></seemfa> + 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 <c>Timeout</c> milliseconds, it will + forcefully be taken down by <c>peer:stop/1</c>. See the + <seeerl marker="#dist_connection_close">warning</seeerl> below for + more info about this. + </p></item> + <tag><c>Timeout :: disconnect_timeout()</c></tag> + <item><p> + Triggers a call to + <seemfa marker="erts:init#stop/0"><c>init:stop()</c></seemfa> + 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 <c>Timeout</c> milliseconds, it will + forcefully be taken down by <c>peer:stop/1</c>. See the + <seeerl marker="#dist_connection_close">warning</seeerl> below for + more info about this. + </p></item> + <tag><c>close</c></tag> + <item><p> + Close the <i>control connection</i> to the peer node and + return. This is the fastest way for the caller of + <c>peer:stop/1</c> to stop a peer node. + </p> + <p> + Note that if the Erlang distribution connection is not used as + control connection it might not have been taken down when + <c>peer:stop/1</c> returns. Also note that the + <seeerl marker="#dist_connection_close">warning</seeerl> below + applies when the Erlang distribution connection is used as control + connection. + </p> + </item> + </taglist> + + <marker id="dist_connection_close"/> + <warning> + <p> + In the cases where the Erlang distribution connection is taken + down by <c>peer:stop/1</c>, 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, + <seeerl marker="kernel:global#prevent_overlapping_partitions"><c>global</c></seeerl> + 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. + </p> + </warning> + </desc> + </func> + + </funcs> +</erlref> + |