| #!/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. |