nodetool 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. #!/usr/bin/env escript
  2. %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
  3. %% ex: ft=erlang ts=4 sw=4 et
  4. %% -------------------------------------------------------------------
  5. %%
  6. %% nodetool: Helper Script for interacting with live nodes
  7. %%
  8. %% -------------------------------------------------------------------
  9. -mode(compile).
  10. main(Args) ->
  11. case os:type() of
  12. {win32, nt} -> ok;
  13. _nix ->
  14. case init:get_argument(start_epmd) of
  15. {ok, [["true"]]} ->
  16. ok = start_epmd();
  17. _ ->
  18. ok
  19. end
  20. end,
  21. ok = do_with_halt(Args, "mnesia_dir", fun create_mnesia_dir/2),
  22. ok = do_with_halt(Args, "chkconfig", fun("-config", X) -> chkconfig(X) end),
  23. ok = do_with_halt(Args, "chkconfig", fun chkconfig/1),
  24. Args1 = do_with_ret(Args, "-name",
  25. fun(TargetName) ->
  26. ThisNode = append_node_suffix(TargetName, "_maint_"),
  27. {ok, _} = net_kernel:start([ThisNode, longnames]),
  28. put(target_node, nodename(TargetName))
  29. end),
  30. Args2 = do_with_ret(Args1, "-sname",
  31. fun(TargetName) ->
  32. ThisNode = append_node_suffix(TargetName, "_maint_"),
  33. {ok, _} = net_kernel:start([ThisNode, shortnames]),
  34. put(target_node, nodename(TargetName))
  35. end),
  36. RestArgs = do_with_ret(Args2, "-setcookie",
  37. fun(Cookie) ->
  38. erlang:set_cookie(node(), list_to_atom(Cookie))
  39. end),
  40. [application:start(App) || App <- [crypto, public_key, ssl]],
  41. TargetNode = get(target_node),
  42. %% See if the node is currently running -- if it's not, we'll bail
  43. case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of
  44. {true, pong} ->
  45. ok;
  46. {false, pong} ->
  47. io:format(standard_error, "Failed to connect to node ~p\n", [TargetNode]),
  48. halt(1);
  49. {_, pang} ->
  50. io:format(standard_error, "Node ~p not responding to pings.\n", [TargetNode]),
  51. halt(1)
  52. end,
  53. case RestArgs of
  54. ["getpid"] ->
  55. io:format("~p\n", [list_to_integer(rpc:call(TargetNode, os, getpid, []))]);
  56. ["ping"] ->
  57. %% If we got this far, the node already responsed to a ping, so just dump
  58. %% a "pong"
  59. io:format("pong\n");
  60. ["stop"] ->
  61. rpc:call(TargetNode, emqx_plugins, unload, [], 60000),
  62. io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]);
  63. ["restart", "-config", ConfigFile | _RestArgs1] ->
  64. io:format("~p\n", [rpc:call(TargetNode, emqx, restart, [ConfigFile], 60000)]);
  65. ["rpc", Module, Function | RpcArgs] ->
  66. case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
  67. [RpcArgs], 60000) of
  68. ok ->
  69. ok;
  70. {error, cmd_not_found} ->
  71. halt(1);
  72. {error, Reason} ->
  73. io:format("RPC to ~s error: ~p\n", [TargetNode, Reason]),
  74. halt(1);
  75. {badrpc, Reason} ->
  76. io:format("RPC to ~s failed: ~p\n", [TargetNode, Reason]),
  77. halt(1);
  78. _ ->
  79. halt(1)
  80. end;
  81. ["rpc_infinity", Module, Function | RpcArgs] ->
  82. case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], infinity) of
  83. ok ->
  84. ok;
  85. {badrpc, Reason} ->
  86. io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
  87. halt(1);
  88. _ ->
  89. halt(1)
  90. end;
  91. ["rpcterms", Module, Function | ArgsAsString] ->
  92. case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
  93. consult(lists:flatten(ArgsAsString)), 60000) of
  94. {badrpc, Reason} ->
  95. io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
  96. halt(1);
  97. Other ->
  98. io:format("~p\n", [Other])
  99. end;
  100. ["eval" | ListOfArgs] ->
  101. % shells may process args into more than one, and end up stripping
  102. % spaces, so this converts all of that to a single string to parse
  103. String = binary_to_list(
  104. list_to_binary(
  105. join(ListOfArgs," ")
  106. )
  107. ),
  108. % then just as a convenience to users, if they forgot a trailing
  109. % '.' add it for them.
  110. Normalized =
  111. case lists:reverse(String) of
  112. [$. | _] -> String;
  113. R -> lists:reverse([$. | R])
  114. end,
  115. % then scan and parse the string
  116. {ok, Scanned, _} = erl_scan:string(Normalized),
  117. {ok, Parsed } = erl_parse:parse_exprs(Scanned),
  118. % and evaluate it on the remote node
  119. case rpc:call(TargetNode, erl_eval, exprs, [Parsed, [] ]) of
  120. {value, Value, _} ->
  121. io:format ("~p\n",[Value]);
  122. {badrpc, Reason} ->
  123. io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
  124. halt(1)
  125. end;
  126. Other ->
  127. io:format("Other: ~p\n", [Other]),
  128. io:format("Usage: nodetool {genconfig, chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval [Terms]} [RPC]\n")
  129. end,
  130. net_kernel:stop().
  131. do_with_ret(Args, Name, Handler) ->
  132. {arity, Arity} = erlang:fun_info(Handler, arity),
  133. case take_args(Args, Name, Arity) of
  134. false ->
  135. Args;
  136. {Args1, Rest} ->
  137. _ = erlang:apply(Handler, Args1),
  138. Rest
  139. end.
  140. do_with_halt(Args, Name, Handler) ->
  141. {arity, Arity} = erlang:fun_info(Handler, arity),
  142. case take_args(Args, Name, Arity) of
  143. false ->
  144. ok;
  145. {Args1, _Rest} ->
  146. erlang:apply(Handler, Args1), %% should halt
  147. io:format(standard_error, "~s handler did not halt", [Name]),
  148. halt(?LINE)
  149. end.
  150. %% Return option args list if found, otherwise 'false'.
  151. take_args(Args, OptName, 0) ->
  152. lists:member(OptName, Args) andalso [];
  153. take_args(Args, OptName, OptArity) ->
  154. take_args(Args, OptName, OptArity, _Scanned = []).
  155. take_args([], _, _, _) -> false; %% no such option
  156. take_args([Name | Rest], Name, Arity, Scanned) ->
  157. length(Rest) >= Arity orelse error({not_enough_args_for, Name}),
  158. {Result, Tail} = lists:split(Arity, Rest),
  159. {Result, lists:reverse(Scanned) ++ Tail};
  160. take_args([Other | Rest], Name, Arity, Scanned) ->
  161. take_args(Rest, Name, Arity, [Other | Scanned]).
  162. start_epmd() ->
  163. [] = os:cmd("\"" ++ epmd_path() ++ "\" -daemon"),
  164. ok.
  165. epmd_path() ->
  166. ErtsBinDir = filename:dirname(escript:script_name()),
  167. Name = "epmd",
  168. case os:find_executable(Name, ErtsBinDir) of
  169. false ->
  170. case os:find_executable(Name) of
  171. false ->
  172. io:format("Could not find epmd.~n"),
  173. halt(1);
  174. GlobalEpmd ->
  175. GlobalEpmd
  176. end;
  177. Epmd ->
  178. Epmd
  179. end.
  180. nodename(Name) ->
  181. case re:split(Name, "@", [{return, list}, unicode]) of
  182. [_Node, _Host] ->
  183. list_to_atom(Name);
  184. [Node] ->
  185. [_, Host] = re:split(atom_to_list(node()), "@", [{return, list}, unicode]),
  186. list_to_atom(lists:concat([Node, "@", Host]))
  187. end.
  188. append_node_suffix(Name, Suffix) ->
  189. case re:split(Name, "@", [{return, list}, unicode]) of
  190. [Node, Host] ->
  191. list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host]));
  192. [Node] ->
  193. list_to_atom(lists:concat([Node, Suffix, os:getpid()]))
  194. end.
  195. %% For windows???
  196. create_mnesia_dir(DataDir, NodeName) ->
  197. MnesiaDir = filename:join(DataDir, NodeName),
  198. file:make_dir(MnesiaDir),
  199. io:format("~s", [MnesiaDir]),
  200. halt(0).
  201. chkconfig(File) ->
  202. case file:consult(File) of
  203. {ok, Terms} ->
  204. case validate(Terms) of
  205. ok ->
  206. halt(0);
  207. {error, Problems} ->
  208. lists:foreach(fun print_issue/1, Problems),
  209. %% halt(1) if any problems were errors
  210. halt(case [x || {error, _} <- Problems] of
  211. [] -> 0;
  212. _ -> 1
  213. end)
  214. end;
  215. {error, {Line, Mod, Term}} ->
  216. io:format(standard_error, ["Error on line ", file:format_error({Line, Mod, Term}), "\n"], []),
  217. halt(1);
  218. {error, Error} ->
  219. io:format(standard_error, ["Error reading config file: ", File, " ", file:format_error(Error), "\n"], []),
  220. halt(1)
  221. end.
  222. %%
  223. %% Given a string or binary, parse it into a list of terms, ala file:consult/0
  224. %%
  225. consult(Str) when is_list(Str) ->
  226. consult([], Str, []);
  227. consult(Bin) when is_binary(Bin)->
  228. consult([], binary_to_list(Bin), []).
  229. consult(Cont, Str, Acc) ->
  230. case erl_scan:tokens(Cont, Str, 0) of
  231. {done, Result, Remaining} ->
  232. case Result of
  233. {ok, Tokens, _} ->
  234. {ok, Term} = erl_parse:parse_term(Tokens),
  235. consult([], Remaining, [Term | Acc]);
  236. {eof, _Other} ->
  237. lists:reverse(Acc);
  238. {error, Info, _} ->
  239. {error, Info}
  240. end;
  241. {more, Cont1} ->
  242. consult(Cont1, eof, Acc)
  243. end.
  244. %%
  245. %% Validation functions for checking the app.config
  246. %%
  247. validate([Terms]) ->
  248. Results = [ValidateFun(Terms) || ValidateFun <- get_validation_funs()],
  249. Failures = [Res || Res <- Results, Res /= true],
  250. case Failures of
  251. [] ->
  252. ok;
  253. _ ->
  254. {error, Failures}
  255. end.
  256. %% Some initial and basic checks for the app.config file
  257. get_validation_funs() ->
  258. [ ].
  259. print_issue({warning, Warning}) ->
  260. io:format(standard_error, "Warning in app.config: ~s~n", [Warning]);
  261. print_issue({error, Error}) ->
  262. io:format(standard_error, "Error in app.config: ~s~n", [Error]).
  263. %% string:join/2 copy; string:join/2 is getting obsoleted
  264. %% and replaced by lists:join/2, but lists:join/2 is too new
  265. %% for version support (only appeared in 19.0) so it cannot be
  266. %% used. Instead we just adopt join/2 locally and hope it works
  267. %% for most unicode use cases anyway.
  268. join([], Sep) when is_list(Sep) ->
  269. [];
  270. join([H|T], Sep) ->
  271. H ++ lists:append([Sep ++ X || X <- T]).