./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 start_backend_module/3,
35 stop_module/2,
36 does_module_support/2,
37 config_spec/1,
38 % Get/set opts by host or from a list
39 get_opt/2,
40 get_opt/3,
41 get_opt/4,
42 get_module_opt/3,
43 get_module_opt/4,
44 get_module_opts/2,
45 get_loaded_module_opts/2,
46 get_opt_subhost/3,
47 get_module_opt_subhost/3,
48
49 loaded_modules/0,
50 loaded_modules/1,
51 loaded_modules_with_opts/0,
52 loaded_modules_with_opts/1,
53 hosts_with_module/1,
54 hosts_and_opts_with_module/1,
55 get_module_proc/2,
56 is_loaded/2,
57 get_deps/3]).
58
59 -export([is_app_running/1]). % we have to mock it in some tests
60
61 -ignore_xref([loaded_modules_with_opts/0,
62 loaded_modules_with_opts/1, hosts_and_opts_with_module/1]).
63
64 -include("mongoose.hrl").
65
66 -type module_feature() :: atom().
67 -type domain_name() :: mongooseim:domain_name().
68 -type host_type() :: mongooseim:host_type().
69 -type key_path() :: mongoose_config:key_path().
70 -type opt_key() :: atom().
71 -type opt_value() :: mongoose_config:value().
72 -type module_opts() :: [{opt_key(), opt_value()}] % deprecated, will be removed
73 | #{opt_key() => opt_value()}. % recommended
74
75 -callback start(HostType :: host_type(), Opts :: module_opts()) -> any().
76 -callback stop(HostType :: host_type()) -> any().
77 -callback supported_features() -> [module_feature()].
78 -callback config_spec() -> mongoose_config_spec:config_section().
79
80 %% Optional callback specifying module dependencies.
81 %% The dependent module can specify parameters with which the dependee should be
82 %% started (the parameters will be merged with params given in user config and
83 %% by other modules).
84 %% The last element of the tuple specifies whether the ordering can be broken in
85 %% case of cycle (in that case soft dependency may be started after the
86 %% dependent module).
87 %%
88 %% TODO: think about getting rid of HostType param for deps/2 interface, currently
89 %% it's used only by global_distrib modules (see mod_global_distrib_utils:deps/4
90 %% function).
91 -callback deps(host_type(), module_opts()) -> gen_mod_deps:deps().
92
93 -optional_callbacks([config_spec/0, supported_features/0, deps/2]).
94
95 %% @doc This function should be called by mongoose_modules only.
96 %% To start a new module at runtime, use mongoose_modules:ensure_module/3 instead.
97 -spec start_module(HostType :: host_type(),
98 Module :: module(),
99 Opts :: [any()]) -> {ok, term()}.
100 start_module(HostType, Module, Opts) ->
101 4581 assert_loaded(HostType, Module),
102 4581 start_module_for_host_type(HostType, Module, Opts).
103
104 start_module_for_host_type(HostType, Module, Opts) ->
105 4581 {links, LinksBefore} = erlang:process_info(self(), links),
106 4581 try
107 4581 lists:map(fun mongoose_service:assert_loaded/1,
108 get_required_services(HostType, Module, Opts)),
109 4581 check_dynamic_domains_support(HostType, Module),
110 4581 Res = Module:start(HostType, Opts),
111 4580 {links, LinksAfter} = erlang:process_info(self(), links),
112 4580 case lists:sort(LinksBefore) =:= lists:sort(LinksAfter) of
113 4580 true -> ok;
114 false ->
115 %% TODO: grepping for "fail_ci_build=true" is bad option
116 %% for ci testing, rework this.
117
:-(
CIInfo = "fail_ci_build=true ",
118 %% Note for programmers:
119 %% Never call start_link directly from your_module:start/2 function!
120 %% The process will be killed if we start modules remotely or in shell
121
:-(
?LOG_ERROR(#{what => unexpected_links, ci_info => CIInfo,
122
:-(
links_before => LinksBefore, links_after => LinksAfter})
123 end,
124 4580 ?LOG_DEBUG(#{what => module_started, module => Module, host_type => HostType}),
125 % normalise result
126 4580 case Res of
127 398 {ok, R} -> {ok, R};
128 4182 _ -> {ok, Res}
129 end
130 catch
131 Class:Reason:StackTrace ->
132 1 ErrorText = io_lib:format("Problem starting the module ~p for "
133 "host_type ~p~n options: ~p~n ~p: ~p~n~p",
134 [Module, HostType, Opts, Class, Reason,
135 StackTrace]),
136 1 ?LOG_CRITICAL(#{what => module_start_failed, module => Module,
137 host_type => HostType, opts => Opts, class => Class,
138
:-(
reason => Reason, stacktrace => StackTrace}),
139 1 case is_mim_or_ct_running() of
140 true ->
141 1 erlang:raise(Class, Reason, StackTrace);
142 false ->
143
:-(
?LOG_CRITICAL(#{what => mim_initialization_aborted,
144 text => <<"mongooseim initialization was aborted "
145 "because a module start failed.">>,
146 class => Class, reason => Reason,
147
:-(
stacktrace => StackTrace}),
148
:-(
timer:sleep(3000),
149
:-(
erlang:halt(string:substr(lists:flatten(ErrorText),
150 1, 199))
151 end
152 end.
153
154 check_dynamic_domains_support(HostType, Module) ->
155 4581 case lists:member(HostType, ?MYHOSTS) of
156 3640 true -> ok;
157 false ->
158 941 case gen_mod:does_module_support(Module, dynamic_domains) of
159 941 true -> ok;
160 false ->
161
:-(
error({Module, HostType, dynamic_domains_feature_is_not_supported})
162 end
163 end.
164
165 is_mim_or_ct_running() ->
166 1 ?MODULE:is_app_running(mongooseim)
167 %% Common tests would be very confused if we kill the whole node
168
:-(
orelse is_common_test_running().
169
170 is_common_test_running() ->
171
:-(
try
172
:-(
is_list(ct:get_status())
173 catch _:_ ->
174
:-(
false
175 end.
176
177 %% @deprecated To be removed when mod_pubsub does not use it anymore
178 start_backend_module(Module, Opts, TrackedFuncs) ->
179 32 Backend = gen_mod:get_opt(backend, Opts, mnesia),
180 32 backend_module:create(Module, Backend, TrackedFuncs).
181
182 -spec is_app_running(_) -> boolean().
183 is_app_running(AppName) ->
184 %% Use a high timeout to prevent a false positive in a high load system
185 1 Timeout = 15000,
186 1 lists:keymember(AppName, 1, application:which_applications(Timeout)).
187
188 %% @doc This function should be called by mongoose_modules only.
189 %% To stop a module at runtime, use mongoose_modules:ensure_stopped/2 instead.
190 -spec stop_module(host_type(), module()) -> ok.
191 stop_module(HostType, Module) ->
192 4561 assert_loaded(HostType, Module),
193 4561 stop_module_for_host_type(HostType, Module).
194
195 -spec stop_module_for_host_type(host_type(), module()) -> ok.
196 stop_module_for_host_type(HostType, Module) ->
197 4561 try Module:stop(HostType) of
198 {wait, ProcList} when is_list(ProcList) ->
199
:-(
lists:foreach(fun wait_for_process/1, ProcList);
200 {wait, Process} ->
201
:-(
wait_for_process(Process);
202 _ ->
203 4561 ok
204 catch Class:Reason:Stacktrace ->
205
:-(
?LOG_ERROR(#{what => module_stopping_failed,
206 host_type => HostType, stop_module => Module,
207
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
208
:-(
erlang:raise(Class, Reason, Stacktrace)
209 end.
210
211 -spec does_module_support(module(), module_feature()) -> boolean().
212 does_module_support(Module, Feature) ->
213 1881 lists:member(Feature, get_supported_features(Module)).
214
215 -spec get_supported_features(module()) -> [module_feature()].
216 get_supported_features(Module) ->
217 %% if module is not loaded, erlang:function_exported/3 returns false
218 1881 case erlang:function_exported(Module, supported_features, 0) of
219 1881 true -> apply(Module, supported_features, []);
220
:-(
false -> []
221 end.
222
223 -spec config_spec(module()) -> mongoose_config_spec:config_section().
224 config_spec(Module) ->
225 6068 Module:config_spec().
226
227 -spec wait_for_process(atom() | pid() | {atom(), atom()}) -> 'ok'.
228 wait_for_process(Process) ->
229
:-(
MonitorReference = erlang:monitor(process, Process),
230
:-(
case wait_for_stop(MonitorReference) of
231
:-(
ok -> ok;
232 timeout ->
233
:-(
catch exit(whereis(Process), kill),
234
:-(
wait_for_stop(MonitorReference),
235
:-(
ok
236 end.
237
238 -spec wait_for_stop(reference()) -> 'ok' | timeout.
239 wait_for_stop(MonitorReference) ->
240
:-(
receive
241 {'DOWN', MonitorReference, _Type, _Object, _Info} ->
242
:-(
ok
243 after 5000 ->
244
:-(
timeout
245 end.
246
247 -spec get_opt(opt_key() | key_path(), module_opts()) -> opt_value().
248 get_opt(Path, Opts) when is_list(Path), is_map(Opts) ->
249 2971 lists:foldl(fun maps:get/2, Opts, Path);
250 get_opt(Opt, Opts) when is_map(Opts) ->
251 22571 maps:get(Opt, Opts);
252 get_opt(Opt, Opts) ->
253 8204 case lists:keysearch(Opt, 1, Opts) of
254 false ->
255 4963 throw({undefined_option, Opt});
256 {value, {_, Val}} ->
257 3241 Val
258 end.
259
260 -spec get_opt(opt_key() | key_path(), module_opts(), opt_value()) -> opt_value().
261 get_opt(Path, Opts, Default) ->
262 15978 try
263 15978 get_opt(Path, Opts)
264 catch
265 3300 error:{badkey, _} -> Default;
266 4737 throw:{undefined_option, _} -> Default
267 end.
268
269 %% @deprecated Processing should be done in the config spec
270 get_opt(Opt, Opts, F, Default) ->
271 322 case lists:keysearch(Opt, 1, Opts) of
272 false ->
273 253 Default;
274 {value, {_, Val}} ->
275 69 F(Val)
276 end.
277
278 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path(), opt_value()) ->
279 opt_value().
280 get_module_opt(HostType, Module, Opt, Default) ->
281 %% Fail in dev builds.
282 %% It protects against passing something weird as a Module argument
283 %% or against wrong argument order.
284 9831 ?ASSERT_MODULE(Module),
285 9831 ModuleOpts = get_module_opts(HostType, Module),
286 9831 get_opt(Opt, ModuleOpts, Default).
287
288 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path()) -> opt_value().
289 get_module_opt(HostType, Module, Opt) ->
290 14986 ?ASSERT_MODULE(Module),
291 14986 ModuleOpts = get_loaded_module_opts(HostType, Module),
292 14986 get_opt(Opt, ModuleOpts).
293
294 -spec get_module_opts(mongooseim:host_type(), module()) -> module_opts().
295 get_module_opts(HostType, Module) ->
296 10537 mongoose_config:get_opt([{modules, HostType}, Module], []).
297
298 -spec get_loaded_module_opts(mongooseim:host_type(), module()) -> module_opts().
299 get_loaded_module_opts(HostType, Module) ->
300 15124 mongoose_config:get_opt([{modules, HostType}, Module]).
301
302 -spec get_opt_subhost(domain_name(),
303 list(),
304 mongoose_subdomain_utils:subdomain_pattern()) ->
305 domain_name().
306 get_opt_subhost(Host, Opts, Default) ->
307 %% TODO: try to get rid of this interface
308 32 Val = get_opt(host, Opts, Default),
309 32 mongoose_subdomain_utils:get_fqdn(Val, Host).
310
311 -spec get_module_opt_subhost(domain_name(),
312 module(),
313 mongoose_subdomain_utils:subdomain_pattern()) ->
314 domain_name().
315 get_module_opt_subhost(Host, Module, Default) ->
316 %% TODO: try to get rid of this interface
317 %% note that get_module_opt/4 requires host_type(), while
318 %% mongoose_subdomain_utils:get_fqdn/2 expects domain_name()
319 22 Spec = get_module_opt(Host, Module, host, Default),
320 22 mongoose_subdomain_utils:get_fqdn(Spec, Host).
321
322 -spec loaded_modules() -> [module()].
323 loaded_modules() ->
324 3 lists:usort(lists:flatmap(fun loaded_modules/1, ?ALL_HOST_TYPES)).
325
326 -spec loaded_modules(host_type()) -> [module()].
327 loaded_modules(HostType) ->
328 487 maps:keys(mongoose_config:get_opt({modules, HostType})).
329
330 -spec loaded_modules_with_opts(host_type()) -> #{module() => module_opts()}.
331 loaded_modules_with_opts(HostType) ->
332 21421 mongoose_config:get_opt({modules, HostType}).
333
334 -spec loaded_modules_with_opts() -> #{host_type() => #{module() => module_opts()}}.
335 loaded_modules_with_opts() ->
336
:-(
maps:from_list([{HostType, loaded_modules_with_opts(HostType)} || HostType <- ?ALL_HOST_TYPES]).
337
338 -spec hosts_with_module(module()) -> [host_type()].
339 hosts_with_module(Module) ->
340 20 [HostType || HostType <- ?ALL_HOST_TYPES, is_loaded(HostType, Module)].
341
342 -spec hosts_and_opts_with_module(module()) -> #{host_type() => module_opts()}.
343 hosts_and_opts_with_module(Module) ->
344
:-(
maps:from_list(
345 lists:flatmap(fun(HostType) ->
346
:-(
case mongoose_config:lookup_opt([{modules, HostType}, Module]) of
347
:-(
{error, not_found} -> [];
348
:-(
{ok, Opts} -> [{HostType, Opts}]
349 end
350 end, ?ALL_HOST_TYPES)).
351
352 -spec get_module_proc(binary() | string(), module()) -> atom().
353 %% TODO:
354 %% split this interface into 2:
355 %% * create_module_proc_name/2 - which can create new atoms by calling list_to_atom/1
356 %% * get_module_proc_name/2 - which should use safe list_to_existing_atom/1 function
357 get_module_proc(Host, Base) when is_binary(Host) ->
358 28591 get_module_proc(binary_to_list(Host), Base);
359 get_module_proc(Host, Base) ->
360 29597 list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
361
362 -spec assert_loaded(mongooseim:host_type(), module()) -> ok.
363 assert_loaded(HostType, Module) ->
364 9142 case is_loaded(HostType, Module) of
365 true ->
366 9142 ok;
367 false ->
368
:-(
error(#{what => module_not_loaded,
369 text => <<"Module missing from mongoose_config">>,
370 host_type => HostType,
371 module => Module})
372 end.
373
374 -spec is_loaded(HostType :: binary(), Module :: atom()) -> boolean().
375 is_loaded(HostType, Module) ->
376 21138 maps:is_key(Module, loaded_modules_with_opts(HostType)).
377
378 -spec get_deps(host_type(), module(), module_opts()) -> gen_mod_deps:module_deps().
379 get_deps(HostType, Module, Opts) ->
380 %% the module has to be loaded,
381 %% otherwise the erlang:function_exported/3 returns false
382 28177 code:ensure_loaded(Module),
383 28177 case erlang:function_exported(Module, deps, 2) of
384 true ->
385 1154 Deps = Module:deps(HostType, Opts),
386 1154 lists:filter(fun(D) -> element(1, D) =/= service end, Deps);
387 _ ->
388 27023 []
389 end.
390
391 -spec get_required_services(host_type(), module(), module_opts()) -> [mongoose_service:service()].
392 get_required_services(HostType, Module, Options) ->
393 %% the module has to be loaded,
394 %% otherwise the erlang:function_exported/3 returns false
395 4581 code:ensure_loaded(Module),
396 4581 case erlang:function_exported(Module, deps, 2) of
397 true ->
398 234 [Service || {service, Service} <- Module:deps(HostType, Options)];
399 _ ->
400 4347 []
401 end.
Line Hits Source