1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2017 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%%============================================================================== |
16 |
|
|
17 |
|
-module(mod_global_distrib_mapping). |
18 |
|
-author('konrad.zemek@erlang-solutions.com'). |
19 |
|
|
20 |
|
-behaviour(gen_mod). |
21 |
|
-behaviour(mongoose_module_metrics). |
22 |
|
|
23 |
|
-include("mongoose.hrl"). |
24 |
|
-include("jlib.hrl"). |
25 |
|
-include("global_distrib_metrics.hrl"). |
26 |
|
|
27 |
|
-define(DOMAIN_TAB, mod_global_distrib_domain_cache_tab). |
28 |
|
-define(JID_TAB, mod_global_distrib_jid_cache_tab). |
29 |
|
|
30 |
|
-export([start/2, stop/1, deps/2]). |
31 |
|
-export([for_domain/1, insert_for_domain/1, insert_for_domain/2, insert_for_domain/3, |
32 |
|
cache_domain/2, delete_for_domain/1, all_domains/0, public_domains/0]). |
33 |
|
-export([for_jid/1, insert_for_jid/1, cache_jid/2, delete_for_jid/1, clear_cache/1]). |
34 |
|
-export([register_subhost/3, unregister_subhost/2, packet_to_component/3, |
35 |
|
session_opened/5, session_closed/5]). |
36 |
|
-export([endpoints/1, hosts/0]). |
37 |
|
|
38 |
|
-define(MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, mod_global_distrib_mapping_backend). |
39 |
|
-ignore_xref([ |
40 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, get_domains, 0}, |
41 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, delete_domain, 1}, |
42 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, delete_session, 1}, |
43 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, get_endpoints, 1}, |
44 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, get_domain, 1}, |
45 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, get_session, 1}, |
46 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, get_hosts, 0}, |
47 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, get_public_domains, 0}, |
48 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, put_domain, 2}, |
49 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, put_session, 1}, |
50 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, start, 1}, |
51 |
|
{?MOD_GLOBAL_DISTRIB_MAPPING_BACKEND, stop, 0}, |
52 |
|
behaviour_info/1, delete_for_domain/1, delete_for_jid/1, insert_for_domain/1, |
53 |
|
insert_for_domain/2, insert_for_domain/3, insert_for_jid/1, packet_to_component/3, |
54 |
|
register_subhost/3, session_closed/5, session_opened/5, unregister_subhost/2 |
55 |
|
]). |
56 |
|
|
57 |
|
-type endpoint() :: mod_global_distrib_utils:endpoint(). |
58 |
|
|
59 |
|
%%-------------------------------------------------------------------- |
60 |
|
%% Callbacks |
61 |
|
%%-------------------------------------------------------------------- |
62 |
|
|
63 |
|
-callback start(Opts :: proplists:proplist()) -> any(). |
64 |
|
-callback stop() -> any(). |
65 |
|
-callback put_session(JID :: binary()) -> ok | error. |
66 |
|
-callback get_session(JID :: binary()) -> {ok, Host :: binary()} | error. |
67 |
|
-callback delete_session(JID :: binary()) -> ok | error. |
68 |
|
-callback put_domain(Domain :: binary(), IsHidden :: boolean()) -> ok | error. |
69 |
|
-callback get_domain(Domain :: binary()) -> {ok, Host :: binary()} | error. |
70 |
|
-callback delete_domain(Domain :: binary()) -> ok | error. |
71 |
|
-callback get_domains() -> {ok, [Domain :: binary()]} | error. |
72 |
|
-callback get_public_domains() -> {ok, [Domain :: binary()]} | error. |
73 |
|
-callback get_endpoints(Host :: binary()) -> {ok, [endpoint()]}. |
74 |
|
-callback get_hosts() -> [Host :: jid:lserver()]. |
75 |
|
|
76 |
|
%%-------------------------------------------------------------------- |
77 |
|
%% API |
78 |
|
%%-------------------------------------------------------------------- |
79 |
|
|
80 |
|
-spec for_domain(Domain :: binary()) -> {ok, Host :: jid:lserver()} | error. |
81 |
|
for_domain(Domain) when is_binary(Domain) -> |
82 |
:-( |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCHES, 1), |
83 |
:-( |
{Time, R} = timer:tc(ets_cache, lookup, [?DOMAIN_TAB, Domain, fun() -> get_domain(Domain) end]), |
84 |
:-( |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCH_TIME, Time), |
85 |
:-( |
R. |
86 |
|
|
87 |
|
-spec insert_for_domain(Domain :: binary()) -> ok. |
88 |
|
insert_for_domain(Domain) when is_binary(Domain) -> |
89 |
:-( |
insert_for_domain(Domain, false). |
90 |
|
|
91 |
|
-spec insert_for_domain(Domain :: binary(), IsHidden :: boolean()) -> ok. |
92 |
|
insert_for_domain(Domain, IsHidden) when is_binary(Domain) -> |
93 |
:-( |
LocalHost = opt(local_host), |
94 |
:-( |
insert_for_domain(Domain, LocalHost, IsHidden). |
95 |
|
|
96 |
|
-spec insert_for_domain(Domain :: binary(), Host :: binary(), IsHidden :: boolean()) -> ok. |
97 |
|
insert_for_domain(Domain, Host, IsHidden) when is_binary(Domain), is_binary(Host) -> |
98 |
:-( |
do_insert_for_domain(Domain, Host, fun(ToStore) -> put_domain(ToStore, IsHidden) end). |
99 |
|
|
100 |
|
-spec cache_domain(Domain :: binary(), Host :: binary()) -> ok. |
101 |
|
cache_domain(Domain, Host) when is_binary(Domain), is_binary(Host) -> |
102 |
:-( |
do_insert_for_domain(Domain, Host, fun(_) -> ok end). |
103 |
|
|
104 |
|
-spec delete_for_domain(Domain :: binary()) -> ok. |
105 |
|
delete_for_domain(Domain) when is_binary(Domain) -> |
106 |
:-( |
delete_domain(Domain), |
107 |
:-( |
ets_cache:delete(?DOMAIN_TAB, Domain). |
108 |
|
|
109 |
|
-spec for_jid(jid:jid() | jid:ljid()) -> {ok, Host :: jid:lserver()} | error. |
110 |
:-( |
for_jid(#jid{} = Jid) -> for_jid(jid:to_lower(Jid)); |
111 |
|
for_jid({_, _, _} = Jid) -> |
112 |
:-( |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCHES, 1), |
113 |
:-( |
{Time, R} = timer:tc(fun do_lookup_jid/1, [Jid]), |
114 |
:-( |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCH_TIME, Time), |
115 |
:-( |
R. |
116 |
|
|
117 |
|
-spec insert_for_jid(jid:jid() | jid:ljid()) -> ok. |
118 |
|
insert_for_jid(Jid) -> |
119 |
:-( |
LocalHost = opt(local_host), |
120 |
:-( |
do_insert_for_jid(Jid, LocalHost, fun put_session/1). |
121 |
|
|
122 |
|
-spec cache_jid(jid:jid() | jid:ljid(), Host :: jid:lserver()) -> ok. |
123 |
|
cache_jid(Jid, Host) when is_binary(Host) -> |
124 |
:-( |
do_insert_for_jid(Jid, Host, fun(_) -> ok end). |
125 |
|
|
126 |
|
-spec clear_cache(jid:jid()) -> ok. |
127 |
|
clear_cache(#jid{} = Jid) -> |
128 |
:-( |
GlobalHost = opt(global_host), |
129 |
:-( |
case jid:to_lower(Jid) of |
130 |
|
{_, GlobalHost, _} = LJid -> |
131 |
:-( |
[ets_cache:delete(?JID_TAB, J) || J <- normalize_jid(LJid)]; |
132 |
|
{_, SubHost, _} -> |
133 |
:-( |
ets_cache:delete(?DOMAIN_TAB, SubHost) |
134 |
|
end. |
135 |
|
|
136 |
|
-spec delete_for_jid(jid:jid() | jid:ljid()) -> ok. |
137 |
:-( |
delete_for_jid(#jid{} = Jid) -> delete_for_jid(jid:to_lower(Jid)); |
138 |
|
delete_for_jid({_, _, _} = Jid) -> |
139 |
:-( |
lists:foreach( |
140 |
|
fun(BinJid) -> |
141 |
:-( |
delete_session(BinJid), |
142 |
:-( |
ets_cache:delete(?JID_TAB, BinJid) |
143 |
|
end, |
144 |
|
normalize_jid(Jid)). |
145 |
|
|
146 |
|
-spec all_domains() -> {ok, [jid:lserver()]}. |
147 |
|
all_domains() -> |
148 |
:-( |
mod_global_distrib_mapping_backend:get_domains(). |
149 |
|
|
150 |
|
-spec public_domains() -> {ok, [jid:lserver()]}. |
151 |
|
public_domains() -> |
152 |
:-( |
mod_global_distrib_mapping_backend:get_public_domains(). |
153 |
|
|
154 |
|
-spec endpoints(Host :: jid:lserver()) -> {ok, [endpoint()]}. |
155 |
|
endpoints(Host) -> |
156 |
:-( |
mod_global_distrib_mapping_backend:get_endpoints(Host). |
157 |
|
|
158 |
|
-spec hosts() -> [Host :: jid:lserver()]. |
159 |
|
hosts() -> |
160 |
:-( |
mod_global_distrib_mapping_backend:get_hosts(). |
161 |
|
|
162 |
|
%%-------------------------------------------------------------------- |
163 |
|
%% gen_mod API |
164 |
|
%%-------------------------------------------------------------------- |
165 |
|
|
166 |
|
-spec start(Host :: jid:lserver(), Opts :: proplists:proplist()) -> any(). |
167 |
|
start(Host, Opts0) -> |
168 |
:-( |
AdvEndpoints = get_advertised_endpoints(Opts0), |
169 |
:-( |
Opts = [{advertised_endpoints, AdvEndpoints}, {backend, redis}, {redis, [no_opts]}, {cache_missed, true}, |
170 |
|
{domain_lifetime_seconds, 600}, {jid_lifetime_seconds, 5}, {max_jids, 10000} | Opts0], |
171 |
:-( |
mod_global_distrib_utils:start(?MODULE, Host, Opts, fun start/0). |
172 |
|
|
173 |
|
-spec stop(Host :: jid:lserver()) -> any(). |
174 |
|
stop(Host) -> |
175 |
:-( |
mod_global_distrib_utils:stop(?MODULE, Host, fun stop/0). |
176 |
|
|
177 |
|
-spec deps(Host :: jid:server(), Opts :: proplists:proplist()) -> gen_mod:deps_list(). |
178 |
|
deps(Host, Opts) -> |
179 |
:-( |
mod_global_distrib_utils:deps(?MODULE, Host, Opts, fun deps/1). |
180 |
|
|
181 |
|
%%-------------------------------------------------------------------- |
182 |
|
%% Hooks implementation |
183 |
|
%%-------------------------------------------------------------------- |
184 |
|
|
185 |
|
-spec session_opened(Acc, binary(), ejabberd_sm:sid(), UserJID :: jid:jid(), Info :: list()) -> |
186 |
|
Acc when Acc :: any(). |
187 |
|
session_opened(Acc, _HostType, _SID, UserJid, _Info) -> |
188 |
:-( |
insert_for_jid(UserJid), |
189 |
:-( |
Acc. |
190 |
|
|
191 |
|
-spec session_closed(mongoose_acc:t(), |
192 |
|
ejabberd_sm:sid(), |
193 |
|
UserJID :: jid:jid(), |
194 |
|
Info :: list(), |
195 |
|
_Status :: any()) -> |
196 |
|
mongoose_acc:t(). |
197 |
|
session_closed(Acc, _SID, UserJid, _Info, _Reason) -> |
198 |
:-( |
delete_for_jid(UserJid), |
199 |
:-( |
Acc. |
200 |
|
|
201 |
|
-spec packet_to_component(Acc :: mongoose_acc:t(), |
202 |
|
From :: jid:jid(), |
203 |
|
To :: jid:jid()) -> mongoose_acc:t(). |
204 |
|
packet_to_component(Acc, From, _To) -> |
205 |
:-( |
mod_global_distrib_utils:maybe_update_mapping(From, Acc), |
206 |
:-( |
Acc. |
207 |
|
|
208 |
|
-spec register_subhost(any(), SubHost :: binary(), IsHidden :: boolean()) -> ok. |
209 |
|
register_subhost(_, SubHost, IsHidden) -> |
210 |
:-( |
IsSubhostOf = |
211 |
|
fun(Host) -> |
212 |
:-( |
case binary:match(SubHost, Host) of |
213 |
:-( |
{Start, Length} -> Start + Length == byte_size(SubHost); |
214 |
:-( |
_ -> false |
215 |
|
end |
216 |
|
end, |
217 |
|
|
218 |
:-( |
GlobalHost = opt(global_host), |
219 |
:-( |
case lists:filter(IsSubhostOf, ?MYHOSTS) of |
220 |
:-( |
[GlobalHost] -> insert_for_domain(SubHost, IsHidden); |
221 |
:-( |
_ -> ok |
222 |
|
end. |
223 |
|
|
224 |
|
-spec unregister_subhost(any(), SubHost :: binary()) -> ok. |
225 |
|
unregister_subhost(_, SubHost) -> |
226 |
:-( |
delete_for_domain(SubHost). |
227 |
|
|
228 |
|
%%-------------------------------------------------------------------- |
229 |
|
%% Helpers |
230 |
|
%%-------------------------------------------------------------------- |
231 |
|
|
232 |
|
-spec deps(proplists:proplist()) -> gen_mod:deps_list(). |
233 |
|
deps(_Opts) -> |
234 |
:-( |
[{mod_global_distrib_receiver, hard}]. |
235 |
|
|
236 |
|
-spec start() -> any(). |
237 |
|
start() -> |
238 |
:-( |
Host = opt(global_host), |
239 |
:-( |
Backend = opt(backend), |
240 |
:-( |
gen_mod:start_backend_module(?MODULE, [{backend, Backend}]), |
241 |
:-( |
mod_global_distrib_mapping_backend:start(opt(Backend)), |
242 |
|
|
243 |
:-( |
mongoose_metrics:ensure_metric(global, ?GLOBAL_DISTRIB_MAPPING_FETCH_TIME, histogram), |
244 |
:-( |
mongoose_metrics:ensure_metric(global, ?GLOBAL_DISTRIB_MAPPING_FETCHES, spiral), |
245 |
:-( |
mongoose_metrics:ensure_metric(global, ?GLOBAL_DISTRIB_MAPPING_CACHE_MISSES, spiral), |
246 |
|
|
247 |
:-( |
CacheMissed = opt(cache_missed), |
248 |
:-( |
DomainLifetime = opt(domain_lifetime_seconds) * 1000, |
249 |
:-( |
JidLifetime = opt(jid_lifetime_seconds) * 1000, |
250 |
:-( |
MaxJids = opt(max_jids), |
251 |
|
|
252 |
:-( |
ejabberd_hooks:add(register_subhost, global, ?MODULE, register_subhost, 90), |
253 |
:-( |
ejabberd_hooks:add(unregister_subhost, global, ?MODULE, unregister_subhost, 90), |
254 |
:-( |
ejabberd_hooks:add(packet_to_component, global, ?MODULE, packet_to_component, 90), |
255 |
:-( |
ejabberd_hooks:add(sm_register_connection_hook, Host, ?MODULE, session_opened, 90), |
256 |
:-( |
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, session_closed, 90), |
257 |
|
|
258 |
:-( |
ets_cache:new(?DOMAIN_TAB, [{cache_missed, CacheMissed}, {life_time, DomainLifetime}]), |
259 |
:-( |
ets_cache:new(?JID_TAB, [{cache_missed, CacheMissed}, {life_time, JidLifetime}, |
260 |
|
{max_size, MaxJids}]). |
261 |
|
|
262 |
|
-spec stop() -> any(). |
263 |
|
stop() -> |
264 |
:-( |
Host = opt(global_host), |
265 |
|
|
266 |
:-( |
ets_cache:delete(?JID_TAB), |
267 |
:-( |
ets_cache:delete(?DOMAIN_TAB), |
268 |
|
|
269 |
:-( |
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, session_closed, 90), |
270 |
:-( |
ejabberd_hooks:delete(sm_register_connection_hook, Host, ?MODULE, session_opened, 90), |
271 |
:-( |
ejabberd_hooks:delete(packet_to_component, global, ?MODULE, packet_to_component, 90), |
272 |
:-( |
ejabberd_hooks:delete(unregister_subhost, global, ?MODULE, unregister_subhost, 90), |
273 |
:-( |
ejabberd_hooks:delete(register_subhost, global, ?MODULE, register_subhost, 90), |
274 |
|
|
275 |
:-( |
mod_global_distrib_mapping_backend:stop(). |
276 |
|
|
277 |
|
-spec normalize_jid(jid:ljid()) -> [binary()]. |
278 |
|
normalize_jid({_, _, _} = FullJid) -> |
279 |
:-( |
case jid:to_bare(FullJid) of |
280 |
:-( |
FullJid -> [jid:to_binary(FullJid)]; |
281 |
:-( |
BareJid -> [jid:to_binary(FullJid), jid:to_binary(BareJid)] |
282 |
|
end. |
283 |
|
|
284 |
|
-spec opt(Key :: atom()) -> term(). |
285 |
|
opt(Key) -> |
286 |
:-( |
mod_global_distrib_utils:opt(?MODULE, Key). |
287 |
|
|
288 |
|
-spec get_session(Key :: binary()) -> {ok, term()} | error. |
289 |
|
get_session(Key) -> |
290 |
:-( |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_CACHE_MISSES, 1), |
291 |
:-( |
mod_global_distrib_mapping_backend:get_session(Key). |
292 |
|
|
293 |
|
-spec put_session(Key :: binary()) -> ok. |
294 |
|
put_session(Key) -> |
295 |
:-( |
?LOG_DEBUG(#{what => gd_mapper_put_session, key => Key}), |
296 |
:-( |
mod_global_distrib_mapping_backend:put_session(Key). |
297 |
|
|
298 |
|
-spec delete_session(Key :: binary()) -> ok. |
299 |
|
delete_session(Key) -> |
300 |
:-( |
?LOG_DEBUG(#{what => gd_mapper_delete_session, key => Key}), |
301 |
:-( |
mod_global_distrib_mapping_backend:delete_session(Key). |
302 |
|
|
303 |
|
-spec get_domain(Key :: binary()) -> {ok, term()} | error. |
304 |
|
get_domain(Key) -> |
305 |
:-( |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_CACHE_MISSES, 1), |
306 |
:-( |
mod_global_distrib_mapping_backend:get_domain(Key). |
307 |
|
|
308 |
|
-spec put_domain(Key :: binary(), IsHidden :: boolean()) -> ok. |
309 |
|
put_domain(Key, IsHidden) -> |
310 |
:-( |
mod_global_distrib_mapping_backend:put_domain(Key, IsHidden). |
311 |
|
|
312 |
|
-spec delete_domain(Key :: binary()) -> ok. |
313 |
|
delete_domain(Key) -> |
314 |
:-( |
mod_global_distrib_mapping_backend:delete_domain(Key). |
315 |
|
|
316 |
|
-spec do_insert_for_jid(jid:jid() | jid:ljid(), Host :: jid:lserver(), |
317 |
|
PutSession :: fun((binary()) -> ok | error)) -> ok. |
318 |
|
do_insert_for_jid(#jid{} = Jid, Host, PutSession) -> |
319 |
:-( |
do_insert_for_jid(jid:to_lower(Jid), Host, PutSession); |
320 |
|
do_insert_for_jid({_, _, _} = Jid, Host, PutSession) -> |
321 |
:-( |
lists:foreach( |
322 |
|
fun(BinJid) -> |
323 |
:-( |
ets_cache:update(?JID_TAB, BinJid, {ok, Host}, fun() -> PutSession(BinJid) end) |
324 |
|
end, |
325 |
|
normalize_jid(Jid)). |
326 |
|
|
327 |
|
-spec do_insert_for_domain(Domain :: binary(), Host :: jid:lserver(), |
328 |
|
PutDomain :: fun((binary()) -> ok | error)) -> ok. |
329 |
|
do_insert_for_domain(Domain, Host, PutDomain) -> |
330 |
:-( |
ets_cache:update(?DOMAIN_TAB, Domain, {ok, Host}, fun() -> PutDomain(Domain) end). |
331 |
|
|
332 |
|
-spec do_lookup_jid(jid:ljid()) -> {ok, Host :: jid:lserver()} | error. |
333 |
|
do_lookup_jid({_, _, _} = Jid) -> |
334 |
:-( |
BinJid = jid:to_binary(Jid), |
335 |
:-( |
LookupInDB = fun(BJid) -> fun() -> get_session(BJid) end end, |
336 |
:-( |
case ets_cache:lookup(?JID_TAB, BinJid, LookupInDB(BinJid)) of |
337 |
:-( |
{ok, _} = Result -> Result; |
338 |
|
Other -> |
339 |
:-( |
case jid:to_bare(Jid) of |
340 |
:-( |
Jid -> Other; |
341 |
:-( |
BareJid -> ets_cache:lookup(?JID_TAB, BinJid, LookupInDB(jid:to_binary(BareJid))) |
342 |
|
end |
343 |
|
end. |
344 |
|
|
345 |
|
-spec get_advertised_endpoints(Opts :: list()) -> [endpoint()]. |
346 |
|
get_advertised_endpoints(Opts) -> |
347 |
:-( |
Conns = proplists:get_value(connections, Opts, []), |
348 |
:-( |
proplists:get_value(advertised_endpoints, Conns, false). |