diff --git a/.travis.yml b/.travis.yml index 8c4432d..697727f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ cache: - $HOME/.cache/rebar3 - _plt install: "true" -script: "make testclean dist coveralls" +script: "make travis" branches: only: - master diff --git a/CHANGELOG.md b/CHANGELOG.md index cd2ba2a..4ce321e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ Below is a non-exhaustive list of changes between `gen_rpc` versions. +## 2.0.1 + +- Support external node driver/port discovery using an `{M, F}` tuple instead of + a map in `client_config_per_node`. + ## 2.0.0 This release boasts a major rengineer/refactor of `gen_rpc` that includes quite a few new features: diff --git a/Makefile b/Makefile index 8629880..07cefde 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ # distclean: rebar3 as dev do clean -a # and explicitly delete other build artifacts # test: rebar3 as test do ct -v, cover -# coveralls: Send coverage to coveralls.io +# travis: Run the proper tests/coveralls in travis # dialyzer: rebar3 as test do dialyzer # xref: rebar3 as dev do xref # dist: rebar3 as test do compile, ct -v -c, xref, dialyzer, cover @@ -26,7 +26,7 @@ .DEFAULT_GOAL := all # Build targets -.PHONY: all test dialyzer xref spec dist coveralls +.PHONY: all test dialyzer xref spec dist travis # Run targets .PHONY: shell shell-master shell-slave @@ -62,8 +62,6 @@ OTP_RELEASE = $(shell escript otp-release.escript) PLT_FILE = $(CURDIR)/_plt/rebar3_$(OTP_RELEASE)_plt -COVERDATA = $(CURDIR)/_build/test/ct.coverdata - # ====================== # Integration test logic # ====================== @@ -103,7 +101,7 @@ spec: dialyzer dist: $(REBAR) test @REBAR_PROFILE=dev $(REBAR) do dialyzer, xref -coveralls: $(COVERDATA) +travis: testclean dist @REBAR_PROFILE=test $(REBAR) do coveralls send || true # ============================================================================= @@ -146,8 +144,5 @@ $(REBAR): tags: find src _build/default/lib -name "*.[he]rl" -print | etags - -$(COVERDATA): - @$(MAKE) test - $(PLT_FILE): @REBAR_PROFILE=dev $(REBAR) do dialyzer || true diff --git a/README.md b/README.md index 88566e3..6429f32 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,9 @@ Finally, start a couple of nodes to test it out: It should be either `tcp` or `ssl`. - `client_config_per_node`: A map of `Node => {Driver, Port}` or `Node => Port` that instructs `gen_rpc` on the `Port` - and/or `Driver` to use when connecting to a `Node`. + and/or `Driver` to use when connecting to a `Node`. If you prefer to use an external discovery service to map `Nodes` + to `{Driver, Port}` tuples, instead of the map, you'll need to define a `{Module, Function}` tuple instead with a function + that takes the `Node` as its single argument, consumes the external discovery service and returns a `{Driver, Port}` tuple. - `rpc_module_control`: Set it to `blacklist` to define a list of modules that will not be exposed to `gen_rpc` or to `whitelist` to define the list of modules that will be exposed to `gen_rpc`. Set it to `disabled` to disable this feature. diff --git a/rebar.config.script b/rebar.config.script index bf34778..837468f 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -2,7 +2,7 @@ %%% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et: %%% -Coveralls = {coveralls, {git, "https://github.com/markusn/coveralls-erl", "master"}}, +Coveralls = {coveralls, {git, "https://github.com/markusn/coveralls-erl", {branch, "master"}}}, case os:getenv("TRAVIS") of "true" -> diff --git a/src/gen_rpc.app.src b/src/gen_rpc.app.src index 4c420d0..14aa7cb 100644 --- a/src/gen_rpc.app.src +++ b/src/gen_rpc.app.src @@ -15,6 +15,7 @@ {files, [ "LICENSE", "README.md", + "CHANGELOG.md", "include", "package.exs", "rebar.config", @@ -41,6 +42,9 @@ %% Describe it as node_name => {driver, port} %% Or node_name => driver to use the default port %% for the specified client driver + %% If you have an external service that allows discovery + %% of port/node mappings, you can replace the map + %% with an {M,F} tuple and it will be called instead {client_config_per_node, #{}}, %% List of modules available for RPC %% This is either whitelist, blacklist or disabled diff --git a/src/gen_rpc_client.erl b/src/gen_rpc_client.erl index 50b958e..580a2cf 100644 --- a/src/gen_rpc_client.erl +++ b/src/gen_rpc_client.erl @@ -227,26 +227,31 @@ sbcast(Nodes, Name, Msg) when is_list(Nodes), is_atom(Name) -> %%% =================================================== init({Node}) -> ok = gen_rpc_helper:set_optimal_process_flags(), - {Driver, Port} = gen_rpc_helper:get_client_config_per_node(Node), - {DriverMod, DriverClosed, DriverError} = gen_rpc_helper:get_client_driver_options(Driver), - ?log(info, "event=initializing_client driver=~s node=\"~s\" port=~B", [Driver, Node, Port]), - case DriverMod:connect(Node, Port) of - {ok, Socket} -> - case DriverMod:authenticate_server(Socket) of - ok -> - {ok, #state{socket=Socket, - driver=Driver, - driver_mod=DriverMod, - driver_closed=DriverClosed, - driver_error=DriverError}, gen_rpc_helper:get_inactivity_timeout(?MODULE)}; - {error, ReasonTuple} -> - ?log(error, "event=client_authentication_failed driver=~s reason=\"~p\"", [Driver, ReasonTuple]), - {stop, ReasonTuple} - end; - {error, {_Class,Reason}} -> - %% This should be badtcp but to conform with - %% the RPC library we return badrpc - {stop, {badrpc,Reason}} + case gen_rpc_helper:get_client_config_per_node(Node) of + {error, {Class,Reason}} -> + ?log(error, "event=external_source_error action=falling_back_to_local reason=\"~s:~p\"", [Class, Reason]), + {stop, {badrpc, {external_source_error, Reason}}}; + {Driver, Port} -> + {DriverMod, DriverClosed, DriverError} = gen_rpc_helper:get_client_driver_options(Driver), + ?log(info, "event=initializing_client driver=~s node=\"~s\" port=~B", [Driver, Node, Port]), + case DriverMod:connect(Node, Port) of + {ok, Socket} -> + case DriverMod:authenticate_server(Socket) of + ok -> + {ok, #state{socket=Socket, + driver=Driver, + driver_mod=DriverMod, + driver_closed=DriverClosed, + driver_error=DriverError}, gen_rpc_helper:get_inactivity_timeout(?MODULE)}; + {error, ReasonTuple} -> + ?log(error, "event=client_authentication_failed driver=~s reason=\"~p\"", [Driver, ReasonTuple]), + {stop, ReasonTuple} + end; + {error, {_Class,Reason}} -> + %% This should be badtcp but to conform with + %% the RPC library we return badrpc + {stop, {badrpc,Reason}} + end end. %% This is the actual CALL handler diff --git a/src/gen_rpc_helper.erl b/src/gen_rpc_helper.erl index 93c4f49..389e995 100644 --- a/src/gen_rpc_helper.erl +++ b/src/gen_rpc_helper.erl @@ -139,21 +139,20 @@ get_client_driver_options(Driver) when is_atom(Driver) -> ErrorMsg = list_to_atom(lists:flatten([DriverStr, "_error"])), {DriverMod, ClosedMsg, ErrorMsg}. --spec get_client_config_per_node(atom()) -> {atom(), inet:port_number()}. +-spec get_client_config_per_node(atom()) -> {atom(), inet:port_number()} | {error, {atom(), term()}}. get_client_config_per_node(Node) when is_atom(Node) -> {ok, NodeConfig} = application:get_env(?APP, client_config_per_node), - case maps:find(Node, NodeConfig) of - error -> - {ok, Driver} = application:get_env(?APP, default_client_driver), - DriverStr = erlang:atom_to_list(Driver), - PortSetting = list_to_atom(lists:flatten([DriverStr, "_client_port"])), - {ok, Port} = application:get_env(?APP, PortSetting), - {Driver, Port}; - {ok, Port} when is_integer(Port) -> - {ok, Driver} = application:get_env(?APP, default_client_driver), - {Driver, Port}; - {ok, {Driver,Port}} -> - {Driver, Port} + case NodeConfig of + {M, F} -> + try + {Driver, Port} = M:F(Node), + {Driver, Port} + catch + Class:Reason -> + {error, {Class,Reason}} + end; + NodeConfig -> + get_client_config_from_map(Node, NodeConfig) end. -spec get_connect_timeout() -> timeout(). @@ -224,6 +223,21 @@ get_async_call_inactivity_timeout() -> %%% =================================================== %%% Private functions %%% =================================================== +get_client_config_from_map(Node, NodeConfig) -> + case maps:find(Node, NodeConfig) of + error -> + {ok, Driver} = application:get_env(?APP, default_client_driver), + DriverStr = erlang:atom_to_list(Driver), + PortSetting = list_to_atom(lists:flatten([DriverStr, "_client_port"])), + {ok, Port} = application:get_env(?APP, PortSetting), + {Driver, Port}; + {ok, {Driver,Port}} -> + {Driver, Port}; + {ok, Port} -> + {ok, Driver} = application:get_env(?APP, default_client_driver), + {Driver, Port} + end. + hybrid_proplist_compare({K1,_V1}, {K2,_V2}) -> K1 =< K2; diff --git a/test/ct/remote_SUITE.erl b/test/ct/remote_SUITE.erl index dccd304..6afa118 100644 --- a/test/ct/remote_SUITE.erl +++ b/test/ct/remote_SUITE.erl @@ -72,6 +72,16 @@ init_per_testcase(rpc_module_blacklist, Config) -> ok = rpc:call(?SLAVE, application, set_env, [?APP, rpc_module_control, blacklist]), Config; +init_per_testcase(external_client_config_source, Config) -> + ok = gen_rpc_test_helper:restart_application(), + ok = gen_rpc_test_helper:start_master(ssl), + ok = gen_rpc_test_helper:start_slave(tcp), + %% No need to restore original setting with an + %% end_per_testcase since this setting gets overwritten + %% upon every application restart + ok = application:set_env(?APP, client_config_per_node, {?MODULE, external_client_config_fun}), + Config; + init_per_testcase(_OtherTest, Config) -> ok = gen_rpc_test_helper:restart_application(), Driver = gen_rpc_test_helper:get_driver_from_config(Config), @@ -266,6 +276,9 @@ rpc_module_blacklist(_Config) -> {badrpc, unauthorized} = gen_rpc:call(?SLAVE, erlang, node), 60000 = gen_rpc:call(?SLAVE, timer, seconds, [60]). +external_client_config_source(_Config) -> + {_Mega, _Sec, _Micro} = gen_rpc:call(?SLAVE, os, timestamp). + wrong_cookie(_Config) -> OrigCookie = erlang:get_cookie(), RandCookie = list_to_atom(atom_to_list(OrigCookie) ++ "123"), @@ -313,3 +326,11 @@ interleaved_call_executor(Num) when is_integer(Num) -> ok = timer:sleep((3 - Num) * 1000), %% Then return the number Num. + +%% Enable TCP only communication to the slave when testing +%% the external client config source +%% This should force communication with the slave +%% over TCP even on the SSL group +external_client_config_fun(?SLAVE) -> + {tcp, ?SLAVE_PORT}. + diff --git a/test/gen_rpc_test_helper.erl b/test/gen_rpc_test_helper.erl index ca3dbf8..e5f3d06 100644 --- a/test/gen_rpc_test_helper.erl +++ b/test/gen_rpc_test_helper.erl @@ -148,6 +148,7 @@ get_test_functions(Module) -> ({interleaved_call_proc,_}) -> false; ({interleaved_call_executor,_}) -> false; ({interleaved_call_loop,_}) -> false; + ({external_client_config_fun,_}) -> false; %% Multi RPC ({spawn_listener,_}) -> false; ({spawn_listener2,_}) -> false;