summaryrefslogtreecommitdiff
path: root/deps/rabbit/src/rabbit_ff_registry.erl
blob: 0ec784c8a0e46531b406345748cf3c40b4ac5744 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
%% 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) 2018-2023 VMware, Inc. or its affiliates.  All rights reserved.
%%

%% @author The RabbitMQ team
%% @copyright 2018-2023 VMware, Inc. or its affiliates.
%%
%% @doc
%% This module exposes the API of the {@link rabbit_feature_flags}
%% registry. The feature flags registry is an Erlang module, compiled at
%% runtime, storing all the informations about feature flags: which are
%% supported, which are enabled, etc.
%%
%% Because it is compiled at runtime, the initial source code is mostly
%% an API reference. What the initial module does is merely ask {@link
%% rabbit_feature_flags} to generate the real registry.

-module(rabbit_ff_registry).

-include_lib("kernel/include/logger.hrl").

-include_lib("rabbit_common/include/logging.hrl").

-export([get/1,
         list/1,
         states/0,
         is_supported/1,
         is_enabled/1,
         is_registry_initialized/0,
         is_registry_written_to_disk/0,
         inventory/0]).

-ifdef(TEST).
-on_load(on_load/0).
-endif.

%% In this registry stub, most functions want to return `init_required' to let
%% {@link rabbit_ff_registry_wrapper} do the first time initialization.
%%
%% The inner case statement is here to convince Dialyzer that the function
%% could return values of type `__ReturnedIfUninitialized' or
%% `__NeverReturned'.
%%
%% If the function was only calling itself (`Call'), Dialyzer would consider
%% that it would never return. With the outer case, Dialyzer would conclude
%% that `__ReturnedIfUninitialized' is always returned and other values will
%% never be returned and there is no point in expecting them.
%%
%% In the end, `Call' is never executed because {@link
%% rabbit_ff_registry_wrapper} is responsible for calling the registry
%% function again after initialization.
%%
%% With both cases in place, it seems that we can convince Dialyzer that the
%% function returns values matching its spec.
-define(convince_dialyzer(__Call, __ReturnedIfUninitialized, __NeverReturned),
        case always_return_true() of
            false ->
                __Call;
            true ->
                case always_return_true() of
                    true  -> __ReturnedIfUninitialized;
                    false -> __NeverReturned
                end
        end).

-spec get(FeatureName) -> Ret when
      FeatureName :: rabbit_feature_flags:feature_name(),
      Ret :: FeatureProps | init_required,
      FeatureProps :: rabbit_feature_flags:feature_props_extended() |
                      undefined.
%% @doc
%% Returns the properties of a feature flag.
%%
%% Only the informations stored in the local registry is used to answer
%% this call.
%%
%% @param FeatureName The name of the feature flag.
%% @returns the properties of the specified feature flag.

get(FeatureName) ->
    ?convince_dialyzer(
       ?MODULE:get(FeatureName),
       init_required,
       #{provided_by => rabbit}).

-spec list(Which) -> Ret when
      Which :: all | enabled | disabled,
      Ret :: FeatureFlags | init_required,
      FeatureFlags :: rabbit_feature_flags:feature_flags().
%% @doc
%% Lists all, enabled or disabled feature flags, depending on the argument.
%%
%% Only the informations stored in the local registry is used to answer
%% this call.
%%
%% @param Which The group of feature flags to return: `all', `enabled' or
%% `disabled'.
%% @returns A map of selected feature flags.

list(Which) ->
    ?convince_dialyzer(?MODULE:list(Which), init_required, #{}).

-spec states() -> Ret when
      Ret :: FeatureStates | init_required,
      FeatureStates :: rabbit_feature_flags:feature_states().
%% @doc
%% Returns the states of supported feature flags.
%%
%% Only the informations stored in the local registry is used to answer
%% this call.
%%
%% @returns A map of feature flag states.

states() ->
    ?convince_dialyzer(?MODULE:states(), init_required, #{}).

-spec is_supported(FeatureName) -> Ret when
      FeatureName :: rabbit_feature_flags:feature_name(),
      Ret :: Supported | init_required,
      Supported :: boolean().
%% @doc
%% Returns if a feature flag is supported.
%%
%% Only the informations stored in the local registry is used to answer
%% this call.
%%
%% @param FeatureName The name of the feature flag to be checked.
%% @returns `true' if the feature flag is supported, or `false'
%%   otherwise.

is_supported(FeatureName) ->
    ?convince_dialyzer(?MODULE:is_supported(FeatureName), init_required, true).

-spec is_enabled(FeatureName) -> Ret when
      FeatureName :: rabbit_feature_flags:feature_name(),
      Ret :: Enabled | init_required,
      Enabled :: boolean() | state_changing.
%% @doc
%% Returns if a feature flag is enabled or if its state is changing.
%%
%% Only the informations stored in the local registry is used to answer
%% this call.
%%
%% @param FeatureName The name of the feature flag to be checked.
%% @returns `true' if the feature flag is enabled, `state_changing' if
%%   its state is transient, or `false' otherwise.

is_enabled(FeatureName) ->
    ?convince_dialyzer(?MODULE:is_enabled(FeatureName), init_required, true).

-spec is_registry_initialized() -> boolean().
%% @doc
%% Indicates if the registry is initialized.
%%
%% The registry is considered initialized once the initial Erlang module
%% was replaced by the copy compiled at runtime.
%%
%% @returns `true' when the module is the one compiled at runtime,
%%   `false' when the module is the initial one compiled from RabbitMQ
%%   source code.

is_registry_initialized() ->
    always_return_false().

-spec is_registry_written_to_disk() -> boolean().
%% @doc
%% Indicates if the feature flags state was successfully persisted to disk.
%%
%% Note that on startup, {@link rabbit_feature_flags} tries to determine
%% the state of each supported feature flag, regardless of the
%% information on disk, to ensure maximum consistency. However, this can
%% be done for feature flags supporting it only.
%%
%% @returns `true' if the state was successfully written to disk and
%%   the registry can be initialized from that during the next RabbitMQ
%%   startup, `false' if the write failed and the node might loose feature
%%   flags state on restart.

is_registry_written_to_disk() ->
    always_return_true().

-spec inventory() -> Ret when
      Ret :: Inventory | init_required,
      Inventory :: rabbit_feature_flags:inventory().

inventory() ->
    _ = rabbit_ff_registry_factory:initialize_registry(),
    Inventory = #{applications => [],
                  feature_flags => #{},
                  states => #{}},
    ?convince_dialyzer(?MODULE:inventory(), Inventory, Inventory).

always_return_true() ->
    %% This function is here to trick Dialyzer. We want some functions
    %% in this initial on-disk registry to always return `true` or
    %% `false`. However the generated registry will return actual
    %% booleans. The `-spec()` correctly advertises a return type of
    %% `boolean()`. But in the meantime, Dialyzer only knows about this
    %% copy which, without the trick below, would always return either
    %% `true` (e.g. in is_registry_written_to_disk/0) or `false` (e.g.
    %% is_registry_initialized/0). This obviously causes some warnings
    %% where the registry functions are used: Dialyzer believes that
    %% e.g. matching the return value of is_registry_initialized/0
    %% against `true` will never succeed.
    %%
    %% That's why this function makes a call which we know the result,
    %% but not Dialyzer, to "create" that hard-coded `true` return
    %% value.
    erlang:get({?MODULE, always_undefined}) =:= undefined.

always_return_false() ->
    not always_return_true().

-ifdef(TEST).
on_load() ->
     _ = (catch ?LOG_DEBUG(
                  "Feature flags: Loading initial (uninitialized) registry "
                  "module (~tp)",
                  [self()],
                  #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS})),
    ok.
-endif.