./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 83 ChildSpec = {?MODULE, {?MODULE, start_link, []},
82 permanent, infinity, worker, [?MODULE]},
83 83 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 83 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 223 case mongoose_domain_api:get_host_type(Domain) of
104 {ok, _} ->
105 123 gen_server:call(?MODULE, {maybe_add_domain_or_subdomain, Domain});
106 {error, not_found} ->
107 100 false
108 end.
109
110 -spec maybe_remove_domain(mongooseim:host_type(), mongooseim:domain()) -> ok.
111 maybe_remove_domain(HostType, Domain) ->
112 14 gen_server:cast(?MODULE, {maybe_remove_domain, HostType, Domain}).
113
114 -spec maybe_remove_subdomain(subdomain_info()) -> ok.
115 maybe_remove_subdomain(SubdomainInfo) ->
116 364 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 4961 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 10 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 4948 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 10 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 83 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 83 ets:new(?ROUTING_TABLE, [set, named_table, protected]),
167 %% no state, all required data is stored in ETS tables
168 83 {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 123 RetValue = handle_maybe_add_domain_or_subdomain(Domain),
174 123 {reply, RetValue, State};
175 handle_call({register_iq_handler_for_domain,
176 HostType, Namespace, Component, IQHandler},
177 _From, State) ->
178 4961 RetValue = handle_register_iq_handler_for_domain(HostType, Namespace,
179 Component, IQHandler),
180 4961 {reply, RetValue, State};
181 handle_call({register_iq_handler_for_subdomain, HostType,
182 SubdomainPattern, Namespace, Component, IQHandler},
183 _From, State) ->
184 10 RetValue = handle_register_iq_handler_for_subdomain(HostType, SubdomainPattern,
185 Namespace, Component, IQHandler),
186 10 {reply, RetValue, State};
187 handle_call({unregister_iq_handler_for_domain, HostType, Namespace, Component},
188 _From, State) ->
189 4948 RetValue = handle_unregister_iq_handler_for_domain(HostType, Namespace, Component),
190 4948 {reply, RetValue, State};
191 handle_call({unregister_iq_handler_for_subdomain, HostType, SubdomainPattern,
192 Namespace, Component},
193 _From, State) ->
194 10 RetValue = handle_unregister_iq_handler_for_subdomain(HostType, SubdomainPattern,
195 Namespace, Component),
196 10 {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 14 handle_maybe_remove_domain(HostType, Domain),
203 14 {noreply, State};
204 handle_cast({maybe_remove_subdomain, SubdomainInfo}, State) ->
205 364 handle_maybe_remove_subdomain(SubdomainInfo),
206 364 {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 83 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 4961 IQKey = #iq_table_key{host_type = HostType, namespace = Namespace,
234 component = Component},
235 4961 NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}),
236 4961 IQ = {IQKey, NewIQHandler},
237 4961 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 4961 Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}),
244 4961 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 10 IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern,
256 namespace = Namespace, component = Component},
257 10 NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}),
258 10 IQ = {IQKey, NewIQHandler},
259 10 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 10 Domains = ets:match_object(?ROUTING_TABLE,
267 {'_', {HostType, SubdomainPattern}}),
268 10 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 4948 IQKey = #iq_table_key{host_type = HostType, namespace = Namespace,
277 component = Component},
278 4948 case ets:lookup(?IQ_TABLE, IQKey) of
279 [] ->
280 640 ?LOG_WARNING(#{what => iq_unregister_missing, host_type => HostType,
281
:-(
namespace => Namespace, component => Component}),
282 640 {error, not_found};
283 [{_, IQHandler} = IQ] ->
284 4308 Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}),
285 4308 unregister_iqs([IQ], Domains),
286 4308 ets:delete(?IQ_TABLE, IQKey),
287 4308 {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 10 IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern,
298 namespace = Namespace, component = Component},
299 10 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 10 Domains = ets:match_object(?ROUTING_TABLE,
307 {'_', {HostType, SubdomainPattern}}),
308 10 unregister_iqs([IQ], Domains),
309 10 ets:delete(?IQ_TABLE, IQKey),
310 10 {ok, IQHandler}
311 end.
312
313 -spec handle_maybe_add_domain_or_subdomain(domain()) -> boolean().
314 handle_maybe_add_domain_or_subdomain(Domain) ->
315 123 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 5 true;
322 false ->
323 118 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 118 case mongoose_domain_api:get_domain_host_type(Domain) of
329 {ok, HostType} ->
330 25 add_domain(HostType, Domain),
331 25 true;
332 {error, not_found} ->
333 93 case mongoose_domain_api:get_subdomain_info(Domain) of
334 {ok, Info} ->
335 93 add_subdomain(Info),
336 93 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 25 DomainElement = {Domain, HostType},
345 25 case ets:insert_new(?ROUTING_TABLE, DomainElement) of
346 true ->
347 25 IQs = get_iqs(#iq_table_key{host_type = HostType}),
348 25 register_iqs(IQs, [DomainElement]),
349 25 ejabberd_local:register_host(Domain),
350 25 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 93 SubdomainElement = {Subdomain, {HostType, SubdomainPattern}},
361 93 case ets:insert_new(?ROUTING_TABLE, SubdomainElement) of
362 true ->
363 93 IQs = get_iqs(#iq_table_key{host_type = HostType,
364 subdomain_pattern = SubdomainPattern}),
365 93 register_iqs(IQs, [SubdomainElement]),
366 93 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 14 case ets:lookup(?ROUTING_TABLE, Domain) of
375 [{Domain, HostType} = DomainElement] ->
376 7 ejabberd_local:unregister_host(Domain),
377 7 IQs = get_iqs(#iq_table_key{host_type = HostType}),
378 7 unregister_iqs(IQs, [DomainElement]),
379 7 ets:delete(?ROUTING_TABLE, Domain);
380 7 _ -> 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 364 case ets:lookup(?ROUTING_TABLE, Subdomain) of
387 [{Domain, {HostType, SubdomainPattern}} = SubdomainElement] ->
388 93 mongoose_router:unregister_route(Domain),
389 93 IQs = get_iqs(#iq_table_key{host_type = HostType,
390 subdomain_pattern = SubdomainPattern}),
391 93 unregister_iqs(IQs, [SubdomainElement]),
392 93 ets:delete(?ROUTING_TABLE, Domain);
393 271 _ -> ok
394 end.
395
396 -spec get_iqs(#iq_table_key{}) -> [iq_entry()].
397 get_iqs(KeyMatchPattern) ->
398 218 ets:match_object(?IQ_TABLE, {KeyMatchPattern, '_'}).
399
400 -spec register_iqs([iq_entry()], [domain_entry()]) -> ok.
401 register_iqs(IQList, DomainList) ->
402 5089 [register_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList,
403 411 check_that_domain_and_iq_match(IQ, Domain)],
404 5089 ok.
405
406 -spec unregister_iqs([iq_entry()], [domain_entry()]) -> ok.
407 unregister_iqs(IQList, DomainList) ->
408 4418 [unregister_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList,
409 324 check_that_domain_and_iq_match(IQ, Domain)],
410 4418 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 719 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 16 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 411 gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler),
440 411 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 324 gen_iq_component:unregister_iq_handler(Component, Domain, Namespace).
Line Hits Source