diff --git a/apps/emqx_gateway/src/emqx_gateway_api_clients.erl b/apps/emqx_gateway/src/emqx_gateway_api_clients.erl index 697bccc1d..3c902ac8d 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api_clients.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api_clients.erl @@ -532,7 +532,21 @@ params_client_searching_in_qs() -> , {lte_connected_at, mk(binary(), M#{desc => <<"Match the client socket connected datatime less than " - " a certain value">>})} + "a certain value">>})} + , {endpoint_name, + mk(binary(), + M#{desc => <<"Match the lwm2m client's endpoint name">>})} + , {like_endpoint_name, + mk(binary(), + M#{desc => <<"Use sub-string to match lwm2m client's endpoint name">>})} + , {gte_lifetime, + mk(binary(), + M#{desc => <<"Match the lwm2m client registered lifetime greater " + "than a certain value">>})} + , {lte_lifetime, + mk(binary(), + M#{desc => <<"Match the lwm2m client registered lifetime less than " + "a certain value">>})} ]. params_paging() -> diff --git a/apps/emqx_gateway/src/emqx_gateway_cli.erl b/apps/emqx_gateway/src/emqx_gateway_cli.erl index 03d55e27e..030c0057b 100644 --- a/apps/emqx_gateway/src/emqx_gateway_cli.erl +++ b/apps/emqx_gateway/src/emqx_gateway_cli.erl @@ -50,18 +50,24 @@ is_cmd(Fun) -> %% Cmds gateway(["list"]) -> - lists:foreach(fun(#{name := Name} = Gateway) -> - %% TODO: More infos: listeners?, connected? - Status = maps:get(status, Gateway, stopped), - print("Gateway(name=~ts, status=~ts)~n", [Name, Status]) - end, emqx_gateway:list()); + lists:foreach( + fun (#{name := Name, status := unloaded}) -> + print("Gateway(name=~ts, status=unloaded)\n", [Name]); + (#{name := Name, status := stopped, stopped_at := StoppedAt}) -> + print("Gateway(name=~ts, status=stopped, stopped_at=~ts)\n", + [Name, StoppedAt]); + (#{name := Name, status := running, current_connections := ConnCnt, + started_at := StartedAt}) -> + print("Gateway(name=~ts, status=running, clients=~w, started_at=~ts)\n", + [Name, ConnCnt, StartedAt]) + end, emqx_gateway_http:gateways(all)); gateway(["lookup", Name]) -> case emqx_gateway:lookup(atom(Name)) of undefined -> - print("undefined~n"); + print("undefined\n"); Info -> - print("~p~n", [Info]) + print("~p\n", [Info]) end; gateway(["load", Name, Conf]) -> @@ -70,17 +76,17 @@ gateway(["load", Name, Conf]) -> emqx_json:decode(Conf, [return_maps]) ) of {ok, _} -> - print("ok~n"); + print("ok\n"); {error, Reason} -> - print("Error: ~p~n", [Reason]) + print("Error: ~p\n", [Reason]) end; gateway(["unload", Name]) -> case emqx_gateway_conf:unload_gateway(bin(Name)) of ok -> - print("ok~n"); + print("ok\n"); {error, Reason} -> - print("Error: ~p~n", [Reason]) + print("Error: ~p\n", [Reason]) end; gateway(["stop", Name]) -> @@ -89,9 +95,9 @@ gateway(["stop", Name]) -> #{<<"enable">> => <<"false">>} ) of {ok, _} -> - print("ok~n"); + print("ok\n"); {error, Reason} -> - print("Error: ~p~n", [Reason]) + print("Error: ~p\n", [Reason]) end; gateway(["start", Name]) -> @@ -100,9 +106,9 @@ gateway(["start", Name]) -> #{<<"enable">> => <<"true">>} ) of {ok, _} -> - print("ok~n"); + print("ok\n"); {error, Reason} -> - print("Error: ~p~n", [Reason]) + print("Error: ~p\n", [Reason]) end; gateway(_) -> @@ -123,7 +129,7 @@ gateway(_) -> 'gateway-registry'(["list"]) -> lists:foreach( fun({Name, #{cbkmod := CbMod}}) -> - print("Registered Name: ~ts, Callback Module: ~ts~n", [Name, CbMod]) + print("Registered Name: ~ts, Callback Module: ~ts\n", [Name, CbMod]) end, emqx_gateway_registry:list()); @@ -137,15 +143,15 @@ gateway(_) -> InfoTab = emqx_gateway_cm:tabname(info, Name), case ets:info(InfoTab) of undefined -> - print("Bad Gateway Name.~n"); + print("Bad Gateway Name.\n"); _ -> - dump(InfoTab, client) + dump(InfoTab, client) end; 'gateway-clients'(["lookup", Name, ClientId]) -> ChanTab = emqx_gateway_cm:tabname(chan, Name), case ets:lookup(ChanTab, bin(ClientId)) of - [] -> print("Not Found.~n"); + [] -> print("Not Found.\n"); [Chann] -> InfoTab = emqx_gateway_cm:tabname(info, Name), [ChannInfo] = ets:lookup(InfoTab, Chann), @@ -154,8 +160,8 @@ gateway(_) -> 'gateway-clients'(["kick", Name, ClientId]) -> case emqx_gateway_cm:kick_session(Name, bin(ClientId)) of - ok -> print("ok~n"); - _ -> print("Not Found.~n") + ok -> print("ok\n"); + _ -> print("Not Found.\n") end; 'gateway-clients'(_) -> @@ -171,11 +177,11 @@ gateway(_) -> Tab = emqx_gateway_metrics:tabname(Name), case ets:info(Tab) of undefined -> - print("Bad Gateway Name.~n"); + print("Bad Gateway Name.\n"); _ -> lists:foreach( fun({K, V}) -> - print("~-30s: ~w~n", [K, V]) + print("~-30s: ~w\n", [K, V]) end, lists:sort(ets:tab2list(Tab))) end; @@ -232,7 +238,7 @@ print_record({client, {_, Infos, Stats}}) -> print("Client(~ts, username=~ts, peername=~ts, " "clean_start=~ts, keepalive=~w, " "subscriptions=~w, delivered_msgs=~w, " - "connected=~ts, created_at=~w, connected_at=~w)~n", + "connected=~ts, created_at=~w, connected_at=~w)\n", [format(K, maps:get(K, Info)) || K <- InfoKeys]). print(S) -> emqx_ctl:print(S). diff --git a/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl new file mode 100644 index 000000000..a2338ea26 --- /dev/null +++ b/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl @@ -0,0 +1,150 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_gateway_cli_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +-define(GP(S), begin S, receive {fmt, P} -> P; O -> O end end). + +%% this parses to #{}, will not cause config cleanup +%% so we will need call emqx_config:erase +-define(CONF_DEFAULT, <<" +gateway {} +">>). + +%%-------------------------------------------------------------------- +%% Setup +%%-------------------------------------------------------------------- + +all() -> emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Conf) -> + emqx_config:erase(gateway), + emqx_config:init_load(emqx_gateway_schema, ?CONF_DEFAULT), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), + Conf. + +end_per_suite(Conf) -> + emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]), + Conf. + +init_per_testcase(_, Conf) -> + Self = self(), + ok = meck:new(emqx_ctl, [passthrough, no_history, no_link]), + ok = meck:expect(emqx_ctl, usage, + fun(L) -> emqx_ctl:format_usage(L) end), + ok = meck:expect(emqx_ctl, print, + fun(Fmt) -> + Self ! {fmt, emqx_ctl:format(Fmt)} + end), + ok = meck:expect(emqx_ctl, print, + fun(Fmt, Args) -> + Self ! {fmt, emqx_ctl:format(Fmt, Args)} + end), + Conf. + +end_per_testcase(_, _) -> + meck:unload([emqx_ctl]), + ok. + +%%-------------------------------------------------------------------- +%% Cases +%%-------------------------------------------------------------------- + +%% TODO: + +t_load_unload(_) -> + ok. + +t_gateway_registry_usage(_) -> + ?assertEqual( + ["gateway-registry list # List all registered gateways\n"], + emqx_gateway_cli:'gateway-registry'(usage)). + +t_gateway_registry_list(_) -> + emqx_gateway_cli:'gateway-registry'(["list"]), + ?assertEqual( + "Registered Name: coap, Callback Module: emqx_coap_impl\n" + "Registered Name: exproto, Callback Module: emqx_exproto_impl\n" + "Registered Name: lwm2m, Callback Module: emqx_lwm2m_impl\n" + "Registered Name: mqttsn, Callback Module: emqx_sn_impl\n" + "Registered Name: stomp, Callback Module: emqx_stomp_impl\n" + , acc_print()). + +t_gateway_usage(_) -> + ?assertEqual( + ["gateway list # List all gateway\n", + "gateway lookup # Lookup a gateway detailed informations\n", + "gateway load # Load a gateway with config\n", + "gateway unload # Unload the gateway\n", + "gateway stop # Stop the gateway\n", + "gateway start # Start the gateway\n"], + emqx_gateway_cli:gateway(usage) + ). + +t_gateway_list(_) -> + emqx_gateway_cli:gateway(["list"]), + ?assertEqual( + "Gateway(name=coap, status=unloaded)\n" + "Gateway(name=exproto, status=unloaded)\n" + "Gateway(name=lwm2m, status=unloaded)\n" + "Gateway(name=mqttsn, status=unloaded)\n" + "Gateway(name=stomp, status=unloaded)\n" + , acc_print()). + +t_gateway_load(_) -> + ok. + +t_gateway_unload(_) -> + ok. + +t_gateway_start(_) -> + ok. + +t_gateway_stop(_) -> + ok. + +t_gateway_clients_usage(_) -> + ok. + +t_gateway_clients_list(_) -> + ok. + +t_gateway_clients_lookup(_) -> + ok. + +t_gateway_clients_kick(_) -> + ok. + +t_gateway_metrcis_usage(_) -> + ok. + +t_gateway_metrcis(_) -> + ok. + +acc_print() -> + lists:concat(lists:reverse(acc_print([]))). + +acc_print(Acc) -> + receive + {fmt, S} -> acc_print([S|Acc]) + after 200 -> + Acc + end.