diff --git a/lib/rabbitmq/cli/core/command_modules.ex b/lib/rabbitmq/cli/core/command_modules.ex index 753be777..1eec4fb8 100644 --- a/lib/rabbitmq/cli/core/command_modules.ex +++ b/lib/rabbitmq/cli/core/command_modules.ex @@ -55,7 +55,7 @@ defmodule RabbitMQ.CLI.Core.CommandModules do end def ctl_and_plugin_modules(opts) do - Helpers.require_rabbit(opts) + Helpers.require_rabbit_and_plugins(opts) enabled_plugins = PluginsHelpers.read_enabled(opts) [:rabbitmqctl | enabled_plugins] |> Enum.flat_map(fn(app) -> Application.spec(app, :modules) || [] end) diff --git a/lib/rabbitmq/cli/core/exit_codes.ex b/lib/rabbitmq/cli/core/exit_codes.ex index 3b0e7447..ceaf7d76 100644 --- a/lib/rabbitmq/cli/core/exit_codes.ex +++ b/lib/rabbitmq/cli/core/exit_codes.ex @@ -38,6 +38,7 @@ defmodule RabbitMQ.CLI.Core.ExitCodes do def exit_code_for({:validation_failure, {:too_many_args, _}}), do: exit_usage() def exit_code_for({:validation_failure, {:bad_argument, _}}), do: exit_dataerr() def exit_code_for({:validation_failure, :bad_argument}), do: exit_dataerr() + def exit_code_for({:validation_failure, :eperm}), do: exit_dataerr() def exit_code_for({:validation_failure, {:bad_option, _}}), do: exit_usage() def exit_code_for({:validation_failure, _}), do: exit_usage() def exit_code_for({:badrpc, :timeout}), do: exit_tempfail() diff --git a/lib/rabbitmq/cli/core/helpers.ex b/lib/rabbitmq/cli/core/helpers.ex index d819bdfd..09291dc4 100644 --- a/lib/rabbitmq/cli/core/helpers.ex +++ b/lib/rabbitmq/cli/core/helpers.ex @@ -47,6 +47,16 @@ defmodule RabbitMQ.CLI.Core.Helpers do def hostname, do: :inet.gethostname() |> elem(1) |> List.to_string + def validate_step(:ok, step) do + case step.() do + {:error, err} -> {:validation_failure, err}; + _ -> :ok + end + end + def validate_step({:validation_failure, err}, _) do + {:validation_failure, err} + end + def memory_units do ["k", "kiB", "M", "MiB", "G", "GiB", "kB", "MB", "GB", ""] end @@ -88,6 +98,10 @@ defmodule RabbitMQ.CLI.Core.Helpers do end def require_rabbit(opts) do + try_load_rabbit_code(opts) + end + + def require_rabbit_and_plugins(opts) do with :ok <- try_load_rabbit_code(opts), :ok <- try_load_rabbit_plugins(opts), do: :ok diff --git a/lib/rabbitmq/cli/ctl/commands/hipe_compile_command.ex b/lib/rabbitmq/cli/ctl/commands/hipe_compile_command.ex new file mode 100644 index 00000000..395b8c36 --- /dev/null +++ b/lib/rabbitmq/cli/ctl/commands/hipe_compile_command.ex @@ -0,0 +1,88 @@ +## 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-2017 Pivotal Software, Inc. All rights reserved. + +defmodule RabbitMQ.CLI.Ctl.Commands.HipeCompileCommand do + alias RabbitMQ.CLI.Core.Helpers, as: Helpers + + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + + # + # API + # + + def merge_defaults(args, opts) do + {args, opts} + end + + def switches(), do: [rabbitmq_home: :string] + + def usage, do: "hipe_compile " + + def validate([], _), do: {:validation_failure, :not_enough_args} + def validate([target_dir], opts) do + :ok + |> Helpers.validate_step(fn() -> + case acceptable_path?(target_dir) do + true -> :ok + false -> {:error, {:bad_argument, "Target directory path cannot be blank"}} + end + end) + |> Helpers.validate_step(fn() -> + case File.dir?(target_dir) do + true -> :ok + false -> + case File.mkdir_p(target_dir) do + :ok -> :ok + {:error, :eperm} -> + {:error, {:bad_argument, "Cannot create target directory #{target_dir}: insufficient permissions"}} + end + end + end) + |> Helpers.validate_step(fn() -> Helpers.require_rabbit(opts) end) + end + def validate(_, _), do: {:validation_failure, :too_many_args} + + def run([target_dir], _opts) do + Code.ensure_loaded(:rabbit_hipe) + hipe_compile(String.trim(target_dir)) + end + + def banner([target_dir], _) do + "Will pre-compile RabbitMQ server modules with HiPE to #{target_dir} ..." + end + + # + # Implementation + # + + # Accepts any non-blank path + defp acceptable_path?(value) do + String.length(String.trim(value)) != 0 + end + + defp hipe_compile(target_dir) do + case :rabbit_hipe.can_hipe_compile() do + true -> + case :rabbit_hipe.compile_to_directory(target_dir) do + {:ok, _, _} -> :ok + {:ok, :already_compiled} -> {:ok, "already compiled"} + {:error, message} -> {:error, message} + end + false -> + {:error, "HiPE compilation is not supported"} + end + end +end diff --git a/lib/rabbitmq/cli/ctl/validators.ex b/lib/rabbitmq/cli/ctl/validators.ex index 5f8f9bed..bc07cfc3 100644 --- a/lib/rabbitmq/cli/ctl/validators.ex +++ b/lib/rabbitmq/cli/ctl/validators.ex @@ -51,5 +51,4 @@ defmodule RabbitMQ.CLI.Ctl.Validators do {:error, err} -> {:validation_failure, err} end end - end diff --git a/lib/rabbitmq/cli/plugins/commands/disable_command.ex b/lib/rabbitmq/cli/plugins/commands/disable_command.ex index 166b6ec8..7c8b9e3a 100644 --- a/lib/rabbitmq/cli/plugins/commands/disable_command.ex +++ b/lib/rabbitmq/cli/plugins/commands/disable_command.ex @@ -44,7 +44,7 @@ defmodule RabbitMQ.CLI.Plugins.Commands.DisableCommand do def validate(_, opts) do :ok - |> validate_step(fn() -> Helpers.require_rabbit(opts) end) + |> validate_step(fn() -> Helpers.require_rabbit_and_plugins(opts) end) |> validate_step(fn() -> PluginHelpers.enabled_plugins_file(opts) end) |> validate_step(fn() -> Helpers.plugins_dir(opts) end) end diff --git a/lib/rabbitmq/cli/plugins/commands/enable_command.ex b/lib/rabbitmq/cli/plugins/commands/enable_command.ex index c8d5fa6e..7bdad12e 100644 --- a/lib/rabbitmq/cli/plugins/commands/enable_command.ex +++ b/lib/rabbitmq/cli/plugins/commands/enable_command.ex @@ -45,19 +45,9 @@ defmodule RabbitMQ.CLI.Plugins.Commands.EnableCommand do def validate(_plugins, opts) do :ok - |> validate_step(fn() -> Helpers.require_rabbit(opts) end) - |> validate_step(fn() -> PluginHelpers.enabled_plugins_file(opts) end) - |> validate_step(fn() -> Helpers.plugins_dir(opts) end) - end - - def validate_step(:ok, step) do - case step.() do - {:error, err} -> {:validation_failure, err}; - _ -> :ok - end - end - def validate_step({:validation_failure, err}, _) do - {:validation_failure, err} + |> Helpers.validate_step(fn() -> Helpers.require_rabbit_and_plugins(opts) end) + |> Helpers.validate_step(fn() -> PluginHelpers.enabled_plugins_file(opts) end) + |> Helpers.validate_step(fn() -> Helpers.plugins_dir(opts) end) end def usage, do: "enable |--all [--offline] [--online]" diff --git a/lib/rabbitmq/cli/plugins/commands/list_command.ex b/lib/rabbitmq/cli/plugins/commands/list_command.ex index 56b2fc9d..2ee91784 100644 --- a/lib/rabbitmq/cli/plugins/commands/list_command.ex +++ b/lib/rabbitmq/cli/plugins/commands/list_command.ex @@ -48,7 +48,7 @@ defmodule RabbitMQ.CLI.Plugins.Commands.ListCommand do def validate(_, opts) do :ok - |> validate_step(fn() -> Helpers.require_rabbit(opts) end) + |> validate_step(fn() -> Helpers.require_rabbit_and_plugins(opts) end) |> validate_step(fn() -> PluginHelpers.enabled_plugins_file(opts) end) |> validate_step(fn() -> Helpers.plugins_dir(opts) end) end diff --git a/lib/rabbitmq/cli/plugins/commands/set_command.ex b/lib/rabbitmq/cli/plugins/commands/set_command.ex index f1c5dcb8..289013ec 100644 --- a/lib/rabbitmq/cli/plugins/commands/set_command.ex +++ b/lib/rabbitmq/cli/plugins/commands/set_command.ex @@ -37,7 +37,7 @@ defmodule RabbitMQ.CLI.Plugins.Commands.SetCommand do def validate(_plugins, opts) do :ok - |> validate_step(fn() -> Helpers.require_rabbit(opts) end) + |> validate_step(fn() -> Helpers.require_rabbit_and_plugins(opts) end) |> validate_step(fn() -> PluginHelpers.enabled_plugins_file(opts) end) |> validate_step(fn() -> Helpers.plugins_dir(opts) end) end diff --git a/lib/rabbitmq/cli/plugins/plugins_helpers.ex b/lib/rabbitmq/cli/plugins/plugins_helpers.ex index feda5cc2..2acc153c 100644 --- a/lib/rabbitmq/cli/plugins/plugins_helpers.ex +++ b/lib/rabbitmq/cli/plugins/plugins_helpers.ex @@ -45,7 +45,7 @@ defmodule RabbitMQ.CLI.Plugins.Helpers do def set_enabled_plugins(plugins, opts) do plugin_atoms = :lists.usort(for plugin <- plugins, do: to_atom(plugin)) - CliHelpers.require_rabbit(opts) + CliHelpers.require_rabbit_and_plugins(opts) {:ok, plugins_file} = enabled_plugins_file(opts) write_enabled_plugins(plugin_atoms, plugins_file, opts) end diff --git a/mix.exs b/mix.exs index 642ca313..b5ead6bf 100644 --- a/mix.exs +++ b/mix.exs @@ -106,6 +106,7 @@ defmodule RabbitMQCtl.MixfileBase do override: true }, {:amqp, "~> 0.1.5", only: :test}, + {:temp, "~> 0.4", only: :test}, {:json, "~> 1.0.0"}, {:csv, "~> 1.4.2"}, {:simetric, "~> 0.1.0"} diff --git a/test/core/helpers_test.exs b/test/core/helpers_test.exs index 40173474..f96208fd 100644 --- a/test/core/helpers_test.exs +++ b/test/core/helpers_test.exs @@ -124,7 +124,7 @@ test "RabbitMQ hostname is properly formed" do opts = %{plugins_dir: to_string(plugins_directory_03), rabbitmq_home: rabbitmq_home} assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_03, 'New project', '0.1.0'}) == false - @subject.require_rabbit(opts) + @subject.require_rabbit_and_plugins(opts) assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_03, 'New project', '0.1.0'}) end @@ -134,7 +134,7 @@ test "RabbitMQ hostname is properly formed" do opts = %{plugins_dir: to_string(plugins_directory_04), rabbitmq_home: rabbitmq_home} assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_04, 'New project', 'rolling'}) == false - @subject.require_rabbit(opts) + @subject.require_rabbit_and_plugins(opts) assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_04, 'New project', 'rolling'}) end diff --git a/test/hipe_compile_command_test.exs b/test/hipe_compile_command_test.exs new file mode 100644 index 00000000..98fb8047 --- /dev/null +++ b/test/hipe_compile_command_test.exs @@ -0,0 +1,81 @@ +## 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-2017 Pivotal Software, Inc. All rights reserved. + +defmodule HipeCompileCommandTest do + use ExUnit.Case, async: false + import TestHelper + + require Temp + + @command RabbitMQ.CLI.Ctl.Commands.HipeCompileCommand + @vhost "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :net_kernel.connect_node(get_rabbit_hostname()) + + start_rabbitmq_app() + + on_exit([], fn -> + :erlang.disconnect_node(get_rabbit_hostname()) + end) + end + + setup do + rabbitmq_home = :rabbit_misc.rpc_call(node(), :code, :lib_dir, [:rabbit]) + + {:ok, tmp_dir} = Temp.path + + {:ok, opts: %{ + node: get_rabbit_hostname(), + vhost: @vhost, + rabbitmq_home: rabbitmq_home + }, + target_dir: tmp_dir + } + end + + test "validate: providing no arguments fails validation", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate: providing two arguments fails validation", context do + assert @command.validate(["/path/one", "/path/two"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: providing three arguments fails validation", context do + assert @command.validate(["/path/one", "/path/two", "/path/three"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: providing one blank directory path and required options fails", context do + assert match?({:validation_failure, {:bad_argument, _}}, @command.validate([""], context[:opts])) + end + + test "validate: providing one path argument that only contains spaces and required options fails", context do + assert match?({:validation_failure, {:bad_argument, _}}, @command.validate([" "], context[:opts])) + end + + test "validate: providing one non-blank directory path and required options succeeds", context do + assert @command.validate([context[:target_dir]], context[:opts]) == :ok + end + + test "validate: failure to load the rabbit application is reported as an error", context do + assert {:validation_failure, {:unable_to_load_rabbit, _}} = + @command.validate([context[:target_dir]], Map.delete(context[:opts], :rabbitmq_home)) + end +end