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, hooks/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/3, packet_to_component/3, |
35 |
|
session_opened/3, session_closed/3]). |
36 |
|
-export([endpoints/1, hosts/0]). |
37 |
|
|
38 |
|
-ignore_xref([ |
39 |
|
delete_for_domain/1, delete_for_jid/1, insert_for_domain/1, |
40 |
|
insert_for_domain/2, insert_for_domain/3, insert_for_jid/1 |
41 |
|
]). |
42 |
|
|
43 |
|
-type endpoint() :: mod_global_distrib_utils:endpoint(). |
44 |
|
|
45 |
|
%%-------------------------------------------------------------------- |
46 |
|
%% API |
47 |
|
%%-------------------------------------------------------------------- |
48 |
|
|
49 |
|
-spec for_domain(Domain :: binary()) -> {ok, Host :: jid:lserver()} | error. |
50 |
|
for_domain(Domain) when is_binary(Domain) -> |
51 |
45 |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCHES, 1), |
52 |
45 |
{Time, R} = timer:tc(ets_cache, lookup, [?DOMAIN_TAB, Domain, fun() -> get_domain(Domain) end]), |
53 |
45 |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCH_TIME, Time), |
54 |
45 |
R. |
55 |
|
|
56 |
|
-spec insert_for_domain(Domain :: binary()) -> ok. |
57 |
|
insert_for_domain(Domain) when is_binary(Domain) -> |
58 |
:-( |
insert_for_domain(Domain, false). |
59 |
|
|
60 |
|
-spec insert_for_domain(Domain :: binary(), IsHidden :: boolean()) -> ok. |
61 |
|
insert_for_domain(Domain, IsHidden) when is_binary(Domain) -> |
62 |
10 |
LocalHost = opt(local_host), |
63 |
10 |
insert_for_domain(Domain, LocalHost, IsHidden). |
64 |
|
|
65 |
|
-spec insert_for_domain(Domain :: binary(), Host :: binary(), IsHidden :: boolean()) -> ok. |
66 |
|
insert_for_domain(Domain, Host, IsHidden) when is_binary(Domain), is_binary(Host) -> |
67 |
10 |
do_insert_for_domain(Domain, Host, fun(ToStore) -> put_domain(ToStore, IsHidden) end). |
68 |
|
|
69 |
|
-spec cache_domain(Domain :: binary(), Host :: binary()) -> ok. |
70 |
|
cache_domain(Domain, Host) when is_binary(Domain), is_binary(Host) -> |
71 |
:-( |
do_insert_for_domain(Domain, Host, fun(_) -> ok end). |
72 |
|
|
73 |
|
-spec delete_for_domain(Domain :: binary()) -> ok. |
74 |
|
delete_for_domain(Domain) when is_binary(Domain) -> |
75 |
12 |
delete_domain(Domain), |
76 |
12 |
ets_cache:delete(?DOMAIN_TAB, Domain). |
77 |
|
|
78 |
|
-spec for_jid(jid:jid() | jid:ljid()) -> {ok, Host :: jid:lserver()} | error. |
79 |
1112 |
for_jid(#jid{} = Jid) -> for_jid(jid:to_lower(Jid)); |
80 |
|
for_jid({_, _, _} = Jid) -> |
81 |
1112 |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCHES, 1), |
82 |
1112 |
{Time, R} = timer:tc(fun do_lookup_jid/1, [Jid]), |
83 |
1112 |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_FETCH_TIME, Time), |
84 |
1112 |
R. |
85 |
|
|
86 |
|
-spec insert_for_jid(jid:jid() | jid:ljid()) -> ok. |
87 |
|
insert_for_jid(Jid) -> |
88 |
71 |
LocalHost = opt(local_host), |
89 |
71 |
do_insert_for_jid(Jid, LocalHost, fun put_session/1). |
90 |
|
|
91 |
|
-spec cache_jid(jid:jid() | jid:ljid(), Host :: jid:lserver()) -> ok. |
92 |
|
cache_jid(Jid, Host) when is_binary(Host) -> |
93 |
9 |
do_insert_for_jid(Jid, Host, fun(_) -> ok end). |
94 |
|
|
95 |
|
-spec clear_cache(jid:jid()) -> ok. |
96 |
|
clear_cache(#jid{} = Jid) -> |
97 |
46 |
GlobalHost = opt(global_host), |
98 |
46 |
case jid:to_lower(Jid) of |
99 |
|
{_, GlobalHost, _} = LJid -> |
100 |
37 |
[ets_cache:delete(?JID_TAB, J) || J <- normalize_jid(LJid)]; |
101 |
|
{_, SubHost, _} -> |
102 |
9 |
ets_cache:delete(?DOMAIN_TAB, SubHost) |
103 |
|
end. |
104 |
|
|
105 |
|
-spec delete_for_jid(jid:jid() | jid:ljid()) -> ok. |
106 |
73 |
delete_for_jid(#jid{} = Jid) -> delete_for_jid(jid:to_lower(Jid)); |
107 |
|
delete_for_jid({_, _, _} = Jid) -> |
108 |
73 |
lists:foreach( |
109 |
|
fun(BinJid) -> |
110 |
146 |
delete_session(BinJid), |
111 |
146 |
ets_cache:delete(?JID_TAB, BinJid) |
112 |
|
end, |
113 |
|
normalize_jid(Jid)). |
114 |
|
|
115 |
|
-spec all_domains() -> [jid:lserver()]. |
116 |
|
all_domains() -> |
117 |
3 |
mod_global_distrib_mapping_backend:get_domains(). |
118 |
|
|
119 |
|
-spec public_domains() -> [jid:lserver()]. |
120 |
|
public_domains() -> |
121 |
3 |
mod_global_distrib_mapping_backend:get_public_domains(). |
122 |
|
|
123 |
|
-spec endpoints(Host :: jid:lserver()) -> [endpoint()]. |
124 |
|
endpoints(Host) -> |
125 |
90 |
mod_global_distrib_mapping_backend:get_endpoints(Host). |
126 |
|
|
127 |
|
-spec hosts() -> [Host :: jid:lserver()]. |
128 |
|
hosts() -> |
129 |
568 |
mod_global_distrib_mapping_backend:get_hosts(). |
130 |
|
|
131 |
|
%%-------------------------------------------------------------------- |
132 |
|
%% gen_mod API |
133 |
|
%%-------------------------------------------------------------------- |
134 |
|
|
135 |
|
-spec start(mongooseim:host_type(), gen_mod:module_opts()) -> any(). |
136 |
|
start(_HostType, Opts = #{cache := CacheOpts}) -> |
137 |
21 |
mod_global_distrib_mapping_backend:start(Opts#{backend => redis}), |
138 |
|
|
139 |
21 |
mongoose_metrics:ensure_metric(global, ?GLOBAL_DISTRIB_MAPPING_FETCH_TIME, histogram), |
140 |
21 |
mongoose_metrics:ensure_metric(global, ?GLOBAL_DISTRIB_MAPPING_FETCHES, spiral), |
141 |
21 |
mongoose_metrics:ensure_metric(global, ?GLOBAL_DISTRIB_MAPPING_CACHE_MISSES, spiral), |
142 |
|
|
143 |
21 |
#{cache_missed := CacheMissed, |
144 |
|
domain_lifetime_seconds := DomainLifetimeSec, |
145 |
|
jid_lifetime_seconds := JidLifeTimeSec, |
146 |
|
max_jids := MaxJids} = CacheOpts, |
147 |
21 |
DomainLifetime = timer:seconds(DomainLifetimeSec), |
148 |
21 |
JidLifetime = timer:seconds(JidLifeTimeSec), |
149 |
|
|
150 |
|
|
151 |
21 |
ets_cache:new(?DOMAIN_TAB, [{cache_missed, CacheMissed}, {life_time, DomainLifetime}]), |
152 |
21 |
ets_cache:new(?JID_TAB, [{cache_missed, CacheMissed}, {life_time, JidLifetime}, |
153 |
|
{max_size, MaxJids}]). |
154 |
|
|
155 |
|
-spec stop(mongooseim:host_type()) -> any(). |
156 |
|
stop(_HostType) -> |
157 |
21 |
ets_cache:delete(?JID_TAB), |
158 |
21 |
ets_cache:delete(?DOMAIN_TAB), |
159 |
21 |
mod_global_distrib_mapping_backend:stop(). |
160 |
|
|
161 |
|
-spec deps(mongooseim:host_type(), gen_mod:module_opts()) -> gen_mod_deps:deps(). |
162 |
|
deps(_HostType, Opts) -> |
163 |
106 |
[{mod_global_distrib_utils, Opts, hard}, |
164 |
|
{mod_global_distrib_receiver, Opts, hard}]. |
165 |
|
|
166 |
|
%%-------------------------------------------------------------------- |
167 |
|
%% Hooks implementation |
168 |
|
%%-------------------------------------------------------------------- |
169 |
|
|
170 |
|
-spec session_opened(Acc, Params, Extra) -> {ok, Acc} when |
171 |
|
Acc :: any(), |
172 |
|
Params :: #{jid := jid:jid()}, |
173 |
|
Extra :: gen_hook:extra(). |
174 |
|
session_opened(Acc, #{jid := UserJid}, _) -> |
175 |
71 |
insert_for_jid(UserJid), |
176 |
71 |
{ok, Acc}. |
177 |
|
|
178 |
|
-spec session_closed(Acc, Params, Extra) -> {ok, Acc} when |
179 |
|
Acc :: mongoose_acc:t(), |
180 |
|
Params :: #{jid := jid:jid()}, |
181 |
|
Extra :: gen_hook:extra(). |
182 |
|
session_closed(Acc, #{jid := UserJid}, _) -> |
183 |
71 |
delete_for_jid(UserJid), |
184 |
71 |
{ok, Acc}. |
185 |
|
|
186 |
|
-spec packet_to_component(Acc, Params, Extra) -> {ok, Acc} when |
187 |
|
Acc :: mongoose_acc:t(), |
188 |
|
Params :: #{from := jid:jid()}, |
189 |
|
Extra :: gen_hook:extra(). |
190 |
|
packet_to_component(Acc, #{from := From}, _) -> |
191 |
5 |
mod_global_distrib_utils:maybe_update_mapping(From, Acc), |
192 |
5 |
{ok, Acc}. |
193 |
|
|
194 |
|
-spec register_subhost(Acc, Params, Extra) -> {ok, ok} when |
195 |
|
Acc :: any(), |
196 |
|
Params :: #{ldomain := binary(), is_hidden := boolean()}, |
197 |
|
Extra :: gen_hook:extra(). |
198 |
|
register_subhost(_, #{ldomain := SubHost, is_hidden := IsHidden}, _) -> |
199 |
10 |
IsSubhostOf = |
200 |
|
fun(Host) -> |
201 |
29 |
case binary:match(SubHost, Host) of |
202 |
10 |
{Start, Length} -> Start + Length == byte_size(SubHost); |
203 |
19 |
_ -> false |
204 |
|
end |
205 |
|
end, |
206 |
|
|
207 |
10 |
GlobalHost = opt(global_host), |
208 |
10 |
NewAcc = case lists:filter(IsSubhostOf, ?MYHOSTS) of |
209 |
10 |
[GlobalHost] -> insert_for_domain(SubHost, IsHidden); |
210 |
:-( |
_ -> ok |
211 |
|
end, |
212 |
10 |
{ok, NewAcc}. |
213 |
|
|
214 |
|
-spec unregister_subhost(Acc, Params, Extra) -> {ok, ok} when |
215 |
|
Acc :: any(), |
216 |
|
Params :: #{ldomain := binary()}, |
217 |
|
Extra :: gen_hook:extra(). |
218 |
|
unregister_subhost(_, #{ldomain := SubHost}, _) -> |
219 |
12 |
{ok, delete_for_domain(SubHost)}. |
220 |
|
|
221 |
|
%%-------------------------------------------------------------------- |
222 |
|
%% Helpers |
223 |
|
%%-------------------------------------------------------------------- |
224 |
|
|
225 |
|
hooks(HostType) -> |
226 |
42 |
[{register_subhost, global, fun ?MODULE:register_subhost/3, #{}, 90}, |
227 |
|
{unregister_subhost, global, fun ?MODULE:unregister_subhost/3, #{}, 90}, |
228 |
|
{packet_to_component, global, fun ?MODULE:packet_to_component/3, #{}, 90}, |
229 |
|
{sm_register_connection_hook, HostType, fun ?MODULE:session_opened/3, #{}, 90}, |
230 |
|
{sm_remove_connection_hook, HostType, fun ?MODULE:session_closed/3, #{}, 90}]. |
231 |
|
|
232 |
|
-spec normalize_jid(jid:ljid()) -> [binary()]. |
233 |
|
normalize_jid({_, _, _} = FullJid) -> |
234 |
190 |
case jid:to_bare(FullJid) of |
235 |
5 |
FullJid -> [jid:to_binary(FullJid)]; |
236 |
185 |
BareJid -> [jid:to_binary(FullJid), jid:to_binary(BareJid)] |
237 |
|
end. |
238 |
|
|
239 |
|
-spec opt(Key :: atom()) -> term(). |
240 |
|
opt(Key) -> |
241 |
137 |
mod_global_distrib_utils:opt(?MODULE, Key). |
242 |
|
|
243 |
|
-spec get_session(Key :: binary()) -> {ok, term()} | error. |
244 |
|
get_session(Key) -> |
245 |
69 |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_CACHE_MISSES, 1), |
246 |
69 |
mod_global_distrib_mapping_backend:get_session(Key). |
247 |
|
|
248 |
|
-spec put_session(Key :: binary()) -> ok. |
249 |
|
put_session(Key) -> |
250 |
142 |
?LOG_DEBUG(#{what => gd_mapper_put_session, key => Key}), |
251 |
142 |
mod_global_distrib_mapping_backend:put_session(Key). |
252 |
|
|
253 |
|
-spec delete_session(Key :: binary()) -> ok. |
254 |
|
delete_session(Key) -> |
255 |
146 |
?LOG_DEBUG(#{what => gd_mapper_delete_session, key => Key}), |
256 |
146 |
mod_global_distrib_mapping_backend:delete_session(Key). |
257 |
|
|
258 |
|
-spec get_domain(Key :: binary()) -> {ok, term()} | error. |
259 |
|
get_domain(Key) -> |
260 |
17 |
mongoose_metrics:update(global, ?GLOBAL_DISTRIB_MAPPING_CACHE_MISSES, 1), |
261 |
17 |
mod_global_distrib_mapping_backend:get_domain(Key). |
262 |
|
|
263 |
|
-spec put_domain(Key :: binary(), IsHidden :: boolean()) -> ok. |
264 |
|
put_domain(Key, IsHidden) -> |
265 |
10 |
mod_global_distrib_mapping_backend:put_domain(Key, IsHidden). |
266 |
|
|
267 |
|
-spec delete_domain(Key :: binary()) -> ok. |
268 |
|
delete_domain(Key) -> |
269 |
12 |
mod_global_distrib_mapping_backend:delete_domain(Key). |
270 |
|
|
271 |
|
-spec do_insert_for_jid(jid:jid() | jid:ljid(), Host :: jid:lserver(), |
272 |
|
PutSession :: fun((binary()) -> ok | error)) -> ok. |
273 |
|
do_insert_for_jid(#jid{} = Jid, Host, PutSession) -> |
274 |
80 |
do_insert_for_jid(jid:to_lower(Jid), Host, PutSession); |
275 |
|
do_insert_for_jid({_, _, _} = Jid, Host, PutSession) -> |
276 |
80 |
lists:foreach( |
277 |
|
fun(BinJid) -> |
278 |
159 |
ets_cache:update(?JID_TAB, BinJid, {ok, Host}, fun() -> PutSession(BinJid) end) |
279 |
|
end, |
280 |
|
normalize_jid(Jid)). |
281 |
|
|
282 |
|
-spec do_insert_for_domain(Domain :: binary(), Host :: jid:lserver(), |
283 |
|
PutDomain :: fun((binary()) -> ok | error)) -> ok. |
284 |
|
do_insert_for_domain(Domain, Host, PutDomain) -> |
285 |
10 |
ets_cache:update(?DOMAIN_TAB, Domain, {ok, Host}, fun() -> PutDomain(Domain) end). |
286 |
|
|
287 |
|
-spec do_lookup_jid(jid:ljid()) -> {ok, Host :: jid:lserver()} | error. |
288 |
|
do_lookup_jid({_, _, _} = Jid) -> |
289 |
1112 |
BinJid = jid:to_binary(Jid), |
290 |
1112 |
LookupInDB = fun(BJid) -> fun() -> get_session(BJid) end end, |
291 |
1112 |
case ets_cache:lookup(?JID_TAB, BinJid, LookupInDB(BinJid)) of |
292 |
1058 |
{ok, _} = Result -> Result; |
293 |
|
Other -> |
294 |
54 |
case jid:to_bare(Jid) of |
295 |
6 |
Jid -> Other; |
296 |
48 |
BareJid -> ets_cache:lookup(?JID_TAB, BinJid, LookupInDB(jid:to_binary(BareJid))) |
297 |
|
end |
298 |
|
end. |