From 786be82d07ceeb393cf036c63d6004e8a947c1a2 Mon Sep 17 00:00:00 2001 From: firest Date: Sat, 2 Apr 2022 14:22:29 +0800 Subject: [PATCH] test(gateway): optimize test codes organization --- .../src/simple_authn/emqx_authn_http.erl | 14 +- .../src/coap/emqx_coap_message.erl | 2 +- apps/emqx_gateway/test/emqx_exproto_SUITE.erl | 8 +- .../test/emqx_gateway_auth_ct.erl | 211 ++++++++++++++++ ...SUITE.erl => emqx_gateway_authn_SUITE.erl} | 228 ++++-------------- apps/emqx_gateway/test/emqx_stomp_SUITE.erl | 5 + 6 files changed, 285 insertions(+), 183 deletions(-) create mode 100644 apps/emqx_gateway/test/emqx_gateway_auth_ct.erl rename apps/emqx_gateway/test/{emqx_gateway_authn_http_SUITE.erl => emqx_gateway_authn_SUITE.erl} (54%) diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl index 31fff4940..8c333f6a6 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -137,11 +137,12 @@ create( #{ method := Method, url := RawURL, - headers := Headers, + headers := HeadersT, body := Body, request_timeout := RequestTimeout } = Config ) -> + Headers = ensure_header_name_type(HeadersT), {BsaeUrlWithPath, Query} = parse_fullpath(RawURL), URIMap = parse_url(BsaeUrlWithPath), ResourceId = emqx_authn_utils:make_resource_id(?MODULE), @@ -383,3 +384,14 @@ to_bin(L) when is_list(L) -> get_conf_val(Name, Conf) -> hocon_maps:get(?CONF_NS ++ "." ++ Name, Conf). + +ensure_header_name_type(Headers) -> + Fun = fun + (Key, _Val, Acc) when is_binary(Key) -> + Acc; + (Key, Val, Acc) when is_atom(Key) -> + Acc2 = maps:remove(Key, Acc), + BinKey = erlang:atom_to_binary(Key), + Acc2#{BinKey => Val} + end, + maps:fold(Fun, Headers, Headers). diff --git a/apps/emqx_gateway/src/coap/emqx_coap_message.erl b/apps/emqx_gateway/src/coap/emqx_coap_message.erl index 41d42b165..d7a229532 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_message.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_message.erl @@ -43,7 +43,7 @@ set_payload_block/3, set_payload_block/4 ]). --include_lib("emqx_gateway/src/coap/include/emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). request(Type, Method) -> request(Type, Method, <<>>, []). diff --git a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl b/apps/emqx_gateway/test/emqx_exproto_SUITE.erl index 50d69cb0a..07d9e8934 100644 --- a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_exproto_SUITE.erl @@ -41,8 +41,8 @@ -define(DTLSOPTS, [binary, {active, false}, {protocol, dtls}]). %%-------------------------------------------------------------------- --define(CONF_DEFAULT, - <<"\n" +-define(CONF_DEFAULT, << + "\n" "gateway.exproto {\n" " server.bind = 9100,\n" " handler.address = \"http://127.0.0.1:9001\"\n" @@ -50,8 +50,8 @@ " bind = 7993,\n" " acceptors = 8\n" " }\n" - "}\n">> -). + "}\n" +>>). %%-------------------------------------------------------------------- %% Setups diff --git a/apps/emqx_gateway/test/emqx_gateway_auth_ct.erl b/apps/emqx_gateway/test/emqx_gateway_auth_ct.erl new file mode 100644 index 000000000..7702a601f --- /dev/null +++ b/apps/emqx_gateway/test/emqx_gateway_auth_ct.erl @@ -0,0 +1,211 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_gateway_auth_ct). + +-compile(nowarn_export_all). +-compile(export_all). + +-behaviour(gen_server). + +%% gen_server callbacks +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3, + format_status/2 +]). + +-import( + emqx_gateway_test_utils, + [ + request/2, + request/3 + ] +). + +-include("emqx_authn.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("emqx/include/emqx_placeholder.hrl"). + +-define(CALL(Msg), gen_server:call(?MODULE, {?FUNCTION_NAME, Msg})). + +-define(HTTP_PORT, 37333). +-define(HTTP_PATH, "/auth"). +-define(GATEWAYS, [coap, lwm2m, mqttsn, stomp, exproto]). + +-define(CONFS, [ + emqx_coap_SUITE, + emqx_lwm2m_SUITE, + emqx_sn_protocol_SUITE, + emqx_stomp_SUITE, + emqx_exproto_SUITE +]). + +-record(state, {}). + +%%------------------------------------------------------------------------------ +%% API +%%------------------------------------------------------------------------------ + +group_names(Auths) -> + [{group, Auth} || Auth <- Auths]. + +init_groups(Suite, Auths) -> + All = emqx_common_test_helpers:all(Suite), + [{Auth, [], All} || Auth <- Auths]. + +start_auth(Name) -> + ?CALL(Name). + +stop_auth(Name) -> + ?CALL(Name). + +start() -> + gen_server:start({local, ?MODULE}, ?MODULE, [], []). + +stop() -> + gen_server:stop(?MODULE). + +%%------------------------------------------------------------------------------ +%% gen_server callbacks +%%------------------------------------------------------------------------------ + +init([]) -> + process_flag(trap_exit, true), + {ok, #state{}}. + +handle_call({start_auth, Name}, _From, State) -> + on_start_auth(Name), + {reply, ok, State}; +handle_call({stop_auth, Name}, _From, State) -> + on_stop_auth(Name), + {reply, ok, State}; +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +format_status(_Opt, Status) -> + Status. + +%%------------------------------------------------------------------------------ +%% Authenticators +%%------------------------------------------------------------------------------ + +on_start_auth(authn_http) -> + %% start test server + {ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, ?HTTP_PATH), + timer:sleep(1000), + + %% set authn for gateway + Setup = fun(Gateway) -> + Path = io_lib:format("/gateway/~ts/authentication", [Gateway]), + {204, _} = request(delete, Path), + {201, _} = request(post, Path, http_auth_config()) + end, + lists:foreach(Setup, ?GATEWAYS), + + %% set handler for test server + Handler = fun(Req0, State) -> + ct:pal("Authn Req:~p~nState:~p~n", [Req0, State]), + case cowboy_req:match_qs([username, password], Req0) of + #{ + username := <<"admin">>, + password := <<"public">> + } -> + Req = cowboy_req:reply(200, Req0); + _ -> + Req = cowboy_req:reply(400, Req0) + end, + {ok, Req, State} + end, + emqx_authn_http_test_server:set_handler(Handler), + + timer:sleep(500). + +on_stop_auth(authn_http) -> + Delete = fun(Gateway) -> + Path = io_lib:format("/gateway/~ts/authentication", [Gateway]), + {204, _} = request(delete, Path) + end, + lists:foreach(Delete, ?GATEWAYS), + ok = emqx_authn_http_test_server:stop(). + +%%------------------------------------------------------------------------------ +%% Configs +%%------------------------------------------------------------------------------ + +http_auth_config() -> + #{ + <<"mechanism">> => <<"password_based">>, + <<"enable">> => <<"true">>, + <<"backend">> => <<"http">>, + <<"method">> => <<"get">>, + <<"url">> => <<"http://127.0.0.1:37333/auth">>, + <<"body">> => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD}, + <<"headers">> => #{<<"X-Test-Header">> => <<"Test Value">>} + }. + +%%------------------------------------------------------------------------------ +%% Helpers +%%------------------------------------------------------------------------------ + +init_gateway_conf() -> + ok = emqx_common_test_helpers:load_config( + emqx_gateway_schema, + merge_conf([X:default_config() || X <- ?CONFS], []) + ). + +merge_conf([Conf | T], Acc) -> + case re:run(Conf, "\s*gateway\\.(.*)", [global, {capture, all_but_first, list}, dotall]) of + {match, [[Content]]} -> + merge_conf(T, [Content | Acc]); + _ -> + merge_conf(T, Acc) + end; +merge_conf([], Acc) -> + erlang:list_to_binary("gateway{" ++ string:join(Acc, ",") ++ "}"). + +with_resource(Init, Close, Fun) -> + Res = + case Init() of + {ok, X} -> X; + Other -> Other + end, + try + Fun(Res) + catch + C:R:S -> + erlang:raise(C, R, S) + after + Close(Res) + end. diff --git a/apps/emqx_gateway/test/emqx_gateway_authn_http_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_authn_SUITE.erl similarity index 54% rename from apps/emqx_gateway/test/emqx_gateway_authn_http_SUITE.erl rename to apps/emqx_gateway/test/emqx_gateway_authn_SUITE.erl index f93995808..6b71f06b5 100644 --- a/apps/emqx_gateway/test/emqx_gateway_authn_http_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_authn_SUITE.erl @@ -14,20 +14,15 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_gateway_authn_http_SUITE). +-module(emqx_gateway_authn_SUITE). -compile(nowarn_export_all). -compile(export_all). --include("emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). --include_lib("emqx/include/emqx_placeholder.hrl"). --define(PATH, [?CONF_NS_ATOM]). - --define(HTTP_PORT, 33333). --define(HTTP_PATH, "/auth"). +-import(emqx_gateway_auth_ct, [init_gateway_conf/0, with_resource/3]). -define(checkMatch(Guard), (fun(Expr) -> @@ -50,44 +45,40 @@ -define(FUNCTOR(Expr), fun() -> Expr end). -define(FUNCTOR(Arg, Expr), fun(Arg) -> Expr end). --define(PROTOCOLS, [coap, lwm2m, 'mqtt-sn', stomp, exproto]). - --define(CONFS, [ - emqx_coap_SUITE, - emqx_lwm2m_SUITE, - emqx_sn_protocol_SUITE, - emqx_stomp_SUITE, - emqx_exproto_SUITE -]). - --define(CASES, [ - fun case_coap/0, - fun case_lwm2m/0, - fun case_emqx_sn/0, - fun case_stomp/0, - fun case_exproto/0 -]). - --define(AUTHNS, [fun set_http_authn/1]). - --type auth_controller() :: fun((start | stop) -> ok). +-define(AUTHNS, [authn_http]). all() -> - emqx_common_test_helpers:all(?MODULE). + emqx_gateway_auth_ct:group_names(?AUTHNS). + +groups() -> + emqx_gateway_auth_ct:init_groups(?MODULE, ?AUTHNS). + +init_per_group(AuthName, Conf) -> + ct:pal("on group start:~p~n", [AuthName]), + {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), + emqx_gateway_auth_ct:start_auth(AuthName), + timer:sleep(500), + Conf. + +end_per_group(AuthName, Conf) -> + ct:pal("on group stop:~p~n", [AuthName]), + emqx_gateway_auth_ct:stop_auth(AuthName), + Conf. init_per_suite(Config) -> - _ = application:load(emqx_conf), - ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, init_conf()), - emqx_common_test_helpers:start_apps([emqx_authn, emqx_gateway]), + emqx_config:erase(gateway), + init_gateway_conf(), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), application:ensure_all_started(cowboy), + emqx_gateway_auth_ct:start(), + timer:sleep(500), Config. -end_per_suite(_) -> - clear_authn(), - ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, <<>>), - emqx_common_test_helpers:stop_apps([emqx_authn, emqx_gateway]), - application:stop(cowboy), - ok. +end_per_suite(Config) -> + emqx_gateway_auth_ct:stop(), + emqx_config:erase(gateway), + emqx_mgmt_api_test_util:end_suite([cowboy, emqx_authn, emqx_gateway]), + Config. init_per_testcase(_Case, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), @@ -99,20 +90,14 @@ end_per_testcase(_Case, Config) -> %%------------------------------------------------------------------------------ %% Tests %%------------------------------------------------------------------------------ -t_authn(_) -> - test_gateway_with_auths(?CASES, ?AUTHNS). -%%------------------------------------------------------------------------------ -%% Tests -%%------------------------------------------------------------------------------ - -case_coap() -> +t_case_coap(_) -> Login = fun(URI, Checker) -> - Action = fun(Channel) -> - Req = emqx_coap_SUITE:make_req(post), - Checker(emqx_coap_SUITE:do_request(Channel, URI, Req)) - end, - emqx_coap_SUITE:do(Action) + Action = fun(Channel) -> + Req = emqx_coap_SUITE:make_req(post), + Checker(emqx_coap_SUITE:do_request(Channel, URI, Req)) + end, + emqx_coap_SUITE:do(Action) end, Prefix = emqx_coap_SUITE:mqtt_prefix(), RightUrl = @@ -128,7 +113,7 @@ case_coap() -> -record(coap_content, {content_format, payload = <<>>}). -case_lwm2m() -> +t_case_lwm2m(_) -> MsgId = 12, Mod = emqx_lwm2m_SUITE, Epn = "urn:oma:lwm2m:oma:3", @@ -174,9 +159,18 @@ case_lwm2m() -> -define(SN_CONNACK, 16#05). -case_emqx_sn() -> +t_case_emqx_sn(_) -> Mod = emqx_sn_protocol_SUITE, - Login = fun(Expect) -> + Login = fun(Username, Password, Expect) -> + RawCfg = emqx_conf:get_raw([gateway, mqttsn], #{}), + NewCfg = RawCfg#{ + <<"clientinfo_override">> => #{ + <<"username">> => Username, + <<"password">> => Password + } + }, + emqx_gateway_conf:update_gateway(mqttsn, NewCfg), + with_resource( ?FUNCTOR(gen_udp:open(0, [binary])), ?FUNCTOR(Socket, gen_udp:close(Socket)), @@ -186,20 +180,11 @@ case_emqx_sn() -> end ) end, - Login(<<>>), - - RawCfg = emqx_conf:get_raw([gateway, mqttsn], #{}), - NewCfg = RawCfg#{ - <<"clientinfo_override">> => #{ - <<"username">> => <<"admin">>, - <<"password">> => <<"public">> - } - }, - emqx_gateway_conf:update_gateway(mqttsn, NewCfg), - Login(<<3, ?SN_CONNACK, 0>>), + Login(<<"badadmin">>, <<"badpassowrd">>, <<>>), + Login(<<"admin">>, <<"public">>, <<3, ?SN_CONNACK, 0>>), ok. -case_stomp() -> +t_case_stomp(_) -> Mod = emqx_stomp_SUITE, Login = fun(Username, Password, Checker) -> Fun = fun(Sock) -> @@ -237,7 +222,7 @@ case_stomp() -> ok. -case_exproto() -> +t_case_exproto(_) -> Mod = emqx_exproto_SUITE, SvrMod = emqx_exproto_echo_svr, Svrs = SvrMod:start(), @@ -266,114 +251,3 @@ case_exproto() -> Login(<<"bad">>, <<"bad">>, SvrMod:frame_connack(1)), SvrMod:stop(Svrs), ok. - -%%------------------------------------------------------------------------------ -%% Authenticators -%%------------------------------------------------------------------------------ - -raw_http_auth_config() -> - #{ - mechanism => <<"password_based">>, - enable => <<"true">>, - - backend => <<"http">>, - method => <<"get">>, - url => <<"http://127.0.0.1:33333/auth">>, - body => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD}, - headers => #{<<"X-Test-Header">> => <<"Test Value">>} - }. - -set_http_authn(start) -> - {ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, ?HTTP_PATH), - - AuthConfig = raw_http_auth_config(), - - Set = fun(Protocol) -> - Chain = emqx_authentication:global_chain(Protocol), - emqx_authn_test_lib:delete_authenticators([authentication], Chain), - - {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, Chain, AuthConfig} - ), - - {ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(Chain) - end, - lists:foreach(Set, ?PROTOCOLS), - - Handler = fun(Req0, State) -> - ct:pal("Req:~p State:~p~n", [Req0, State]), - case cowboy_req:match_qs([username, password], Req0) of - #{ - username := <<"admin">>, - password := <<"public">> - } -> - Req = cowboy_req:reply(200, Req0); - _ -> - Req = cowboy_req:reply(400, Req0) - end, - {ok, Req, State} - end, - emqx_authn_http_test_server:set_handler(Handler); -set_http_authn(stop) -> - ok = emqx_authn_http_test_server:stop(). - -clear_authn() -> - Clear = fun(Protocol) -> - Chain = emqx_authentication:global_chain(Protocol), - emqx_authn_test_lib:delete_authenticators([authentication], Chain) - end, - lists:foreach(Clear, ?PROTOCOLS). - -%%------------------------------------------------------------------------------ -%% Helpers -%%------------------------------------------------------------------------------ --spec test_gateway_with_auths(_, list(auth_controller())) -> ok. -test_gateway_with_auths(Gateways, Authenticators) -> - Cases = [{Auth, Gateways} || Auth <- Authenticators], - test_gateway_with_auths(Cases). - -test_gateway_with_auths([{Auth, Gateways} | T]) -> - {name, Name} = erlang:fun_info(Auth, name), - ct:pal("start auth:~p~n", [Name]), - Auth(start), - lists:foreach( - fun(Gateway) -> - {name, GwName} = erlang:fun_info(Gateway, name), - ct:pal("start gateway case:~p~n", [GwName]), - Gateway() - end, - Gateways - ), - ct:pal("stop auth:~p~n", [Name]), - Auth(stop), - test_gateway_with_auths(T); -test_gateway_with_auths([]) -> - ok. - -init_conf() -> - merge_conf([X:default_config() || X <- ?CONFS], []). - -merge_conf([Conf | T], Acc) -> - case re:run(Conf, "\s*gateway\\.(.*)", [global, {capture, all_but_first, list}, dotall]) of - {match, [[Content]]} -> - merge_conf(T, [Content | Acc]); - _ -> - merge_conf(T, Acc) - end; -merge_conf([], Acc) -> - erlang:list_to_binary("gateway{" ++ string:join(Acc, ",") ++ "}"). - -with_resource(Init, Close, Fun) -> - Res = - case Init() of - {ok, X} -> X; - Other -> Other - end, - try - Fun(Res) - catch - C:R:S -> - Close(Res), - erlang:raise(C, R, S) - end. diff --git a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl index 06e7ce318..5dbcd8df1 100644 --- a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl @@ -849,3 +849,8 @@ parse(Data) -> }, Parser = emqx_stomp_frame:initial_parse_state(ProtoEnv), emqx_stomp_frame:parse(Data, Parser). + +get_field(command, #stomp_frame{command = Command}) -> + Command; +get_field(body, #stomp_frame{body = Body}) -> + Body.