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)).