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