Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: list_deprecated_features command #9901

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions deps/rabbit/app.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def all_beam_files(name = "all_beam_files"):
"src/rabbit_definitions_hashing.erl",
"src/rabbit_definitions_import_https.erl",
"src/rabbit_definitions_import_local_filesystem.erl",
"src/rabbit_depr_ff_extra.erl",
"src/rabbit_deprecated_features.erl",
"src/rabbit_diagnostics.erl",
"src/rabbit_direct.erl",
Expand Down Expand Up @@ -373,6 +374,7 @@ def all_test_beam_files(name = "all_test_beam_files"):
"src/rabbit_definitions_hashing.erl",
"src/rabbit_definitions_import_https.erl",
"src/rabbit_definitions_import_local_filesystem.erl",
"src/rabbit_depr_ff_extra.erl",
"src/rabbit_deprecated_features.erl",
"src/rabbit_diagnostics.erl",
"src/rabbit_direct.erl",
Expand Down Expand Up @@ -652,6 +654,7 @@ def all_srcs(name = "all_srcs"):
"src/rabbit_definitions_hashing.erl",
"src/rabbit_definitions_import_https.erl",
"src/rabbit_definitions_import_local_filesystem.erl",
"src/rabbit_depr_ff_extra.erl",
"src/rabbit_deprecated_features.erl",
"src/rabbit_diagnostics.erl",
"src/rabbit_direct.erl",
Expand Down
69 changes: 69 additions & 0 deletions deps/rabbit/src/rabbit_depr_ff_extra.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2023 Broadcom. All Rights Reserved. The term “Broadcom”
%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
%%
%% @doc
%% This module provides extra functions unused by the feature flags
%% subsystem core functionality.

-module(rabbit_depr_ff_extra).

-export([cli_info/1]).

-type cli_info() :: [cli_info_entry()].
%% A list of deprecated feature properties, formatted for the RabbitMQ CLI.

-type cli_info_entry() ::
#{name => rabbit_feature_flags:feature_name(),
deprecation_phase => rabbit_deprecated_features:deprecation_phase(),
provided_by => atom(),
desc => string(),
doc_url => string()}.
%% A list of properties for a single deprecated feature, formatted for the
%% RabbitMQ CLI.

-spec cli_info(Which) -> CliInfo when
Which :: all | used,
CliInfo :: cli_info().
%% @doc
%% Returns a list of all or used deprecated features properties,
%% depending on the argument.
%%
%% @param Which The group of deprecated features to return: `all' or `used'.
%% @returns the list of all deprecated feature properties.

cli_info(all) ->
cli_info0(rabbit_deprecated_features:list(all));
cli_info(used) ->
cli_info0(rabbit_deprecated_features:list(used)).

-spec cli_info0(FeatureFlags) -> CliInfo when
FeatureFlags :: rabbit_feature_flags:feature_flags(),
CliInfo :: cli_info().
%% @doc
%% Formats a map of deprecated features and their properties into a list of
%% deprecated feature properties as expected by the RabbitMQ CLI.
%%
%% @param DeprecatedFeatures A map of deprecated features.
%% @returns the list of deprecated features properties, created from the map
%% specified in arguments.

cli_info0(DeprecatedFeature) ->
lists:foldr(
fun(FeatureName, Acc) ->
FeatureProps = maps:get(FeatureName, DeprecatedFeature),

App = maps:get(provided_by, FeatureProps),
DeprecationPhase = maps:get(deprecation_phase, FeatureProps, ""),
Desc = maps:get(desc, FeatureProps, ""),
DocUrl = maps:get(doc_url, FeatureProps, ""),
Info = #{name => FeatureName,
desc => unicode:characters_to_binary(Desc),
deprecation_phase => DeprecationPhase,
doc_url => unicode:characters_to_binary(DocUrl),
provided_by => App},
[Info | Acc]
end, [], lists:sort(maps:keys(DeprecatedFeature))).
66 changes: 48 additions & 18 deletions deps/rabbit/src/rabbit_deprecated_features.erl
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@
get_warning/1]).
-export([extend_properties/2,
should_be_permitted/2,
enable_underlying_feature_flag_cb/1]).
enable_underlying_feature_flag_cb/1,
list/1]).

-type deprecated_feature_modattr() :: {rabbit_feature_flags:feature_name(),
feature_props()}.
Expand Down Expand Up @@ -202,6 +203,10 @@
%% needed. Other added properties are the same as {@link
%% rabbit_feature_flags:feature_props_extended()}.

-type deprecated_features() ::
#{rabbit_feature_flags:feature_name() =>
feature_props_extended()}.

-type callbacks() :: is_feature_used_callback().
%% All possible callbacks.

Expand Down Expand Up @@ -346,6 +351,28 @@ get_warning(FeatureProps, Permitted) when is_map(FeatureProps) ->
maps:get(when_removed, Msgs)
end.

-spec list(Which :: all | used) -> deprecated_features().
%% @doc
%% Lists all or used deprecated features, depending on the argument.
%%
%% @param Which The group of deprecated features to return: `all' or `used'.
%% @returns A map of selected deprecated features.

list(all) ->
maps:filter(
fun(_, FeatureProps) -> ?IS_DEPRECATION(FeatureProps) end,
rabbit_ff_registry_wrapper:list(all));
list(used) ->
maps:filter(
fun(FeatureName, FeatureProps) ->
?IS_DEPRECATION(FeatureProps)
and
is_deprecated_feature_in_use(
#{feature_name => FeatureName,
feature_props => FeatureProps}) =:= true
end,
rabbit_ff_registry_wrapper:list(all)).

%% -------------------------------------------------------------------
%% Internal functions.
%% -------------------------------------------------------------------
Expand Down Expand Up @@ -581,24 +608,27 @@ should_log_warning(FeatureName) ->

enable_underlying_feature_flag_cb(
#{command := enable,
feature_name := FeatureName,
feature_props := #{callbacks := Callbacks}} = Args) ->
feature_name := FeatureName} = Args) ->
IsUsed = is_deprecated_feature_in_use(Args),
case IsUsed of
true ->
?LOG_ERROR(
"Deprecated features: `~ts`: can't deny deprecated "
"feature because it is actively used",
[FeatureName],
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
{error,
{failed_to_deny_deprecated_features, [FeatureName]}};
_ ->
ok
end.

is_deprecated_feature_in_use(
#{feature_props := #{callbacks := Callbacks}} = Args1) ->
case Callbacks of
#{is_feature_used := {CallbackMod, CallbackFun}} ->
Args1 = Args#{command => is_feature_used},
IsUsed = erlang:apply(CallbackMod, CallbackFun, [Args1]),
case IsUsed of
false ->
ok;
true ->
?LOG_ERROR(
"Deprecated features: `~ts`: can't deny deprecated "
"feature because it is actively used",
[FeatureName],
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
{error,
{failed_to_deny_deprecated_features, [FeatureName]}}
end;
Args = Args1#{command => is_feature_used},
erlang:apply(CallbackMod, CallbackFun, [Args]);
_ ->
ok
undefined
end.
53 changes: 52 additions & 1 deletion deps/rabbit/test/deprecated_features_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
get_appropriate_warning_when_disconnected/1,
get_appropriate_warning_when_removed/1,
deprecated_feature_enabled_if_feature_flag_depends_on_it/1,
list_all_deprecated_features/1,
list_used_deprecated_features/1,

feature_is_unused/1,
feature_is_used/1
Expand Down Expand Up @@ -67,7 +69,9 @@ groups() ->
get_appropriate_warning_when_denied,
get_appropriate_warning_when_disconnected,
get_appropriate_warning_when_removed,
deprecated_feature_enabled_if_feature_flag_depends_on_it
deprecated_feature_enabled_if_feature_flag_depends_on_it,
list_all_deprecated_features,
list_used_deprecated_features
],
[
{cluster_size_1, [], Tests},
Expand Down Expand Up @@ -726,3 +730,50 @@ deprecated_feature_enabled_if_feature_flag_depends_on_it(Config) ->
ok
end )
|| Node <- AllNodes].

list_all_deprecated_features(Config) ->
[FirstNode | _] = AllNodes = ?config(nodes, Config),
feature_flags_v2_SUITE:connect_nodes(AllNodes),
feature_flags_v2_SUITE:override_running_nodes(AllNodes),

FeatureName = ?FUNCTION_NAME,
FeatureFlags = #{FeatureName =>
#{provided_by => rabbit,
deprecation_phase => permitted_by_default}},
?assertEqual(
ok,
feature_flags_v2_SUITE:inject_on_nodes(AllNodes, FeatureFlags)),

feature_flags_v2_SUITE:run_on_node(
FirstNode,
fun() ->
Map = rabbit_deprecated_features:list(all),
?assert(maps:is_key(FeatureName, Map))
end).

list_used_deprecated_features(Config) ->
[FirstNode | _] = AllNodes = ?config(nodes, Config),
feature_flags_v2_SUITE:connect_nodes(AllNodes),
feature_flags_v2_SUITE:override_running_nodes(AllNodes),

UsedFeatureName = used_deprecated_feature,
UnusedFeatureName = unused_deprecated_feature,
FeatureFlags = #{UsedFeatureName =>
#{provided_by => rabbit,
deprecation_phase => permitted_by_default,
callbacks => #{is_feature_used => {?MODULE, feature_is_used}}},
UnusedFeatureName =>
#{provided_by => rabbit,
deprecation_phase => permitted_by_default,
callbacks => #{is_feature_used => {?MODULE, feature_is_unused}}}},
?assertEqual(
ok,
feature_flags_v2_SUITE:inject_on_nodes(AllNodes, FeatureFlags)),

feature_flags_v2_SUITE:run_on_node(
FirstNode,
fun() ->
Map = rabbit_deprecated_features:list(used),
?assertNot(maps:is_key(UnusedFeatureName, Map)),
?assert(maps:is_key(UsedFeatureName, Map))
end).
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
## This Source Code Form is subject to the terms of the Mozilla Public
## License, v. 2.0. If a copy of the MPL was not distributed with this
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
##
## Copyright (c) 2023 Broadcom. All Rights Reserved. The term “Broadcom”
## refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.

defmodule RabbitMQ.CLI.Ctl.Commands.ListDeprecatedFeaturesCommand do
alias RabbitMQ.CLI.Core.{DocGuide, Validators}
alias RabbitMQ.CLI.Ctl.InfoKeys

@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput

def formatter(), do: RabbitMQ.CLI.Formatters.Table

@info_keys ~w(name deprecation_phase provided_by desc doc_url)a

def info_keys(), do: @info_keys

def scopes(), do: [:ctl, :diagnostics]

def switches(), do: [used: :boolean]

def merge_defaults([], opts) do
{["name", "deprecation_phase"], Map.merge(%{used: false}, opts)}
end

def merge_defaults(args, opts) do
{args, Map.merge(%{used: false}, opts)}
end

def validate(args, _) do
case InfoKeys.validate_info_keys(args, @info_keys) do
{:ok, _} -> :ok
err -> err
end
end

def validate_execution_environment(args, opts) do
Validators.chain(
[
&Validators.rabbit_is_loaded/2,
&Validators.rabbit_is_running/2
],
[args, opts]
)
end

def run([_ | _] = args, %{node: node_name, timeout: timeout, used: false}) do
case :rabbit_misc.rpc_call(
node_name,
:rabbit_depr_ff_extra,
:cli_info,
[:all],
timeout
) do
# Server does not support deprecated features, consider none are available.
{:badrpc, {:EXIT, {:undef, _}}} -> []
{:badrpc, _} = err -> err
val -> filter_by_arg(val, args)
end
end

def run([_ | _] = args, %{node: node_name, timeout: timeout, used: true}) do
case :rabbit_misc.rpc_call(
node_name,
:rabbit_deprecated_feature_extra,
:cli_info,
[:used],
timeout
) do
# Server does not support deprecated features, consider none are available.
{:badrpc, {:EXIT, {:undef, _}}} -> []
{:badrpc, _} = err -> err
val -> filter_by_arg(val, args)
end
end

def banner(_, %{used: false}), do: "Listing deprecated features ..."
def banner(_, %{used: true}), do: "Listing deprecated features in use ..."

def usage, do: "list_deprecated_features [--used] [<column> ...]"

def usage_additional() do
[
["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")],
["--used", "returns deprecated features in use"]
]
end

def usage_doc_guides() do
[
DocGuide.feature_flags()
]
end

def help_section(), do: :feature_flags

def description(), do: "Lists deprecated features"

#
# Implementation
#

defp filter_by_arg(ff_info, _) when is_tuple(ff_info) do
# tuple means unexpected data
ff_info
end

defp filter_by_arg(ff_info, [_ | _] = args) when is_list(ff_info) do
symbol_args = InfoKeys.prepare_info_keys(args)

Enum.map(
ff_info,
fn ff ->
symbol_args
|> Enum.filter(fn arg -> ff[arg] != nil end)
|> Enum.map(fn arg -> {arg, ff[arg]} end)
end
)
end
end
Loading
Loading