./ct_report/coverage/mongoose_lazy_routing.COVER.html

1 %% Generally, you should not call anything from this module directly.
2 %%
3 %% This module works together with mongoose_router_dynamic_domains.
4 %% The lazy registration approach helps to speed up initialisation
5 %% of the new node in a cluster with a large number of dynamically
6 %% configured domains.
7 %%
8 %% In addition to dynamic domains/subdomains tracking, this module
9 %% is also responsible for the lazy IQ handlers registration for the
10 %% added domains/subdomains.
11 %%
12 %% while registration is done in a lazy manner, removal of domains,
13 %% subdomain and corresponding IQ handlers is triggered immediately
14 %% once domain or subdomain deleted from mongoose_subdomain_core or
15 %% mongoose_domain_core ETS table.
16
17 -module(mongoose_lazy_routing).
18 -behaviour(gen_server).
19
20 -include("mongoose_logger.hrl").
21
22 %% start/stop API
23 -export([start/0, stop/0]).
24 -export([start_link/0]).
25 -export([sync/0]).
26
27 %% domain/subdomain API
28 -export([maybe_add_domain_or_subdomain/1,
29 maybe_remove_domain/2,
30 maybe_remove_subdomain/1]).
31
32 %% IQ handling API
33 -export([register_iq_handler_for_domain/4,
34 register_iq_handler_for_subdomain/5,
35 unregister_iq_handler_for_domain/3,
36 unregister_iq_handler_for_subdomain/4]).
37
38 %% gen_server callbacks
39 -export([init/1,
40 handle_call/3,
41 handle_cast/2,
42 handle_info/2,
43 terminate/2,
44 code_change/3]).
45
46 -ignore_xref([start_link/0, stop/0, sync/0]).
47
48 -define(IQ_TABLE, mongoose_lazy_routing_iqs).
49 -define(ROUTING_TABLE, mongoose_lazy_routing).
50
51 -record(iq_table_key, {host_type :: host_type(),
52 %% subdomain_pattern is 'undefined' for domains
53 subdomain_pattern :: subdomain_pattern() | undefined,
54 namespace = '_' :: binary() | '_',
55 component = '_' :: module() | '_'}).
56
57 -type subdomain_pattern() :: mongoose_subdomain_utils:subdomain_pattern().
58 -type subdomain_info() :: mongoose_subdomain_core:subdomain_info().
59 -type host_type() :: mongooseim:host_type().
60 -type domain() :: mongooseim:domain_name().
61
62 -type iq_entry() :: {#iq_table_key{}, mongoose_iq_handler:t()}.
63 -type domain_entry() :: {domain(), host_type() | {host_type(), subdomain_pattern()}}.
64
65
66 %%--------------------------------------------------------------------
67 %% API
68 %%--------------------------------------------------------------------
69 -ifdef(TEST).
70
71 %% required for unit tests
72 start() ->
73 just_ok(gen_server:start({local, ?MODULE}, ?MODULE, [], [])).
74
75 stop() ->
76 gen_server:stop(?MODULE).
77
78 -else.
79
80 start() ->
81 73 ChildSpec = {?MODULE, {?MODULE, start_link, []},
82 permanent, infinity, worker, [?MODULE]},
83 73 just_ok(supervisor:start_child(ejabberd_sup, ChildSpec)).
84
85 %% required for integration tests
86 stop() ->
87
:-(
supervisor:terminate_child(ejabberd_sup, ?MODULE),
88
:-(
supervisor:delete_child(ejabberd_sup, ?MODULE),
89
:-(
ok.
90
91 -endif.
92
93 start_link() ->
94 73 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
95
96 -spec sync() -> ok.
97 sync() ->
98
:-(
gen_server:call(?MODULE, sync).
99
100 -spec maybe_add_domain_or_subdomain(mongooseim:domain()) -> boolean().
101 maybe_add_domain_or_subdomain(Domain) ->
102 %% don't run gen_server:call/2 if this domain name is unknown.
103 135 case mongoose_domain_api:get_host_type(Domain) of
104 {ok, _} ->
105 104 gen_server:call(?MODULE, {maybe_add_domain_or_subdomain, Domain});
106 {error, not_found} ->
107 31 false
108 end.
109
110 -spec maybe_remove_domain(mongooseim:host_type(), mongooseim:domain()) -> ok.
111 maybe_remove_domain(HostType, Domain) ->
112 64 gen_server:cast(?MODULE, {maybe_remove_domain, HostType, Domain}).
113
114 -spec maybe_remove_subdomain(subdomain_info()) -> ok.
115 maybe_remove_subdomain(SubdomainInfo) ->
116 549 gen_server:cast(?MODULE, {maybe_remove_subdomain, SubdomainInfo}).
117
118 -spec register_iq_handler_for_domain(HostType :: mongooseim:host_type(),
119 Namespace :: binary(),
120 Component :: module(),
121 IQHandler :: mongoose_iq_handler:t()) ->
122 ok | {error, atom()}.
123 register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) ->
124 4637 gen_server:call(?MODULE, {register_iq_handler_for_domain, HostType,
125 Namespace, Component, IQHandler}).
126
127 -spec register_iq_handler_for_subdomain(HostType :: mongooseim:host_type(),
128 SubdomainPattern :: subdomain_pattern(),
129 Namespace :: binary(),
130 Component :: module(),
131 IQHandler :: mongoose_iq_handler:t()) ->
132 ok | {error, atom()}.
133 register_iq_handler_for_subdomain(HostType, SubdomainPattern,
134 Namespace, Component, IQHandler) ->
135 166 gen_server:call(?MODULE, {register_iq_handler_for_subdomain, HostType,
136 SubdomainPattern, Namespace, Component, IQHandler}).
137
138 -spec unregister_iq_handler_for_domain(HostType :: mongooseim:host_type(),
139 Namespace :: binary(),
140 Component :: module()) ->
141 {ok, mongoose_iq_handler:t()} | {error, not_found}.
142 unregister_iq_handler_for_domain(HostType, Namespace, Component) ->
143 4626 gen_server:call(?MODULE, {unregister_iq_handler_for_domain,
144 HostType, Namespace, Component}).
145
146 -spec unregister_iq_handler_for_subdomain(HostType :: mongooseim:host_type(),
147 SubdomainPattern :: subdomain_pattern(),
148 Namespace :: binary(),
149 Component :: module()) ->
150 {ok, mongoose_iq_handler:t()} | {error, not_found}.
151 unregister_iq_handler_for_subdomain(HostType, SubdomainPattern,
152 Namespace, Component) ->
153 166 gen_server:call(?MODULE, {unregister_iq_handler_for_subdomain, HostType,
154 SubdomainPattern, Namespace, Component}).
155
156 %%--------------------------------------------------------------------
157 %% gen_server callbacks
158 %%--------------------------------------------------------------------
159 init(_) ->
160 %% ?IQ_TABLE contains tuples of following format:
161 %% * {#iq_table_key{}, IQHandler}
162 73 ets:new(?IQ_TABLE, [set, named_table, protected]),
163 %% ?ROUTING_TABLE contains tuples of one of the following formats:
164 %% * {DomainName, HostType}
165 %% * {SubdomainName, {HostType, SubdomainPattern}}
166 73 ets:new(?ROUTING_TABLE, [set, named_table, protected]),
167 %% no state, all required data is stored in ETS tables
168 73 {ok, ok}.
169
170 handle_call(sync, _From, State) ->
171
:-(
{reply, ok, State};
172 handle_call({maybe_add_domain_or_subdomain, Domain}, _From, State) ->
173 104 RetValue = handle_maybe_add_domain_or_subdomain(Domain),
174 104 {reply, RetValue, State};
175 handle_call({register_iq_handler_for_domain,
176 HostType, Namespace, Component, IQHandler},
177 _From, State) ->
178 4637 RetValue = handle_register_iq_handler_for_domain(HostType, Namespace,
179 Component, IQHandler),
180 4637 {reply, RetValue, State};
181 handle_call({register_iq_handler_for_subdomain, HostType,
182 SubdomainPattern, Namespace, Component, IQHandler},
183 _From, State) ->
184 166 RetValue = handle_register_iq_handler_for_subdomain(HostType, SubdomainPattern,
185 Namespace, Component, IQHandler),
186 166 {reply, RetValue, State};
187 handle_call({unregister_iq_handler_for_domain, HostType, Namespace, Component},
188 _From, State) ->
189 4626 RetValue = handle_unregister_iq_handler_for_domain(HostType, Namespace, Component),
190 4626 {reply, RetValue, State};
191 handle_call({unregister_iq_handler_for_subdomain, HostType, SubdomainPattern,
192 Namespace, Component},
193 _From, State) ->
194 166 RetValue = handle_unregister_iq_handler_for_subdomain(HostType, SubdomainPattern,
195 Namespace, Component),
196 166 {reply, RetValue, State};
197 handle_call(Request, From, State) ->
198
:-(
?UNEXPECTED_CALL(Request, From),
199
:-(
{reply, ok, State}.
200
201 handle_cast({maybe_remove_domain, HostType, Domain}, State) ->
202 64 handle_maybe_remove_domain(HostType, Domain),
203 64 {noreply, State};
204 handle_cast({maybe_remove_subdomain, SubdomainInfo}, State) ->
205 549 handle_maybe_remove_subdomain(SubdomainInfo),
206 549 {noreply, State};
207 handle_cast(Msg, State) ->
208
:-(
?UNEXPECTED_CAST(Msg),
209
:-(
{noreply, State}.
210
211 handle_info(Info, State) ->
212
:-(
?UNEXPECTED_INFO(Info),
213
:-(
{noreply, State}.
214
215 terminate(_Reason, _State) ->
216
:-(
ok.
217
218 code_change(_OldVsn, State, _Extra) ->
219
:-(
{ok, State}.
220
221 %%--------------------------------------------------------------------
222 %% local functions
223 %%--------------------------------------------------------------------
224 73 just_ok({ok, _}) -> ok;
225
:-(
just_ok(Other) -> Other.
226
227 -spec handle_register_iq_handler_for_domain(HostType :: mongooseim:host_type(),
228 Namespace :: binary(),
229 Component :: module(),
230 IQHandler :: mongoose_iq_handler:t()) ->
231 ok | {error, atom()}.
232 handle_register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) ->
233 4637 IQKey = #iq_table_key{host_type = HostType, namespace = Namespace,
234 component = Component},
235 4637 NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}),
236 4637 IQ = {IQKey, NewIQHandler},
237 4637 case ets:insert_new(?IQ_TABLE, IQ) of
238 false ->
239
:-(
?LOG_WARNING(#{what => iq_already_registered, host_type => HostType,
240
:-(
namespace => Namespace, component => Component}),
241
:-(
{error, already_registered};
242 true ->
243 4637 Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}),
244 4637 register_iqs([IQ], Domains)
245 end.
246
247 -spec handle_register_iq_handler_for_subdomain(HostType :: mongooseim:host_type(),
248 SubdomainPattern :: subdomain_pattern(),
249 Namespace :: binary(),
250 Component :: module(),
251 IQHandler :: mongoose_iq_handler:t()) ->
252 ok | {error, atom()}.
253 handle_register_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace,
254 Component, IQHandler) ->
255 166 IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern,
256 namespace = Namespace, component = Component},
257 166 NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}),
258 166 IQ = {IQKey, NewIQHandler},
259 166 case ets:insert_new(?IQ_TABLE, IQ) of
260 false ->
261
:-(
?LOG_WARNING(#{what => iq_already_registered, host_type => HostType,
262 subdomain_pattern => SubdomainPattern,
263
:-(
namespace => Namespace, component => Component}),
264
:-(
{error, already_registered};
265 true ->
266 166 Domains = ets:match_object(?ROUTING_TABLE,
267 {'_', {HostType, SubdomainPattern}}),
268 166 register_iqs([IQ], Domains)
269 end.
270
271 -spec handle_unregister_iq_handler_for_domain(HostType :: mongooseim:host_type(),
272 Namespace :: binary(),
273 Component :: module()) ->
274 {ok, mongoose_iq_handler:t()} | {error, not_found}.
275 handle_unregister_iq_handler_for_domain(HostType, Namespace, Component) ->
276 4626 IQKey = #iq_table_key{host_type = HostType, namespace = Namespace,
277 component = Component},
278 4626 case ets:lookup(?IQ_TABLE, IQKey) of
279 [] ->
280 570 ?LOG_WARNING(#{what => iq_unregister_missing, host_type => HostType,
281
:-(
namespace => Namespace, component => Component}),
282 570 {error, not_found};
283 [{_, IQHandler} = IQ] ->
284 4056 Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}),
285 4056 unregister_iqs([IQ], Domains),
286 4056 ets:delete(?IQ_TABLE, IQKey),
287 4056 {ok, IQHandler}
288 end.
289
290 -spec handle_unregister_iq_handler_for_subdomain(HostType :: mongooseim:host_type(),
291 SubdomainPattern :: subdomain_pattern(),
292 Namespace :: binary(),
293 Component :: module()) ->
294 {ok, mongoose_iq_handler:t()} | {error, not_found}.
295 handle_unregister_iq_handler_for_subdomain(HostType, SubdomainPattern,
296 Namespace, Component) ->
297 166 IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern,
298 namespace = Namespace, component = Component},
299 166 case ets:lookup(?IQ_TABLE, IQKey) of
300 [] ->
301
:-(
?LOG_WARNING(#{what => iq_unregister_missing, host_type => HostType,
302 subdomain_pattern => SubdomainPattern,
303
:-(
namespace => Namespace, component => Component}),
304
:-(
{error, not_found};
305 [{_, IQHandler} = IQ] ->
306 166 Domains = ets:match_object(?ROUTING_TABLE,
307 {'_', {HostType, SubdomainPattern}}),
308 166 unregister_iqs([IQ], Domains),
309 166 ets:delete(?IQ_TABLE, IQKey),
310 166 {ok, IQHandler}
311 end.
312
313 -spec handle_maybe_add_domain_or_subdomain(domain()) -> boolean().
314 handle_maybe_add_domain_or_subdomain(Domain) ->
315 104 case ets:member(?ROUTING_TABLE, Domain) of
316 true ->
317 %% It's absolutely normal situation. We can receive
318 %% a couple of maybe_add_domain_or_subdomain requests
319 %% from different processes, so this domain can be
320 %% already added.
321
:-(
true;
322 false ->
323 104 try_to_add_domain_or_subdomain(Domain)
324 end.
325
326 -spec try_to_add_domain_or_subdomain(domain()) -> boolean().
327 try_to_add_domain_or_subdomain(Domain) ->
328 104 case mongoose_domain_api:get_domain_host_type(Domain) of
329 {ok, HostType} ->
330 24 add_domain(HostType, Domain),
331 24 true;
332 {error, not_found} ->
333 80 case mongoose_domain_api:get_subdomain_info(Domain) of
334 {ok, Info} ->
335 80 add_subdomain(Info),
336 80 true;
337 {error, not_found} ->
338
:-(
false
339 end
340 end.
341
342 -spec add_domain(host_type(), domain()) -> ok.
343 add_domain(HostType, Domain) ->
344 24 DomainElement = {Domain, HostType},
345 24 case ets:insert_new(?ROUTING_TABLE, DomainElement) of
346 true ->
347 24 IQs = get_iqs(#iq_table_key{host_type = HostType}),
348 24 register_iqs(IQs, [DomainElement]),
349 24 ejabberd_local:register_host(Domain),
350 24 ok;
351 false ->
352 %% we should never get here, but it's ok to just ignore this.
353
:-(
ok
354 end.
355
356 -spec add_subdomain(subdomain_info()) -> ok.
357 add_subdomain(#{subdomain := Subdomain, host_type := HostType,
358 subdomain_pattern := SubdomainPattern,
359 packet_handler := PacketHandler}) ->
360 80 SubdomainElement = {Subdomain, {HostType, SubdomainPattern}},
361 80 case ets:insert_new(?ROUTING_TABLE, SubdomainElement) of
362 true ->
363 80 IQs = get_iqs(#iq_table_key{host_type = HostType,
364 subdomain_pattern = SubdomainPattern}),
365 80 register_iqs(IQs, [SubdomainElement]),
366 80 mongoose_router:register_route(Subdomain, PacketHandler);
367 false ->
368 %% we should never get here, but it's ok to just ignore this.
369
:-(
ok
370 end.
371
372 -spec handle_maybe_remove_domain(host_type(), domain()) -> ok.
373 handle_maybe_remove_domain(HostType, Domain) ->
374 64 case ets:lookup(?ROUTING_TABLE, Domain) of
375 [{Domain, HostType} = DomainElement] ->
376 15 ejabberd_local:unregister_host(Domain),
377 15 IQs = get_iqs(#iq_table_key{host_type = HostType}),
378 15 unregister_iqs(IQs, [DomainElement]),
379 15 ets:delete(?ROUTING_TABLE, Domain);
380 49 _ -> ok
381 end.
382
383 -spec handle_maybe_remove_subdomain(subdomain_info()) -> ok.
384 handle_maybe_remove_subdomain(#{subdomain := Subdomain, host_type := HostType,
385 subdomain_pattern := SubdomainPattern}) ->
386 549 case ets:lookup(?ROUTING_TABLE, Subdomain) of
387 [{Domain, {HostType, SubdomainPattern}} = SubdomainElement] ->
388 80 mongoose_router:unregister_route(Domain),
389 80 IQs = get_iqs(#iq_table_key{host_type = HostType,
390 subdomain_pattern = SubdomainPattern}),
391 80 unregister_iqs(IQs, [SubdomainElement]),
392 80 ets:delete(?ROUTING_TABLE, Domain);
393 469 _ -> ok
394 end.
395
396 -spec get_iqs(#iq_table_key{}) -> [iq_entry()].
397 get_iqs(KeyMatchPattern) ->
398 199 ets:match_object(?IQ_TABLE, {KeyMatchPattern, '_'}).
399
400 -spec register_iqs([iq_entry()], [domain_entry()]) -> ok.
401 register_iqs(IQList, DomainList) ->
402 4907 [register_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList,
403 955 check_that_domain_and_iq_match(IQ, Domain)],
404 4907 ok.
405
406 -spec unregister_iqs([iq_entry()], [domain_entry()]) -> ok.
407 unregister_iqs(IQList, DomainList) ->
408 4317 [unregister_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList,
409 939 check_that_domain_and_iq_match(IQ, Domain)],
410 4317 ok.
411
412 -spec check_that_domain_and_iq_match(iq_entry(), domain_entry()) -> boolean().
413 check_that_domain_and_iq_match({#iq_table_key{host_type = HostType,
414 subdomain_pattern = undefined,
415 namespace = Namespace,
416 component = Component}, _},
417 {_, HostType}) when is_binary(Namespace),
418 Component =/= '_' ->
419 1634 true;
420 check_that_domain_and_iq_match({#iq_table_key{host_type = HostType,
421 subdomain_pattern = Pattern,
422 namespace = Namespace,
423 component = Component}, _},
424 {_, {HostType, Pattern}}) when is_binary(Namespace),
425 Component =/= '_' ->
426 260 true;
427 check_that_domain_and_iq_match({Key = #iq_table_key{}, _} = IQEntry, Domain) ->
428 %% we should not get here, log error
429
:-(
?LOG_ERROR(#{what => domain_and_iq_doesnt_match, domain => Domain,
430 iq_entry => IQEntry,
431 iq_key_record => mongoose_record_pp:format(Key,
432
:-(
iq_table_key, record_info(fields, iq_table_key))}),
433
:-(
false.
434
435 -spec register_iq(iq_entry(), domain_entry()) -> ok.
436 register_iq({#iq_table_key{namespace = Namespace,
437 component = Component}, IQHandler},
438 {Domain, _})->
439 955 gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler),
440 955 gen_iq_component:sync(Component).
441
442 -spec unregister_iq(iq_entry(), domain_entry()) -> ok.
443 unregister_iq({#iq_table_key{namespace = Namespace,
444 component = Component}, _},
445 {Domain, _}) ->
446 939 gen_iq_component:unregister_iq_handler(Component, Domain, Namespace).
Line Hits Source