emqx_gateway_cli.erl 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. %% @doc The Command-Line-Interface module for Gateway Application
  17. -module(emqx_gateway_cli).
  18. -export([
  19. load/0,
  20. unload/0
  21. ]).
  22. -export([
  23. gateway/1,
  24. 'gateway-registry'/1,
  25. 'gateway-clients'/1,
  26. 'gateway-metrics'/1
  27. %, 'gateway-banned'/1
  28. ]).
  29. -elvis([{elvis_style, function_naming_convention, disable}]).
  30. -spec load() -> ok.
  31. load() ->
  32. Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)],
  33. lists:foreach(
  34. fun(Cmd) ->
  35. emqx_ctl:register_command(Cmd, {?MODULE, Cmd}, [])
  36. end,
  37. Cmds
  38. ).
  39. -spec unload() -> ok.
  40. unload() ->
  41. Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)],
  42. lists:foreach(fun(Cmd) -> emqx_ctl:unregister_command(Cmd) end, Cmds).
  43. is_cmd(Fun) ->
  44. case atom_to_list(Fun) of
  45. "gateway" ++ _ -> true;
  46. _ -> false
  47. end.
  48. %%--------------------------------------------------------------------
  49. %% Cmds
  50. gateway(["list"]) ->
  51. lists:foreach(
  52. fun(GwSummary) ->
  53. print(format_gw_summary(GwSummary))
  54. end,
  55. emqx_gateway_http:gateways(all)
  56. );
  57. gateway(["lookup", Name]) ->
  58. case emqx_gateway:lookup(atom(Name)) of
  59. undefined ->
  60. print("undefined\n");
  61. Gateway ->
  62. print(format_gateway(Gateway))
  63. end;
  64. gateway(["load", Name, Conf]) ->
  65. case
  66. emqx_gateway_conf:load_gateway(
  67. bin(Name),
  68. emqx_utils_json:decode(Conf, [return_maps])
  69. )
  70. of
  71. {ok, _} ->
  72. print("ok\n");
  73. {error, Reason} ->
  74. print("Error: ~ts\n", [format_error(Reason)])
  75. end;
  76. gateway(["unload", Name]) ->
  77. case emqx_gateway_conf:unload_gateway(bin(Name)) of
  78. ok ->
  79. print("ok\n");
  80. {error, Reason} ->
  81. print("Error: ~ts\n", [format_error(Reason)])
  82. end;
  83. gateway(["stop", Name]) ->
  84. case
  85. emqx_gateway_conf:update_gateway(
  86. bin(Name),
  87. #{<<"enable">> => <<"false">>}
  88. )
  89. of
  90. {ok, _} ->
  91. print("ok\n");
  92. {error, Reason} ->
  93. print("Error: ~ts\n", [format_error(Reason)])
  94. end;
  95. gateway(["start", Name]) ->
  96. case
  97. emqx_gateway_conf:update_gateway(
  98. bin(Name),
  99. #{<<"enable">> => <<"true">>}
  100. )
  101. of
  102. {ok, _} ->
  103. print("ok\n");
  104. {error, Reason} ->
  105. print("Error: ~ts\n", [format_error(Reason)])
  106. end;
  107. gateway(_) ->
  108. emqx_ctl:usage(
  109. [
  110. {"gateway list", "List all gateway"},
  111. {"gateway lookup <Name>", "Lookup a gateway detailed information"},
  112. {"gateway load <Name> <JsonConf>", "Load a gateway with config"},
  113. {"gateway unload <Name>", "Unload the gateway"},
  114. {"gateway stop <Name>", "Stop the gateway"},
  115. {"gateway start <Name>", "Start the gateway"}
  116. ]
  117. ).
  118. 'gateway-registry'(["list"]) ->
  119. lists:foreach(
  120. fun({Name, #{cbkmod := CbMod}}) ->
  121. print("Registered Name: ~ts, Callback Module: ~ts\n", [Name, CbMod])
  122. end,
  123. emqx_gateway_registry:list()
  124. );
  125. 'gateway-registry'(_) ->
  126. emqx_ctl:usage([{"gateway-registry list", "List all registered gateways"}]).
  127. 'gateway-clients'(["list", Name]) ->
  128. %% XXX: page me?
  129. InfoTab = emqx_gateway_cm:tabname(info, Name),
  130. case ets:info(InfoTab) of
  131. undefined ->
  132. print("Bad Gateway Name.\n");
  133. _ ->
  134. dump(InfoTab, client)
  135. end;
  136. 'gateway-clients'(["lookup", Name, ClientId]) ->
  137. ChanTab = emqx_gateway_cm:tabname(chan, Name),
  138. case ets:info(ChanTab) of
  139. undefined ->
  140. print("Bad Gateway Name.\n");
  141. _ ->
  142. case ets:lookup(ChanTab, bin(ClientId)) of
  143. [] ->
  144. print("Not Found.\n");
  145. [Chann] ->
  146. InfoTab = emqx_gateway_cm:tabname(info, Name),
  147. [ChannInfo] = ets:lookup(InfoTab, Chann),
  148. print_record({client, ChannInfo})
  149. end
  150. end;
  151. 'gateway-clients'(["kick", Name, ClientId]) ->
  152. case emqx_gateway_cm:kick_session(Name, bin(ClientId)) of
  153. ok -> print("ok\n");
  154. _ -> print("Not Found.\n")
  155. end;
  156. 'gateway-clients'(_) ->
  157. emqx_ctl:usage([
  158. {"gateway-clients list <Name>", "List all clients for a gateway"},
  159. {"gateway-clients lookup <Name> <ClientId>", "Lookup the Client Info for specified client"},
  160. {"gateway-clients kick <Name> <ClientId>", "Kick out a client"}
  161. ]).
  162. 'gateway-metrics'([Name]) ->
  163. case emqx_gateway_metrics:lookup(atom(Name)) of
  164. undefined ->
  165. print("Bad Gateway Name.\n");
  166. Metrics ->
  167. lists:foreach(
  168. fun({K, V}) -> print("~-30s: ~w\n", [K, V]) end,
  169. Metrics
  170. )
  171. end;
  172. 'gateway-metrics'(_) ->
  173. emqx_ctl:usage([{"gateway-metrics <Name>", "List all metrics for a gateway"}]).
  174. atom(Id) ->
  175. try
  176. list_to_existing_atom(Id)
  177. catch
  178. _:_ -> undefined
  179. end.
  180. %%--------------------------------------------------------------------
  181. %% Internal funcs
  182. %%--------------------------------------------------------------------
  183. bin(S) -> iolist_to_binary(S).
  184. dump(Table, Tag) ->
  185. dump(Table, Tag, ets:first(Table), []).
  186. dump(_Table, _, '$end_of_table', Result) ->
  187. lists:reverse(Result);
  188. dump(Table, Tag, Key, Result) ->
  189. PrintValue = [print_record({Tag, Record}) || Record <- ets:lookup(Table, Key)],
  190. dump(Table, Tag, ets:next(Table, Key), [PrintValue | Result]).
  191. print_record({client, {_, Infos, Stats}}) ->
  192. ClientInfo = maps:get(clientinfo, Infos, #{}),
  193. ConnInfo = maps:get(conninfo, Infos, #{}),
  194. _Session = maps:get(session, Infos, #{}),
  195. SafeGet = fun(K, M) -> maps:get(K, M, undefined) end,
  196. StatsGet = fun(K) -> proplists:get_value(K, Stats, 0) end,
  197. ConnectedAt = SafeGet(connected_at, ConnInfo),
  198. InfoKeys = [
  199. clientid,
  200. username,
  201. peername,
  202. clean_start,
  203. keepalive,
  204. subscriptions_cnt,
  205. send_msg,
  206. connected,
  207. created_at,
  208. connected_at
  209. ],
  210. Info = #{
  211. clientid => SafeGet(clientid, ClientInfo),
  212. username => SafeGet(username, ClientInfo),
  213. peername => SafeGet(peername, ConnInfo),
  214. clean_start => SafeGet(clean_start, ConnInfo),
  215. keepalive => SafeGet(keepalive, ConnInfo),
  216. subscriptions_cnt => StatsGet(subscriptions_cnt),
  217. send_msg => StatsGet(send_msg),
  218. connected => SafeGet(conn_state, Infos) == connected,
  219. created_at => ConnectedAt,
  220. connected_at => ConnectedAt
  221. },
  222. print(
  223. "Client(~ts, username=~ts, peername=~ts, "
  224. "clean_start=~ts, keepalive=~w, "
  225. "subscriptions=~w, delivered_msgs=~w, "
  226. "connected=~ts, created_at=~w, connected_at=~w)\n",
  227. [format(K, maps:get(K, Info)) || K <- InfoKeys]
  228. ).
  229. print(S) -> emqx_ctl:print(S).
  230. print(S, A) -> emqx_ctl:print(S, A).
  231. format(_, undefined) ->
  232. undefined;
  233. format(peername, {IPAddr, Port}) ->
  234. IPStr = emqx_mgmt_util:ntoa(IPAddr),
  235. io_lib:format("~ts:~p", [IPStr, Port]);
  236. format(_, Val) ->
  237. Val.
  238. format_gw_summary(#{name := Name, status := unloaded}) ->
  239. io_lib:format("Gateway(name=~ts, status=unloaded)\n", [Name]);
  240. format_gw_summary(#{
  241. name := Name,
  242. status := stopped,
  243. stopped_at := StoppedAt
  244. }) ->
  245. io_lib:format(
  246. "Gateway(name=~ts, status=stopped, stopped_at=~ts)\n",
  247. [Name, StoppedAt]
  248. );
  249. format_gw_summary(#{
  250. name := Name,
  251. status := running,
  252. current_connections := ConnCnt,
  253. started_at := StartedAt
  254. }) ->
  255. io_lib:format(
  256. "Gateway(name=~ts, status=running, clients=~w, "
  257. "started_at=~ts)\n",
  258. [Name, ConnCnt, StartedAt]
  259. ).
  260. format_gateway(#{
  261. name := Name,
  262. status := unloaded
  263. }) ->
  264. io_lib:format(
  265. "name: ~ts\n"
  266. "status: unloaded\n",
  267. [Name]
  268. );
  269. format_gateway(
  270. Gw =
  271. #{
  272. name := Name,
  273. status := Status,
  274. created_at := CreatedAt,
  275. config := Config
  276. }
  277. ) ->
  278. {StopOrStart, Timestamp} =
  279. case Status of
  280. stopped -> {stopped_at, maps:get(stopped_at, Gw)};
  281. running -> {started_at, maps:get(started_at, Gw)}
  282. end,
  283. io_lib:format(
  284. "name: ~ts\n"
  285. "status: ~ts\n"
  286. "created_at: ~ts\n"
  287. "~ts: ~ts\n"
  288. "config: ~p\n",
  289. [
  290. Name,
  291. Status,
  292. emqx_utils_calendar:epoch_to_rfc3339(CreatedAt),
  293. StopOrStart,
  294. emqx_utils_calendar:epoch_to_rfc3339(Timestamp),
  295. Config
  296. ]
  297. ).
  298. format_error(Reason) ->
  299. case emqx_gateway_http:reason2msg(Reason) of
  300. error -> io_lib:format("~p", [Reason]);
  301. Msg -> Msg
  302. end.