diff options
author | Michael Klishin <mklishin@pivotal.io> | 2019-03-06 20:19:52 +0300 |
---|---|---|
committer | Michael Klishin <mklishin@pivotal.io> | 2019-03-06 20:19:52 +0300 |
commit | c339b6a10cc36696135ddf5c5dc2ca2b648e013a (patch) | |
tree | 7a7f9971fff17d25800aff22137e2add99cbb7ad | |
parent | 89d5eb80bb97c5881505f01c294cabdd7e2ebc65 (diff) | |
download | rabbitmq-server-git-c339b6a10cc36696135ddf5c5dc2ca2b648e013a.tar.gz |
ctl shutdown: infer hostnames from node names
inet_db is not a very reliable source as it doesn't take
node name CLI arguments and ERL_INETRC file settings.
That can lead to false positives in environments where
inet_db returns the same value (e.g. `localhost`) for
every cluster member.
Per discussion with @gerhard.
Closes #327.
References #309.
3 files changed, 50 insertions, 39 deletions
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex index ec200161e5..df5d0e97b2 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex @@ -34,6 +34,40 @@ defmodule RabbitMQ.CLI.Core.NodeName do def hostname, do: :inet_db.gethostname() |> List.to_string() @doc """ + Get hostname part of current node name + """ + def hostname_from_node do + [_, hostname] = split_node(Node.self()) + hostname + end + + @doc """ + Get hostname part of given node name + """ + def hostname_from_node(name) do + [_, hostname] = split_node(name) + hostname + end + + def split_node(name) when is_atom(name) do + split_node(to_string(name)) + end + + def split_node(name) do + case String.split(name, "@", parts: 2) do + ["", host] -> + default_name = to_string(Config.get_option(:node)) + [default_name, host] + + [_head, _host] = rslt -> + rslt + + [head] -> + [head, ""] + end + end + + @doc """ Get local domain. If unavailable, makes a good guess. We're using :inet_db here because that's what Erlang/OTP uses when it creates a node name: @@ -43,6 +77,10 @@ defmodule RabbitMQ.CLI.Core.NodeName do domain(1) end + # + # Implementation + # + defp domain(attempt) do case {attempt, :inet_db.res_option(:domain), :os.type()} do {1, [], _} -> @@ -162,24 +200,6 @@ defmodule RabbitMQ.CLI.Core.NodeName do Regex.match?(rx, head) end - defp split_node(name) when is_atom(name) do - split_node(to_string(name)) - end - - defp split_node(name) do - case String.split(name, "@", parts: 2) do - ["", host] -> - default_name = to_string(Config.get_option(:node)) - [default_name, host] - - [_head, _host] = rslt -> - rslt - - [head] -> - [head, ""] - end - end - defp do_load_resolv do # It could be we haven't read domain name from resolv file yet :ok = :inet_config.do_load_resolv(:os.type(), :longnames) diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex index 051bc89e4d..74a9b53333 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex @@ -15,7 +15,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ShutdownCommand do @behaviour RabbitMQ.CLI.CommandBehaviour - alias RabbitMQ.CLI.Core.OsPid + alias RabbitMQ.CLI.Core.{OsPid, NodeName} def switches() do [timeout: :integer, @@ -32,18 +32,17 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ShutdownCommand do :ok end - def validate([], %{node: node_name, wait: true, timeout: timeout}) do - hostname = :inet_db.gethostname() - case :rabbit_misc.rpc_call(node_name, :inet_db, :gethostname, [], timeout) do - {:badrpc, _} = err -> {:error, err} - remote_hostname -> - case hostname == remote_hostname do - true -> :ok; - false -> - msg = "\nThis command can only --wait for shutdown of local nodes but node #{node_name} is remote (local hostname: #{hostname}, remote: #{remote_hostname}).\n" <> - "Pass --no-wait to shut node #{node_name} down without waiting.\n" - {:validation_failure, {:unsupported_target, msg}} - end + def validate([], %{node: node_name, wait: true}) do + local_hostname = NodeName.hostname_from_node(Node.self()) + remote_hostname = NodeName.hostname_from_node(node_name) + case local_hostname == remote_hostname do + true -> :ok; + false -> + msg = "\nThis command can only --wait for shutdown of local nodes " <> + "but node #{node_name} seems to be remote " <> + "(local hostname: #{local_hostname}, remote: #{remote_hostname}).\n" <> + "Pass --no-wait to shut node #{node_name} down without waiting.\n" + {:validation_failure, {:unsupported_target, msg}} end end use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments diff --git a/deps/rabbitmq_cli/test/ctl/shutdown_command_test.exs b/deps/rabbitmq_cli/test/ctl/shutdown_command_test.exs index b427c967dd..c4f658687b 100644 --- a/deps/rabbitmq_cli/test/ctl/shutdown_command_test.exs +++ b/deps/rabbitmq_cli/test/ctl/shutdown_command_test.exs @@ -42,14 +42,6 @@ defmodule ShutdownCommandTest do assert @command.validate([], Map.merge(%{wait: false}, context[:opts])) == :ok end - # this command performs rpc calls in validate/2 - test "validate: request to a non-existent node returns nodedown" do - target = :jake@thedog - - opts = %{node: target, wait: true, timeout: 10} - assert match?({:error, {:badrpc, _}}, @command.validate([], opts)) - end - test "run: request to a non-existent node returns nodedown" do target = :jake@thedog |