Skip to content

Commit b137279

Browse files
committed
rabbit_feature_flags: Introduce hard vs. soft required feature flags
[Why] Before this patch, required feature flags were basically checked during boot: they must have been enabled when they were mere stable feature flags. If they were not, the node refused to boot. This was easy for the developer because making a feature flag required allowed to remove the entire compatibility code. Very satisfying. Unfortunately, this was a pain point to end users, especially those who did not pay attention to RabbitMQ and the release notes and were just asking their package manager to update everything. They could end up with a node that refuse to boot. The only solution was to downgrade, enable the disabled stable feature flags, upgrade again. [How] This patch introduces two levels of requirement to required feature flags: * `hard`: this corresponds to the existing behavior where a node will refuse to boot if a hard required feature flag is not enabled before the upgrade. * `soft`: such a required feature flag will be automatically enabled during the upgrade to a version where it is marked as required. The level of requirement is set in the feature flag definition: -rabbit_feature_flag( {my_feature_flag, #{stability => required, require_level => hard }}). The default requirement level is `soft`. All existing required feature flags have now a requirement level of `hard`. The handling of soft required feature flag is done when the cluster feature flags states are verified and synchronized. If a required feature flag is not enabled yet, it is enabled at that time. This means that as developers, we will have to keep compatibility code forever for every soft required feature flag, like the feature flag definition itself.
1 parent 444df00 commit b137279

File tree

7 files changed

+299
-62
lines changed

7 files changed

+299
-62
lines changed

deps/rabbit/src/rabbit_core_ff.erl

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,81 +10,93 @@
1010
-rabbit_feature_flag(
1111
{classic_mirrored_queue_version,
1212
#{desc => "Support setting version for classic mirrored queues",
13-
stability => required
13+
stability => required,
14+
require_level => hard
1415
}}).
1516

1617
-rabbit_feature_flag(
1718
{quorum_queue,
1819
#{desc => "Support queues of type `quorum`",
1920
doc_url => "https://www.rabbitmq.com/docs/quorum-queues",
20-
stability => required
21+
stability => required,
22+
require_level => hard
2123
}}).
2224

2325
-rabbit_feature_flag(
2426
{stream_queue,
2527
#{desc => "Support queues of type `stream`",
2628
doc_url => "https://www.rabbitmq.com/docs/stream",
2729
stability => required,
30+
require_level => hard,
2831
depends_on => [quorum_queue]
2932
}}).
3033

3134
-rabbit_feature_flag(
3235
{implicit_default_bindings,
3336
#{desc => "Default bindings are now implicit, instead of "
3437
"being stored in the database",
35-
stability => required
38+
stability => required,
39+
require_level => hard
3640
}}).
3741

3842
-rabbit_feature_flag(
3943
{virtual_host_metadata,
4044
#{desc => "Virtual host metadata (description, tags, etc)",
41-
stability => required
45+
stability => required,
46+
require_level => hard
4247
}}).
4348

4449
-rabbit_feature_flag(
4550
{maintenance_mode_status,
4651
#{desc => "Maintenance mode status",
47-
stability => required
52+
stability => required,
53+
require_level => hard
4854
}}).
4955

5056
-rabbit_feature_flag(
51-
{user_limits,
52-
#{desc => "Configure connection and channel limits for a user",
53-
stability => required
57+
{user_limits,
58+
#{desc => "Configure connection and channel limits for a user",
59+
stability => required,
60+
require_level => hard
5461
}}).
5562

5663
-rabbit_feature_flag(
5764
{stream_single_active_consumer,
5865
#{desc => "Single active consumer for streams",
5966
doc_url => "https://www.rabbitmq.com/docs/stream",
6067
stability => required,
68+
require_level => hard,
6169
depends_on => [stream_queue]
6270
}}).
6371

6472
-rabbit_feature_flag(
65-
{feature_flags_v2,
66-
#{desc => "Feature flags subsystem V2",
67-
stability => required
73+
{feature_flags_v2,
74+
#{desc => "Feature flags subsystem V2",
75+
stability => required,
76+
require_level => hard
6877
}}).
6978

7079
-rabbit_feature_flag(
7180
{direct_exchange_routing_v2,
72-
#{desc => "v2 direct exchange routing implementation",
73-
stability => required,
74-
depends_on => [feature_flags_v2, implicit_default_bindings]
81+
#{desc => "v2 direct exchange routing implementation",
82+
stability => required,
83+
require_level => hard,
84+
depends_on => [feature_flags_v2, implicit_default_bindings]
7585
}}).
7686

7787
-rabbit_feature_flag(
7888
{listener_records_in_ets,
79-
#{desc => "Store listener records in ETS instead of Mnesia",
80-
stability => required,
81-
depends_on => [feature_flags_v2]
89+
#{desc => "Store listener records in ETS instead of Mnesia",
90+
stability => required,
91+
require_level => hard,
92+
depends_on => [feature_flags_v2]
8293
}}).
8394

8495
-rabbit_feature_flag(
8596
{tracking_records_in_ets,
8697
#{desc => "Store tracking records in ETS instead of Mnesia",
8798
stability => required,
99+
require_level => hard,
88100
depends_on => [feature_flags_v2]
89101
}}).
90102

@@ -94,6 +106,7 @@
94106
doc_url => "https://github.com/rabbitmq/rabbitmq-server/issues/5931",
95107
%%TODO remove compatibility code
96108
stability => required,
109+
require_level => hard,
97110
depends_on => [stream_queue]
98111
}}).
99112

@@ -102,6 +115,7 @@
102115
#{desc => "Support for restarting streams with optional preferred next leader argument."
103116
"Used to implement stream leader rebalancing",
104117
stability => required,
118+
require_level => hard,
105119
depends_on => [stream_queue]
106120
}}).
107121

@@ -110,20 +124,23 @@
110124
#{desc => "Bug fix to unblock a group of consumers in a super stream partition",
111125
doc_url => "https://github.com/rabbitmq/rabbitmq-server/issues/7743",
112126
stability => required,
127+
require_level => hard,
113128
depends_on => [stream_single_active_consumer]
114129
}}).
115130

116131
-rabbit_feature_flag(
117132
{stream_filtering,
118133
#{desc => "Support for stream filtering.",
119134
stability => required,
135+
require_level => hard,
120136
depends_on => [stream_queue]
121137
}}).
122138

123139
-rabbit_feature_flag(
124140
{message_containers,
125141
#{desc => "Message containers.",
126142
stability => required,
143+
require_level => hard,
127144
depends_on => [feature_flags_v2]
128145
}}).
129146

@@ -154,6 +171,7 @@
154171
#{desc => "A new internal command that is used to update streams as "
155172
"part of a policy.",
156173
stability => required,
174+
require_level => hard,
157175
depends_on => [stream_queue]
158176
}}).
159177

deps/rabbit/src/rabbit_feature_flags.erl

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
init/0,
106106
get_state/1,
107107
get_stability/1,
108+
get_require_level/1,
108109
check_node_compatibility/1, check_node_compatibility/2,
109110
sync_feature_flags_with_cluster/2,
110111
refresh_feature_flags_after_app_load/0,
@@ -147,6 +148,7 @@
147148
-type feature_props() :: #{desc => string(),
148149
doc_url => string(),
149150
stability => stability(),
151+
require_level => require_level(),
150152
depends_on => [feature_name()],
151153
callbacks =>
152154
#{callback_name() => callback_fun_name()}}.
@@ -183,6 +185,7 @@
183185
desc => string(),
184186
doc_url => string(),
185187
stability => stability(),
188+
require_level => require_level(),
186189
depends_on => [feature_name()],
187190
callbacks =>
188191
#{callback_name() => callback_fun_name()},
@@ -207,6 +210,15 @@
207210
%% Experimental feature flags are not enabled by default on a fresh RabbitMQ
208211
%% node. They must be enabled by the user.
209212

213+
-type require_level() :: hard | soft.
214+
%% The level of requirement of a feature flag.
215+
%%
216+
%% A hard required feature flags must be enabled before a RabbitMQ node is
217+
%% upgraded to a version where it is required.
218+
%%
219+
%% A soft required feature flag will be automatically enabled when a RabbitMQ
220+
%% node is upgraded to a version where it is required.
221+
210222
-type callback_fun_name() :: {Module :: module(), Function :: atom()}.
211223
%% The name of the module and function to call when changing the state of
212224
%% the feature flag.
@@ -755,6 +767,50 @@ get_stability(FeatureProps) when ?IS_DEPRECATION(FeatureProps) ->
755767
permitted_by_default -> experimental
756768
end.
757769

770+
-spec get_require_level
771+
(FeatureName) -> RequireLevel | undefined when
772+
FeatureName :: feature_name(),
773+
RequireLevel :: require_level() | none;
774+
(FeatureProps) -> RequireLevel when
775+
FeatureProps ::
776+
feature_props_extended() |
777+
rabbit_deprecated_features:feature_props_extended(),
778+
RequireLevel :: require_level() | none.
779+
%% @doc
780+
%% Returns the requirement level of a feature flag.
781+
%%
782+
%% The possible requirement levels are:
783+
%% <ul>
784+
%% <li>`hard': the feature flag must be enabled before the RabbitMQ node is
785+
%% upgraded to a version where it is hard required.</li>
786+
%% <li>`soft': the feature flag will be automatically enabled wher a RabbitMQ
787+
%% node is upgraded to a version where it is soft required.</li>
788+
%% <li>`none': the feature flag is not required.</li>
789+
%% </ul>
790+
%%
791+
%% @param FeatureName The name of the feature flag to check.
792+
%% @param FeatureProps A feature flag properties map.
793+
%% @returns `hard', `soft' or `none', or `undefined' if the given feature flag
794+
%% name doesn't correspond to a known feature flag.
795+
796+
get_require_level(FeatureName) when is_atom(FeatureName) ->
797+
case rabbit_ff_registry_wrapper:get(FeatureName) of
798+
undefined -> undefined;
799+
FeatureProps -> get_require_level(FeatureProps)
800+
end;
801+
get_require_level(FeatureProps) when ?IS_FEATURE_FLAG(FeatureProps) ->
802+
case get_stability(FeatureProps) of
803+
required -> maps:get(require_level, FeatureProps, soft);
804+
undefined -> undefined;
805+
_ -> none
806+
end;
807+
get_require_level(FeatureProps) when ?IS_DEPRECATION(FeatureProps) ->
808+
case get_stability(FeatureProps) of
809+
required -> hard;
810+
undefind -> undefined;
811+
_ -> none
812+
end.
813+
758814
%% -------------------------------------------------------------------
759815
%% Feature flags registry.
760816
%% -------------------------------------------------------------------
@@ -913,6 +969,7 @@ assert_feature_flag_is_valid(FeatureName, FeatureProps) ->
913969
ValidProps = [desc,
914970
doc_url,
915971
stability,
972+
require_level,
916973
depends_on,
917974
callbacks],
918975
?assertEqual([], maps:keys(FeatureProps) -- ValidProps),
@@ -1363,7 +1420,7 @@ run_feature_flags_mod_on_remote_node(Node, Function, Args, Timeout) ->
13631420
sync_feature_flags_with_cluster([] = _Nodes, true = _NodeIsVirgin) ->
13641421
rabbit_ff_controller:enable_default();
13651422
sync_feature_flags_with_cluster([] = _Nodes, false = _NodeIsVirgin) ->
1366-
ok;
1423+
rabbit_ff_controller:enable_required();
13671424
sync_feature_flags_with_cluster(Nodes, _NodeIsVirgin) ->
13681425
%% We don't use `rabbit_nodes:filter_running()' here because the given
13691426
%% `Nodes' list may contain nodes which are not members yet (the cluster

0 commit comments

Comments
 (0)