From d06751d5f3617ffb9eabbf10970f1cfe0af7c44e Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Wed, 12 May 2021 01:21:48 +0200 Subject: [PATCH 01/13] fixing mod_muc_iq:unregister_iq_handler/2 --- src/mod_muc_iq.erl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mod_muc_iq.erl b/src/mod_muc_iq.erl index 305a2298a6..623eb003cb 100644 --- a/src/mod_muc_iq.erl +++ b/src/mod_muc_iq.erl @@ -96,6 +96,12 @@ handle_cast({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) - ets:insert(tbl_name(), {{XMLNS, Host}, Module, Function, Opts}), {noreply, State}; handle_cast({unregister_iq_handler, Host, XMLNS}, State) -> + case ets:lookup(tbl_name(), {XMLNS, Host}) of + [{_, Module, Function, Opts}] -> + gen_iq_handler:stop_iq_handler(Module, Function, Opts); + _ -> + ok + end, ets:delete(tbl_name(), {XMLNS, Host}), {noreply, State}; handle_cast(_Msg, State) -> From e0560bf06861cdbd50f3183401a63923f35ac3e2 Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Fri, 14 May 2021 20:31:16 +0200 Subject: [PATCH 02/13] changing extra field of #packet_handler{} from any() to map() --- big_tests/tests/push_SUITE.erl | 4 ++-- src/ejabberd_local.erl | 4 ++-- src/ejabberd_service.erl | 8 ++++---- src/mod_muc.erl | 11 ++++++----- src/mod_vcard.erl | 6 +++--- src/mongoose_packet_handler.erl | 18 ++++++++++++------ src/muc_light/mod_muc_light.erl | 2 +- src/pubsub/mod_pubsub.erl | 8 +++++--- 8 files changed, 35 insertions(+), 26 deletions(-) diff --git a/big_tests/tests/push_SUITE.erl b/big_tests/tests/push_SUITE.erl index 5895a3235c..421f8336b1 100644 --- a/big_tests/tests/push_SUITE.erl +++ b/big_tests/tests/push_SUITE.erl @@ -690,11 +690,11 @@ start_route_listener(Config) -> State = #{ pid => self(), pub_options_ns => push_helper:ns_pubsub_pub_options(), push_form_ns => push_helper:push_form_type() }, - Handler = rpc(mongoose_packet_handler, new, [?MODULE, State]), + Handler = rpc(mongoose_packet_handler, new, [?MODULE, #{state => State}]), Domain = pubsub_domain(Config), rpc(ejabberd_router, register_route, [Domain, Handler]). -process_packet(_Acc, _From, To, El, State) -> +process_packet(_Acc, _From, To, El, #{state := State}) -> #{ pid := TestCasePid, pub_options_ns := PubOptionsNS, push_form_ns := PushFormNS } = State, PublishXML = exml_query:path(El, [{element, <<"pubsub">>}, {element, <<"publish-options">>}, diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index fec63e3cdc..b472bb9f65 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -144,7 +144,7 @@ process_iq_reply(From, To, Acc, #iq{id = ID} = IQ) -> From :: jid:jid(), To ::jid:jid(), El :: exml:element(), - Extra :: any()) -> mongoose_acc:t(). + Extra :: map()) -> mongoose_acc:t(). process_packet(Acc, From, To, El, _Extra) -> try do_route(Acc, From, To, El) @@ -333,7 +333,7 @@ handle_cast(_Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({route, Acc, From, To, El}, State) -> - process_packet(Acc, From, To, El, undefined), + process_packet(Acc, From, To, El, #{}), {noreply, State}; handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}), diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 54597cd36e..a1795f7250 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -133,8 +133,8 @@ socket_type() -> %%%---------------------------------------------------------------------- -spec process_packet(Acc :: mongoose_acc:t(), From :: jid:jid(), To :: jid:jid(), - El :: exml:element(), Pid :: pid()) -> any(). -process_packet(Acc, From, To, _El, Pid) -> + El :: exml:element(), #{pid := pid()}) -> any(). +process_packet(Acc, From, To, _El, #{pid := Pid}) -> Pid ! {route, From, To, Acc}. %%%---------------------------------------------------------------------- @@ -475,7 +475,7 @@ routes_info_to_pids(RoutesInfo) -> %% Flatten the list of lists ExtComponents = lists:append(ExtComponentsPerHost), %% Ignore handlers from other modules - [mongoose_packet_handler:extra(H) + [maps:get(pid, mongoose_packet_handler:extra(H)) || #external_component{handler = H} <- ExtComponents, mongoose_packet_handler:module(H) =:= ?MODULE]. @@ -508,7 +508,7 @@ lookup_routes(StateData) -> -spec register_routes(state()) -> any(). register_routes(StateData = #state{hidden_components = AreHidden}) -> Routes = get_routes(StateData), - Handler = mongoose_packet_handler:new(?MODULE, self()), + Handler = mongoose_packet_handler:new(?MODULE, #{pid => self()}), ejabberd_router:register_components(Routes, node(), Handler, AreHidden). -spec unregister_routes(state()) -> any(). diff --git a/src/mod_muc.erl b/src/mod_muc.erl index fd3ed9808b..e4b9f1ac0e 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -417,7 +417,8 @@ init([Host, Opts]) -> ejabberd_hooks:add(can_access_room, MyHost, ?MODULE, can_access_room, 50), ejabberd_hooks:add(can_access_identity, MyHost, ?MODULE, can_access_identity, 50), - ejabberd_router:register_route(MyHost, mongoose_packet_handler:new(?MODULE, State)), + ejabberd_router:register_route(MyHost, mongoose_packet_handler:new(?MODULE, + #{state => State})), mongoose_subhosts:register(Host, MyHost), case gen_mod:get_module_opt(Host, mod_muc, load_permanent_rooms_at_startup, false) of @@ -584,10 +585,10 @@ stop_supervisor(Host) -> From :: jid:jid(), To :: jid:simple_jid() | jid:jid(), El :: exml:element(), - State :: state()) -> ok | mongoose_acc:t(). -process_packet(Acc, From, To, El, #state{ - access = {AccessRoute, _, _, _}, - server_host = ServerHost} = State) -> + #{state := state()}) -> ok | mongoose_acc:t(). +process_packet(Acc, From, To, El, #{state := State}) -> + {AccessRoute, _, _, _} = State#state.access, + ServerHost = State#state.server_host, case acl:match_rule(ServerHost, AccessRoute, From) of allow -> {Room, _, _} = jid:to_lower(To), diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 11623d53d8..d1b042b535 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -302,8 +302,8 @@ process_search_reported_spec(KVs) -> %%-------------------------------------------------------------------- -spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), - Packet :: exml:element(), Pid :: pid()) -> any(). -process_packet(Acc, From, To, Packet, Pid) -> + Packet :: exml:element(), #{pid := pid()}) -> any(). +process_packet(Acc, From, To, Packet, #{pid := Pid}) -> Pid ! {route, From, To, Acc, Packet}. %%-------------------------------------------------------------------- @@ -328,7 +328,7 @@ init([VHost, Opts]) -> case Search of true -> ejabberd_router:register_route( - DirectoryHost, mongoose_packet_handler:new(?MODULE, self())); + DirectoryHost, mongoose_packet_handler:new(?MODULE, #{pid => self()})); _ -> ok end, diff --git a/src/mongoose_packet_handler.erl b/src/mongoose_packet_handler.erl index 58f0e12ec1..53cb14c0bf 100644 --- a/src/mongoose_packet_handler.erl +++ b/src/mongoose_packet_handler.erl @@ -18,7 +18,7 @@ -type t() :: #packet_handler{ module :: module(), - extra :: any() + extra :: map() }. -export_type([t/0]). @@ -28,22 +28,22 @@ %%---------------------------------------------------------------------- -callback process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), - El :: exml:element(), Extra :: any()) -> any(). + El :: exml:element(), Extra :: map()) -> any(). %%---------------------------------------------------------------------- %% API %%---------------------------------------------------------------------- --export([new/1, new/2, process/5]). +-export([new/1, new/2, process/5, add_extra/2]). %% Getters -export([module/1, extra/1]). -spec new(Module :: module()) -> t(). new(Module) -> - new(Module, undefined). + new(Module, #{}). --spec new(Module :: module(), Extra :: any()) -> t(). -new(Module, Extra) when is_atom(Module) -> +-spec new(Module :: module(), Extra :: map()) -> t(). +new(Module, Extra) when is_atom(Module), is_map(Extra) -> #packet_handler{ module = Module, extra = Extra }. -spec process(Handler :: t(), @@ -59,3 +59,9 @@ module(#packet_handler{ module = Module }) -> extra(#packet_handler{ extra = Extra }) -> Extra. + +add_extra(#packet_handler{ extra = OldExtra } = Handler, Extra) -> + %% KV pairs from the OldExtra map will remain unchanged, only + %% the new keys from Extra map will be added to the NewExtra map + NewExtra = maps:merge(Extra,OldExtra), + Handler#packet_handler{extra=NewExtra}. diff --git a/src/muc_light/mod_muc_light.erl b/src/muc_light/mod_muc_light.erl index 5d81aaa3a7..6abbdcd0c7 100644 --- a/src/muc_light/mod_muc_light.erl +++ b/src/muc_light/mod_muc_light.erl @@ -263,7 +263,7 @@ hooks(Host, MUCHost) -> %%==================================================================== -spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), - El :: exml:element(), Extra :: any()) -> any(). + El :: exml:element(), Extra :: map()) -> any(). process_packet(Acc, From, To, El, _Extra) -> process_decoded_packet(From, To, mod_muc_light_codec_backend:decode(From, To, El), Acc, El). diff --git a/src/pubsub/mod_pubsub.erl b/src/pubsub/mod_pubsub.erl index c59e28d2b8..80743c3de8 100644 --- a/src/pubsub/mod_pubsub.erl +++ b/src/pubsub/mod_pubsub.erl @@ -325,8 +325,9 @@ default_host() -> %% State is an extra data, required for processing -spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), El :: exml:element(), - State :: #state{}) -> any(). -process_packet(_Acc, From, To, El, #state{server_host = ServerHost, access = Access, plugins = Plugins}) -> + #{state := #state{}}) -> any(). +process_packet(_Acc, From, To, El, #{state := State}) -> + #state{server_host = ServerHost, access = Access, plugins = Plugins} = State, do_route(ServerHost, Access, Plugins, To#jid.lserver, From, To, El). %%==================================================================== @@ -381,7 +382,8 @@ init([ServerHost, Opts]) -> {_, State} = init_send_loop(ServerHost), %% Pass State as extra into ?MODULE:process_packet/5 function - ejabberd_router:register_route(Host, mongoose_packet_handler:new(?MODULE, State)), + ejabberd_router:register_route(Host, mongoose_packet_handler:new(?MODULE, + #{state => State})), {ok, State}. init_backend(ServerHost, Opts) -> From 04dbcd29975db876330cd43caf3337a99adedd1a Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Fri, 14 May 2021 23:19:01 +0200 Subject: [PATCH 03/13] extending subdomain packet handler extra fields with host_type --- src/domain/mongoose_subdomain_core.erl | 4 ++- test/mongoose_subdomain_core_SUITE.erl | 38 ++++++++++++++++---------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/domain/mongoose_subdomain_core.erl b/src/domain/mongoose_subdomain_core.erl index e127ffe1d4..330b01c47f 100644 --- a/src/domain/mongoose_subdomain_core.erl +++ b/src/domain/mongoose_subdomain_core.erl @@ -99,7 +99,9 @@ start_link() -> mongoose_packet_handler:t()) -> ok | {error, already_registered | subdomain_already_exists}. register_subdomain(HostType, SubdomainPattern, PacketHandler) -> - gen_server:call(?MODULE, {register, HostType, SubdomainPattern, PacketHandler}). + NewPacketHandler = mongoose_packet_handler:add_extra(PacketHandler, + #{host_type => HostType}), + gen_server:call(?MODULE, {register, HostType, SubdomainPattern, NewPacketHandler}). -spec unregister_subdomain(host_type(), subdomain_pattern()) -> ok. unregister_subdomain(HostType, SubdomainPattern) -> diff --git a/test/mongoose_subdomain_core_SUITE.erl b/test/mongoose_subdomain_core_SUITE.erl index 702459c835..29dd7d223a 100644 --- a/test/mongoose_subdomain_core_SUITE.erl +++ b/test/mongoose_subdomain_core_SUITE.erl @@ -102,12 +102,14 @@ can_register_and_unregister_subdomain_for_dynamic_host_type_with_domains(_Config ?assertEqualLists(Subdomains1 ++ Subdomains2, get_all_subdomains()), %% check mongoose_subdomain_core:get_all_subdomains_for_domain/1 interface. [DynamicDomain | _] = ?DYNAMIC_DOMAINS, + HostTypeExtra = #{host_type => ?DYNAMIC_HOST_TYPE2}, + HandlerWithHostType = mongoose_packet_handler:add_extra(Handler, HostTypeExtra), ?assertEqualLists( [#{host_type => ?DYNAMIC_HOST_TYPE2, subdomain_pattern => Pattern1, - parent_domain => DynamicDomain, packet_handler => Handler, + parent_domain => DynamicDomain, packet_handler => HandlerWithHostType, subdomain => mongoose_subdomain_utils:get_fqdn(Pattern1, DynamicDomain)}, #{host_type => ?DYNAMIC_HOST_TYPE2, subdomain_pattern => Pattern2, - parent_domain => DynamicDomain, packet_handler => Handler, + parent_domain => DynamicDomain, packet_handler => HandlerWithHostType, subdomain => mongoose_subdomain_utils:get_fqdn(Pattern2, DynamicDomain)}], mongoose_subdomain_core:get_all_subdomains_for_domain(DynamicDomain)), %% unregister (previously registered) subdomains one by one. @@ -186,12 +188,14 @@ can_register_and_unregister_fqdn_for_dynamic_host_type_with_domains(_Config) -> ?assertEqual(ok, mongoose_subdomain_core:register_subdomain(?DYNAMIC_HOST_TYPE2, Pattern2, Handler)), ?assertEqualLists([<<"some.fqdn">>, <<"another.fqdn">>], get_all_subdomains()), + HostTypeExtra = #{host_type => ?DYNAMIC_HOST_TYPE2}, + HandlerWithHostType = mongoose_packet_handler:add_extra(Handler, HostTypeExtra), ?assertEqualLists( [#{host_type => ?DYNAMIC_HOST_TYPE2, parent_domain => no_parent_domain, - subdomain_pattern => Pattern1, packet_handler => Handler, + subdomain_pattern => Pattern1, packet_handler => HandlerWithHostType, subdomain => <<"some.fqdn">>}, #{host_type => ?DYNAMIC_HOST_TYPE2, parent_domain => no_parent_domain, - subdomain_pattern => Pattern2, packet_handler => Handler, + subdomain_pattern => Pattern2, packet_handler => HandlerWithHostType, subdomain => <<"another.fqdn">>}], mongoose_subdomain_core:get_all_subdomains_for_domain(no_parent_domain)), %% unregister (previously registered) subdomains one by one. @@ -256,17 +260,23 @@ can_get_host_type_and_subdomain_details(_Config) -> mongoose_subdomain_core:get_host_type(Subdomain2)), ?assertEqual({error, not_found}, mongoose_subdomain_core:get_host_type(<<"unknown.subdomain">>)), + HostTypeExtra1 = #{host_type => ?STATIC_HOST_TYPE}, + HandlerWithHostType1 = mongoose_packet_handler:add_extra(Handler, HostTypeExtra1), ?assertEqual({ok, #{host_type => ?STATIC_HOST_TYPE, subdomain_pattern => Pattern1, - parent_domain => ?STATIC_DOMAIN, packet_handler => Handler, - subdomain => Subdomain1}}, + parent_domain => ?STATIC_DOMAIN, subdomain => Subdomain1, + packet_handler => HandlerWithHostType1}}, mongoose_subdomain_core:get_subdomain_info(Subdomain1)), + HostTypeExtra2 = #{host_type => ?DYNAMIC_HOST_TYPE1}, + HandlerWithHostType2 = mongoose_packet_handler:add_extra(Handler, HostTypeExtra2), ?assertEqual({ok, #{host_type => ?DYNAMIC_HOST_TYPE1, subdomain_pattern => Pattern2, - parent_domain => no_parent_domain, packet_handler => Handler, - subdomain => <<"some.fqdn">>}}, + parent_domain => no_parent_domain, subdomain => <<"some.fqdn">>, + packet_handler => HandlerWithHostType2}}, mongoose_subdomain_core:get_subdomain_info(<<"some.fqdn">>)), + HostTypeExtra3 = #{host_type => ?DYNAMIC_HOST_TYPE2}, + HandlerWithHostType3 = mongoose_packet_handler:add_extra(Handler, HostTypeExtra3), ?assertEqual({ok, #{host_type => ?DYNAMIC_HOST_TYPE2, subdomain_pattern => Pattern1, - parent_domain => hd(?DYNAMIC_DOMAINS), packet_handler => Handler, - subdomain => Subdomain2}}, + parent_domain => hd(?DYNAMIC_DOMAINS), subdomain => Subdomain2, + packet_handler => HandlerWithHostType3}}, mongoose_subdomain_core:get_subdomain_info(Subdomain2)), ?assertEqual({error, not_found}, mongoose_subdomain_core:get_subdomain_info(<<"unknown.subdomain">>)), @@ -355,7 +365,7 @@ prevents_double_subdomain_registration(_Config) -> prevents_prefix_subdomain_overriding_by_prefix_subdomain(_Config) -> Pattern1 = mongoose_subdomain_utils:make_subdomain_pattern("sub.@HOST@"), Pattern2 = mongoose_subdomain_utils:make_subdomain_pattern("sub.domain.@HOST@"), - Handler = mongoose_packet_handler:new(?MODULE), + Handler = mongoose_packet_handler:new(?MODULE, #{host_type => dummy_type}), ?assertEqual(ok, mongoose_subdomain_core:register_subdomain(?DYNAMIC_HOST_TYPE1, Pattern1, Handler)), ?assertEqual(ok, mongoose_subdomain_core:register_subdomain(?DYNAMIC_HOST_TYPE1, @@ -393,7 +403,7 @@ prevents_prefix_subdomain_overriding_by_prefix_subdomain(_Config) -> prevents_fqdn_subdomain_overriding_by_prefix_subdomain(_Config) -> Pattern1 = mongoose_subdomain_utils:make_subdomain_pattern("subdomain.@HOST@"), Pattern2 = mongoose_subdomain_utils:make_subdomain_pattern("subdomain.fqdn"), - Handler = mongoose_packet_handler:new(?MODULE), + Handler = mongoose_packet_handler:new(?MODULE, #{host_type => dummy_type}), ?assertEqual(ok, mongoose_subdomain_core:register_subdomain(?DYNAMIC_HOST_TYPE1, Pattern1, Handler)), ?assertEqual(ok, mongoose_subdomain_core:register_subdomain(?DYNAMIC_HOST_TYPE1, @@ -424,7 +434,7 @@ prevents_fqdn_subdomain_overriding_by_prefix_subdomain(_Config) -> prevents_fqdn_subdomain_overriding_by_fqdn_subdomain(_Config) -> Pattern = mongoose_subdomain_utils:make_subdomain_pattern("subdomain.fqdn"), - Handler = mongoose_packet_handler:new(?MODULE), + Handler = mongoose_packet_handler:new(?MODULE, #{host_type => dummy_type}), %% FQDN subdomain conflicts with another FQDN subdomain ?assertEqual(ok, mongoose_subdomain_core:register_subdomain(?DYNAMIC_HOST_TYPE1, Pattern, Handler)), @@ -456,7 +466,7 @@ prevents_fqdn_subdomain_overriding_by_fqdn_subdomain(_Config) -> prevents_prefix_subdomain_overriding_by_fqdn_subdomain(_Config) -> Pattern1 = mongoose_subdomain_utils:make_subdomain_pattern("subdomain.@HOST@"), Pattern2 = mongoose_subdomain_utils:make_subdomain_pattern("subdomain.fqdn"), - Handler = mongoose_packet_handler:new(?MODULE), + Handler = mongoose_packet_handler:new(?MODULE, #{host_type => dummy_type}), %% FQDN subdomain conflicts with another FQDN subdomain ?assertEqual(ok, mongoose_subdomain_core:register_subdomain(?DYNAMIC_HOST_TYPE1, Pattern1, Handler)), From 742aa049b0edae7c807beac81a31ad078f1900e0 Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Sun, 16 May 2021 21:35:43 +0200 Subject: [PATCH 04/13] reworking gen_iq_handler module --- src/ejabberd_local.erl | 72 ++++------ src/ejabberd_sm.erl | 42 +++--- src/ejabberd_sup.erl | 2 +- src/gen_iq_component.erl | 48 +++++++ src/gen_iq_handler.erl | 224 +++++--------------------------- src/mod_muc_iq.erl | 24 ++-- src/mongoose_iq_handler.erl | 133 +++++++++++++++++++ src/mongoose_iq_worker.erl | 55 ++++++++ test/mongoose_cleanup_SUITE.erl | 4 +- 9 files changed, 322 insertions(+), 282 deletions(-) create mode 100644 src/gen_iq_component.erl create mode 100644 src/mongoose_iq_handler.erl create mode 100644 src/mongoose_iq_worker.erl diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index b472bb9f65..bd7cade083 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -33,6 +33,7 @@ -behaviour(gen_server). -behaviour(mongoose_packet_handler). +-behaviour(gen_iq_component). %% API -export([start_link/0]). @@ -41,8 +42,7 @@ route_iq/5, route_iq/6, process_iq_reply/4, - register_iq_handler/4, - register_iq_handler/5, + register_iq_handler/3, register_host/1, register_iq_response_handler/4, register_iq_response_handler/5, @@ -110,14 +110,8 @@ process_iq(#iq{ type = Type } = IQReply, Acc, From, To, _El) process_iq(#iq{ xmlns = XMLNS } = IQ, Acc, From, To, _El) -> Host = To#jid.lserver, case ets:lookup(?IQTABLE, {XMLNS, Host}) of - [{_, Module, Function}] -> - case Module:Function(From, To, IQ) of - {Acc1, ignore} -> Acc1; - {Acc1, ResIQ} -> ejabberd_router:route(To, From, Acc1, jlib:iq_to_xml(ResIQ)) - end; - [{_, Module, Function, Opts}] -> - gen_iq_handler:handle(Host, Module, Function, Opts, - From, To, Acc, IQ); + [{_, IQHandler}] -> + gen_iq_component:handle(IQHandler, Acc, From, To, IQ); [] -> ejabberd_router:route_error_reply(To, From, Acc, mongoose_xmpp_errors:feature_not_implemented()) end; @@ -202,20 +196,11 @@ register_iq_response_handler(_Host, ID, Module, Function, Timeout0) -> function = Function, timer = TRef}). --spec register_iq_handler(Host :: jid:server(), - XMLNS :: binary(), - Module :: atom(), - Function :: fun()) -> {register_iq_handler, _, _, _, _}. -register_iq_handler(Host, XMLNS, Module, Fun) -> - ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}. - --spec register_iq_handler(Host :: jid:server(), - XMLNS :: binary(), - Module :: atom(), - Function :: fun(), - Opts :: [any()]) -> {register_iq_handler, _, _, _, _, _}. -register_iq_handler(Host, XMLNS, Module, Fun, Opts) -> - ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}. +-spec register_iq_handler(Domain :: jid:server(), Namespace :: binary(), + IQHandler :: mongoose_iq_handler:t()) -> ok. +register_iq_handler(Domain, XMLNS, IQHandler) -> + ejabberd_local ! {register_iq_handler, Domain, XMLNS, IQHandler}, + ok. -spec unregister_iq_response_handler(_Host :: jid:server(), ID :: id()) -> 'ok'. @@ -223,10 +208,10 @@ unregister_iq_response_handler(_Host, ID) -> catch get_iq_callback(ID), ok. --spec unregister_iq_handler(Host :: jid:server(), - XMLNS :: binary()) -> {unregister_iq_handler, _, _}. -unregister_iq_handler(Host, XMLNS) -> - ejabberd_local ! {unregister_iq_handler, Host, XMLNS}. +-spec unregister_iq_handler(Domain :: jid:server(), Namespace :: binary()) -> ok. +unregister_iq_handler(Domain, XMLNS) -> + ejabberd_local ! {unregister_iq_handler, Domain, XMLNS}, + ok. refresh_iq_handlers() -> ejabberd_local ! refresh_iq_handlers. @@ -282,7 +267,7 @@ disable_domain(_Acc, _HostType, Domain) -> %%-------------------------------------------------------------------- init([]) -> lists:foreach(fun do_register_host/1, ?MYHOSTS), - catch ets:new(?IQTABLE, [named_table, public]), + catch ets:new(?IQTABLE, [named_table, protected]), update_table(), mnesia:create_table(iq_response, [{ram_copies, [node()]}, @@ -335,36 +320,25 @@ handle_cast(_Msg, State) -> handle_info({route, Acc, From, To, El}, State) -> process_packet(Acc, From, To, El, #{}), {noreply, State}; -handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> - ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}), - catch mod_disco:register_feature(Host, XMLNS), - {noreply, State}; -handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) -> - ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}), +handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) -> + ets:insert(?IQTABLE, {{XMLNS, Host}, IQHandler}), catch mod_disco:register_feature(Host, XMLNS), {noreply, State}; handle_info({unregister_iq_handler, Host, XMLNS}, State) -> case ets:lookup(?IQTABLE, {XMLNS, Host}) of - [{_, Module, Function, Opts}] -> - gen_iq_handler:stop_iq_handler(Module, Function, Opts); + [{_, IQHandler}] -> + gen_iq_component:stop_iq_handler(IQHandler), + ets:delete(?IQTABLE, {XMLNS, Host}), + catch mod_disco:unregister_feature(Host, XMLNS); _ -> ok end, - ets:delete(?IQTABLE, {XMLNS, Host}), - catch mod_disco:unregister_feature(Host, XMLNS), {noreply, State}; handle_info(refresh_iq_handlers, State) -> lists:foreach( - fun(T) -> - case T of - {{XMLNS, Host}, _Module, _Function, _Opts} -> - catch mod_disco:register_feature(Host, XMLNS); - {{XMLNS, Host}, _Module, _Function} -> - catch mod_disco:register_feature(Host, XMLNS); - _ -> - ok - end - end, ets:tab2list(?IQTABLE)), + fun({{XMLNS, Host}, _IQHandler}) -> + catch mod_disco:register_feature(Host, XMLNS) + end, ets:tab2list(?IQTABLE)), {noreply, State}; handle_info({timeout, _TRef, ID}, State) -> process_iq_timeout(ID), diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 4a682ba0cd..a77d0fa5f5 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -26,6 +26,8 @@ -author('alexey@process-one.net'). -behaviour(gen_server). +-behaviour(gen_iq_component). + %% API -export([start/0, @@ -51,8 +53,7 @@ get_vh_session_number/1, get_vh_session_list/1, get_full_session_list/0, - register_iq_handler/4, - register_iq_handler/5, + register_iq_handler/3, unregister_iq_handler/2, force_update_presence/1, user_resources/2, @@ -428,16 +429,14 @@ get_full_session_list() -> ejabberd_gen_sm:get_sessions(sm_backend()). -register_iq_handler(Host, XMLNS, Module, Fun) -> - ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}. - - -register_iq_handler(Host, XMLNS, Module, Fun, Opts) -> - ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}. +register_iq_handler(Host, XMLNS, IQHandler) -> + ejabberd_sm ! {register_iq_handler, Host, XMLNS, IQHandler}, + ok. unregister_iq_handler(Host, XMLNS) -> - ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}. + ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}, + ok. %%==================================================================== %% Hook handlers @@ -527,20 +526,17 @@ handle_cast(_Msg, State) -> handle_info({route, From, To, Packet}, State) -> route(From, To, Packet), {noreply, State}; -handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> - ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}), - {noreply, State}; -handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) -> - ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function, Opts}), +handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) -> + ets:insert(sm_iqtable, {{XMLNS, Host}, IQHandler}), {noreply, State}; handle_info({unregister_iq_handler, Host, XMLNS}, State) -> case ets:lookup(sm_iqtable, {XMLNS, Host}) of - [{_, Module, Function, Opts}] -> - gen_iq_handler:stop_iq_handler(Module, Function, Opts); + [{_, IQHandler}] -> + gen_iq_component:stop_iq_handler(IQHandler), + ets:delete(sm_iqtable, {XMLNS, Host}); _ -> ok end, - ets:delete(sm_iqtable, {XMLNS, Host}), {noreply, State}; handle_info(_Info, State) -> {noreply, State}. @@ -960,16 +956,8 @@ process_iq(#iq{type = Type}, _From, _To, Acc, _Packet) when Type == result; Type process_iq(#iq{xmlns = XMLNS} = IQ, From, To, Acc, Packet) -> Host = To#jid.lserver, case ets:lookup(sm_iqtable, {XMLNS, Host}) of - [{_, Module, Function}] -> - case Module:Function(From, To, IQ) of - {Acc1, ignore} -> Acc1; - {Acc1, ResIQ} -> - ejabberd_router:route(To, From, Acc1, - jlib:iq_to_xml(ResIQ)) - end; - [{_, Module, Function, Opts}] -> - gen_iq_handler:handle(Host, Module, Function, Opts, - From, To, Acc, IQ); + [{_, IQHandler}] -> + gen_iq_component:handle(IQHandler, Acc, From, To, IQ); [] -> {Acc1, Err} = jlib:make_error_reply( Acc, Packet, mongoose_xmpp_errors:service_unavailable()), diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index 0758f68b02..4a43012b4b 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -126,7 +126,7 @@ init([]) -> IQSupervisor = {ejabberd_iq_sup, {ejabberd_tmp_sup, start_link, - [ejabberd_iq_sup, gen_iq_handler]}, + [ejabberd_iq_sup, mongoose_iq_worker]}, permanent, infinity, supervisor, diff --git a/src/gen_iq_component.erl b/src/gen_iq_component.erl new file mode 100644 index 0000000000..73902a6365 --- /dev/null +++ b/src/gen_iq_component.erl @@ -0,0 +1,48 @@ +-module(gen_iq_component). + +%% API +-export([register_iq_handler/4, + unregister_iq_handler/3, + stop_iq_handler/1, + handle/5]). + +-callback register_iq_handler(Domain :: jid:server(), Namespace :: binary(), + IQHandler :: mongoose_iq_handler:t()) -> ok. + +-callback unregister_iq_handler(Domain :: jid:server(), Namespace :: binary()) -> ok. +%%==================================================================== +%% API +%%==================================================================== +-spec register_iq_handler(Component :: module(), + Domain :: jid:server(), + Namespace :: binary(), + IQHandler :: mongoose_iq_handler:t()) -> ok. +register_iq_handler(Component, Domain, Namespace, IQHandler) -> + Component:register_iq_handler(Domain, Namespace, IQHandler). + +-spec unregister_iq_handler(Component :: module(), + Domain :: jid:server(), + Namespace :: binary()) -> ok. +unregister_iq_handler(Component, Domain, Namespace) -> + Component:unregister_iq_handler(Domain, Namespace). + +-spec stop_iq_handler(IQHandler :: mongoose_iq_handler:t()) -> any(). +stop_iq_handler(IQHandler) -> + %% TODO: this function is required only for correct implementation of the legacy + %% gen_iq_handler:remove_iq_handler/3 interface, get rid of it once gen_iq_handler + %% module is removed. + case mongoose_iq_handler:extra(IQHandler) of + #{delete_on_unregister := true} -> + mongoose_iq_handler:delete(IQHandler); + _ -> + ok + end. + + +-spec handle(IQHandler :: mongoose_iq_handler:t(), + Acc :: mongoose_acc:t(), + From :: jid:jid(), + To :: jid:jid(), + IQ :: jlib:iq()) -> mongoose_acc:t(). +handle(IQHandler, Acc, From, To, IQ) -> + mongoose_iq_handler:process_iq(IQHandler, Acc, From, To, IQ). diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index a6d1425e64..21fbd03472 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -26,204 +26,46 @@ -module(gen_iq_handler). -author('alexey@process-one.net'). --behaviour(gen_server). - -%% API --export([start_link/3, - add_iq_handler/6, +%% Old API +-export([add_iq_handler/6, remove_iq_handler/3, - stop_iq_handler/3, - handle/8, - process_iq/7, check_type/1]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --include("mongoose.hrl"). - --record(state, {host :: jid:server(), - module :: module(), - function :: atom() - }). --type state() :: #state{}. --type component() :: atom() | tuple(). --type ns() :: binary(). --type type() :: 'no_queue' | 'one_queue' | 'parallel' | {'queues', integer()}. --type options() :: atom() | {one_queue | queues, pid() | [pid()]}. - %%==================================================================== -%% API +%% Old API %%==================================================================== - -%% @doc Starts the server --spec start_link(jid:server(), atom(), atom() - ) -> 'ignore' | {'error', _} | {'ok', pid()}. -start_link(Host, Module, Function) -> - gen_server:start_link(?MODULE, [Host, Module, Function], []). - - --spec add_iq_handler(component(), Host :: jid:server(), NS :: ns(), - Module :: atom(), Function :: atom(), Type :: type()) -> any(). -add_iq_handler(Component, Host, NS, Module, Function, Type) -> - case Type of - no_queue -> - Component:register_iq_handler(Host, NS, Module, Function, no_queue); - one_queue -> - {ok, Pid} = supervisor:start_child(ejabberd_iq_sup, - [Host, Module, Function]), - Component:register_iq_handler(Host, NS, Module, Function, - {one_queue, Pid}); - {queues, N} -> - Pids = - lists:map( - fun(_) -> - {ok, Pid} = supervisor:start_child( - ejabberd_iq_sup, - [Host, Module, Function]), - Pid - end, lists:seq(1, N)), - Component:register_iq_handler(Host, NS, Module, Function, - {queues, Pids}); - parallel -> - Component:register_iq_handler(Host, NS, Module, Function, parallel) - end. - - --spec remove_iq_handler(Component :: component(), - Host :: jid:server(), - NS :: ns()) -> any(). -remove_iq_handler(Component, Host, NS) -> - Component:unregister_iq_handler(Host, NS). - - --spec stop_iq_handler(M :: atom(), F :: atom(), Opts :: options()) -> any(). -stop_iq_handler(_Module, _Function, Opts) -> - case Opts of - {one_queue, Pid} -> - gen_server:call(Pid, stop); - {queues, Pids} -> - lists:foreach(fun(Pid) -> - catch gen_server:call(Pid, stop) - end, Pids); - _ -> - ok - end. - - --spec handle(Host :: jid:server(), Module :: atom(), Function :: atom(), - Opts :: options(), From :: jid:jid(), To :: jid:jid(), - mongoose_acc:t(), - IQ :: jlib:iq()) -> mongoose_acc:t(). -handle(Host, Module, Function, Opts, From, To, Acc, IQ) -> - case Opts of - no_queue -> - process_iq(Host, Module, Function, From, To, Acc, IQ); - {one_queue, Pid} -> - Pid ! {process_iq, From, To, Acc, IQ}, - Acc; - {queues, Pids} -> - Pid = lists:nth(erlang:phash(erlang:unique_integer(), length(Pids)), Pids), - Pid ! {process_iq, From, To, Acc, IQ}, - Acc; - parallel -> - spawn(?MODULE, process_iq, [Host, Module, Function, From, To, Acc, IQ]), - Acc; - _ -> - Acc - end. - - --spec process_iq(Host :: jid:server(), Module :: atom(), Function :: atom(), - From :: jid:jid(), To :: jid:jid(), Acc :: mongoose_acc:t(), - IQ :: jlib:iq()) -> mongoose_acc:t(). -process_iq(Host, Module, Function, From, To, Acc, IQ) -> - try Module:Function(From, To, Acc, IQ) of - {Acc1, ignore} -> - Acc1; - {Acc1, ResIQ} -> - ejabberd_router:route(To, From, Acc1, - jlib:iq_to_xml(ResIQ)) - catch Class:Reason:StackTrace -> - ?LOG_WARNING(#{what => process_iq_error, server => Host, acc => Acc, - handler_module => Module, handler_function => Function, - class => Class, reason => Reason, stacktrace => StackTrace}), - Acc - end. - --spec check_type(type()) -> type(). - -check_type(no_queue) -> no_queue; +-spec add_iq_handler(Component :: module(), + Domain :: jid:server(), + Namespace :: binary(), + Module :: atom(), + Function :: atom(), + ExecutionType :: mongoose_iq_handler:execution_type()) -> any(). +add_iq_handler(Component, Domain, Namespace, Module, Function, ExecutionType) -> + Extra = #{delete_on_unregister => true, module => Module, function => Function}, + IQHandlerFn = make_iq_handler_fn(Module, Function), + IQHandler = mongoose_iq_handler:new(IQHandlerFn, Extra, ExecutionType), + gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler). + + +-spec remove_iq_handler(Component :: module(), + Domain :: jid:server(), + Namespace :: binary()) -> any(). +remove_iq_handler(Component, Domain, Namespace) -> + gen_iq_component:unregister_iq_handler(Component, Domain, Namespace). + +-spec check_type(mongoose_iq_handler:execution_type()) -> + mongoose_iq_handler:execution_type(). +check_type(no_queue) -> no_queue; +check_type(parallel) -> parallel; check_type(one_queue) -> one_queue; -check_type(N) when is_integer(N), N>0 -> N; -check_type(parallel) -> parallel. - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%% @doc Initiates the server --spec init([atom() | binary(), ...]) -> {'ok', state()}. -init([Host, Module, Function]) -> - {ok, #state{host = Host, - module = Module, - function = Function}}. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call(stop, _From, State) -> - Reply = ok, - {stop, normal, Reply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast(_Msg, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({process_iq, From, To, Acc, IQ}, - #state{host = Host, - module = Module, - function = Function} = State) -> - process_iq(Host, Module, Function, From, To, Acc, IQ), - {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -terminate(_Reason, _State) -> - ok. - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. +check_type({queues, Int}) when is_integer(Int), Int > 0 -> + {queues, Int}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +-spec make_iq_handler_fn(module(), atom()) -> mongoose_iq_handler:iq_handler(). +make_iq_handler_fn(Module, Function) -> + fun(Acc, From, To, IQ, _Extra) -> + Module:Function(From, To, Acc, IQ) + end. diff --git a/src/mod_muc_iq.erl b/src/mod_muc_iq.erl index 623eb003cb..1744e5b509 100644 --- a/src/mod_muc_iq.erl +++ b/src/mod_muc_iq.erl @@ -1,9 +1,10 @@ %% @doc Stores a table of custom IQ-handlers for mod_muc_room. -module(mod_muc_iq). +-behaviour(gen_iq_component). -export([start_link/0, process_iq/5, - register_iq_handler/5, + register_iq_handler/3, unregister_iq_handler/2]). %% gen_server callbacks @@ -39,17 +40,16 @@ start_link() -> jlib:iq()) -> mongoose_acc:t() | {mongoose_acc:t(), error}. process_iq(Host, From, RoomJID, Acc, IQ = #iq{xmlns = XMLNS}) -> case ets:lookup(tbl_name(), {XMLNS, Host}) of - [{_, Module, Function, Opts}] -> - gen_iq_handler:handle(Host, Module, Function, Opts, From, - RoomJID, Acc, IQ); + [{_, IQHandler}] -> + gen_iq_component:handle(IQHandler, Acc, From, RoomJID, IQ); [] -> {Acc, error} end. --spec register_iq_handler(jid:server(), binary(), module(), atom(), any()) -> ok. -register_iq_handler(Host, XMLNS, Module, Fun, Opts) -> +-spec register_iq_handler(jid:server(), binary(), mongoose_iq_handler:t()) -> ok. +register_iq_handler(Host, XMLNS, IQHandler) -> gen_server:cast(srv_name(), - {register_iq_handler, Host, XMLNS, Module, Fun, Opts}). + {register_iq_handler, Host, XMLNS, IQHandler}). -spec unregister_iq_handler(jid:server(), binary()) -> ok. @@ -92,17 +92,17 @@ handle_call(_Request, _From, State) -> %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) -> - ets:insert(tbl_name(), {{XMLNS, Host}, Module, Function, Opts}), +handle_cast({register_iq_handler, Host, XMLNS, IQHandler}, State) -> + ets:insert(tbl_name(), {{XMLNS, Host}, IQHandler}), {noreply, State}; handle_cast({unregister_iq_handler, Host, XMLNS}, State) -> case ets:lookup(tbl_name(), {XMLNS, Host}) of - [{_, Module, Function, Opts}] -> - gen_iq_handler:stop_iq_handler(Module, Function, Opts); + [{_, IQHandler}] -> + gen_iq_componentr:stop_iq_handler(IQHandler), + ets:delete(tbl_name(), {XMLNS, Host}); _ -> ok end, - ets:delete(tbl_name(), {XMLNS, Host}), {noreply, State}; handle_cast(_Msg, State) -> {noreply, State}. diff --git a/src/mongoose_iq_handler.erl b/src/mongoose_iq_handler.erl new file mode 100644 index 0000000000..6113b75388 --- /dev/null +++ b/src/mongoose_iq_handler.erl @@ -0,0 +1,133 @@ + +-module(mongoose_iq_handler). +-include("mongoose.hrl"). + +%%---------------------------------------------------------------------- +%% Types +%%---------------------------------------------------------------------- + +-record(iq_handler, {iq_handler_fn :: iq_handler(), + extra :: map(), + execution_method :: execution_method()}). + +-type t() :: #iq_handler{}. + +-type iq_handler() :: fun((Acc :: mongoose_acc:t(), + From :: jid:jid(), + To :: jid:jid(), + IQ :: jlib:iq(), + Extra :: map()) -> {NewAcc :: mongoose_acc:t(), + IQResp :: ignore | jlib:iq()}). + +-type execution_method() :: no_queue | parallel | {one_queue, pid()} | {queues, [pid()]}. + +-type execution_type() :: no_queue | parallel | one_queue | {queues, pos_integer()}. + +-export_type([t/0, iq_handler/0, execution_type/0]). + +%%---------------------------------------------------------------------- +%% API +%%---------------------------------------------------------------------- + +-export([new/3, + delete/1, + process_iq/5, + add_extra/2, + execute_handler/5]). +%% Getters +-export([module/1, extra/1]). + +-spec new(IQHandlerFn :: iq_handler(), + Extra :: map(), + ExecutionType :: execution_type()) -> t(). +new(IQHandlerFn, Extra, ExecutionType) -> + ExecutionMethod = execution_method(ExecutionType), + #iq_handler{iq_handler_fn = IQHandlerFn, extra = Extra, + execution_method = ExecutionMethod}. + +-spec delete(IQHandler :: t()) -> ok. +delete(#iq_handler{execution_method = ExecutionMethod}) -> + case ExecutionMethod of + {one_queue, Pid} -> mongoose_iq_worker:stop(Pid); + {queues, Pids} -> + [mongoose_iq_worker:stop(Pid) || Pid <- Pids], + ok; + _ -> ok + end. + +-spec module(t()) -> module(). +module(#iq_handler{iq_handler_fn = Fn}) -> + {module, Mod} = erlang:fun_info(Fn, module), + Mod. + +-spec process_iq(Handler :: t(), + Acc :: mongoose_acc:t(), + From ::jid:jid(), + To ::jid:jid(), + IQ :: jlib:iq()) -> mongoose_acc:t(). +process_iq(#iq_handler{execution_method = ExecutionMethod} = Handler, + Acc, From, To, IQ) -> + case ExecutionMethod of + no_queue -> + ?MODULE:execute_handler(Handler, Acc, From, To, IQ); + {one_queue, Pid} -> + mongoose_iq_worker:process_iq(Pid, Handler, Acc, From, To, IQ), + Acc; + {queues, Pids} -> + Pid = lists:nth(erlang:phash(erlang:unique_integer(), length(Pids)), Pids), + mongoose_iq_worker:process_iq(Pid, Handler, Acc, From, To, IQ), + Acc; + parallel -> + spawn(?MODULE, execute_handler, [Handler, Acc, From, To, IQ]), + Acc + end. + +-spec extra(t()) -> map(). +extra(#iq_handler{ extra = Extra }) -> + Extra. + +-spec add_extra(t(), map()) -> t(). +add_extra(#iq_handler{ extra = OldExtra } = Handler, Extra) -> + %% KV pairs from the OldExtra map will remain unchanged, only + %% the new keys from Extra map will be added to the NewExtra map + NewExtra = maps:merge(Extra,OldExtra), + Handler#iq_handler{extra=NewExtra}. + +-spec execute_handler(Handler :: t(), + Acc :: mongoose_acc:t(), + From ::jid:jid(), + To ::jid:jid(), + IQ :: jlib:iq()) -> mongoose_acc:t(). +execute_handler(#iq_handler{iq_handler_fn = IQHandlerFn, extra = Extra}, + Acc, From, To, IQ) -> + try IQHandlerFn(Acc, From, To, IQ, Extra) of + {Acc1, ignore} -> + Acc1; + {Acc1, ResIQ} -> + ejabberd_router:route(To, From, Acc1, + jlib:iq_to_xml(ResIQ)) + catch Class:Reason:StackTrace -> + ?LOG_WARNING(#{what => process_iq_error, from => From, to => To, iq => IQ, + acc => Acc, extra => Extra, handler_function => IQHandlerFn, + class => Class, reason => Reason, stacktrace => StackTrace}), + Acc + end. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +-spec execution_method(execution_type()) -> execution_method(). +execution_method(ExecutionType) -> + case ExecutionType of + no_queue -> no_queue; + parallel -> parallel; + one_queue -> + {ok, Pid} = mongoose_iq_worker:start(), + {one_queue, Pid}; + {queues, N} -> + Pids = lists:map(fun(_) -> + {ok, Pid} = mongoose_iq_worker:start(), + Pid + end, lists:seq(1, N)), + {queues, Pids} + end. diff --git a/src/mongoose_iq_worker.erl b/src/mongoose_iq_worker.erl new file mode 100644 index 0000000000..fdf45d31f3 --- /dev/null +++ b/src/mongoose_iq_worker.erl @@ -0,0 +1,55 @@ + +-module(mongoose_iq_worker). +-behaviour(gen_server). + +%% API +-export([start/0, start_link/0, process_iq/6, stop/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2]). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- +-spec start() -> {ok,pid()}. +start() -> + supervisor:start_child(ejabberd_iq_sup, []). + +-spec start_link() -> + 'ignore' | {'error', _} | {'ok', pid()}. +start_link() -> + gen_server:start_link(?MODULE, ok, []). + +-spec process_iq(Pid :: pid(), + IQHandler :: mongoose_iq_handler:t(), + Acc :: mongoose_acc:t(), + From :: jid:jid(), + To :: jid:jid(), + IQ :: jlib:iq()) -> ok. +process_iq(Pid, IQHandler, Acc, From, To, IQ) -> + gen_server:cast(Pid, {process_iq, IQHandler, Acc, From, To, IQ}). + +-spec stop(Pid :: pid()) -> ok. +stop(Pid) -> + gen_server:cast(Pid, stop). +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- + +%% @doc Initiates the server +-spec init(_) -> {ok, ok}. +init(_) -> + %% no need for a state + {ok, ok}. + +handle_call(stop, _From, State) -> + Reply = ok, + {stop, normal, Reply, State}; +handle_call(_Msg, _From, State) -> + {reply, not_implemented, State}. + +handle_cast({process_iq, IQHandler, Acc, From, To, IQ}, State) -> + mongoose_iq_handler:execute_handler(IQHandler, Acc, From, To, IQ), + {noreply, State}; +handle_cast(_Msg, State) -> + {noreply, State}. diff --git a/test/mongoose_cleanup_SUITE.erl b/test/mongoose_cleanup_SUITE.erl index 4f214f982d..fc4a32eced 100644 --- a/test/mongoose_cleanup_SUITE.erl +++ b/test/mongoose_cleanup_SUITE.erl @@ -164,12 +164,12 @@ setup_meck([exometer | R]) -> setup_meck([ejabberd_sm | R]) -> meck:new(ejabberd_sm), meck:expect(ejabberd_sm, register_iq_handler, - fun(_A1, _A2, _A3, _A4, _A5) -> ok end), + fun(_A1, _A2, _A3) -> ok end), setup_meck(R); setup_meck([ejabberd_local | R]) -> meck:new(ejabberd_local), meck:expect(ejabberd_local, register_iq_handler, - fun(_A1, _A2, _A3, _A4, _A5) -> ok end), + fun(_A1, _A2, _A3) -> ok end), setup_meck(R); setup_meck([ejabberd_config | R]) -> meck:new(ejabberd_config), From c5ef3f4792ddd20026f0037d21cb0b4fb8d45bd6 Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Fri, 14 May 2021 15:09:30 +0200 Subject: [PATCH 05/13] adding mongoose_lazy_routing module --- src/domain/mongoose_domain_api.erl | 53 +++++++- src/domain/mongoose_domain_core.erl | 6 +- src/domain/mongoose_lazy_routing.erl | 173 ++++++++++++++++++++++++ src/domain/mongoose_subdomain_core.erl | 14 +- src/ejabberd_local.erl | 6 - src/mongoose_hooks.erl | 19 +-- src/mongoose_router_dynamic_domains.erl | 8 +- src/mongoose_router_localdomain.erl | 6 + test/mongoose_domain_core_SUITE.erl | 14 +- test/mongoose_subdomain_core_SUITE.erl | 17 +-- 10 files changed, 262 insertions(+), 54 deletions(-) create mode 100644 src/domain/mongoose_lazy_routing.erl diff --git a/src/domain/mongoose_domain_api.erl b/src/domain/mongoose_domain_api.erl index c2b3aeee6e..d0e5d6b69b 100644 --- a/src/domain/mongoose_domain_api.erl +++ b/src/domain/mongoose_domain_api.erl @@ -3,24 +3,36 @@ -module(mongoose_domain_api). -export([init/0, - insert_domain/2, + get_host_type/1]). + +%% domain API +-export([insert_domain/2, delete_domain/2, disable_domain/1, enable_domain/1, - get_host_type/1, + get_domain_host_type/1, get_all_static/0, get_domains_by_host_type/1]). +%% subdomain API +-export([register_subdomain/3, + unregister_subdomain/2, + get_subdomain_host_type/1, + get_subdomain_info/1]). + -type domain() :: jid:lserver(). -type host_type() :: mongooseim:host_type(). -type pair() :: {domain(), host_type()}. +-type subdomain_pattern() :: mongoose_subdomain_utils:subdomain_pattern(). -spec init() -> ok | {error, term()}. init() -> Pairs = get_static_pairs(), AllowedHostTypes = ejabberd_config:get_global_option_or_default(host_types, []), - mongoose_domain_core:start(Pairs, AllowedHostTypes). + mongoose_domain_core:start(Pairs, AllowedHostTypes), + mongoose_subdomain_core:start(), + mongoose_lazy_routing:start(). %% Domain should be nameprepped using `jid:nameprep'. -spec insert_domain(domain(), host_type()) -> @@ -90,8 +102,30 @@ check_db(Result) -> -spec get_host_type(domain()) -> {ok, host_type()} | {error, not_found}. get_host_type(Domain) -> + case get_domain_host_type(Domain) of + {ok, HostType} -> {ok, HostType}; + {error, not_found} -> + get_subdomain_host_type(Domain) + end. + +%% Domain should be nameprepped using `jid:nameprep' +-spec get_domain_host_type(domain()) -> + {ok, host_type()} | {error, not_found}. +get_domain_host_type(Domain) -> mongoose_domain_core:get_host_type(Domain). +%% Subdomain should be nameprepped using `jid:nameprep' +-spec get_subdomain_host_type(domain()) -> + {ok, host_type()} | {error, not_found}. +get_subdomain_host_type(Subdomain) -> + mongoose_subdomain_core:get_host_type(Subdomain). + +%% Subdomain should be nameprepped using `jid:nameprep' +-spec get_subdomain_info(domain()) -> + {ok, mongoose_subdomain_core:subdomain_info()} | {error, not_found}. +get_subdomain_info(Subdomain) -> + mongoose_subdomain_core:get_subdomain_info(Subdomain). + %% Get the list of the host_types provided during initialisation %% This has complexity N, where N is the number of online domains. -spec get_all_static() -> [{domain(), host_type()}]. @@ -120,6 +154,17 @@ check_domain(Domain, HostType) -> end. %% Domains should be nameprepped using `jid:nameprep' --spec get_static_pairs() -> [{domain(), host_type()}]. +-spec get_static_pairs() -> [pair()]. get_static_pairs() -> [{H, H} || H <- ejabberd_config:get_global_option_or_default(hosts, [])]. + +-spec register_subdomain(host_type(), subdomain_pattern(), + mongoose_packet_handler:t()) -> + ok | {error, already_registered | subdomain_already_exists}. +register_subdomain(HostType, SubdomainPattern, PacketHandler) -> + mongoose_subdomain_core:register_subdomain(HostType, SubdomainPattern, + PacketHandler). + +-spec unregister_subdomain(host_type(), subdomain_pattern()) -> ok. +unregister_subdomain(HostType, SubdomainPattern) -> + mongoose_subdomain_core:unregister_subdomain(HostType, SubdomainPattern). diff --git a/src/domain/mongoose_domain_core.erl b/src/domain/mongoose_domain_core.erl index 6ec7d96cca..fbeaa5cc03 100644 --- a/src/domain/mongoose_domain_core.erl +++ b/src/domain/mongoose_domain_core.erl @@ -1,6 +1,8 @@ %% Generally, you should not call anything from this module. %% Use mongoose_domain_api module instead. -module(mongoose_domain_core). +-behaviour(gen_server). + -include("mongoose_logger.hrl"). %% required for ets:fun2ms/1 pseudo function @@ -37,6 +39,7 @@ -type domain() :: mongooseim:domain_name(). -ifdef(TEST). + %% required for unit tests start(Pairs, AllowedHostTypes) -> just_ok(gen_server:start({local, ?MODULE}, ?MODULE, [Pairs, AllowedHostTypes], [])). @@ -123,6 +126,7 @@ get_start_args() -> %%-------------------------------------------------------------------- %% gen_server callbacks +%%-------------------------------------------------------------------- init([Pairs, AllowedHostTypes]) -> ets:new(?TABLE, [set, named_table, protected, {read_concurrency, true}]), ets:new(?HOST_TYPE_TABLE, [set, named_table, protected, {read_concurrency, true}]), @@ -199,7 +203,7 @@ handle_delete(Domain) -> [{Domain, HostType, _Source}] -> ets:delete(?TABLE, Domain), mongoose_subdomain_core:remove_domain(HostType, Domain), - mongoose_hooks:disable_domain(HostType, Domain), + mongoose_lazy_routing:maybe_remove_domain(HostType, Domain), ok end. diff --git a/src/domain/mongoose_lazy_routing.erl b/src/domain/mongoose_lazy_routing.erl new file mode 100644 index 0000000000..1a560c238e --- /dev/null +++ b/src/domain/mongoose_lazy_routing.erl @@ -0,0 +1,173 @@ +%% Generally, you should not call anything from this module. +%% Use mongoose_domain_api module instead. +-module(mongoose_lazy_routing). +-behaviour(gen_server). + +-include("mongoose_logger.hrl"). + +%% API +-export([start/0, stop/0]). +-export([start_link/0]). + +-export([maybe_add_domain_or_subdomain/1, + maybe_remove_domain/2, + maybe_remove_subdomain/1]). + +%% gen_server callbacks +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- +-ifdef(TEST). + +%% required for unit tests +start() -> + just_ok(gen_server:start({local, ?MODULE}, ?MODULE, [], [])). + +stop() -> + gen_server:stop(?MODULE). + +-else. + +start() -> + ChildSpec = {?MODULE, {?MODULE, start_link, []}, + permanent, infinity, worker, [?MODULE]}, + just_ok(supervisor:start_child(ejabberd_sup, ChildSpec)). + +%% required for integration tests +stop() -> + supervisor:terminate_child(ejabberd_sup, ?MODULE), + supervisor:delete_child(ejabberd_sup, ?MODULE), + ok. + +-endif. + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +-spec maybe_add_domain_or_subdomain(mongooseim:domain()) -> boolean(). +maybe_add_domain_or_subdomain(Domain) -> + %% it can be domain or subdomain name + gen_server:call(?MODULE, {maybe_add_domain_or_subdomain, Domain}). + +-spec maybe_remove_domain(mongooseim:host_type(), mongooseim:domain()) -> ok. +maybe_remove_domain(HostType, Domain) -> + gen_server:cast(?MODULE, {maybe_remove_domain, HostType, Domain}). + +-spec maybe_remove_subdomain(mongoose_subdomain_core:subdomain_info()) -> ok. +maybe_remove_subdomain(SubdomainInfo) -> + gen_server:cast(?MODULE, {maybe_remove_subdomain, SubdomainInfo}). + +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- +init(_) -> + ets:new(?MODULE, [set, named_table, protected]), + {ok,ok}. + +handle_call({maybe_add_domain_or_subdomain, Domain}, _From, State) -> + RetValue = handle_maybe_add_domain_or_subdomain(Domain), + {reply, RetValue, State}; +handle_call(Request, From, State) -> + ?UNEXPECTED_CALL(Request, From), + {reply, ok, State}. + +handle_cast({maybe_remove_domain, HostType, Domain}, State) -> + handle_maybe_remove_domain(HostType, Domain), + {noreply, State}; +handle_cast({maybe_remove_subdomain, SubdomainInfo}, State) -> + handle_maybe_remove_subdomain(SubdomainInfo), + {noreply, State}; +handle_cast(Msg, State) -> + ?UNEXPECTED_CAST(Msg), + {noreply, State}. + +handle_info(Info, State) -> + ?UNEXPECTED_INFO(Info), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%% local functions +%%-------------------------------------------------------------------- +just_ok({ok, _}) -> ok; +just_ok(Other) -> Other. + +handle_maybe_add_domain_or_subdomain(Domain) -> + case ets:lookup(?MODULE, Domain) of + [_] -> + %% It's absolutely normal situation. We can receive + %% a couple of maybe_add_domain_or_subdomain requests + %% from different processes, so this domain can be + %% already added. + true; + [] -> + try_to_add_domain_or_subdomain(Domain) + end. + +try_to_add_domain_or_subdomain(Domain) -> + case mongoose_domain_api:get_host_type(Domain) of + {ok, HostType} -> + add_domain(HostType, Domain), + true; + {error, not_found} -> + case mongoose_domain_api:get_subdomain_info(Domain) of + {ok, Info} -> + add_subdomain(Info), + true; + {error, not_found} -> + false + end + end. + +add_domain(HostType, Domain) -> + case ets:insert_new(?MODULE, {Domain, HostType}) of + true -> + %% TODO: register IQ handlers before domain registration + ejabberd_local:register_host(Domain); + false -> + %% we should never get here, but it's ok to just ignore this. + ok + end. + +add_subdomain(#{subdomain := Subdomain, host_type := HostType, + subdomain_pattern := SubdomainPattern, + packet_handler := PacketHandler}) -> + case ets:insert_new(?MODULE, {Subdomain, {HostType, SubdomainPattern}}) of + true -> + %% TODO: register IQ handlers before subdomain registration + ejabberd_router:register_route(Subdomain, PacketHandler); + false -> + %% we should never get here, but it's ok to just ignore this. + ok + end. + +handle_maybe_remove_domain(HostType, Domain) -> + case ets:lookup(?MODULE, Domain) of + [{Domain, HostType}] -> + ejabberd_local:unregister_host(Domain), + %% TODO: unregister IQ handlers after domain + ets:delete(?MODULE, Domain); + _ -> ok + end. + +handle_maybe_remove_subdomain(#{subdomain := Subdomain, host_type := HostType, + subdomain_pattern := SubdomainPattern}) -> + case ets:lookup(?MODULE, Subdomain) of + [{Domain, {HostType, SubdomainPattern}}] -> + ejabberd_router:unregister_route(Domain), + %% TODO: unregister IQ handlers after domain + ets:delete(?MODULE, Domain); + _ -> ok + end. diff --git a/src/domain/mongoose_subdomain_core.erl b/src/domain/mongoose_subdomain_core.erl index 330b01c47f..be2e7e8eb6 100644 --- a/src/domain/mongoose_subdomain_core.erl +++ b/src/domain/mongoose_subdomain_core.erl @@ -67,6 +67,7 @@ %% API %%-------------------------------------------------------------------- -ifdef(TEST). + %% required for unit tests start() -> just_ok(gen_server:start({local, ?MODULE}, ?MODULE, [], [])). @@ -247,15 +248,16 @@ handle_remove_domain(HostType, Domain) -> -spec remove_subdomains([subdomain_item()]) -> ok. remove_subdomains(SubdomainItems) -> - Fn = fun(#subdomain_item{host_type = HostType, subdomain = Subdomain}) -> - remove_subdomain(HostType, Subdomain) + Fn = fun(SubdomainItem) -> + remove_subdomain(SubdomainItem) end, lists:foreach(Fn, SubdomainItems). --spec remove_subdomain(host_type(), domain()) -> true. -remove_subdomain(HostType, Subdomain) -> - mongoose_hooks:disable_subdomain(HostType, Subdomain), - ets:delete(?SUBDOMAINS_TABLE, Subdomain). +-spec remove_subdomain(subdomain_item()) -> ok. +remove_subdomain(#subdomain_item{subdomain = Subdomain} = SubdomainItem) -> + ets:delete(?SUBDOMAINS_TABLE, Subdomain), + SubdomainInfo = convert_subdomain_item_to_map(SubdomainItem), + mongoose_lazy_routing:maybe_remove_subdomain(SubdomainInfo). -spec add_subdomains([reg_item()], domain()) -> ok. add_subdomains(RegItems, Domain) -> diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index bd7cade083..06799ccd71 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -251,9 +251,6 @@ node_cleanup(Acc, Node) -> Res = mnesia:async_dirty(F), maps:put(?MODULE, Res, Acc). -disable_domain(_Acc, _HostType, Domain) -> - unregister_host(Domain). - %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -266,14 +263,12 @@ disable_domain(_Acc, _HostType, Domain) -> %% Description: Initiates the server %%-------------------------------------------------------------------- init([]) -> - lists:foreach(fun do_register_host/1, ?MYHOSTS), catch ets:new(?IQTABLE, [named_table, protected]), update_table(), mnesia:create_table(iq_response, [{ram_copies, [node()]}, {attributes, record_info(fields, iq_response)}]), mnesia:add_table_copy(iq_response, node(), ram_copies), - ejabberd_hooks:add(disable_domain, global, fun disable_domain/3,50), ejabberd_hooks:add(node_cleanup, global, ?MODULE, node_cleanup, 50), {ok, #state{}}. @@ -355,7 +350,6 @@ handle_info(_Info, State) -> %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ejabberd_hooks:delete(node_cleanup, global, ?MODULE, node_cleanup, 50), - ejabberd_hooks:delete(disable_domain, global, fun disable_domain/3, 50), ok. %%-------------------------------------------------------------------- diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index 1e43d66204..2ec4ba208c 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -153,9 +153,7 @@ -export([c2s_remote_hook/5]). --export([disable_domain/2, - disable_subdomain/2, - remove_domain/2, +-export([remove_domain/2, node_cleanup/1]). %% Just a map, used by some hooks as a first argument. @@ -233,21 +231,6 @@ anonymous_purge_hook(LServer, Acc, LUser) -> auth_failed(HostType, Server, Username) -> ejabberd_hooks:run_for_host_type(auth_failed, HostType, ok, [Username, Server]). --spec disable_domain(HostType, Domain) -> Result when - HostType :: mongooseim:host_type(), - Domain :: jid:lserver(), - Result :: ok. -disable_domain(HostType, Domain) -> - ejabberd_hooks:run_global(disable_domain, ok, [HostType, Domain]). - - --spec disable_subdomain(HostType, Subdomain) -> Result when - HostType :: mongooseim:host_type(), - Subdomain :: jid:lserver(), - Result :: ok. -disable_subdomain(HostType, Subdomain) -> - ejabberd_hooks:run_global(disable_subdomain, ok, [HostType, Subdomain]). - -spec remove_domain(HostType, Domain) -> Result when HostType :: binary(), Domain :: jid:lserver(), diff --git a/src/mongoose_router_dynamic_domains.erl b/src/mongoose_router_dynamic_domains.erl index ab4b1cc2c7..f68bcfc5e6 100644 --- a/src/mongoose_router_dynamic_domains.erl +++ b/src/mongoose_router_dynamic_domains.erl @@ -10,7 +10,6 @@ %%% @end %%%------------------------------------------------------------------- -module(mongoose_router_dynamic_domains). --author('bartlomiej.gorny@erlang-solutions.com'). -behaviour(xmpp_router). @@ -27,10 +26,9 @@ filter(From, To, Acc, Packet) -> route(From, To, Acc, Packet) -> LDstDomain = To#jid.lserver, - case mongoose_domain_api:get_host_type(LDstDomain) of - {ok, _} -> - ejabberd_local:register_host(LDstDomain), + case mongoose_lazy_routing:maybe_add_domain_or_subdomain(LDstDomain) of + true -> mongoose_router_localdomain:route(From, To, Acc, Packet); - {error, not_found} -> {From, To, Acc, Packet} + false -> {From, To, Acc, Packet} end. diff --git a/src/mongoose_router_localdomain.erl b/src/mongoose_router_localdomain.erl index cdf36c0d1a..f7cfdacd88 100644 --- a/src/mongoose_router_localdomain.erl +++ b/src/mongoose_router_localdomain.erl @@ -25,6 +25,12 @@ route(From, To, Acc, Packet) -> case route_to_host(From, To, Acc, Packet, LDstDomain) of done -> done; Result -> + %% TODO: get rid of this ugly case-of construction + %% subdomain must be registered in the 'route' table. + %% Also this hack may not work if host is not added + %% yet, cause now all the hosts (including statically + %% configured hosts) are added in a lazy manner (see + %% mongoose_router_dynamic_domains module). case mongoose_subhosts:get_host(LDstDomain) of {ok, Host} -> route_to_host(From, To, Acc, Packet, Host); undefined -> Result diff --git a/test/mongoose_domain_core_SUITE.erl b/test/mongoose_domain_core_SUITE.erl index 545577f310..81dec09271 100644 --- a/test/mongoose_domain_core_SUITE.erl +++ b/test/mongoose_domain_core_SUITE.erl @@ -27,8 +27,8 @@ all() -> run_for_each_domain]. init_per_suite(Config) -> - meck:new(mongoose_hooks, [no_link]), - meck:expect(mongoose_hooks, disable_domain, fun(_, _) -> ok end), + meck:new(mongoose_lazy_routing, [no_link]), + meck:expect(mongoose_lazy_routing, maybe_remove_domain, fun(_, _) -> ok end), Config. end_per_suite(Config) -> @@ -37,7 +37,7 @@ end_per_suite(Config) -> init_per_testcase(_, Config) -> ok = mongoose_domain_core:start(?STATIC_PAIRS, ?ALLOWED_TYPES), - meck:reset(mongoose_hooks), + meck:reset(mongoose_lazy_routing), Config. end_per_testcase(_, Config) -> @@ -55,7 +55,8 @@ lookup_works(_) -> {ok, <<"type #3">>} = mongoose_domain_core:get_host_type(<<"some.domain">>), ok = mongoose_domain_core:delete(<<"some.domain">>), {error, not_found} = mongoose_domain_core:get_host_type(<<"some.domain">>), - ok = meck:wait(mongoose_hooks, disable_domain, [<<"type #3">>, <<"some.domain">>], 0). + ok = meck:wait(mongoose_lazy_routing, maybe_remove_domain, + [<<"type #3">>, <<"some.domain">>], 0). double_insert_double_remove_works(_) -> {error, not_found} = mongoose_domain_core:get_host_type(<<"some.domain">>), @@ -65,8 +66,9 @@ double_insert_double_remove_works(_) -> ok = mongoose_domain_core:delete(<<"some.domain">>), ok = mongoose_domain_core:delete(<<"some.domain">>), {error, not_found} = mongoose_domain_core:get_host_type(<<"some.domain">>), - ok = meck:wait(mongoose_hooks, disable_domain, [<<"type #3">>, <<"some.domain">>], 0), - 1 = meck:num_calls(mongoose_hooks, disable_domain, 2). + ok = meck:wait(mongoose_lazy_routing, maybe_remove_domain, + [<<"type #3">>, <<"some.domain">>], 0), + 1 = meck:num_calls(mongoose_lazy_routing, maybe_remove_domain, 2). static_domain_check(_) -> true = mongoose_domain_core:is_static(<<"example.cfg">>), diff --git a/test/mongoose_subdomain_core_SUITE.erl b/test/mongoose_subdomain_core_SUITE.erl index 29dd7d223a..bf7e46f1ea 100644 --- a/test/mongoose_subdomain_core_SUITE.erl +++ b/test/mongoose_subdomain_core_SUITE.erl @@ -33,10 +33,10 @@ all() -> detects_domain_conflict_with_fqdn_subdomain]. init_per_suite(Config) -> - meck:new(mongoose_hooks, [no_link]), + meck:new(mongoose_lazy_routing, [no_link]), meck:new(mongoose_subdomain_core, [no_link, passthrough]), - meck:expect(mongoose_hooks, disable_domain, fun(_, _) -> ok end), - meck:expect(mongoose_hooks, disable_subdomain, fun(_, _) -> ok end), + meck:expect(mongoose_lazy_routing, maybe_remove_domain, fun(_, _) -> ok end), + meck:expect(mongoose_lazy_routing, maybe_remove_subdomain, fun(_) -> ok end), Config. end_per_suite(Config) -> @@ -54,7 +54,7 @@ init_per_testcase(_, Config) -> ok = mongoose_subdomain_core:start(), [mongoose_domain_core:insert(Domain, ?DYNAMIC_HOST_TYPE2, dummy_source) || Domain <- ?DYNAMIC_DOMAINS], - [meck:reset(M) || M <- [mongoose_hooks, mongoose_subdomain_core]], + [meck:reset(M) || M <- [mongoose_lazy_routing, mongoose_subdomain_core]], Config. end_per_testcase(_, Config) -> @@ -317,7 +317,7 @@ handles_domain_removal_during_subdomain_registration(_Config) -> || Domain <- AllDomains], ?assertEqualLists(AllExpectedSubDomains, Subdomains), ?assertEqual(NumOfDomainsToRemove, - meck:num_calls(mongoose_hooks, disable_subdomain, 2)), + meck:num_calls(mongoose_lazy_routing, maybe_remove_subdomain, 1)), no_collisions(), meck:unload(mongoose_domain_core). @@ -649,6 +649,7 @@ get_list_of_subdomain_collisions() -> From =:= report_subdomains_collision]. get_list_of_disabled_subdomains() -> - History = meck:history(mongoose_hooks), - [lists:nth(2, Args) %% Subdomain is the second argument - || {_Pid, {_Mod, disable_subdomain = _Func, Args}, _Result} <- History]. + History = meck:history(mongoose_lazy_routing), + [maps:get(subdomain, Info) + || {_Pid, {_Mod, Func, [Info] = _Args}, _Result} <- History, + Func =:= maybe_remove_subdomain]. From 37e39b9184c9967c61164b063d0fcd604a77472f Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Mon, 17 May 2021 17:34:03 +0200 Subject: [PATCH 06/13] adding new API for gen_iq_handler --- src/domain/mongoose_lazy_routing.erl | 47 +++++++++++++++-- src/gen_iq_component.erl | 4 +- src/gen_iq_handler.erl | 78 ++++++++++++++++++++++++++-- 3 files changed, 120 insertions(+), 9 deletions(-) diff --git a/src/domain/mongoose_lazy_routing.erl b/src/domain/mongoose_lazy_routing.erl index 1a560c238e..e68e070457 100644 --- a/src/domain/mongoose_lazy_routing.erl +++ b/src/domain/mongoose_lazy_routing.erl @@ -1,18 +1,24 @@ -%% Generally, you should not call anything from this module. -%% Use mongoose_domain_api module instead. +%% Generally, you should not call anything from this module directly. -module(mongoose_lazy_routing). -behaviour(gen_server). -include("mongoose_logger.hrl"). -%% API +%% start/stop API -export([start/0, stop/0]). -export([start_link/0]). +%% domain/subdomain API -export([maybe_add_domain_or_subdomain/1, maybe_remove_domain/2, maybe_remove_subdomain/1]). +%% IQ handling API +-export([register_iq_handler_for_domain/4, + register_iq_handler_for_subdomain/5, + unregister_iq_handler_for_domain/3, + unregister_iq_handler_for_subdomain/4]). + %% gen_server callbacks -export([init/1, handle_call/3, @@ -21,6 +27,8 @@ terminate/2, code_change/3]). +-type subdomain_pattern() :: mongoose_subdomain_utils:subdomain_pattern(). + %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- @@ -64,6 +72,39 @@ maybe_remove_domain(HostType, Domain) -> maybe_remove_subdomain(SubdomainInfo) -> gen_server:cast(?MODULE, {maybe_remove_subdomain, SubdomainInfo}). +-spec register_iq_handler_for_domain(HostType :: mongooseim:host_type(), + Namespace :: binary(), + Component :: module(), + IQHandler :: mongoose_iq_handler:t()) -> + ok | {error, atom()}. +register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) -> + ok. + +-spec register_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), + SubdomainPattern :: subdomain_pattern(), + Namespace :: binary(), + Component :: module(), + IQHandler :: mongoose_iq_handler:t()) -> + ok | {error, atom()}. +register_iq_handler_for_subdomain(HostType, SubdomainPattern, + Namespace, Component, IQHandler) -> + ok. + +-spec unregister_iq_handler_for_domain(HostType :: mongooseim:host_type(), + Namespace :: binary(), + Component :: module()) -> + {ok, mongoose_iq_handler:t()} | {error, not_found}. +unregister_iq_handler_for_domain(HostType, Namespace, Component) -> + {error, not_found}. + +-spec unregister_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), + SubdomainPattern :: subdomain_pattern(), + Namespace :: binary(), + Component :: module()) -> + {ok, mongoose_iq_handler:t()} | {error, not_found}. +unregister_iq_handler_for_subdomain(HostType, SubdomainPattern, + Namespace, Component) -> + {error, not_found}. %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- diff --git a/src/gen_iq_component.erl b/src/gen_iq_component.erl index 73902a6365..d335f0f76b 100644 --- a/src/gen_iq_component.erl +++ b/src/gen_iq_component.erl @@ -29,8 +29,8 @@ unregister_iq_handler(Component, Domain, Namespace) -> -spec stop_iq_handler(IQHandler :: mongoose_iq_handler:t()) -> any(). stop_iq_handler(IQHandler) -> %% TODO: this function is required only for correct implementation of the legacy - %% gen_iq_handler:remove_iq_handler/3 interface, get rid of it once gen_iq_handler - %% module is removed. + %% gen_iq_handler:remove_iq_handler/3 interface, get rid of it once old API is + %% removed from gen_iq_handler module. case mongoose_iq_handler:extra(IQHandler) of #{delete_on_unregister := true} -> mongoose_iq_handler:delete(IQHandler); diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index 21fbd03472..a2962d4927 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -26,13 +26,22 @@ -module(gen_iq_handler). -author('alexey@process-one.net'). -%% Old API +%% Old API. Get rid of it once all the modules adopted. -export([add_iq_handler/6, remove_iq_handler/3, check_type/1]). +%% New API. +-export([add_iq_handler_for_domain/6, + add_iq_handler_for_subdomain/7, + remove_iq_handler_for_domain/3, + remove_iq_handler_for_subdomain/4]). + +-type execution_type() :: mongoose_iq_handler:execution_type(). +-type subdomain_pattern() :: mongoose_subdomain_utils:subdomain_pattern(). + %%==================================================================== -%% Old API +%% Old API. Get rid of it once all the modules adopted. %%==================================================================== -spec add_iq_handler(Component :: module(), Domain :: jid:server(), @@ -53,19 +62,80 @@ add_iq_handler(Component, Domain, Namespace, Module, Function, ExecutionType) -> remove_iq_handler(Component, Domain, Namespace) -> gen_iq_component:unregister_iq_handler(Component, Domain, Namespace). --spec check_type(mongoose_iq_handler:execution_type()) -> - mongoose_iq_handler:execution_type(). +-spec check_type(execution_type()) -> execution_type(). check_type(no_queue) -> no_queue; check_type(parallel) -> parallel; check_type(one_queue) -> one_queue; check_type({queues, Int}) when is_integer(Int), Int > 0 -> {queues, Int}. + +%%==================================================================== +%% New API. +%%==================================================================== +-spec add_iq_handler_for_domain(HostType :: mongooseim:host_type(), + Namespace :: binary(), + Component :: module(), + IQHandlerFn :: mongoose_iq_handler:iq_handler(), + Extra :: map(), + ExecutionType :: execution_type()) -> + ok | {error, atom()}. +add_iq_handler_for_domain(HostType, Namespace, Component, IQHandlerFn, + Extra, ExecutionType) -> + %% TODO: `delete_on_unregister` extra field is not needed once old API is removed + NewExtra = Extra#{delete_on_unregister => false}, + IQHandler = mongoose_iq_handler:new(IQHandlerFn, NewExtra, ExecutionType), + mongoose_lazy_routing:register_iq_handler_for_domain(HostType, Namespace, + Component, IQHandler). + +-spec add_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), + SubdomainPattern :: subdomain_pattern(), + Namespace :: binary(), + Component :: module(), + IQHandlerFn :: mongoose_iq_handler:iq_handler(), + Extra :: map(), + ExecutionType :: execution_type()) -> + ok | {error, atom()}. +add_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace, Component, + IQHandlerFn, Extra, ExecutionType) -> + %% TODO: `delete_on_unregister` extra field is not needed once old API is removed + NewExtra = Extra#{delete_on_unregister => false}, + IQHandler = mongoose_iq_handler:new(IQHandlerFn, NewExtra, ExecutionType), + mongoose_lazy_routing:register_iq_handler_for_subdomain(HostType, SubdomainPattern, + Namespace, Component, + IQHandler). + +-spec remove_iq_handler_for_domain(HostType :: mongooseim:host_type(), + Namespace :: binary(), + Component :: module()) -> + ok | {error, not_registered}. +remove_iq_handler_for_domain(HostType, Namespace, Component) -> + case mongoose_lazy_routing:unregister_iq_handler_for_domain( + HostType, Namespace, Component) of + {ok, IQHandler} -> + mongoose_iq_handler:delete(IQHandler); + {error, not_found} -> {error, not_registered} + end. + +-spec remove_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), + SubdomainPattern :: subdomain_pattern(), + Namespace :: binary(), + Component :: module()) -> + ok | {error, not_registered}. +remove_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace, Component) -> + case mongoose_lazy_routing:unregister_iq_handler_for_subdomain( + HostType, SubdomainPattern, Namespace, Component) of + {ok, IQHandler} -> + mongoose_iq_handler:delete(IQHandler); + {error, not_found} -> {error, not_registered} + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -spec make_iq_handler_fn(module(), atom()) -> mongoose_iq_handler:iq_handler(). make_iq_handler_fn(Module, Function) -> + %TODO: remove this function with removal of the old API fun(Acc, From, To, IQ, _Extra) -> Module:Function(From, To, Acc, IQ) end. From b48c3e93bb5b1ecf781bf61b55aabd5b3f328ebf Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Tue, 18 May 2021 02:00:54 +0200 Subject: [PATCH 07/13] implementing IQ handling API at mongoose_lazy_routing --- src/domain/mongoose_lazy_routing.erl | 228 ++++++++++++++++++++++++--- 1 file changed, 205 insertions(+), 23 deletions(-) diff --git a/src/domain/mongoose_lazy_routing.erl b/src/domain/mongoose_lazy_routing.erl index e68e070457..1f28191717 100644 --- a/src/domain/mongoose_lazy_routing.erl +++ b/src/domain/mongoose_lazy_routing.erl @@ -27,7 +27,23 @@ terminate/2, code_change/3]). +-define(IQ_TABLE, mongoose_lazy_routing_iqs). +-define(ROUTING_TABLE, mongoose_lazy_routing). + +-record(iq_table_key, {host_type :: host_type(), + %% subdomain_pattern is 'undefined' for domains + subdomain_pattern :: subdomain_pattern() | undefined, + namespace = '_' :: binary() | '_', + component = '_' :: module() | '_'}). + -type subdomain_pattern() :: mongoose_subdomain_utils:subdomain_pattern(). +-type subdomain_info() :: mongoose_subdomain_core:subdomain_info(). +-type host_type() :: mongooseim:host_type(). +-type domain() :: mongooseim:domain_name(). + +-type iq_record() :: {#iq_table_key{}, mongoose_iq_handler:t()}. +-type domain_record() :: {domain(), host_type() | {host_type(), subdomain_pattern()}}. + %%-------------------------------------------------------------------- %% API @@ -68,7 +84,7 @@ maybe_add_domain_or_subdomain(Domain) -> maybe_remove_domain(HostType, Domain) -> gen_server:cast(?MODULE, {maybe_remove_domain, HostType, Domain}). --spec maybe_remove_subdomain(mongoose_subdomain_core:subdomain_info()) -> ok. +-spec maybe_remove_subdomain(subdomain_info()) -> ok. maybe_remove_subdomain(SubdomainInfo) -> gen_server:cast(?MODULE, {maybe_remove_subdomain, SubdomainInfo}). @@ -78,7 +94,8 @@ maybe_remove_subdomain(SubdomainInfo) -> IQHandler :: mongoose_iq_handler:t()) -> ok | {error, atom()}. register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) -> - ok. + gen_server:call(?MODULE, {register_iq_handler_for_domain, HostType, + Namespace, Component, IQHandler}). -spec register_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), SubdomainPattern :: subdomain_pattern(), @@ -88,14 +105,16 @@ register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) -> ok | {error, atom()}. register_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace, Component, IQHandler) -> - ok. + gen_server:call(?MODULE, {register_iq_handler_for_subdomain, HostType, + SubdomainPattern, Namespace, Component, IQHandler}). -spec unregister_iq_handler_for_domain(HostType :: mongooseim:host_type(), Namespace :: binary(), Component :: module()) -> {ok, mongoose_iq_handler:t()} | {error, not_found}. unregister_iq_handler_for_domain(HostType, Namespace, Component) -> - {error, not_found}. + gen_server:call(?MODULE, {unregister_iq_handler_for_domain, + HostType, Namespace, Component}). -spec unregister_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), SubdomainPattern :: subdomain_pattern(), @@ -104,17 +123,48 @@ unregister_iq_handler_for_domain(HostType, Namespace, Component) -> {ok, mongoose_iq_handler:t()} | {error, not_found}. unregister_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace, Component) -> - {error, not_found}. + gen_server:call(?MODULE, {unregister_iq_handler_for_subdomain, HostType, + SubdomainPattern, Namespace, Component}). + %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- init(_) -> - ets:new(?MODULE, [set, named_table, protected]), - {ok,ok}. + %% ?IQ_TABLE contains tuples of following format: + %% * {#iq_table_key{}, IQHandler} + ets:new(?IQ_TABLE, [set, named_table, protected]), + %% ?ROUTING_TABLE contains tuples of one of the following formats: + %% * {DomainName, HostType} + %% * {SubdomainName, {HostType, SubdomainPattern}} + ets:new(?ROUTING_TABLE, [set, named_table, protected]), + %% no state, all required data is stored in ETS tables + {ok, ok}. handle_call({maybe_add_domain_or_subdomain, Domain}, _From, State) -> RetValue = handle_maybe_add_domain_or_subdomain(Domain), {reply, RetValue, State}; +handle_call({register_iq_handler_for_domain, + HostType, Namespace, Component, IQHandler}, + _From, State) -> + RetValue = handle_register_iq_handler_for_domain(HostType, Namespace, + Component, IQHandler), + {reply, RetValue, State}; +handle_call({register_iq_handler_for_subdomain, HostType, + SubdomainPattern, Namespace, Component, IQHandler}, + _From, State) -> + RetValue = handle_register_iq_handler_for_subdomain(HostType, SubdomainPattern, + Namespace, Component, IQHandler), + {reply, RetValue, State}; +handle_call({unregister_iq_handler_for_domain, HostType, Namespace, Component}, + _From, State) -> + RetValue = handle_unregister_iq_handler_for_domain(HostType, Namespace, Component), + {reply, RetValue, State}; +handle_call({unregister_iq_handler_for_subdomain, HostType, SubdomainPattern, + Namespace, Component}, + _From, State) -> + RetValue = handle_unregister_iq_handler_for_subdomain(HostType, SubdomainPattern, + Namespace, Component), + {reply, RetValue, State}; handle_call(Request, From, State) -> ?UNEXPECTED_CALL(Request, From), {reply, ok, State}. @@ -145,18 +195,89 @@ code_change(_OldVsn, State, _Extra) -> just_ok({ok, _}) -> ok; just_ok(Other) -> Other. +-spec handle_register_iq_handler_for_domain(HostType :: mongooseim:host_type(), + Namespace :: binary(), + Component :: module(), + IQHandler :: mongoose_iq_handler:t()) -> + ok | {error, atom()}. +handle_register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) -> + IQKey = #iq_table_key{host_type = HostType, namespace = Namespace, + component = Component}, + NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}), + IQ = {IQKey, NewIQHandler}, + case ets:insert_new(?IQ_TABLE, IQ) of + false -> {error, already_registered}; + true -> + Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}), + register_iqs([IQ], Domains) + end. + +-spec handle_register_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), + SubdomainPattern :: subdomain_pattern(), + Namespace :: binary(), + Component :: module(), + IQHandler :: mongoose_iq_handler:t()) -> + ok | {error, atom()}. +handle_register_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace, + Component, IQHandler) -> + IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern, + namespace = Namespace, component = Component}, + NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}), + IQ = {IQKey, NewIQHandler}, + case ets:insert_new(?IQ_TABLE, IQ) of + false -> {error, already_registered}; + true -> + Domains = ets:match_object(?ROUTING_TABLE, + {'_', {HostType, SubdomainPattern}}), + register_iqs([IQ], Domains) + end. + +-spec handle_unregister_iq_handler_for_domain(HostType :: mongooseim:host_type(), + Namespace :: binary(), + Component :: module()) -> + {ok, mongoose_iq_handler:t()} | {error, not_found}. +handle_unregister_iq_handler_for_domain(HostType, Namespace, Component) -> + IQKey = #iq_table_key{host_type = HostType, namespace = Namespace, + component = Component}, + case ets:lookup(?IQ_TABLE, IQKey) of + [] -> {error, not_found}; + [{_, IQHandler} = IQ] -> + Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}), + unregister_iqs([IQ], Domains), + {ok, IQHandler} + end. + +-spec handle_unregister_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), + SubdomainPattern :: subdomain_pattern(), + Namespace :: binary(), + Component :: module()) -> + {ok, mongoose_iq_handler:t()} | {error, not_found}. +handle_unregister_iq_handler_for_subdomain(HostType, SubdomainPattern, + Namespace, Component) -> + IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern, + namespace = Namespace, component = Component}, + case ets:lookup(?IQ_TABLE, IQKey) of + [] -> {error, not_found}; + [{_, IQHandler} = IQ] -> + Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}), + unregister_iqs([IQ], Domains), + {ok, IQHandler} + end. + +-spec handle_maybe_add_domain_or_subdomain(domain()) -> boolean(). handle_maybe_add_domain_or_subdomain(Domain) -> - case ets:lookup(?MODULE, Domain) of - [_] -> + case ets:member(?ROUTING_TABLE, Domain) of + true -> %% It's absolutely normal situation. We can receive %% a couple of maybe_add_domain_or_subdomain requests %% from different processes, so this domain can be %% already added. true; - [] -> + false -> try_to_add_domain_or_subdomain(Domain) end. +-spec try_to_add_domain_or_subdomain(domain()) -> boolean(). try_to_add_domain_or_subdomain(Domain) -> case mongoose_domain_api:get_host_type(Domain) of {ok, HostType} -> @@ -172,43 +293,104 @@ try_to_add_domain_or_subdomain(Domain) -> end end. +-spec add_domain(host_type(), domain()) -> ok. add_domain(HostType, Domain) -> - case ets:insert_new(?MODULE, {Domain, HostType}) of + DomainElement = {Domain, HostType}, + case ets:insert_new(?ROUTING_TABLE, DomainElement) of true -> - %% TODO: register IQ handlers before domain registration - ejabberd_local:register_host(Domain); + IQs = get_iqs(#iq_table_key{host_type = HostType}), + register_iqs(IQs, [DomainElement]), + ejabberd_local:register_host(Domain), + ok; false -> %% we should never get here, but it's ok to just ignore this. ok end. +-spec add_subdomain(subdomain_info()) -> ok. add_subdomain(#{subdomain := Subdomain, host_type := HostType, subdomain_pattern := SubdomainPattern, packet_handler := PacketHandler}) -> - case ets:insert_new(?MODULE, {Subdomain, {HostType, SubdomainPattern}}) of + SubdomainElement = {Subdomain, {HostType, SubdomainPattern}}, + case ets:insert_new(?ROUTING_TABLE, SubdomainElement) of true -> - %% TODO: register IQ handlers before subdomain registration + IQs = get_iqs(#iq_table_key{host_type = HostType, + subdomain_pattern = SubdomainPattern}), + register_iqs(IQs, [SubdomainElement]), ejabberd_router:register_route(Subdomain, PacketHandler); false -> %% we should never get here, but it's ok to just ignore this. ok end. +-spec handle_maybe_remove_domain(host_type(), domain()) -> ok. handle_maybe_remove_domain(HostType, Domain) -> - case ets:lookup(?MODULE, Domain) of - [{Domain, HostType}] -> + case ets:lookup(?ROUTING_TABLE, Domain) of + [{Domain, HostType} = DomainElement] -> ejabberd_local:unregister_host(Domain), - %% TODO: unregister IQ handlers after domain - ets:delete(?MODULE, Domain); + IQs = get_iqs(#iq_table_key{host_type = HostType}), + unregister_iqs(IQs, [DomainElement]), + ets:delete(?ROUTING_TABLE, Domain); _ -> ok end. +-spec handle_maybe_remove_subdomain(subdomain_info()) -> ok. handle_maybe_remove_subdomain(#{subdomain := Subdomain, host_type := HostType, subdomain_pattern := SubdomainPattern}) -> - case ets:lookup(?MODULE, Subdomain) of - [{Domain, {HostType, SubdomainPattern}}] -> + case ets:lookup(?ROUTING_TABLE, Subdomain) of + [{Domain, {HostType, SubdomainPattern}} = SubdomainElement] -> ejabberd_router:unregister_route(Domain), - %% TODO: unregister IQ handlers after domain - ets:delete(?MODULE, Domain); + IQs = get_iqs(#iq_table_key{host_type = HostType, + subdomain_pattern = SubdomainPattern}), + unregister_iqs(IQs, [SubdomainElement]), + ets:delete(?ROUTING_TABLE, Domain); _ -> ok end. + +-spec get_iqs(#iq_table_key{}) -> [iq_record()]. +get_iqs(KeyMatchPattern) -> + ets:match_object(?IQ_TABLE, {KeyMatchPattern, '_'}). + +-spec register_iqs([iq_record()], [domain_record()]) -> ok. +register_iqs(IQList, DomainList) -> + [register_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList, + check_that_domain_and_iq_match(IQ, Domain)], + ok. + +-spec unregister_iqs([iq_record()], [domain_record()]) -> ok. +unregister_iqs(IQList, DomainList) -> + [unregister_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList, + check_that_domain_and_iq_match(IQ, Domain)], + ok. + +-spec check_that_domain_and_iq_match(iq_record(), domain_record()) -> boolean(). +check_that_domain_and_iq_match({#iq_table_key{host_type = HostType, + subdomain_pattern = undefined, + namespace = Namespace, + component = Component}, _}, + {_, HostType}) when is_binary(Namespace), + Component =/= '_' -> + true; +check_that_domain_and_iq_match({#iq_table_key{host_type = HostType, + subdomain_pattern = Pattern, + namespace = Namespace, + component = Component}, _}, + {_, {HostType, Pattern}}) when is_binary(Namespace), + Component =/= '_' -> + true; +check_that_domain_and_iq_match(IQ, Domain) -> + %% we should not get here, log error + ?LOG_ERROR(#{what => domain_and_iq_doesnt_match, domain => Domain, iq => IQ}), + false. + +-spec register_iq(iq_record(), domain_record()) -> ok. +register_iq({#iq_table_key{namespace = Namespace, + component = Component}, IQHandler}, + {Domain, _})-> + gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler). + +-spec unregister_iq(iq_record(), domain_record()) -> ok. +unregister_iq({#iq_table_key{namespace = Namespace, + component = Component}, _}, + {Domain, _}) -> + gen_iq_component:unregister_iq_handler(Component, Domain, Namespace). From 25cd9c805a61711e0b7dbcb89fc0c58e5d45621d Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Tue, 18 May 2021 11:19:25 +0200 Subject: [PATCH 08/13] adding mod_dynamic_domains_test demo module and basic integration tests for mongoose_lazy_routing --- big_tests/tests/dynamic_domains_pm_SUITE.erl | 85 ++++++++++++++++- src/domain/mongoose_lazy_routing.erl | 2 +- src/mod_dynamic_domains_test.erl | 98 ++++++++++++++++++++ 3 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 src/mod_dynamic_domains_test.erl diff --git a/big_tests/tests/dynamic_domains_pm_SUITE.erl b/big_tests/tests/dynamic_domains_pm_SUITE.erl index ba049a1ab2..29ac215aa7 100644 --- a/big_tests/tests/dynamic_domains_pm_SUITE.erl +++ b/big_tests/tests/dynamic_domains_pm_SUITE.erl @@ -1,8 +1,12 @@ -module(dynamic_domains_pm_SUITE). +-include_lib("exml/include/exml.hrl"). + %% API -compile(export_all). --import(distributed_helper, [mim/0, mim2/0, require_rpc_nodes/1, rpc/4]). +-import(distributed_helper, [mim/0, mim2/0, rpc/4, +require_rpc_nodes/1, +subhost_pattern/1]). -define(TEST_NODES, [mim() | ?CLUSTER_NODES]). -define(CLUSTER_NODES, [mim2()]). @@ -16,7 +20,8 @@ all() -> [can_authenticate, pm_messages, disconnected_on_domain_disabling, - auth_domain_removal_is_triggered_on_hook]. + auth_domain_removal_is_triggered_on_hook, + test_routing]. init_per_suite(Config0) -> Config = cluster_nodes(?CLUSTER_NODES, Config0), @@ -69,6 +74,79 @@ auth_domain_removal_is_triggered_on_hook(_Config) -> 1 = rpc(mim(), meck, num_calls, [ejabberd_auth_dummy, remove_domain, Params]), rpc(mim(), meck, unload, [ejabberd_auth_dummy]). +test_routing(Config) -> + StoryFn = + fun(Alice) -> + dynamic_modules:start(?HOST_TYPE, mod_dynamic_domains_test, + [{host1, subhost_pattern("subdomain1.@HOST@")}, + {host2, subhost_pattern("subdomain2.@HOST@")}, + {namespace, <<"dummy.namespace">>}]), + rpc(mim(), meck, unload, []), + ok = rpc(mim(), meck, new, [mod_dynamic_domains_test, + [passthrough, no_link]]), + Subdomains1 = [<<"subdomain1.", Domain/binary>> || Domain <- ?DOMAINS], + [escalus:send(Alice, escalus_stanza:chat_to(Subdomain, <<"OH, HAI!">>)) + || Subdomain <- Subdomains1], + rpc(mim(), meck, wait, [2, mod_dynamic_domains_test, process_packet, 5, 200]), + + ct:pal("!!! ~p~n", [rpc(mim(), meck, history, [mod_dynamic_domains_test])]), + dump_ets_tables([route, local_iqtable]), + + rpc(mim(), meck, reset, [mod_dynamic_domains_test]), + + QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []), + [begin + IQ = escalus_stanza:iq(Subdomain, <<"get">>, [QueryEl]), + escalus:send(Alice, IQ) + end || Subdomain <- Subdomains1], + + %% check that all the IQs to any of Subdomains1 landed at process_packet/5 + %% and no stanzas received in response + rpc(mim(), meck, wait, [2, mod_dynamic_domains_test, process_packet, 5, 200]), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), + rpc(mim(), meck, reset, [mod_dynamic_domains_test]), + [] = escalus:wait_for_stanzas(Alice, 5, 1000), + + [begin + IQ = escalus_stanza:iq(Domain, <<"get">>, [QueryEl]), + escalus:send_iq_and_wait_for_result(Alice, IQ) + end || Domain <- ?DOMAINS], + 2 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]), + %% check that process_iq/5 is called from one and the same worker process + History = rpc(mim(), meck, history, [mod_dynamic_domains_test]), + rpc(mim(), meck, reset, [mod_dynamic_domains_test]), + Pids = [Pid||{Pid,{_,process_iq,_},_}<-History], + [_] = (lists:usort(Pids)), + + + Subdomains2 = [<<"subdomain2.", Domain/binary>> || Domain <- ?DOMAINS], + [begin + IQ = escalus_stanza:iq(Subomain, <<"get">>, [QueryEl]), + escalus:send_iq_and_wait_for_result(Alice, IQ) + end || Subomain <- Subdomains2], + 2 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]), + + rpc(mim(), meck, unload, [mod_dynamic_domains_test]), + + [Domain | _] = ?DOMAINS, + remove_domains(?TEST_NODES, [Domain]), + timer:sleep(1000), + ct:pal("after removing domain ~p~n",[Domain]), + dump_ets_tables([route, local_iqtable]), + insert_domains(?TEST_NODES, [Domain]), + timer:sleep(1000), + ct:pal("after inserting domain ~p~n",[Domain]), + dump_ets_tables([route, local_iqtable]), + + dynamic_modules:stop(?HOST_TYPE, mod_dynamic_domains_test), + timer:sleep(1000), + ct:pal("after stopping the module~n"), + dump_ets_tables([route, local_iqtable]) + end, + escalus:story(Config, [{alice3, 1}], StoryFn). + %% helper functions insert_domains(Nodes, Domains) -> [domain_helper:insert_domain(Node, Domain) || Node <- Nodes, Domain <- Domains]. @@ -85,3 +163,6 @@ uncluster_nodes([], Config) -> Config; uncluster_nodes([Node | T], Config) -> NewConfig = distributed_helper:remove_node_from_cluster(Node, Config), cluster_nodes(T, NewConfig). + +dump_ets_tables(Tables) -> + [ct:pal("!!! ~p = ~p~n", [T, rpc(mim(), ets, tab2list, [T])]) || T <- Tables]. diff --git a/src/domain/mongoose_lazy_routing.erl b/src/domain/mongoose_lazy_routing.erl index 1f28191717..af5c13ce13 100644 --- a/src/domain/mongoose_lazy_routing.erl +++ b/src/domain/mongoose_lazy_routing.erl @@ -279,7 +279,7 @@ handle_maybe_add_domain_or_subdomain(Domain) -> -spec try_to_add_domain_or_subdomain(domain()) -> boolean(). try_to_add_domain_or_subdomain(Domain) -> - case mongoose_domain_api:get_host_type(Domain) of + case mongoose_domain_api:get_domain_host_type(Domain) of {ok, HostType} -> add_domain(HostType, Domain), true; diff --git a/src/mod_dynamic_domains_test.erl b/src/mod_dynamic_domains_test.erl new file mode 100644 index 0000000000..de100eae26 --- /dev/null +++ b/src/mod_dynamic_domains_test.erl @@ -0,0 +1,98 @@ +-module(mod_dynamic_domains_test). + +-include("mongoose_config_spec.hrl"). +-include("jlib.hrl"). + +%% API +-export([start/2, stop/1, + config_spec/0, + supported_features/0]). +-export([process_packet/5, process_iq/5]). + +-define(DUMMY_NAMESPACE, <<"dummy.namespace">>). + +-spec config_spec() -> mongoose_config_spec:config_section(). +config_spec() -> + #section{items = #{ + <<"host1">> => + #option{type = string, + validate = subdomain_template, + process = fun mongoose_subdomain_utils:make_subdomain_pattern/1}, + <<"host2">> => + #option{type = string, + validate = subdomain_template, + process = fun mongoose_subdomain_utils:make_subdomain_pattern/1}, + <<"namespace">> => + #option{type = binary, + validate = non_empty}}}. + +supported_features() -> [dynamic_domains]. + +-spec start(Host :: jid:server(), Opts :: list()) -> ok. +start(HostType, Opts) -> + Namespace = gen_mod:get_opt(namespace, Opts), + + %% if we need to intercept traffic to some subdomain, we can create custom packet + %% handler and register it. not that IQ will be delivered to this packet handler + %% as well + SubdomainPattern1 = gen_mod:get_opt(host1, Opts), + Handler1 = mongoose_packet_handler:new(?MODULE), + mongoose_domain_api:register_subdomain(HostType, SubdomainPattern1, Handler1), + %% the call below makes no sense, it's added for demo/testing purposes only + %% all the IQs for SubdomainPattern1 will go to process_packet/5 function + gen_iq_handler:add_iq_handler_for_subdomain(HostType, SubdomainPattern1, + Namespace, ejabberd_local, + fun ?MODULE:process_iq/5, + #{handler_type => subdomain}, + one_queue), + + %% if we want to just process IQ sent to some subdomain, and we don't care about + %% all other messages, then we can use `ejabberd_local` packet handler for such + %% subdomain and register IQ handler in the similar way as we do for domains. + SubdomainPattern2 = gen_mod:get_opt(host2, Opts), + Handler2 = mongoose_packet_handler:new(ejabberd_local), + mongoose_domain_api:register_subdomain(HostType, SubdomainPattern2, Handler2), + gen_iq_handler:add_iq_handler_for_subdomain(HostType, SubdomainPattern2, + Namespace, ejabberd_local, + fun ?MODULE:process_iq/5, + #{handler_type => subdomain}, + one_queue), + + %% we can use the new gen_iq_handler API to register IQ handlers for dynamic domains + gen_iq_handler:add_iq_handler_for_domain(HostType, Namespace, ejabberd_local, + fun ?MODULE:process_iq/5, + #{handler_type => domain}, + one_queue), + ok. + +-spec stop(Host :: jid:server()) -> ok. +stop(HostType) -> + Opts = gen_mod:get_module_opts(HostType, ?MODULE), + Namespace = gen_mod:get_opt(namespace, Opts), + + SubdomainPattern1 = gen_mod:get_opt(host1, Opts), + mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern1), + gen_iq_handler:remove_iq_handler_for_subdomain(HostType, SubdomainPattern1, + Namespace, ejabberd_local), + + SubdomainPattern2 = gen_mod:get_opt(host2, Opts), + mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern2), + gen_iq_handler:remove_iq_handler_for_subdomain(HostType, SubdomainPattern2, + Namespace, ejabberd_local), + + gen_iq_handler:remove_iq_handler_for_domain(HostType, Namespace, ejabberd_local), + + ok. + +-spec process_packet(Acc :: mongoose_acc:t(), From :: jid:jid(), To :: jid:jid(), + El :: exml:element(), Extra :: map()) -> any(). +process_packet(_Acc, _From, _To, _El, _Extra) -> + %% do nothing, just ignore the packet + ok. + +-spec process_iq(Acc :: mongoose_acc:t(), From :: jid:jid(), To :: jid:jid(), + IQ :: jlib:iq(), Extra :: map()) -> {NewAcc :: mongoose_acc:t(), + IQResp :: ignore | jlib:iq()}. +process_iq(Acc, _From, _To, IQ, _Extra) -> + %% reply with empty result IQ stanza + {Acc, IQ#iq{type = result, sub_el = []}}. \ No newline at end of file From bc36e6fc8fb875536858bd9006f59585bdcf24de Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Tue, 18 May 2021 13:04:58 +0200 Subject: [PATCH 09/13] resolving race condition during lazy registration of iq handlers. not sure why iq handlers are registered asynchronously, but keeping that as is (in case that is done to resolve some deadlocks) --- src/domain/mongoose_lazy_routing.erl | 3 ++- src/ejabberd_local.erl | 9 ++++++++- src/ejabberd_sm.erl | 8 +++++++- src/gen_iq_component.erl | 9 +++++++++ src/mod_muc_iq.erl | 8 +++++++- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/domain/mongoose_lazy_routing.erl b/src/domain/mongoose_lazy_routing.erl index af5c13ce13..03b4dde66b 100644 --- a/src/domain/mongoose_lazy_routing.erl +++ b/src/domain/mongoose_lazy_routing.erl @@ -387,7 +387,8 @@ check_that_domain_and_iq_match(IQ, Domain) -> register_iq({#iq_table_key{namespace = Namespace, component = Component}, IQHandler}, {Domain, _})-> - gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler). + gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler), + gen_iq_component:sync(Component). -spec unregister_iq(iq_record(), domain_record()) -> ok. unregister_iq({#iq_table_key{namespace = Namespace, diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index 06799ccd71..23758cf3ae 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -50,7 +50,8 @@ unregister_host/1, unregister_iq_response_handler/2, refresh_iq_handlers/0, - bounce_resource_packet/4 + bounce_resource_packet/4, + sync/0 ]). %% Hooks callbacks @@ -202,6 +203,10 @@ register_iq_handler(Domain, XMLNS, IQHandler) -> ejabberd_local ! {register_iq_handler, Domain, XMLNS, IQHandler}, ok. +-spec sync() -> ok. +sync() -> + gen_server:call(ejabberd_local, sync). + -spec unregister_iq_response_handler(_Host :: jid:server(), ID :: id()) -> 'ok'. unregister_iq_response_handler(_Host, ID) -> @@ -293,6 +298,8 @@ handle_call({register_host, Host}, _From, State) -> do_register_host(Host), mongoose_metrics:init_predefined_host_metrics(Host), {reply, ok, State}; +handle_call(sync, _From, State) -> + {reply, ok, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index a77d0fa5f5..62339a31b6 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -63,7 +63,8 @@ get_user_present_resources/1, get_raw_sessions/1, is_offline/1, - get_user_present_pids/2 + get_user_present_pids/2, + sync/0 ]). %% Hook handlers @@ -433,6 +434,9 @@ register_iq_handler(Host, XMLNS, IQHandler) -> ejabberd_sm ! {register_iq_handler, Host, XMLNS, IQHandler}, ok. +-spec sync() -> ok. +sync() -> + gen_server:call(ejabberd_sm, sync). unregister_iq_handler(Host, XMLNS) -> ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}, @@ -503,6 +507,8 @@ handle_call({node_cleanup, Node}, _From, State) -> cleanup_node => Node, duration => erlang:round(TimeDiff / 1000)}), {reply, ok, State}; +handle_call(sync, _From, State) -> + {reply, ok, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. diff --git a/src/gen_iq_component.erl b/src/gen_iq_component.erl index d335f0f76b..d83e7ad020 100644 --- a/src/gen_iq_component.erl +++ b/src/gen_iq_component.erl @@ -3,6 +3,7 @@ %% API -export([register_iq_handler/4, unregister_iq_handler/3, + sync/1, stop_iq_handler/1, handle/5]). @@ -10,6 +11,10 @@ IQHandler :: mongoose_iq_handler:t()) -> ok. -callback unregister_iq_handler(Domain :: jid:server(), Namespace :: binary()) -> ok. + +%% this callback can be used to ensure that all of the register/unregister requests +%% are done if they processed asynchronously by the component. +-callback sync() -> ok. %%==================================================================== %% API %%==================================================================== @@ -26,6 +31,10 @@ register_iq_handler(Component, Domain, Namespace, IQHandler) -> unregister_iq_handler(Component, Domain, Namespace) -> Component:unregister_iq_handler(Domain, Namespace). +-spec sync(Component :: module()) -> ok. +sync(Component) -> + Component:sync(). + -spec stop_iq_handler(IQHandler :: mongoose_iq_handler:t()) -> any(). stop_iq_handler(IQHandler) -> %% TODO: this function is required only for correct implementation of the legacy diff --git a/src/mod_muc_iq.erl b/src/mod_muc_iq.erl index 1744e5b509..199872a06d 100644 --- a/src/mod_muc_iq.erl +++ b/src/mod_muc_iq.erl @@ -5,7 +5,8 @@ -export([start_link/0, process_iq/5, register_iq_handler/3, - unregister_iq_handler/2]). + unregister_iq_handler/2, + sync/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -51,6 +52,9 @@ register_iq_handler(Host, XMLNS, IQHandler) -> gen_server:cast(srv_name(), {register_iq_handler, Host, XMLNS, IQHandler}). +-spec sync() -> ok. +sync() -> + gen_server:call(srv_name(), sync). -spec unregister_iq_handler(jid:server(), binary()) -> ok. unregister_iq_handler(Host, XMLNS) -> @@ -81,6 +85,8 @@ init([]) -> %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- +handle_call(sync, _From, State) -> + {reply, ok, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. From 51f5168acec9a4048c31fbe452fb4cc96d98cf98 Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Tue, 18 May 2021 13:27:19 +0200 Subject: [PATCH 10/13] cleaning up dynamic_domains_pm_SUITE:test_routing/1 test case --- big_tests/tests/dynamic_domains_pm_SUITE.erl | 22 +------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/big_tests/tests/dynamic_domains_pm_SUITE.erl b/big_tests/tests/dynamic_domains_pm_SUITE.erl index 29ac215aa7..87aa918918 100644 --- a/big_tests/tests/dynamic_domains_pm_SUITE.erl +++ b/big_tests/tests/dynamic_domains_pm_SUITE.erl @@ -88,10 +88,6 @@ test_routing(Config) -> [escalus:send(Alice, escalus_stanza:chat_to(Subdomain, <<"OH, HAI!">>)) || Subdomain <- Subdomains1], rpc(mim(), meck, wait, [2, mod_dynamic_domains_test, process_packet, 5, 200]), - - ct:pal("!!! ~p~n", [rpc(mim(), meck, history, [mod_dynamic_domains_test])]), - dump_ets_tables([route, local_iqtable]), - rpc(mim(), meck, reset, [mod_dynamic_domains_test]), QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []), @@ -99,7 +95,6 @@ test_routing(Config) -> IQ = escalus_stanza:iq(Subdomain, <<"get">>, [QueryEl]), escalus:send(Alice, IQ) end || Subdomain <- Subdomains1], - %% check that all the IQs to any of Subdomains1 landed at process_packet/5 %% and no stanzas received in response rpc(mim(), meck, wait, [2, mod_dynamic_domains_test, process_packet, 5, 200]), @@ -119,7 +114,6 @@ test_routing(Config) -> Pids = [Pid||{Pid,{_,process_iq,_},_}<-History], [_] = (lists:usort(Pids)), - Subdomains2 = [<<"subdomain2.", Domain/binary>> || Domain <- ?DOMAINS], [begin IQ = escalus_stanza:iq(Subomain, <<"get">>, [QueryEl]), @@ -129,21 +123,7 @@ test_routing(Config) -> 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]), rpc(mim(), meck, unload, [mod_dynamic_domains_test]), - - [Domain | _] = ?DOMAINS, - remove_domains(?TEST_NODES, [Domain]), - timer:sleep(1000), - ct:pal("after removing domain ~p~n",[Domain]), - dump_ets_tables([route, local_iqtable]), - insert_domains(?TEST_NODES, [Domain]), - timer:sleep(1000), - ct:pal("after inserting domain ~p~n",[Domain]), - dump_ets_tables([route, local_iqtable]), - - dynamic_modules:stop(?HOST_TYPE, mod_dynamic_domains_test), - timer:sleep(1000), - ct:pal("after stopping the module~n"), - dump_ets_tables([route, local_iqtable]) + dynamic_modules:stop(?HOST_TYPE, mod_dynamic_domains_test) end, escalus:story(Config, [{alice3, 1}], StoryFn). From b48b6c2584492c129de1bfd8e9e40bc618115837 Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Tue, 18 May 2021 14:02:14 +0200 Subject: [PATCH 11/13] removing dependency between mongoose_router_localdomain and mongoose_subhosts --- src/http_upload/mod_http_upload.erl | 2 ++ src/mongoose_router_localdomain.erl | 15 +-------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/http_upload/mod_http_upload.erl b/src/http_upload/mod_http_upload.erl index 513d47da1b..7ab621fbdf 100644 --- a/src/http_upload/mod_http_upload.erl +++ b/src/http_upload/mod_http_upload.erl @@ -55,6 +55,7 @@ start(Host, Opts) -> SubHost = subhost(Host), mod_disco:register_subhost(Host, SubHost), mongoose_subhosts:register(Host, SubHost), + ejabberd_router:register_route(SubHost, mongoose_packet_handler:new(ejabberd_local)), ejabberd_hooks:add(disco_local_features, SubHost, ?MODULE, get_disco_features, 90), ejabberd_hooks:add(disco_local_identity, SubHost, ?MODULE, get_disco_identity, 90), ejabberd_hooks:add(disco_info, SubHost, ?MODULE, get_disco_info, 90), @@ -72,6 +73,7 @@ stop(Host) -> ejabberd_hooks:delete(disco_info, SubHost, ?MODULE, get_disco_info, 90), ejabberd_hooks:delete(disco_local_identity, SubHost, ?MODULE, get_disco_identity, 90), ejabberd_hooks:delete(disco_local_features, SubHost, ?MODULE, get_disco_features, 90), + ejabberd_router:unregister_route(SubHost), mongoose_subhosts:unregister(SubHost), mod_disco:unregister_subhost(Host, SubHost). diff --git a/src/mongoose_router_localdomain.erl b/src/mongoose_router_localdomain.erl index f7cfdacd88..51027cd209 100644 --- a/src/mongoose_router_localdomain.erl +++ b/src/mongoose_router_localdomain.erl @@ -22,20 +22,7 @@ filter(From, To, Acc, Packet) -> route(From, To, Acc, Packet) -> LDstDomain = To#jid.lserver, - case route_to_host(From, To, Acc, Packet, LDstDomain) of - done -> done; - Result -> - %% TODO: get rid of this ugly case-of construction - %% subdomain must be registered in the 'route' table. - %% Also this hack may not work if host is not added - %% yet, cause now all the hosts (including statically - %% configured hosts) are added in a lazy manner (see - %% mongoose_router_dynamic_domains module). - case mongoose_subhosts:get_host(LDstDomain) of - {ok, Host} -> route_to_host(From, To, Acc, Packet, Host); - undefined -> Result - end - end. + route_to_host(From, To, Acc, Packet, LDstDomain). route_to_host(From, To, Acc, Packet, Host) -> case mnesia:dirty_read(route, Host) of From 1ab85df4bf75bb67c6cf9232de049ff39f9baa76 Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Wed, 19 May 2021 17:53:04 +0200 Subject: [PATCH 12/13] changes according to the review comments --- big_tests/tests/dynamic_domains_pm_SUITE.erl | 9 ++--- src/domain/mongoose_domain_core.erl | 2 +- src/domain/mongoose_lazy_routing.erl | 40 +++++++++++++++----- src/gen_iq_handler.erl | 12 +++--- src/mod_dynamic_domains_test.erl | 15 ++++---- src/mod_muc_iq.erl | 2 +- src/mongoose_iq_handler.erl | 36 +++++++++--------- src/mongoose_packet_handler.erl | 4 +- src/mongoose_router_dynamic_domains.erl | 2 +- src/mongoose_router_localdomain.erl | 2 +- 10 files changed, 70 insertions(+), 54 deletions(-) diff --git a/big_tests/tests/dynamic_domains_pm_SUITE.erl b/big_tests/tests/dynamic_domains_pm_SUITE.erl index 87aa918918..a8a28ea8b5 100644 --- a/big_tests/tests/dynamic_domains_pm_SUITE.erl +++ b/big_tests/tests/dynamic_domains_pm_SUITE.erl @@ -5,8 +5,8 @@ %% API -compile(export_all). -import(distributed_helper, [mim/0, mim2/0, rpc/4, -require_rpc_nodes/1, -subhost_pattern/1]). + require_rpc_nodes/1, + subhost_pattern/1]). -define(TEST_NODES, [mim() | ?CLUSTER_NODES]). -define(CLUSTER_NODES, [mim2()]). @@ -111,7 +111,7 @@ test_routing(Config) -> %% check that process_iq/5 is called from one and the same worker process History = rpc(mim(), meck, history, [mod_dynamic_domains_test]), rpc(mim(), meck, reset, [mod_dynamic_domains_test]), - Pids = [Pid||{Pid,{_,process_iq,_},_}<-History], + Pids = [Pid || {Pid, {_, process_iq, _}, _} <- History], [_] = (lists:usort(Pids)), Subdomains2 = [<<"subdomain2.", Domain/binary>> || Domain <- ?DOMAINS], @@ -143,6 +143,3 @@ uncluster_nodes([], Config) -> Config; uncluster_nodes([Node | T], Config) -> NewConfig = distributed_helper:remove_node_from_cluster(Node, Config), cluster_nodes(T, NewConfig). - -dump_ets_tables(Tables) -> - [ct:pal("!!! ~p = ~p~n", [T, rpc(mim(), ets, tab2list, [T])]) || T <- Tables]. diff --git a/src/domain/mongoose_domain_core.erl b/src/domain/mongoose_domain_core.erl index fbeaa5cc03..06747e1570 100644 --- a/src/domain/mongoose_domain_core.erl +++ b/src/domain/mongoose_domain_core.erl @@ -202,8 +202,8 @@ handle_delete(Domain) -> ok; [{Domain, HostType, _Source}] -> ets:delete(?TABLE, Domain), - mongoose_subdomain_core:remove_domain(HostType, Domain), mongoose_lazy_routing:maybe_remove_domain(HostType, Domain), + mongoose_subdomain_core:remove_domain(HostType, Domain), ok end. diff --git a/src/domain/mongoose_lazy_routing.erl b/src/domain/mongoose_lazy_routing.erl index 03b4dde66b..e690908fd4 100644 --- a/src/domain/mongoose_lazy_routing.erl +++ b/src/domain/mongoose_lazy_routing.erl @@ -1,4 +1,19 @@ %% Generally, you should not call anything from this module directly. +%% +%% This module works together with mongoose_router_dynamic_domains. +%% The lazy registration approach helps to speed up initialisation +%% of the new node in a cluster with a large number of dynamically +%% configured domains. +%% +%% In addition to dynamic domains/subdomains tracking, this module +%% is also responsible for the lazy IQ handlers registration for the +%% added domains/subdomains. +%% +%% while registration is done in a lazy manner, removal of domains, +%% subdomain and corresponding IQ handlers is triggered immediately +%% once domain or subdomain deleted from mongoose_subdomain_core or +%% mongoose_domain_core ETS table. + -module(mongoose_lazy_routing). -behaviour(gen_server). @@ -41,8 +56,8 @@ -type host_type() :: mongooseim:host_type(). -type domain() :: mongooseim:domain_name(). --type iq_record() :: {#iq_table_key{}, mongoose_iq_handler:t()}. --type domain_record() :: {domain(), host_type() | {host_type(), subdomain_pattern()}}. +-type iq_entry() :: {#iq_table_key{}, mongoose_iq_handler:t()}. +-type domain_entry() :: {domain(), host_type() | {host_type(), subdomain_pattern()}}. %%-------------------------------------------------------------------- @@ -77,8 +92,13 @@ start_link() -> -spec maybe_add_domain_or_subdomain(mongooseim:domain()) -> boolean(). maybe_add_domain_or_subdomain(Domain) -> - %% it can be domain or subdomain name - gen_server:call(?MODULE, {maybe_add_domain_or_subdomain, Domain}). + %% don't run gen_server:call/2 if this domain name is unknown. + case mongoose_domain_api:get_host_type(Domain) of + {ok, _} -> + gen_server:call(?MODULE, {maybe_add_domain_or_subdomain, Domain}); + {error, not_found} -> + false + end. -spec maybe_remove_domain(mongooseim:host_type(), mongooseim:domain()) -> ok. maybe_remove_domain(HostType, Domain) -> @@ -347,23 +367,23 @@ handle_maybe_remove_subdomain(#{subdomain := Subdomain, host_type := HostType, _ -> ok end. --spec get_iqs(#iq_table_key{}) -> [iq_record()]. +-spec get_iqs(#iq_table_key{}) -> [iq_entry()]. get_iqs(KeyMatchPattern) -> ets:match_object(?IQ_TABLE, {KeyMatchPattern, '_'}). --spec register_iqs([iq_record()], [domain_record()]) -> ok. +-spec register_iqs([iq_entry()], [domain_entry()]) -> ok. register_iqs(IQList, DomainList) -> [register_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList, check_that_domain_and_iq_match(IQ, Domain)], ok. --spec unregister_iqs([iq_record()], [domain_record()]) -> ok. +-spec unregister_iqs([iq_entry()], [domain_entry()]) -> ok. unregister_iqs(IQList, DomainList) -> [unregister_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList, check_that_domain_and_iq_match(IQ, Domain)], ok. --spec check_that_domain_and_iq_match(iq_record(), domain_record()) -> boolean(). +-spec check_that_domain_and_iq_match(iq_entry(), domain_entry()) -> boolean(). check_that_domain_and_iq_match({#iq_table_key{host_type = HostType, subdomain_pattern = undefined, namespace = Namespace, @@ -383,14 +403,14 @@ check_that_domain_and_iq_match(IQ, Domain) -> ?LOG_ERROR(#{what => domain_and_iq_doesnt_match, domain => Domain, iq => IQ}), false. --spec register_iq(iq_record(), domain_record()) -> ok. +-spec register_iq(iq_entry(), domain_entry()) -> ok. register_iq({#iq_table_key{namespace = Namespace, component = Component}, IQHandler}, {Domain, _})-> gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler), gen_iq_component:sync(Component). --spec unregister_iq(iq_record(), domain_record()) -> ok. +-spec unregister_iq(iq_entry(), domain_entry()) -> ok. unregister_iq({#iq_table_key{namespace = Namespace, component = Component}, _}, {Domain, _}) -> diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index a2962d4927..cd2f3a8146 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -44,7 +44,7 @@ %% Old API. Get rid of it once all the modules adopted. %%==================================================================== -spec add_iq_handler(Component :: module(), - Domain :: jid:server(), + Domain :: jid:lserver(), Namespace :: binary(), Module :: atom(), Function :: atom(), @@ -55,9 +55,8 @@ add_iq_handler(Component, Domain, Namespace, Module, Function, ExecutionType) -> IQHandler = mongoose_iq_handler:new(IQHandlerFn, Extra, ExecutionType), gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler). - -spec remove_iq_handler(Component :: module(), - Domain :: jid:server(), + Domain :: jid:lserver(), Namespace :: binary()) -> any(). remove_iq_handler(Component, Domain, Namespace) -> gen_iq_component:unregister_iq_handler(Component, Domain, Namespace). @@ -69,14 +68,13 @@ check_type(one_queue) -> one_queue; check_type({queues, Int}) when is_integer(Int), Int > 0 -> {queues, Int}. - %%==================================================================== %% New API. %%==================================================================== -spec add_iq_handler_for_domain(HostType :: mongooseim:host_type(), Namespace :: binary(), Component :: module(), - IQHandlerFn :: mongoose_iq_handler:iq_handler(), + IQHandlerFn :: mongoose_iq_handler:handler_fn(), Extra :: map(), ExecutionType :: execution_type()) -> ok | {error, atom()}. @@ -92,7 +90,7 @@ add_iq_handler_for_domain(HostType, Namespace, Component, IQHandlerFn, SubdomainPattern :: subdomain_pattern(), Namespace :: binary(), Component :: module(), - IQHandlerFn :: mongoose_iq_handler:iq_handler(), + IQHandlerFn :: mongoose_iq_handler:handler_fn(), Extra :: map(), ExecutionType :: execution_type()) -> ok | {error, atom()}. @@ -133,7 +131,7 @@ remove_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace, Component %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- --spec make_iq_handler_fn(module(), atom()) -> mongoose_iq_handler:iq_handler(). +-spec make_iq_handler_fn(module(), atom()) -> mongoose_iq_handler:handler_fn(). make_iq_handler_fn(Module, Function) -> %TODO: remove this function with removal of the old API fun(Acc, From, To, IQ, _Extra) -> diff --git a/src/mod_dynamic_domains_test.erl b/src/mod_dynamic_domains_test.erl index de100eae26..d53af1254f 100644 --- a/src/mod_dynamic_domains_test.erl +++ b/src/mod_dynamic_domains_test.erl @@ -33,21 +33,22 @@ start(HostType, Opts) -> Namespace = gen_mod:get_opt(namespace, Opts), %% if we need to intercept traffic to some subdomain, we can create custom packet - %% handler and register it. not that IQ will be delivered to this packet handler - %% as well + %% handler and register it. note that IQs will be delivered to this packet handler + %% as well, even if an IQ handler is registered for the same subdomain. IQ handler + %% doesn't work at all if the IQ packet is not routed to the corresponding component. SubdomainPattern1 = gen_mod:get_opt(host1, Opts), Handler1 = mongoose_packet_handler:new(?MODULE), mongoose_domain_api:register_subdomain(HostType, SubdomainPattern1, Handler1), - %% the call below makes no sense, it's added for demo/testing purposes only - %% all the IQs for SubdomainPattern1 will go to process_packet/5 function + %% the call below is added for the demo & testing purposes, all the IQs sent for + %% SubdomainPattern1 will go only to the process_packet/5 function. gen_iq_handler:add_iq_handler_for_subdomain(HostType, SubdomainPattern1, Namespace, ejabberd_local, fun ?MODULE:process_iq/5, #{handler_type => subdomain}, one_queue), - %% if we want to just process IQ sent to some subdomain, and we don't care about - %% all other messages, then we can use `ejabberd_local` packet handler for such + %% If we want to just process IQs sent to some subdomain, and we don't care about + %% any other messages, then we can use `ejabberd_local` packet handler for such %% subdomain and register IQ handler in the similar way as we do for domains. SubdomainPattern2 = gen_mod:get_opt(host2, Opts), Handler2 = mongoose_packet_handler:new(ejabberd_local), @@ -95,4 +96,4 @@ process_packet(_Acc, _From, _To, _El, _Extra) -> IQResp :: ignore | jlib:iq()}. process_iq(Acc, _From, _To, IQ, _Extra) -> %% reply with empty result IQ stanza - {Acc, IQ#iq{type = result, sub_el = []}}. \ No newline at end of file + {Acc, IQ#iq{type = result, sub_el = []}}. diff --git a/src/mod_muc_iq.erl b/src/mod_muc_iq.erl index 199872a06d..8caab0feee 100644 --- a/src/mod_muc_iq.erl +++ b/src/mod_muc_iq.erl @@ -104,7 +104,7 @@ handle_cast({register_iq_handler, Host, XMLNS, IQHandler}, State) -> handle_cast({unregister_iq_handler, Host, XMLNS}, State) -> case ets:lookup(tbl_name(), {XMLNS, Host}) of [{_, IQHandler}] -> - gen_iq_componentr:stop_iq_handler(IQHandler), + gen_iq_component:stop_iq_handler(IQHandler), ets:delete(tbl_name(), {XMLNS, Host}); _ -> ok diff --git a/src/mongoose_iq_handler.erl b/src/mongoose_iq_handler.erl index 6113b75388..5a30ae5ab8 100644 --- a/src/mongoose_iq_handler.erl +++ b/src/mongoose_iq_handler.erl @@ -6,24 +6,24 @@ %% Types %%---------------------------------------------------------------------- --record(iq_handler, {iq_handler_fn :: iq_handler(), +-record(iq_handler, {handler_fn :: handler_fn(), extra :: map(), execution_method :: execution_method()}). -type t() :: #iq_handler{}. --type iq_handler() :: fun((Acc :: mongoose_acc:t(), - From :: jid:jid(), - To :: jid:jid(), - IQ :: jlib:iq(), - Extra :: map()) -> {NewAcc :: mongoose_acc:t(), - IQResp :: ignore | jlib:iq()}). +-type handler_fn() :: fun((Acc :: mongoose_acc:t(), + From :: jid:jid(), + To :: jid:jid(), + IQ :: jlib:iq(), + Extra :: map()) -> {NewAcc :: mongoose_acc:t(), + IQResp :: ignore | jlib:iq()}). -type execution_method() :: no_queue | parallel | {one_queue, pid()} | {queues, [pid()]}. -type execution_type() :: no_queue | parallel | one_queue | {queues, pos_integer()}. --export_type([t/0, iq_handler/0, execution_type/0]). +-export_type([t/0, handler_fn/0, execution_type/0]). %%---------------------------------------------------------------------- %% API @@ -37,12 +37,12 @@ %% Getters -export([module/1, extra/1]). --spec new(IQHandlerFn :: iq_handler(), +-spec new(IQHandlerFn :: handler_fn(), Extra :: map(), ExecutionType :: execution_type()) -> t(). new(IQHandlerFn, Extra, ExecutionType) -> ExecutionMethod = execution_method(ExecutionType), - #iq_handler{iq_handler_fn = IQHandlerFn, extra = Extra, + #iq_handler{handler_fn = IQHandlerFn, extra = Extra, execution_method = ExecutionMethod}. -spec delete(IQHandler :: t()) -> ok. @@ -56,14 +56,14 @@ delete(#iq_handler{execution_method = ExecutionMethod}) -> end. -spec module(t()) -> module(). -module(#iq_handler{iq_handler_fn = Fn}) -> +module(#iq_handler{handler_fn = Fn}) -> {module, Mod} = erlang:fun_info(Fn, module), Mod. -spec process_iq(Handler :: t(), Acc :: mongoose_acc:t(), - From ::jid:jid(), - To ::jid:jid(), + From :: jid:jid(), + To :: jid:jid(), IQ :: jlib:iq()) -> mongoose_acc:t(). process_iq(#iq_handler{execution_method = ExecutionMethod} = Handler, Acc, From, To, IQ) -> @@ -90,15 +90,15 @@ extra(#iq_handler{ extra = Extra }) -> add_extra(#iq_handler{ extra = OldExtra } = Handler, Extra) -> %% KV pairs from the OldExtra map will remain unchanged, only %% the new keys from Extra map will be added to the NewExtra map - NewExtra = maps:merge(Extra,OldExtra), - Handler#iq_handler{extra=NewExtra}. + NewExtra = maps:merge(Extra, OldExtra), + Handler#iq_handler{extra = NewExtra}. -spec execute_handler(Handler :: t(), Acc :: mongoose_acc:t(), - From ::jid:jid(), - To ::jid:jid(), + From :: jid:jid(), + To :: jid:jid(), IQ :: jlib:iq()) -> mongoose_acc:t(). -execute_handler(#iq_handler{iq_handler_fn = IQHandlerFn, extra = Extra}, +execute_handler(#iq_handler{handler_fn = IQHandlerFn, extra = Extra}, Acc, From, To, IQ) -> try IQHandlerFn(Acc, From, To, IQ, Extra) of {Acc1, ignore} -> diff --git a/src/mongoose_packet_handler.erl b/src/mongoose_packet_handler.erl index 53cb14c0bf..b1bc5846e0 100644 --- a/src/mongoose_packet_handler.erl +++ b/src/mongoose_packet_handler.erl @@ -63,5 +63,5 @@ extra(#packet_handler{ extra = Extra }) -> add_extra(#packet_handler{ extra = OldExtra } = Handler, Extra) -> %% KV pairs from the OldExtra map will remain unchanged, only %% the new keys from Extra map will be added to the NewExtra map - NewExtra = maps:merge(Extra,OldExtra), - Handler#packet_handler{extra=NewExtra}. + NewExtra = maps:merge(Extra, OldExtra), + Handler#packet_handler{extra = NewExtra}. diff --git a/src/mongoose_router_dynamic_domains.erl b/src/mongoose_router_dynamic_domains.erl index f68bcfc5e6..26373d69e8 100644 --- a/src/mongoose_router_dynamic_domains.erl +++ b/src/mongoose_router_dynamic_domains.erl @@ -2,7 +2,7 @@ %%% @doc %%% this router should be tried in the very end, but before s2s. %%% it checks if destination domain is configured dynamically, -%%% if it is so - the router adds domain fo the routing table, +%%% if it is so - the router adds domain to the routing table, %%% and retries local routing. %%% %%% this ensures lazy dynamic domains registration in the routing diff --git a/src/mongoose_router_localdomain.erl b/src/mongoose_router_localdomain.erl index 51027cd209..d11cfb5ff1 100644 --- a/src/mongoose_router_localdomain.erl +++ b/src/mongoose_router_localdomain.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- %%% @doc %%% Part of a routing chain: searches for a route registered for the domain, -%%% forwards the messge there, or passes on. +%%% forwards the message there, or passes on. %%% @end %%%------------------------------------------------------------------- -module(mongoose_router_localdomain). From ca2fcc619e01bb6ce0e9f8fd8ca531c54afbad07 Mon Sep 17 00:00:00 2001 From: DenysGonchar Date: Wed, 19 May 2021 19:38:57 +0200 Subject: [PATCH 13/13] reworking dynamic_domains_pm_SUITE and renaming it into dynamic_domains_SUITE --- big_tests/default.spec | 1 + big_tests/test.config | 4 +- big_tests/tests/dynamic_domains_SUITE.erl | 216 +++++++++++++++++++ big_tests/tests/dynamic_domains_pm_SUITE.erl | 145 ------------- 4 files changed, 219 insertions(+), 147 deletions(-) create mode 100644 big_tests/tests/dynamic_domains_SUITE.erl delete mode 100644 big_tests/tests/dynamic_domains_pm_SUITE.erl diff --git a/big_tests/default.spec b/big_tests/default.spec index 4bb8b9f247..2dae7dda6e 100644 --- a/big_tests/default.spec +++ b/big_tests/default.spec @@ -86,6 +86,7 @@ {suites, "tests", domain_isolation_SUITE}. {suites, "tests", domain_removal_SUITE}. {suites, "tests", mam_send_message_SUITE}. +{suites, "tests", dynamic_domains_SUITE}. {config, ["test.config"]}. {logdir, "ct_report"}. diff --git a/big_tests/test.config b/big_tests/test.config index abcffadaa8..afe18e6930 100644 --- a/big_tests/test.config +++ b/big_tests/test.config @@ -192,12 +192,12 @@ {starttls, required}, {tls_module, fast_tls} ]}, - {alice3, [ %% used in dynamic_domains_pm_SUITE + {alice3, [ %% used in dynamic_domains_SUITE {username, <<"alice">>}, {server, <<"example.com">>}, {host, <<"localhost">>}, {password, <<"makota2">>}]}, - {bob3, [ + {bob3, [ %% used in dynamic_domains_SUITE {username, <<"bob">>}, {server, <<"example.org">>}, {host, <<"localhost">>}, diff --git a/big_tests/tests/dynamic_domains_SUITE.erl b/big_tests/tests/dynamic_domains_SUITE.erl new file mode 100644 index 0000000000..81accb21ce --- /dev/null +++ b/big_tests/tests/dynamic_domains_SUITE.erl @@ -0,0 +1,216 @@ +-module(dynamic_domains_SUITE). + +-include_lib("exml/include/exml.hrl"). + +%% API +-compile(export_all). +-import(distributed_helper, [mim/0, mim2/0, rpc/4, + require_rpc_nodes/1, + subhost_pattern/1]). + +-define(TEST_NODES, [mim() | ?CLUSTER_NODES]). +-define(CLUSTER_NODES, [mim2()]). +-define(DOMAINS, [<<"example.com">>, <<"example.org">>]). +-define(HOST_TYPE, <<"test type">>). %% preconfigured in the toml file + +suite() -> + require_rpc_nodes([mim, mim2]). + +all() -> + [can_authenticate, + pm_messages, + disconnected_on_domain_disabling, + auth_domain_removal_is_triggered_on_hook, + {group, with_mod_dynamic_domains_test}]. + +groups() -> + [{with_mod_dynamic_domains_test, [], [packet_handling_for_subdomain, + iq_handling_for_subdomain, + iq_handling_for_domain]}]. + +init_per_suite(Config0) -> + Config = cluster_nodes(?CLUSTER_NODES, Config0), + insert_domains(?TEST_NODES, ?DOMAINS), + escalus:init_per_suite(Config). + +end_per_suite(Config0) -> + Config = escalus:end_per_suite(Config0), + remove_domains(?TEST_NODES, ?DOMAINS), + uncluster_nodes(?CLUSTER_NODES, Config). + +init_per_group(with_mod_dynamic_domains_test, Config) -> + MockedModules = [mod_dynamic_domains_test, ejabberd_router], + [ok = rpc(mim(), meck, new, [Module, [passthrough, no_link]]) + || Module <- MockedModules], + dynamic_modules:start(?HOST_TYPE, mod_dynamic_domains_test, + [{host1, subhost_pattern("subdomain1.@HOST@")}, + {host2, subhost_pattern("subdomain2.@HOST@")}, + {namespace, <<"dummy.namespace">>}]), + [{reset_meck, MockedModules} | Config]; +init_per_group(_, Config) -> + Config. + +end_per_group(with_mod_dynamic_domains_test, Config) -> + dynamic_modules:stop(?HOST_TYPE, mod_dynamic_domains_test), + rpc(mim(), meck, unload, []), + Config; +end_per_group(_, Config) -> + Config. + +init_per_testcase(CN, Config) -> + Modules = proplists:get_value(reset_meck, Config, []), + [rpc(mim(), meck, reset, [M]) || M <- Modules], + escalus:init_per_testcase(CN, Config). + +end_per_testcase(CN, Config) -> + escalus:end_per_testcase(CN, Config). + +can_authenticate(Config) -> + UserSpecA = escalus_users:get_userspec(Config, alice3), + {ok, ClientA, _} = escalus_connection:start(UserSpecA), + UserSpecB = escalus_users:get_userspec(Config, bob3), + {ok, ClientB, _} = escalus_connection:start(UserSpecB), + escalus_connection:stop(ClientA), + escalus_connection:stop(ClientB). + +pm_messages(Config) -> + StoryFn = + fun(Alice, Bob) -> + escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), + escalus:assert(is_chat_message, [<<"OH, HAI!">>], escalus:wait_for_stanza(Bob)), + escalus:send(Bob, escalus_stanza:chat_to(Alice, <<"Hello there!">>)), + escalus:assert(is_chat_message, [<<"Hello there!">>], escalus:wait_for_stanza(Alice)) + end, + escalus:story(Config, [{alice3, 1}, {bob3, 1}], StoryFn). + +disconnected_on_domain_disabling(Config) -> + StoryFn = + fun(Alice, Bob) -> + remove_domains(?TEST_NODES, ?DOMAINS), + escalus_connection:wait_for_close(Alice, timer:seconds(5)), + escalus_connection:wait_for_close(Bob, timer:seconds(5)), + insert_domains(?TEST_NODES, ?DOMAINS) + end, + escalus:story(Config, [{alice3, 1}, {bob3, 1}], StoryFn). + +auth_domain_removal_is_triggered_on_hook(_Config) -> + ok = rpc(mim(), meck, new, [ejabberd_auth_dummy, [passthrough, no_link]]), + Params = [?HOST_TYPE, <<"dummy.domain.name">>], + rpc(mim(), mongoose_hooks, remove_domain, Params), + 1 = rpc(mim(), meck, num_calls, [ejabberd_auth_dummy, remove_domain, Params]), + rpc(mim(), meck, unload, [ejabberd_auth_dummy]). + +packet_handling_for_subdomain(Config) -> + StoryFn = + fun(Alice) -> + NewDomain = <<"example.test">>, + insert_domains(?TEST_NODES, [NewDomain]), + Domains = [NewDomain | ?DOMAINS], + Subdomains = [<<"subdomain1.", Domain/binary>> || Domain <- Domains], + [NewSubdomain | _] = Subdomains, + [escalus:send(Alice, escalus_stanza:chat_to(Subdomain, <<"OH, HAI!">>)) + || Subdomain <- Subdomains], + rpc(mim(), meck, wait, [3, mod_dynamic_domains_test, process_packet, 5, 500]), + rpc(mim(), meck, reset, [mod_dynamic_domains_test]), + + QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []), + [begin + IQ = escalus_stanza:iq(Subdomain, <<"get">>, [QueryEl]), + escalus:send(Alice, IQ) + end || Subdomain <- Subdomains], + %% check that all the IQs to any of Subdomains1 landed at process_packet/5 + %% and no stanzas received in response + rpc(mim(), meck, wait, [3, mod_dynamic_domains_test, process_packet, 5, 500]), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), + [] = escalus:wait_for_stanzas(Alice, 5, 500), + rpc(mim(), meck, reset, [mod_dynamic_domains_test]), + + %% check that subdomain is not served after the parent domain removal + remove_domains(?TEST_NODES, [NewDomain]), + rpc(mim(), meck, wait, [ejabberd_router, unregister_route, [NewSubdomain], 500]), + IQ = escalus_stanza:iq(NewSubdomain, <<"get">>, [QueryEl]), + escalus:send(Alice, IQ), + Stanza = escalus:wait_for_stanza(Alice, 10000), + escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]) + end, + escalus:story(Config, [{alice3, 1}], StoryFn). + +iq_handling_for_domain(Config) -> + StoryFn = + fun(Alice) -> + NewDomain = <<"example.test">>, + insert_domains(?TEST_NODES, [NewDomain]), + Domains = [NewDomain | ?DOMAINS], + QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []), + [begin + IQ = escalus_stanza:iq(Domain, <<"get">>, [QueryEl]), + escalus:send_iq_and_wait_for_result(Alice, IQ) + end || Domain <- Domains], + 3 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]), + %% check that process_iq/5 is called from one and the same worker process + History = rpc(mim(), meck, history, [mod_dynamic_domains_test]), + rpc(mim(), meck, reset, [mod_dynamic_domains_test]), + Pids = [Pid || {Pid, {_, process_iq, _}, _} <- History], + [_] = (lists:usort(Pids)), + + %% check that domain is not served removal + remove_domains(?TEST_NODES, [NewDomain]), + rpc(mim(), meck, wait, [ejabberd_router, unregister_route, [NewDomain], 500]), + IQ = escalus_stanza:iq(NewDomain, <<"get">>, [QueryEl]), + escalus:send(Alice, IQ), + Stanza = escalus:wait_for_stanza(Alice, 10000), + escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]) + end, + escalus:story(Config, [{alice3, 1}], StoryFn). + +iq_handling_for_subdomain(Config) -> + StoryFn = + fun(Alice) -> + NewDomain = <<"example.test">>, + insert_domains(?TEST_NODES, [NewDomain]), + Domains = [NewDomain | ?DOMAINS], + Subdomains = [<<"subdomain2.", Domain/binary>> || Domain <- Domains], + [NewSubdomain | _] = Subdomains, + QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []), + [begin + IQ = escalus_stanza:iq(Subdomain, <<"get">>, [QueryEl]), + escalus:send_iq_and_wait_for_result(Alice, IQ) + end || Subdomain <- Subdomains], + 3 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]), + %% check that process_iq/5 is called from one and the same worker process + History = rpc(mim(), meck, history, [mod_dynamic_domains_test]), + rpc(mim(), meck, reset, [mod_dynamic_domains_test]), + Pids = [Pid || {Pid, {_, process_iq, _}, _} <- History], + [_] = (lists:usort(Pids)), + + %% check that subdomain is not served after the parent domain removal + remove_domains(?TEST_NODES, [NewDomain]), + rpc(mim(), meck, wait, [ejabberd_router, unregister_route, [NewSubdomain], 500]), + IQ = escalus_stanza:iq(NewSubdomain, <<"get">>, [QueryEl]), + escalus:send(Alice, IQ), + Stanza = escalus:wait_for_stanza(Alice, 10000), + escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza), + 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]) + end, + escalus:story(Config, [{alice3, 1}], StoryFn). + +%% helper functions +insert_domains(Nodes, Domains) -> + [domain_helper:insert_domain(Node, Domain) || Node <- Nodes, Domain <- Domains]. + +remove_domains(Nodes, Domains) -> + [domain_helper:delete_domain(Node, Domain) || Node <- Nodes, Domain <- Domains]. + +cluster_nodes([], Config) -> Config; +cluster_nodes([Node | T], Config) -> + NewConfig = distributed_helper:add_node_to_cluster(Node, Config), + cluster_nodes(T, NewConfig). + +uncluster_nodes([], Config) -> Config; +uncluster_nodes([Node | T], Config) -> + NewConfig = distributed_helper:remove_node_from_cluster(Node, Config), + cluster_nodes(T, NewConfig). diff --git a/big_tests/tests/dynamic_domains_pm_SUITE.erl b/big_tests/tests/dynamic_domains_pm_SUITE.erl deleted file mode 100644 index a8a28ea8b5..0000000000 --- a/big_tests/tests/dynamic_domains_pm_SUITE.erl +++ /dev/null @@ -1,145 +0,0 @@ --module(dynamic_domains_pm_SUITE). - --include_lib("exml/include/exml.hrl"). - -%% API --compile(export_all). --import(distributed_helper, [mim/0, mim2/0, rpc/4, - require_rpc_nodes/1, - subhost_pattern/1]). - --define(TEST_NODES, [mim() | ?CLUSTER_NODES]). --define(CLUSTER_NODES, [mim2()]). --define(DOMAINS, [<<"example.com">>, <<"example.org">>]). --define(HOST_TYPE, <<"test type">>). %% preconfigured in the toml file - -suite() -> - require_rpc_nodes([mim, mim2]). - -all() -> - [can_authenticate, - pm_messages, - disconnected_on_domain_disabling, - auth_domain_removal_is_triggered_on_hook, - test_routing]. - -init_per_suite(Config0) -> - Config = cluster_nodes(?CLUSTER_NODES, Config0), - insert_domains(?TEST_NODES, ?DOMAINS), - escalus:init_per_suite(Config). - -end_per_suite(Config0) -> - Config = escalus:end_per_suite(Config0), - remove_domains(?TEST_NODES, ?DOMAINS), - uncluster_nodes(?CLUSTER_NODES, Config). - -init_per_testcase(CN, Config) -> - escalus:init_per_testcase(CN, Config). - -end_per_testcase(CN, Config) -> - escalus:end_per_testcase(CN, Config). - -can_authenticate(Config) -> - UserSpecA = escalus_users:get_userspec(Config, alice3), - {ok, ClientA, _} = escalus_connection:start(UserSpecA), - UserSpecB = escalus_users:get_userspec(Config, bob3), - {ok, ClientB, _} = escalus_connection:start(UserSpecB), - escalus_connection:stop(ClientA), - escalus_connection:stop(ClientB). - -pm_messages(Config) -> - StoryFn = - fun(Alice, Bob) -> - escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), - escalus:assert(is_chat_message, [<<"OH, HAI!">>], escalus:wait_for_stanza(Bob)), - escalus:send(Bob, escalus_stanza:chat_to(Alice, <<"Hello there!">>)), - escalus:assert(is_chat_message, [<<"Hello there!">>], escalus:wait_for_stanza(Alice)) - end, - escalus:story(Config, [{alice3, 1}, {bob3, 1}], StoryFn). - -disconnected_on_domain_disabling(Config) -> - StoryFn = - fun(Alice, Bob) -> - remove_domains(?TEST_NODES, ?DOMAINS), - escalus_connection:wait_for_close(Alice, timer:seconds(5)), - escalus_connection:wait_for_close(Bob, timer:seconds(5)), - insert_domains(?TEST_NODES, ?DOMAINS) - end, - escalus:story(Config, [{alice3, 1}, {bob3, 1}], StoryFn). - -auth_domain_removal_is_triggered_on_hook(_Config) -> - ok = rpc(mim(), meck, new, [ejabberd_auth_dummy, [passthrough, no_link]]), - Params = [?HOST_TYPE, <<"dummy.domain.name">>], - rpc(mim(), mongoose_hooks, remove_domain, Params), - 1 = rpc(mim(), meck, num_calls, [ejabberd_auth_dummy, remove_domain, Params]), - rpc(mim(), meck, unload, [ejabberd_auth_dummy]). - -test_routing(Config) -> - StoryFn = - fun(Alice) -> - dynamic_modules:start(?HOST_TYPE, mod_dynamic_domains_test, - [{host1, subhost_pattern("subdomain1.@HOST@")}, - {host2, subhost_pattern("subdomain2.@HOST@")}, - {namespace, <<"dummy.namespace">>}]), - rpc(mim(), meck, unload, []), - ok = rpc(mim(), meck, new, [mod_dynamic_domains_test, - [passthrough, no_link]]), - Subdomains1 = [<<"subdomain1.", Domain/binary>> || Domain <- ?DOMAINS], - [escalus:send(Alice, escalus_stanza:chat_to(Subdomain, <<"OH, HAI!">>)) - || Subdomain <- Subdomains1], - rpc(mim(), meck, wait, [2, mod_dynamic_domains_test, process_packet, 5, 200]), - rpc(mim(), meck, reset, [mod_dynamic_domains_test]), - - QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []), - [begin - IQ = escalus_stanza:iq(Subdomain, <<"get">>, [QueryEl]), - escalus:send(Alice, IQ) - end || Subdomain <- Subdomains1], - %% check that all the IQs to any of Subdomains1 landed at process_packet/5 - %% and no stanzas received in response - rpc(mim(), meck, wait, [2, mod_dynamic_domains_test, process_packet, 5, 200]), - 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), - rpc(mim(), meck, reset, [mod_dynamic_domains_test]), - [] = escalus:wait_for_stanzas(Alice, 5, 1000), - - [begin - IQ = escalus_stanza:iq(Domain, <<"get">>, [QueryEl]), - escalus:send_iq_and_wait_for_result(Alice, IQ) - end || Domain <- ?DOMAINS], - 2 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), - 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]), - %% check that process_iq/5 is called from one and the same worker process - History = rpc(mim(), meck, history, [mod_dynamic_domains_test]), - rpc(mim(), meck, reset, [mod_dynamic_domains_test]), - Pids = [Pid || {Pid, {_, process_iq, _}, _} <- History], - [_] = (lists:usort(Pids)), - - Subdomains2 = [<<"subdomain2.", Domain/binary>> || Domain <- ?DOMAINS], - [begin - IQ = escalus_stanza:iq(Subomain, <<"get">>, [QueryEl]), - escalus:send_iq_and_wait_for_result(Alice, IQ) - end || Subomain <- Subdomains2], - 2 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]), - 0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]), - - rpc(mim(), meck, unload, [mod_dynamic_domains_test]), - dynamic_modules:stop(?HOST_TYPE, mod_dynamic_domains_test) - end, - escalus:story(Config, [{alice3, 1}], StoryFn). - -%% helper functions -insert_domains(Nodes, Domains) -> - [domain_helper:insert_domain(Node, Domain) || Node <- Nodes, Domain <- Domains]. - -remove_domains(Nodes, Domains) -> - [domain_helper:delete_domain(Node, Domain) || Node <- Nodes, Domain <- Domains]. - -cluster_nodes([], Config) -> Config; -cluster_nodes([Node | T], Config) -> - NewConfig = distributed_helper:add_node_to_cluster(Node, Config), - cluster_nodes(T, NewConfig). - -uncluster_nodes([], Config) -> Config; -uncluster_nodes([Node | T], Config) -> - NewConfig = distributed_helper:remove_node_from_cluster(Node, Config), - cluster_nodes(T, NewConfig).