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