./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(HostType :: host_type(), Opts :: module_opts()) -> any().
71 -callback stop(HostType :: host_type()) -> any().
72 -callback hooks(HostType :: host_type()) -> gen_hook:hook_list().
73 -callback supported_features() -> [module_feature()].
74 -callback config_spec() -> mongoose_config_spec:config_section().
75
76 %% Optional callback specifying module dependencies.
77 %% The dependent module can specify parameters with which the dependee should be
78 %% started (the parameters will be merged with params given in user config and
79 %% by other modules).
80 %% The last element of the tuple specifies whether the ordering can be broken in
81 %% case of cycle (in that case soft dependency may be started after the
82 %% dependent module).
83 %%
84 %% TODO: think about getting rid of HostType param for deps/2 interface, currently
85 %% it's used only by global_distrib modules (see mod_global_distrib_utils:deps/4
86 %% function).
87 -callback deps(host_type(), module_opts()) -> gen_mod_deps:deps().
88
89 -optional_callbacks([hooks/1, config_spec/0, supported_features/0, deps/2]).
90
91 %% @doc This function should be called by mongoose_modules only.
92 %% To start a new module at runtime, use mongoose_modules:ensure_module/3 instead.
93 -spec start_module(host_type(), module(), module_opts()) -> {ok, term()}.
94 start_module(HostType, Module, Opts) ->
95 6216 assert_loaded(HostType, Module),
96 6216 start_module_for_host_type(HostType, Module, Opts).
97
98 start_module_for_host_type(HostType, Module, Opts) ->
99 6216 {links, LinksBefore} = erlang:process_info(self(), links),
100 6216 try
101 6216 lists:map(fun mongoose_service:assert_loaded/1,
102 get_required_services(HostType, Module, Opts)),
103 6216 check_dynamic_domains_support(HostType, Module),
104 6216 Res = Module:start(HostType, Opts),
105 6215 run_for_hooks(HostType, fun gen_hook:add_handlers/1, Module),
106 6215 {links, LinksAfter} = erlang:process_info(self(), links),
107 6215 case lists:sort(LinksBefore) =:= lists:sort(LinksAfter) of
108 6215 true -> ok;
109 false ->
110 %% TODO: grepping for "fail_ci_build=true" is bad option
111 %% for ci testing, rework this.
112
:-(
CIInfo = "fail_ci_build=true ",
113 %% Note for programmers:
114 %% Never call start_link directly from your_module:start/2 function!
115 %% The process will be killed if we start modules remotely or in shell
116
:-(
?LOG_ERROR(#{what => unexpected_links, ci_info => CIInfo,
117
:-(
links_before => LinksBefore, links_after => LinksAfter})
118 end,
119 6215 ?LOG_DEBUG(#{what => module_started, module => Module, host_type => HostType}),
120 % normalise result
121 6215 case Res of
122 579 {ok, R} -> {ok, R};
123 5636 _ -> {ok, Res}
124 end
125 catch
126 Class:Reason:StackTrace ->
127 1 ErrorText = io_lib:format("Problem starting the module ~p for "
128 "host_type ~p~n options: ~p~n ~p: ~p~n~p",
129 [Module, HostType, Opts, Class, Reason,
130 StackTrace]),
131 1 ?LOG_CRITICAL(#{what => module_start_failed, module => Module,
132 host_type => HostType, opts => Opts, class => Class,
133
:-(
reason => Reason, stacktrace => StackTrace}),
134 1 case is_mim_or_ct_running() of
135 true ->
136 1 erlang:raise(Class, Reason, StackTrace);
137 false ->
138
:-(
?LOG_CRITICAL(#{what => mim_initialization_aborted,
139 text => <<"mongooseim initialization was aborted "
140 "because a module start failed.">>,
141 class => Class, reason => Reason,
142
:-(
stacktrace => StackTrace}),
143
:-(
timer:sleep(3000),
144
:-(
erlang:halt(string:substr(lists:flatten(ErrorText),
145 1, 199))
146 end
147 end.
148
149 run_for_hooks(HostType, Fun, Module) ->
150 12414 case erlang:function_exported(Module, hooks, 1) of
151 8808 true -> Fun(Module:hooks(HostType));
152 3606 false -> ok
153 end.
154
155 check_dynamic_domains_support(HostType, Module) ->
156 6216 case lists:member(HostType, ?MYHOSTS) of
157 4952 true -> ok;
158 false ->
159 1264 case gen_mod:does_module_support(Module, dynamic_domains) of
160 1264 true -> ok;
161 false ->
162
:-(
error({Module, HostType, dynamic_domains_feature_is_not_supported})
163 end
164 end.
165
166 is_mim_or_ct_running() ->
167 1 ?MODULE:is_app_running(mongooseim)
168 %% Common tests would be very confused if we kill the whole node
169
:-(
orelse is_common_test_running().
170
171 is_common_test_running() ->
172
:-(
try
173
:-(
is_list(ct:get_status())
174 catch _:_ ->
175
:-(
false
176 end.
177
178 -spec is_app_running(_) -> boolean().
179 is_app_running(AppName) ->
180 %% Use a high timeout to prevent a false positive in a high load system
181 1 Timeout = 15000,
182 1 lists:keymember(AppName, 1, application:which_applications(Timeout)).
183
184 %% @doc This function should be called by mongoose_modules only.
185 %% To stop a module at runtime, use mongoose_modules:ensure_stopped/2 instead.
186 -spec stop_module(host_type(), module()) -> ok.
187 stop_module(HostType, Module) ->
188 6199 assert_loaded(HostType, Module),
189 6199 stop_module_for_host_type(HostType, Module).
190
191 -spec stop_module_for_host_type(host_type(), module()) -> ok.
192 stop_module_for_host_type(HostType, Module) ->
193 6199 run_for_hooks(HostType, fun gen_hook:delete_handlers/1, Module),
194 6199 try Module:stop(HostType) of
195 {wait, ProcList} when is_list(ProcList) ->
196
:-(
lists:foreach(fun wait_for_process/1, ProcList);
197 {wait, Process} ->
198
:-(
wait_for_process(Process);
199 _ ->
200 6199 ok
201 catch Class:Reason:Stacktrace ->
202
:-(
?LOG_ERROR(#{what => module_stopping_failed,
203 host_type => HostType, stop_module => Module,
204
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
205
:-(
erlang:raise(Class, Reason, Stacktrace)
206 end.
207
208 -spec does_module_support(module(), module_feature()) -> boolean().
209 does_module_support(Module, Feature) ->
210 2374 lists:member(Feature, get_supported_features(Module)).
211
212 -spec get_supported_features(module()) -> [module_feature()].
213 get_supported_features(Module) ->
214 %% if module is not loaded, erlang:function_exported/3 returns false
215 2374 case erlang:function_exported(Module, supported_features, 0) of
216 2374 true -> apply(Module, supported_features, []);
217
:-(
false -> []
218 end.
219
220 -spec config_spec(module()) -> mongoose_config_spec:config_section().
221 config_spec(Module) ->
222 7904 Module:config_spec().
223
224 -spec wait_for_process(atom() | pid() | {atom(), atom()}) -> 'ok'.
225 wait_for_process(Process) ->
226
:-(
MonitorReference = erlang:monitor(process, Process),
227
:-(
case wait_for_stop(MonitorReference) of
228
:-(
ok -> ok;
229 timeout ->
230
:-(
catch exit(whereis(Process), kill),
231
:-(
wait_for_stop(MonitorReference),
232
:-(
ok
233 end.
234
235 -spec wait_for_stop(reference()) -> 'ok' | timeout.
236 wait_for_stop(MonitorReference) ->
237
:-(
receive
238 {'DOWN', MonitorReference, _Type, _Object, _Info} ->
239
:-(
ok
240 after 5000 ->
241
:-(
timeout
242 end.
243
244 -spec get_opt(opt_key() | key_path(), module_opts()) -> opt_value().
245 get_opt(Path, Opts) when is_list(Path), is_map(Opts) ->
246
:-(
lists:foldl(fun maps:get/2, Opts, Path);
247 get_opt(Opt, Opts) when is_map(Opts) ->
248 4475 maps:get(Opt, Opts).
249
250 -spec get_opt(opt_key() | key_path(), module_opts(), opt_value()) -> opt_value().
251 get_opt(Path, Opts, Default) ->
252 1730 try
253 1730 get_opt(Path, Opts)
254 catch
255 489 error:{badkey, _} -> Default
256 end.
257
258 -spec lookup_module_opt(mongooseim:host_type(), module(), opt_key() | key_path()) ->
259 {ok, opt_value()} | {error, not_found}.
260 lookup_module_opt(HostType, Module, Key) ->
261 124 mongoose_config:lookup_opt(config_path(HostType, Module, Key)).
262
263 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path(), opt_value()) ->
264 opt_value().
265 get_module_opt(HostType, Module, Key, Default) ->
266 %% Fail in dev builds.
267 %% It protects against passing something weird as a Module argument
268 %% or against wrong argument order.
269 49587 ?ASSERT_MODULE(Module),
270 49587 mongoose_config:get_opt(config_path(HostType, Module, Key), Default).
271
272 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path()) -> opt_value().
273 get_module_opt(HostType, Module, Key) ->
274 124922 mongoose_config:get_opt(config_path(HostType, Module, Key)).
275
276 -spec config_path(mongooseim:host_type(), module(), opt_key() | key_path()) -> key_path().
277 config_path(HostType, Module, Path) when is_list(Path) ->
278 2563 [{modules, HostType}, Module] ++ Path;
279 config_path(HostType, Module, Key) when is_atom(Key) ->
280 172070 [{modules, HostType}, Module, Key].
281
282 -spec get_module_opts(mongooseim:host_type(), module()) -> module_opts().
283 get_module_opts(HostType, Module) ->
284 1267 ?ASSERT_MODULE(Module),
285 1267 mongoose_config:get_opt([{modules, HostType}, Module], #{}).
286
287 -spec get_loaded_module_opts(mongooseim:host_type(), module()) -> module_opts().
288 get_loaded_module_opts(HostType, Module) ->
289 1618 mongoose_config:get_opt([{modules, HostType}, Module]).
290
291 -spec loaded_modules() -> [module()].
292 loaded_modules() ->
293 10 lists:usort(lists:flatmap(fun loaded_modules/1, ?ALL_HOST_TYPES)).
294
295 -spec loaded_modules(host_type()) -> [module()].
296 loaded_modules(HostType) ->
297 629 maps:keys(mongoose_config:get_opt({modules, HostType})).
298
299 -spec loaded_modules_with_opts(host_type()) -> #{module() => module_opts()}.
300 loaded_modules_with_opts(HostType) ->
301 27846 mongoose_config:get_opt({modules, HostType}).
302
303 -spec loaded_modules_with_opts() -> #{host_type() => #{module() => module_opts()}}.
304 loaded_modules_with_opts() ->
305
:-(
maps:from_list([{HostType, loaded_modules_with_opts(HostType)} || HostType <- ?ALL_HOST_TYPES]).
306
307 -spec hosts_with_module(module()) -> [host_type()].
308 hosts_with_module(Module) ->
309 34 [HostType || HostType <- ?ALL_HOST_TYPES, is_loaded(HostType, Module)].
310
311 -spec hosts_and_opts_with_module(module()) -> #{host_type() => module_opts()}.
312 hosts_and_opts_with_module(Module) ->
313
:-(
maps:from_list(
314 lists:flatmap(fun(HostType) ->
315
:-(
case mongoose_config:lookup_opt([{modules, HostType}, Module]) of
316
:-(
{error, not_found} -> [];
317
:-(
{ok, Opts} -> [{HostType, Opts}]
318 end
319 end, ?ALL_HOST_TYPES)).
320
321 -spec get_module_proc(binary() | string(), module()) -> atom().
322 %% TODO:
323 %% split this interface into 2:
324 %% * create_module_proc_name/2 - which can create new atoms by calling list_to_atom/1
325 %% * get_module_proc_name/2 - which should use safe list_to_existing_atom/1 function
326 get_module_proc(Host, Base) when is_binary(Host) ->
327 39906 get_module_proc(binary_to_list(Host), Base);
328 get_module_proc(Host, Base) ->
329 39906 list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
330
331 -spec assert_loaded(mongooseim:host_type(), module()) -> ok.
332 assert_loaded(HostType, Module) ->
333 12415 case is_loaded(HostType, Module) of
334 true ->
335 12415 ok;
336 false ->
337
:-(
error(#{what => module_not_loaded,
338 text => <<"Module missing from mongoose_config">>,
339 host_type => HostType,
340 module => Module})
341 end.
342
343 -spec is_loaded(HostType :: binary(), Module :: atom()) -> boolean().
344 is_loaded(HostType, Module) ->
345 27258 maps:is_key(Module, loaded_modules_with_opts(HostType)).
346
347 -spec get_deps(host_type(), module(), module_opts()) -> gen_mod_deps:module_deps().
348 get_deps(HostType, Module, Opts) ->
349 54433 case mongoose_lib:is_exported(Module, deps, 2) of
350 true ->
351 2866 Deps = Module:deps(HostType, Opts),
352 2866 lists:filter(fun(D) -> element(1, D) =/= service end, Deps);
353 _ ->
354 51567 []
355 end.
356
357 -spec get_required_services(host_type(), module(), module_opts()) -> [mongoose_service:service()].
358 get_required_services(HostType, Module, Options) ->
359 6216 case mongoose_lib:is_exported(Module, deps, 2) of
360 true ->
361 507 [Service || {service, Service} <- Module:deps(HostType, Options)];
362 _ ->
363 5709 []
364 end.
Line Hits Source