diff --git a/apps/emqx_management/rebar.config b/apps/emqx_management/rebar.config index f75bd9ce5..73cbf471f 100644 --- a/apps/emqx_management/rebar.config +++ b/apps/emqx_management/rebar.config @@ -1,20 +1,28 @@ %% -*- mode: erlang -*- -{deps, [ {emqx, {path, "../emqx"}} - ]}. +{deps, [{emqx, {path, "../emqx"}}]}. {edoc_opts, [{preprocess, true}]}. -{erl_opts, [warn_unused_vars, - warn_shadow_vars, - warn_unused_import, - warn_obsolete_guard, - warnings_as_errors, - debug_info, - {parse_transform}]}. +{erl_opts, [ + warn_unused_vars, + warn_shadow_vars, + warn_unused_import, + warn_obsolete_guard, + warnings_as_errors, + debug_info, + {parse_transform} +]}. -{xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, deprecated_function_calls, - warnings_as_errors, deprecated_functions]}. +{xref_checks, [ + undefined_function_calls, + undefined_functions, + locals_not_used, + deprecated_function_calls, + warnings_as_errors, + deprecated_functions +]}. {cover_enabled, true}. {cover_opts, [verbose]}. {cover_export_enabled, true}. + +{project_plugins, [erlfmt]}. diff --git a/apps/emqx_management/src/emqx_management.app.src b/apps/emqx_management/src/emqx_management.app.src index 283eb21f0..0ca60e3d6 100644 --- a/apps/emqx_management/src/emqx_management.app.src +++ b/apps/emqx_management/src/emqx_management.app.src @@ -1,15 +1,17 @@ %% -*- mode: erlang -*- -{application, emqx_management, - [{description, "EMQX Management API and CLI"}, - {vsn, "5.0.0"}, % strict semver, bump manually! - {modules, []}, - {registered, [emqx_management_sup]}, - {applications, [kernel,stdlib,emqx_plugins,minirest,emqx]}, - {mod, {emqx_mgmt_app,[]}}, - {env, []}, - {licenses, ["Apache-2.0"]}, - {maintainers, ["EMQX Team "]}, - {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://github.com/emqx/emqx-management"} - ]} - ]}. +{application, emqx_management, [ + {description, "EMQX Management API and CLI"}, + % strict semver, bump manually! + {vsn, "5.0.0"}, + {modules, []}, + {registered, [emqx_management_sup]}, + {applications, [kernel, stdlib, emqx_plugins, minirest, emqx]}, + {mod, {emqx_mgmt_app, []}}, + {env, []}, + {licenses, ["Apache-2.0"]}, + {maintainers, ["EMQX Team "]}, + {links, [ + {"Homepage", "https://emqx.io/"}, + {"Github", "https://github.com/emqx/emqx-management"} + ]} +]}. diff --git a/apps/emqx_management/src/emqx_management_schema.erl b/apps/emqx_management/src/emqx_management_schema.erl index 937233c2c..90c05781d 100644 --- a/apps/emqx_management/src/emqx_management_schema.erl +++ b/apps/emqx_management/src/emqx_management_schema.erl @@ -19,9 +19,11 @@ -behaviour(hocon_schema). --export([ namespace/0 - , roots/0 - , fields/1]). +-export([ + namespace/0, + roots/0, + fields/1 +]). namespace() -> management. diff --git a/apps/emqx_management/src/emqx_mgmt.erl b/apps/emqx_management/src/emqx_mgmt.erl index b460b69b4..40bc1af25 100644 --- a/apps/emqx_management/src/emqx_mgmt.erl +++ b/apps/emqx_management/src/emqx_mgmt.erl @@ -25,76 +25,82 @@ -include_lib("emqx/include/emqx_mqtt.hrl"). %% Nodes and Brokers API --export([ list_nodes/0 - , lookup_node/1 - , list_brokers/0 - , lookup_broker/1 - , node_info/0 - , node_info/1 - , broker_info/0 - , broker_info/1 - ]). +-export([ + list_nodes/0, + lookup_node/1, + list_brokers/0, + lookup_broker/1, + node_info/0, + node_info/1, + broker_info/0, + broker_info/1 +]). %% Metrics and Stats --export([ get_metrics/0 - , get_metrics/1 - , get_stats/0 - , get_stats/1 - ]). +-export([ + get_metrics/0, + get_metrics/1, + get_stats/0, + get_stats/1 +]). %% Clients, Sessions --export([ lookup_client/2 - , lookup_client/3 - , kickout_client/1 - , list_authz_cache/1 - , list_client_subscriptions/1 - , client_subscriptions/2 - , clean_authz_cache/1 - , clean_authz_cache/2 - , clean_authz_cache_all/0 - , clean_authz_cache_all/1 - , set_ratelimit_policy/2 - , set_quota_policy/2 - , set_keepalive/2 - ]). +-export([ + lookup_client/2, + lookup_client/3, + kickout_client/1, + list_authz_cache/1, + list_client_subscriptions/1, + client_subscriptions/2, + clean_authz_cache/1, + clean_authz_cache/2, + clean_authz_cache_all/0, + clean_authz_cache_all/1, + set_ratelimit_policy/2, + set_quota_policy/2, + set_keepalive/2 +]). %% Internal funcs -export([do_call_client/2]). %% Subscriptions --export([ list_subscriptions/1 - , list_subscriptions_via_topic/2 - , list_subscriptions_via_topic/3 - , lookup_subscriptions/1 - , lookup_subscriptions/2 +-export([ + list_subscriptions/1, + list_subscriptions_via_topic/2, + list_subscriptions_via_topic/3, + lookup_subscriptions/1, + lookup_subscriptions/2, - , do_list_subscriptions/0 - ]). + do_list_subscriptions/0 +]). %% PubSub --export([ subscribe/2 - , do_subscribe/2 - , publish/1 - , unsubscribe/2 - , do_unsubscribe/2 - ]). +-export([ + subscribe/2, + do_subscribe/2, + publish/1, + unsubscribe/2, + do_unsubscribe/2 +]). %% Alarms --export([ get_alarms/1 - , get_alarms/2 - , deactivate/2 - , delete_all_deactivated_alarms/0 - , delete_all_deactivated_alarms/1 - ]). +-export([ + get_alarms/1, + get_alarms/2, + deactivate/2, + delete_all_deactivated_alarms/0, + delete_all_deactivated_alarms/1 +]). %% Banned --export([ create_banned/1 - , delete_banned/1 - ]). +-export([ + create_banned/1, + delete_banned/1 +]). %% Common Table API --export([ max_row_limit/0 - ]). +-export([max_row_limit/0]). -define(APP, emqx_management). @@ -113,24 +119,26 @@ list_nodes() -> lookup_node(Node) -> node_info(Node). node_info() -> - Memory = emqx_vm:get_memory(), + Memory = emqx_vm:get_memory(), Info = maps:from_list([{K, list_to_binary(V)} || {K, V} <- emqx_vm:loads()]), BrokerInfo = emqx_sys:info(), - Info#{node => node(), - otp_release => otp_rel(), - memory_total => proplists:get_value(allocated, Memory), - memory_used => proplists:get_value(used, Memory), - process_available => erlang:system_info(process_limit), - process_used => erlang:system_info(process_count), + Info#{ + node => node(), + otp_release => otp_rel(), + memory_total => proplists:get_value(allocated, Memory), + memory_used => proplists:get_value(used, Memory), + process_available => erlang:system_info(process_limit), + process_used => erlang:system_info(process_count), - max_fds => proplists:get_value( - max_fds, lists:usort(lists:flatten(erlang:system_info(check_io)))), - connections => ets:info(emqx_channel, size), - node_status => 'Running', - uptime => proplists:get_value(uptime, BrokerInfo), - version => iolist_to_binary(proplists:get_value(version, BrokerInfo)), - role => mria_rlog:role() - }. + max_fds => proplists:get_value( + max_fds, lists:usort(lists:flatten(erlang:system_info(check_io))) + ), + connections => ets:info(emqx_channel, size), + node_status => 'Running', + uptime => proplists:get_value(uptime, BrokerInfo), + version => iolist_to_binary(proplists:get_value(version, BrokerInfo)), + role => mria_rlog:role() + }. node_info(Node) -> wrap_rpc(emqx_management_proto_v1:node_info(Node)). @@ -167,18 +175,21 @@ get_metrics(Node) -> get_stats() -> GlobalStatsKeys = - [ 'retained.count' - , 'retained.max' - , 'topics.count' - , 'topics.max' - , 'subscriptions.shared.count' - , 'subscriptions.shared.max' + [ + 'retained.count', + 'retained.max', + 'topics.count', + 'topics.max', + 'subscriptions.shared.count', + 'subscriptions.shared.max' ], CountStats = nodes_info_count([ begin Stats = get_stats(Node), delete_keys(Stats, GlobalStatsKeys) - end || Node <- mria_mnesia:running_nodes()]), + end + || Node <- mria_mnesia:running_nodes() + ]), GlobalStats = maps:with(GlobalStatsKeys, maps:from_list(get_stats(node()))), maps:merge(CountStats, GlobalStats). @@ -207,21 +218,28 @@ nodes_info_count(PropList) -> %%-------------------------------------------------------------------- lookup_client({clientid, ClientId}, FormatFun) -> - lists:append([lookup_client(Node, {clientid, ClientId}, FormatFun) - || Node <- mria_mnesia:running_nodes()]); - + lists:append([ + lookup_client(Node, {clientid, ClientId}, FormatFun) + || Node <- mria_mnesia:running_nodes() + ]); lookup_client({username, Username}, FormatFun) -> - lists:append([lookup_client(Node, {username, Username}, FormatFun) - || Node <- mria_mnesia:running_nodes()]). + lists:append([ + lookup_client(Node, {username, Username}, FormatFun) + || Node <- mria_mnesia:running_nodes() + ]). lookup_client(Node, Key, {M, F}) -> case wrap_rpc(emqx_cm_proto_v1:lookup_client(Node, Key)) of - {error, Err} -> {error, Err}; - L -> lists:map(fun({Chan, Info0, Stats}) -> - Info = Info0#{node => Node}, - M:F({Chan, Info, Stats}) - end, - L) + {error, Err} -> + {error, Err}; + L -> + lists:map( + fun({Chan, Info0, Stats}) -> + Info = Info0#{node => Node}, + M:F({Chan, Info, Stats}) + end, + L + ) end. kickout_client({ClientID, FormatFun}) -> @@ -266,7 +284,7 @@ clean_authz_cache(Node, ClientId) -> clean_authz_cache_all() -> Results = [{Node, clean_authz_cache_all(Node)} || Node <- mria_mnesia:running_nodes()], case lists:filter(fun({_Node, Item}) -> Item =/= ok end, Results) of - [] -> ok; + [] -> ok; BadNodes -> {error, BadNodes} end. @@ -287,9 +305,13 @@ set_keepalive(_ClientId, _Interval) -> %% @private call_client(ClientId, Req) -> Results = [call_client(Node, ClientId, Req) || Node <- mria_mnesia:running_nodes()], - Expected = lists:filter(fun({error, _}) -> false; - (_) -> true - end, Results), + Expected = lists:filter( + fun + ({error, _}) -> false; + (_) -> true + end, + Results + ), case Expected of [] -> {error, not_found}; [Result | _] -> Result @@ -299,13 +321,15 @@ call_client(ClientId, Req) -> -spec do_call_client(emqx_types:clientid(), term()) -> term(). do_call_client(ClientId, Req) -> case emqx_cm:lookup_channels(ClientId) of - [] -> {error, not_found}; + [] -> + {error, not_found}; Pids when is_list(Pids) -> Pid = lists:last(Pids), case emqx_cm:get_chan_info(ClientId, Pid) of #{conninfo := #{conn_mod := ConnMod}} -> erlang:apply(ConnMod, call, [Pid, Req]); - undefined -> {error, not_found} + undefined -> + {error, not_found} end end. @@ -320,22 +344,28 @@ call_client(Node, ClientId, Req) -> -spec do_list_subscriptions() -> [map()]. do_list_subscriptions() -> case check_row_limit([mqtt_subproperty]) of - false -> throw(max_row_limit); - ok -> [#{topic => Topic, clientid => ClientId, options => Options} - || {{Topic, ClientId}, Options} <- ets:tab2list(mqtt_subproperty)] + false -> + throw(max_row_limit); + ok -> + [ + #{topic => Topic, clientid => ClientId, options => Options} + || {{Topic, ClientId}, Options} <- ets:tab2list(mqtt_subproperty) + ] end. list_subscriptions(Node) -> wrap_rpc(emqx_management_proto_v1:list_subscriptions(Node)). list_subscriptions_via_topic(Topic, FormatFun) -> - lists:append([list_subscriptions_via_topic(Node, Topic, FormatFun) - || Node <- mria_mnesia:running_nodes()]). + lists:append([ + list_subscriptions_via_topic(Node, Topic, FormatFun) + || Node <- mria_mnesia:running_nodes() + ]). list_subscriptions_via_topic(Node, Topic, _FormatFun = {M, F}) -> case wrap_rpc(emqx_broker_proto_v1:list_subscriptions_via_topic(Node, Topic)) of {error, Reason} -> {error, Reason}; - Result -> M:F(Result) + Result -> M:F(Result) end. lookup_subscriptions(ClientId) -> @@ -354,20 +384,17 @@ subscribe(ClientId, TopicTables) -> subscribe([Node | Nodes], ClientId, TopicTables) -> case wrap_rpc(emqx_management_proto_v1:subscribe(Node, ClientId, TopicTables)) of {error, _} -> subscribe(Nodes, ClientId, TopicTables); - {subscribe, Res} -> - {subscribe, Res, Node} + {subscribe, Res} -> {subscribe, Res, Node} end; - subscribe([], _ClientId, _TopicTables) -> {error, channel_not_found}. -spec do_subscribe(emqx_types:clientid(), emqx_types:topic_filters()) -> - {subscribe, _} | {error, atom()}. + {subscribe, _} | {error, atom()}. do_subscribe(ClientId, TopicTables) -> case ets:lookup(emqx_channel, ClientId) of [] -> {error, channel_not_found}; - [{_, Pid}] -> - Pid ! {subscribe, TopicTables} + [{_, Pid}] -> Pid ! {subscribe, TopicTables} end. %%TODO: ??? @@ -376,12 +403,12 @@ publish(Msg) -> emqx:publish(Msg). -spec unsubscribe(emqx_types:clientid(), emqx_types:topic()) -> - {unsubscribe, _} | {error, channel_not_found}. + {unsubscribe, _} | {error, channel_not_found}. unsubscribe(ClientId, Topic) -> unsubscribe(mria_mnesia:running_nodes(), ClientId, Topic). -spec unsubscribe([node()], emqx_types:clientid(), emqx_types:topic()) -> - {unsubscribe, _} | {error, channel_not_found}. + {unsubscribe, _} | {error, channel_not_found}. unsubscribe([Node | Nodes], ClientId, Topic) -> case wrap_rpc(emqx_management_proto_v1:unsubscribe(Node, ClientId, Topic)) of {error, _} -> unsubscribe(Nodes, ClientId, Topic); @@ -391,12 +418,11 @@ unsubscribe([], _ClientId, _Topic) -> {error, channel_not_found}. -spec do_unsubscribe(emqx_types:clientid(), emqx_types:topic()) -> - {unsubscribe, _} | {error, _}. + {unsubscribe, _} | {error, _}. do_unsubscribe(ClientId, Topic) -> case ets:lookup(emqx_channel, ClientId) of [] -> {error, channel_not_found}; - [{_, Pid}] -> - Pid ! {unsubscribe, [emqx_topic:parse(Topic)]} + [{_, Pid}] -> Pid ! {unsubscribe, [emqx_topic:parse(Topic)]} end. %%-------------------------------------------------------------------- @@ -426,11 +452,18 @@ add_duration_field([], _Now, Acc) -> Acc; add_duration_field([Alarm = #{activated := true, activate_at := ActivateAt} | Rest], Now, Acc) -> add_duration_field(Rest, Now, [Alarm#{duration => Now - ActivateAt} | Acc]); - -add_duration_field( [Alarm = #{ activated := false - , activate_at := ActivateAt - , deactivate_at := DeactivateAt} | Rest] - , Now, Acc) -> +add_duration_field( + [ + Alarm = #{ + activated := false, + activate_at := ActivateAt, + deactivate_at := DeactivateAt + } + | Rest + ], + Now, + Acc +) -> add_duration_field(Rest, Now, [Alarm#{duration => DeactivateAt - ActivateAt} | Acc]). %%-------------------------------------------------------------------- @@ -462,13 +495,13 @@ check_row_limit([], _Limit) -> ok; check_row_limit([Tab | Tables], Limit) -> case table_size(Tab) > Limit of - true -> false; + true -> false; false -> check_row_limit(Tables, Limit) end. check_results(Results) -> case lists:any(fun(Item) -> Item =:= ok end, Results) of - true -> ok; + true -> ok; false -> wrap_rpc(lists:last(Results)) end. diff --git a/apps/emqx_management/src/emqx_mgmt_api.erl b/apps/emqx_management/src/emqx_mgmt_api.erl index 36d647f57..342228b19 100644 --- a/apps/emqx_management/src/emqx_mgmt_api.erl +++ b/apps/emqx_management/src/emqx_mgmt_api.erl @@ -22,16 +22,18 @@ -define(FRESH_SELECT, fresh_select). --export([ paginate/3 - , paginate/4 - ]). +-export([ + paginate/3, + paginate/4 +]). %% first_next query APIs --export([ node_query/5 - , cluster_query/4 - , select_table_with_count/5 - , b2i/1 - ]). +-export([ + node_query/5, + cluster_query/4, + select_table_with_count/5, + b2i/1 +]). -export([do_query/6]). @@ -50,30 +52,30 @@ do_paginate(Qh, Count, Params, {Module, FormatFun}) -> Limit = b2i(limit(Params)), Cursor = qlc:cursor(Qh), case Page > 1 of - true -> + true -> _ = qlc:next_answers(Cursor, (Page - 1) * Limit), ok; - false -> ok + false -> + ok end, Rows = qlc:next_answers(Cursor, Limit), qlc:delete_cursor(Cursor), - #{meta => #{page => Page, limit => Limit, count => Count}, - data => [erlang:apply(Module, FormatFun, [Row]) || Row <- Rows]}. + #{ + meta => #{page => Page, limit => Limit, count => Count}, + data => [erlang:apply(Module, FormatFun, [Row]) || Row <- Rows] + }. query_handle(Table) when is_atom(Table) -> qlc:q([R || R <- ets:table(Table)]); - query_handle({Table, Opts}) when is_atom(Table) -> qlc:q([R || R <- ets:table(Table, Opts)]); - query_handle([Table]) when is_atom(Table) -> qlc:q([R || R <- ets:table(Table)]); - query_handle([{Table, Opts}]) when is_atom(Table) -> qlc:q([R || R <- ets:table(Table, Opts)]); - query_handle(Tables) -> - qlc:append([query_handle(T) || T <- Tables]). % + % + qlc:append([query_handle(T) || T <- Tables]). query_handle(Table, MatchSpec) when is_atom(Table) -> Options = {traverse, {select, MatchSpec}}, @@ -87,16 +89,12 @@ query_handle(Tables, MatchSpec) -> count(Table) when is_atom(Table) -> ets:info(Table, size); - count({Table, _}) when is_atom(Table) -> ets:info(Table, size); - count([Table]) when is_atom(Table) -> ets:info(Table, size); - count([{Table, _}]) when is_atom(Table) -> ets:info(Table, size); - count(Tables) -> lists:sum([count(T) || T <- Tables]). @@ -121,7 +119,7 @@ limit(Params) -> init_meta(Params) -> Limit = b2i(limit(Params)), - Page = b2i(page(Params)), + Page = b2i(page(Params)), #{ page => Page, limit => Limit, @@ -134,17 +132,24 @@ init_meta(Params) -> node_query(Node, QString, Tab, QSchema, QueryFun) -> {_CodCnt, NQString} = parse_qstring(QString, QSchema), - page_limit_check_query( init_meta(QString) - , { fun do_node_query/5 - , [Node, Tab, NQString, QueryFun, init_meta(QString)]}). + page_limit_check_query( + init_meta(QString), + {fun do_node_query/5, [Node, Tab, NQString, QueryFun, init_meta(QString)]} + ). %% @private do_node_query(Node, Tab, QString, QueryFun, Meta) -> do_node_query(Node, Tab, QString, QueryFun, _Continuation = ?FRESH_SELECT, Meta, _Results = []). -do_node_query( Node, Tab, QString, QueryFun, Continuation - , Meta = #{limit := Limit} - , Results) -> +do_node_query( + Node, + Tab, + QString, + QueryFun, + Continuation, + Meta = #{limit := Limit}, + Results +) -> case do_query(Node, Tab, QString, QueryFun, Continuation, Limit) of {error, {badrpc, R}} -> {error, Node, {badrpc, R}}; @@ -164,18 +169,33 @@ cluster_query(QString, Tab, QSchema, QueryFun) -> {_CodCnt, NQString} = parse_qstring(QString, QSchema), Nodes = mria_mnesia:running_nodes(), page_limit_check_query( - init_meta(QString) - , {fun do_cluster_query/5, [Nodes, Tab, NQString, QueryFun, init_meta(QString)]}). + init_meta(QString), + {fun do_cluster_query/5, [Nodes, Tab, NQString, QueryFun, init_meta(QString)]} + ). %% @private do_cluster_query(Nodes, Tab, QString, QueryFun, Meta) -> - do_cluster_query( Nodes, Tab, QString, QueryFun - , _Continuation = ?FRESH_SELECT, Meta, _Results = []). + do_cluster_query( + Nodes, + Tab, + QString, + QueryFun, + _Continuation = ?FRESH_SELECT, + Meta, + _Results = [] + ). do_cluster_query([], _Tab, _QString, _QueryFun, _Continuation, Meta, Results) -> #{meta => Meta, data => Results}; -do_cluster_query([Node | Tail] = Nodes, Tab, QString, QueryFun, Continuation, - Meta = #{limit := Limit}, Results) -> +do_cluster_query( + [Node | Tail] = Nodes, + Tab, + QString, + QueryFun, + Continuation, + Meta = #{limit := Limit}, + Results +) -> case do_query(Node, Tab, QString, QueryFun, Continuation, Limit) of {error, {badrpc, R}} -> {error, Node, {bar_rpc, R}}; @@ -192,11 +212,18 @@ do_cluster_query([Node | Tail] = Nodes, Tab, QString, QueryFun, Continuation, %%-------------------------------------------------------------------- %% @private This function is exempt from BPAPI -do_query(Node, Tab, QString, {M,F}, Continuation, Limit) when Node =:= node() -> +do_query(Node, Tab, QString, {M, F}, Continuation, Limit) when Node =:= node() -> erlang:apply(M, F, [Tab, QString, Continuation, Limit]); do_query(Node, Tab, QString, QueryFun, Continuation, Limit) -> - case rpc:call(Node, ?MODULE, do_query, - [Node, Tab, QString, QueryFun, Continuation, Limit], 50000) of + case + rpc:call( + Node, + ?MODULE, + do_query, + [Node, Tab, QString, QueryFun, Continuation, Limit], + 50000 + ) + of {badrpc, _} = R -> {error, R}; Ret -> Ret end. @@ -220,8 +247,9 @@ sub_query_result(Len, Rows, Limit, Results, Meta) -> %% Table Select %%-------------------------------------------------------------------- -select_table_with_count(Tab, {Ms, FuzzyFilterFun}, ?FRESH_SELECT, Limit, FmtFun) - when is_function(FuzzyFilterFun) andalso Limit > 0 -> +select_table_with_count(Tab, {Ms, FuzzyFilterFun}, ?FRESH_SELECT, Limit, FmtFun) when + is_function(FuzzyFilterFun) andalso Limit > 0 +-> case ets:select(Tab, Ms, Limit) of '$end_of_table' -> {0, [], ?FRESH_SELECT}; @@ -229,8 +257,9 @@ select_table_with_count(Tab, {Ms, FuzzyFilterFun}, ?FRESH_SELECT, Limit, FmtFun) Rows = FuzzyFilterFun(RawResult), {length(Rows), lists:map(FmtFun, Rows), NContinuation} end; -select_table_with_count(_Tab, {Ms, FuzzyFilterFun}, Continuation, _Limit, FmtFun) - when is_function(FuzzyFilterFun) -> +select_table_with_count(_Tab, {Ms, FuzzyFilterFun}, Continuation, _Limit, FmtFun) when + is_function(FuzzyFilterFun) +-> case ets:select(ets:repair_continuation(Continuation, Ms)) of '$end_of_table' -> {0, [], ?FRESH_SELECT}; @@ -238,8 +267,9 @@ select_table_with_count(_Tab, {Ms, FuzzyFilterFun}, Continuation, _Limit, FmtFun Rows = FuzzyFilterFun(RawResult), {length(Rows), lists:map(FmtFun, Rows), NContinuation} end; -select_table_with_count(Tab, Ms, ?FRESH_SELECT, Limit, FmtFun) - when Limit > 0 -> +select_table_with_count(Tab, Ms, ?FRESH_SELECT, Limit, FmtFun) when + Limit > 0 +-> case ets:select(Tab, Ms, Limit) of '$end_of_table' -> {0, [], ?FRESH_SELECT}; @@ -267,36 +297,53 @@ parse_qstring(QString, QSchema) -> do_parse_qstring([], _, Acc1, Acc2) -> NAcc2 = [E || E <- Acc2, not lists:keymember(element(1, E), 1, Acc1)], {lists:reverse(Acc1), lists:reverse(NAcc2)}; - do_parse_qstring([{Key, Value} | RestQString], QSchema, Acc1, Acc2) -> case proplists:get_value(Key, QSchema) of - undefined -> do_parse_qstring(RestQString, QSchema, Acc1, Acc2); + undefined -> + do_parse_qstring(RestQString, QSchema, Acc1, Acc2); Type -> case Key of - <> - when Prefix =:= <<"gte_">>; - Prefix =:= <<"lte_">> -> - OpposeKey = case Prefix of - <<"gte_">> -> <<"lte_", NKey/binary>>; - <<"lte_">> -> <<"gte_", NKey/binary>> - end, + <> when + Prefix =:= <<"gte_">>; + Prefix =:= <<"lte_">> + -> + OpposeKey = + case Prefix of + <<"gte_">> -> <<"lte_", NKey/binary>>; + <<"lte_">> -> <<"gte_", NKey/binary>> + end, case lists:keytake(OpposeKey, 1, RestQString) of false -> - do_parse_qstring( RestQString, QSchema - , [qs(Key, Value, Type) | Acc1], Acc2); + do_parse_qstring( + RestQString, + QSchema, + [qs(Key, Value, Type) | Acc1], + Acc2 + ); {value, {K2, V2}, NParams} -> - do_parse_qstring( NParams, QSchema - , [qs(Key, Value, K2, V2, Type) | Acc1], Acc2) + do_parse_qstring( + NParams, + QSchema, + [qs(Key, Value, K2, V2, Type) | Acc1], + Acc2 + ) end; _ -> case is_fuzzy_key(Key) of true -> - do_parse_qstring( RestQString, QSchema - , Acc1, [qs(Key, Value, Type) | Acc2]); + do_parse_qstring( + RestQString, + QSchema, + Acc1, + [qs(Key, Value, Type) | Acc2] + ); _ -> - do_parse_qstring( RestQString, QSchema - , [qs(Key, Value, Type) | Acc1], Acc2) - + do_parse_qstring( + RestQString, + QSchema, + [qs(Key, Value, Type) | Acc1], + Acc2 + ) end end end. @@ -310,7 +357,7 @@ qs(K, Value0, Type) -> try qs(K, to_type(Value0, Type)) catch - throw : bad_value_type -> + throw:bad_value_type -> throw({bad_value_type, {K, Type, Value0}}) end. @@ -333,12 +380,11 @@ is_fuzzy_key(_) -> false. page_start(1, _) -> 1; -page_start(Page, Limit) -> (Page-1) * Limit + 1. - +page_start(Page, Limit) -> (Page - 1) * Limit + 1. judge_page_with_counting(Len, Meta = #{page := Page, limit := Limit, count := Count}) -> PageStart = page_start(Page, Limit), - PageEnd = Page * Limit, + PageEnd = Page * Limit, case Count + Len of NCount when NCount < PageStart -> {more, Meta#{count => NCount}}; @@ -353,7 +399,7 @@ rows_sub_params(Len, _Meta = #{page := Page, limit := Limit, count := Count}) -> case (Count - Len) < PageStart of true -> NeedNowNum = Count - PageStart + 1, - SubStart = Len - NeedNowNum + 1, + SubStart = Len - NeedNowNum + 1, {SubStart, NeedNowNum}; false -> {_SubStart = 1, _NeedNowNum = Len} @@ -361,8 +407,9 @@ rows_sub_params(Len, _Meta = #{page := Page, limit := Limit, count := Count}) -> page_limit_check_query(Meta, {F, A}) -> case Meta of - #{page := Page, limit := Limit} - when Page < 1; Limit < 1 -> + #{page := Page, limit := Limit} when + Page < 1; Limit < 1 + -> {error, page_limit_invalid}; _ -> erlang:apply(F, A) @@ -376,7 +423,7 @@ to_type(V, TargetType) -> try to_type_(V, TargetType) catch - _ : _ -> + _:_ -> throw(bad_value_type) end. @@ -419,37 +466,43 @@ to_ip_port(IPAddress) -> -include_lib("eunit/include/eunit.hrl"). params2qs_test() -> - QSchema = [{<<"str">>, binary}, - {<<"int">>, integer}, - {<<"atom">>, atom}, - {<<"ts">>, timestamp}, - {<<"gte_range">>, integer}, - {<<"lte_range">>, integer}, - {<<"like_fuzzy">>, binary}, - {<<"match_topic">>, binary}], - QString = [{<<"str">>, <<"abc">>}, - {<<"int">>, <<"123">>}, - {<<"atom">>, <<"connected">>}, - {<<"ts">>, <<"156000">>}, - {<<"gte_range">>, <<"1">>}, - {<<"lte_range">>, <<"5">>}, - {<<"like_fuzzy">>, <<"user">>}, - {<<"match_topic">>, <<"t/#">>}], - ExpectedQs = [{str, '=:=', <<"abc">>}, - {int, '=:=', 123}, - {atom, '=:=', connected}, - {ts, '=:=', 156000}, - {range, '>=', 1, '=<', 5} - ], - FuzzyNQString = [{fuzzy, like, <<"user">>}, - {topic, match, <<"t/#">>}], + QSchema = [ + {<<"str">>, binary}, + {<<"int">>, integer}, + {<<"atom">>, atom}, + {<<"ts">>, timestamp}, + {<<"gte_range">>, integer}, + {<<"lte_range">>, integer}, + {<<"like_fuzzy">>, binary}, + {<<"match_topic">>, binary} + ], + QString = [ + {<<"str">>, <<"abc">>}, + {<<"int">>, <<"123">>}, + {<<"atom">>, <<"connected">>}, + {<<"ts">>, <<"156000">>}, + {<<"gte_range">>, <<"1">>}, + {<<"lte_range">>, <<"5">>}, + {<<"like_fuzzy">>, <<"user">>}, + {<<"match_topic">>, <<"t/#">>} + ], + ExpectedQs = [ + {str, '=:=', <<"abc">>}, + {int, '=:=', 123}, + {atom, '=:=', connected}, + {ts, '=:=', 156000}, + {range, '>=', 1, '=<', 5} + ], + FuzzyNQString = [ + {fuzzy, like, <<"user">>}, + {topic, match, <<"t/#">>} + ], ?assertEqual({7, {ExpectedQs, FuzzyNQString}}, parse_qstring(QString, QSchema)), {0, {[], []}} = parse_qstring([{not_a_predefined_params, val}], QSchema). -endif. - b2i(Bin) when is_binary(Bin) -> binary_to_integer(Bin); b2i(Any) -> diff --git a/apps/emqx_management/src/emqx_mgmt_api_alarms.erl b/apps/emqx_management/src/emqx_mgmt_api_alarms.erl index f5685993a..5c455b4e4 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_alarms.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_alarms.erl @@ -43,9 +43,12 @@ schema("/alarms") -> parameters => [ hoconsc:ref(emqx_dashboard_swagger, page), hoconsc:ref(emqx_dashboard_swagger, limit), - {activated, hoconsc:mk(boolean(), #{in => query, - desc => ?DESC(get_alarms_qs_activated), - required => false})} + {activated, + hoconsc:mk(boolean(), #{ + in => query, + desc => ?DESC(get_alarms_qs_activated), + required => false + })} ], responses => #{ 200 => [ @@ -54,7 +57,7 @@ schema("/alarms") -> ] } }, - delete => #{ + delete => #{ description => ?DESC(delete_alarms_api), responses => #{ 204 => ?DESC(delete_alarms_api_response204) @@ -64,21 +67,38 @@ schema("/alarms") -> fields(alarm) -> [ - {node, hoconsc:mk(binary(), - #{desc => ?DESC(node), example => atom_to_list(node())})}, - {name, hoconsc:mk(binary(), - #{desc => ?DESC(node), example => <<"high_system_memory_usage">>})}, - {message, hoconsc:mk(binary(), #{desc => ?DESC(message), - example => <<"System memory usage is higher than 70%">>})}, - {details, hoconsc:mk(map(), #{desc => ?DESC(details), - example => #{<<"high_watermark">> => 70}})}, + {node, + hoconsc:mk( + binary(), + #{desc => ?DESC(node), example => atom_to_list(node())} + )}, + {name, + hoconsc:mk( + binary(), + #{desc => ?DESC(node), example => <<"high_system_memory_usage">>} + )}, + {message, + hoconsc:mk(binary(), #{ + desc => ?DESC(message), + example => <<"System memory usage is higher than 70%">> + })}, + {details, + hoconsc:mk(map(), #{ + desc => ?DESC(details), + example => #{<<"high_watermark">> => 70} + })}, {duration, hoconsc:mk(integer(), #{desc => ?DESC(duration), example => 297056})}, - {activate_at, hoconsc:mk(binary(), #{desc => ?DESC(activate_at), - example => <<"2021-10-25T11:52:52.548+08:00">>})}, - {deactivate_at, hoconsc:mk(binary(), #{desc => ?DESC(deactivate_at), - example => <<"2021-10-31T10:52:52.548+08:00">>})} + {activate_at, + hoconsc:mk(binary(), #{ + desc => ?DESC(activate_at), + example => <<"2021-10-25T11:52:52.548+08:00">> + })}, + {deactivate_at, + hoconsc:mk(binary(), #{ + desc => ?DESC(deactivate_at), + example => <<"2021-10-31T10:52:52.548+08:00">> + })} ]; - fields(meta) -> emqx_dashboard_swagger:fields(page) ++ emqx_dashboard_swagger:fields(limit) ++ @@ -100,7 +120,6 @@ alarms(get, #{query_string := QString}) -> Response -> {200, Response} end; - alarms(delete, _Params) -> _ = emqx_mgmt:delete_all_deactivated_alarms(), {204}. @@ -109,11 +128,10 @@ alarms(delete, _Params) -> %% internal query(Table, _QsSpec, Continuation, Limit) -> - Ms = [{'$1',[],['$1']}], + Ms = [{'$1', [], ['$1']}], emqx_mgmt_api:select_table_with_count(Table, Ms, Continuation, Limit, fun format_alarm/1). format_alarm(Alarms) when is_list(Alarms) -> [emqx_alarm:format(Alarm) || Alarm <- Alarms]; - format_alarm(Alarm) -> emqx_alarm:format(Alarm). diff --git a/apps/emqx_management/src/emqx_mgmt_api_app.erl b/apps/emqx_management/src/emqx_mgmt_api_app.erl index 239e9d3a6..3668a4aea 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_app.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_app.erl @@ -31,7 +31,6 @@ api_spec() -> paths() -> ["/api_key", "/api_key/:name"]. - schema("/api_key") -> #{ 'operationId' => api_key, @@ -82,41 +81,80 @@ schema("/api_key/:name") -> fields(app) -> [ - {name, hoconsc:mk(binary(), - #{desc => "Unique and format by [a-zA-Z0-9-_]", - validator => fun ?MODULE:validate_name/1, - example => <<"EMQX-API-KEY-1">>})}, - {api_key, hoconsc:mk(binary(), - #{desc => """TODO:uses HMAC-SHA256 for signing.""", - example => <<"a4697a5c75a769f6">>})}, - {api_secret, hoconsc:mk(binary(), - #{desc => """An API secret is a simple encrypted string that identifies""" - """an application without any principal.""" - """They are useful for accessing public data anonymously,""" - """and are used to associate API requests.""", - example => <<"MzAyMjk3ODMwMDk0NjIzOTUxNjcwNzQ0NzQ3MTE2NDYyMDI">>})}, - {expired_at, hoconsc:mk(hoconsc:union([undefined, emqx_datetime:epoch_second()]), - #{desc => "No longer valid datetime", - example => <<"2021-12-05T02:01:34.186Z">>, - required => false, - default => undefined - })}, - {created_at, hoconsc:mk(emqx_datetime:epoch_second(), - #{desc => "ApiKey create datetime", - example => <<"2021-12-01T00:00:00.000Z">> - })}, - {desc, hoconsc:mk(binary(), - #{example => <<"Note">>, required => false})}, + {name, + hoconsc:mk( + binary(), + #{ + desc => "Unique and format by [a-zA-Z0-9-_]", + validator => fun ?MODULE:validate_name/1, + example => <<"EMQX-API-KEY-1">> + } + )}, + {api_key, + hoconsc:mk( + binary(), + #{ + desc => "" "TODO:uses HMAC-SHA256 for signing." "", + example => <<"a4697a5c75a769f6">> + } + )}, + {api_secret, + hoconsc:mk( + binary(), + #{ + desc => + "" + "An API secret is a simple encrypted string that identifies" + "" + "" + "an application without any principal." + "" + "" + "They are useful for accessing public data anonymously," + "" + "" + "and are used to associate API requests." + "", + example => <<"MzAyMjk3ODMwMDk0NjIzOTUxNjcwNzQ0NzQ3MTE2NDYyMDI">> + } + )}, + {expired_at, + hoconsc:mk( + hoconsc:union([undefined, emqx_datetime:epoch_second()]), + #{ + desc => "No longer valid datetime", + example => <<"2021-12-05T02:01:34.186Z">>, + required => false, + default => undefined + } + )}, + {created_at, + hoconsc:mk( + emqx_datetime:epoch_second(), + #{ + desc => "ApiKey create datetime", + example => <<"2021-12-01T00:00:00.000Z">> + } + )}, + {desc, + hoconsc:mk( + binary(), + #{example => <<"Note">>, required => false} + )}, {enable, hoconsc:mk(boolean(), #{desc => "Enable/Disable", required => false})} ]; fields(name) -> - [{name, hoconsc:mk(binary(), - #{ - desc => <<"^[A-Za-z]+[A-Za-z0-9-_]*$">>, - example => <<"EMQX-API-KEY-1">>, - in => path, - validator => fun ?MODULE:validate_name/1 - })} + [ + {name, + hoconsc:mk( + binary(), + #{ + desc => <<"^[A-Za-z]+[A-Za-z0-9-_]*$">>, + example => <<"EMQX-API-KEY-1">>, + in => path, + validator => fun ?MODULE:validate_name/1 + } + )} ]. -define(NAME_RE, "^[A-Za-z]+[A-Za-z0-9-_]*$"). @@ -129,7 +167,8 @@ validate_name(Name) -> nomatch -> {error, "Name should be " ?NAME_RE}; _ -> ok end; - false -> {error, "Name Length must =< 256"} + false -> + {error, "Name Length must =< 256"} end. delete(Keys, Fields) -> @@ -146,10 +185,13 @@ api_key(post, #{body := App}) -> ExpiredAt = ensure_expired_at(App), Desc = unicode:characters_to_binary(Desc0, unicode), case emqx_mgmt_auth:create(Name, Enable, ExpiredAt, Desc) of - {ok, NewApp} -> {200, format(NewApp)}; + {ok, NewApp} -> + {200, format(NewApp)}; {error, Reason} -> - {400, #{code => 'BAD_REQUEST', - message => iolist_to_binary(io_lib:format("~p", [Reason]))}} + {400, #{ + code => 'BAD_REQUEST', + message => iolist_to_binary(io_lib:format("~p", [Reason])) + }} end. -define(NOT_FOUND_RESPONSE, #{code => 'NOT_FOUND', message => <<"Name NOT FOUND">>}). @@ -184,5 +226,5 @@ format(App = #{expired_at := ExpiredAt0, created_at := CreateAt}) -> created_at => list_to_binary(calendar:system_time_to_rfc3339(CreateAt)) }. -ensure_expired_at(#{<<"expired_at">> := ExpiredAt})when is_integer(ExpiredAt) -> ExpiredAt; +ensure_expired_at(#{<<"expired_at">> := ExpiredAt}) when is_integer(ExpiredAt) -> ExpiredAt; ensure_expired_at(_) -> undefined. diff --git a/apps/emqx_management/src/emqx_mgmt_api_banned.erl b/apps/emqx_management/src/emqx_mgmt_api_banned.erl index 52fb64b75..fd542e1f4 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_banned.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_banned.erl @@ -24,16 +24,19 @@ -behaviour(minirest_api). --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). -export([format/1]). --export([ banned/2 - , delete_banned/2 - ]). +-export([ + banned/2, + delete_banned/2 +]). -define(TAB, emqx_banned). @@ -49,7 +52,7 @@ paths() -> schema("/banned") -> #{ - 'operationId' => banned, + 'operationId' => banned, get => #{ description => ?DESC(list_banned_api), parameters => [ @@ -57,7 +60,7 @@ schema("/banned") -> hoconsc:ref(emqx_dashboard_swagger, limit) ], responses => #{ - 200 =>[ + 200 => [ {data, hoconsc:mk(hoconsc:array(hoconsc:ref(ban)), #{})}, {meta, hoconsc:mk(hoconsc:ref(meta), #{})} ] @@ -69,8 +72,9 @@ schema("/banned") -> responses => #{ 200 => [{data, hoconsc:mk(hoconsc:array(hoconsc:ref(ban)), #{})}], 400 => emqx_dashboard_swagger:error_codes( - ['ALREADY_EXISTS', 'BAD_REQUEST'], - ?DESC(create_banned_api_response400)) + ['ALREADY_EXISTS', 'BAD_REQUEST'], + ?DESC(create_banned_api_response400) + ) } } }; @@ -80,51 +84,67 @@ schema("/banned/:as/:who") -> delete => #{ description => ?DESC(delete_banned_api), parameters => [ - {as, hoconsc:mk(hoconsc:enum(?BANNED_TYPES), #{ - desc => ?DESC(as), - in => path, - example => username})}, - {who, hoconsc:mk(binary(), #{ - desc => ?DESC(who), - in => path, - example => <<"Badass">>})} - ], + {as, + hoconsc:mk(hoconsc:enum(?BANNED_TYPES), #{ + desc => ?DESC(as), + in => path, + example => username + })}, + {who, + hoconsc:mk(binary(), #{ + desc => ?DESC(who), + in => path, + example => <<"Badass">> + })} + ], responses => #{ 204 => <<"Delete banned success">>, 404 => emqx_dashboard_swagger:error_codes( - ['NOT_FOUND'], - ?DESC(delete_banned_api_response404)) + ['NOT_FOUND'], + ?DESC(delete_banned_api_response404) + ) } } }. fields(ban) -> [ - {as, hoconsc:mk(hoconsc:enum(?BANNED_TYPES), #{ - desc => ?DESC(as), - required => true, - example => username})}, - {who, hoconsc:mk(binary(), #{ - desc => ?DESC(who), - required => true, - example => <<"Banned name"/utf8>>})}, - {by, hoconsc:mk(binary(), #{ - desc => ?DESC(by), - required => false, - example => <<"mgmt_api">>})}, - {reason, hoconsc:mk(binary(), #{ - desc => ?DESC(reason), - required => false, - example => <<"Too many requests">>})}, - {at, hoconsc:mk(emqx_datetime:epoch_second(), #{ - desc => ?DESC(at), - required => false, - example => <<"2021-10-25T21:48:47+08:00">>})}, - {until, hoconsc:mk(emqx_datetime:epoch_second(), #{ - desc => ?DESC(until), - required => false, - example => <<"2021-10-25T21:53:47+08:00">>}) - } + {as, + hoconsc:mk(hoconsc:enum(?BANNED_TYPES), #{ + desc => ?DESC(as), + required => true, + example => username + })}, + {who, + hoconsc:mk(binary(), #{ + desc => ?DESC(who), + required => true, + example => <<"Banned name"/utf8>> + })}, + {by, + hoconsc:mk(binary(), #{ + desc => ?DESC(by), + required => false, + example => <<"mgmt_api">> + })}, + {reason, + hoconsc:mk(binary(), #{ + desc => ?DESC(reason), + required => false, + example => <<"Too many requests">> + })}, + {at, + hoconsc:mk(emqx_datetime:epoch_second(), #{ + desc => ?DESC(at), + required => false, + example => <<"2021-10-25T21:48:47+08:00">> + })}, + {until, + hoconsc:mk(emqx_datetime:epoch_second(), #{ + desc => ?DESC(until), + required => false, + example => <<"2021-10-25T21:53:47+08:00">> + })} ]; fields(meta) -> emqx_dashboard_swagger:fields(page) ++ @@ -141,8 +161,7 @@ banned(post, #{body := Body}) -> Ban -> case emqx_banned:create(Ban) of {ok, Banned} -> {200, format(Banned)}; - {error, {already_exist, Old}} -> - {400, 'ALREADY_EXISTS', format(Old)} + {error, {already_exist, Old}} -> {400, 'ALREADY_EXISTS', format(Old)} end end. diff --git a/apps/emqx_management/src/emqx_mgmt_api_clients.erl b/apps/emqx_management/src/emqx_mgmt_api_clients.erl index 7b6b1cf40..393af6b5d 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_clients.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_clients.erl @@ -26,63 +26,70 @@ -include("emqx_mgmt.hrl"). %% API --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). --export([ clients/2 - , client/2 - , subscriptions/2 - , authz_cache/2 - , subscribe/2 - , unsubscribe/2 - , subscribe_batch/2 - , set_keepalive/2 - ]). +-export([ + clients/2, + client/2, + subscriptions/2, + authz_cache/2, + subscribe/2, + unsubscribe/2, + subscribe_batch/2, + set_keepalive/2 +]). --export([ query/4 - , format_channel_info/1 - ]). +-export([ + query/4, + format_channel_info/1 +]). %% for batch operation -export([do_subscribe/3]). -define(CLIENT_QTAB, emqx_channel_info). --define(CLIENT_QSCHEMA, - [ {<<"node">>, atom} - , {<<"username">>, binary} - , {<<"zone">>, atom} - , {<<"ip_address">>, ip} - , {<<"conn_state">>, atom} - , {<<"clean_start">>, atom} - , {<<"proto_name">>, binary} - , {<<"proto_ver">>, integer} - , {<<"like_clientid">>, binary} - , {<<"like_username">>, binary} - , {<<"gte_created_at">>, timestamp} - , {<<"lte_created_at">>, timestamp} - , {<<"gte_connected_at">>, timestamp} - , {<<"lte_connected_at">>, timestamp}]). +-define(CLIENT_QSCHEMA, [ + {<<"node">>, atom}, + {<<"username">>, binary}, + {<<"zone">>, atom}, + {<<"ip_address">>, ip}, + {<<"conn_state">>, atom}, + {<<"clean_start">>, atom}, + {<<"proto_name">>, binary}, + {<<"proto_ver">>, integer}, + {<<"like_clientid">>, binary}, + {<<"like_username">>, binary}, + {<<"gte_created_at">>, timestamp}, + {<<"lte_created_at">>, timestamp}, + {<<"gte_connected_at">>, timestamp}, + {<<"lte_connected_at">>, timestamp} +]). -define(QUERY_FUN, {?MODULE, query}). -define(FORMAT_FUN, {?MODULE, format_channel_info}). -define(CLIENT_ID_NOT_FOUND, - <<"{\"code\": \"RESOURCE_NOT_FOUND\", \"reason\": \"Client id not found\"}">>). + <<"{\"code\": \"RESOURCE_NOT_FOUND\", \"reason\": \"Client id not found\"}">> +). api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}). paths() -> - [ "/clients" - , "/clients/:clientid" - , "/clients/:clientid/authorization/cache" - , "/clients/:clientid/subscriptions" - , "/clients/:clientid/subscribe" - , "/clients/:clientid/unsubscribe" - , "/clients/:clientid/keepalive" + [ + "/clients", + "/clients/:clientid", + "/clients/:clientid/authorization/cache", + "/clients/:clientid/subscriptions", + "/clients/:clientid/subscribe", + "/clients/:clientid/unsubscribe", + "/clients/:clientid/keepalive" ]. schema("/clients") -> @@ -93,69 +100,105 @@ schema("/clients") -> parameters => [ hoconsc:ref(emqx_dashboard_swagger, page), hoconsc:ref(emqx_dashboard_swagger, limit), - {node, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Node name">>, - example => atom_to_list(node())})}, - {username, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"User name">>})}, - {zone, hoconsc:mk(binary(), #{ - in => query, - required => false})}, - {ip_address, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Client's IP address">>, - example => <<"127.0.0.1">>})}, - {conn_state, hoconsc:mk(hoconsc:enum([connected, idle, disconnected]), #{ - in => query, - required => false, - desc => <<"The current connection status of the client, ", - "the possible values are connected,idle,disconnected">>})}, - {clean_start, hoconsc:mk(boolean(), #{ - in => query, - required => false, - description => <<"Whether the client uses a new session">>})}, - {proto_name, hoconsc:mk(hoconsc:enum(['MQTT', 'CoAP', 'LwM2M', 'MQTT-SN']), #{ - in => query, - required => false, - description => <<"Client protocol name, ", - "the possible values are MQTT,CoAP,LwM2M,MQTT-SN">>})}, - {proto_ver, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Client protocol version">>})}, - {like_clientid, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Fuzzy search `clientid` as substring">>})}, - {like_username, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Fuzzy search `username` as substring">>})}, - {gte_created_at, hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ - in => query, - required => false, - desc => <<"Search client session creation time by greater", - " than or equal method, rfc3339 or timestamp(millisecond)">>})}, - {lte_created_at, hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ - in => query, - required => false, - desc => <<"Search client session creation time by less", - " than or equal method, rfc3339 or timestamp(millisecond)">>})}, - {gte_connected_at, hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ - in => query, - required => false, - desc => <<"Search client connection creation time by greater" - " than or equal method, rfc3339 or timestamp(epoch millisecond)">>})}, - {lte_connected_at, hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ - in => query, - required => false, - desc => <<"Search client connection creation time by less" - " than or equal method, rfc3339 or timestamp(millisecond)">>})} + {node, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Node name">>, + example => atom_to_list(node()) + })}, + {username, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"User name">> + })}, + {zone, + hoconsc:mk(binary(), #{ + in => query, + required => false + })}, + {ip_address, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Client's IP address">>, + example => <<"127.0.0.1">> + })}, + {conn_state, + hoconsc:mk(hoconsc:enum([connected, idle, disconnected]), #{ + in => query, + required => false, + desc => + <<"The current connection status of the client, ", + "the possible values are connected,idle,disconnected">> + })}, + {clean_start, + hoconsc:mk(boolean(), #{ + in => query, + required => false, + description => <<"Whether the client uses a new session">> + })}, + {proto_name, + hoconsc:mk(hoconsc:enum(['MQTT', 'CoAP', 'LwM2M', 'MQTT-SN']), #{ + in => query, + required => false, + description => + <<"Client protocol name, ", + "the possible values are MQTT,CoAP,LwM2M,MQTT-SN">> + })}, + {proto_ver, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Client protocol version">> + })}, + {like_clientid, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Fuzzy search `clientid` as substring">> + })}, + {like_username, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Fuzzy search `username` as substring">> + })}, + {gte_created_at, + hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ + in => query, + required => false, + desc => + <<"Search client session creation time by greater", + " than or equal method, rfc3339 or timestamp(millisecond)">> + })}, + {lte_created_at, + hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ + in => query, + required => false, + desc => + <<"Search client session creation time by less", + " than or equal method, rfc3339 or timestamp(millisecond)">> + })}, + {gte_connected_at, + hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ + in => query, + required => false, + desc => << + "Search client connection creation time by greater" + " than or equal method, rfc3339 or timestamp(epoch millisecond)" + >> + })}, + {lte_connected_at, + hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ + in => query, + required => false, + desc => << + "Search client connection creation time by less" + " than or equal method, rfc3339 or timestamp(millisecond)" + >> + })} ], responses => #{ 200 => [ @@ -164,10 +207,11 @@ schema("/clients") -> ], 400 => emqx_dashboard_swagger:error_codes( - ['INVALID_PARAMETER'], <<"Invalid parameters">>)} + ['INVALID_PARAMETER'], <<"Invalid parameters">> + ) + } } }; - schema("/clients/:clientid") -> #{ 'operationId' => client, @@ -177,19 +221,23 @@ schema("/clients/:clientid") -> responses => #{ 200 => hoconsc:mk(hoconsc:ref(?MODULE, client), #{}), 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>)}}, + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) + } + }, delete => #{ description => <<"Kick out client by client ID">>, parameters => [ - {clientid, hoconsc:mk(binary(), #{in => path})}], + {clientid, hoconsc:mk(binary(), #{in => path})} + ], responses => #{ 204 => <<"Kick out client successfully">>, 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>) + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) } } }; - schema("/clients/:clientid/authorization/cache") -> #{ 'operationId' => authz_cache, @@ -199,7 +247,8 @@ schema("/clients/:clientid/authorization/cache") -> responses => #{ 200 => hoconsc:mk(hoconsc:ref(?MODULE, authz_cache), #{}), 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>) + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) } }, delete => #{ @@ -208,11 +257,11 @@ schema("/clients/:clientid/authorization/cache") -> responses => #{ 204 => <<"Kick out client successfully">>, 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>) + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) } } }; - schema("/clients/:clientid/subscriptions") -> #{ 'operationId' => subscriptions, @@ -220,13 +269,15 @@ schema("/clients/:clientid/subscriptions") -> description => <<"Get client subscriptions">>, parameters => [{clientid, hoconsc:mk(binary(), #{in => path})}], responses => #{ - 200 => hoconsc:mk(hoconsc:array(hoconsc:ref(emqx_mgmt_api_subscriptions, subscription)), #{}), + 200 => hoconsc:mk( + hoconsc:array(hoconsc:ref(emqx_mgmt_api_subscriptions, subscription)), #{} + ), 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>) + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) } } }; - schema("/clients/:clientid/subscribe") -> #{ 'operationId' => subscribe, @@ -237,11 +288,11 @@ schema("/clients/:clientid/subscribe") -> responses => #{ 200 => hoconsc:ref(emqx_mgmt_api_subscriptions, subscription), 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>) + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) } } }; - schema("/clients/:clientid/unsubscribe") -> #{ 'operationId' => unsubscribe, @@ -252,11 +303,11 @@ schema("/clients/:clientid/unsubscribe") -> responses => #{ 204 => <<"Unsubscribe OK">>, 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>) + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) } } }; - schema("/clients/:clientid/keepalive") -> #{ 'operationId' => set_keepalive, @@ -267,96 +318,187 @@ schema("/clients/:clientid/keepalive") -> responses => #{ 200 => hoconsc:mk(hoconsc:ref(?MODULE, client), #{}), 404 => emqx_dashboard_swagger:error_codes( - ['CLIENTID_NOT_FOUND'], <<"Client id not found">>) + ['CLIENTID_NOT_FOUND'], <<"Client id not found">> + ) } } }. fields(client) -> [ - {awaiting_rel_cnt, hoconsc:mk(integer(), #{desc => - <<"v4 api name [awaiting_rel] Number of awaiting PUBREC packet">>})}, - {awaiting_rel_max, hoconsc:mk(integer(), #{desc => - <<"v4 api name [max_awaiting_rel]. " - "Maximum allowed number of awaiting PUBREC packet">>})}, - {clean_start, hoconsc:mk(boolean(), #{desc => - <<"Indicate whether the client is using a brand new session">>})}, + {awaiting_rel_cnt, + hoconsc:mk(integer(), #{ + desc => + <<"v4 api name [awaiting_rel] Number of awaiting PUBREC packet">> + })}, + {awaiting_rel_max, + hoconsc:mk(integer(), #{ + desc => + << + "v4 api name [max_awaiting_rel]. " + "Maximum allowed number of awaiting PUBREC packet" + >> + })}, + {clean_start, + hoconsc:mk(boolean(), #{ + desc => + <<"Indicate whether the client is using a brand new session">> + })}, {clientid, hoconsc:mk(binary(), #{desc => <<"Client identifier">>})}, {connected, hoconsc:mk(boolean(), #{desc => <<"Whether the client is connected">>})}, - {connected_at, hoconsc:mk(emqx_datetime:epoch_millisecond(), - #{desc => <<"Client connection time, rfc3339 or timestamp(millisecond)">>})}, - {created_at, hoconsc:mk(emqx_datetime:epoch_millisecond(), - #{desc => <<"Session creation time, rfc3339 or timestamp(millisecond)">>})}, - {disconnected_at, hoconsc:mk(emqx_datetime:epoch_millisecond(), #{desc => - <<"Client offline time." - " It's Only valid and returned when connected is false, rfc3339 or timestamp(millisecond)">>})}, - {expiry_interval, hoconsc:mk(integer(), #{desc => - <<"Session expiration interval, with the unit of second">>})}, - {heap_size, hoconsc:mk(integer(), #{desc => - <<"Process heap size with the unit of byte">>})}, + {connected_at, + hoconsc:mk( + emqx_datetime:epoch_millisecond(), + #{desc => <<"Client connection time, rfc3339 or timestamp(millisecond)">>} + )}, + {created_at, + hoconsc:mk( + emqx_datetime:epoch_millisecond(), + #{desc => <<"Session creation time, rfc3339 or timestamp(millisecond)">>} + )}, + {disconnected_at, + hoconsc:mk(emqx_datetime:epoch_millisecond(), #{ + desc => + << + "Client offline time." + " It's Only valid and returned when connected is false, rfc3339 or timestamp(millisecond)" + >> + })}, + {expiry_interval, + hoconsc:mk(integer(), #{ + desc => + <<"Session expiration interval, with the unit of second">> + })}, + {heap_size, + hoconsc:mk(integer(), #{ + desc => + <<"Process heap size with the unit of byte">> + })}, {inflight_cnt, hoconsc:mk(integer(), #{desc => <<"Current length of inflight">>})}, - {inflight_max, hoconsc:mk(integer(), #{desc => - <<"v4 api name [max_inflight]. Maximum length of inflight">>})}, + {inflight_max, + hoconsc:mk(integer(), #{ + desc => + <<"v4 api name [max_inflight]. Maximum length of inflight">> + })}, {ip_address, hoconsc:mk(binary(), #{desc => <<"Client's IP address">>})}, - {is_bridge, hoconsc:mk(boolean(), #{desc => - <<"Indicates whether the client is connectedvia bridge">>})}, - {keepalive, hoconsc:mk(integer(), #{desc => - <<"keepalive time, with the unit of second">>})}, + {is_bridge, + hoconsc:mk(boolean(), #{ + desc => + <<"Indicates whether the client is connectedvia bridge">> + })}, + {keepalive, + hoconsc:mk(integer(), #{ + desc => + <<"keepalive time, with the unit of second">> + })}, {mailbox_len, hoconsc:mk(integer(), #{desc => <<"Process mailbox size">>})}, - {mqueue_dropped, hoconsc:mk(integer(), #{desc => - <<"Number of messages dropped by the message queue due to exceeding the length">>})}, + {mqueue_dropped, + hoconsc:mk(integer(), #{ + desc => + <<"Number of messages dropped by the message queue due to exceeding the length">> + })}, {mqueue_len, hoconsc:mk(integer(), #{desc => <<"Current length of message queue">>})}, - {mqueue_max, hoconsc:mk(integer(), #{desc => - <<"v4 api name [max_mqueue]. Maximum length of message queue">>})}, - {node, hoconsc:mk(binary(), #{desc => - <<"Name of the node to which the client is connected">>})}, + {mqueue_max, + hoconsc:mk(integer(), #{ + desc => + <<"v4 api name [max_mqueue]. Maximum length of message queue">> + })}, + {node, + hoconsc:mk(binary(), #{ + desc => + <<"Name of the node to which the client is connected">> + })}, {port, hoconsc:mk(integer(), #{desc => <<"Client's port">>})}, {proto_name, hoconsc:mk(binary(), #{desc => <<"Client protocol name">>})}, {proto_ver, hoconsc:mk(integer(), #{desc => <<"Protocol version used by the client">>})}, {recv_cnt, hoconsc:mk(integer(), #{desc => <<"Number of TCP packets received">>})}, {recv_msg, hoconsc:mk(integer(), #{desc => <<"Number of PUBLISH packets received">>})}, - {'recv_msg.dropped', hoconsc:mk(integer(), #{desc => - <<"Number of dropped PUBLISH packets">>})}, - {'recv_msg.dropped.await_pubrel_timeout', hoconsc:mk(integer(), #{desc => - <<"Number of dropped PUBLISH packets due to expired">>})}, - {'recv_msg.qos0', hoconsc:mk(integer(), #{desc => - <<"Number of PUBLISH QoS0 packets received">>})}, - {'recv_msg.qos1', hoconsc:mk(integer(), #{desc => - <<"Number of PUBLISH QoS1 packets received">>})}, - {'recv_msg.qos2', hoconsc:mk(integer(), #{desc => - <<"Number of PUBLISH QoS2 packets received">>})}, + {'recv_msg.dropped', + hoconsc:mk(integer(), #{ + desc => + <<"Number of dropped PUBLISH packets">> + })}, + {'recv_msg.dropped.await_pubrel_timeout', + hoconsc:mk(integer(), #{ + desc => + <<"Number of dropped PUBLISH packets due to expired">> + })}, + {'recv_msg.qos0', + hoconsc:mk(integer(), #{ + desc => + <<"Number of PUBLISH QoS0 packets received">> + })}, + {'recv_msg.qos1', + hoconsc:mk(integer(), #{ + desc => + <<"Number of PUBLISH QoS1 packets received">> + })}, + {'recv_msg.qos2', + hoconsc:mk(integer(), #{ + desc => + <<"Number of PUBLISH QoS2 packets received">> + })}, {recv_oct, hoconsc:mk(integer(), #{desc => <<"Number of bytes received">>})}, {recv_pkt, hoconsc:mk(integer(), #{desc => <<"Number of MQTT packets received">>})}, {reductions, hoconsc:mk(integer(), #{desc => <<"Erlang reduction">>})}, {send_cnt, hoconsc:mk(integer(), #{desc => <<"Number of TCP packets sent">>})}, {send_msg, hoconsc:mk(integer(), #{desc => <<"Number of PUBLISH packets sent">>})}, - {'send_msg.dropped', hoconsc:mk(integer(), #{desc => - <<"Number of dropped PUBLISH packets">>})}, - {'send_msg.dropped.expired', hoconsc:mk(integer(), #{desc => - <<"Number of dropped PUBLISH packets due to expired">>})}, - {'send_msg.dropped.queue_full', hoconsc:mk(integer(), #{desc => - <<"Number of dropped PUBLISH packets due to queue full">>})}, - {'send_msg.dropped.too_large', hoconsc:mk(integer(), #{desc => - <<"Number of dropped PUBLISH packets due to packet length too large">>})}, - {'send_msg.qos0', hoconsc:mk(integer(), #{desc => - <<"Number of PUBLISH QoS0 packets sent">>})}, - {'send_msg.qos1', hoconsc:mk(integer(), #{desc => - <<"Number of PUBLISH QoS1 packets sent">>})}, - {'send_msg.qos2', hoconsc:mk(integer(), #{desc => - <<"Number of PUBLISH QoS2 packets sent">>})}, + {'send_msg.dropped', + hoconsc:mk(integer(), #{ + desc => + <<"Number of dropped PUBLISH packets">> + })}, + {'send_msg.dropped.expired', + hoconsc:mk(integer(), #{ + desc => + <<"Number of dropped PUBLISH packets due to expired">> + })}, + {'send_msg.dropped.queue_full', + hoconsc:mk(integer(), #{ + desc => + <<"Number of dropped PUBLISH packets due to queue full">> + })}, + {'send_msg.dropped.too_large', + hoconsc:mk(integer(), #{ + desc => + <<"Number of dropped PUBLISH packets due to packet length too large">> + })}, + {'send_msg.qos0', + hoconsc:mk(integer(), #{ + desc => + <<"Number of PUBLISH QoS0 packets sent">> + })}, + {'send_msg.qos1', + hoconsc:mk(integer(), #{ + desc => + <<"Number of PUBLISH QoS1 packets sent">> + })}, + {'send_msg.qos2', + hoconsc:mk(integer(), #{ + desc => + <<"Number of PUBLISH QoS2 packets sent">> + })}, {send_oct, hoconsc:mk(integer(), #{desc => <<"Number of bytes sent">>})}, {send_pkt, hoconsc:mk(integer(), #{desc => <<"Number of MQTT packets sent">>})}, - {subscriptions_cnt, hoconsc:mk(integer(), #{desc => - <<"Number of subscriptions established by this client.">>})}, - {subscriptions_max, hoconsc:mk(integer(), #{desc => - <<"v4 api name [max_subscriptions]", - " Maximum number of subscriptions allowed by this client">>})}, + {subscriptions_cnt, + hoconsc:mk(integer(), #{ + desc => + <<"Number of subscriptions established by this client.">> + })}, + {subscriptions_max, + hoconsc:mk(integer(), #{ + desc => + <<"v4 api name [max_subscriptions]", + " Maximum number of subscriptions allowed by this client">> + })}, {username, hoconsc:mk(binary(), #{desc => <<"User name of client when connecting">>})}, {will_msg, hoconsc:mk(binary(), #{desc => <<"Client will message">>})}, - {zone, hoconsc:mk(binary(), #{desc => - <<"Indicate the configuration group used by the client">>})} + {zone, + hoconsc:mk(binary(), #{ + desc => + <<"Indicate the configuration group used by the client">> + })} ]; - fields(authz_cache) -> [ {access, hoconsc:mk(binary(), #{desc => <<"Access type">>})}, @@ -364,23 +506,19 @@ fields(authz_cache) -> {topic, hoconsc:mk(binary(), #{desc => <<"Topic name">>})}, {updated_time, hoconsc:mk(integer(), #{desc => <<"Update time">>})} ]; - fields(keepalive) -> [ {interval, hoconsc:mk(integer(), #{desc => <<"Keepalive time, with the unit of second">>})} ]; - fields(subscribe) -> [ {topic, hoconsc:mk(binary(), #{desc => <<"Topic">>})}, {qos, hoconsc:mk(emqx_schema:qos(), #{desc => <<"QoS">>})} ]; - fields(unsubscribe) -> [ {topic, hoconsc:mk(binary(), #{desc => <<"Topic">>})} ]; - fields(meta) -> emqx_dashboard_swagger:fields(page) ++ emqx_dashboard_swagger:fields(limit) ++ @@ -393,13 +531,11 @@ clients(get, #{query_string := QString}) -> client(get, #{bindings := Bindings}) -> lookup(Bindings); - client(delete, #{bindings := Bindings}) -> kickout(Bindings). authz_cache(get, #{bindings := Bindings}) -> get_authz_cache(Bindings); - authz_cache(delete, #{bindings := Bindings}) -> clean_authz_cache(Bindings). @@ -415,11 +551,14 @@ unsubscribe(post, #{bindings := #{clientid := ClientID}, body := TopicInfo}) -> %% TODO: batch subscribe_batch(post, #{bindings := #{clientid := ClientID}, body := TopicInfos}) -> Topics = - [begin - Topic = maps:get(<<"topic">>, TopicInfo), - Qos = maps:get(<<"qos">>, TopicInfo, 0), - #{topic => Topic, qos => Qos} - end || TopicInfo <- TopicInfos], + [ + begin + Topic = maps:get(<<"topic">>, TopicInfo), + Qos = maps:get(<<"qos">>, TopicInfo, 0), + #{topic => Topic, qos => Qos} + end + || TopicInfo <- TopicInfos + ], subscribe_batch(#{clientid => ClientID, topics => Topics}). subscriptions(get, #{bindings := #{clientid := ClientID}}) -> @@ -436,16 +575,17 @@ subscriptions(get, #{bindings := #{clientid := ClientID}}) -> qos => maps:get(qos, SubOpts) } end, - {200, lists:map(Formatter, Subs)} + {200, lists:map(Formatter, Subs)} end. set_keepalive(put, #{bindings := #{clientid := ClientID}, body := Body}) -> case maps:find(<<"interval">>, Body) of - error -> {400, 'BAD_REQUEST',"Interval Not Found"}; + error -> + {400, 'BAD_REQUEST', "Interval Not Found"}; {ok, Interval} -> case emqx_mgmt:set_keepalive(emqx_mgmt_util:urldecode(ClientID), Interval) of ok -> lookup(#{clientid => ClientID}); - {error, not_found} ->{404, ?CLIENT_ID_NOT_FOUND}; + {error, not_found} -> {404, ?CLIENT_ID_NOT_FOUND}; {error, Reason} -> {400, #{code => 'PARAMS_ERROR', message => Reason}} end end. @@ -454,16 +594,26 @@ set_keepalive(put, #{bindings := #{clientid := ClientID}, body := Body}) -> %% api apply list_clients(QString) -> - Result = case maps:get(<<"node">>, QString, undefined) of - undefined -> - emqx_mgmt_api:cluster_query(QString, ?CLIENT_QTAB, - ?CLIENT_QSCHEMA, ?QUERY_FUN); - Node0 -> - Node1 = binary_to_atom(Node0, utf8), - QStringWithoutNode = maps:without([<<"node">>], QString), - emqx_mgmt_api:node_query(Node1, QStringWithoutNode, - ?CLIENT_QTAB, ?CLIENT_QSCHEMA, ?QUERY_FUN) - end, + Result = + case maps:get(<<"node">>, QString, undefined) of + undefined -> + emqx_mgmt_api:cluster_query( + QString, + ?CLIENT_QTAB, + ?CLIENT_QSCHEMA, + ?QUERY_FUN + ); + Node0 -> + Node1 = binary_to_atom(Node0, utf8), + QStringWithoutNode = maps:without([<<"node">>], QString), + emqx_mgmt_api:node_query( + Node1, + QStringWithoutNode, + ?CLIENT_QTAB, + ?CLIENT_QSCHEMA, + ?QUERY_FUN + ) + end, case Result of {error, page_limit_invalid} -> {400, #{code => <<"INVALID_PARAMETER">>, message => <<"page_limit_invalid">>}}; @@ -490,7 +640,7 @@ kickout(#{clientid := ClientID}) -> {204} end. -get_authz_cache(#{clientid := ClientID})-> +get_authz_cache(#{clientid := ClientID}) -> case emqx_mgmt:list_authz_cache(ClientID) of {error, not_found} -> {404, ?CLIENT_ID_NOT_FOUND}; @@ -524,9 +674,9 @@ subscribe(#{clientid := ClientID, topic := Topic, qos := Qos}) -> Response = #{ clientid => ClientID, - topic => Topic, - qos => Qos, - node => Node + topic => Topic, + qos => Qos, + node => Node }, {200, Response} end. @@ -562,7 +712,7 @@ do_subscribe(ClientID, Topic0, Qos) -> end. -spec do_unsubscribe(emqx_types:clientid(), emqx_types:topic()) -> - {unsubscribe, _} | {error, channel_not_found}. + {unsubscribe, _} | {error, channel_not_found}. do_unsubscribe(ClientID, Topic) -> case emqx_mgmt:unsubscribe(ClientID, Topic) of {error, Reason} -> @@ -576,14 +726,23 @@ do_unsubscribe(ClientID, Topic) -> query(Tab, {QString, []}, Continuation, Limit) -> Ms = qs2ms(QString), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_channel_info/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_channel_info/1 + ); query(Tab, {QString, FuzzyQString}, Continuation, Limit) -> Ms = qs2ms(QString), FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString), - emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_channel_info/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_channel_info/1 + ). %%-------------------------------------------------------------------- %% QueryString to Match Spec @@ -595,7 +754,6 @@ qs2ms(Qs) -> qs2ms([], _, {MtchHead, Conds}) -> {MtchHead, lists:reverse(Conds)}; - qs2ms([{Key, '=:=', Value} | Rest], N, {MtchHead, Conds}) -> NMtchHead = emqx_mgmt_util:merge_maps(MtchHead, ms(Key, Value)), qs2ms(Rest, N, {NMtchHead, Conds}); @@ -603,13 +761,16 @@ qs2ms([Qs | Rest], N, {MtchHead, Conds}) -> Holder = binary_to_atom(iolist_to_binary(["$", integer_to_list(N)]), utf8), NMtchHead = emqx_mgmt_util:merge_maps(MtchHead, ms(element(1, Qs), Holder)), NConds = put_conds(Qs, Holder, Conds), - qs2ms(Rest, N+1, {NMtchHead, NConds}). + qs2ms(Rest, N + 1, {NMtchHead, NConds}). put_conds({_, Op, V}, Holder, Conds) -> [{Op, Holder, V} | Conds]; put_conds({_, Op1, V1, Op2, V2}, Holder, Conds) -> - [{Op2, Holder, V2}, - {Op1, Holder, V1} | Conds]. + [ + {Op2, Holder, V2}, + {Op1, Holder, V1} + | Conds + ]. ms(clientid, X) -> #{clientinfo => #{clientid => X}}; @@ -637,68 +798,78 @@ ms(created_at, X) -> fuzzy_filter_fun(Fuzzy) -> fun(MsRaws) when is_list(MsRaws) -> - lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end - , MsRaws) + lists:filter( + fun(E) -> run_fuzzy_filter(E, Fuzzy) end, + MsRaws + ) end. run_fuzzy_filter(_, []) -> true; run_fuzzy_filter(E = {_, #{clientinfo := ClientInfo}, _}, [{Key, like, SubStr} | Fuzzy]) -> - Val = case maps:get(Key, ClientInfo, <<>>) of - undefined -> <<>>; - V -> V - end, + Val = + case maps:get(Key, ClientInfo, <<>>) of + undefined -> <<>>; + V -> V + end, binary:match(Val, SubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy). %%-------------------------------------------------------------------- %% format funcs format_channel_info({_, ClientInfo, ClientStats}) -> - Node = case ClientInfo of - #{node := N} -> N; - _ -> node() - end, - StatsMap = maps:without([memory, next_pkt_id, total_heap_size], - maps:from_list(ClientStats)), + Node = + case ClientInfo of + #{node := N} -> N; + _ -> node() + end, + StatsMap = maps:without( + [memory, next_pkt_id, total_heap_size], + maps:from_list(ClientStats) + ), ClientInfoMap0 = maps:fold(fun take_maps_from_inner/3, #{}, ClientInfo), {IpAddress, Port} = peername_dispart(maps:get(peername, ClientInfoMap0)), - Connected = maps:get(conn_state, ClientInfoMap0) =:= connected, + Connected = maps:get(conn_state, ClientInfoMap0) =:= connected, ClientInfoMap1 = maps:merge(StatsMap, ClientInfoMap0), ClientInfoMap2 = maps:put(node, Node, ClientInfoMap1), ClientInfoMap3 = maps:put(ip_address, IpAddress, ClientInfoMap2), ClientInfoMap4 = maps:put(port, Port, ClientInfoMap3), - ClientInfoMap = maps:put(connected, Connected, ClientInfoMap4), + ClientInfoMap = maps:put(connected, Connected, ClientInfoMap4), RemoveList = - [ auth_result - , peername - , sockname - , peerhost - , conn_state - , send_pend - , conn_props - , peercert - , sockstate - , subscriptions - , receive_maximum - , protocol - , is_superuser - , sockport - , anonymous - , mountpoint - , socktype - , active_n - , await_rel_timeout - , conn_mod - , sockname - , retry_interval - , upgrade_qos - , id %% sessionID, defined in emqx_session.erl - ], + [ + auth_result, + peername, + sockname, + peerhost, + conn_state, + send_pend, + conn_props, + peercert, + sockstate, + subscriptions, + receive_maximum, + protocol, + is_superuser, + sockport, + anonymous, + mountpoint, + socktype, + active_n, + await_rel_timeout, + conn_mod, + sockname, + retry_interval, + upgrade_qos, + %% sessionID, defined in emqx_session.erl + id + ], TimesKeys = [created_at, connected_at, disconnected_at], %% format timestamp to rfc3339 - lists:foldl(fun result_format_time_fun/2 - , maps:without(RemoveList, ClientInfoMap) - , TimesKeys). + lists:foldl( + fun result_format_time_fun/2, + maps:without(RemoveList, ClientInfoMap), + TimesKeys + ). %% format func helpers take_maps_from_inner(_Key, Value, Current) when is_map(Value) -> @@ -710,20 +881,22 @@ result_format_time_fun(Key, NClientInfoMap) -> case NClientInfoMap of #{Key := TimeStamp} -> NClientInfoMap#{ - Key => emqx_datetime:epoch_to_rfc3339(TimeStamp)}; + Key => emqx_datetime:epoch_to_rfc3339(TimeStamp) + }; #{} -> NClientInfoMap end. --spec(peername_dispart(emqx_types:peername()) -> {binary(), inet:port_number()}). +-spec peername_dispart(emqx_types:peername()) -> {binary(), inet:port_number()}. peername_dispart({Addr, Port}) -> AddrBinary = list_to_binary(inet:ntoa(Addr)), %% PortBinary = integer_to_binary(Port), {AddrBinary, Port}. format_authz_cache({{PubSub, Topic}, {AuthzResult, Timestamp}}) -> - #{ access => PubSub, - topic => Topic, - result => AuthzResult, - updated_time => Timestamp - }. + #{ + access => PubSub, + topic => Topic, + result => AuthzResult, + updated_time => Timestamp + }. diff --git a/apps/emqx_management/src/emqx_mgmt_api_cluster.erl b/apps/emqx_management/src/emqx_mgmt_api_cluster.erl index 0c6cd151d..25ec5c9a2 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_cluster.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_cluster.erl @@ -125,7 +125,7 @@ force_leave(delete, #{bindings := #{node := Node0}}) -> {400, #{code => 'BAD_REQUEST', message => error_message(Error)}} end. --spec(join(node()) -> ok | ignore | {error, term()}). +-spec join(node()) -> ok | ignore | {error, term()}. join(Node) -> ekka:join(Node). diff --git a/apps/emqx_management/src/emqx_mgmt_api_configs.erl b/apps/emqx_management/src/emqx_mgmt_api_configs.erl index 2b6e67f7f..4769d42f5 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_configs.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_configs.erl @@ -23,11 +23,13 @@ -export([api_spec/0, namespace/0]). -export([paths/0, schema/1, fields/1]). --export([ config/3 - , config_reset/3 - , configs/3 - , get_full_config/0 - , global_zone_configs/3]). +-export([ + config/3, + config_reset/3, + configs/3, + get_full_config/0, + global_zone_configs/3 +]). -export([gen_schema/1]). @@ -36,28 +38,29 @@ -define(ERR_MSG(MSG), list_to_binary(io_lib:format("~p", [MSG]))). -define(OPTS, #{rawconf_with_defaults => true, override_to => cluster}). --define(EXCLUDES, [ - <<"exhook">>, - <<"gateway">>, - <<"plugins">>, - <<"bridges">>, - <<"rule_engine">>, - <<"authorization">>, - <<"authentication">>, - <<"rpc">>, - <<"db">>, - <<"connectors">>, - <<"slow_subs">>, - <<"psk_authentication">>, - <<"topic_metrics">>, - <<"rewrite">>, - <<"auto_subscribe">>, - <<"retainer">>, - <<"statsd">>, - <<"delayed">>, - <<"event_message">>, - <<"prometheus">>, - <<"telemetry">> +-define(EXCLUDES, + [ + <<"exhook">>, + <<"gateway">>, + <<"plugins">>, + <<"bridges">>, + <<"rule_engine">>, + <<"authorization">>, + <<"authentication">>, + <<"rpc">>, + <<"db">>, + <<"connectors">>, + <<"slow_subs">>, + <<"psk_authentication">>, + <<"topic_metrics">>, + <<"rewrite">>, + <<"auto_subscribe">>, + <<"retainer">>, + <<"statsd">>, + <<"delayed">>, + <<"event_message">>, + <<"prometheus">>, + <<"telemetry">> ] ++ global_zone_roots() ). @@ -68,7 +71,7 @@ namespace() -> "configuration". paths() -> ["/configs", "/configs_reset/:rootname", "/configs/global_zone"] ++ - lists:map(fun({Name, _Type}) -> ?PREFIX ++ binary_to_list(Name) end, config_list()). + lists:map(fun({Name, _Type}) -> ?PREFIX ++ binary_to_list(Name) end, config_list()). schema("/configs") -> #{ @@ -76,12 +79,20 @@ schema("/configs") -> get => #{ tags => [conf], description => -<<"Get all the configurations of the specified node, including hot and non-hot updatable items.">>, + <<"Get all the configurations of the specified node, including hot and non-hot updatable items.">>, parameters => [ - {node, hoconsc:mk(typerefl:atom(), - #{in => query, required => false, example => <<"emqx@127.0.0.1">>, - desc => - <<"Node's name: If you do not fill in the fields, this node will be used by default.">>})}], + {node, + hoconsc:mk( + typerefl:atom(), + #{ + in => query, + required => false, + example => <<"emqx@127.0.0.1">>, + desc => + <<"Node's name: If you do not fill in the fields, this node will be used by default.">> + } + )} + ], responses => #{ 200 => lists:map(fun({_, Schema}) -> Schema end, config_list()) } @@ -94,18 +105,29 @@ schema("/configs_reset/:rootname") -> post => #{ tags => [conf], description => -<<"Reset the config entry specified by the query string parameter `conf_path`.
-- For a config entry that has default value, this resets it to the default value; -- For a config entry that has no default value, an error 400 will be returned">>, + <<"Reset the config entry specified by the query string parameter `conf_path`.
\n" + "- For a config entry that has default value, this resets it to the default value;\n" + "- For a config entry that has no default value, an error 400 will be returned">>, %% We only return "200" rather than the new configs that has been changed, as %% the schema of the changed configs is depends on the request parameter %% `conf_path`, it cannot be defined here. parameters => [ - {rootname, hoconsc:mk( hoconsc:enum(Paths) - , #{in => path, example => <<"sysmon">>})}, - {conf_path, hoconsc:mk(typerefl:binary(), - #{in => query, required => false, example => <<"os.sysmem_high_watermark">>, - desc => <<"The config path separated by '.' character">>})}], + {rootname, + hoconsc:mk( + hoconsc:enum(Paths), + #{in => path, example => <<"sysmon">>} + )}, + {conf_path, + hoconsc:mk( + typerefl:binary(), + #{ + in => query, + required => false, + example => <<"os.sysmem_high_watermark">>, + desc => <<"The config path separated by '.' character">> + } + )} + ], responses => #{ 200 => <<"Rest config successfully">>, 400 => emqx_dashboard_swagger:error_codes(['NO_DEFAULT_VALUE', 'REST_FAILED']) @@ -137,9 +159,11 @@ schema(Path) -> 'operationId' => config, get => #{ tags => [conf], - description => iolist_to_binary([ <<"Get the sub-configurations under *">> - , RootKey - , <<"*">>]), + description => iolist_to_binary([ + <<"Get the sub-configurations under *">>, + RootKey, + <<"*">> + ]), responses => #{ 200 => Schema, 404 => emqx_dashboard_swagger:error_codes(['NOT_FOUND'], <<"config not found">>) @@ -147,9 +171,11 @@ schema(Path) -> }, put => #{ tags => [conf], - description => iolist_to_binary([ <<"Update the sub-configurations under *">> - , RootKey - , <<"*">>]), + description => iolist_to_binary([ + <<"Update the sub-configurations under *">>, + RootKey, + <<"*">> + ]), 'requestBody' => Schema, responses => #{ 200 => Schema, @@ -176,7 +202,6 @@ config(get, _Params, Req) -> Path = conf_path(Req), {ok, Conf} = emqx_map_lib:deep_find(Path, get_full_config()), {200, Conf}; - config(put, #{body := Body}, Req) -> Path = conf_path(Req), case emqx_conf:update(Path, Body, ?OPTS) of @@ -188,21 +213,32 @@ config(put, #{body := Body}, Req) -> global_zone_configs(get, _Params, _Req) -> Paths = global_zone_roots(), - Zones = lists:foldl(fun(Path, Acc) -> Acc#{Path => get_config_with_default([Path])} end, - #{}, Paths), + Zones = lists:foldl( + fun(Path, Acc) -> Acc#{Path => get_config_with_default([Path])} end, + #{}, + Paths + ), {200, Zones}; global_zone_configs(put, #{body := Body}, _Req) -> Res = - maps:fold(fun(Path, Value, Acc) -> - case emqx_conf:update([Path], Value, ?OPTS) of - {ok, #{raw_config := RawConf}} -> - Acc#{Path => RawConf}; - {error, Reason} -> - ?SLOG(error, #{msg => "update global zone failed", reason => Reason, - path => Path, value => Value}), - Acc - end - end, #{}, Body), + maps:fold( + fun(Path, Value, Acc) -> + case emqx_conf:update([Path], Value, ?OPTS) of + {ok, #{raw_config := RawConf}} -> + Acc#{Path => RawConf}; + {error, Reason} -> + ?SLOG(error, #{ + msg => "update global zone failed", + reason => Reason, + path => Path, + value => Value + }), + Acc + end + end, + #{}, + Body + ), case maps:size(Res) =:= maps:size(Body) of true -> {200, Res}; false -> {400, #{code => 'UPDATE_FAILED'}} @@ -212,7 +248,8 @@ config_reset(post, _Params, Req) -> %% reset the config specified by the query string param 'conf_path' Path = conf_path_reset(Req) ++ conf_path_from_querystr(Req), case emqx:reset_config(Path, #{}) of - {ok, _} -> {200}; + {ok, _} -> + {200}; {error, no_default_value} -> {400, #{code => 'NO_DEFAULT_VALUE', message => <<"No Default Value.">>}}; {error, Reason} -> @@ -222,9 +259,8 @@ config_reset(post, _Params, Req) -> configs(get, Params, _Req) -> Node = maps:get(node, Params, node()), case - lists:member(Node, mria_mnesia:running_nodes()) - andalso - emqx_management_proto_v1:get_full_config(Node) + lists:member(Node, mria_mnesia:running_nodes()) andalso + emqx_management_proto_v1:get_full_config(Node) of false -> Message = list_to_binary(io_lib:format("Bad node ~p, reason not found", [Node])), @@ -242,8 +278,11 @@ conf_path_reset(Req) -> get_full_config() -> emqx_config:fill_defaults( - maps:without(?EXCLUDES, - emqx:get_raw_config([]))). + maps:without( + ?EXCLUDES, + emqx:get_raw_config([]) + ) + ). get_config_with_default(Path) -> emqx_config:fill_defaults(emqx:get_raw_config(Path)). @@ -278,8 +317,11 @@ gen_schema(Conf) when is_list(Conf) -> #{type => array, items => gen_schema(hd(Conf))} end; gen_schema(Conf) when is_map(Conf) -> - #{type => object, properties => - maps:map(fun(_K, V) -> gen_schema(V) end, Conf)}; + #{ + type => object, + properties => + maps:map(fun(_K, V) -> gen_schema(V) end, Conf) + }; gen_schema(_Conf) -> %% the conf is not of JSON supported type, it may have been converted %% by the hocon schema diff --git a/apps/emqx_management/src/emqx_mgmt_api_metrics.erl b/apps/emqx_management/src/emqx_mgmt_api_metrics.erl index 96f3176b0..69ce7723c 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_metrics.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_metrics.erl @@ -23,14 +23,16 @@ -import(hoconsc, [mk/2, ref/2]). %% minirest/dashbaord_swagger behaviour callbacks --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). --export([ roots/0 - , fields/1 - ]). +-export([ + roots/0, + fields/1 +]). %% http handlers -export([metrics/2]). @@ -53,9 +55,12 @@ metrics(get, #{query_string := Qs}) -> true -> {200, emqx_mgmt:get_metrics()}; false -> - Data = [maps:from_list( - emqx_mgmt:get_metrics(Node) ++ [{node, Node}]) - || Node <- mria_mnesia:running_nodes()], + Data = [ + maps:from_list( + emqx_mgmt:get_metrics(Node) ++ [{node, Node}] + ) + || Node <- mria_mnesia:running_nodes() + ], {200, Data} end. @@ -64,23 +69,34 @@ metrics(get, #{query_string := Qs}) -> %%-------------------------------------------------------------------- schema("/metrics") -> - #{ 'operationId' => metrics - , get => - #{ description => <<"EMQX metrics">> - , parameters => - [{ aggregate - , mk( boolean() - , #{ in => query - , required => false - , desc => <<"Whether to aggregate all nodes Metrics">>}) - }] - , responses => - #{ 200 => hoconsc:union( - [ref(?MODULE, aggregated_metrics), - hoconsc:array(ref(?MODULE, node_metrics))]) - } + #{ + 'operationId' => metrics, + get => + #{ + description => <<"EMQX metrics">>, + parameters => + [ + {aggregate, + mk( + boolean(), + #{ + in => query, + required => false, + desc => <<"Whether to aggregate all nodes Metrics">> + } + )} + ], + responses => + #{ + 200 => hoconsc:union( + [ + ref(?MODULE, aggregated_metrics), + hoconsc:array(ref(?MODULE, node_metrics)) + ] + ) + } } - }. + }. roots() -> []. @@ -91,176 +107,353 @@ fields(node_metrics) -> [{node, mk(binary(), #{desc => <<"Node name">>})}] ++ properties(). properties() -> - [ m('actions.failure', - <<"Number of failure executions of the rule engine action">>) - , m('actions.success', - <<"Number of successful executions of the rule engine action">>) - , m('bytes.received', - <<"Number of bytes received ">>) - , m('bytes.sent', - <<"Number of bytes sent on this connection">>) - , m('client.auth.anonymous', - <<"Number of clients who log in anonymously">>) - , m('client.authenticate', - <<"Number of client authentications">>) - , m('client.check_authz', - <<"Number of Authorization rule checks">>) - , m('client.connack', - <<"Number of CONNACK packet sent">>) - , m('client.connect', - <<"Number of client connections">>) - , m('client.connected', - <<"Number of successful client connections">>) - , m('client.disconnected', - <<"Number of client disconnects">>) - , m('client.subscribe', - <<"Number of client subscriptions">>) - , m('client.unsubscribe', - <<"Number of client unsubscriptions">>) - , m('delivery.dropped', - <<"Total number of discarded messages when sending">>) - , m('delivery.dropped.expired', - <<"Number of messages dropped due to message expiration on sending">>) - , m('delivery.dropped.no_local', - <<"Number of messages that were dropped due to the No Local subscription " - "option when sending">>) - , m('delivery.dropped.qos0_msg', - <<"Number of messages with QoS 0 that were dropped because the message " - "queue was full when sending">>) - , m('delivery.dropped.queue_full', - <<"Number of messages with a non-zero QoS that were dropped because the " - "message queue was full when sending">>) - , m('delivery.dropped.too_large', - <<"The number of messages that were dropped because the length exceeded " - "the limit when sending">>) - , m('messages.acked', - <<"Number of received PUBACK and PUBREC packet">>) - , m('messages.delayed', - <<"Number of delay-published messages">>) - , m('messages.delivered', - <<"Number of messages forwarded to the subscription process internally">>) - , m('messages.dropped', - <<"Total number of messages dropped before forwarding to the subscription process">>) - , m('messages.dropped.await_pubrel_timeout', - <<"Number of messages dropped due to waiting PUBREL timeout">>) - , m('messages.dropped.no_subscribers', - <<"Number of messages dropped due to no subscribers">>) - , m('messages.forward', - <<"Number of messages forwarded to other nodes">>) - , m('messages.publish', - <<"Number of messages published in addition to system messages">>) - , m('messages.qos0.received', - <<"Number of QoS 0 messages received from clients">>) - , m('messages.qos0.sent', - <<"Number of QoS 0 messages sent to clients">>) - , m('messages.qos1.received', - <<"Number of QoS 1 messages received from clients">>) - , m('messages.qos1.sent', - <<"Number of QoS 1 messages sent to clients">>) - , m('messages.qos2.received', - <<"Number of QoS 2 messages received from clients">>) - , m('messages.qos2.sent', - <<"Number of QoS 2 messages sent to clients">>) - , m('messages.received', - <<"Number of messages received from the client, equal to the sum of " - "messages.qos0.received\fmessages.qos1.received and messages.qos2.received">>) - , m('messages.retained', - <<"Number of retained messages">>) - , m('messages.sent', - <<"Number of messages sent to the client, equal to the sum of " - "messages.qos0.sent\fmessages.qos1.sent and messages.qos2.sent">>) - , m('packets.auth.received', - <<"Number of received AUTH packet">>) - , m('packets.auth.sent', - <<"Number of sent AUTH packet">>) - , m('packets.connack.auth_error', - <<"Number of received CONNECT packet with failed authentication">>) - , m('packets.connack.error', - <<"Number of received CONNECT packet with unsuccessful connections">>) - , m('packets.connack.sent', - <<"Number of sent CONNACK packet">>) - , m('packets.connect.received', - <<"Number of received CONNECT packet">>) - , m('packets.disconnect.received', - <<"Number of received DISCONNECT packet">>) - , m('packets.disconnect.sent', - <<"Number of sent DISCONNECT packet">>) - , m('packets.pingreq.received', - <<"Number of received PINGREQ packet">>) - , m('packets.pingresp.sent', - <<"Number of sent PUBRESP packet">>) - , m('packets.puback.inuse', - <<"Number of received PUBACK packet with occupied identifiers">>) - , m('packets.puback.missed', - <<"Number of received packet with identifiers.">>) - , m('packets.puback.received', - <<"Number of received PUBACK packet">>) - , m('packets.puback.sent', - <<"Number of sent PUBACK packet">>) - , m('packets.pubcomp.inuse', - <<"Number of received PUBCOMP packet with occupied identifiers">>) - , m('packets.pubcomp.missed', - <<"Number of missed PUBCOMP packet">>) - , m('packets.pubcomp.received', - <<"Number of received PUBCOMP packet">>) - , m('packets.pubcomp.sent', - <<"Number of sent PUBCOMP packet">>) - , m('packets.publish.auth_error', - <<"Number of received PUBLISH packets with failed the Authorization check">>) - , m('packets.publish.dropped', - <<"Number of messages discarded due to the receiving limit">>) - , m('packets.publish.error', - <<"Number of received PUBLISH packet that cannot be published">>) - , m('packets.publish.inuse', - <<"Number of received PUBLISH packet with occupied identifiers">>) - , m('packets.publish.received', - <<"Number of received PUBLISH packet">>) - , m('packets.publish.sent', - <<"Number of sent PUBLISH packet">>) - , m('packets.pubrec.inuse', - <<"Number of received PUBREC packet with occupied identifiers">>) - , m('packets.pubrec.missed', - <<"Number of received PUBREC packet with unknown identifiers">>) - , m('packets.pubrec.received', - <<"Number of received PUBREC packet">>) - , m('packets.pubrec.sent', - <<"Number of sent PUBREC packet">>) - , m('packets.pubrel.missed', - <<"Number of received PUBREC packet with unknown identifiers">>) - , m('packets.pubrel.received', - <<"Number of received PUBREL packet">>) - , m('packets.pubrel.sent', - <<"Number of sent PUBREL packet">>) - , m('packets.received', - <<"Number of received packet">>) - , m('packets.sent', - <<"Number of sent packet">>) - , m('packets.suback.sent', - <<"Number of sent SUBACK packet">>) - , m('packets.subscribe.auth_error', - <<"Number of received SUBACK packet with failed Authorization check">>) - , m('packets.subscribe.error', - <<"Number of received SUBSCRIBE packet with failed subscriptions">>) - , m('packets.subscribe.received', - <<"Number of received SUBSCRIBE packet">>) - , m('packets.unsuback.sent', - <<"Number of sent UNSUBACK packet">>) - , m('packets.unsubscribe.error', - <<"Number of received UNSUBSCRIBE packet with failed unsubscriptions">>) - , m('packets.unsubscribe.received', - <<"Number of received UNSUBSCRIBE packet">>) - , m('rules.matched', - <<"Number of rule matched">>) - , m('session.created', - <<"Number of sessions created">>) - , m('session.discarded', - <<"Number of sessions dropped because Clean Session or Clean Start is true">>) - , m('session.resumed', - <<"Number of sessions resumed because Clean Session or Clean Start is false">>) - , m('session.takenover', - <<"Number of sessions takenover because Clean Session or Clean Start is false">>) - , m('session.terminated', - <<"Number of terminated sessions">>) + [ + m( + 'actions.failure', + <<"Number of failure executions of the rule engine action">> + ), + m( + 'actions.success', + <<"Number of successful executions of the rule engine action">> + ), + m( + 'bytes.received', + <<"Number of bytes received ">> + ), + m( + 'bytes.sent', + <<"Number of bytes sent on this connection">> + ), + m( + 'client.auth.anonymous', + <<"Number of clients who log in anonymously">> + ), + m( + 'client.authenticate', + <<"Number of client authentications">> + ), + m( + 'client.check_authz', + <<"Number of Authorization rule checks">> + ), + m( + 'client.connack', + <<"Number of CONNACK packet sent">> + ), + m( + 'client.connect', + <<"Number of client connections">> + ), + m( + 'client.connected', + <<"Number of successful client connections">> + ), + m( + 'client.disconnected', + <<"Number of client disconnects">> + ), + m( + 'client.subscribe', + <<"Number of client subscriptions">> + ), + m( + 'client.unsubscribe', + <<"Number of client unsubscriptions">> + ), + m( + 'delivery.dropped', + <<"Total number of discarded messages when sending">> + ), + m( + 'delivery.dropped.expired', + <<"Number of messages dropped due to message expiration on sending">> + ), + m( + 'delivery.dropped.no_local', + << + "Number of messages that were dropped due to the No Local subscription " + "option when sending" + >> + ), + m( + 'delivery.dropped.qos0_msg', + << + "Number of messages with QoS 0 that were dropped because the message " + "queue was full when sending" + >> + ), + m( + 'delivery.dropped.queue_full', + << + "Number of messages with a non-zero QoS that were dropped because the " + "message queue was full when sending" + >> + ), + m( + 'delivery.dropped.too_large', + << + "The number of messages that were dropped because the length exceeded " + "the limit when sending" + >> + ), + m( + 'messages.acked', + <<"Number of received PUBACK and PUBREC packet">> + ), + m( + 'messages.delayed', + <<"Number of delay-published messages">> + ), + m( + 'messages.delivered', + <<"Number of messages forwarded to the subscription process internally">> + ), + m( + 'messages.dropped', + <<"Total number of messages dropped before forwarding to the subscription process">> + ), + m( + 'messages.dropped.await_pubrel_timeout', + <<"Number of messages dropped due to waiting PUBREL timeout">> + ), + m( + 'messages.dropped.no_subscribers', + <<"Number of messages dropped due to no subscribers">> + ), + m( + 'messages.forward', + <<"Number of messages forwarded to other nodes">> + ), + m( + 'messages.publish', + <<"Number of messages published in addition to system messages">> + ), + m( + 'messages.qos0.received', + <<"Number of QoS 0 messages received from clients">> + ), + m( + 'messages.qos0.sent', + <<"Number of QoS 0 messages sent to clients">> + ), + m( + 'messages.qos1.received', + <<"Number of QoS 1 messages received from clients">> + ), + m( + 'messages.qos1.sent', + <<"Number of QoS 1 messages sent to clients">> + ), + m( + 'messages.qos2.received', + <<"Number of QoS 2 messages received from clients">> + ), + m( + 'messages.qos2.sent', + <<"Number of QoS 2 messages sent to clients">> + ), + m( + 'messages.received', + << + "Number of messages received from the client, equal to the sum of " + "messages.qos0.received\fmessages.qos1.received and messages.qos2.received" + >> + ), + m( + 'messages.retained', + <<"Number of retained messages">> + ), + m( + 'messages.sent', + << + "Number of messages sent to the client, equal to the sum of " + "messages.qos0.sent\fmessages.qos1.sent and messages.qos2.sent" + >> + ), + m( + 'packets.auth.received', + <<"Number of received AUTH packet">> + ), + m( + 'packets.auth.sent', + <<"Number of sent AUTH packet">> + ), + m( + 'packets.connack.auth_error', + <<"Number of received CONNECT packet with failed authentication">> + ), + m( + 'packets.connack.error', + <<"Number of received CONNECT packet with unsuccessful connections">> + ), + m( + 'packets.connack.sent', + <<"Number of sent CONNACK packet">> + ), + m( + 'packets.connect.received', + <<"Number of received CONNECT packet">> + ), + m( + 'packets.disconnect.received', + <<"Number of received DISCONNECT packet">> + ), + m( + 'packets.disconnect.sent', + <<"Number of sent DISCONNECT packet">> + ), + m( + 'packets.pingreq.received', + <<"Number of received PINGREQ packet">> + ), + m( + 'packets.pingresp.sent', + <<"Number of sent PUBRESP packet">> + ), + m( + 'packets.puback.inuse', + <<"Number of received PUBACK packet with occupied identifiers">> + ), + m( + 'packets.puback.missed', + <<"Number of received packet with identifiers.">> + ), + m( + 'packets.puback.received', + <<"Number of received PUBACK packet">> + ), + m( + 'packets.puback.sent', + <<"Number of sent PUBACK packet">> + ), + m( + 'packets.pubcomp.inuse', + <<"Number of received PUBCOMP packet with occupied identifiers">> + ), + m( + 'packets.pubcomp.missed', + <<"Number of missed PUBCOMP packet">> + ), + m( + 'packets.pubcomp.received', + <<"Number of received PUBCOMP packet">> + ), + m( + 'packets.pubcomp.sent', + <<"Number of sent PUBCOMP packet">> + ), + m( + 'packets.publish.auth_error', + <<"Number of received PUBLISH packets with failed the Authorization check">> + ), + m( + 'packets.publish.dropped', + <<"Number of messages discarded due to the receiving limit">> + ), + m( + 'packets.publish.error', + <<"Number of received PUBLISH packet that cannot be published">> + ), + m( + 'packets.publish.inuse', + <<"Number of received PUBLISH packet with occupied identifiers">> + ), + m( + 'packets.publish.received', + <<"Number of received PUBLISH packet">> + ), + m( + 'packets.publish.sent', + <<"Number of sent PUBLISH packet">> + ), + m( + 'packets.pubrec.inuse', + <<"Number of received PUBREC packet with occupied identifiers">> + ), + m( + 'packets.pubrec.missed', + <<"Number of received PUBREC packet with unknown identifiers">> + ), + m( + 'packets.pubrec.received', + <<"Number of received PUBREC packet">> + ), + m( + 'packets.pubrec.sent', + <<"Number of sent PUBREC packet">> + ), + m( + 'packets.pubrel.missed', + <<"Number of received PUBREC packet with unknown identifiers">> + ), + m( + 'packets.pubrel.received', + <<"Number of received PUBREL packet">> + ), + m( + 'packets.pubrel.sent', + <<"Number of sent PUBREL packet">> + ), + m( + 'packets.received', + <<"Number of received packet">> + ), + m( + 'packets.sent', + <<"Number of sent packet">> + ), + m( + 'packets.suback.sent', + <<"Number of sent SUBACK packet">> + ), + m( + 'packets.subscribe.auth_error', + <<"Number of received SUBACK packet with failed Authorization check">> + ), + m( + 'packets.subscribe.error', + <<"Number of received SUBSCRIBE packet with failed subscriptions">> + ), + m( + 'packets.subscribe.received', + <<"Number of received SUBSCRIBE packet">> + ), + m( + 'packets.unsuback.sent', + <<"Number of sent UNSUBACK packet">> + ), + m( + 'packets.unsubscribe.error', + <<"Number of received UNSUBSCRIBE packet with failed unsubscriptions">> + ), + m( + 'packets.unsubscribe.received', + <<"Number of received UNSUBSCRIBE packet">> + ), + m( + 'rules.matched', + <<"Number of rule matched">> + ), + m( + 'session.created', + <<"Number of sessions created">> + ), + m( + 'session.discarded', + <<"Number of sessions dropped because Clean Session or Clean Start is true">> + ), + m( + 'session.resumed', + <<"Number of sessions resumed because Clean Session or Clean Start is false">> + ), + m( + 'session.takenover', + <<"Number of sessions takenover because Clean Session or Clean Start is false">> + ), + m( + 'session.terminated', + <<"Number of terminated sessions">> + ) ]. m(K, Desc) -> diff --git a/apps/emqx_management/src/emqx_mgmt_api_nodes.erl b/apps/emqx_management/src/emqx_mgmt_api_nodes.erl index dba1cc5a8..5813e6522 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_nodes.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_nodes.erl @@ -28,18 +28,20 @@ -define(SOURCE_ERROR, 'SOURCE_ERROR'). %% Swagger specs from hocon schema --export([ api_spec/0 - , schema/1 - , paths/0 - , fields/1 - ]). +-export([ + api_spec/0, + schema/1, + paths/0, + fields/1 +]). %% API callbacks --export([ nodes/2 - , node/2 - , node_metrics/2 - , node_stats/2 - ]). +-export([ + nodes/2, + node/2, + node_metrics/2, + node_stats/2 +]). %%-------------------------------------------------------------------- %% API spec funcs @@ -49,123 +51,183 @@ api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/nodes" - , "/nodes/:node" - , "/nodes/:node/metrics" - , "/nodes/:node/stats" + [ + "/nodes", + "/nodes/:node", + "/nodes/:node/metrics", + "/nodes/:node/stats" ]. schema("/nodes") -> - #{ 'operationId' => nodes - , get => - #{ description => <<"List EMQX nodes">> - , responses => - #{200 => mk( array(ref(node_info)) - , #{desc => <<"List all EMQX nodes">>})} + #{ + 'operationId' => nodes, + get => + #{ + description => <<"List EMQX nodes">>, + responses => + #{ + 200 => mk( + array(ref(node_info)), + #{desc => <<"List all EMQX nodes">>} + ) + } } - }; + }; schema("/nodes/:node") -> - #{ 'operationId' => node - , get => - #{ description => <<"Get node info">> - , parameters => [ref(node_name)] - , responses => - #{ 200 => mk( ref(node_info) - , #{desc => <<"Get node info successfully">>}) - , 400 => node_error() - } + #{ + 'operationId' => node, + get => + #{ + description => <<"Get node info">>, + parameters => [ref(node_name)], + responses => + #{ + 200 => mk( + ref(node_info), + #{desc => <<"Get node info successfully">>} + ), + 400 => node_error() + } } - }; + }; schema("/nodes/:node/metrics") -> - #{ 'operationId' => node_metrics - , get => - #{ description => <<"Get node metrics">> - , parameters => [ref(node_name)] - , responses => - #{ 200 => mk( ref(?NODE_METRICS_MODULE, node_metrics) - , #{desc => <<"Get node metrics successfully">>}) - , 400 => node_error() - } + #{ + 'operationId' => node_metrics, + get => + #{ + description => <<"Get node metrics">>, + parameters => [ref(node_name)], + responses => + #{ + 200 => mk( + ref(?NODE_METRICS_MODULE, node_metrics), + #{desc => <<"Get node metrics successfully">>} + ), + 400 => node_error() + } } - }; + }; schema("/nodes/:node/stats") -> - #{ 'operationId' => node_stats - , get => - #{ description => <<"Get node stats">> - , parameters => [ref(node_name)] - , responses => - #{ 200 => mk( ref(?NODE_STATS_MODULE, node_stats_data) - , #{desc => <<"Get node stats successfully">>}) - , 400 => node_error() - } + #{ + 'operationId' => node_stats, + get => + #{ + description => <<"Get node stats">>, + parameters => [ref(node_name)], + responses => + #{ + 200 => mk( + ref(?NODE_STATS_MODULE, node_stats_data), + #{desc => <<"Get node stats successfully">>} + ), + 400 => node_error() + } } - }. + }. %%-------------------------------------------------------------------- %% Fields fields(node_name) -> - [ { node - , mk(atom() - , #{ in => path - , description => <<"Node name">> - , required => true - , example => <<"emqx@127.0.0.1">> - }) - } + [ + {node, + mk( + atom(), + #{ + in => path, + description => <<"Node name">>, + required => true, + example => <<"emqx@127.0.0.1">> + } + )} ]; fields(node_info) -> - [ { node - , mk( atom() - , #{desc => <<"Node name">>, example => <<"emqx@127.0.0.1">>})} - , { connections - , mk( non_neg_integer() - , #{desc => <<"Number of clients currently connected to this node">>, example => 0})} - , { load1 - , mk( string() - , #{desc => <<"CPU average load in 1 minute">>, example => "2.66"})} - , { load5 - , mk( string() - , #{desc => <<"CPU average load in 5 minute">>, example => "2.66"})} - , { load15 - , mk( string() - , #{desc => <<"CPU average load in 15 minute">>, example => "2.66"})} - , { max_fds - , mk( non_neg_integer() - , #{desc => <<"File descriptors limit">>, example => 1024})} - , { memory_total - , mk( emqx_schema:bytesize() - , #{desc => <<"Allocated memory">>, example => "512.00M"})} - , { memory_used - , mk( emqx_schema:bytesize() - , #{desc => <<"Used memory">>, example => "256.00M"})} - , { node_status - , mk( enum(['Running', 'Stopped']) - , #{desc => <<"Node status">>, example => "Running"})} - , { otp_release - , mk( string() - , #{ desc => <<"Erlang/OTP version">>, example => "24.2/12.2"})} - , { process_available - , mk( non_neg_integer() - , #{desc => <<"Erlang processes limit">>, example => 2097152})} - , { process_used - , mk( non_neg_integer() - , #{desc => <<"Running Erlang processes">>, example => 1024})} - , { uptime - , mk( non_neg_integer() - , #{desc => <<"System uptime, milliseconds">>, example => 5120000})} - , { version - , mk( string() - , #{desc => <<"Release version">>, example => "5.0.0-beat.3-00000000"})} - , { sys_path - , mk( string() - , #{desc => <<"Path to system files">>, example => "path/to/emqx"})} - , { log_path - , mk( string() - , #{desc => <<"Path to log files">>, example => "path/to/log | not found"})} - , { role - , mk( enum([core, replicant]) - , #{desc => <<"Node role">>, example => "core"})} + [ + {node, + mk( + atom(), + #{desc => <<"Node name">>, example => <<"emqx@127.0.0.1">>} + )}, + {connections, + mk( + non_neg_integer(), + #{desc => <<"Number of clients currently connected to this node">>, example => 0} + )}, + {load1, + mk( + string(), + #{desc => <<"CPU average load in 1 minute">>, example => "2.66"} + )}, + {load5, + mk( + string(), + #{desc => <<"CPU average load in 5 minute">>, example => "2.66"} + )}, + {load15, + mk( + string(), + #{desc => <<"CPU average load in 15 minute">>, example => "2.66"} + )}, + {max_fds, + mk( + non_neg_integer(), + #{desc => <<"File descriptors limit">>, example => 1024} + )}, + {memory_total, + mk( + emqx_schema:bytesize(), + #{desc => <<"Allocated memory">>, example => "512.00M"} + )}, + {memory_used, + mk( + emqx_schema:bytesize(), + #{desc => <<"Used memory">>, example => "256.00M"} + )}, + {node_status, + mk( + enum(['Running', 'Stopped']), + #{desc => <<"Node status">>, example => "Running"} + )}, + {otp_release, + mk( + string(), + #{desc => <<"Erlang/OTP version">>, example => "24.2/12.2"} + )}, + {process_available, + mk( + non_neg_integer(), + #{desc => <<"Erlang processes limit">>, example => 2097152} + )}, + {process_used, + mk( + non_neg_integer(), + #{desc => <<"Running Erlang processes">>, example => 1024} + )}, + {uptime, + mk( + non_neg_integer(), + #{desc => <<"System uptime, milliseconds">>, example => 5120000} + )}, + {version, + mk( + string(), + #{desc => <<"Release version">>, example => "5.0.0-beat.3-00000000"} + )}, + {sys_path, + mk( + string(), + #{desc => <<"Path to system files">>, example => "path/to/emqx"} + )}, + {log_path, + mk( + string(), + #{desc => <<"Path to log files">>, example => "path/to/log | not found"} + )}, + {role, + mk( + enum([core, replicant]), + #{desc => <<"Node role">>, example => "core"} + )} ]. %%-------------------------------------------------------------------- @@ -221,17 +283,20 @@ get_stats(Node) -> format(_Node, Info = #{memory_total := Total, memory_used := Used}) -> {ok, SysPathBinary} = file:get_cwd(), SysPath = list_to_binary(SysPathBinary), - LogPath = case log_path() of - undefined -> - <<"not found">>; - Path0 -> - Path = list_to_binary(Path0), - <> - end, - Info#{ memory_total := emqx_mgmt_util:kmg(Total) - , memory_used := emqx_mgmt_util:kmg(Used) - , sys_path => SysPath - , log_path => LogPath}. + LogPath = + case log_path() of + undefined -> + <<"not found">>; + Path0 -> + Path = list_to_binary(Path0), + <> + end, + Info#{ + memory_total := emqx_mgmt_util:kmg(Total), + memory_used := emqx_mgmt_util:kmg(Used), + sys_path => SysPath, + log_path => LogPath + }. log_path() -> Configs = logger:get_handler_config(), diff --git a/apps/emqx_management/src/emqx_mgmt_api_plugins.erl b/apps/emqx_management/src/emqx_mgmt_api_plugins.erl index b9415391e..ea9f1e0ad 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_plugins.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_plugins.erl @@ -22,27 +22,30 @@ -include_lib("emqx/include/logger.hrl"). %%-include_lib("emqx_plugins/include/emqx_plugins.hrl"). --export([ api_spec/0 - , fields/1 - , paths/0 - , schema/1 - , namespace/0 - ]). +-export([ + api_spec/0, + fields/1, + paths/0, + schema/1, + namespace/0 +]). --export([ list_plugins/2 - , upload_install/2 - , plugin/2 - , update_plugin/2 - , update_boot_order/2 - ]). +-export([ + list_plugins/2, + upload_install/2, + plugin/2, + update_plugin/2, + update_boot_order/2 +]). --export([ validate_name/1 - , get_plugins/0 - , install_package/2 - , delete_package/1 - , describe_package/1 - , ensure_action/2 - ]). +-export([ + validate_name/1, + get_plugins/0, + install_package/2, + delete_package/1, + describe_package/1, + ensure_action/2 +]). -define(NAME_RE, "^[A-Za-z]+[A-Za-z0-9-_.]*$"). @@ -65,9 +68,10 @@ schema("/plugins") -> #{ 'operationId' => list_plugins, get => #{ - description => "List all install plugins.
" - "Plugins are launched in top-down order.
" - "Using `POST /plugins/{name}/move` to change the boot order.", + description => + "List all install plugins.
" + "Plugins are launched in top-down order.
" + "Using `POST /plugins/{name}/move` to change the boot order.", responses => #{ 200 => hoconsc:array(hoconsc:ref(plugin)) } @@ -77,20 +81,26 @@ schema("/plugins/install") -> #{ 'operationId' => upload_install, post => #{ - description => "Install a plugin(plugin-vsn.tar.gz)." - "Follow [emqx-plugin-template](https://github.com/emqx/emqx-plugin-template) " - "to develop plugin.", + description => + "Install a plugin(plugin-vsn.tar.gz)." + "Follow [emqx-plugin-template](https://github.com/emqx/emqx-plugin-template) " + "to develop plugin.", 'requestBody' => #{ content => #{ 'multipart/form-data' => #{ schema => #{ type => object, properties => #{ - plugin => #{type => string, format => binary}}}, - encoding => #{plugin => #{'contentType' => 'application/gzip'}}}}}, + plugin => #{type => string, format => binary} + } + }, + encoding => #{plugin => #{'contentType' => 'application/gzip'}} + } + } + }, responses => #{ 200 => <<"OK">>, - 400 => emqx_dashboard_swagger:error_codes(['UNEXPECTED_ERROR','ALREADY_INSTALLED']) + 400 => emqx_dashboard_swagger:error_codes(['UNEXPECTED_ERROR', 'ALREADY_INSTALLED']) } } }; @@ -118,12 +128,14 @@ schema("/plugins/:name/:action") -> #{ 'operationId' => update_plugin, put => #{ - description => "start/stop a installed plugin.
" - "- **start**: start the plugin.
" - "- **stop**: stop the plugin.
", + description => + "start/stop a installed plugin.
" + "- **start**: start the plugin.
" + "- **stop**: stop the plugin.
", parameters => [ hoconsc:ref(name), - {action, hoconsc:mk(hoconsc:enum([start, stop]), #{desc => "Action", in => path})}], + {action, hoconsc:mk(hoconsc:enum([start, stop]), #{desc => "Action", in => path})} + ], responses => #{ 200 => <<"OK">>, 404 => emqx_dashboard_swagger:error_codes(['NOT_FOUND'], <<"Plugin Not Found">>) @@ -143,57 +155,83 @@ schema("/plugins/:name/move") -> fields(plugin) -> [ - {name, hoconsc:mk(binary(), - #{ - desc => "Name-Vsn: without .tar.gz", - validator => fun ?MODULE:validate_name/1, - required => true, - example => "emqx_plugin_template-5.0-rc.1"}) - }, + {name, + hoconsc:mk( + binary(), + #{ + desc => "Name-Vsn: without .tar.gz", + validator => fun ?MODULE:validate_name/1, + required => true, + example => "emqx_plugin_template-5.0-rc.1" + } + )}, {author, hoconsc:mk(list(string()), #{example => [<<"EMQX Team">>]})}, {builder, hoconsc:ref(?MODULE, builder)}, {built_on_otp_release, hoconsc:mk(string(), #{example => "24"})}, {compatibility, hoconsc:mk(map(), #{example => #{<<"emqx">> => <<"~>5.0">>}})}, - {git_commit_or_build_date, hoconsc:mk(string(), #{ - example => "2021-12-25", - desc => "Last git commit date by `git log -1 --pretty=format:'%cd' " - "--date=format:'%Y-%m-%d`.\n" - " If the last commit date is not available, the build date will be presented." - })}, + {git_commit_or_build_date, + hoconsc:mk(string(), #{ + example => "2021-12-25", + desc => + "Last git commit date by `git log -1 --pretty=format:'%cd' " + "--date=format:'%Y-%m-%d`.\n" + " If the last commit date is not available, the build date will be presented." + })}, {functionality, hoconsc:mk(hoconsc:array(string()), #{example => [<<"Demo">>]})}, {git_ref, hoconsc:mk(string(), #{example => "ddab50fafeed6b1faea70fc9ffd8c700d7e26ec1"})}, {metadata_vsn, hoconsc:mk(string(), #{example => "0.1.0"})}, - {rel_vsn, hoconsc:mk(binary(), - #{desc => "Plugins release version", - required => true, - example => <<"5.0-rc.1">>}) - }, - {rel_apps, hoconsc:mk(hoconsc:array(binary()), - #{desc => "Aplications in plugin.", - required => true, - example => [<<"emqx_plugin_template-5.0.0">>, <<"map_sets-1.1.0">>]}) - }, + {rel_vsn, + hoconsc:mk( + binary(), + #{ + desc => "Plugins release version", + required => true, + example => <<"5.0-rc.1">> + } + )}, + {rel_apps, + hoconsc:mk( + hoconsc:array(binary()), + #{ + desc => "Aplications in plugin.", + required => true, + example => [<<"emqx_plugin_template-5.0.0">>, <<"map_sets-1.1.0">>] + } + )}, {repo, hoconsc:mk(string(), #{example => "https://github.com/emqx/emqx-plugin-template"})}, - {description, hoconsc:mk(binary(), - #{desc => "Plugin description.", - required => true, - example => "This is an demo plugin description"}) - }, - {running_status, hoconsc:mk(hoconsc:array(hoconsc:ref(running_status)), - #{required => true})}, - {readme, hoconsc:mk(binary(), #{ - example => "This is an demo plugin.", - desc => "only return when `GET /plugins/{name}`.", - required => false})} + {description, + hoconsc:mk( + binary(), + #{ + desc => "Plugin description.", + required => true, + example => "This is an demo plugin description" + } + )}, + {running_status, + hoconsc:mk( + hoconsc:array(hoconsc:ref(running_status)), + #{required => true} + )}, + {readme, + hoconsc:mk(binary(), #{ + example => "This is an demo plugin.", + desc => "only return when `GET /plugins/{name}`.", + required => false + })} ]; fields(name) -> - [{name, hoconsc:mk(binary(), - #{ - desc => list_to_binary(?NAME_RE), - example => "emqx_plugin_template-5.0-rc.1", - in => path, - validator => fun ?MODULE:validate_name/1 - })} + [ + {name, + hoconsc:mk( + binary(), + #{ + desc => list_to_binary(?NAME_RE), + example => "emqx_plugin_template-5.0-rc.1", + in => path, + validator => fun ?MODULE:validate_name/1 + } + )} ]; fields(builder) -> [ @@ -202,27 +240,38 @@ fields(builder) -> {website, hoconsc:mk(string(), #{example => "www.emqx.com"})} ]; fields(position) -> - [{position, hoconsc:mk(hoconsc:union([front, rear, binary()]), - #{ - desc => """ - Enable auto-boot at position in the boot list, where Position could be - 'front', 'rear', or 'before:other-vsn', 'after:other-vsn' - to specify a relative position. - """, - required => false - })}]; + [ + {position, + hoconsc:mk( + hoconsc:union([front, rear, binary()]), + #{ + desc => + "" + "\n" + " Enable auto-boot at position in the boot list, where Position could be\n" + " 'front', 'rear', or 'before:other-vsn', 'after:other-vsn'\n" + " to specify a relative position.\n" + " " + "", + required => false + } + )} + ]; fields(running_status) -> [ {node, hoconsc:mk(string(), #{example => "emqx@127.0.0.1"})}, - {status, hoconsc:mk(hoconsc:enum([running, stopped]), #{ - desc => "Install plugin status at runtime
" - "1. running: plugin is running.
" - "2. stopped: plugin is stopped.
" - })} + {status, + hoconsc:mk(hoconsc:enum([running, stopped]), #{ + desc => + "Install plugin status at runtime
" + "1. running: plugin is running.
" + "2. stopped: plugin is stopped.
" + })} ]. move_request_body() -> - emqx_dashboard_swagger:schema_with_examples(hoconsc:ref(?MODULE, position), + emqx_dashboard_swagger:schema_with_examples( + hoconsc:ref(?MODULE, position), #{ move_to_front => #{ summary => <<"move plugin on the front">>, @@ -240,7 +289,8 @@ move_request_body() -> summary => <<"move plugin after other plugins">>, value => #{position => <<"after:emqx_plugin_demo-5.1-rc.2">>} } - }). + } + ). validate_name(Name) -> NameLen = byte_size(Name), @@ -250,7 +300,8 @@ validate_name(Name) -> nomatch -> {error, "Name should be " ?NAME_RE}; _ -> ok end; - false -> {error, "Name Length must =< 256"} + false -> + {error, "Name Length must =< 256"} end. %% API CallBack Begin @@ -271,29 +322,42 @@ upload_install(post, #{body := #{<<"plugin">> := Plugin}}) when is_map(Plugin) - {AppName, _Vsn} = emqx_plugins:parse_name_vsn(FileName), AppDir = filename:join(emqx_plugins:install_dir(), AppName), case filelib:wildcard(AppDir ++ "*.tar.gz") of - [] -> do_install_package(FileName, Bin); + [] -> + do_install_package(FileName, Bin); OtherVsn -> - {400, #{code => 'ALREADY_INSTALLED', - message => iolist_to_binary(io_lib:format("~p already installed", - [OtherVsn]))}} + {400, #{ + code => 'ALREADY_INSTALLED', + message => iolist_to_binary( + io_lib:format( + "~p already installed", + [OtherVsn] + ) + ) + }} end; {ok, _} -> - {400, #{code => 'ALREADY_INSTALLED', - message => iolist_to_binary(io_lib:format("~p is already installed", [FileName]))}} + {400, #{ + code => 'ALREADY_INSTALLED', + message => iolist_to_binary(io_lib:format("~p is already installed", [FileName])) + }} end; upload_install(post, #{}) -> - {400, #{code => 'BAD_FORM_DATA', + {400, #{ + code => 'BAD_FORM_DATA', message => - <<"form-data should be `plugin=@packagename-vsn.tar.gz;type=application/x-gzip`">>} - }. + <<"form-data should be `plugin=@packagename-vsn.tar.gz;type=application/x-gzip`">> + }}. do_install_package(FileName, Bin) -> {Res, _} = emqx_mgmt_api_plugins_proto_v1:install_package(FileName, Bin), case lists:filter(fun(R) -> R =/= ok end, Res) of - [] -> {200}; + [] -> + {200}; [{error, Reason} | _] -> - {400, #{code => 'UNEXPECTED_ERROR', - message => iolist_to_binary(io_lib:format("~p", [Reason]))}} + {400, #{ + code => 'UNEXPECTED_ERROR', + message => iolist_to_binary(io_lib:format("~p", [Reason])) + }} end. plugin(get, #{bindings := #{name := Name}}) -> @@ -302,7 +366,6 @@ plugin(get, #{bindings := #{name := Name}}) -> [Plugin] -> {200, Plugin}; [] -> {404, #{code => 'NOT_FOUND', message => Name}} end; - plugin(delete, #{bindings := #{name := Name}}) -> {ok, _TnxId, Res} = emqx_mgmt_api_plugins_proto_v1:delete_package(Name), return(204, Res). @@ -313,13 +376,17 @@ update_plugin(put, #{bindings := #{name := Name, action := Action}}) -> update_boot_order(post, #{bindings := #{name := Name}, body := Body}) -> case parse_position(Body, Name) of - {error, Reason} -> {400, #{code => 'BAD_POSITION', message => Reason}}; + {error, Reason} -> + {400, #{code => 'BAD_POSITION', message => Reason}}; Position -> case emqx_plugins:ensure_enabled(Name, Position) of - ok -> {200}; + ok -> + {200}; {error, Reason} -> - {400, #{code => 'MOVE_FAILED', - message => iolist_to_binary(io_lib:format("~p", [Reason]))}} + {400, #{ + code => 'MOVE_FAILED', + message => iolist_to_binary(io_lib:format("~p", [Reason])) + }} end end. @@ -347,7 +414,8 @@ delete_package(Name) -> _ = emqx_plugins:ensure_disabled(Name), _ = emqx_plugins:purge(Name), _ = emqx_plugins:delete_package(Name); - Error -> Error + Error -> + Error end. %% for RPC plugin update @@ -361,15 +429,19 @@ ensure_action(Name, restart) -> _ = emqx_plugins:ensure_enabled(Name), _ = emqx_plugins:restart(Name). -return(Code, ok) -> {Code}; -return(Code, {ok, Result}) -> {Code, Result}; +return(Code, ok) -> + {Code}; +return(Code, {ok, Result}) -> + {Code, Result}; return(_, {error, #{error := "bad_info_file", return := {enoent, _}, path := Path}}) -> {404, #{code => 'NOT_FOUND', message => Path}}; return(_, {error, Reason}) -> {400, #{code => 'PARAM_ERROR', message => iolist_to_binary(io_lib:format("~p", [Reason]))}}. -parse_position(#{<<"position">> := <<"front">>}, _) -> front; -parse_position(#{<<"position">> := <<"rear">>}, _) -> rear; +parse_position(#{<<"position">> := <<"front">>}, _) -> + front; +parse_position(#{<<"position">> := <<"rear">>}, _) -> + rear; parse_position(#{<<"position">> := <<"before:", Name/binary>>}, Name) -> {error, <<"Invalid parameter. Cannot be placed before itself">>}; parse_position(#{<<"position">> := <<"after:", Name/binary>>}, Name) -> @@ -382,7 +454,8 @@ parse_position(#{<<"position">> := <<"before:", Before/binary>>}, _Name) -> {before, binary_to_list(Before)}; parse_position(#{<<"position">> := <<"after:", After/binary>>}, _Name) -> {behind, binary_to_list(After)}; -parse_position(Position, _) -> {error, iolist_to_binary(io_lib:format("~p", [Position]))}. +parse_position(Position, _) -> + {error, iolist_to_binary(io_lib:format("~p", [Position]))}. format_plugins(List) -> StatusMap = aggregate_status(List), @@ -392,13 +465,18 @@ format_plugins(List) -> pack_status_in_order(List, StatusMap) -> {Plugins, _} = - lists:foldl(fun({_Node, PluginList}, {Acc, StatusAcc}) -> - pack_plugin_in_order(PluginList, Acc, StatusAcc) - end, {[], StatusMap}, List), + lists:foldl( + fun({_Node, PluginList}, {Acc, StatusAcc}) -> + pack_plugin_in_order(PluginList, Acc, StatusAcc) + end, + {[], StatusMap}, + List + ), lists:reverse(Plugins). -pack_plugin_in_order([], Acc, StatusAcc) -> {Acc, StatusAcc}; -pack_plugin_in_order(_, Acc, StatusAcc)when map_size(StatusAcc) =:= 0 -> {Acc, StatusAcc}; +pack_plugin_in_order([], Acc, StatusAcc) -> + {Acc, StatusAcc}; +pack_plugin_in_order(_, Acc, StatusAcc) when map_size(StatusAcc) =:= 0 -> {Acc, StatusAcc}; pack_plugin_in_order([Plugin0 | Plugins], Acc, StatusAcc) -> #{<<"name">> := Name, <<"rel_vsn">> := Vsn} = Plugin0, case maps:find({Name, Vsn}, StatusAcc) of @@ -413,15 +491,20 @@ pack_plugin_in_order([Plugin0 | Plugins], Acc, StatusAcc) -> aggregate_status(List) -> aggregate_status(List, #{}). -aggregate_status([], Acc) -> Acc; +aggregate_status([], Acc) -> + Acc; aggregate_status([{Node, Plugins} | List], Acc) -> NewAcc = - lists:foldl(fun(Plugin, SubAcc) -> - #{<<"name">> := Name, <<"rel_vsn">> := Vsn} = Plugin, - Key = {Name, Vsn}, - Value = #{node => Node, status => plugin_status(Plugin)}, - SubAcc#{Key => [Value | maps:get(Key, Acc, [])]} - end, Acc, Plugins), + lists:foldl( + fun(Plugin, SubAcc) -> + #{<<"name">> := Name, <<"rel_vsn">> := Vsn} = Plugin, + Key = {Name, Vsn}, + Value = #{node => Node, status => plugin_status(Plugin)}, + SubAcc#{Key => [Value | maps:get(Key, Acc, [])]} + end, + Acc, + Plugins + ), aggregate_status(List, NewAcc). % running_status: running loaded, stopped diff --git a/apps/emqx_management/src/emqx_mgmt_api_publish.erl b/apps/emqx_management/src/emqx_mgmt_api_publish.erl index c0e208fbb..27280051c 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_publish.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_publish.erl @@ -20,14 +20,17 @@ -behaviour(minirest_api). --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). --export([ publish/2 - , publish_batch/2]). +-export([ + publish/2, + publish_batch/2 +]). api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}). @@ -46,7 +49,6 @@ schema("/publish") -> } } }; - schema("/publish/bulk") -> #{ 'operationId' => publish_batch, @@ -61,32 +63,43 @@ schema("/publish/bulk") -> fields(publish_message) -> [ - {topic, hoconsc:mk(binary(), #{ - desc => <<"Topic Name">>, - required => true, - example => <<"api/example/topic">>})}, - {qos, hoconsc:mk(emqx_schema:qos(), #{ - desc => <<"MQTT QoS">>, - required => false, - default => 0})}, - {from, hoconsc:mk(binary(), #{ - desc => <<"From client ID">>, - required => false, - example => <<"api_example_client">>})}, - {payload, hoconsc:mk(binary(), #{ - desc => <<"MQTT Payload">>, - required => true, - example => <<"hello emqx api">>})}, - {retain, hoconsc:mk(boolean(), #{ - desc => <<"MQTT Retain Message">>, - required => false, - default => false})} + {topic, + hoconsc:mk(binary(), #{ + desc => <<"Topic Name">>, + required => true, + example => <<"api/example/topic">> + })}, + {qos, + hoconsc:mk(emqx_schema:qos(), #{ + desc => <<"MQTT QoS">>, + required => false, + default => 0 + })}, + {from, + hoconsc:mk(binary(), #{ + desc => <<"From client ID">>, + required => false, + example => <<"api_example_client">> + })}, + {payload, + hoconsc:mk(binary(), #{ + desc => <<"MQTT Payload">>, + required => true, + example => <<"hello emqx api">> + })}, + {retain, + hoconsc:mk(boolean(), #{ + desc => <<"MQTT Retain Message">>, + required => false, + default => false + })} ]; - fields(publish_message_info) -> [ - {id, hoconsc:mk(binary(), #{ - desc => <<"Internal Message ID">>})} + {id, + hoconsc:mk(binary(), #{ + desc => <<"Internal Message ID">> + })} ] ++ fields(publish_message). publish(post, #{body := Body}) -> @@ -100,19 +113,21 @@ publish_batch(post, #{body := Body}) -> {200, format_message(Messages)}. message(Map) -> - From = maps:get(<<"from">>, Map, http_api), - QoS = maps:get(<<"qos">>, Map, 0), - Topic = maps:get(<<"topic">>, Map), + From = maps:get(<<"from">>, Map, http_api), + QoS = maps:get(<<"qos">>, Map, 0), + Topic = maps:get(<<"topic">>, Map), Payload = maps:get(<<"payload">>, Map), - Retain = maps:get(<<"retain">>, Map, false), + Retain = maps:get(<<"retain">>, Map, false), emqx_message:make(From, QoS, Topic, Payload, #{retain => Retain}, #{}). messages(List) -> [message(MessageMap) || MessageMap <- List]. -format_message(Messages) when is_list(Messages)-> +format_message(Messages) when is_list(Messages) -> [format_message(Message) || Message <- Messages]; -format_message(#message{id = ID, qos = Qos, from = From, topic = Topic, payload = Payload, flags = Flags}) -> +format_message(#message{ + id = ID, qos = Qos, from = From, topic = Topic, payload = Payload, flags = Flags +}) -> #{ id => emqx_guid:to_hexstr(ID), qos => Qos, @@ -124,5 +139,5 @@ format_message(#message{id = ID, qos = Qos, from = From, topic = Topic, payload to_binary(Data) when is_binary(Data) -> Data; -to_binary(Data) -> +to_binary(Data) -> list_to_binary(io_lib:format("~p", [Data])). diff --git a/apps/emqx_management/src/emqx_mgmt_api_stats.erl b/apps/emqx_management/src/emqx_mgmt_api_stats.erl index e67f142ac..6263380ad 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_stats.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_stats.erl @@ -19,17 +19,22 @@ -include_lib("typerefl/include/types.hrl"). --import( hoconsc - , [ mk/2 - , ref/1 - , ref/2 - , array/1]). +-import( + hoconsc, + [ + mk/2, + ref/1, + ref/2, + array/1 + ] +). --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). -export([list/2]). @@ -40,27 +45,38 @@ paths() -> ["/stats"]. schema("/stats") -> - #{ 'operationId' => list - , get => - #{ description => <<"EMQX stats">> - , tags => [<<"stats">>] - , parameters => [ref(aggregate)] - , responses => - #{ 200 => mk( hoconsc:union([ ref(?MODULE, node_stats_data) - , array(ref(?MODULE, aggergate_data)) - ]) - , #{ desc => <<"List stats ok">> }) - } + #{ + 'operationId' => list, + get => + #{ + description => <<"EMQX stats">>, + tags => [<<"stats">>], + parameters => [ref(aggregate)], + responses => + #{ + 200 => mk( + hoconsc:union([ + ref(?MODULE, node_stats_data), + array(ref(?MODULE, aggergate_data)) + ]), + #{desc => <<"List stats ok">>} + ) + } } - }. + }. fields(aggregate) -> - [ { aggregate - , mk( boolean() - , #{ desc => <<"Calculation aggregate for all nodes">> - , in => query - , required => false - , example => false})} + [ + {aggregate, + mk( + boolean(), + #{ + desc => <<"Calculation aggregate for all nodes">>, + in => query, + required => false, + example => false + } + )} ]; fields(node_stats_data) -> [ @@ -80,17 +96,25 @@ fields(node_stats_data) -> stats_schema('suboptions.max', <<"subscriptions.max">>), stats_schema('subscribers.count', <<"Number of current subscribers">>), stats_schema('subscribers.max', <<"Historical maximum number of subscribers">>), - stats_schema('subscriptions.count', <<"Number of current subscriptions, including shared subscriptions">>), + stats_schema( + 'subscriptions.count', + <<"Number of current subscriptions, including shared subscriptions">> + ), stats_schema('subscriptions.max', <<"Historical maximum number of subscriptions">>), stats_schema('subscriptions.shared.count', <<"Number of current shared subscriptions">>), - stats_schema('subscriptions.shared.max', <<"Historical maximum number of shared subscriptions">>), + stats_schema( + 'subscriptions.shared.max', <<"Historical maximum number of shared subscriptions">> + ), stats_schema('topics.count', <<"Number of current topics">>), stats_schema('topics.max', <<"Historical maximum number of topics">>) ]; fields(aggergate_data) -> - [ { node - , mk( string(), #{ desc => <<"Node name">> - , example => <<"emqx@127.0.0.1">>})} + [ + {node, + mk(string(), #{ + desc => <<"Node name">>, + example => <<"emqx@127.0.0.1">> + })} ] ++ fields(node_stats_data). stats_schema(Name, Desc) -> @@ -103,7 +127,9 @@ list(get, #{query_string := Qs}) -> true -> {200, emqx_mgmt:get_stats()}; _ -> - Data = [maps:from_list(emqx_mgmt:get_stats(Node) ++ [{node, Node}]) || - Node <- mria_mnesia:running_nodes()], + Data = [ + maps:from_list(emqx_mgmt:get_stats(Node) ++ [{node, Node}]) + || Node <- mria_mnesia:running_nodes() + ], {200, Data} end. diff --git a/apps/emqx_management/src/emqx_mgmt_api_status.erl b/apps/emqx_management/src/emqx_mgmt_api_status.erl index 882fd271c..3a29d8567 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_status.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_status.erl @@ -17,10 +17,11 @@ %% API -behaviour(minirest_api). --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). -export([running_status/2]). @@ -31,22 +32,30 @@ paths() -> ["/status"]. schema("/status") -> - #{ 'operationId' => running_status - , get => - #{ description => <<"Node running status">> - , security => [] - , responses => - #{200 => - #{ description => <<"Node is running">> - , content => - #{ 'text/plain' => - #{ schema => #{type => string} - , example => <<"Node emqx@127.0.0.1 is started\nemqx is running">>} - } - } - } + #{ + 'operationId' => running_status, + get => + #{ + description => <<"Node running status">>, + security => [], + responses => + #{ + 200 => + #{ + description => <<"Node is running">>, + content => + #{ + 'text/plain' => + #{ + schema => #{type => string}, + example => + <<"Node emqx@127.0.0.1 is started\nemqx is running">> + } + } + } + } } - }. + }. %%-------------------------------------------------------------------- %% API Handler funcs @@ -62,7 +71,7 @@ running_status(get, _Params) -> end, AppStatus = case lists:keysearch(emqx, 1, application:which_applications()) of - false -> not_running; + false -> not_running; {value, _Val} -> running end, Status = io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), BrokerStatus, AppStatus]), diff --git a/apps/emqx_management/src/emqx_mgmt_api_subscriptions.erl b/apps/emqx_management/src/emqx_mgmt_api_subscriptions.erl index cd766f956..be3d3f293 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_subscriptions.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_subscriptions.erl @@ -22,26 +22,30 @@ -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). -export([subscriptions/2]). --export([ query/4 - , format/1 - ]). +-export([ + query/4, + format/1 +]). -define(SUBS_QTABLE, emqx_suboption). --define(SUBS_QSCHEMA, - [ {<<"clientid">>, binary} - , {<<"topic">>, binary} - , {<<"share">>, binary} - , {<<"share_group">>, binary} - , {<<"qos">>, integer} - , {<<"match_topic">>, binary}]). +-define(SUBS_QSCHEMA, [ + {<<"clientid">>, binary}, + {<<"topic">>, binary}, + {<<"share">>, binary}, + {<<"share_group">>, binary}, + {<<"qos">>, integer}, + {<<"match_topic">>, binary} +]). -define(QUERY_FUN, {?MODULE, query}). @@ -58,7 +62,9 @@ schema("/subscriptions") -> description => <<"List subscriptions">>, parameters => parameters(), responses => #{ - 200 => hoconsc:mk(hoconsc:array(hoconsc:ref(?MODULE, subscription)), #{})}} + 200 => hoconsc:mk(hoconsc:array(hoconsc:ref(?MODULE, subscription)), #{}) + } + } }. fields(subscription) -> @@ -74,41 +80,53 @@ parameters() -> hoconsc:ref(emqx_dashboard_swagger, page), hoconsc:ref(emqx_dashboard_swagger, limit), { - node, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Node name">>, - example => atom_to_list(node())}) + node, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Node name">>, + example => atom_to_list(node()) + }) }, { - clientid, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Client ID">>}) + clientid, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Client ID">> + }) }, { - qos, hoconsc:mk(emqx_schema:qos(), #{ - in => query, - required => false, - desc => <<"QoS">>}) + qos, + hoconsc:mk(emqx_schema:qos(), #{ + in => query, + required => false, + desc => <<"QoS">> + }) }, { - topic, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Topic, url encoding">>}) + topic, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Topic, url encoding">> + }) }, { - match_topic, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Match topic string, url encoding">>}) + match_topic, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Match topic string, url encoding">> + }) }, { - share_group, hoconsc:mk(binary(), #{ - in => query, - required => false, - desc => <<"Shared subscription group name">>}) + share_group, + hoconsc:mk(binary(), #{ + in => query, + required => false, + desc => <<"Shared subscription group name">> + }) } ]. @@ -116,11 +134,20 @@ subscriptions(get, #{query_string := QString}) -> Response = case maps:get(<<"node">>, QString, undefined) of undefined -> - emqx_mgmt_api:cluster_query(QString, ?SUBS_QTABLE, - ?SUBS_QSCHEMA, ?QUERY_FUN); + emqx_mgmt_api:cluster_query( + QString, + ?SUBS_QTABLE, + ?SUBS_QSCHEMA, + ?QUERY_FUN + ); Node0 -> - emqx_mgmt_api:node_query(binary_to_atom(Node0, utf8), QString, - ?SUBS_QTABLE, ?SUBS_QSCHEMA, ?QUERY_FUN) + emqx_mgmt_api:node_query( + binary_to_atom(Node0, utf8), + QString, + ?SUBS_QTABLE, + ?SUBS_QSCHEMA, + ?QUERY_FUN + ) end, case Response of {error, page_limit_invalid} -> @@ -134,10 +161,8 @@ subscriptions(get, #{query_string := QString}) -> format(Items) when is_list(Items) -> [format(Item) || Item <- Items]; - format({{Subscriber, Topic}, Options}) -> format({Subscriber, Topic, Options}); - format({_Subscriber, Topic, Options = #{share := Group}}) -> QoS = maps:get(qos, Options), #{ @@ -161,19 +186,30 @@ format({_Subscriber, Topic, Options}) -> query(Tab, {Qs, []}, Continuation, Limit) -> Ms = qs2ms(Qs), - emqx_mgmt_api:select_table_with_count( Tab, Ms - , Continuation, Limit, fun format/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format/1 + ); query(Tab, {Qs, Fuzzy}, Continuation, Limit) -> Ms = qs2ms(Qs), FuzzyFilterFun = fuzzy_filter_fun(Fuzzy), - emqx_mgmt_api:select_table_with_count( Tab, {Ms, FuzzyFilterFun} - , Continuation, Limit, fun format/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format/1 + ). fuzzy_filter_fun(Fuzzy) -> fun(MsRaws) when is_list(MsRaws) -> - lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end - , MsRaws) + lists:filter( + fun(E) -> run_fuzzy_filter(E, Fuzzy) end, + MsRaws + ) end. run_fuzzy_filter(_, []) -> diff --git a/apps/emqx_management/src/emqx_mgmt_api_topics.erl b/apps/emqx_management/src/emqx_mgmt_api_topics.erl index 99533eed4..ef07cabe3 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_topics.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_topics.erl @@ -22,23 +22,24 @@ %% API -behaviour(minirest_api). --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). --export([ topics/2 - , topic/2 - ]). +-export([ + topics/2, + topic/2 +]). --export([ query/4]). +-export([query/4]). -define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND'). -define(TOPICS_QUERY_SCHEMA, [{<<"topic">>, binary}, {<<"node">>, atom}]). - api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}). @@ -73,19 +74,23 @@ schema("/topics/:topic") -> responses => #{ 200 => hoconsc:mk(hoconsc:ref(topic), #{}), 404 => - emqx_dashboard_swagger:error_codes(['TOPIC_NOT_FOUND'],<<"Topic not found">>) + emqx_dashboard_swagger:error_codes(['TOPIC_NOT_FOUND'], <<"Topic not found">>) } } }. fields(topic) -> [ - {topic, hoconsc:mk(binary(), #{ - desc => <<"Topic Name">>, - required => true})}, - {node, hoconsc:mk(binary(), #{ - desc => <<"Node">>, - required => true})} + {topic, + hoconsc:mk(binary(), #{ + desc => <<"Topic Name">>, + required => true + })}, + {node, + hoconsc:mk(binary(), #{ + desc => <<"Node">>, + required => true + })} ]; fields(meta) -> emqx_dashboard_swagger:fields(page) ++ @@ -103,8 +108,11 @@ topic(get, #{bindings := Bindings}) -> %%%============================================================================================== %% api apply do_list(Params) -> - case emqx_mgmt_api:node_query( - node(), Params, emqx_route, ?TOPICS_QUERY_SCHEMA, {?MODULE, query}) of + case + emqx_mgmt_api:node_query( + node(), Params, emqx_route, ?TOPICS_QUERY_SCHEMA, {?MODULE, query} + ) + of {error, page_limit_invalid} -> {400, #{code => <<"INVALID_PARAMETER">>, message => <<"page_limit_invalid">>}}; {error, Node, {badrpc, R}} -> @@ -128,16 +136,18 @@ generate_topic(Params = #{<<"topic">> := Topic}) -> Params#{<<"topic">> => uri_string:percent_decode(Topic)}; generate_topic(Params = #{topic := Topic}) -> Params#{topic => uri_string:percent_decode(Topic)}; -generate_topic(Params) -> Params. +generate_topic(Params) -> + Params. query(Tab, {Qs, _}, Continuation, Limit) -> Ms = qs2ms(Qs, [{{route, '_', '_'}, [], ['$_']}]), emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, fun format/1). -qs2ms([], Res) -> Res; -qs2ms([{topic,'=:=', T} | Qs], [{{route, _, N}, [], ['$_']}]) -> +qs2ms([], Res) -> + Res; +qs2ms([{topic, '=:=', T} | Qs], [{{route, _, N}, [], ['$_']}]) -> qs2ms(Qs, [{{route, T, N}, [], ['$_']}]); -qs2ms([{node,'=:=', N} | Qs], [{{route, T, _}, [], ['$_']}]) -> +qs2ms([{node, '=:=', N} | Qs], [{{route, T, _}, [], ['$_']}]) -> qs2ms(Qs, [{{route, T, N}, [], ['$_']}]). format(#route{topic = Topic, dest = {_, Node}}) -> @@ -147,7 +157,8 @@ format(#route{topic = Topic, dest = Node}) -> topic_param(In) -> { - topic, hoconsc:mk(binary(), #{ + topic, + hoconsc:mk(binary(), #{ desc => <<"Topic Name">>, in => In, required => (In == path), @@ -155,9 +166,10 @@ topic_param(In) -> }) }. -node_param()-> +node_param() -> { - node, hoconsc:mk(binary(), #{ + node, + hoconsc:mk(binary(), #{ desc => <<"Node Name">>, in => query, required => false, diff --git a/apps/emqx_management/src/emqx_mgmt_api_trace.erl b/apps/emqx_management/src/emqx_mgmt_api_trace.erl index 055a7f9e0..c1a6e196f 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_trace.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_trace.erl @@ -21,26 +21,29 @@ -include_lib("typerefl/include/types.hrl"). -include_lib("emqx/include/logger.hrl"). --export([ api_spec/0 - , fields/1 - , paths/0 - , schema/1 - , namespace/0 - ]). +-export([ + api_spec/0, + fields/1, + paths/0, + schema/1, + namespace/0 +]). --export([ trace/2 - , delete_trace/2 - , update_trace/2 - , download_trace_log/2 - , stream_log_file/2 - ]). +-export([ + trace/2, + delete_trace/2, + update_trace/2, + download_trace_log/2, + stream_log_file/2 +]). -export([validate_name/1]). %% for rpc --export([ read_trace_file/3 - , get_trace_size/0 - ]). +-export([ + read_trace_file/3, + get_trace_size/0 +]). -define(TO_BIN(_B_), iolist_to_binary(_B_)). -define(NOT_FOUND(N), {404, #{code => 'NOT_FOUND', message => ?TO_BIN([N, " NOT FOUND"])}}). @@ -53,7 +56,6 @@ api_spec() -> paths() -> ["/trace", "/trace/:name/stop", "/trace/:name/download", "/trace/:name/log", "/trace/:name"]. - schema("/trace") -> #{ 'operationId' => trace, @@ -68,9 +70,14 @@ schema("/trace") -> 'requestBody' => delete([status, log_size], fields(trace)), responses => #{ 200 => hoconsc:ref(trace), - 400 => emqx_dashboard_swagger:error_codes(['ALREADY_EXISTS', - 'DUPLICATE_CONDITION', 'INVALID_PARAMS'], - <<"trace name already exists">>) + 400 => emqx_dashboard_swagger:error_codes( + [ + 'ALREADY_EXISTS', + 'DUPLICATE_CONDITION', + 'INVALID_PARAMS' + ], + <<"trace name already exists">> + ) } }, delete => #{ @@ -112,12 +119,13 @@ schema("/trace/:name/download") -> parameters => [hoconsc:ref(name)], responses => #{ 200 => - #{description => "A trace zip file", - content => #{ - 'application/octet-stream' => - #{schema => #{type => "string", format => "binary"}} + #{ + description => "A trace zip file", + content => #{ + 'application/octet-stream' => + #{schema => #{type => "string", format => "binary"}} + } } - } } } }; @@ -134,92 +142,151 @@ schema("/trace/:name/log") -> ], responses => #{ 200 => - [ - {items, hoconsc:mk(binary(), #{example => "TEXT-LOG-ITEMS"})} - | fields(bytes) ++ fields(position) - ] + [ + {items, hoconsc:mk(binary(), #{example => "TEXT-LOG-ITEMS"})} + | fields(bytes) ++ fields(position) + ] } } }. fields(trace) -> [ - {name, hoconsc:mk(binary(), - #{desc => "Unique and format by [a-zA-Z0-9-_]", - validator => fun ?MODULE:validate_name/1, - required => true, - example => <<"EMQX-TRACE-1">>})}, - {type, hoconsc:mk(hoconsc:enum([clientid, topic, ip_address]), - #{desc => """Filter type""", - required => true, - example => <<"clientid">>})}, - {topic, hoconsc:mk(binary(), - #{desc => """support mqtt wildcard topic.""", - required => false, - example => <<"/dev/#">>})}, - {clientid, hoconsc:mk(binary(), - #{desc => """mqtt clientid.""", - required => false, - example => <<"dev-001">>})}, + {name, + hoconsc:mk( + binary(), + #{ + desc => "Unique and format by [a-zA-Z0-9-_]", + validator => fun ?MODULE:validate_name/1, + required => true, + example => <<"EMQX-TRACE-1">> + } + )}, + {type, + hoconsc:mk( + hoconsc:enum([clientid, topic, ip_address]), + #{ + desc => "" "Filter type" "", + required => true, + example => <<"clientid">> + } + )}, + {topic, + hoconsc:mk( + binary(), + #{ + desc => "" "support mqtt wildcard topic." "", + required => false, + example => <<"/dev/#">> + } + )}, + {clientid, + hoconsc:mk( + binary(), + #{ + desc => "" "mqtt clientid." "", + required => false, + example => <<"dev-001">> + } + )}, %% TODO add ip_address type in emqx_schema.erl - {ip_address, hoconsc:mk(binary(), - #{desc => "client ip address", - required => false, - example => <<"127.0.0.1">> - })}, - {status, hoconsc:mk(hoconsc:enum([running, stopped, waiting]), - #{desc => "trace status", - required => false, - example => running - })}, - {start_at, hoconsc:mk(emqx_datetime:epoch_second(), - #{desc => "rfc3339 timestamp or epoch second", - required => false, - example => <<"2021-11-04T18:17:38+08:00">> - })}, - {end_at, hoconsc:mk(emqx_datetime:epoch_second(), - #{desc => "rfc3339 timestamp or epoch second", - required => false, - example => <<"2021-11-05T18:17:38+08:00">> - })}, - {log_size, hoconsc:mk(hoconsc:array(map()), - #{desc => "trace log size", - example => [#{<<"node">> => <<"emqx@127.0.0.1">>, <<"size">> => 1024}], - required => false})} + {ip_address, + hoconsc:mk( + binary(), + #{ + desc => "client ip address", + required => false, + example => <<"127.0.0.1">> + } + )}, + {status, + hoconsc:mk( + hoconsc:enum([running, stopped, waiting]), + #{ + desc => "trace status", + required => false, + example => running + } + )}, + {start_at, + hoconsc:mk( + emqx_datetime:epoch_second(), + #{ + desc => "rfc3339 timestamp or epoch second", + required => false, + example => <<"2021-11-04T18:17:38+08:00">> + } + )}, + {end_at, + hoconsc:mk( + emqx_datetime:epoch_second(), + #{ + desc => "rfc3339 timestamp or epoch second", + required => false, + example => <<"2021-11-05T18:17:38+08:00">> + } + )}, + {log_size, + hoconsc:mk( + hoconsc:array(map()), + #{ + desc => "trace log size", + example => [#{<<"node">> => <<"emqx@127.0.0.1">>, <<"size">> => 1024}], + required => false + } + )} ]; fields(name) -> - [{name, hoconsc:mk(binary(), - #{ - desc => <<"[a-zA-Z0-9-_]">>, - example => <<"EMQX-TRACE-1">>, - in => path, - validator => fun ?MODULE:validate_name/1 - })} + [ + {name, + hoconsc:mk( + binary(), + #{ + desc => <<"[a-zA-Z0-9-_]">>, + example => <<"EMQX-TRACE-1">>, + in => path, + validator => fun ?MODULE:validate_name/1 + } + )} ]; fields(node) -> - [{node, hoconsc:mk(binary(), - #{ - desc => "Node name", - in => query, - required => false - })}]; + [ + {node, + hoconsc:mk( + binary(), + #{ + desc => "Node name", + in => query, + required => false + } + )} + ]; fields(bytes) -> - [{bytes, hoconsc:mk(integer(), - #{ - desc => "Maximum number of bytes to store in request", - in => query, - required => false, - default => 1000 - })}]; + [ + {bytes, + hoconsc:mk( + integer(), + #{ + desc => "Maximum number of bytes to store in request", + in => query, + required => false, + default => 1000 + } + )} + ]; fields(position) -> - [{position, hoconsc:mk(integer(), - #{ - desc => "Offset from the current trace position.", - in => query, - required => false, - default => 0 - })}]. - + [ + {position, + hoconsc:mk( + integer(), + #{ + desc => "Offset from the current trace position.", + in => query, + required => false, + default => 0 + } + )} + ]. -define(NAME_RE, "^[A-Za-z]+[A-Za-z0-9-_]*$"). @@ -231,7 +298,8 @@ validate_name(Name) -> nomatch -> {error, "Name should be " ?NAME_RE}; _ -> ok end; - false -> {error, "Name Length must =< 256"} + false -> + {error, "Name Length must =< 256"} end. delete(Keys, Fields) -> @@ -239,32 +307,48 @@ delete(Keys, Fields) -> trace(get, _Params) -> case emqx_trace:list() of - [] -> {200, []}; + [] -> + {200, []}; List0 -> - List = lists:sort(fun(#{start_at := A}, #{start_at := B}) -> A > B end, - emqx_trace:format(List0)), + List = lists:sort( + fun(#{start_at := A}, #{start_at := B}) -> A > B end, + emqx_trace:format(List0) + ), Nodes = mria_mnesia:running_nodes(), TraceSize = wrap_rpc(emqx_mgmt_trace_proto_v1:get_trace_size(Nodes)), AllFileSize = lists:foldl(fun(F, Acc) -> maps:merge(Acc, F) end, #{}, TraceSize), Now = erlang:system_time(second), Traces = - lists:map(fun(Trace = #{name := Name, start_at := Start, - end_at := End, enable := Enable, type := Type, filter := Filter}) -> - FileName = emqx_trace:filename(Name, Start), - LogSize = collect_file_size(Nodes, FileName, AllFileSize), - Trace0 = maps:without([enable, filter], Trace), - Trace0#{log_size => LogSize - , Type => iolist_to_binary(Filter) - , start_at => list_to_binary(calendar:system_time_to_rfc3339(Start)) - , end_at => list_to_binary(calendar:system_time_to_rfc3339(End)) - , status => status(Enable, Start, End, Now) - } - end, List), + lists:map( + fun( + Trace = #{ + name := Name, + start_at := Start, + end_at := End, + enable := Enable, + type := Type, + filter := Filter + } + ) -> + FileName = emqx_trace:filename(Name, Start), + LogSize = collect_file_size(Nodes, FileName, AllFileSize), + Trace0 = maps:without([enable, filter], Trace), + Trace0#{ + log_size => LogSize, + Type => iolist_to_binary(Filter), + start_at => list_to_binary(calendar:system_time_to_rfc3339(Start)), + end_at => list_to_binary(calendar:system_time_to_rfc3339(End)), + status => status(Enable, Start, End, Now) + } + end, + List + ), {200, Traces} end; trace(post, #{body := Param}) -> case emqx_trace:create(Param) of - {ok, Trace0} -> {200, format_trace(Trace0)}; + {ok, Trace0} -> + {200, format_trace(Trace0)}; {error, {already_existed, Name}} -> {400, #{ code => 'ALREADY_EXISTS', @@ -287,18 +371,27 @@ trace(delete, _Param) -> format_trace(Trace0) -> [ - #{start_at := Start, end_at := End, - enable := Enable, type := Type, filter := Filter} = Trace1 + #{ + start_at := Start, + end_at := End, + enable := Enable, + type := Type, + filter := Filter + } = Trace1 ] = emqx_trace:format([Trace0]), Now = erlang:system_time(second), - LogSize = lists:foldl(fun(Node, Acc) -> Acc#{Node => 0} end, #{}, - mria_mnesia:running_nodes()), + LogSize = lists:foldl( + fun(Node, Acc) -> Acc#{Node => 0} end, + #{}, + mria_mnesia:running_nodes() + ), Trace2 = maps:without([enable, filter], Trace1), - Trace2#{log_size => LogSize - , Type => iolist_to_binary(Filter) - , start_at => list_to_binary(calendar:system_time_to_rfc3339(Start)) - , end_at => list_to_binary(calendar:system_time_to_rfc3339(End)) - , status => status(Enable, Start, End, Now) + Trace2#{ + log_size => LogSize, + Type => iolist_to_binary(Filter), + start_at => list_to_binary(calendar:system_time_to_rfc3339(Start)), + end_at => list_to_binary(calendar:system_time_to_rfc3339(End)), + status => status(Enable, Start, End, Now) }. delete_trace(delete, #{bindings := #{name := Name}}) -> @@ -334,25 +427,34 @@ download_trace_log(get, #{bindings := #{name := Name}}) -> <<"content-disposition">> => iolist_to_binary("attachment; filename=" ++ ZipName) }, {200, Headers, {file_binary, ZipName, Binary}}; - {error, not_found} -> ?NOT_FOUND(Name) + {error, not_found} -> + ?NOT_FOUND(Name) end. group_trace_file(ZipDir, TraceLog, TraceFiles) -> - lists:foldl(fun(Res, Acc) -> - case Res of - {ok, Node, Bin} -> - FileName = Node ++ "-" ++ TraceLog, - ZipName = filename:join([ZipDir, FileName]), - case file:write_file(ZipName, Bin) of - ok -> [FileName | Acc]; - _ -> Acc - end; - {error, Node, Reason} -> - ?SLOG(error, #{msg => "download_trace_log_error", node => Node, - log => TraceLog, reason => Reason}), - Acc - end - end, [], TraceFiles). + lists:foldl( + fun(Res, Acc) -> + case Res of + {ok, Node, Bin} -> + FileName = Node ++ "-" ++ TraceLog, + ZipName = filename:join([ZipDir, FileName]), + case file:write_file(ZipName, Bin) of + ok -> [FileName | Acc]; + _ -> Acc + end; + {error, Node, Reason} -> + ?SLOG(error, #{ + msg => "download_trace_log_error", + node => Node, + log => TraceLog, + reason => Reason + }), + Acc + end + end, + [], + TraceFiles + ). collect_trace_file(TraceLog) -> Nodes = mria_mnesia:running_nodes(), @@ -376,18 +478,25 @@ stream_log_file(get, #{bindings := #{name := Name}, query_string := Query}) -> {eof, Size} -> Meta = #{<<"position">> => Size, <<"bytes">> => Bytes}, {200, #{meta => Meta, items => <<"">>}}; - {error, enoent} -> %% the waiting trace should return "" not error. + %% the waiting trace should return "" not error. + {error, enoent} -> Meta = #{<<"position">> => Position, <<"bytes">> => Bytes}, {200, #{meta => Meta, items => <<"">>}}; {error, Reason} -> - ?SLOG(error, #{msg => "read_file_failed", - node => Node, name => Name, reason => Reason, - position => Position, bytes => Bytes}), + ?SLOG(error, #{ + msg => "read_file_failed", + node => Node, + name => Name, + reason => Reason, + position => Position, + bytes => Bytes + }), {400, #{code => 'READ_FILE_ERROR', message => Reason}}; {badrpc, nodedown} -> {400, #{code => 'RPC_ERROR', message => "BadRpc node down"}} end; - {error, not_found} -> {400, #{code => 'NODE_ERROR', message => <<"Node not found">>}} + {error, not_found} -> + {400, #{code => 'NODE_ERROR', message => <<"Node not found">>}} end. -spec get_trace_size() -> #{{node(), file:name_all()} => non_neg_integer()}. @@ -396,23 +505,31 @@ get_trace_size() -> Node = node(), case file:list_dir(TraceDir) of {ok, AllFiles} -> - lists:foldl(fun(File, Acc) -> - FullFileName = filename:join(TraceDir, File), - Acc#{{Node, File} => filelib:file_size(FullFileName)} - end, #{}, lists:delete("zip", AllFiles)); - _ -> #{} + lists:foldl( + fun(File, Acc) -> + FullFileName = filename:join(TraceDir, File), + Acc#{{Node, File} => filelib:file_size(FullFileName)} + end, + #{}, + lists:delete("zip", AllFiles) + ); + _ -> + #{} end. %% this is an rpc call for stream_log_file/2 --spec read_trace_file( binary() - , non_neg_integer() - , non_neg_integer() - ) -> {ok, binary()} - | {error, _} - | {eof, non_neg_integer()}. +-spec read_trace_file( + binary(), + non_neg_integer(), + non_neg_integer() +) -> + {ok, binary()} + | {error, _} + | {eof, non_neg_integer()}. read_trace_file(Name, Position, Limit) -> case emqx_trace:get_trace_filename(Name) of - {error, _} = Error -> Error; + {error, _} = Error -> + Error; {ok, TraceFile} -> TraceDir = emqx_trace:trace_dir(), TracePath = filename:join([TraceDir, TraceFile]), @@ -423,13 +540,16 @@ read_file(Path, Offset, Bytes) -> case file:open(Path, [read, raw, binary]) of {ok, IoDevice} -> try - _ = case Offset of + _ = + case Offset of 0 -> ok; _ -> file:position(IoDevice, {bof, Offset}) end, case file:read(IoDevice, Bytes) of - {ok, Bin} -> {ok, Bin}; - {error, Reason} -> {error, Reason}; + {ok, Bin} -> + {ok, Bin}; + {error, Reason} -> + {error, Reason}; eof -> {ok, #file_info{size = Size}} = file:read_file_info(IoDevice), {eof, Size} @@ -437,20 +557,27 @@ read_file(Path, Offset, Bytes) -> after file:close(IoDevice) end; - {error, Reason} -> {error, Reason} + {error, Reason} -> + {error, Reason} end. to_node(Node) -> - try {ok, binary_to_existing_atom(Node)} - catch _:_ -> - {error, not_found} + try + {ok, binary_to_existing_atom(Node)} + catch + _:_ -> + {error, not_found} end. collect_file_size(Nodes, FileName, AllFiles) -> - lists:foldl(fun(Node, Acc) -> - Size = maps:get({Node, FileName}, AllFiles, 0), - Acc#{Node => Size} - end, #{}, Nodes). + lists:foldl( + fun(Node, Acc) -> + Size = maps:get({Node, FileName}, AllFiles, 0), + Acc#{Node => Size} + end, + #{}, + Nodes + ). status(false, _Start, _End, _Now) -> <<"stopped">>; status(true, Start, _End, Now) when Now < Start -> <<"waiting">>; diff --git a/apps/emqx_management/src/emqx_mgmt_app.erl b/apps/emqx_management/src/emqx_mgmt_app.erl index 372690cf4..9ad1930a0 100644 --- a/apps/emqx_management/src/emqx_mgmt_app.erl +++ b/apps/emqx_management/src/emqx_mgmt_app.erl @@ -20,9 +20,10 @@ -define(APP, emqx_management). --export([ start/2 - , stop/1 - ]). +-export([ + start/2, + stop/1 +]). -include("emqx_mgmt.hrl"). diff --git a/apps/emqx_management/src/emqx_mgmt_auth.erl b/apps/emqx_management/src/emqx_mgmt_auth.erl index e0aeab49c..fe1919632 100644 --- a/apps/emqx_management/src/emqx_mgmt_auth.erl +++ b/apps/emqx_management/src/emqx_mgmt_auth.erl @@ -20,14 +20,15 @@ -export([mnesia/1]). -boot_mnesia({mnesia, [boot]}). --export([ create/4 - , read/1 - , update/4 - , delete/1 - , list/0 - ]). +-export([ + create/4, + read/1, + update/4, + delete/1, + list/0 +]). --export([ authorize/3 ]). +-export([authorize/3]). -define(APP, emqx_app). @@ -39,7 +40,7 @@ desc = <<>> :: binary() | '_', expired_at = 0 :: integer() | undefined | '_', created_at = 0 :: integer() | '_' - }). +}). mnesia(boot) -> ok = mria:create_table(?APP, [ @@ -47,7 +48,8 @@ mnesia(boot) -> {rlog_shard, ?COMMON_SHARD}, {storage, disc_copies}, {record_name, ?APP}, - {attributes, record_info(fields, ?APP)}]). + {attributes, record_info(fields, ?APP)} + ]). create(Name, Enable, ExpiredAt, Desc) -> case mnesia:table_info(?APP, size) < 30 of @@ -61,13 +63,14 @@ read(Name) -> [] -> mnesia:abort(not_found); [App] -> to_map(App) end - end, + end, trans(Fun). update(Name, Enable, ExpiredAt, Desc) -> Fun = fun() -> case mnesia:read(?APP, Name, write) of - [] -> mnesia:abort(not_found); + [] -> + mnesia:abort(not_found); [App0 = #?APP{enable = Enable0, desc = Desc0}] -> App = App0#?APP{ @@ -78,22 +81,25 @@ update(Name, Enable, ExpiredAt, Desc) -> ok = mnesia:write(App), to_map(App) end - end, + end, trans(Fun). delete(Name) -> Fun = fun() -> case mnesia:read(?APP, Name) of [] -> mnesia:abort(not_found); - [_App] -> mnesia:delete({?APP, Name}) end - end, + [_App] -> mnesia:delete({?APP, Name}) + end + end, trans(Fun). list() -> to_map(ets:match_object(?APP, #?APP{_ = '_'})). -authorize(<<"/api/v5/users", _/binary>>, _ApiKey, _ApiSecret) -> {error, <<"not_allowed">>}; -authorize(<<"/api/v5/api_key", _/binary>>, _ApiKey, _ApiSecret) -> {error, <<"not_allowed">>}; +authorize(<<"/api/v5/users", _/binary>>, _ApiKey, _ApiSecret) -> + {error, <<"not_allowed">>}; +authorize(<<"/api/v5/api_key", _/binary>>, _ApiKey, _ApiSecret) -> + {error, <<"not_allowed">>}; authorize(_Path, ApiKey, ApiSecret) -> Now = erlang:system_time(second), case find_by_api_key(ApiKey) of @@ -102,28 +108,35 @@ authorize(_Path, ApiKey, ApiSecret) -> ok -> ok; error -> {error, "secret_error"} end; - {ok, true, _ExpiredAt, _SecretHash} -> {error, "secret_expired"}; - {ok, false, _ExpiredAt, _SecretHash} -> {error, "secret_disable"}; - {error, Reason} -> {error, Reason} + {ok, true, _ExpiredAt, _SecretHash} -> + {error, "secret_expired"}; + {ok, false, _ExpiredAt, _SecretHash} -> + {error, "secret_disable"}; + {error, Reason} -> + {error, Reason} end. find_by_api_key(ApiKey) -> - Fun = fun() -> mnesia:match_object(#?APP{api_key = ApiKey, _ = '_'}) end, + Fun = fun() -> mnesia:match_object(#?APP{api_key = ApiKey, _ = '_'}) end, case trans(Fun) of {ok, [#?APP{api_secret_hash = SecretHash, enable = Enable, expired_at = ExpiredAt}]} -> {ok, Enable, ExpiredAt, SecretHash}; - _ -> {error, "not_found"} + _ -> + {error, "not_found"} end. ensure_not_undefined(undefined, Old) -> Old; ensure_not_undefined(New, _Old) -> New. -to_map(Apps)when is_list(Apps) -> +to_map(Apps) when is_list(Apps) -> Fields = record_info(fields, ?APP), - lists:map(fun(Trace0 = #?APP{}) -> - [_ | Values] = tuple_to_list(Trace0), - maps:remove(api_secret_hash, maps:from_list(lists:zip(Fields, Values))) - end, Apps); + lists:map( + fun(Trace0 = #?APP{}) -> + [_ | Values] = tuple_to_list(Trace0), + maps:remove(api_secret_hash, maps:from_list(lists:zip(Fields, Values))) + end, + Apps + ); to_map(App0) -> [App] = to_map([App0]), App. @@ -149,16 +162,18 @@ create_app(Name, Enable, ExpiredAt, Desc) -> create_app(App = #?APP{api_key = ApiKey, name = Name}) -> trans(fun() -> case mnesia:read(?APP, Name) of - [_] -> mnesia:abort(name_already_existed); + [_] -> + mnesia:abort(name_already_existed); [] -> case mnesia:match_object(?APP, #?APP{api_key = ApiKey, _ = '_'}, read) of [] -> ok = mnesia:write(App), to_map(App); - _ -> mnesia:abort(api_key_already_existed) + _ -> + mnesia:abort(api_key_already_existed) end end - end). + end). trans(Fun) -> case mria:transaction(?COMMON_SHARD, Fun) of diff --git a/apps/emqx_management/src/emqx_mgmt_sup.erl b/apps/emqx_management/src/emqx_mgmt_sup.erl index ef1d05833..74f77dbd0 100644 --- a/apps/emqx_management/src/emqx_mgmt_sup.erl +++ b/apps/emqx_management/src/emqx_mgmt_sup.erl @@ -23,8 +23,7 @@ -export([init/1]). start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). + supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - {ok, {{one_for_one, 1, 5}, []}}. - + {ok, {{one_for_one, 1, 5}, []}}. diff --git a/apps/emqx_management/src/emqx_mgmt_util.erl b/apps/emqx_management/src/emqx_mgmt_util.erl index 173000b2a..f553be22d 100644 --- a/apps/emqx_management/src/emqx_mgmt_util.erl +++ b/apps/emqx_management/src/emqx_mgmt_util.erl @@ -16,38 +16,40 @@ -module(emqx_mgmt_util). --export([ strftime/1 - , datetime/1 - , kmg/1 - , ntoa/1 - , merge_maps/2 - , batch_operation/3 - ]). +-export([ + strftime/1, + datetime/1, + kmg/1, + ntoa/1, + merge_maps/2, + batch_operation/3 +]). --export([ bad_request/0 - , bad_request/1 - , properties/1 - , page_params/0 - , schema/1 - , schema/2 - , object_schema/1 - , object_schema/2 - , array_schema/1 - , array_schema/2 - , object_array_schema/1 - , object_array_schema/2 - , page_schema/1 - , page_object_schema/1 - , error_schema/1 - , error_schema/2 - , batch_schema/1 - ]). +-export([ + bad_request/0, + bad_request/1, + properties/1, + page_params/0, + schema/1, + schema/2, + object_schema/1, + object_schema/2, + array_schema/1, + array_schema/2, + object_array_schema/1, + object_array_schema/2, + page_schema/1, + page_object_schema/1, + error_schema/1, + error_schema/2, + batch_schema/1 +]). -export([urldecode/1]). -define(KB, 1024). --define(MB, (1024*1024)). --define(GB, (1024*1024*1024)). +-define(MB, (1024 * 1024)). +-define(GB, (1024 * 1024 * 1024)). %%-------------------------------------------------------------------- %% Strftime @@ -55,17 +57,17 @@ strftime({MegaSecs, Secs, _MicroSecs}) -> strftime(datetime(MegaSecs * 1000000 + Secs)); - strftime(Secs) when is_integer(Secs) -> strftime(datetime(Secs)); - -strftime({{Y,M,D}, {H,MM,S}}) -> +strftime({{Y, M, D}, {H, MM, S}}) -> lists:flatten( io_lib:format( - "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])). + "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S] + ) + ). datetime(Timestamp) when is_integer(Timestamp) -> - Epoch = calendar:datetime_to_gregorian_seconds({{1970,1,1}, {0,0,0}}), + Epoch = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), Universal = calendar:gregorian_seconds_to_datetime(Timestamp + Epoch), calendar:universal_time_to_local_time(Universal). @@ -80,19 +82,27 @@ kmg(Byte) -> kmg(F, S) -> iolist_to_binary(io_lib:format("~.2f~ts", [F, S])). -ntoa({0,0,0,0,0,16#ffff,AB,CD}) -> +ntoa({0, 0, 0, 0, 0, 16#ffff, AB, CD}) -> inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256}); ntoa(IP) -> inet_parse:ntoa(IP). merge_maps(Default, New) -> - maps:fold(fun(K, V, Acc) -> - case maps:get(K, Acc, undefined) of - OldV when is_map(OldV), - is_map(V) -> Acc#{K => merge_maps(OldV, V)}; - _ -> Acc#{K => V} - end - end, Default, New). + maps:fold( + fun(K, V, Acc) -> + case maps:get(K, Acc, undefined) of + OldV when + is_map(OldV), + is_map(V) + -> + Acc#{K => merge_maps(OldV, V)}; + _ -> + Acc#{K => V} + end + end, + Default, + New + ). urldecode(S) -> emqx_http_lib:uri_decode(S). @@ -123,8 +133,13 @@ array_schema(Schema, Desc) -> object_array_schema(Properties) when is_map(Properties) -> json_content_schema(#{type => array, items => #{type => object, properties => Properties}}). object_array_schema(Properties, Desc) -> - json_content_schema(#{type => array, - items => #{type => object, properties => Properties}}, Desc). + json_content_schema( + #{ + type => array, + items => #{type => object, properties => Properties} + }, + Desc + ). page_schema(Ref) when is_atom(Ref) -> page_schema(minirest:ref(atom_to_binary(Ref, utf8))); @@ -134,9 +149,11 @@ page_schema(Schema) -> properties => #{ meta => #{ type => object, - properties => properties([{page, integer}, - {limit, integer}, - {count, integer}]) + properties => properties([ + {page, integer}, + {limit, integer}, + {count, integer} + ]) }, data => #{ type => array, @@ -155,8 +172,10 @@ error_schema(Description) -> error_schema(Description, Enum) -> Schema = #{ type => object, - properties => properties([{code, string, <<>>, Enum}, - {message, string}]) + properties => properties([ + {code, string, <<>>, Enum}, + {message, string} + ]) }, json_content_schema(Schema, Description). @@ -168,20 +187,28 @@ batch_schema(DefName) when is_binary(DefName) -> properties => #{ success => #{ type => integer, - description => <<"Success count">>}, + description => <<"Success count">> + }, failed => #{ type => integer, - description => <<"Failed count">>}, + description => <<"Failed count">> + }, detail => #{ type => array, description => <<"Failed object & reason">>, items => #{ type => object, properties => - #{ - data => minirest:ref(DefName), - reason => #{ - type => <<"string">>}}}}}}, + #{ + data => minirest:ref(DefName), + reason => #{ + type => <<"string">> + } + } + } + } + } + }, json_content_schema(Schema). json_content_schema(Schema) when is_map(Schema) -> @@ -211,7 +238,7 @@ batch_operation(Module, Function, [Args | ArgsList], Failed) -> case erlang:apply(Module, Function, Args) of ok -> batch_operation(Module, Function, ArgsList, Failed); - {error ,Reason} -> + {error, Reason} -> batch_operation(Module, Function, ArgsList, [{Args, Reason} | Failed]) end. @@ -224,36 +251,75 @@ properties([Key | Props], Acc) when is_atom(Key) -> properties([{Key, Type} | Props], Acc) -> properties(Props, maps:put(Key, #{type => Type}, Acc)); properties([{Key, object, Props1} | Props], Acc) -> - properties(Props, maps:put(Key, #{type => object, - properties => properties(Props1)}, Acc)); + properties( + Props, + maps:put( + Key, + #{ + type => object, + properties => properties(Props1) + }, + Acc + ) + ); properties([{Key, {array, object}, Props1} | Props], Acc) -> - properties(Props, maps:put(Key, #{type => array, - items => #{type => object, - properties => properties(Props1) - }}, Acc)); + properties( + Props, + maps:put( + Key, + #{ + type => array, + items => #{ + type => object, + properties => properties(Props1) + } + }, + Acc + ) + ); properties([{Key, {array, Type}, Desc} | Props], Acc) -> - properties(Props, maps:put(Key, #{type => array, - items => #{type => Type}, - description => Desc}, Acc)); + properties( + Props, + maps:put( + Key, + #{ + type => array, + items => #{type => Type}, + description => Desc + }, + Acc + ) + ); properties([{Key, Type, Desc} | Props], Acc) -> properties(Props, maps:put(Key, #{type => Type, description => Desc}, Acc)); properties([{Key, Type, Desc, Enum} | Props], Acc) -> - properties(Props, maps:put(Key, #{type => Type, - description => Desc, - enum => Enum}, Acc)). + properties( + Props, + maps:put( + Key, + #{ + type => Type, + description => Desc, + enum => Enum + }, + Acc + ) + ). page_params() -> - [#{ - name => page, - in => query, - description => <<"Page">>, - schema => #{type => integer, default => 1} - }, - #{ - name => limit, - in => query, - description => <<"Page size">>, - schema => #{type => integer, default => emqx_mgmt:max_row_limit()} - }]. + [ + #{ + name => page, + in => query, + description => <<"Page">>, + schema => #{type => integer, default => 1} + }, + #{ + name => limit, + in => query, + description => <<"Page size">>, + schema => #{type => integer, default => emqx_mgmt:max_row_limit()} + } + ]. bad_request() -> bad_request(<<"Bad Request">>). diff --git a/apps/emqx_management/src/proto/emqx_mgmt_api_plugins_proto_v1.erl b/apps/emqx_management/src/proto/emqx_mgmt_api_plugins_proto_v1.erl index 4a2d9e65d..dc7904d01 100644 --- a/apps/emqx_management/src/proto/emqx_mgmt_api_plugins_proto_v1.erl +++ b/apps/emqx_management/src/proto/emqx_mgmt_api_plugins_proto_v1.erl @@ -17,12 +17,13 @@ -behaviour(emqx_bpapi). --export([ introduced_in/0 - , get_plugins/0 - , install_package/2 - , describe_package/1 - , delete_package/1 - , ensure_action/2 +-export([ + introduced_in/0, + get_plugins/0, + install_package/2, + describe_package/1, + delete_package/1, + ensure_action/2 ]). -include_lib("emqx/include/bpapi.hrl"). diff --git a/apps/emqx_management/src/proto/emqx_mgmt_trace_proto_v1.erl b/apps/emqx_management/src/proto/emqx_mgmt_trace_proto_v1.erl index 6579dcd02..4f847219e 100644 --- a/apps/emqx_management/src/proto/emqx_mgmt_trace_proto_v1.erl +++ b/apps/emqx_management/src/proto/emqx_mgmt_trace_proto_v1.erl @@ -18,12 +18,13 @@ -behaviour(emqx_bpapi). --export([ introduced_in/0 +-export([ + introduced_in/0, - , trace_file/2 - , get_trace_size/1 - , read_trace_file/4 - ]). + trace_file/2, + get_trace_size/1, + read_trace_file/4 +]). -include_lib("emqx/include/bpapi.hrl"). @@ -31,21 +32,22 @@ introduced_in() -> "5.0.0". -spec get_trace_size([node()]) -> - emqx_rpc:multicall_result(#{{node(), file:name_all()} => non_neg_integer()}). + emqx_rpc:multicall_result(#{{node(), file:name_all()} => non_neg_integer()}). get_trace_size(Nodes) -> rpc:multicall(Nodes, emqx_mgmt_api_trace, get_trace_size, [], 30000). -spec trace_file([node()], file:name_all()) -> - emqx_rpc:multicall_result( - {ok, Node :: list(), Binary :: binary()} | - {error, Node :: list(), Reason :: term()}). + emqx_rpc:multicall_result( + {ok, Node :: list(), Binary :: binary()} + | {error, Node :: list(), Reason :: term()} + ). trace_file(Nodes, File) -> rpc:multicall(Nodes, emqx_trace, trace_file, [File], 60000). -spec read_trace_file(node(), binary(), non_neg_integer(), non_neg_integer()) -> - {ok, binary()} - | {error, _} - | {eof, non_neg_integer()} - | {badrpc, _}. + {ok, binary()} + | {error, _} + | {eof, non_neg_integer()} + | {badrpc, _}. read_trace_file(Node, Name, Position, Limit) -> rpc:call(Node, emqx_mgmt_api_trace, read_trace_file, [Name, Position, Limit]). diff --git a/apps/emqx_management/test/emqx_mgmt_api_alarms_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_alarms_SUITE.erl index e87a8a6b0..a499e7cd2 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_alarms_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_alarms_SUITE.erl @@ -15,7 +15,6 @@ %%-------------------------------------------------------------------- -module(emqx_mgmt_api_alarms_SUITE). - -compile(export_all). -compile(nowarn_export_all). @@ -55,8 +54,8 @@ get_alarms(AssertCount, Activated) -> Headers = emqx_mgmt_api_test_util:auth_header_(), {ok, Response} = emqx_mgmt_api_test_util:request_api(get, Path, Qs, Headers), Data = emqx_json:decode(Response, [return_maps]), - Meta = maps:get(<<"meta">>, Data), - Page = maps:get(<<"page">>, Meta), + Meta = maps:get(<<"meta">>, Data), + Page = maps:get(<<"page">>, Meta), Limit = maps:get(<<"limit">>, Meta), Count = maps:get(<<"count">>, Meta), ?assertEqual(Page, 1), diff --git a/apps/emqx_management/test/emqx_mgmt_api_app_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_app_SUITE.erl index a8319fa73..85f712fed 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_app_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_app_SUITE.erl @@ -22,10 +22,11 @@ all() -> [{group, parallel}, {group, sequence}]. suite() -> [{timetrap, {minutes, 1}}]. -groups() -> [ - {parallel, [parallel], [t_create, t_update, t_delete, t_authorize, t_create_unexpired_app]}, - {sequence, [], [t_create_failed]} - ]. +groups() -> + [ + {parallel, [parallel], [t_create, t_update, t_delete, t_authorize, t_create_unexpired_app]}, + {sequence, [], [t_create_failed]} + ]. init_per_suite(Config) -> emqx_mgmt_api_test_util:init_suite(), @@ -37,15 +38,20 @@ end_per_suite(_) -> t_create(_Config) -> Name = <<"EMQX-API-KEY-1">>, {ok, Create} = create_app(Name), - ?assertMatch(#{<<"api_key">> := _, - <<"api_secret">> := _, - <<"created_at">> := _, - <<"desc">> := _, - <<"enable">> := true, - <<"expired_at">> := _, - <<"name">> := Name}, Create), + ?assertMatch( + #{ + <<"api_key">> := _, + <<"api_secret">> := _, + <<"created_at">> := _, + <<"desc">> := _, + <<"enable">> := true, + <<"expired_at">> := _, + <<"name">> := Name + }, + Create + ), {ok, List} = list_app(), - [App] = lists:filter(fun(#{<<"name">> := NameA}) -> NameA =:= Name end, List), + [App] = lists:filter(fun(#{<<"name">> := NameA}) -> NameA =:= Name end, List), ?assertEqual(false, maps:is_key(<<"api_secret">>, App)), {ok, App1} = read_app(Name), ?assertEqual(Name, maps:get(<<"name">>, App1)), @@ -64,9 +70,12 @@ t_create_failed(_Config) -> {ok, List} = list_app(), CreateNum = 30 - erlang:length(List), - Names = lists:map(fun(Seq) -> - <<"EMQX-API-FAILED-KEY-", (integer_to_binary(Seq))/binary>> - end, lists:seq(1, CreateNum)), + Names = lists:map( + fun(Seq) -> + <<"EMQX-API-FAILED-KEY-", (integer_to_binary(Seq))/binary>> + end, + lists:seq(1, CreateNum) + ), lists:foreach(fun(N) -> {ok, _} = create_app(N) end, Names), ?assertEqual(BadRequest, create_app(<<"EMQX-API-KEY-MAXIMUM">>)), @@ -93,7 +102,8 @@ t_update(_Config) -> ?assertEqual(Name, maps:get(<<"name">>, Update1)), ?assertEqual(false, maps:get(<<"enable">>, Update1)), ?assertEqual(<<"NoteVersion1"/utf8>>, maps:get(<<"desc">>, Update1)), - ?assertEqual(calendar:rfc3339_to_system_time(binary_to_list(ExpiredAt)), + ?assertEqual( + calendar:rfc3339_to_system_time(binary_to_list(ExpiredAt)), calendar:rfc3339_to_system_time(binary_to_list(maps:get(<<"expired_at">>, Update1))) ), Unexpired1 = maps:without([expired_at], Change), @@ -117,10 +127,14 @@ t_delete(_Config) -> t_authorize(_Config) -> Name = <<"EMQX-API-AUTHORIZE-KEY">>, {ok, #{<<"api_key">> := ApiKey, <<"api_secret">> := ApiSecret}} = create_app(Name), - BasicHeader = emqx_common_test_http:auth_header(binary_to_list(ApiKey), - binary_to_list(ApiSecret)), - SecretError = emqx_common_test_http:auth_header(binary_to_list(ApiKey), - binary_to_list(ApiKey)), + BasicHeader = emqx_common_test_http:auth_header( + binary_to_list(ApiKey), + binary_to_list(ApiSecret) + ), + SecretError = emqx_common_test_http:auth_header( + binary_to_list(ApiKey), + binary_to_list(ApiKey) + ), KeyError = emqx_common_test_http:auth_header("not_found_key", binary_to_list(ApiSecret)), Unauthorized = {error, {"HTTP/1.1", 401, "Unauthorized"}}, @@ -134,8 +148,10 @@ t_authorize(_Config) -> ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, ApiKeyPath, BasicHeader)), ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, UserPath, BasicHeader)), - ?assertMatch({ok, #{<<"api_key">> := _, <<"enable">> := false}}, - update_app(Name, #{enable => false})), + ?assertMatch( + {ok, #{<<"api_key">> := _, <<"enable">> := false}}, + update_app(Name, #{enable => false}) + ), ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)), Expired = #{ @@ -145,8 +161,10 @@ t_authorize(_Config) -> ?assertMatch({ok, #{<<"api_key">> := _, <<"enable">> := true}}, update_app(Name, Expired)), ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)), UnExpired = #{expired_at => undefined}, - ?assertMatch({ok, #{<<"api_key">> := _, <<"expired_at">> := <<"undefined">>}}, - update_app(Name, UnExpired)), + ?assertMatch( + {ok, #{<<"api_key">> := _, <<"expired_at">> := <<"undefined">>}}, + update_app(Name, UnExpired) + ), {ok, _Status1} = emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader), ok. @@ -159,7 +177,6 @@ t_create_unexpired_app(_Config) -> ?assertMatch(#{<<"expired_at">> := <<"undefined">>}, Create2), ok. - list_app() -> Path = emqx_mgmt_api_test_util:api_path(["api_key"]), case emqx_mgmt_api_test_util:request_api(get, Path) of diff --git a/apps/emqx_management/test/emqx_mgmt_api_banned_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_banned_SUITE.erl index 828312b9c..3e69dd851 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_banned_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_banned_SUITE.erl @@ -47,13 +47,17 @@ t_create(_Config) -> until => Until }, {ok, ClientIdBannedRes} = create_banned(ClientIdBanned), - ?assertEqual(#{<<"as">> => As, - <<"at">> => At, - <<"by">> => By, - <<"reason">> => Reason, - <<"until">> => Until, - <<"who">> => ClientId - }, ClientIdBannedRes), + ?assertEqual( + #{ + <<"as">> => As, + <<"at">> => At, + <<"by">> => By, + <<"reason">> => Reason, + <<"until">> => Until, + <<"who">> => ClientId + }, + ClientIdBannedRes + ), PeerHost = <<"192.168.2.13">>, PeerHostBanned = #{ as => <<"peerhost">>, @@ -64,15 +68,19 @@ t_create(_Config) -> until => Until }, {ok, PeerHostBannedRes} = create_banned(PeerHostBanned), - ?assertEqual(#{<<"as">> => <<"peerhost">>, - <<"at">> => At, - <<"by">> => By, - <<"reason">> => Reason, - <<"until">> => Until, - <<"who">> => PeerHost - }, PeerHostBannedRes), + ?assertEqual( + #{ + <<"as">> => <<"peerhost">>, + <<"at">> => At, + <<"by">> => By, + <<"reason">> => Reason, + <<"until">> => Until, + <<"who">> => PeerHost + }, + PeerHostBannedRes + ), {ok, #{<<"data">> := List}} = list_banned(), - Bans = lists:sort(lists:map(fun(#{<<"who">> := W, <<"as">> := A}) -> {A, W} end, List)), + Bans = lists:sort(lists:map(fun(#{<<"who">> := W, <<"as">> := A}) -> {A, W} end, List)), ?assertEqual([{<<"clientid">>, ClientId}, {<<"peerhost">>, PeerHost}], Bans), ok. @@ -94,8 +102,10 @@ t_create_failed(_Config) -> }, BadRequest = {error, {"HTTP/1.1", 400, "Bad Request"}}, ?assertEqual(BadRequest, create_banned(BadPeerHost)), - Expired = BadPeerHost#{until => emqx_banned:to_rfc3339(Now - 1), - who => <<"127.0.0.1">>}, + Expired = BadPeerHost#{ + until => emqx_banned:to_rfc3339(Now - 1), + who => <<"127.0.0.1">> + }, ?assertEqual(BadRequest, create_banned(Expired)), ok. @@ -117,8 +127,10 @@ t_delete(_Config) -> }, {ok, _} = create_banned(Banned), ?assertMatch({ok, _}, delete_banned(binary_to_list(As), binary_to_list(Who))), - ?assertMatch({error,{"HTTP/1.1",404,"Not Found"}}, - delete_banned(binary_to_list(As), binary_to_list(Who))), + ?assertMatch( + {error, {"HTTP/1.1", 404, "Not Found"}}, + delete_banned(binary_to_list(As), binary_to_list(Who)) + ), ok. list_banned() -> diff --git a/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl index 295bb0e9e..897862b20 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl @@ -44,20 +44,20 @@ t_clients(_) -> AuthHeader = emqx_mgmt_api_test_util:auth_header_(), {ok, C1} = emqtt:start_link(#{username => Username1, clientid => ClientId1}), - {ok, _} = emqtt:connect(C1), + {ok, _} = emqtt:connect(C1), {ok, C2} = emqtt:start_link(#{username => Username2, clientid => ClientId2}), - {ok, _} = emqtt:connect(C2), + {ok, _} = emqtt:connect(C2), timer:sleep(300), %% get /clients - ClientsPath = emqx_mgmt_api_test_util:api_path(["clients"]), - {ok, Clients} = emqx_mgmt_api_test_util:request_api(get, ClientsPath), + ClientsPath = emqx_mgmt_api_test_util:api_path(["clients"]), + {ok, Clients} = emqx_mgmt_api_test_util:request_api(get, ClientsPath), ClientsResponse = emqx_json:decode(Clients, [return_maps]), - ClientsMeta = maps:get(<<"meta">>, ClientsResponse), - ClientsPage = maps:get(<<"page">>, ClientsMeta), - ClientsLimit = maps:get(<<"limit">>, ClientsMeta), - ClientsCount = maps:get(<<"count">>, ClientsMeta), + ClientsMeta = maps:get(<<"meta">>, ClientsResponse), + ClientsPage = maps:get(<<"page">>, ClientsMeta), + ClientsLimit = maps:get(<<"limit">>, ClientsMeta), + ClientsCount = maps:get(<<"count">>, ClientsMeta), ?assertEqual(ClientsPage, 1), ?assertEqual(ClientsLimit, emqx_mgmt:max_row_limit()), ?assertEqual(ClientsCount, 2), @@ -77,28 +77,48 @@ t_clients(_) -> ?assertEqual({error, {"HTTP/1.1", 404, "Not Found"}}, AfterKickoutResponse2), %% get /clients/:clientid/authorization/cache should has no authz cache - Client1AuthzCachePath = emqx_mgmt_api_test_util:api_path(["clients", - binary_to_list(ClientId1), "authorization", "cache"]), + Client1AuthzCachePath = emqx_mgmt_api_test_util:api_path([ + "clients", + binary_to_list(ClientId1), + "authorization", + "cache" + ]), {ok, Client1AuthzCache} = emqx_mgmt_api_test_util:request_api(get, Client1AuthzCachePath), ?assertEqual("[]", Client1AuthzCache), %% post /clients/:clientid/subscribe SubscribeBody = #{topic => Topic, qos => Qos}, - SubscribePath = emqx_mgmt_api_test_util:api_path(["clients", - binary_to_list(ClientId1), "subscribe"]), - {ok, _} = emqx_mgmt_api_test_util:request_api(post, SubscribePath, - "", AuthHeader, SubscribeBody), + SubscribePath = emqx_mgmt_api_test_util:api_path([ + "clients", + binary_to_list(ClientId1), + "subscribe" + ]), + {ok, _} = emqx_mgmt_api_test_util:request_api( + post, + SubscribePath, + "", + AuthHeader, + SubscribeBody + ), timer:sleep(100), [{AfterSubTopic, #{qos := AfterSubQos}}] = emqx_mgmt:lookup_subscriptions(ClientId1), ?assertEqual(AfterSubTopic, Topic), ?assertEqual(AfterSubQos, Qos), %% post /clients/:clientid/unsubscribe - UnSubscribePath = emqx_mgmt_api_test_util:api_path(["clients", - binary_to_list(ClientId1), "unsubscribe"]), + UnSubscribePath = emqx_mgmt_api_test_util:api_path([ + "clients", + binary_to_list(ClientId1), + "unsubscribe" + ]), UnSubscribeBody = #{topic => Topic}, - {ok, _} = emqx_mgmt_api_test_util:request_api(post, UnSubscribePath, - "", AuthHeader, UnSubscribeBody), + {ok, _} = emqx_mgmt_api_test_util:request_api( + post, + UnSubscribePath, + "", + AuthHeader, + UnSubscribeBody + ), timer:sleep(100), ?assertEqual([], emqx_mgmt:lookup_subscriptions(Client1)), @@ -118,44 +138,58 @@ t_query_clients_with_time(_) -> ClientId2 = <<"client2">>, {ok, C1} = emqtt:start_link(#{username => Username1, clientid => ClientId1}), - {ok, _} = emqtt:connect(C1), + {ok, _} = emqtt:connect(C1), {ok, C2} = emqtt:start_link(#{username => Username2, clientid => ClientId2}), - {ok, _} = emqtt:connect(C2), + {ok, _} = emqtt:connect(C2), timer:sleep(100), - AuthHeader = emqx_mgmt_api_test_util:auth_header_(), - ClientsPath = emqx_mgmt_api_test_util:api_path(["clients"]), + AuthHeader = emqx_mgmt_api_test_util:auth_header_(), + ClientsPath = emqx_mgmt_api_test_util:api_path(["clients"]), %% get /clients with time(rfc3339) NowTimeStampInt = erlang:system_time(millisecond), %% Do not uri_encode `=` to `%3D` - Rfc3339String = emqx_http_lib:uri_encode(binary:bin_to_list( - emqx_datetime:epoch_to_rfc3339(NowTimeStampInt))), + Rfc3339String = emqx_http_lib:uri_encode( + binary:bin_to_list( + emqx_datetime:epoch_to_rfc3339(NowTimeStampInt) + ) + ), TimeStampString = emqx_http_lib:uri_encode(integer_to_list(NowTimeStampInt)), - LteKeys = ["lte_created_at=", "lte_connected_at="], - GteKeys = ["gte_created_at=", "gte_connected_at="], - LteParamRfc3339 = [Param ++ Rfc3339String || Param <- LteKeys], - LteParamStamp = [Param ++ TimeStampString || Param <- LteKeys], - GteParamRfc3339 = [Param ++ Rfc3339String || Param <- GteKeys], - GteParamStamp = [Param ++ TimeStampString || Param <- GteKeys], + LteKeys = ["lte_created_at=", "lte_connected_at="], + GteKeys = ["gte_created_at=", "gte_connected_at="], + LteParamRfc3339 = [Param ++ Rfc3339String || Param <- LteKeys], + LteParamStamp = [Param ++ TimeStampString || Param <- LteKeys], + GteParamRfc3339 = [Param ++ Rfc3339String || Param <- GteKeys], + GteParamStamp = [Param ++ TimeStampString || Param <- GteKeys], - RequestResults = - [emqx_mgmt_api_test_util:request_api(get, ClientsPath, Param, AuthHeader) - || Param <- LteParamRfc3339 ++ LteParamStamp - ++ GteParamRfc3339 ++ GteParamStamp], - DecodedResults = [emqx_json:decode(Response, [return_maps]) - || {ok, Response} <- RequestResults], + RequestResults = + [ + emqx_mgmt_api_test_util:request_api(get, ClientsPath, Param, AuthHeader) + || Param <- + LteParamRfc3339 ++ LteParamStamp ++ + GteParamRfc3339 ++ GteParamStamp + ], + DecodedResults = [ + emqx_json:decode(Response, [return_maps]) + || {ok, Response} <- RequestResults + ], {LteResponseDecodeds, GteResponseDecodeds} = lists:split(4, DecodedResults), %% EachData :: list() - [?assert(time_string_to_epoch_millisecond(CreatedAt) < NowTimeStampInt) + [ + ?assert(time_string_to_epoch_millisecond(CreatedAt) < NowTimeStampInt) || #{<<"data">> := EachData} <- LteResponseDecodeds, - #{<<"created_at">> := CreatedAt} <- EachData], - [?assert(time_string_to_epoch_millisecond(ConnectedAt) < NowTimeStampInt) + #{<<"created_at">> := CreatedAt} <- EachData + ], + [ + ?assert(time_string_to_epoch_millisecond(ConnectedAt) < NowTimeStampInt) || #{<<"data">> := EachData} <- LteResponseDecodeds, - #{<<"connected_at">> := ConnectedAt} <- EachData], - [?assertEqual(EachData, []) - || #{<<"data">> := EachData} <- GteResponseDecodeds], + #{<<"connected_at">> := ConnectedAt} <- EachData + ], + [ + ?assertEqual(EachData, []) + || #{<<"data">> := EachData} <- GteResponseDecodeds + ], %% testcase cleanup, kickout client1 and client2 Client1Path = emqx_mgmt_api_test_util:api_path(["clients", binary_to_list(ClientId1)]), @@ -169,7 +203,7 @@ t_keepalive(_Config) -> AuthHeader = emqx_mgmt_api_test_util:auth_header_(), Path = emqx_mgmt_api_test_util:api_path(["clients", ClientId, "keepalive"]), Body = #{interval => 11}, - {error,{"HTTP/1.1",404,"Not Found"}} = + {error, {"HTTP/1.1", 404, "Not Found"}} = emqx_mgmt_api_test_util:request_api(put, Path, <<"">>, AuthHeader, Body), {ok, C1} = emqtt:start_link(#{username => Username, clientid => ClientId}), {ok, _} = emqtt:connect(C1), @@ -190,5 +224,6 @@ time_string_to_epoch(DateTime, Unit) when is_binary(DateTime) -> catch error:badarg -> calendar:rfc3339_to_system_time( - binary_to_list(DateTime), [{unit, Unit}]) + binary_to_list(DateTime), [{unit, Unit}] + ) end. diff --git a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl index 592c6ede2..b594751af 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl @@ -32,10 +32,13 @@ end_per_suite(_) -> t_get(_Config) -> {ok, Configs} = get_configs(), - maps:map(fun(Name, Value) -> - {ok, Config} = get_config(Name), - ?assertEqual(Value, Config) - end, maps:remove(<<"license">>, Configs)), + maps:map( + fun(Name, Value) -> + {ok, Config} = get_config(Name), + ?assertEqual(Value, Config) + end, + maps:remove(<<"license">>, Configs) + ), ok. t_update(_Config) -> @@ -50,8 +53,10 @@ t_update(_Config) -> %% update failed ErrorSysMon = emqx_map_lib:deep_put([<<"vm">>, <<"busy_port">>], SysMon, "123"), - ?assertMatch({error, {"HTTP/1.1", 400, _}}, - update_config(<<"sysmon">>, ErrorSysMon)), + ?assertMatch( + {error, {"HTTP/1.1", 400, _}}, + update_config(<<"sysmon">>, ErrorSysMon) + ), {ok, SysMon2} = get_config(<<"sysmon">>), ?assertEqual(SysMon1, SysMon2), @@ -101,8 +106,10 @@ t_global_zone(_Config) -> {ok, Zones} = get_global_zone(), ZonesKeys = lists:map(fun({K, _}) -> K end, hocon_schema:roots(emqx_zone_schema)), ?assertEqual(lists:usort(ZonesKeys), lists:usort(maps:keys(Zones))), - ?assertEqual(emqx_config:get_zone_conf(no_default, [mqtt, max_qos_allowed]), - emqx_map_lib:deep_get([<<"mqtt">>, <<"max_qos_allowed">>], Zones)), + ?assertEqual( + emqx_config:get_zone_conf(no_default, [mqtt, max_qos_allowed]), + emqx_map_lib:deep_get([<<"mqtt">>, <<"max_qos_allowed">>], Zones) + ), NewZones = emqx_map_lib:deep_put([<<"mqtt">>, <<"max_qos_allowed">>], Zones, 1), {ok, #{}} = update_global_zone(NewZones), ?assertEqual(1, emqx_config:get_zone_conf(no_default, [mqtt, max_qos_allowed])), @@ -133,7 +140,8 @@ get_config(Name) -> case emqx_mgmt_api_test_util:request_api(get, Path) of {ok, Res} -> {ok, emqx_json:decode(Res, [return_maps])}; - Error -> Error + Error -> + Error end. get_configs() -> @@ -153,8 +161,11 @@ update_config(Name, Change) -> reset_config(Name, Key) -> AuthHeader = emqx_mgmt_api_test_util:auth_header_(), - Path = binary_to_list(iolist_to_binary( - emqx_mgmt_api_test_util:api_path(["configs_reset", Name]))), + Path = binary_to_list( + iolist_to_binary( + emqx_mgmt_api_test_util:api_path(["configs_reset", Name]) + ) + ), case emqx_mgmt_api_test_util:request_api(post, Path, Key, AuthHeader, []) of {ok, []} -> ok; Error -> Error diff --git a/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl index 5d6f9e80b..1166903d7 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl @@ -40,16 +40,17 @@ t_single_node_metrics_api(_) -> {ok, MetricsResponse} = request_helper("metrics"), [MetricsFromAPI] = emqx_json:decode(MetricsResponse, [return_maps]), LocalNodeMetrics = maps:from_list( - emqx_mgmt:get_metrics(node()) ++ [{node, to_bin(node())}]), + emqx_mgmt:get_metrics(node()) ++ [{node, to_bin(node())}] + ), match_helper(LocalNodeMetrics, MetricsFromAPI). match_helper(SystemMetrics, MetricsFromAPI) -> length_equal(SystemMetrics, MetricsFromAPI), Fun = - fun (Key, {SysMetrics, APIMetrics}) -> - Value = maps:get(Key, SysMetrics), - ?assertEqual(Value, maps:get(to_bin(Key), APIMetrics)), - {Value, {SysMetrics, APIMetrics}} + fun(Key, {SysMetrics, APIMetrics}) -> + Value = maps:get(Key, SysMetrics), + ?assertEqual(Value, maps:get(to_bin(Key), APIMetrics)), + {Value, {SysMetrics, APIMetrics}} end, lists:mapfoldl(Fun, {SystemMetrics, MetricsFromAPI}, maps:keys(SystemMetrics)). diff --git a/apps/emqx_management/test/emqx_mgmt_api_nodes_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_nodes_SUITE.erl index 43e032991..0bb98aca6 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_nodes_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_nodes_SUITE.erl @@ -67,19 +67,21 @@ t_nodes_api(_) -> BadNodePath = emqx_mgmt_api_test_util:api_path(["nodes", "badnode"]), ?assertMatch( {error, {_, 400, _}}, - emqx_mgmt_api_test_util:request_api(get, BadNodePath)). + emqx_mgmt_api_test_util:request_api(get, BadNodePath) + ). t_log_path(_) -> NodePath = emqx_mgmt_api_test_util:api_path(["nodes", atom_to_list(node())]), {ok, NodeInfo} = emqx_mgmt_api_test_util:request_api(get, NodePath), #{<<"log_path">> := Path} = emqx_json:decode(NodeInfo, [return_maps]), ?assertEqual( - <<"emqx-test.log">>, - filename:basename(Path)). + <<"emqx-test.log">>, + filename:basename(Path) + ). t_node_stats_api(_) -> StatsPath = emqx_mgmt_api_test_util:api_path(["nodes", atom_to_binary(node(), utf8), "stats"]), - SystemStats= emqx_mgmt:get_stats(), + SystemStats = emqx_mgmt:get_stats(), {ok, StatsResponse} = emqx_mgmt_api_test_util:request_api(get, StatsPath), Stats = emqx_json:decode(StatsResponse, [return_maps]), Fun = @@ -91,12 +93,13 @@ t_node_stats_api(_) -> BadNodePath = emqx_mgmt_api_test_util:api_path(["nodes", "badnode", "stats"]), ?assertMatch( {error, {_, 400, _}}, - emqx_mgmt_api_test_util:request_api(get, BadNodePath)). + emqx_mgmt_api_test_util:request_api(get, BadNodePath) + ). t_node_metrics_api(_) -> MetricsPath = emqx_mgmt_api_test_util:api_path(["nodes", atom_to_binary(node(), utf8), "metrics"]), - SystemMetrics= emqx_mgmt:get_metrics(), + SystemMetrics = emqx_mgmt:get_metrics(), {ok, MetricsResponse} = emqx_mgmt_api_test_util:request_api(get, MetricsPath), Metrics = emqx_json:decode(MetricsResponse, [return_maps]), Fun = @@ -108,4 +111,5 @@ t_node_metrics_api(_) -> BadNodePath = emqx_mgmt_api_test_util:api_path(["nodes", "badnode", "metrics"]), ?assertMatch( {error, {_, 400, _}}, - emqx_mgmt_api_test_util:request_api(get, BadNodePath)). + emqx_mgmt_api_test_util:request_api(get, BadNodePath) + ). diff --git a/apps/emqx_management/test/emqx_mgmt_api_plugins_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_plugins_SUITE.erl index a2612d176..ddf4de1a4 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_plugins_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_plugins_SUITE.erl @@ -56,17 +56,35 @@ todo_t_plugins(Config) -> ok = emqx_plugins:delete_package(NameVsn), ok = install_plugin(PackagePath), {ok, StopRes} = describe_plugins(NameVsn), - ?assertMatch(#{<<"running_status">> := [ - #{<<"node">> := <<"test@127.0.0.1">>, <<"status">> := <<"stopped">>}]}, StopRes), + ?assertMatch( + #{ + <<"running_status">> := [ + #{<<"node">> := <<"test@127.0.0.1">>, <<"status">> := <<"stopped">>} + ] + }, + StopRes + ), {ok, StopRes1} = update_plugin(NameVsn, "start"), ?assertEqual([], StopRes1), {ok, StartRes} = describe_plugins(NameVsn), - ?assertMatch(#{<<"running_status">> := [ - #{<<"node">> := <<"test@127.0.0.1">>, <<"status">> := <<"running">>}]}, StartRes), + ?assertMatch( + #{ + <<"running_status">> := [ + #{<<"node">> := <<"test@127.0.0.1">>, <<"status">> := <<"running">>} + ] + }, + StartRes + ), {ok, []} = update_plugin(NameVsn, "stop"), {ok, StopRes2} = describe_plugins(NameVsn), - ?assertMatch(#{<<"running_status">> := [ - #{<<"node">> := <<"test@127.0.0.1">>, <<"status">> := <<"stopped">>}]}, StopRes2), + ?assertMatch( + #{ + <<"running_status">> := [ + #{<<"node">> := <<"test@127.0.0.1">>, <<"status">> := <<"stopped">>} + ] + }, + StopRes2 + ), {ok, []} = uninstall_plugin(NameVsn), ok. @@ -87,8 +105,16 @@ describe_plugins(Name) -> install_plugin(FilePath) -> {ok, Token} = emqx_dashboard_admin:sign_token(<<"admin">>, <<"public">>), Path = emqx_mgmt_api_test_util:api_path(["plugins", "install"]), - case emqx_mgmt_api_test_util:upload_request(Path, FilePath, "plugin", - <<"application/gzip">>, [], Token) of + case + emqx_mgmt_api_test_util:upload_request( + Path, + FilePath, + "plugin", + <<"application/gzip">>, + [], + Token + ) + of {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, <<>>}} -> ok; Error -> Error end. @@ -109,7 +135,6 @@ uninstall_plugin(Name) -> DeletePath = emqx_mgmt_api_test_util:api_path(["plugins", Name]), emqx_mgmt_api_test_util:request_api(delete, DeletePath). - build_demo_plugin_package(Dir) -> #{package := Pkg} = emqx_plugins_SUITE:build_demo_plugin_package(), FileName = "emqx_plugin_template-" ++ ?EMQX_PLUGIN_TEMPLATE_VSN ++ ?PACKAGE_SUFFIX, diff --git a/apps/emqx_management/test/emqx_mgmt_api_publish_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_publish_SUITE.erl index adf8e8456..4d17451c9 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_publish_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_publish_SUITE.erl @@ -37,7 +37,9 @@ end_per_suite(_) -> emqx_mgmt_api_test_util:end_suite(). t_publish_api(_) -> - {ok, Client} = emqtt:start_link(#{username => <<"api_username">>, clientid => <<"api_clientid">>}), + {ok, Client} = emqtt:start_link(#{ + username => <<"api_username">>, clientid => <<"api_clientid">> + }), {ok, _} = emqtt:connect(Client), {ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC1), {ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC2), @@ -50,14 +52,16 @@ t_publish_api(_) -> emqtt:disconnect(Client). t_publish_bulk_api(_) -> - {ok, Client} = emqtt:start_link(#{username => <<"api_username">>, clientid => <<"api_clientid">>}), + {ok, Client} = emqtt:start_link(#{ + username => <<"api_username">>, clientid => <<"api_clientid">> + }), {ok, _} = emqtt:connect(Client), {ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC1), {ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC2), Payload = <<"hello">>, Path = emqx_mgmt_api_test_util:api_path(["publish", "bulk"]), Auth = emqx_mgmt_api_test_util:auth_header_(), - Body =[#{topic => ?TOPIC1, payload => Payload}, #{topic => ?TOPIC2, payload => Payload}], + Body = [#{topic => ?TOPIC1, payload => Payload}, #{topic => ?TOPIC2, payload => Payload}], {ok, Response} = emqx_mgmt_api_test_util:request_api(post, Path, "", Auth, Body), ResponseMap = emqx_json:decode(Response, [return_maps]), ?assertEqual(2, erlang:length(ResponseMap)), @@ -68,12 +72,12 @@ t_publish_bulk_api(_) -> receive_assert(Topic, Qos, Payload) -> receive {publish, Message} -> - ReceiveTopic = maps:get(topic, Message), - ReceiveQos = maps:get(qos, Message), - ReceivePayload = maps:get(payload, Message), - ?assertEqual(ReceiveTopic , Topic), - ?assertEqual(ReceiveQos , Qos), - ?assertEqual(ReceivePayload , Payload), + ReceiveTopic = maps:get(topic, Message), + ReceiveQos = maps:get(qos, Message), + ReceivePayload = maps:get(payload, Message), + ?assertEqual(ReceiveTopic, Topic), + ?assertEqual(ReceiveQos, Qos), + ?assertEqual(ReceivePayload, Payload), ok after 5000 -> timeout diff --git a/apps/emqx_management/test/emqx_mgmt_api_stats_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_stats_SUITE.erl index e27a2d7f8..2b260aa9e 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_stats_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_stats_SUITE.erl @@ -37,7 +37,7 @@ t_stats_api(_) -> SystemStats1 = emqx_mgmt:get_stats(), Fun1 = fun(Key) -> - ?assertEqual(maps:get(Key, SystemStats1), maps:get(atom_to_binary(Key, utf8), Stats1)) + ?assertEqual(maps:get(Key, SystemStats1), maps:get(atom_to_binary(Key, utf8), Stats1)) end, lists:foreach(Fun1, maps:keys(SystemStats1)), StatsPath = emqx_mgmt_api_test_util:api_path(["stats?aggregate=true"]), diff --git a/apps/emqx_management/test/emqx_mgmt_api_subscription_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_subscription_SUITE.erl index a668888a5..89c36d933 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_subscription_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_subscription_SUITE.erl @@ -58,13 +58,13 @@ t_subscription_api(_) -> fun(#{<<"topic">> := T1}, #{<<"topic">> := T2}) -> maps:get(T1, ?TOPIC_SORT) =< maps:get(T2, ?TOPIC_SORT) end, - [Subscriptions1, Subscriptions2] = lists:sort(Sort, Subscriptions), + [Subscriptions1, Subscriptions2] = lists:sort(Sort, Subscriptions), ?assertEqual(maps:get(<<"topic">>, Subscriptions1), ?TOPIC1), ?assertEqual(maps:get(<<"topic">>, Subscriptions2), ?TOPIC2), ?assertEqual(maps:get(<<"clientid">>, Subscriptions1), ?CLIENTID), ?assertEqual(maps:get(<<"clientid">>, Subscriptions2), ?CLIENTID), - QS = uri_string:compose_query([ + QS = uri_string:compose_query([ {"clientid", ?CLIENTID}, {"topic", ?TOPIC2_TOPIC_ONLY}, {"node", atom_to_list(node())}, @@ -83,11 +83,11 @@ t_subscription_api(_) -> ?assertEqual(length(SubscriptionsList2), 1), MatchQs = uri_string:compose_query([ - {"clientid", ?CLIENTID}, - {"node", atom_to_list(node())}, - {"qos", "0"}, - {"match_topic", "t/#"} - ]), + {"clientid", ?CLIENTID}, + {"node", atom_to_list(node())}, + {"qos", "0"}, + {"match_topic", "t/#"} + ]), {ok, MatchRes} = emqx_mgmt_api_test_util:request_api(get, Path, MatchQs, Headers), MatchData = emqx_json:decode(MatchRes, [return_maps]), diff --git a/apps/emqx_management/test/emqx_mgmt_api_test_util.erl b/apps/emqx_management/test/emqx_mgmt_api_test_util.erl index e6f85b1d6..1bdf584e5 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_test_util.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_test_util.erl @@ -28,7 +28,6 @@ init_suite(Apps) -> application:load(emqx_management), emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], fun set_special_configs/1). - end_suite() -> end_suite([]). @@ -53,36 +52,44 @@ request_api(Method, Url, AuthOrHeaders) -> request_api(Method, Url, QueryParams, AuthOrHeaders) -> request_api(Method, Url, QueryParams, AuthOrHeaders, []). -request_api(Method, Url, QueryParams, AuthOrHeaders, []) - when (Method =:= options) orelse - (Method =:= get) orelse - (Method =:= put) orelse - (Method =:= head) orelse - (Method =:= delete) orelse - (Method =:= trace) -> - NewUrl = case QueryParams of - "" -> Url; - _ -> Url ++ "?" ++ QueryParams - end, +request_api(Method, Url, QueryParams, AuthOrHeaders, []) when + (Method =:= options) orelse + (Method =:= get) orelse + (Method =:= put) orelse + (Method =:= head) orelse + (Method =:= delete) orelse + (Method =:= trace) +-> + NewUrl = + case QueryParams of + "" -> Url; + _ -> Url ++ "?" ++ QueryParams + end, do_request_api(Method, {NewUrl, build_http_header(AuthOrHeaders)}); -request_api(Method, Url, QueryParams, AuthOrHeaders, Body) - when (Method =:= post) orelse - (Method =:= patch) orelse - (Method =:= put) orelse - (Method =:= delete) -> - NewUrl = case QueryParams of - "" -> Url; - _ -> Url ++ "?" ++ QueryParams - end, - do_request_api(Method, {NewUrl, build_http_header(AuthOrHeaders), "application/json", emqx_json:encode(Body)}). +request_api(Method, Url, QueryParams, AuthOrHeaders, Body) when + (Method =:= post) orelse + (Method =:= patch) orelse + (Method =:= put) orelse + (Method =:= delete) +-> + NewUrl = + case QueryParams of + "" -> Url; + _ -> Url ++ "?" ++ QueryParams + end, + do_request_api( + Method, + {NewUrl, build_http_header(AuthOrHeaders), "application/json", emqx_json:encode(Body)} + ). -do_request_api(Method, Request)-> +do_request_api(Method, Request) -> ct:pal("Method: ~p, Request: ~p", [Method, Request]), case httpc:request(Method, Request, [], []) of {error, socket_closed_remotely} -> {error, socket_closed_remotely}; - {ok, {{"HTTP/1.1", Code, _}, _, Return} } - when Code >= 200 andalso Code =< 299 -> + {ok, {{"HTTP/1.1", Code, _}, _, Return}} when + Code >= 200 andalso Code =< 299 + -> {ok, Return}; {ok, {Reason, _, _} = Error} -> ct:pal("error: ~p~n", [Error]), @@ -97,11 +104,10 @@ auth_header_() -> build_http_header(X) when is_list(X) -> X; - build_http_header(X) -> [X]. -api_path(Parts)-> +api_path(Parts) -> ?SERVER ++ filename:join([?BASE_PATH | Parts]). %% Usage: @@ -117,20 +123,27 @@ api_path(Parts)-> %% upload_request(<<"site.com/api/upload">>, <<"path/to/file.png">>, %% <<"upload">>, <<"image/png">>, RequestData, <<"some-token">>) -spec upload_request(URL, FilePath, Name, MimeType, RequestData, AuthorizationToken) -> - {ok, binary()} | {error, list()} when - URL:: binary(), - FilePath:: binary(), - Name:: binary(), - MimeType:: binary(), - RequestData:: list(), - AuthorizationToken:: binary(). + {ok, binary()} | {error, list()} +when + URL :: binary(), + FilePath :: binary(), + Name :: binary(), + MimeType :: binary(), + RequestData :: list(), + AuthorizationToken :: binary(). upload_request(URL, FilePath, Name, MimeType, RequestData, AuthorizationToken) -> Method = post, Filename = filename:basename(FilePath), {ok, Data} = file:read_file(FilePath), Boundary = emqx_guid:to_base62(emqx_guid:gen()), - RequestBody = format_multipart_formdata(Data, RequestData, Name, - [Filename], MimeType, Boundary), + RequestBody = format_multipart_formdata( + Data, + RequestData, + Name, + [Filename], + MimeType, + Boundary + ), ContentType = "multipart/form-data; boundary=" ++ binary_to_list(Boundary), ContentLength = integer_to_list(length(binary_to_list(RequestBody))), Headers = [ @@ -146,34 +159,56 @@ upload_request(URL, FilePath, Name, MimeType, RequestData, AuthorizationToken) - httpc:request(Method, {URL, Headers, ContentType, RequestBody}, HTTPOptions, Options). -spec format_multipart_formdata(Data, Params, Name, FileNames, MimeType, Boundary) -> - binary() when - Data:: binary(), - Params:: list(), - Name:: binary(), - FileNames:: list(), - MimeType:: binary(), - Boundary:: binary(). + binary() +when + Data :: binary(), + Params :: list(), + Name :: binary(), + FileNames :: list(), + MimeType :: binary(), + Boundary :: binary(). format_multipart_formdata(Data, Params, Name, FileNames, MimeType, Boundary) -> StartBoundary = erlang:iolist_to_binary([<<"--">>, Boundary]), LineSeparator = <<"\r\n">>, - WithParams = lists:foldl(fun({Key, Value}, Acc) -> - erlang:iolist_to_binary([ - Acc, - StartBoundary, LineSeparator, - <<"Content-Disposition: form-data; name=\"">>, Key, <<"\"">>, - LineSeparator, LineSeparator, - Value, LineSeparator - ]) - end, <<"">>, Params), - WithPaths = lists:foldl(fun(FileName, Acc) -> - erlang:iolist_to_binary([ - Acc, - StartBoundary, LineSeparator, - <<"Content-Disposition: form-data; name=\"">>, Name, <<"\"; filename=\"">>, - FileName, <<"\"">>, LineSeparator, - <<"Content-Type: ">>, MimeType, LineSeparator, LineSeparator, - Data, - LineSeparator - ]) - end, WithParams, FileNames), + WithParams = lists:foldl( + fun({Key, Value}, Acc) -> + erlang:iolist_to_binary([ + Acc, + StartBoundary, + LineSeparator, + <<"Content-Disposition: form-data; name=\"">>, + Key, + <<"\"">>, + LineSeparator, + LineSeparator, + Value, + LineSeparator + ]) + end, + <<"">>, + Params + ), + WithPaths = lists:foldl( + fun(FileName, Acc) -> + erlang:iolist_to_binary([ + Acc, + StartBoundary, + LineSeparator, + <<"Content-Disposition: form-data; name=\"">>, + Name, + <<"\"; filename=\"">>, + FileName, + <<"\"">>, + LineSeparator, + <<"Content-Type: ">>, + MimeType, + LineSeparator, + LineSeparator, + Data, + LineSeparator + ]) + end, + WithParams, + FileNames + ), erlang:iolist_to_binary([WithPaths, StartBoundary, <<"--">>, LineSeparator]). diff --git a/apps/emqx_management/test/emqx_mgmt_api_topics_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_topics_SUITE.erl index f6536c3b1..a06154400 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_topics_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_topics_SUITE.erl @@ -32,7 +32,9 @@ end_per_suite(_) -> t_nodes_api(_) -> Topic = <<"test_topic">>, - {ok, Client} = emqtt:start_link(#{username => <<"routes_username">>, clientid => <<"routes_cid">>}), + {ok, Client} = emqtt:start_link(#{ + username => <<"routes_username">>, clientid => <<"routes_cid">> + }), {ok, _} = emqtt:connect(Client), {ok, _, _} = emqtt:subscribe(Client, Topic), diff --git a/apps/emqx_management/test/emqx_mgmt_api_trace_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_trace_SUITE.erl index 8625a804c..255698b16 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_trace_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_trace_SUITE.erl @@ -59,7 +59,9 @@ t_http_test(_Config) -> #{ <<"code">> => <<"BAD_REQUEST">>, <<"message">> => <<"name : mandatory_required_field">> - }, json(Body)), + }, + json(Body) + ), Name = <<"test-name">>, Trace = [ @@ -77,32 +79,47 @@ t_http_test(_Config) -> %% update {ok, Update} = request_api(put, api_path("trace/test-name/stop"), Header, #{}), - ?assertEqual(#{<<"enable">> => false, - <<"name">> => <<"test-name">>}, json(Update)), + ?assertEqual( + #{ + <<"enable">> => false, + <<"name">> => <<"test-name">> + }, + json(Update) + ), - ?assertMatch({error, {"HTTP/1.1", 404, _}, _}, - request_api(put, api_path("trace/test-name-not-found/stop"), Header, #{})), + ?assertMatch( + {error, {"HTTP/1.1", 404, _}, _}, + request_api(put, api_path("trace/test-name-not-found/stop"), Header, #{}) + ), {ok, List1} = request_api(get, api_path("trace"), Header), [Data1] = json(List1), Node = atom_to_binary(node()), - ?assertMatch(#{ - <<"status">> := <<"stopped">>, - <<"name">> := <<"test-name">>, - <<"log_size">> := #{Node := _}, - <<"start_at">> := _, - <<"end_at">> := _, - <<"type">> := <<"topic">>, - <<"topic">> := <<"/x/y/z">> - }, Data1), + ?assertMatch( + #{ + <<"status">> := <<"stopped">>, + <<"name">> := <<"test-name">>, + <<"log_size">> := #{Node := _}, + <<"start_at">> := _, + <<"end_at">> := _, + <<"type">> := <<"topic">>, + <<"topic">> := <<"/x/y/z">> + }, + Data1 + ), %% delete {ok, Delete} = request_api(delete, api_path("trace/test-name"), Header), ?assertEqual(<<>>, Delete), - {error, {"HTTP/1.1", 404, "Not Found"}, DeleteNotFound} - = request_api(delete, api_path("trace/test-name"), Header), - ?assertEqual(#{<<"code">> => <<"NOT_FOUND">>, - <<"message">> => <<"test-name NOT FOUND">>}, json(DeleteNotFound)), + {error, {"HTTP/1.1", 404, "Not Found"}, DeleteNotFound} = + request_api(delete, api_path("trace/test-name"), Header), + ?assertEqual( + #{ + <<"code">> => <<"NOT_FOUND">>, + <<"message">> => <<"test-name NOT FOUND">> + }, + json(DeleteNotFound) + ), {ok, List2} = request_api(get, api_path("trace"), Header), ?assertEqual([], json(List2)), @@ -123,29 +140,43 @@ t_create_failed(_Config) -> Trace = [{<<"type">>, <<"topic">>}, {<<"topic">>, <<"/x/y/z">>}], BadName1 = {<<"name">>, <<"test/bad">>}, - ?assertMatch({error, {"HTTP/1.1", 400, _}, _}, - request_api(post, api_path("trace"), Header, [BadName1 | Trace])), + ?assertMatch( + {error, {"HTTP/1.1", 400, _}, _}, + request_api(post, api_path("trace"), Header, [BadName1 | Trace]) + ), BadName2 = {<<"name">>, list_to_binary(lists:duplicate(257, "t"))}, - ?assertMatch({error, {"HTTP/1.1", 400, _}, _}, - request_api(post, api_path("trace"), Header, [BadName2 | Trace])), + ?assertMatch( + {error, {"HTTP/1.1", 400, _}, _}, + request_api(post, api_path("trace"), Header, [BadName2 | Trace]) + ), %% already_exist GoodName = {<<"name">>, <<"test-name-0">>}, {ok, Create} = request_api(post, api_path("trace"), Header, [GoodName | Trace]), ?assertMatch(#{<<"name">> := <<"test-name-0">>}, json(Create)), - ?assertMatch({error, {"HTTP/1.1", 400, _}, _}, - request_api(post, api_path("trace"), Header, [GoodName | Trace])), + ?assertMatch( + {error, {"HTTP/1.1", 400, _}, _}, + request_api(post, api_path("trace"), Header, [GoodName | Trace]) + ), %% MAX Limited - lists:map(fun(Seq) -> - Name0 = list_to_binary("name" ++ integer_to_list(Seq)), - Trace0 = [{name, Name0}, {type, topic}, - {topic, list_to_binary("/x/y/" ++ integer_to_list(Seq))}], - {ok, _} = emqx_trace:create(Trace0) - end, lists:seq(1, 30 - ets:info(emqx_trace, size))), + lists:map( + fun(Seq) -> + Name0 = list_to_binary("name" ++ integer_to_list(Seq)), + Trace0 = [ + {name, Name0}, + {type, topic}, + {topic, list_to_binary("/x/y/" ++ integer_to_list(Seq))} + ], + {ok, _} = emqx_trace:create(Trace0) + end, + lists:seq(1, 30 - ets:info(emqx_trace, size)) + ), GoodName1 = {<<"name">>, <<"test-name-1">>}, - ?assertMatch({error, {"HTTP/1.1", 400, _}, _}, - request_api(post, api_path("trace"), Header, [GoodName1 | Trace])), + ?assertMatch( + {error, {"HTTP/1.1", 400, _}, _}, + request_api(post, api_path("trace"), Header, [GoodName1 | Trace]) + ), unload(), emqx_trace:clear(), ok. @@ -158,14 +189,23 @@ t_download_log(_Config) -> create_trace(Name, ClientId, Now), {ok, Client} = emqtt:start_link([{clean_start, true}, {clientid, ClientId}]), {ok, _} = emqtt:connect(Client), - [begin _ = emqtt:ping(Client) end ||_ <- lists:seq(1, 5)], + [ + begin + _ = emqtt:ping(Client) + end + || _ <- lists:seq(1, 5) + ], ok = emqx_trace_handler_SUITE:filesync(Name, clientid), Header = auth_header_(), {ok, Binary} = request_api(get, api_path("trace/test_client_id/download"), Header), - {ok, [_Comment, - #zip_file{name = ZipName, - info = #file_info{size = Size, type = regular, access = read_write}}]} - = zip:table(Binary), + {ok, [ + _Comment, + #zip_file{ + name = ZipName, + info = #file_info{size = Size, type = regular, access = read_write} + } + ]} = + zip:table(Binary), ?assert(Size > 0), ZipNamePrefix = lists:flatten(io_lib:format("~s-trace_~s", [node(), Name])), ?assertNotEqual(nomatch, re:run(ZipName, [ZipNamePrefix])), @@ -176,13 +216,18 @@ create_trace(Name, ClientId, Start) -> ?check_trace( #{timetrap => 900}, begin - {ok, _} = emqx_trace:create([{<<"name">>, Name}, - {<<"type">>, clientid}, {<<"clientid">>, ClientId}, {<<"start_at">>, Start}]), + {ok, _} = emqx_trace:create([ + {<<"name">>, Name}, + {<<"type">>, clientid}, + {<<"clientid">>, ClientId}, + {<<"start_at">>, Start} + ]), ?block_until(#{?snk_kind := update_trace_done}) end, fun(Trace) -> ?assertMatch([#{}], ?of_kind(update_trace_done, Trace)) - end). + end + ). t_stream_log(_Config) -> application:set_env(emqx, allow_anonymous, true), @@ -194,7 +239,12 @@ t_stream_log(_Config) -> create_trace(Name, ClientId, Now - 10), {ok, Client} = emqtt:start_link([{clean_start, true}, {clientid, ClientId}]), {ok, _} = emqtt:connect(Client), - [begin _ = emqtt:ping(Client) end || _ <- lists:seq(1, 5)], + [ + begin + _ = emqtt:ping(Client) + end + || _ <- lists:seq(1, 5) + ], emqtt:publish(Client, <<"/good">>, #{}, <<"ghood1">>, [{qos, 0}]), emqtt:publish(Client, <<"/good">>, #{}, <<"ghood2">>, [{qos, 0}]), ok = emqtt:disconnect(Client), @@ -239,8 +289,9 @@ do_request_api(Method, Request) -> {error, socket_closed_remotely}; {error, {shutdown, server_closed}} -> {error, server_closed}; - {ok, {{"HTTP/1.1", Code, _}, _Headers, Return}} - when Code =:= 200 orelse Code =:= 201 orelse Code =:= 204 -> + {ok, {{"HTTP/1.1", Code, _}, _Headers, Return}} when + Code =:= 200 orelse Code =:= 201 orelse Code =:= 204 + -> {ok, Return}; {ok, {Reason, _Header, Body}} -> {error, Reason, Body} @@ -250,7 +301,8 @@ api_path(Path) -> ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, Path]). json(Data) -> - {ok, Jsx} = emqx_json:safe_decode(Data, [return_maps]), Jsx. + {ok, Jsx} = emqx_json:safe_decode(Data, [return_maps]), + Jsx. load() -> emqx_trace:start_link().