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/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, 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 |
|
|
45 |
|
-ifdef(TEST). |
46 |
|
|
47 |
|
%% required for unit tests |
48 |
|
start(Pairs, AllowedHostTypes) -> |
49 |
|
just_ok(gen_server:start({local, ?MODULE}, ?MODULE, [Pairs, AllowedHostTypes], [])). |
50 |
|
|
51 |
|
stop() -> |
52 |
|
gen_server:stop(?MODULE). |
53 |
|
|
54 |
|
-else. |
55 |
|
|
56 |
|
start(Pairs, AllowedHostTypes) -> |
57 |
99 |
ChildSpec = |
58 |
|
{?MODULE, |
59 |
|
{?MODULE, start_link, [Pairs, AllowedHostTypes]}, |
60 |
|
permanent, infinity, worker, [?MODULE]}, |
61 |
99 |
just_ok(supervisor:start_child(ejabberd_sup, ChildSpec)). |
62 |
|
|
63 |
|
%% required for integration tests |
64 |
|
stop() -> |
65 |
17 |
supervisor:terminate_child(ejabberd_sup, ?MODULE), |
66 |
17 |
supervisor:delete_child(ejabberd_sup, ?MODULE), |
67 |
17 |
ok. |
68 |
|
|
69 |
|
-endif. |
70 |
|
|
71 |
|
start_link(Pairs, AllowedHostTypes) -> |
72 |
99 |
gen_server:start_link({local, ?MODULE}, ?MODULE, [Pairs, AllowedHostTypes], []). |
73 |
|
|
74 |
|
get_host_type(Domain) -> |
75 |
37447 |
case ets:lookup(?TABLE, Domain) of |
76 |
|
[] -> |
77 |
2545 |
{error, not_found}; |
78 |
|
[{_Domain, HostType, _Source}] -> |
79 |
34902 |
{ok, HostType} |
80 |
|
end. |
81 |
|
|
82 |
|
is_static(Domain) -> |
83 |
3 |
case ets:lookup(?TABLE, Domain) of |
84 |
|
[{_Domain, _HostType, _Source = config}] -> |
85 |
3 |
true; |
86 |
|
_ -> |
87 |
:-( |
false |
88 |
|
end. |
89 |
|
|
90 |
|
is_host_type_allowed(HostType) -> |
91 |
15 |
ets:member(?HOST_TYPE_TABLE, HostType). |
92 |
|
|
93 |
|
get_all_static() -> |
94 |
1 |
pairs(ets:match(?TABLE, {'$1', '$2', config})). |
95 |
|
|
96 |
|
get_all_dynamic() -> |
97 |
:-( |
pairs(ets:match(?TABLE, {'$1', '$2', {dynamic, '_'}})). |
98 |
|
|
99 |
|
get_domains_by_host_type(HostType) when is_binary(HostType) -> |
100 |
3 |
heads(ets:match(?TABLE, {'$1', HostType, '_'})). |
101 |
|
|
102 |
|
domains_count() -> |
103 |
40 |
ets:info(?TABLE, size). |
104 |
|
|
105 |
|
-spec for_each_domain(host_type(), fun((host_type(), domain())-> any())) -> ok. |
106 |
|
for_each_domain(HostType, Func) -> |
107 |
422 |
ets:safe_fixtable(?TABLE, true), |
108 |
422 |
MS = ets:fun2ms(fun({Domain, HT, _}) when HT =:= HostType -> |
109 |
|
[HostType, Domain] |
110 |
|
end), |
111 |
422 |
Selection = ets:select(?TABLE, MS, 100), |
112 |
422 |
for_each_selected_domain(Selection, Func), |
113 |
422 |
ets:safe_fixtable(?TABLE, false), |
114 |
422 |
ok. |
115 |
|
|
116 |
|
get_all_outdated(CurrentSource) -> |
117 |
:-( |
MS = ets:fun2ms(fun({Domain, HostType, {dynamic, Src}}) when Src =/= CurrentSource -> |
118 |
|
{Domain, HostType} |
119 |
|
end), |
120 |
:-( |
ets:select(?TABLE, MS). |
121 |
|
|
122 |
|
heads(List) -> |
123 |
3 |
[H || [H|_] <- List]. |
124 |
|
|
125 |
|
pairs(List) -> |
126 |
1 |
[{K, V} || [K, V] <- List]. |
127 |
|
|
128 |
|
insert(Domain, HostType, Source) -> |
129 |
14 |
gen_server:call(?MODULE, {insert, Domain, HostType, Source}). |
130 |
|
|
131 |
|
delete(Domain) -> |
132 |
14 |
gen_server:call(?MODULE, {delete, Domain}). |
133 |
|
|
134 |
|
get_start_args() -> |
135 |
3 |
gen_server:call(?MODULE, get_start_args). |
136 |
|
|
137 |
|
%%-------------------------------------------------------------------- |
138 |
|
%% gen_server callbacks |
139 |
|
%%-------------------------------------------------------------------- |
140 |
|
init([Pairs, AllowedHostTypes]) -> |
141 |
99 |
mongoose_loader_state:init(), |
142 |
99 |
ets:new(?TABLE, [set, named_table, protected, {read_concurrency, true}]), |
143 |
99 |
ets:new(?HOST_TYPE_TABLE, [set, named_table, protected, {read_concurrency, true}]), |
144 |
99 |
insert_host_types(?HOST_TYPE_TABLE, AllowedHostTypes), |
145 |
99 |
insert_initial(?TABLE, Pairs), |
146 |
99 |
{ok, #{initial_pairs => Pairs, |
147 |
|
initial_host_types => AllowedHostTypes}}. |
148 |
|
|
149 |
|
handle_call({delete, Domain}, _From, State) -> |
150 |
14 |
Result = handle_delete(Domain), |
151 |
14 |
{reply, Result, State}; |
152 |
|
handle_call({insert, Domain, HostType, Source}, _From, State) -> |
153 |
14 |
Result = handle_insert(Domain, HostType, Source), |
154 |
14 |
{reply, Result, State}; |
155 |
|
handle_call(get_start_args, _From, State = #{initial_pairs := Pairs, |
156 |
|
initial_host_types := AllowedHostTypes}) -> |
157 |
3 |
{reply, [Pairs, AllowedHostTypes], State}; |
158 |
|
handle_call(Request, From, State) -> |
159 |
:-( |
?UNEXPECTED_CALL(Request, From), |
160 |
:-( |
{reply, ok, State}. |
161 |
|
|
162 |
|
handle_cast(Msg, State) -> |
163 |
:-( |
?UNEXPECTED_CAST(Msg), |
164 |
:-( |
{noreply, State}. |
165 |
|
|
166 |
|
handle_info(Info, State) -> |
167 |
:-( |
?UNEXPECTED_INFO(Info), |
168 |
:-( |
{noreply, State}. |
169 |
|
|
170 |
|
terminate(_Reason, _State) -> |
171 |
:-( |
ok. |
172 |
|
|
173 |
|
code_change(_OldVsn, State, _Extra) -> |
174 |
:-( |
{ok, State}. |
175 |
|
|
176 |
|
%%-------------------------------------------------------------------- |
177 |
|
%% internal functions |
178 |
|
%%-------------------------------------------------------------------- |
179 |
422 |
for_each_selected_domain('$end_of_table', _) -> ok; |
180 |
|
for_each_selected_domain({MatchList, Continuation}, Func) -> |
181 |
349 |
[safely:apply_and_log(Func, Args, log_context(Args)) || Args <- MatchList], |
182 |
349 |
Selection = ets:select(Continuation), |
183 |
349 |
for_each_selected_domain(Selection, Func). |
184 |
|
|
185 |
|
log_context(Args) -> |
186 |
351 |
#{what => domain_operation_failed, |
187 |
|
args => Args}. |
188 |
|
|
189 |
|
insert_initial(Tab, Pairs) -> |
190 |
99 |
lists:foreach(fun({Domain, HostType}) -> |
191 |
273 |
insert_initial_pair(Tab, Domain, HostType) |
192 |
|
end, Pairs). |
193 |
|
|
194 |
|
insert_initial_pair(Tab, Domain, HostType) -> |
195 |
273 |
ets:insert_new(Tab, new_object(Domain, HostType, config)). |
196 |
|
|
197 |
|
new_object(Domain, HostType, Source) -> |
198 |
287 |
{Domain, HostType, Source}. |
199 |
|
|
200 |
99 |
just_ok({ok, _}) -> ok; |
201 |
:-( |
just_ok(Other) -> Other. |
202 |
|
|
203 |
|
insert_host_types(Tab, AllowedHostTypes) -> |
204 |
99 |
lists:foreach(fun(HostType) -> |
205 |
274 |
ets:insert_new(Tab, {HostType}) |
206 |
|
end, AllowedHostTypes), |
207 |
99 |
ok. |
208 |
|
|
209 |
|
handle_delete(Domain) -> |
210 |
14 |
case ets:lookup(?TABLE, Domain) of |
211 |
|
[{Domain, _HostType, _Source = config}] -> |
212 |
|
%% Ignore any static domains |
213 |
:-( |
?LOG_ERROR(#{what => domain_static_but_was_in_db, domain => Domain}), |
214 |
:-( |
{error, static}; |
215 |
|
[] -> |
216 |
|
%% nothing to remove |
217 |
:-( |
ok; |
218 |
|
[{Domain, HostType, _Source}] -> |
219 |
14 |
ets:delete(?TABLE, Domain), |
220 |
14 |
mongoose_lazy_routing:maybe_remove_domain(HostType, Domain), |
221 |
14 |
mongoose_subdomain_core:remove_domain(HostType, Domain), |
222 |
14 |
ok |
223 |
|
end. |
224 |
|
|
225 |
|
handle_insert(Domain, HostType, Source) -> |
226 |
14 |
case is_host_type_allowed(HostType) of |
227 |
|
true -> |
228 |
14 |
case ets:lookup(?TABLE, Domain) of |
229 |
|
[{Domain, _HostType, _Source = config}] -> |
230 |
|
%% Ignore any static domains |
231 |
:-( |
?LOG_ERROR(#{what => domain_static_but_in_db, domain => Domain}), |
232 |
:-( |
{error, static}; |
233 |
|
[] -> |
234 |
14 |
ets:insert_new(?TABLE, new_object(Domain, HostType, {dynamic, Source})), |
235 |
14 |
mongoose_subdomain_core:add_domain(HostType, Domain), |
236 |
14 |
ok; |
237 |
|
[{Domain, HT, _Source}] when HT =:= HostType -> |
238 |
:-( |
ets:insert(?TABLE, new_object(Domain, HostType, {dynamic, Source})), |
239 |
:-( |
ok; |
240 |
|
[{Domain, HT, _Source}] when HT =/= HostType -> |
241 |
:-( |
?LOG_ERROR(#{what => ignore_domain_from_db_with_different_host_type, |
242 |
|
domain => Domain, |
243 |
|
core_host_type => HT, |
244 |
:-( |
db_host_type => HostType}), |
245 |
:-( |
{error, bad_insert} |
246 |
|
end; |
247 |
|
false -> |
248 |
:-( |
?LOG_ERROR(#{what => ignore_domain_from_db_with_unknown_host_type, |
249 |
:-( |
domain => Domain, host_type => HostType}), |
250 |
:-( |
{error, unknown_host_type} |
251 |
|
|
252 |
|
end. |
253 |
|
|