./ct_report/coverage/gen_mod.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : gen_mod.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose :
5 %%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(gen_mod).
27 -author('alexey@process-one.net').
28
29 -export_type([key_path/0, opt_key/0, opt_value/0, module_opts/0]).
30
31 -export([
32 % Modules start & stop, do NOT use in the tests, use mongoose_modules API instead
33 start_module/3,
34 stop_module/2,
35 does_module_support/2,
36 config_spec/1,
37 % Get/set opts by host or from a list
38 get_opt/2,
39 get_opt/3,
40 lookup_module_opt/3,
41 get_module_opt/3,
42 get_module_opt/4,
43 get_module_opts/2,
44 get_loaded_module_opts/2,
45
46 loaded_modules/0,
47 loaded_modules/1,
48 loaded_modules_with_opts/0,
49 loaded_modules_with_opts/1,
50 hosts_with_module/1,
51 hosts_and_opts_with_module/1,
52 get_module_proc/2,
53 is_loaded/2,
54 get_deps/3]).
55
56 -export([is_app_running/1]). % we have to mock it in some tests
57
58 -ignore_xref([loaded_modules_with_opts/0,
59 loaded_modules_with_opts/1, hosts_and_opts_with_module/1]).
60
61 -include("mongoose.hrl").
62
63 -type module_feature() :: atom().
64 -type host_type() :: mongooseim:host_type().
65 -type key_path() :: mongoose_config:key_path().
66 -type opt_key() :: atom().
67 -type opt_value() :: mongoose_config:value().
68 -type module_opts() :: #{opt_key() => opt_value()}.
69
70 -callback start(host_type(), module_opts()) -> any().
71 -callback stop(host_type()) -> any().
72 -callback hooks(host_type()) -> gen_hook:hook_list().
73 -callback supported_features() -> [module_feature()].
74 -callback config_spec() -> mongoose_config_spec:config_section().
75 -callback instrumentation(host_type()) -> [mongoose_instrument:spec()].
76
77 %% Optional callback specifying module dependencies.
78 %% The dependent module can specify parameters with which the dependee should be
79 %% started (the parameters will be merged with params given in user config and
80 %% by other modules).
81 %% The last element of the tuple specifies whether the ordering can be broken in
82 %% case of cycle (in that case soft dependency may be started after the
83 %% dependent module).
84 %%
85 %% TODO: think about getting rid of HostType param for deps/2 interface, currently
86 %% it's used only by global_distrib modules (see mod_global_distrib_utils:deps/4
87 %% function).
88 -callback deps(host_type(), module_opts()) -> gen_mod_deps:deps().
89
90 -optional_callbacks([hooks/1, config_spec/0, supported_features/0, instrumentation/1, deps/2]).
91
92 %% @doc This function should be called by mongoose_modules only.
93 %% To start a new module at runtime, use mongoose_modules:ensure_module/3 instead.
94 -spec start_module(host_type(), module(), module_opts()) -> {ok, term()}.
95 start_module(HostType, Module, Opts) ->
96 6216 assert_loaded(HostType, Module),
97 6216 start_module_for_host_type(HostType, Module, Opts).
98
99 start_module_for_host_type(HostType, Module, Opts) ->
100 6216 {links, LinksBefore} = erlang:process_info(self(), links),
101 6216 try
102 6216 lists:map(fun mongoose_service:assert_loaded/1,
103 get_required_services(HostType, Module, Opts)),
104 6216 check_dynamic_domains_support(HostType, Module),
105 6216 run_for_instrumentation(HostType, fun mongoose_instrument:set_up/1, Module),
106 6216 Res = Module:start(HostType, Opts),
107 6215 run_for_hooks(HostType, fun gen_hook:add_handlers/1, Module),
108 6215 {links, LinksAfter} = erlang:process_info(self(), links),
109 6215 case lists:sort(LinksBefore) =:= lists:sort(LinksAfter) of
110 6215 true -> ok;
111 false ->
112 %% TODO: grepping for "fail_ci_build=true" is bad option
113 %% for ci testing, rework this.
114
:-(
CIInfo = "fail_ci_build=true ",
115 %% Note for programmers:
116 %% Never call start_link directly from your_module:start/2 function!
117 %% The process will be killed if we start modules remotely or in shell
118
:-(
?LOG_ERROR(#{what => unexpected_links, ci_info => CIInfo,
119
:-(
links_before => LinksBefore, links_after => LinksAfter})
120 end,
121 6215 ?LOG_DEBUG(#{what => module_started, module => Module, host_type => HostType}),
122 % normalise result
123 6215 case Res of
124 579 {ok, R} -> {ok, R};
125 5636 _ -> {ok, Res}
126 end
127 catch
128 Class:Reason:StackTrace ->
129 1 ErrorText = io_lib:format("Problem starting the module ~p for "
130 "host_type ~p~n options: ~p~n ~p: ~p~n~p",
131 [Module, HostType, Opts, Class, Reason,
132 StackTrace]),
133 1 ?LOG_CRITICAL(#{what => module_start_failed, module => Module,
134 host_type => HostType, opts => Opts, class => Class,
135
:-(
reason => Reason, stacktrace => StackTrace}),
136 1 case is_mim_or_ct_running() of
137 true ->
138 1 erlang:raise(Class, Reason, StackTrace);
139 false ->
140
:-(
?LOG_CRITICAL(#{what => mim_initialization_aborted,
141 text => <<"mongooseim initialization was aborted "
142 "because a module start failed.">>,
143 class => Class, reason => Reason,
144
:-(
stacktrace => StackTrace}),
145
:-(
timer:sleep(3000),
146
:-(
erlang:halt(string:substr(lists:flatten(ErrorText),
147 1, 199))
148 end
149 end.
150
151 run_for_hooks(HostType, Fun, Module) ->
152 12414 case erlang:function_exported(Module, hooks, 1) of
153 9216 true -> Fun(Module:hooks(HostType));
154 3198 false -> ok
155 end.
156
157 run_for_instrumentation(HostType, Fun, Module) ->
158 12415 case erlang:function_exported(Module, instrumentation, 1) of
159 524 true -> Fun(Module:instrumentation(HostType));
160 11891 false -> ok
161 end.
162
163 check_dynamic_domains_support(HostType, Module) ->
164 6216 case lists:member(HostType, ?MYHOSTS) of
165 4952 true -> ok;
166 false ->
167 1264 case gen_mod:does_module_support(Module, dynamic_domains) of
168 1264 true -> ok;
169 false ->
170
:-(
error({Module, HostType, dynamic_domains_feature_is_not_supported})
171 end
172 end.
173
174 is_mim_or_ct_running() ->
175 1 ?MODULE:is_app_running(mongooseim)
176 %% Common tests would be very confused if we kill the whole node
177
:-(
orelse is_common_test_running().
178
179 is_common_test_running() ->
180
:-(
try
181
:-(
is_list(ct:get_status())
182 catch _:_ ->
183
:-(
false
184 end.
185
186 -spec is_app_running(_) -> boolean().
187 is_app_running(AppName) ->
188 %% Use a high timeout to prevent a false positive in a high load system
189 1 Timeout = 15000,
190 1 lists:keymember(AppName, 1, application:which_applications(Timeout)).
191
192 %% @doc This function should be called by mongoose_modules only.
193 %% To stop a module at runtime, use mongoose_modules:ensure_stopped/2 instead.
194 -spec stop_module(host_type(), module()) -> ok.
195 stop_module(HostType, Module) ->
196 6199 assert_loaded(HostType, Module),
197 6199 stop_module_for_host_type(HostType, Module).
198
199 -spec stop_module_for_host_type(host_type(), module()) -> ok.
200 stop_module_for_host_type(HostType, Module) ->
201 6199 run_for_hooks(HostType, fun gen_hook:delete_handlers/1, Module),
202 6199 try Module:stop(HostType) of
203 {wait, ProcList} when is_list(ProcList) ->
204
:-(
lists:foreach(fun wait_for_process/1, ProcList);
205 {wait, Process} ->
206
:-(
wait_for_process(Process);
207 _ ->
208 6199 ok
209 catch Class:Reason:Stacktrace ->
210
:-(
?LOG_ERROR(#{what => module_stopping_failed,
211 host_type => HostType, stop_module => Module,
212
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
213
:-(
erlang:raise(Class, Reason, Stacktrace)
214 end,
215 6199 run_for_instrumentation(HostType, fun mongoose_instrument:tear_down/1, Module).
216
217 -spec does_module_support(module(), module_feature()) -> boolean().
218 does_module_support(Module, Feature) ->
219 2374 lists:member(Feature, get_supported_features(Module)).
220
221 -spec get_supported_features(module()) -> [module_feature()].
222 get_supported_features(Module) ->
223 %% if module is not loaded, erlang:function_exported/3 returns false
224 2374 case erlang:function_exported(Module, supported_features, 0) of
225 2374 true -> apply(Module, supported_features, []);
226
:-(
false -> []
227 end.
228
229 -spec config_spec(module()) -> mongoose_config_spec:config_section().
230 config_spec(Module) ->
231 7904 Module:config_spec().
232
233 -spec wait_for_process(atom() | pid() | {atom(), atom()}) -> 'ok'.
234 wait_for_process(Process) ->
235
:-(
MonitorReference = erlang:monitor(process, Process),
236
:-(
case wait_for_stop(MonitorReference) of
237
:-(
ok -> ok;
238 timeout ->
239
:-(
catch exit(whereis(Process), kill),
240
:-(
wait_for_stop(MonitorReference),
241
:-(
ok
242 end.
243
244 -spec wait_for_stop(reference()) -> 'ok' | timeout.
245 wait_for_stop(MonitorReference) ->
246
:-(
receive
247 {'DOWN', MonitorReference, _Type, _Object, _Info} ->
248
:-(
ok
249 after 5000 ->
250
:-(
timeout
251 end.
252
253 -spec get_opt(opt_key() | key_path(), module_opts()) -> opt_value().
254 get_opt(Path, Opts) when is_list(Path), is_map(Opts) ->
255
:-(
lists:foldl(fun maps:get/2, Opts, Path);
256 get_opt(Opt, Opts) when is_map(Opts) ->
257 6587 maps:get(Opt, Opts).
258
259 -spec get_opt(opt_key() | key_path(), module_opts(), opt_value()) -> opt_value().
260 get_opt(Path, Opts, Default) ->
261 3950 try
262 3950 get_opt(Path, Opts)
263 catch
264 489 error:{badkey, _} -> Default
265 end.
266
267 -spec lookup_module_opt(mongooseim:host_type(), module(), opt_key() | key_path()) ->
268 {ok, opt_value()} | {error, not_found}.
269 lookup_module_opt(HostType, Module, Key) ->
270 124 mongoose_config:lookup_opt(config_path(HostType, Module, Key)).
271
272 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path(), opt_value()) ->
273 opt_value().
274 get_module_opt(HostType, Module, Key, Default) ->
275 %% Fail in dev builds.
276 %% It protects against passing something weird as a Module argument
277 %% or against wrong argument order.
278 50166 ?ASSERT_MODULE(Module),
279 50166 mongoose_config:get_opt(config_path(HostType, Module, Key), Default).
280
281 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path()) -> opt_value().
282 get_module_opt(HostType, Module, Key) ->
283 137301 mongoose_config:get_opt(config_path(HostType, Module, Key)).
284
285 -spec config_path(mongooseim:host_type(), module(), opt_key() | key_path()) -> key_path().
286 config_path(HostType, Module, Path) when is_list(Path) ->
287 2557 [{modules, HostType}, Module] ++ Path;
288 config_path(HostType, Module, Key) when is_atom(Key) ->
289 185034 [{modules, HostType}, Module, Key].
290
291 -spec get_module_opts(mongooseim:host_type(), module()) -> module_opts().
292 get_module_opts(HostType, Module) ->
293 1159 ?ASSERT_MODULE(Module),
294 1159 mongoose_config:get_opt([{modules, HostType}, Module], #{}).
295
296 -spec get_loaded_module_opts(mongooseim:host_type(), module()) -> module_opts().
297 get_loaded_module_opts(HostType, Module) ->
298 1612 mongoose_config:get_opt([{modules, HostType}, Module]).
299
300 -spec loaded_modules() -> [module()].
301 loaded_modules() ->
302 10 lists:usort(lists:flatmap(fun loaded_modules/1, ?ALL_HOST_TYPES)).
303
304 -spec loaded_modules(host_type()) -> [module()].
305 loaded_modules(HostType) ->
306 557 maps:keys(mongoose_config:get_opt({modules, HostType})).
307
308 -spec loaded_modules_with_opts(host_type()) -> #{module() => module_opts()}.
309 loaded_modules_with_opts(HostType) ->
310 28063 mongoose_config:get_opt({modules, HostType}).
311
312 -spec loaded_modules_with_opts() -> #{host_type() => #{module() => module_opts()}}.
313 loaded_modules_with_opts() ->
314
:-(
maps:from_list([{HostType, loaded_modules_with_opts(HostType)} || HostType <- ?ALL_HOST_TYPES]).
315
316 -spec hosts_with_module(module()) -> [host_type()].
317 hosts_with_module(Module) ->
318 34 [HostType || HostType <- ?ALL_HOST_TYPES, is_loaded(HostType, Module)].
319
320 -spec hosts_and_opts_with_module(module()) -> #{host_type() => module_opts()}.
321 hosts_and_opts_with_module(Module) ->
322
:-(
maps:from_list(
323 lists:flatmap(fun(HostType) ->
324
:-(
case mongoose_config:lookup_opt([{modules, HostType}, Module]) of
325
:-(
{error, not_found} -> [];
326
:-(
{ok, Opts} -> [{HostType, Opts}]
327 end
328 end, ?ALL_HOST_TYPES)).
329
330 -spec get_module_proc(binary() | string(), module()) -> atom().
331 %% TODO:
332 %% split this interface into 2:
333 %% * create_module_proc_name/2 - which can create new atoms by calling list_to_atom/1
334 %% * get_module_proc_name/2 - which should use safe list_to_existing_atom/1 function
335 get_module_proc(Host, Base) when is_binary(Host) ->
336 40457 get_module_proc(binary_to_list(Host), Base);
337 get_module_proc(Host, Base) ->
338 40457 list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
339
340 -spec assert_loaded(mongooseim:host_type(), module()) -> ok.
341 assert_loaded(HostType, Module) ->
342 12415 case is_loaded(HostType, Module) of
343 true ->
344 12415 ok;
345 false ->
346
:-(
error(#{what => module_not_loaded,
347 text => <<"Module missing from mongoose_config">>,
348 host_type => HostType,
349 module => Module})
350 end.
351
352 -spec is_loaded(HostType :: binary(), Module :: atom()) -> boolean().
353 is_loaded(HostType, Module) ->
354 27292 maps:is_key(Module, loaded_modules_with_opts(HostType)).
355
356 -spec get_deps(host_type(), module(), module_opts()) -> gen_mod_deps:module_deps().
357 get_deps(HostType, Module, Opts) ->
358 120345 case mongoose_lib:is_exported(Module, deps, 2) of
359 true ->
360 8050 Deps = Module:deps(HostType, Opts),
361 8050 lists:filter(fun(D) -> element(1, D) =/= service end, Deps);
362 _ ->
363 112295 []
364 end.
365
366 -spec get_required_services(host_type(), module(), module_opts()) -> [mongoose_service:service()].
367 get_required_services(HostType, Module, Options) ->
368 6216 case mongoose_lib:is_exported(Module, deps, 2) of
369 true ->
370 507 [Service || {service, Service} <- Module:deps(HostType, Options)];
371 _ ->
372 5709 []
373 end.
Line Hits Source