Skip to content

Commit

Permalink
Merge pull request #4047 from esl/mu-cets-components
Browse files Browse the repository at this point in the history
CETS support for components
  • Loading branch information
pawlooss1 authored Jul 13, 2023
2 parents 1cc81f6 + 8cda94d commit 8d30969
Show file tree
Hide file tree
Showing 24 changed files with 495 additions and 346 deletions.
1 change: 1 addition & 0 deletions big_tests/test.config
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@
[{dbs, [redis, pgsql]},
{sm_backend, "\"cets\""},
{bosh_backend, "\"cets\""},
{component_backend, "\"cets\""},
{stream_management_backend, cets},
{auth_method, "rdbms"},
{internal_databases, "[internal_databases.cets]
Expand Down
2 changes: 1 addition & 1 deletion big_tests/tests/component_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ disconnect_component(Component, Addr) ->
disconnect_components(Components, Addr) ->
%% TODO replace 'kill' with 'stop' when server supports stream closing
[escalus_connection:kill(Component) || Component <- Components],
mongoose_helper:wait_until(fun() -> rpc(ejabberd_router, lookup_component, [Addr]) =:= [] end, true,
mongoose_helper:wait_until(fun() -> rpc(mongoose_component, lookup_component, [Addr]) =:= [] end, true,
#{name => rpc}).

rpc(M, F, A) ->
Expand Down
2 changes: 1 addition & 1 deletion rebar.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{<<"certifi">>,{pkg,<<"certifi">>,<<"2.9.0">>},1},
{<<"cets">>,
{git,"https://github.com/esl/cets.git",
{ref,"c4f47edbe1bc7d467986c8e9dca56991c14f6a77"}},
{ref,"458e2e1df3fb51896fe334385bb0d2c9c53ef87f"}},
0},
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},0},
{<<"cowboy_swagger">>,{pkg,<<"cowboy_swagger">>,<<"2.5.1">>},0},
Expand Down
3 changes: 3 additions & 0 deletions rel/files/mongooseim.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
{{#sm_backend}}
sm_backend = {{{sm_backend}}}
{{/sm_backend}}
{{#component_backend}}
component_backend = {{{component_backend}}}
{{/component_backend}}
max_fsm_queue = 1000
{{#http_server_name}}
http_server_name = {{{http_server_name}}}
Expand Down
173 changes: 173 additions & 0 deletions src/component/mongoose_component.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
-module(mongoose_component).
%% API
-export([has_component/1,
dirty_get_all_components/1,
register_components/4,
unregister_components/1,
lookup_component/1,
lookup_component/2]).

-export([start/0, stop/0]).
-export([node_cleanup/3]).

-include("mongoose.hrl").
-include("jlib.hrl").
-include("external_component.hrl").

-type domain() :: jid:server().

-type external_component() :: #external_component{domain :: domain(),
handler :: mongoose_packet_handler:t(),
node :: node(),
is_hidden :: boolean()}.

-export_type([external_component/0]).

% Not simple boolean() because is probably going to support third value in the future: only_hidden.
% Besides, it increases readability.
-type return_hidden() :: only_public | all.

-export_type([return_hidden/0]).

%%====================================================================
%% API
%%====================================================================

start() ->
Backend = mongoose_config:get_opt(component_backend),
mongoose_component_backend:init(#{backend => Backend}),
gen_hook:add_handlers(hooks()).

stop() ->
gen_hook:delete_handlers(hooks()).

-spec hooks() -> [gen_hook:hook_tuple()].
hooks() ->
[{node_cleanup, global, fun ?MODULE:node_cleanup/3, #{}, 90}].

-spec register_components(Domain :: [domain()],
Node :: node(),
Handler :: mongoose_packet_handler:t(),
AreHidden :: boolean()) -> {ok, [external_component()]} | {error, any()}.
register_components(Domains, Node, Handler, AreHidden) ->
try
register_components_unsafe(Domains, Node, Handler, AreHidden)
catch Class:Reason:Stacktrace ->
?LOG_ERROR(#{what => component_register_failed,
class => Class, reason => Reason, stacktrace => Stacktrace}),
{error, Reason}
end.

register_components_unsafe(Domains, Node, Handler, AreHidden) ->
LDomains = prepare_ldomains(Domains),
Components = make_components(LDomains, Node, Handler, AreHidden),
assert_can_register_components(Components),
register_components(Components),
%% We do it outside of Mnesia transaction
lists:foreach(fun run_register_hook/1, Components),
{ok, Components}.

register_components(Components) ->
mongoose_component_backend:register_components(Components).

make_components(LDomains, Node, Handler, AreHidden) ->
[make_record_component(LDomain, Handler, Node, AreHidden) || LDomain <- LDomains].

make_record_component(LDomain, Handler, Node, IsHidden) ->
#external_component{domain = LDomain, handler = Handler,
node = Node, is_hidden = IsHidden}.

run_register_hook(#external_component{domain = LDomain, is_hidden = IsHidden}) ->
mongoose_hooks:register_subhost(LDomain, IsHidden),
ok.

run_unregister_hook(#external_component{domain = LDomain}) ->
mongoose_hooks:unregister_subhost(LDomain),
ok.

-spec unregister_components(Components :: [external_component()]) -> ok.
unregister_components(Components) ->
lists:foreach(fun run_unregister_hook/1, Components),
mongoose_component_backend:unregister_components(Components).

assert_can_register_components(Components) ->
ConflictComponents = lists:filter(fun is_already_registered/1, Components),
ConflictDomains = records_to_domains(ConflictComponents),
case ConflictDomains of
[] ->
ok;
_ ->
error({routes_already_exist, ConflictDomains})
end.

records_to_domains(Components) ->
[LDomain || #external_component{domain = LDomain} <- Components].

%% Returns true if any component route is registered for the domain.
-spec has_component(jid:lserver()) -> boolean().
has_component(Domain) ->
[] =/= lookup_component(Domain).

%% @doc Check if the component/route is already registered somewhere.
-spec is_already_registered(external_component()) -> boolean().
is_already_registered(#external_component{domain = LDomain, node = Node}) ->
has_dynamic_domains(LDomain)
orelse has_domain_route(LDomain)
orelse has_component_registered(LDomain, Node).

has_dynamic_domains(LDomain) ->
{error, not_found} =/= mongoose_domain_api:get_host_type(LDomain).

%% check that route for this domain is not already registered
has_domain_route(LDomain) ->
no_route =/= mongoose_router:lookup_route(LDomain).

%% check that there is no component registered globally for this node
has_component_registered(LDomain, Node) ->
no_route =/= get_component(LDomain, Node).

%% Find a component registered globally for this node (internal use)
get_component(LDomain, Node) ->
filter_component(lookup_component(LDomain), Node).

filter_component([], _) ->
no_route;
filter_component([Comp|Tail], Node) ->
case Comp of
#external_component{node = Node} ->
Comp;
_ ->
filter_component(Tail, Node)
end.

%% @doc Returns a list of components registered for this domain by any node,
%% the choice is yours.
-spec lookup_component(Domain :: jid:lserver()) -> [external_component()].
lookup_component(Domain) ->
mongoose_component_backend:lookup_component(Domain).

%% @doc Returns a list of components registered for this domain at the given node.
%% (must be only one, or nothing)
-spec lookup_component(Domain :: jid:lserver(), Node :: node()) -> [external_component()].
lookup_component(Domain, Node) ->
mongoose_component_backend:lookup_component(Domain, Node).

-spec dirty_get_all_components(return_hidden()) -> [jid:lserver()].
dirty_get_all_components(ReturnHidden) ->
mongoose_component_backend:get_all_components(ReturnHidden).

-spec node_cleanup(map(), map(), map()) -> {ok, map()}.
node_cleanup(Acc, #{node := Node}, _) ->
mongoose_component_backend:node_cleanup(Node),
{ok, maps:put(?MODULE, ok, Acc)}.

prepare_ldomains(Domains) ->
LDomains = [jid:nameprep(Domain) || Domain <- Domains],
Zip = lists:zip(Domains, LDomains),
InvalidDomains = [Domain || {Domain, error} <- Zip],
case InvalidDomains of
[] ->
LDomains;
_ ->
error({invalid_domains, InvalidDomains})
end.
65 changes: 65 additions & 0 deletions src/component/mongoose_component_backend.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
-module(mongoose_component_backend).

-type external_component() :: mongoose_component:external_component().

-callback init(map()) -> any().

-callback node_cleanup(node()) -> ok.

-callback register_components(Components :: [external_component()]) -> ok.

-callback unregister_components(Components :: [external_component()]) -> ok.

-callback lookup_component(Domain :: jid:lserver()) -> [external_component()].

-callback lookup_component(Domain :: jid:lserver(), Node :: node()) -> [external_component()].

-callback get_all_components(ReturnHidden :: mongoose_component:return_hidden()) -> [jid:lserver()].

-export([init/1,
node_cleanup/1,
register_components/1,
unregister_components/1,
lookup_component/1,
lookup_component/2,
get_all_components/1]).

-ignore_xref([behaviour_info/1]).

-define(MAIN_MODULE, mongoose_component).

-spec init(map()) -> any().
init(Opts) ->
Args = [Opts],
mongoose_backend:init(global, ?MAIN_MODULE, [], Opts),
mongoose_backend:call(global, ?MAIN_MODULE, ?FUNCTION_NAME, Args).

-spec node_cleanup(node()) -> ok.
node_cleanup(Node) ->
Args = [Node],
mongoose_backend:call(global, ?MAIN_MODULE, ?FUNCTION_NAME, Args).

-spec register_components(Components :: [external_component()]) -> ok.
register_components(Components) ->
Args = [Components],
mongoose_backend:call(global, ?MAIN_MODULE, ?FUNCTION_NAME, Args).

-spec unregister_components(Components :: [external_component()]) -> ok.
unregister_components(Components) ->
Args = [Components],
mongoose_backend:call(global, ?MAIN_MODULE, ?FUNCTION_NAME, Args).

-spec lookup_component(Domain :: jid:lserver()) -> [external_component()].
lookup_component(Domain) ->
Args = [Domain],
mongoose_backend:call(global, ?MAIN_MODULE, ?FUNCTION_NAME, Args).

-spec lookup_component(Domain :: jid:lserver(), Node :: node()) -> [external_component()].
lookup_component(Domain, Node) ->
Args = [Domain, Node],
mongoose_backend:call(global, ?MAIN_MODULE, ?FUNCTION_NAME, Args).

-spec get_all_components(ReturnHidden :: mongoose_component:return_hidden()) -> [jid:lserver()].
get_all_components(ReturnHidden) ->
Args = [ReturnHidden],
mongoose_backend:call(global, ?MAIN_MODULE, ?FUNCTION_NAME, Args).
42 changes: 42 additions & 0 deletions src/component/mongoose_component_cets.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-module(mongoose_component_cets).
-behaviour(mongoose_component_backend).

-export([init/1,
node_cleanup/1,
register_components/1,
unregister_components/1,
lookup_component/1,
lookup_component/2,
get_all_components/1]).

-include("external_component.hrl").
-define(TABLE, cets_external_component).

init(_) ->
cets:start(?TABLE, #{type => bag, keypos => 2}),
cets_discovery:add_table(mongoose_cets_discovery, ?TABLE).

node_cleanup(Node) ->
ets:match_delete(?TABLE, #external_component{node = Node, _ = '_'}),
ok.

register_components(Components) ->
cets:insert_many(?TABLE, Components),
ok.

unregister_components(Components) ->
cets:delete_objects(?TABLE, Components),
ok.

lookup_component(Domain) ->
ets:lookup(?TABLE, Domain).

lookup_component(Domain, Node) ->
ets:match_object(?TABLE, #external_component{domain = Domain, node = Node, _ = '_'}).

get_all_components(all) ->
MatchAll = {#external_component{ domain = '$1', _ = '_' }, [], ['$1']},
ets:select(?TABLE, [MatchAll]);
get_all_components(only_public) ->
MatchNonHidden = {#external_component{ domain = '$1', is_hidden = false, _ = '_' }, [], ['$1']},
ets:select(?TABLE, [MatchNonHidden]).
67 changes: 67 additions & 0 deletions src/component/mongoose_component_mnesia.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
-module(mongoose_component_mnesia).
-behaviour(mongoose_component_backend).

-export([init/1,
node_cleanup/1,
register_components/1,
unregister_components/1,
lookup_component/1,
lookup_component/2,
get_all_components/1]).

-include("external_component.hrl").

init(_) ->
update_tables(),
%% add distributed service_component routes
mnesia:create_table(external_component,
[{attributes, record_info(fields, external_component)},
{type, bag}, {ram_copies, [node()]}]),
mnesia:add_table_copy(external_component, node(), ram_copies).

update_tables() ->
%% delete old schema
case catch mnesia:table_info(external_componenst, local_content) of
true ->
mnesia:delete_table(external_component);
_ ->
ok
end.

node_cleanup(Node) ->
Entries = mnesia:dirty_match_object(external_component,
#external_component{node = Node, _ = '_'}),
[mnesia:dirty_delete_object(external_component, Entry) || Entry <- Entries],
ok.

register_components(Components) ->
F = fun() ->
lists:foreach(fun mnesia:write/1, Components)
end,
case mnesia:transaction(F) of
{atomic, ok} -> ok;
{aborted, Reason} -> error({mnesia_aborted_write, Reason})
end.

unregister_components(Components) ->
F = fun() ->
lists:foreach(fun do_unregister_component/1, Components)
end,
{atomic, ok} = mnesia:transaction(F),
ok.

do_unregister_component(Component) ->
ok = mnesia:delete_object(external_component, Component, write).

lookup_component(Domain) ->
mnesia:dirty_read(external_component, Domain).

lookup_component(Domain, Node) ->
mnesia:dirty_match_object(external_component,
#external_component{domain = Domain, node = Node, _ = '_'}).

get_all_components(all) ->
mnesia:dirty_all_keys(external_component);
get_all_components(only_public) ->
MatchNonHidden = {#external_component{ domain = '$1', is_hidden = false, _ = '_' }, [], ['$1']},
mnesia:dirty_select(external_component, [MatchNonHidden]).
Loading

0 comments on commit 8d30969

Please sign in to comment.