1 |
|
%% Generally, you should not call anything from this module. |
2 |
|
%% Use mongoose_domain_api module instead. |
3 |
|
-module(mongoose_domain_core). |
4 |
|
-behaviour(gen_server). |
5 |
|
|
6 |
|
-include("mongoose_logger.hrl"). |
7 |
|
|
8 |
|
%% required for ets:fun2ms/1 pseudo function |
9 |
|
-include_lib("stdlib/include/ms_transform.hrl"). |
10 |
|
|
11 |
|
-export([start/0, start/2, stop/0]). |
12 |
|
-export([start_link/2]). |
13 |
|
-export([get_host_type/1]). |
14 |
|
-export([is_static/1]). |
15 |
|
|
16 |
|
%% API, used by DB module |
17 |
|
-export([insert/3, |
18 |
|
delete/1]). |
19 |
|
|
20 |
|
-export([get_all_static/0, |
21 |
|
get_all_dynamic/0, |
22 |
|
get_all_outdated/1, |
23 |
|
get_domains_by_host_type/1, |
24 |
|
domains_count/0]). |
25 |
|
|
26 |
|
-export([for_each_domain/2]). |
27 |
|
|
28 |
|
-export([is_host_type_allowed/1]). |
29 |
|
|
30 |
|
%% For testing |
31 |
|
-export([get_start_args/0]). |
32 |
|
|
33 |
|
%% gen_server callbacks |
34 |
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
35 |
|
terminate/2, code_change/3]). |
36 |
|
|
37 |
|
-ignore_xref([get_start_args/0, start_link/2, start/2, stop/0]). |
38 |
|
|
39 |
|
-define(TABLE, ?MODULE). |
40 |
|
-define(HOST_TYPE_TABLE, mongoose_domain_core_host_types). |
41 |
|
|
42 |
|
-type host_type() :: mongooseim:host_type(). |
43 |
|
-type domain() :: mongooseim:domain_name(). |
44 |
|
-type pair() :: {domain(), host_type()}. |
45 |
|
|
46 |
|
start() -> |
47 |
79 |
Pairs = get_static_pairs(), |
48 |
79 |
AllowedHostTypes = mongoose_config:get_opt(host_types), |
49 |
79 |
start(Pairs, AllowedHostTypes). |
50 |
|
|
51 |
|
-ifdef(TEST). |
52 |
|
|
53 |
|
%% required for unit tests |
54 |
|
start(Pairs, AllowedHostTypes) -> |
55 |
|
just_ok(gen_server:start({local, ?MODULE}, ?MODULE, [Pairs, AllowedHostTypes], [])). |
56 |
|
|
57 |
|
stop() -> |
58 |
|
gen_server:stop(?MODULE). |
59 |
|
|
60 |
|
-else. |
61 |
|
|
62 |
|
start(Pairs, AllowedHostTypes) -> |
63 |
344 |
ChildSpec = |
64 |
|
{?MODULE, |
65 |
|
{?MODULE, start_link, [Pairs, AllowedHostTypes]}, |
66 |
|
permanent, infinity, worker, [?MODULE]}, |
67 |
344 |
just_ok(supervisor:start_child(ejabberd_sup, ChildSpec)). |
68 |
|
|
69 |
|
%% required for integration tests |
70 |
|
stop() -> |
71 |
268 |
supervisor:terminate_child(ejabberd_sup, ?MODULE), |
72 |
268 |
supervisor:delete_child(ejabberd_sup, ?MODULE), |
73 |
268 |
ok. |
74 |
|
|
75 |
|
-endif. |
76 |
|
|
77 |
|
start_link(Pairs, AllowedHostTypes) -> |
78 |
344 |
gen_server:start_link({local, ?MODULE}, ?MODULE, [Pairs, AllowedHostTypes], []). |
79 |
|
|
80 |
|
get_host_type(Domain) -> |
81 |
72385 |
case ets:lookup(?TABLE, Domain) of |
82 |
|
[] -> |
83 |
3917 |
{error, not_found}; |
84 |
|
[{_Domain, HostType, _Source}] -> |
85 |
68468 |
{ok, HostType} |
86 |
|
end. |
87 |
|
|
88 |
|
is_static(Domain) -> |
89 |
213 |
case ets:lookup(?TABLE, Domain) of |
90 |
|
[{_Domain, _HostType, _Source = config}] -> |
91 |
14 |
true; |
92 |
|
_ -> |
93 |
199 |
false |
94 |
|
end. |
95 |
|
|
96 |
|
is_host_type_allowed(HostType) -> |
97 |
568 |
ets:member(?HOST_TYPE_TABLE, HostType). |
98 |
|
|
99 |
|
get_all_static() -> |
100 |
2 |
pairs(ets:match(?TABLE, {'$1', '$2', config})). |
101 |
|
|
102 |
|
get_all_dynamic() -> |
103 |
1 |
pairs(ets:match(?TABLE, {'$1', '$2', {dynamic, '_'}})). |
104 |
|
|
105 |
|
get_domains_by_host_type(HostType) when is_binary(HostType) -> |
106 |
33 |
heads(ets:match(?TABLE, {'$1', HostType, '_'})). |
107 |
|
|
108 |
|
domains_count() -> |
109 |
33 |
ets:info(?TABLE, size). |
110 |
|
|
111 |
|
-spec for_each_domain(host_type(), fun((host_type(), domain())-> any())) -> ok. |
112 |
|
for_each_domain(HostType, Func) -> |
113 |
390 |
ets:safe_fixtable(?TABLE, true), |
114 |
390 |
MS = ets:fun2ms(fun({Domain, HT, _}) when HT =:= HostType -> |
115 |
|
[HostType, Domain] |
116 |
|
end), |
117 |
390 |
Selection = ets:select(?TABLE, MS, 100), |
118 |
390 |
for_each_selected_domain(Selection, Func), |
119 |
390 |
ets:safe_fixtable(?TABLE, false), |
120 |
390 |
ok. |
121 |
|
|
122 |
|
get_all_outdated(CurrentSource) -> |
123 |
1 |
MS = ets:fun2ms(fun({Domain, HostType, {dynamic, Src}}) when Src =/= CurrentSource -> |
124 |
|
{Domain, HostType} |
125 |
|
end), |
126 |
1 |
ets:select(?TABLE, MS). |
127 |
|
|
128 |
|
heads(List) -> |
129 |
33 |
[H || [H|_] <- List]. |
130 |
|
|
131 |
|
pairs(List) -> |
132 |
3 |
[{K, V} || [K, V] <- List]. |
133 |
|
|
134 |
|
insert(Domain, HostType, Source) -> |
135 |
410 |
gen_server:call(?MODULE, {insert, Domain, HostType, Source}). |
136 |
|
|
137 |
|
delete(Domain) -> |
138 |
84 |
gen_server:call(?MODULE, {delete, Domain}). |
139 |
|
|
140 |
|
get_start_args() -> |
141 |
:-( |
gen_server:call(?MODULE, get_start_args). |
142 |
|
|
143 |
|
%%-------------------------------------------------------------------- |
144 |
|
%% gen_server callbacks |
145 |
|
%%-------------------------------------------------------------------- |
146 |
|
init([Pairs, AllowedHostTypes]) -> |
147 |
344 |
mongoose_loader_state:init(), |
148 |
344 |
ets:new(?TABLE, [set, named_table, protected, {read_concurrency, true}]), |
149 |
344 |
ets:new(?HOST_TYPE_TABLE, [set, named_table, protected, {read_concurrency, true}]), |
150 |
344 |
insert_host_types(?HOST_TYPE_TABLE, AllowedHostTypes), |
151 |
344 |
insert_initial(?TABLE, Pairs), |
152 |
344 |
{ok, #{initial_pairs => Pairs, |
153 |
|
initial_host_types => AllowedHostTypes}}. |
154 |
|
|
155 |
|
handle_call({delete, Domain}, _From, State) -> |
156 |
84 |
Result = handle_delete(Domain), |
157 |
84 |
{reply, Result, State}; |
158 |
|
handle_call({insert, Domain, HostType, Source}, _From, State) -> |
159 |
410 |
Result = handle_insert(Domain, HostType, Source), |
160 |
410 |
{reply, Result, State}; |
161 |
|
handle_call(get_start_args, _From, State = #{initial_pairs := Pairs, |
162 |
|
initial_host_types := AllowedHostTypes}) -> |
163 |
:-( |
{reply, [Pairs, AllowedHostTypes], State}; |
164 |
|
handle_call(Request, From, State) -> |
165 |
:-( |
?UNEXPECTED_CALL(Request, From), |
166 |
:-( |
{reply, ok, State}. |
167 |
|
|
168 |
|
handle_cast(Msg, State) -> |
169 |
:-( |
?UNEXPECTED_CAST(Msg), |
170 |
:-( |
{noreply, State}. |
171 |
|
|
172 |
|
handle_info(Info, State) -> |
173 |
:-( |
?UNEXPECTED_INFO(Info), |
174 |
:-( |
{noreply, State}. |
175 |
|
|
176 |
|
terminate(_Reason, _State) -> |
177 |
:-( |
ok. |
178 |
|
|
179 |
|
code_change(_OldVsn, State, _Extra) -> |
180 |
:-( |
{ok, State}. |
181 |
|
|
182 |
|
%%-------------------------------------------------------------------- |
183 |
|
%% internal functions |
184 |
|
%%-------------------------------------------------------------------- |
185 |
|
%% Domains should be nameprepped using `jid:nameprep' |
186 |
|
-spec get_static_pairs() -> [pair()]. |
187 |
|
get_static_pairs() -> |
188 |
79 |
[{H, H} || H <- mongoose_config:get_opt(hosts)]. |
189 |
|
|
190 |
390 |
for_each_selected_domain('$end_of_table', _) -> ok; |
191 |
|
for_each_selected_domain({MatchList, Continuation}, Func) -> |
192 |
387 |
[safely:apply_and_log(Func, Args, log_context(Args)) || Args <- MatchList], |
193 |
387 |
Selection = ets:select(Continuation), |
194 |
387 |
for_each_selected_domain(Selection, Func). |
195 |
|
|
196 |
|
log_context(Args) -> |
197 |
546 |
#{what => domain_operation_failed, |
198 |
|
args => Args}. |
199 |
|
|
200 |
|
insert_initial(Tab, Pairs) -> |
201 |
344 |
lists:foreach(fun({Domain, HostType}) -> |
202 |
631 |
insert_initial_pair(Tab, Domain, HostType) |
203 |
|
end, Pairs). |
204 |
|
|
205 |
|
insert_initial_pair(Tab, Domain, HostType) -> |
206 |
631 |
ets:insert_new(Tab, new_object(Domain, HostType, config)). |
207 |
|
|
208 |
|
new_object(Domain, HostType, Source) -> |
209 |
971 |
{Domain, HostType, Source}. |
210 |
|
|
211 |
344 |
just_ok({ok, _}) -> ok; |
212 |
:-( |
just_ok(Other) -> Other. |
213 |
|
|
214 |
|
insert_host_types(Tab, AllowedHostTypes) -> |
215 |
344 |
lists:foreach(fun(HostType) -> |
216 |
1882 |
ets:insert_new(Tab, {HostType}) |
217 |
|
end, AllowedHostTypes), |
218 |
344 |
ok. |
219 |
|
|
220 |
|
handle_delete(Domain) -> |
221 |
84 |
case ets:lookup(?TABLE, Domain) of |
222 |
|
[{Domain, _HostType, _Source = config}] -> |
223 |
|
%% Ignore any static domains |
224 |
:-( |
?LOG_ERROR(#{what => domain_static_but_was_in_db, domain => Domain}), |
225 |
:-( |
{error, static}; |
226 |
|
[] -> |
227 |
|
%% nothing to remove |
228 |
16 |
ok; |
229 |
|
[{Domain, HostType, _Source}] -> |
230 |
68 |
ets:delete(?TABLE, Domain), |
231 |
68 |
mongoose_lazy_routing:maybe_remove_domain(HostType, Domain), |
232 |
68 |
mongoose_subdomain_core:remove_domain(HostType, Domain), |
233 |
68 |
ok |
234 |
|
end. |
235 |
|
|
236 |
|
handle_insert(Domain, HostType, Source) -> |
237 |
410 |
case is_host_type_allowed(HostType) of |
238 |
|
true -> |
239 |
342 |
case ets:lookup(?TABLE, Domain) of |
240 |
|
[{Domain, _HostType, _Source = config}] -> |
241 |
|
%% Ignore any static domains |
242 |
1 |
?LOG_ERROR(#{what => domain_static_but_in_db, domain => Domain}), |
243 |
1 |
{error, static}; |
244 |
|
[] -> |
245 |
335 |
ets:insert_new(?TABLE, new_object(Domain, HostType, {dynamic, Source})), |
246 |
335 |
mongoose_subdomain_core:add_domain(HostType, Domain), |
247 |
335 |
ok; |
248 |
|
[{Domain, HT, _Source}] when HT =:= HostType -> |
249 |
5 |
ets:insert(?TABLE, new_object(Domain, HostType, {dynamic, Source})), |
250 |
5 |
ok; |
251 |
|
[{Domain, HT, _Source}] when HT =/= HostType -> |
252 |
1 |
?LOG_ERROR(#{what => ignore_domain_from_db_with_different_host_type, |
253 |
|
domain => Domain, |
254 |
|
core_host_type => HT, |
255 |
:-( |
db_host_type => HostType}), |
256 |
1 |
{error, bad_insert} |
257 |
|
end; |
258 |
|
false -> |
259 |
68 |
?LOG_ERROR(#{what => ignore_domain_from_db_with_unknown_host_type, |
260 |
:-( |
domain => Domain, host_type => HostType}), |
261 |
68 |
{error, unknown_host_type} |
262 |
|
|
263 |
|
end. |
264 |
|
|