From 1b1534f86bfe5cf113f2ef3cf249e6c4a3e09b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20P=C3=A9dron?= Date: Mon, 6 Jun 2016 16:02:58 +0200 Subject: [PATCH] Implement the `join_cluster` command Compared to the Erlang-based rabbitmqctl's command, this one accepts `--disc` to specify the node type. In the implementation, it does nothing because that's the default value already. It is only to improve consistency, because there is a `--ram` flag. The testsuite for this command is incomplete in this commit. When a testcase will be able to start and stop multiple nodes, the testsuite will need to be completed. [#116551629] --- deps/rabbitmq_cli/lib/join_cluster_command.ex | 75 ++++++++++++ deps/rabbitmq_cli/lib/rabbitmqctl.ex | 9 +- deps/rabbitmq_cli/lib/standard_codes.ex | 1 + .../test/join_cluster_command_test.exs | 112 ++++++++++++++++++ 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 deps/rabbitmq_cli/lib/join_cluster_command.ex create mode 100644 deps/rabbitmq_cli/test/join_cluster_command_test.exs diff --git a/deps/rabbitmq_cli/lib/join_cluster_command.ex b/deps/rabbitmq_cli/lib/join_cluster_command.ex new file mode 100644 index 0000000000..6808e46fb5 --- /dev/null +++ b/deps/rabbitmq_cli/lib/join_cluster_command.ex @@ -0,0 +1,75 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at http://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is Pivotal Software, Inc. +## Copyright (c) 2016 Pivotal Software, Inc. All rights reserved. + + +defmodule JoinClusterCommand do + + @behaviour CommandBehaviour + @flags [ + :disc, # --disc is accepted for consistency's sake. + :ram + ] + + def flags, do: @flags + def switches() do + [ + disc: :boolean, + ram: :boolean + ] + end + + def merge_defaults(args, opts) do + {args, Map.merge(%{disc: true, ram: false}, opts)} + end + + def validate(_, %{disc: true, ram: true}) do + {:validation_failure, + {:bad_argument, "The node type must be either disc or ram."}} + end + def validate([], _), do: {:validation_failure, :not_enough_args} + def validate([_], _), do: :ok + def validate(_, _), do: {:validation_failure, :too_many_args} + + def run([target_node], %{node: node_name, ram: ram}) do + node_type = case ram do + true -> :ram + _ -> :disc + end + ret = + node_name + |> Helpers.parse_node + |> :rabbit_misc.rpc_call( + :rabbit_mnesia, + :join_cluster, + [Helpers.parse_node(target_node), node_type] + ) + case ret do + {:error, reason} -> + {:join_cluster_failed, {reason, node_name}} + result -> + result + end + end + + def usage() do + [ + "join_cluster [--disc|--ram] " + ] + end + + def banner([target_node], %{node: node_name}) do + "Clustering node #{node_name} with #{Helpers.parse_node(target_node)}" + end +end diff --git a/deps/rabbitmq_cli/lib/rabbitmqctl.ex b/deps/rabbitmq_cli/lib/rabbitmqctl.ex index 3853f76950..fe5df7f535 100644 --- a/deps/rabbitmq_cli/lib/rabbitmqctl.ex +++ b/deps/rabbitmq_cli/lib/rabbitmqctl.ex @@ -124,7 +124,13 @@ defmodule RabbitMQCtl do result end - defp print_standard_messages({:reset_failed, {:mnesia_unexpectedly_running, node_name}} = result, _) do + defp print_standard_messages( + {failed_command, + {:mnesia_unexpectedly_running, node_name}} = result, _) + when + failed_command == :reset_failed or + failed_command == :join_cluster_failed + do IO.puts "Mnesia is still running on node #{node_name}." IO.puts "Please stop RabbitMQ with rabbitmqctl stop_app first." result @@ -201,6 +207,7 @@ defmodule RabbitMQCtl do defp handle_exit({:badrpc, :nodedown}), do: exit_program(exit_unavailable) defp handle_exit({:refused, _, _, _}), do: exit_program(exit_dataerr) defp handle_exit({:healthcheck_failed, _}), do: exit_program(exit_software) + defp handle_exit({:join_cluster_failed, _}), do: exit_program(exit_software) defp handle_exit({:reset_failed, _}), do: exit_program(exit_software) defp handle_exit({:error, _}), do: exit_program(exit_software) defp handle_exit(true), do: handle_exit(:ok, exit_ok) diff --git a/deps/rabbitmq_cli/lib/standard_codes.ex b/deps/rabbitmq_cli/lib/standard_codes.ex index 5263826596..6729b82673 100644 --- a/deps/rabbitmq_cli/lib/standard_codes.ex +++ b/deps/rabbitmq_cli/lib/standard_codes.ex @@ -23,6 +23,7 @@ defmodule StandardCodes do def map_to_standard_code({:refused, _, _, _} = input), do: input def map_to_standard_code({:bad_option, _} = input), do: input def map_to_standard_code({:error, _} = input), do: input + def map_to_standard_code({:join_cluster_failed, _} = input), do: input def map_to_standard_code({:reset_failed, _} = input), do: input def map_to_standard_code({:validation_failure, _} = input), do: input def map_to_standard_code({:healthcheck_failed, _} = input), do: input diff --git a/deps/rabbitmq_cli/test/join_cluster_command_test.exs b/deps/rabbitmq_cli/test/join_cluster_command_test.exs new file mode 100644 index 0000000000..7af25c125a --- /dev/null +++ b/deps/rabbitmq_cli/test/join_cluster_command_test.exs @@ -0,0 +1,112 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at http://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is Pivotal Software, Inc. +## Copyright (c) 2016 Pivotal Software, Inc. All rights reserved. + + +defmodule JoinClusterCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command JoinClusterCommand + + setup_all do + :net_kernel.start([:rabbitmqctl, :shortnames]) + :net_kernel.connect_node(get_rabbit_hostname) + + start_rabbitmq_app + + on_exit([], fn -> + start_rabbitmq_app + :erlang.disconnect_node(get_rabbit_hostname) + :net_kernel.stop() + end) + + :ok + end + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname, + disc: true, + ram: false, + }} + end + + test "validate: specifying both --disc and --ram is reported as invalid", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["a"], Map.merge(context[:opts], %{disc: true, ram: true})) + ) + end + test "validate: specifying no target node is reported as an error", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + test "validate: specifying multiple target nodes is reported as an error", context do + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + # TODO + #test "run: successful join as a disc node", context do + #end + + # TODO + #test "run: successful join as a RAM node", context do + #end + + test "run: joining self is invalid", context do + stop_rabbitmq_app + assert match?( + {:join_cluster_failed, {:cannot_cluster_node_with_itself, _}}, + @command.run([context[:opts][:node]], context[:opts])) + start_rabbitmq_app + end + + # TODO + #test "run: request to an active node fails", context do + # assert match?( + # {:join_cluster_failed, {:mnesia_unexpectedly_running, _}}, + # @command.run([context[:opts][:node]], context[:opts])) + #end + + test "run: request to a non-existent node returns nodedown", context do + target = :jake@thedog + :net_kernel.connect_node(target) + opts = %{ + node: target, + disc: true, + ram: false, + } + # We use "self" node as the target. It's enough to trigger the error. + assert match?( + {:badrpc, :nodedown}, + @command.run([context[:opts][:node]], opts)) + end + + test "run: joining a non-existent node returns nodedown", context do + target = :jake@thedog + :net_kernel.connect_node(target) + stop_rabbitmq_app + assert match?( + {:badrpc_multi, :nodedown, [_]}, + @command.run([target], context[:opts])) + start_rabbitmq_app + end + + test "banner", context do + assert @command.banner(["a"], context[:opts]) =~ + ~r/Clustering node #{get_rabbit_hostname} with a@/ + end +end