查看源代码 peer (stdlib v6.2)

启动和控制链接的 Erlang 节点。

此模块提供了启动链接的 Erlang 节点的函数。 产生新节点的节点称为,新启动的节点是对等节点或对等体。 当对等节点失去与源的控制连接时,它会自动终止。 此连接可以是 Erlang 分布连接,也可以是替代连接 - TCP 或标准 I/O。 即使 Erlang 分布不可用,替代连接也提供了一种执行远程过程调用的方法,从而可以测试分布本身。

对等节点终端输入/输出通过源进行中继。 如果请求标准 I/O 替代连接,控制台输出也会通过源进行,从而可以调试节点启动和引导脚本执行(请参阅 -init_debug)。 文件 I/O 不会被重定向,这与 slave 的行为相反。

对等节点可以在同一主机或不同的主机(通过 ssh)或在单独的容器(例如 Docker)中启动。 当对等节点在与源相同的主机上启动时,它会从源继承当前目录和环境变量。

注意

此模块旨在方便使用 Common Test 进行多节点测试。 使用 ?CT_PEER() 宏根据 Common Test 约定启动链接的对等节点:崩溃转储写入特定位置,节点名称以模块名称、调用函数和源操作系统进程 ID 为前缀)。 如果您需要更多控制,请使用 random_name/1 创建足够唯一的节点名称。

在没有替代连接的情况下启动的对等节点的行为类似于 slave。 当请求替代连接时,其行为类似于 test_server:start_node(Name, peer, Args).

示例

以下示例实现了一个启动额外 Erlang 节点的测试套件。 它采用多种技术来加速测试并可靠地关闭对等节点

  • 对等体启动时会链接到测试运行器进程。 如果测试用例失败,对等节点会自动停止,而不会在后台运行任何流氓节点
  • 用于启动对等体的参数保存在控制进程状态中,以供手动分析。 如果测试用例失败,则 CRASH REPORT 会包含这些参数
  • 多个测试用例可以并发运行,从而加快整体测试过程,即使并行运行同一测试套件的多个实例,对等节点名称也是唯一的
-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).

下一个示例演示如何并发启动多个节点

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].

在不同的主机上启动对等体。 需要设置基于 ssh 密钥的身份验证,允许“another_host”连接而无需密码提示。

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

以下 Common Test 用例演示了 Docker 集成,启动两个主机名为“one”和“two”的容器。 在此示例中,在容器内运行的 Erlang 节点形成一个 Erlang 集群。

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).

摘要

类型

源和对等体之间的替代连接。 当连接关闭时,对等节点会自动终止。

断开连接超时。 请参阅 stop()

覆盖用于启动对等节点的执行文件。

对等节点状态。

标识对等节点的控制进程。

通过 start/1start_link/0,1 启动 peer 节点时可以使用的选项。

以毫秒为单位指定 start/start_link 超时。 可以设置为 false,允许对等体异步启动。 如果指定 {Pid, Tag} 而不是超时,则对等体会将 Tag 发送到请求的进程。

函数

使用替代连接在对等节点上评估 apply(Module, Function, Args) 并返回相应的 Result 值。

使用替代连接在对等节点上评估 apply(Module, Function, Args)。 不会将响应传递给调用进程。

返回对等节点状态。

为当前主机创建足够唯一的节点名称,组合前缀、唯一编号和当前操作系统进程 ID。

使用替代连接将 Message 发送到对等节点上的进程。

使用指定的 start_options/0 启动对等节点。 返回控制进程和完整的对等节点名称,除非不请求 wait_boot 并且事先不知道主机名。

以与 start/1 相同的方式启动对等节点,不同之处在于对等节点链接到当前正在执行的进程。 如果该进程终止,则对等节点也会终止。

停止对等节点。 如何停止节点取决于启动对等节点时传递的 shutdown 选项。 当前支持以下 shutdown 选项

类型

链接到此类型

connection()

查看源代码 (未导出) (自 OTP 25.0 起)
-type connection() :: Port :: 0..65535 | {inet:ip_address(), 0..65535} | standard_io.

源和对等体之间的替代连接。 当连接关闭时,对等节点会自动终止。

如果 peer_down 启动标志设置为 crash,则源节点上的控制进程会因相应的理由而退出,从而有效地提供双向链接。

connection 设置为端口号时,源会开始侦听请求的 TCP 端口,并且对等节点会连接到该端口。 当它设置为 {IP, Port} 元组时,源仅侦听指定的 IP。 可以将端口号设置为 0 以进行自动选择。

使用 standard_io 替代连接会启动附加到源的对等体(其他连接使用 -detached 标志启动 erl)。 在此模式下,对等体和源通过 stdin/stdout 进行通信。

链接到此类型

disconnect_timeout()

查看源代码 (自 OTP 25.0 起)
-type disconnect_timeout() :: 1000..4294967295 | infinity.

断开连接超时。 请参阅 stop()

链接到此类型

exec()

查看源代码 (自 OTP 25.0 起)
-type exec() :: file:name() | {file:name(), [string()]}.

覆盖用于启动对等节点的执行文件。

默认情况下,它是“erl”的路径,取自 init:get_argument(progname)。 如果 progname 未知,则 peer 会根据当前的 ERTS 版本做出最佳猜测。

当传递元组时,第一个元素是可执行文件的路径,第二个元素会预先添加到最终命令行。 这可用于在远程主机或 Docker 容器中启动对等体。 请参阅上面的示例。

当 Erlang 安装位置缺少 PATH 时,此选项对于测试与安装在特定路径中的以前版本向后兼容性非常有用。

链接到此类型

peer_state()

查看源代码 (自 OTP 25.0 起)
-type peer_state() :: booting | running | {down, Reason :: term()}.

对等节点状态。

链接到此类型

server_ref()

查看源代码 (自 OTP 25.0 起)
-type server_ref() :: pid().

标识对等节点的控制进程。

链接到此类型

start_options()

查看源代码 (自 OTP 25.0 起)
-type start_options() ::
          #{name => atom() | string(),
            longnames => boolean(),
            host => string(),
            peer_down => stop | continue | crash,
            connection => connection(),
            exec => exec(),
            detached => boolean(),
            args => [string()],
            post_process_args => fun(([string()]) -> [string()]),
            env => [{string(), string()}],
            wait_boot => wait_boot(),
            shutdown => close | halt | {halt, disconnect_timeout()} | disconnect_timeout()}.

通过 start/1start_link/0,1 启动 peer 节点时可以使用的选项。

  • name - 节点名称(“@”之前的部分)。 当未指定 name 但指定了 host 时,peer 会遵循兼容性行为并使用源节点名称。

  • longnames - 使用长名称启动节点。 默认值取自使用 net_kernel:longnames() 的源。 如果源未分布,则默认值为短名称。

  • host - 强制使用特定主机名。 可用于覆盖默认行为并启动“node@localhost”而不是“node@realhostname”。

  • peer_down - 定义当从对等节点侧关闭控制连接时(例如,当对等体崩溃或转储核心时)对等控制进程的行为。 当设置为 stop(默认)时,丢失的控制连接会导致控制进程正常退出。 将 peer_down 设置为 continue 会使控制进程保持运行,而 crash 会导致控制进程异常退出。

  • connection - 替代连接规范。 请参阅 connection 数据类型

  • exec - 替代启动对等节点的机制,例如,使用 ssh 而不是默认的 bash。

  • detached - 定义是否将 -detached 标志传递给启动的对等节点。使用 standard_io 替代连接类型时,此选项不能设置为 false。默认为 true

  • args - 要附加到 “erl” 命令的额外命令行参数。参数按原样传递,无需转义或加引号,也不接受转义或加引号。

  • post_process_args - 允许用户在对等节点启动之前更改传递给 exec 的参数。例如,当 exec 程序希望将 “erl” 的参数作为单个参数时,这会很有用。示例

    peer:start(#{ name => peer:random_name(),
      exec => {os:find_executable("bash"),["-c","erl"]},
      post_process_args =>
         fun(["-c"|Args]) -> ["-c", lists:flatten(lists:join($\s, Args))] end
      }).
  • env - 包含值的环境变量列表。此列表应用于本地启动的可执行文件。如果需要更改远程对等节点的环境,请调整 args 以包含 -env ENV_KEY ENV_VALUE

  • wait_boot - 指定启动/start_link 超时。请参阅 wait_boot 数据类型

  • shutdown - 指定对等节点停止行为。请参阅 stop()

链接到此类型

wait_boot()

查看源代码 (未导出) (自 OTP 25.0 起)
-type wait_boot() :: timeout() | {pid(), Tag :: term()} | false.

以毫秒为单位指定 start/start_link 超时。 可以设置为 false,允许对等体异步启动。 如果指定 {Pid, Tag} 而不是超时,则对等体会将 Tag 发送到请求的进程。

默认值为 15_000 毫秒。

函数

链接到此函数

call(Dest, Module, Function, Args)

查看源代码 (自 OTP 25.0 起)
-spec call(Dest :: server_ref(), Module :: module(), Function :: atom(), Args :: [term()]) ->
              Result :: term().

等效于 call(Dest, Module, Function, Args, 5000)

链接到此函数

call(Dest, Module, Function, Args, Timeout)

查看源代码 (自 OTP 25.0 起)
-spec call(Dest :: server_ref(),
           Module :: module(),
           Function :: atom(),
           Args :: [term()],
           Timeout :: timeout()) ->
              Result :: term().

使用替代连接在对等节点上评估 apply(Module, Function, Args) 并返回相应的 Result 值。

Timeout 是一个整数,表示以毫秒为单位的超时时间,或者是原子 infinity,表示该操作永远不会超时。

当未请求替代连接时,此函数将引发带有 noconnection 原因的 exit 信号。使用 erpc 模块通过 Erlang 分布式进行通信。

链接到此函数

cast(Dest, Module, Function, Args)

查看源代码 (自 OTP 25.0 起)
-spec cast(Dest :: server_ref(), Module :: module(), Function :: atom(), Args :: [term()]) -> ok.

使用替代连接在对等节点上评估 apply(Module, Function, Args)。 不会将响应传递给调用进程。

当未配置替代连接时,peer:cast/4 会静默失败。使用 erpc 模块通过 Erlang 分布式进行通信。

链接到此函数

get_state(Dest)

查看源代码 (自 OTP 25.0 起)
-spec get_state(Dest :: server_ref()) -> peer_state().

返回对等节点状态。

初始状态为 booting;节点保持在该状态,直到启动脚本完成,然后节点进入 running 状态。如果节点停止(正常或不正常),状态将更改为 down

链接到此函数

random_name()

查看源代码 (自 OTP 25.0 起)
-spec random_name() -> string().

等效于 random_name(peer)

链接到此函数

random_name(Prefix)

查看源代码 (自 OTP 25.0 起)
-spec random_name(Prefix :: string() | atom()) -> string().

为当前主机创建足够唯一的节点名称,组合前缀、唯一编号和当前操作系统进程 ID。

注意

为方便起见,请使用 Common Test 提供的 ?CT_PEER(["erl_arg1"])-include_lib("common_test/include/ct.hrl")。它使用 Erlang 分布式作为控制通道启动一个新的对等节点,将调用模块的代码路径提供给对等节点,并使用调用函数名称作为名称前缀。

链接到此函数

send(Dest, To, Message)

查看源代码 (自 OTP 25.0 起)
-spec send(Dest :: server_ref(), To :: pid() | atom(), Message :: term()) -> ok.

使用替代连接将 Message 发送到对等节点上的进程。

如果未配置替代连接,则会静默失败。可以通过进程 ID 或注册名称引用该进程。

链接到此函数

start(Options)

查看源代码 (自 OTP 25.0 起)
-spec start(start_options()) -> {ok, pid()} | {ok, pid(), node()} | {error, Reason}
               when Reason :: term().

使用指定的 start_options/0 启动对等节点。 返回控制进程和完整的对等节点名称,除非不请求 wait_boot 并且事先不知道主机名。

链接到此函数

start_link()

查看源代码 (自 OTP 25.0 起)
-spec start_link() -> {ok, pid(), node()} | {error, Reason :: term()}.

start_link(#{name => random_name()}) 相同。

链接到此函数

start_link(Options)

查看源代码 (自 OTP 25.0 起)
-spec start_link(start_options()) -> {ok, pid()} | {ok, pid(), node()} | {error, Reason}
                    when Reason :: term().

以与 start/1 相同的方式启动对等节点,不同之处在于对等节点链接到当前正在执行的进程。 如果该进程终止,则对等节点也会终止。

接受 start_options/0。返回控制进程和完整的对等节点名称,除非未请求 wait_boot 并且主机名不是预先知道的。

当请求 standard_io 替代连接,并且 wait_boot 未设置为 false 时,失败的对等节点启动序列会导致调用方以 {boot_failed, {exit_status, ExitCode}} 原因退出。

链接到此函数

stop(Dest)

查看源代码 (自 OTP 25.0 起)
-spec stop(Dest :: server_ref()) -> ok.

停止对等节点。 如何停止节点取决于启动对等节点时传递的 shutdown 选项。 当前支持以下 shutdown 选项

  • halt - 这是默认的关闭行为。它的行为与 shutdown 选项 {halt, DefaultTimeout} 相同,其中 DefaultTimeout 当前等于 5000

  • {halt, Timeout :: disconnect_timeout()} - 触发对等节点上的 erlang:halt() 的调用,然后等待对等节点的 Erlang 分布式连接被关闭。如果此连接在 Timeout 毫秒后仍未关闭,它将被 peer:stop/1 强制关闭。有关此的更多信息,请参阅下面的 警告

  • Timeout :: disconnect_timeout() - 触发对等节点上的 init:stop() 的调用,然后等待对等节点的 Erlang 分布式连接被关闭。如果此连接在 Timeout 毫秒后仍未关闭,它将被 peer:stop/1 强制关闭。有关此的更多信息,请参阅下面的 警告

  • close - 关闭与对等节点的控制连接并返回。这是 peer:stop/1 的调用者停止对等节点的最快方法。

    请注意,如果 Erlang 分布式连接不用作控制连接,则当 peer:stop/1 返回时,它可能尚未被关闭。另请注意,当 Erlang 分布式连接用作控制连接时,下面的 警告适用。

警告

peer:stop/1 关闭 Erlang 分布式连接的情况下,独立于对等节点代码的其他代码可能会在对等节点停止之前对连接丢失做出反应,这可能会导致不良影响。例如,global 可能会触发更多 Erlang 分布式连接到其他节点被关闭。然而,潜在的不良影响不仅限于此。很难说这些影响是什么,因为这些影响可能是由任何具有到原节点的链接或监视器的代码,或监视与原节点连接的代码引起的。