#!/usr/bin/env escript %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ft=erlang ts=4 sw=4 et %% ------------------------------------------------------------------- %% %% nodetool: Helper Script for interacting with live nodes %% %% ------------------------------------------------------------------- main(Args) -> ok = start_epmd(), %% Extract the args {RestArgs, TargetNode} = process_args(Args, [], undefined), %% any commands that don't need a running node case RestArgs of ["chkconfig", File] -> case file:consult(File) of {ok, _} -> io:format("ok\n"), halt(0); {error, {Line, Mod, Term}} -> io:format(standard_error, ["Error on line ", file:format_error({Line, Mod, Term}), "\n"], []), halt(1); {error, R} -> io:format(standard_error, ["Error reading config file: ", file:format_error(R), "\n"], []), halt(1) end; _ -> ok end, %% See if the node is currently running -- if it's not, we'll bail case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of {true, pong} -> ok; {false,pong} -> io:format("Failed to connect to node ~p .\n", [TargetNode]), halt(1); {_, pang} -> io:format("Node ~p not responding to pings.\n", [TargetNode]), halt(1) end, case RestArgs of ["getpid"] -> io:format("~p\n", [list_to_integer(rpc:call(TargetNode, os, getpid, []))]); ["ping"] -> %% If we got this far, the node already responsed to a %% ping, so just dump a "pong" io:format("pong\n"); ["stop"] -> io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); ["restart"] -> io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); ["reboot"] -> io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); ["rpc", Module, Function | RpcArgs] -> case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], 60000) of ok -> ok; {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), halt(1); _ -> halt(1) end; ["rpc_infinity", Module, Function | RpcArgs] -> case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], infinity) of ok -> ok; {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), halt(1); _ -> halt(1) end; ["rpcterms", Module, Function, ArgsAsString] -> case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), consult(ArgsAsString), 60000) of {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), halt(1); Other -> io:format("~p\n", [Other]) end; ["eval", Str0] -> Str = string:strip(Str0, right, $.) ++ ".", Bindings = erl_eval:new_bindings(), case rpc:call(TargetNode, erl_eval, exprs, [parse(Str), Bindings], 60000) of {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), halt(1); {value, Value, _Bindings} -> io:format("~p\n", [Value]) end; ["upgrade", ReleasePackage] -> %% TODO: This script currently does NOT support slim releases. %% Necessary steps to upgrade a slim release are as follows: %% 1. unpack relup archive manually %% 2. copy releases directory and necessary libraries %% 3. using release_hander:set_unpacked/2 . %% For more details, see https://github.com/rebar/rebar/pull/52 %% and https://github.com/rebar/rebar/issues/202 {ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release, [ReleasePackage], 60000), io:format("Unpacked Release ~p\n", [Vsn]), {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, check_install_release, [Vsn], 60000), {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, install_release, [Vsn], 60000), io:format("Installed Release ~p\n", [Vsn]), ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], 60000), io:format("Made Release ~p Permanent\n", [Vsn]); Other -> io:format("Other: ~p\n", [Other]), io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval|upgrade}\n") end, net_kernel:stop(). process_args([], Acc, TargetNode) -> {lists:reverse(Acc), TargetNode}; process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> erlang:set_cookie(node(), list_to_atom(Cookie)), process_args(Rest, Acc, TargetNode); process_args(["-name", TargetName | Rest], Acc, _) -> ThisNode = append_node_suffix(TargetName, "_maint_"), {ok, _} = net_kernel:start([ThisNode, longnames]), process_args(Rest, Acc, nodename(TargetName)); process_args(["-sname", TargetName | Rest], Acc, _) -> ThisNode = append_node_suffix(TargetName, "_maint_"), {ok, _} = net_kernel:start([ThisNode, shortnames]), process_args(Rest, Acc, nodename(TargetName)); process_args([Arg | Rest], Acc, Opts) -> process_args(Rest, [Arg | Acc], Opts). start_epmd() -> [] = os:cmd(epmd_path() ++ " -daemon"), ok. epmd_path() -> ErtsBinDir = filename:dirname(escript:script_name()), Name = "epmd", case os:find_executable(Name, ErtsBinDir) of false -> case os:find_executable(Name) of false -> io:format("Could not find epmd.~n"), halt(1); GlobalEpmd -> GlobalEpmd end; Epmd -> Epmd end. nodename(Name) -> case string:tokens(Name, "@") of [_Node, _Host] -> list_to_atom(Name); [Node] -> [_, Host] = string:tokens(atom_to_list(node()), "@"), list_to_atom(lists:concat([Node, "@", Host])) end. append_node_suffix(Name, Suffix) -> case string:tokens(Name, "@") of [Node, Host] -> list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); [Node] -> list_to_atom(lists:concat([Node, Suffix, os:getpid()])) end. %% %% Given a string or binary, parse it into a list of terms, ala file:consult/0 %% consult(Str) when is_list(Str) -> consult([], Str, []); consult(Bin) when is_binary(Bin)-> consult([], binary_to_list(Bin), []). consult(Cont, Str, Acc) -> case erl_scan:tokens(Cont, Str, 0) of {done, Result, Remaining} -> case Result of {ok, Tokens, _} -> {ok, Term} = erl_parse:parse_term(Tokens), consult([], Remaining, [Term | Acc]); {eof, _Other} -> lists:reverse(Acc); {error, Info, _} -> {error, Info} end; {more, Cont1} -> consult(Cont1, eof, Acc) end. parse(Str) -> {ok, Tokens, _} = erl_scan:string(Str), {ok, Exprs} = erl_parse:parse_exprs(Tokens), Exprs.