From e59eacb891634273a599a0dfddc0b6be14118ca6 Mon Sep 17 00:00:00 2001 From: Zaiming Shi Date: Mon, 19 Apr 2021 21:32:09 +0200 Subject: [PATCH] test(webhook): test ipv6 for real --- .../test/emqx_web_hook_SUITE.erl | 148 ++++++++++++------ apps/emqx_web_hook/test/http_server.erl | 56 +++---- 2 files changed, 123 insertions(+), 81 deletions(-) diff --git a/apps/emqx_web_hook/test/emqx_web_hook_SUITE.erl b/apps/emqx_web_hook/test/emqx_web_hook_SUITE.erl index ca2bbec4d..b9d203a54 100644 --- a/apps/emqx_web_hook/test/emqx_web_hook_SUITE.erl +++ b/apps/emqx_web_hook/test/emqx_web_hook_SUITE.erl @@ -31,55 +31,74 @@ %%-------------------------------------------------------------------- all() -> - [{group, http}, - {group, https}, - {group, ipv6http}, - {group, ipv6https}]. + [ {group, http} + , {group, https} + , {group, ipv6http} + , {group, ipv6https} + , {group, all} + ]. groups() -> - Cases = emqx_ct:all(?MODULE), - [{http, [sequence], Cases}, - {https, [sequence], Cases}, - {ipv6http, [sequence], Cases}, - {ipv6https, [sequence], Cases}]. + Cases = [test_full_flow], + [ {http, [sequence], Cases} + , {https, [sequence], Cases} + , {ipv6http, [sequence], Cases} + , {ipv6https, [sequence], Cases} + , {all, [sequence], emqx_ct:all(?MODULE)} + ]. + +start_apps(F) -> emqx_ct_helpers:start_apps(apps(), F). init_per_group(Name, Config) -> application:ensure_all_started(emqx_management), set_special_cfgs(), - case Name of - http -> - emqx_ct_helpers:start_apps(apps(), fun set_special_configs_http/1); - https -> - emqx_ct_helpers:start_apps(apps(), fun set_special_configs_https/1); - ipv6http -> - emqx_ct_helpers:start_apps(apps(), fun set_special_configs_ipv6_http/1); - ipv6https -> - emqx_ct_helpers:start_apps(apps(), fun set_special_configs_ipv6_https/1) - end, - Config. + BasePort = + case Name of + all -> 8801; + http -> 8811; + https -> 8821; + ipv6http -> 8831; + ipv6https -> 8841 + end, + CF = case Name of + all -> fun set_special_configs_http/1; + http -> fun set_special_configs_http/1; + https -> fun set_special_configs_https/1; + ipv6http -> fun set_special_configs_ipv6_http/1; + ipv6https -> fun set_special_configs_ipv6_https/1 + end, + start_apps(fun(_) -> CF(BasePort) end), + Opts = case atom_to_list(Name) of + "ipv6" ++ _ -> [{ip, {0,0,0,0,0,0,0,1}}, inet6]; + _ -> [inet] + end, + [{base_port, BasePort}, {transport_opts, Opts} | Config]. end_per_group(_Name, Config) -> emqx_ct_helpers:stop_apps(apps()), Config. -set_special_configs_http(_) -> - application:set_env(emqx_web_hook, url, "http://127.0.0.1:9999"). +set_special_configs_http(Port) -> + application:set_env(emqx_web_hook, url, "http://127.0.0.1:" ++ integer_to_list(Port)). -set_special_configs_https(_) -> +set_special_configs_https(Port) -> + set_ssl_configs(), + application:set_env(emqx_web_hook, url, "https://127.0.0.1:" ++ integer_to_list(Port+1)). + +set_special_configs_ipv6_http(Port) -> + application:set_env(emqx_web_hook, url, "http://[::1]:" ++ integer_to_list(Port)). + +set_special_configs_ipv6_https(Port) -> + set_ssl_configs(), + application:set_env(emqx_web_hook, url, "https://[::1]:" ++ integer_to_list(Port+1)). + +set_ssl_configs() -> Path = emqx_ct_helpers:deps_path(emqx_web_hook, "test/emqx_web_hook_SUITE_data/"), SslOpts = [{keyfile, Path ++ "/client-key.pem"}, {certfile, Path ++ "/client-cert.pem"}, {cacertfile, Path ++ "/ca.pem"}], application:set_env(emqx_web_hook, ssl, true), - application:set_env(emqx_web_hook, ssloptions, SslOpts), - application:set_env(emqx_web_hook, url, "https://127.0.0.1:8888"). - -set_special_configs_ipv6_http(_) -> - application:set_env(emqx_web_hook, url, "http://[::1]:9999"). - -set_special_configs_ipv6_https(N) -> - set_special_configs_https(N), - application:set_env(emqx_web_hook, url, "https://[::1]:8888"). + application:set_env(emqx_web_hook, ssloptions, SslOpts). set_special_cfgs() -> AllRules = [{"message.acked", "{\"action\": \"on_message_acked\"}"}, @@ -95,34 +114,63 @@ set_special_cfgs() -> {"client.connack", "{\"action\": \"on_client_connack\"}"}, {"client.connect", "{\"action\": \"on_client_connect\"}"}], application:set_env(emqx_web_hook, rules, AllRules). + %%-------------------------------------------------------------------- %% Test cases %%-------------------------------------------------------------------- -t_valid(Config) -> - {ok, ServerPid} = http_server:start_link(), +test_full_flow(Config) -> + [_|_] = Opts = proplists:get_value(transport_opts, Config), + BasePort = proplists:get_value(base_port, Config), + Tester = self(), + {ok, ServerPid} = http_server:start_link(Tester, BasePort, Opts), + receive {ServerPid, ready} -> ok + after 1000 -> error(timeout) + end, application:set_env(emqx_web_hook, headers, [{"k1","K1"}, {"k2", "K2"}]), {ok, C} = emqtt:start_link([ {clientid, <<"simpleClient">>} , {proto_ver, v5} , {keepalive, 60} ]), - try - {ok, _} = emqtt:connect(C), - emqtt:subscribe(C, <<"TopicA">>, qos2), - emqtt:publish(C, <<"TopicA">>, <<"Payload...">>, qos2), - emqtt:unsubscribe(C, <<"TopicA">>), - emqtt:disconnect(C), - timer:sleep(100), - [begin - Maps = emqx_json:decode(P, [return_maps]), - validate_hook_resp(Maps), - validate_hook_headers(Headers) - end - || {{P, _Bool}, Headers} <- http_server:get_received_data()] + try + do_test_full_flow(C) after - http_server:stop(ServerPid) - end, - Config. + Ref = erlang:monitor(process, ServerPid), + http_server:stop(ServerPid), + receive {'DOWN', Ref, _, _, _} -> ok + after 5000 -> error(timeout) + end + end. + +do_test_full_flow(C) -> + {ok, _} = emqtt:connect(C), + {ok, _, _} = emqtt:subscribe(C, <<"TopicA">>, qos2), + {ok, _} = emqtt:publish(C, <<"TopicA">>, <<"Payload...">>, qos2), + {ok, _, _} = emqtt:unsubscribe(C, <<"TopicA">>), + emqtt:disconnect(C), + validate_params_and_headers(undefined). + +validate_params_and_headers(ClientState) -> + receive + {http_server, {Params0, _Bool}, Headers} -> + Params = emqx_json:decode(Params0, [return_maps]), + validate_hook_resp(Params), + validate_hook_headers(Headers), + case maps:get(<<"action">>, Params) of + <<"session_terminated">> -> + ok; + <<"client_connect">> -> + validate_params_and_headers(connected); + _ -> + validate_params_and_headers(ClientState) %% continue looping + end + after + 5000 -> + case ClientState =:= undefined of + true -> error("client_was_never_connected"); + false -> error("terminate_action_is_not_received_in_time") + end + end. t_check_hooked(_) -> {ok, Rules} = application:get_env(emqx_web_hook, rules), diff --git a/apps/emqx_web_hook/test/http_server.erl b/apps/emqx_web_hook/test/http_server.erl index dc3436df5..791f725d1 100644 --- a/apps/emqx_web_hook/test/http_server.erl +++ b/apps/emqx_web_hook/test/http_server.erl @@ -9,30 +9,25 @@ -module(http_server). -behaviour(gen_server). --export([start_link/0]). --export([get_received_data/0]). +-export([start_link/3]). -export([stop/1]). -export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, init/1, init/2, terminate/2]). --define(HTTP_PORT, 9999). --define(HTTPS_PORT, 8888). --record(state, {}). +-record(state, {parent :: pid()}). %%-------------------------------------------------------------------- %% APIs %%-------------------------------------------------------------------- -start_link() -> +start_link(Parent, BasePort, Opts) -> stop_http(), stop_https(), timer:sleep(100), - gen_server:start_link(?MODULE, [], []). + gen_server:start_link(?MODULE, {Parent, BasePort, Opts}, []). -init([]) -> - EtsOptions = [named_table, public, set, {write_concurrency, true}, - {read_concurrency, true}], - emqx_web_hook_http_test = ets:new(emqx_web_hook_http_test, EtsOptions), - ok = start_http(?HTTP_PORT), - ok = start_https(?HTTPS_PORT), - {ok, #state{}}. +init({Parent, BasePort, Opts}) -> + ok = start_http(Parent, [{port, BasePort} | Opts]), + ok = start_https(Parent, [{port, BasePort + 1} | Opts]), + Parent ! {self(), ready}, + {ok, #state{parent = Parent}}. handle_call(_Request, _From, State) -> {reply, ignored, State}. @@ -50,9 +45,6 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -get_received_data() -> - ets:tab2list(emqx_web_hook_http_test). - stop(Pid) -> ok = gen_server:stop(Pid). @@ -60,37 +52,39 @@ stop(Pid) -> %% Callbacks %%-------------------------------------------------------------------- -start_http(Port) -> - {ok, _Pid1} = cowboy:start_clear(http, [{port, Port}], #{ - env => #{dispatch => compile_router()} +start_http(Parent, Opts) -> + {ok, _Pid1} = cowboy:start_clear(http, Opts, #{ + env => #{dispatch => compile_router(Parent)} }), - io:format(standard_error, "[TEST LOG] Start http server on 9999 successfully!~n", []). + Port = proplists:get_value(port, Opts), + io:format(standard_error, "[TEST LOG] Start http server on ~p successfully!~n", [Port]). -start_https(Port) -> +start_https(Parent, Opts) -> Path = emqx_ct_helpers:deps_path(emqx_web_hook, "test/emqx_web_hook_SUITE_data/"), SslOpts = [{keyfile, Path ++ "/server-key.pem"}, {cacertfile, Path ++ "/ca.pem"}, {certfile, Path ++ "/server-cert.pem"}], - {ok, _Pid2} = cowboy:start_tls(https, [{port, Port}] ++ SslOpts, - #{env => #{dispatch => compile_router()}}), - io:format(standard_error, "[TEST LOG] Start https server on 8888 successfully!~n", []). + {ok, _Pid2} = cowboy:start_tls(https, Opts ++ SslOpts, + #{env => #{dispatch => compile_router(Parent)}}), + Port = proplists:get_value(port, Opts), + io:format(standard_error, "[TEST LOG] Start https server on ~p successfully!~n", [Port]). stop_http() -> cowboy:stop_listener(http), - io:format("[TEST LOG] Stopped http server on 9999"). + io:format("[TEST LOG] Stopped http server"). stop_https() -> cowboy:stop_listener(https), - io:format("[TEST LOG] Stopped https server on 8888"). + io:format("[TEST LOG] Stopped https server"). -compile_router() -> +compile_router(Parent) -> {ok, _} = application:ensure_all_started(cowboy), cowboy_router:compile([ - {'_', [{"/", ?MODULE, #{}}]} + {'_', [{"/", ?MODULE, #{parent => Parent}}]} ]). -init(Req, State) -> +init(Req, #{parent := Parent} = State) -> Method = cowboy_req:method(Req), Headers = cowboy_req:headers(Req), [Params] = case Method of @@ -99,7 +93,7 @@ init(Req, State) -> {ok, PostVals, _} = cowboy_req:read_urlencoded_body(Req), PostVals end, - ets:insert(emqx_web_hook_http_test, {Params, Headers}), + Parent ! {?MODULE, Params, Headers}, {ok, reply(Req, ok), State}. reply(Req, ok) ->