Implement cache for mod_announce

This commit is contained in:
Evgeniy Khramtsov 2017-05-22 16:14:28 +03:00
parent 908bedeaa6
commit d7878ef131
4 changed files with 202 additions and 58 deletions

View File

@ -36,7 +36,7 @@
import_start/2, import/5, announce/1, send_motd/1, disco_identity/5, import_start/2, import/5, announce/1, send_motd/1, disco_identity/5,
disco_features/5, disco_items/5, depends/2, disco_features/5, disco_items/5, depends/2,
send_announcement_to_all/3, announce_commands/4, send_announcement_to_all/3, announce_commands/4,
announce_items/4, mod_opt_type/1]). announce_items/4, mod_opt_type/1, clean_cache/1]).
-export([init/1, handle_call/3, handle_cast/2, -export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-export([announce_all/1, -export([announce_all/1,
@ -57,17 +57,22 @@
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), binary(), [binary()]) -> ok. -callback import(binary(), binary(), [binary()]) -> ok.
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}. -callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> ok | {error, any()}.
-callback set_motd(binary(), xmlel()) -> {atomic, any()}. -callback set_motd(binary(), xmlel()) -> ok | {error, any()}.
-callback delete_motd(binary()) -> {atomic, any()}. -callback delete_motd(binary()) -> ok | {error, any()}.
-callback get_motd(binary()) -> {ok, xmlel()} | error. -callback get_motd(binary()) -> {ok, xmlel()} | error | {error, any()}.
-callback is_motd_user(binary(), binary()) -> boolean(). -callback is_motd_user(binary(), binary()) -> {ok, boolean()} | {error, any()}.
-callback set_motd_user(binary(), binary()) -> {atomic, any()}. -callback set_motd_user(binary(), binary()) -> ok | {error, any()}.
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-optional_callbacks([use_cache/1, cache_nodes/1]).
-record(state, {host :: binary()}). -record(state, {host :: binary()}).
-define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>, -define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>,
<<"admin">>, <<Sub>>]). <<"admin">>, <<Sub>>]).
-define(MOTD_CACHE, motd_cache).
tokenize(Node) -> str:tokens(Node, <<"/#">>). tokenize(Node) -> str:tokens(Node, <<"/#">>).
@ -88,7 +93,7 @@ reload(Host, NewOpts, OldOpts) ->
true -> true ->
ok ok
end, end,
ok. init_cache(NewMod, Host, NewOpts).
depends(_Host, _Opts) -> depends(_Host, _Opts) ->
[{mod_adhoc, hard}]. [{mod_adhoc, hard}].
@ -100,6 +105,7 @@ init([Host, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host, ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50), ?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
@ -684,19 +690,19 @@ announce_all_hosts_motd_update(Packet) ->
announce_motd_update(LServer, Packet) -> announce_motd_update(LServer, Packet) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer), delete_motd(Mod, LServer),
Mod:set_motd(LServer, xmpp:encode(Packet)). set_motd(Mod, LServer, xmpp:encode(Packet)).
announce_motd_delete(#message{to = To}) -> announce_motd_delete(#message{to = To}) ->
LServer = To#jid.lserver, LServer = To#jid.lserver,
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer). delete_motd(Mod, LServer).
announce_all_hosts_motd_delete(_Packet) -> announce_all_hosts_motd_delete(_Packet) ->
lists:foreach( lists:foreach(
fun(Host) -> fun(Host) ->
Mod = gen_mod:db_mod(Host, ?MODULE), Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:delete_motd(Host) delete_motd(Mod, Host)
end, ?MYHOSTS). end, ?MYHOSTS).
-spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}. -spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}.
@ -707,16 +713,16 @@ send_motd({#presence{type = available},
#{jid := #jid{luser = LUser, lserver = LServer} = JID}} = Acc) #{jid := #jid{luser = LUser, lserver = LServer} = JID}} = Acc)
when LUser /= <<>> -> when LUser /= <<>> ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of case get_motd(Mod, LServer) of
{ok, Packet} -> {ok, Packet} ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
Msg -> Msg ->
case Mod:is_motd_user(LUser, LServer) of case is_motd_user(Mod, LUser, LServer) of
false -> false ->
Local = jid:make(LServer), Local = jid:make(LServer),
ejabberd_router:route( ejabberd_router:route(
xmpp:set_from_to(Msg, Local, JID)), xmpp:set_from_to(Msg, Local, JID)),
Mod:set_motd_user(LUser, LServer); set_motd_user(Mod, LUser, LServer);
true -> true ->
ok ok
end end
@ -724,16 +730,81 @@ send_motd({#presence{type = available},
?ERROR_MSG("failed to decode motd packet ~p: ~s", ?ERROR_MSG("failed to decode motd packet ~p: ~s",
[Packet, xmpp:format_error(Why)]) [Packet, xmpp:format_error(Why)])
end; end;
error -> _ ->
ok ok
end, end,
Acc; Acc;
send_motd(Acc) -> send_motd(Acc) ->
Acc. Acc.
-spec get_motd(module(), binary()) -> {ok, xmlel()} | error | {error, any()}.
get_motd(Mod, LServer) ->
case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?MOTD_CACHE, {<<"">>, LServer},
fun() -> Mod:get_motd(LServer) end);
false ->
Mod:get_motd(LServer)
end.
-spec set_motd(module(), binary(), xmlel()) -> any().
set_motd(Mod, LServer, XML) ->
case use_cache(Mod, LServer) of
true ->
ets_cache:update(
?MOTD_CACHE, {<<"">>, LServer}, {ok, XML},
fun() -> Mod:set_motd(LServer, XML) end,
cache_nodes(Mod, LServer));
false ->
Mod:set_motd(LServer, XML)
end.
-spec is_motd_user(module(), binary(), binary()) -> boolean().
is_motd_user(Mod, LUser, LServer) ->
Res = case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?MOTD_CACHE, {LUser, LServer},
fun() -> Mod:is_motd_user(LUser, LServer) end);
false ->
Mod:is_motd_user(LUser, LServer)
end,
case Res of
{ok, Bool} -> Bool;
_ -> false
end.
-spec set_motd_user(module(), binary(), binary()) -> any().
set_motd_user(Mod, LUser, LServer) ->
case use_cache(Mod, LServer) of
true ->
ets_cache:update(
?MOTD_CACHE, {LUser, LServer}, {ok, true},
fun() -> Mod:set_motd_user(LUser, LServer) end,
cache_nodes(Mod, LServer));
false ->
Mod:set_motd_user(LUser, LServer)
end.
-spec delete_motd(module(), binary()) -> ok | {error, any()}.
delete_motd(Mod, LServer) ->
case Mod:delete_motd(LServer) of
ok ->
case use_cache(Mod, LServer) of
true ->
ejabberd_cluster:eval_everywhere(
?MODULE, clean_cache, [LServer]);
false ->
ok
end;
Err ->
Err
end.
get_stored_motd(LServer) -> get_stored_motd(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of case get_motd(Mod, LServer) of
{ok, Packet} -> {ok, Packet} ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
#message{body = Body, subject = Subject} -> #message{body = Body, subject = Subject} ->
@ -742,7 +813,7 @@ get_stored_motd(LServer) ->
?ERROR_MSG("failed to decode motd packet ~p: ~s", ?ERROR_MSG("failed to decode motd packet ~p: ~s",
[Packet, xmpp:format_error(Why)]) [Packet, xmpp:format_error(Why)])
end; end;
error -> _ ->
{<<>>, <<>>} {<<>>, <<>>}
end. end.
@ -775,6 +846,55 @@ route_forbidden_error(Packet) ->
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang), Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
ejabberd_router:route_error(Packet, Err). ejabberd_router:route_error(Packet, Err).
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
init_cache(Mod, Host, Opts) ->
case use_cache(Mod, Host) of
true ->
CacheOpts = cache_opts(Host, Opts),
ets_cache:new(?MOTD_CACHE, CacheOpts);
false ->
ets_cache:delete(?MOTD_CACHE)
end.
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
cache_opts(Host, Opts) ->
MaxSize = gen_mod:get_opt(
cache_size, Opts,
ejabberd_config:cache_size(Host)),
CacheMissed = gen_mod:get_opt(
cache_missed, Opts,
ejabberd_config:cache_missed(Host)),
LifeTime = case gen_mod:get_opt(
cache_life_time, Opts,
ejabberd_config:cache_life_time(Host)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(Host);
false ->
gen_mod:get_module_opt(
Host, ?MODULE, use_cache,
ejabberd_config:use_cache(Host))
end.
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, Host) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(Host);
false -> ejabberd_cluster:get_nodes()
end.
-spec clean_cache(binary()) -> non_neg_integer().
clean_cache(LServer) ->
ets_cache:filter(
?MOTD_CACHE,
fun({_, S}, _) -> S /= LServer end).
%%------------------------------------------------------------------------- %%-------------------------------------------------------------------------
export(LServer) -> export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),

View File

@ -40,11 +40,11 @@
%%%=================================================================== %%%===================================================================
init(_Host, _Opts) -> init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, motd, ejabberd_mnesia:create(?MODULE, motd,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, {attributes,
record_info(fields, motd)}]), record_info(fields, motd)}]),
ejabberd_mnesia:create(?MODULE, motd_users, ejabberd_mnesia:create(?MODULE, motd_users,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, {attributes,
record_info(fields, motd_users)}]). record_info(fields, motd_users)}]).
@ -55,13 +55,13 @@ set_motd_users(_LServer, USRs) ->
mnesia:write(#motd_users{us = {U, S}}) mnesia:write(#motd_users{us = {U, S}})
end, USRs) end, USRs)
end, end,
mnesia:transaction(F). transaction(F).
set_motd(LServer, Packet) -> set_motd(LServer, Packet) ->
F = fun() -> F = fun() ->
mnesia:write(#motd{server = LServer, packet = Packet}) mnesia:write(#motd{server = LServer, packet = Packet})
end, end,
mnesia:transaction(F). transaction(F).
delete_motd(LServer) -> delete_motd(LServer) ->
F = fun() -> F = fun() ->
@ -76,27 +76,27 @@ delete_motd(LServer) ->
mnesia:delete({motd_users, US}) mnesia:delete({motd_users, US})
end, Users) end, Users)
end, end,
mnesia:transaction(F). transaction(F).
get_motd(LServer) -> get_motd(LServer) ->
case mnesia:dirty_read({motd, LServer}) of case mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] -> [#motd{packet = Packet}] ->
{ok, Packet}; {ok, Packet};
_ -> [] ->
error error
end. end.
is_motd_user(LUser, LServer) -> is_motd_user(LUser, LServer) ->
case mnesia:dirty_read({motd_users, {LUser, LServer}}) of case mnesia:dirty_read({motd_users, {LUser, LServer}}) of
[#motd_users{}] -> true; [#motd_users{}] -> {ok, true};
_ -> false _ -> {ok, false}
end. end.
set_motd_user(LUser, LServer) -> set_motd_user(LUser, LServer) ->
F = fun() -> F = fun() ->
mnesia:write(#motd_users{us = {LUser, LServer}}) mnesia:write(#motd_users{us = {LUser, LServer}})
end, end,
mnesia:transaction(F). transaction(F).
need_transform(#motd{server = S}) when is_list(S) -> need_transform(#motd{server = S}) when is_list(S) ->
?INFO_MSG("Mnesia table 'motd' will be converted to binary", []), ?INFO_MSG("Mnesia table 'motd' will be converted to binary", []),
@ -124,3 +124,11 @@ import(LServer, <<"motd">>, [LUser, <<>>, _TimeStamp]) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
transaction(F) ->
case mnesia:transaction(F) of
{atomic, Res} ->
Res;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.

View File

@ -46,47 +46,48 @@ set_motd_users(_LServer, USRs) ->
ok = ejabberd_riak:put(#motd_users{us = {U, S}}, ok = ejabberd_riak:put(#motd_users{us = {U, S}},
motd_users_schema(), motd_users_schema(),
[{'2i', [{<<"server">>, S}]}]) [{'2i', [{<<"server">>, S}]}])
end, USRs), end, USRs)
{atomic, ok} catch _:{badmatch, {error, _} = Err} ->
catch _:{badmatch, Err} -> Err
{atomic, Err}
end. end.
set_motd(LServer, Packet) -> set_motd(LServer, Packet) ->
{atomic, ejabberd_riak:put(#motd{server = LServer, ejabberd_riak:put(#motd{server = LServer,
packet = Packet}, packet = Packet},
motd_schema())}. motd_schema()).
delete_motd(LServer) -> delete_motd(LServer) ->
try try
ok = ejabberd_riak:delete(motd, LServer), ok = ejabberd_riak:delete(motd, LServer),
ok = ejabberd_riak:delete_by_index(motd_users, ok = ejabberd_riak:delete_by_index(motd_users,
<<"server">>, <<"server">>,
LServer), LServer)
{atomic, ok} catch _:{badmatch, {error, _} = Err} ->
catch _:{badmatch, Err} -> Err
{atomic, Err}
end. end.
get_motd(LServer) -> get_motd(LServer) ->
case ejabberd_riak:get(motd, motd_schema(), LServer) of case ejabberd_riak:get(motd, motd_schema(), LServer) of
{ok, #motd{packet = Packet}} -> {ok, #motd{packet = Packet}} ->
{ok, Packet}; {ok, Packet};
_ -> {error, notfound} ->
error error;
{error, _} = Err ->
Err
end. end.
is_motd_user(LUser, LServer) -> is_motd_user(LUser, LServer) ->
case ejabberd_riak:get(motd_users, motd_users_schema(), case ejabberd_riak:get(motd_users, motd_users_schema(),
{LUser, LServer}) of {LUser, LServer}) of
{ok, #motd_users{}} -> true; {ok, #motd_users{}} -> {ok, true};
_ -> false {error, notfound} -> {ok, false};
{error, _} = Err -> Err
end. end.
set_motd_user(LUser, LServer) -> set_motd_user(LUser, LServer) ->
{atomic, ejabberd_riak:put( ejabberd_riak:put(
#motd_users{us = {LUser, LServer}}, motd_users_schema(), #motd_users{us = {LUser, LServer}}, motd_users_schema(),
[{'2i', [{<<"server">>, LServer}]}])}. [{'2i', [{<<"server">>, LServer}]}]).
import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) -> import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) ->
El = fxml_stream:parse_element(XML), El = fxml_stream:parse_element(XML),

View File

@ -36,6 +36,7 @@
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("mod_announce.hrl"). -include("mod_announce.hrl").
-include("ejabberd_sql_pt.hrl"). -include("ejabberd_sql_pt.hrl").
-include("logger.hrl").
%%%=================================================================== %%%===================================================================
%%% API %%% API
@ -53,7 +54,7 @@ set_motd_users(LServer, USRs) ->
"xml=''"]) "xml=''"])
end, USRs) end, USRs)
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
set_motd(LServer, Packet) -> set_motd(LServer, Packet) ->
XML = fxml:element_to_binary(Packet), XML = fxml:element_to_binary(Packet),
@ -63,27 +64,24 @@ set_motd(LServer, Packet) ->
["!username=''", ["!username=''",
"xml=%(XML)s"]) "xml=%(XML)s"])
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
delete_motd(LServer) -> delete_motd(LServer) ->
F = fun() -> F = fun() ->
ejabberd_sql:sql_query_t(?SQL("delete from motd")) ejabberd_sql:sql_query_t(?SQL("delete from motd"))
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
get_motd(LServer) -> get_motd(LServer) ->
case catch ejabberd_sql:sql_query( case catch ejabberd_sql:sql_query(
LServer, LServer,
?SQL("select @(xml)s from motd where username=''")) of ?SQL("select @(xml)s from motd where username=''")) of
{selected, [{XML}]} -> {selected, [{XML}]} ->
case fxml_stream:parse_element(XML) of parse_element(XML);
{error, _} -> {selected, []} ->
error; error;
Packet ->
{ok, Packet}
end;
_ -> _ ->
error {error, db_failure}
end. end.
is_motd_user(LUser, LServer) -> is_motd_user(LUser, LServer) ->
@ -92,9 +90,11 @@ is_motd_user(LUser, LServer) ->
?SQL("select @(username)s from motd" ?SQL("select @(username)s from motd"
" where username=%(LUser)s")) of " where username=%(LUser)s")) of
{selected, [_|_]} -> {selected, [_|_]} ->
true; {ok, true};
{selected, []} ->
{ok, false};
_ -> _ ->
false {error, db_failure}
end. end.
set_motd_user(LUser, LServer) -> set_motd_user(LUser, LServer) ->
@ -104,7 +104,7 @@ set_motd_user(LUser, LServer) ->
["!username=%(LUser)s", ["!username=%(LUser)s",
"xml=''"]) "xml=''"])
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
export(_Server) -> export(_Server) ->
[{motd, [{motd,
@ -131,3 +131,18 @@ import(_, _, _) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
transaction(LServer, F) ->
case ejabberd_sql:sql_transaction(LServer, F) of
{atomic, _} -> ok;
_ -> {error, db_failure}
end.
parse_element(XML) ->
case fxml_stream:parse_element(XML) of
El when is_record(El, xmlel) ->
{ok, El};
_ ->
?ERROR_MSG("malformed XML element in SQL table "
"'motd' for username='': ~s", [XML]),
{error, db_failure}
end.