From 0ce5eb1bbb73cd79449c18be6fe97df4b8bdc892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20P=C3=A9dron?= Date: Fri, 1 Apr 2016 16:30:18 +0200 Subject: [PATCH] rabbit_ct_helpers: Use arbitrary node name and TCP ports Furthermore, if the node fails to start, try again with another name and set of TCP ports until we find a setup which boots fine. This allows to run the testsuite while there is another unrelated RabbitMQ node already running. Moreover, this avoids unrelated AMQP clients to mess with the testsuite node. --- deps/amqp_client/test/rabbit_ct_helpers.erl | 224 +++++++++++++------- deps/amqp_client/test/system_SUITE.erl | 30 ++- 2 files changed, 174 insertions(+), 80 deletions(-) diff --git a/deps/amqp_client/test/rabbit_ct_helpers.erl b/deps/amqp_client/test/rabbit_ct_helpers.erl index 439e775ef6..aafcc37d21 100644 --- a/deps/amqp_client/test/rabbit_ct_helpers.erl +++ b/deps/amqp_client/test/rabbit_ct_helpers.erl @@ -32,6 +32,12 @@ -define(DEFAULT_USER, "guest"). -define(UNAUTHORIZED_USER, "test_user_no_perm"). -define(SSL_CERT_PASSWORD, "test"). +-define(TCP_PORTS_LIST, [ + tcp_port_amqp, + tcp_port_amqp_tls, + tcp_port_mgmt, + tcp_port_erlang_dist + ]). %% ------------------------------------------------------------------- %% Testsuite internal helpers. @@ -43,32 +49,30 @@ log_environment() -> [io_lib:format(" ~s~n", [V]) || V <- Vars]]). run_setup_steps(Config) -> - Checks = [ + Steps = [ fun ensure_amqp_client_srcdir/1, fun ensure_amqp_client_depsdir/1, fun ensure_make_cmd/1, fun ensure_rabbitmqctl_cmd/1, - fun ensure_nodename/1, fun ensure_ssl_certs/1, - fun write_config_file/1, fun start_rabbitmq_node/1, fun create_unauthorized_user/1 ], - run_steps(Checks, Config). + run_steps(Config, Steps). run_teardown_steps(Config) -> - Checks = [ + Steps = [ fun delete_unauthorized_user/1, fun stop_rabbitmq_node/1 ], - run_steps(Checks, Config). + run_steps(Config, Steps). -run_steps([Check | Rest], Config) -> - case Check(Config) of +run_steps(Config, [Step | Rest]) -> + case Step(Config) of {skip, _} = Error -> Error; - Config1 -> run_steps(Rest, Config1) + Config1 -> run_steps(Config1, Rest) end; -run_steps([], Config) -> +run_steps(Config, []) -> Config. ensure_amqp_client_srcdir(Config) -> @@ -173,47 +177,6 @@ ensure_rabbitmqctl_cmd(Config) -> end end. -ensure_nodename(Config) -> - Nodename = case get_config(Config, rmq_nodename) of - undefined -> - case os:getenv("RABBITMQ_NODENAME") of - false -> - Rabbitmqctl = ?config(rabbitmqctl_cmd, Config), - Cmd = Rabbitmqctl ++ " status 2>/dev/null |" ++ - " awk '/^Status of node/ { print $4; exit; }'", - case run_cmd_and_capture_output(Cmd) of - {ok, Output} -> - list_to_atom( - string:strip(Output, both, $\n)); - {error, _} -> - false - end; - N -> - list_to_atom(string:strip(N)) - end; - N -> - N - end, - case Nodename of - false -> - {skip, - "RabbitMQ nodename required, " ++ - "please set RABBITMQ_NODENAME or 'rmq_nodename' " ++ - "in ct config"}; - _ -> - %% We test if the nodename is available. This test is - %% incomplete because a node with that name could be running - %% but with another cookie (so the ping will fail). But - %% that's ok, it covers the most common situation. RabbitMQ - %% will fail to start later for the other situation. - case net_adm:ping(Nodename) of - pang -> set_config(Config, {rmq_nodename, Nodename}); - pong -> {skip, - "A node with the name '" ++ atom_to_list(Nodename) ++ - "' is already running"} - end - end. - ensure_ssl_certs(Config) -> Make = ?config(make_cmd, Config), DepsDir = ?config(amqp_client_depsdir, Config), @@ -227,7 +190,6 @@ ensure_ssl_certs(Config) -> true -> %% Add SSL certs to the broker configuration. Config1 = merge_app_env(Config, rabbit, [ - {ssl_listeners, [5671]}, {ssl_options, [ {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}, {certfile, filename:join([CertsDir, "server", "cert.pem"])}, @@ -240,29 +202,54 @@ ensure_ssl_certs(Config) -> {skip, "Failed to create SSL certificates"} end. -write_config_file(Config) -> - %% Prepare a RabbitMQ configuration. - PrivDir = ?config(priv_dir, Config), - ConfigFile = filename:join(PrivDir, "rabbitmq"), - ErlangConfig = ?config(erlang_node_config, Config), - Ret = file:write_file(ConfigFile ++ ".config", - io_lib:format("% vim:ft=erlang:~n~n~p.~n", - [ErlangConfig])), - case Ret of - ok -> - set_config(Config, {erlang_node_config_filename, ConfigFile}); - {error, Reason} -> - {skip, "Failed to create Erlang node config file \"" ++ - ConfigFile ++ "\": " ++ file:format_error(Reason)} - end. +%% To start a RabbitMQ node, we need to: +%% 1. Pick TCP port numbers +%% 2. Generate a node name +%% 3. Write a configuration file +%% 4. Start the node +%% +%% If this fails (usually because the node name is taken or a TCP port +%% is already in use), we start again with another set of TCP ports. The +%% node name is derived from the AMQP TCP port so a new node name is +%% generated. start_rabbitmq_node(Config) -> + Attempts = case get_config(Config, rmq_failed_boot_attempts) of + undefined -> 0; + N -> N + end, + Config1 = init_tcp_port_numbers(Config), + Config2 = init_nodename(Config1), + Config3 = init_config_filename(Config2), + Steps = [ + fun write_config_file/1, + fun do_start_rabbitmq_node/1 + ], + case run_steps(Config3, Steps) of + {skip, _} = Error when Attempts >= 50 -> + %% It's unlikely we'll ever succeed to start RabbitMQ. + Error; + {skip, _} -> + %% Try again with another TCP port numbers base. + Config4 = move_nonworking_nodedir_away(Config3), + Config5 = set_config(Config4, + {rmq_failed_boot_attempts, Attempts + 1}), + start_rabbitmq_node(Config5); + Config4 -> + Config4 + end. + +do_start_rabbitmq_node(Config) -> Make = ?config(make_cmd, Config), SrcDir = ?config(amqp_client_srcdir, Config), PrivDir = ?config(priv_dir, Config), + Nodename = ?config(rmq_nodename, Config), + DistPort = ?config(tcp_port_erlang_dist, Config), ConfigFile = ?config(erlang_node_config_filename, Config), Cmd = Make ++ " -C " ++ SrcDir ++ make_verbosity() ++ - " start-background-node start-rabbit-on-node" ++ + " start-background-broker" ++ + " RABBITMQ_NODENAME='" ++ atom_to_list(Nodename) ++ "'" ++ + " RABBITMQ_DIST_PORT='" ++ integer_to_list(DistPort) ++ "'" ++ " RABBITMQ_CONFIG_FILE='" ++ ConfigFile ++ "'" ++ " TEST_TMPDIR='" ++ PrivDir ++ "'", case run_cmd(Cmd) of @@ -275,10 +262,101 @@ start_rabbitmq_node(Config) -> false -> {skip, "Failed to initialize RabbitMQ"} end. +init_tcp_port_numbers(Config) -> + %% If there is no TCP port numbers base previously calculated, + %% use the TCP port 21000. If a base was previously calculated, + %% increment it by the number of TCP ports we may open. + %% + %% Port 21000 is an arbitrary choice. We don't want to use the + %% default AMQP port of 5672 so other AMQP clients on the same host + %% do not accidentally use the testsuite broker. There seems to be + %% no registered service around this port in /etc/services. And it + %% should be far enough away from the default ephemeral TCP ports + %% range. + Base = case get_config(Config, tcp_ports_base) of + undefined -> 21000; + P -> P + length(?TCP_PORTS_LIST) + end, + Config1 = set_config(Config, {tcp_ports_base, Base}), + %% Now, compute all TCP port numbers from this base. + {Config2, _} = lists:foldl( + fun(PortName, {NewConfig, NextPort}) -> + { + set_config(NewConfig, {PortName, NextPort}), + NextPort + 1 + } + end, + {Config1, Base}, ?TCP_PORTS_LIST), + %% Finally, update the RabbitMQ configuration with the computed TCP + %% port numbers. + update_tcp_ports_in_rmq_config(Config2, ?TCP_PORTS_LIST). + +update_tcp_ports_in_rmq_config(Config, [tcp_port_amqp = Key | Rest]) -> + Config1 = merge_app_env(Config, rabbit, + [{tcp_listeners, [?config(Key, Config)]}]), + update_tcp_ports_in_rmq_config(Config1, Rest); +update_tcp_ports_in_rmq_config(Config, [tcp_port_amqp_tls = Key | Rest]) -> + Config1 = merge_app_env(Config, rabbit, + [{ssl_listeners, [?config(Key, Config)]}]), + update_tcp_ports_in_rmq_config(Config1, Rest); +update_tcp_ports_in_rmq_config(Config, [tcp_port_mgmt = Key | Rest]) -> + Config1 = merge_app_env(Config, rabbitmq_management, + [{listener, [{port, ?config(Key, Config)}]}]), + update_tcp_ports_in_rmq_config(Config1, Rest); +update_tcp_ports_in_rmq_config(Config, [tcp_port_erlang_dist | Rest]) -> + %% The Erlang distribution port doesn't appear in the configuration file. + update_tcp_ports_in_rmq_config(Config, Rest); +update_tcp_ports_in_rmq_config(Config, []) -> + Config. + +init_nodename(Config) -> + Base = ?config(tcp_ports_base, Config), + Nodename = list_to_atom(rabbit_misc:format("rmq-ct-~b@localhost", [Base])), + set_config(Config, {rmq_nodename, Nodename}). + +init_config_filename(Config) -> + PrivDir = ?config(priv_dir, Config), + Nodename = ?config(rmq_nodename, Config), + ConfigDir = filename:join(PrivDir, Nodename), + ConfigFile = filename:join(ConfigDir, Nodename), + set_config(Config, {erlang_node_config_filename, ConfigFile}). + +write_config_file(Config) -> + %% Prepare a RabbitMQ configuration. + ErlangConfig = ?config(erlang_node_config, Config), + ConfigFile = ?config(erlang_node_config_filename, Config), + ConfigDir = filename:dirname(ConfigFile), + Ret1 = file:make_dir(ConfigDir), + Ret2 = file:write_file(ConfigFile ++ ".config", + io_lib:format("% vim:ft=erlang:~n~n~p.~n", + [ErlangConfig])), + case {Ret1, Ret2} of + {ok, ok} -> + Config; + {{error, eexist}, ok} -> + Config; + {{error, Reason}, _} when Reason =/= eexist -> + {skip, "Failed to create Erlang node config directory \"" ++ + ConfigDir ++ "\": " ++ file:format_error(Reason)}; + {_, {error, Reason}} -> + {skip, "Failed to create Erlang node config file \"" ++ + ConfigFile ++ "\": " ++ file:format_error(Reason)} + end. + +move_nonworking_nodedir_away(Config) -> + ConfigFile = ?config(erlang_node_config_filename, Config), + ConfigDir = filename:dirname(ConfigFile), + NewName = filename:join( + filename:dirname(ConfigDir), + "_unused_nodedir_" ++ filename:basename(ConfigDir)), + file:rename(ConfigDir, NewName), + lists:keydelete(erlang_node_config_filename, 1, Config). + create_unauthorized_user(Config) -> Rabbitmqctl = ?config(rabbitmqctl_cmd, Config), - Cmd = Rabbitmqctl ++ " add_user " ++ - ?UNAUTHORIZED_USER ++ " " ++ ?UNAUTHORIZED_USER, + Nodename = ?config(rmq_nodename, Config), + Cmd = Rabbitmqctl ++ " -n " ++ atom_to_list(Nodename) ++ + " add_user " ++ ?UNAUTHORIZED_USER ++ " " ++ ?UNAUTHORIZED_USER, case run_cmd(Cmd) of true -> set_config(Config, [{rmq_unauthorized_username, @@ -290,7 +368,9 @@ create_unauthorized_user(Config) -> delete_unauthorized_user(Config) -> Rabbitmqctl = ?config(rabbitmqctl_cmd, Config), - Cmd = Rabbitmqctl ++ " delete_user " ++ ?UNAUTHORIZED_USER, + Nodename = ?config(rmq_nodename, Config), + Cmd = Rabbitmqctl ++ " -n " ++ atom_to_list(Nodename) ++ + " delete_user " ++ ?UNAUTHORIZED_USER, run_cmd(Cmd), Config. @@ -298,8 +378,10 @@ stop_rabbitmq_node(Config) -> Make = ?config(make_cmd, Config), SrcDir = ?config(amqp_client_srcdir, Config), PrivDir = ?config(priv_dir, Config), + Nodename = ?config(rmq_nodename, Config), Cmd = Make ++ " -C " ++ SrcDir ++ make_verbosity() ++ " stop-rabbit-on-node stop-node" ++ + " RABBITMQ_NODENAME='" ++ atom_to_list(Nodename) ++ "'" ++ " TEST_TMPDIR='" ++ PrivDir ++ "'", run_cmd(Cmd), Config. diff --git a/deps/amqp_client/test/system_SUITE.erl b/deps/amqp_client/test/system_SUITE.erl index 41499f5dfe..a67b593c88 100644 --- a/deps/amqp_client/test/system_SUITE.erl +++ b/deps/amqp_client/test/system_SUITE.erl @@ -179,19 +179,22 @@ init_per_testcase(Test, Config) -> basic_get_ipv6_ssl -> "::1"; _ -> ?config(rmq_hostname, Config) end, - SSLOpts = if + {Port, SSLOpts} = if Test =:= basic_get_ipv4_ssl orelse Test =:= basic_get_ipv6_ssl -> CertsDir = ?config(amqp_client_certsdir, Config), - [ - {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}, - {certfile, filename:join([CertsDir, "client", "cert.pem"])}, - {keyfile, filename:join([CertsDir, "client", "key.pem"])}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true} - ]; + { + ?config(tcp_port_amqp_tls, Config), + [ + {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}, + {certfile, filename:join([CertsDir, "client", "cert.pem"])}, + {keyfile, filename:join([CertsDir, "client", "key.pem"])}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true} + ] + }; true -> - none + {?config(tcp_port_amqp, Config), none} end, ChannelMax = case Test of channel_tune_negotiation -> 10; @@ -209,6 +212,7 @@ init_per_testcase(Test, Config) -> username = Username, password = Password, host = Hostname, + port = Port, virtual_host = VHost, channel_max = ChannelMax, ssl_options = SSLOpts} @@ -1374,27 +1378,35 @@ assert_down_with_error(MonitorRef, CodeAtom) -> set_resource_alarm(memory, Config) -> Make = ?config(make_cmd, Config), SrcDir = ?config(amqp_client_srcdir, Config), + Nodename = ?config(rmq_nodename, Config), true = rabbit_ct_helpers:run_cmd( Make ++ " -C " ++ SrcDir ++ rabbit_ct_helpers:make_verbosity() ++ + " RABBITMQ_NODENAME='" ++ atom_to_list(Nodename) ++ "'" ++ " set-resource-alarm SOURCE=memory"); set_resource_alarm(disk, Config) -> Make = ?config(make_cmd, Config), SrcDir = ?config(amqp_client_srcdir, Config), + Nodename = ?config(rmq_nodename, Config), true = rabbit_ct_helpers:run_cmd( Make ++ " -C " ++ SrcDir ++ rabbit_ct_helpers:make_verbosity() ++ + " RABBITMQ_NODENAME='" ++ atom_to_list(Nodename) ++ "'" ++ " set-resource-alarm SOURCE=disk"). clear_resource_alarm(memory, Config) -> Make = ?config(make_cmd, Config), SrcDir = ?config(amqp_client_srcdir, Config), + Nodename = ?config(rmq_nodename, Config), true = rabbit_ct_helpers:run_cmd( Make ++ " -C " ++ SrcDir ++ rabbit_ct_helpers:make_verbosity() ++ + " RABBITMQ_NODENAME='" ++ atom_to_list(Nodename) ++ "'" ++ " clear-resource-alarm SOURCE=memory"); clear_resource_alarm(disk, Config) -> Make = ?config(make_cmd, Config), SrcDir = ?config(amqp_client_srcdir, Config), + Nodename = ?config(rmq_nodename, Config), true = rabbit_ct_helpers:run_cmd( Make ++ " -C " ++ SrcDir ++ rabbit_ct_helpers:make_verbosity() ++ + " RABBITMQ_NODENAME='" ++ atom_to_list(Nodename) ++ "'" ++ " clear-resource-alarm SOURCE=disk"). fmt(Fmt, Args) -> list_to_binary(rabbit_misc:format(Fmt, Args)).