Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components/dynamic domains #4450

Merged
merged 4 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions big_tests/dynamic_domains.config
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@
{server, <<"domain.example.com">>},
{host, <<"localhost">>},
{password, <<"nicniema">>}]},
{astrid, [
{username, <<"astrid">>},
{server, <<"sogndal">>},
{host, <<"localhost">>},
{password, <<"doctor">>}]},
{geralt, [
{username, <<"geralt">>},
{server, <<"domain.example.com">>},
Expand Down
1 change: 1 addition & 0 deletions big_tests/dynamic_domains.spec
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
%% to minimise impact on other tests
{suites, "tests", auth_methods_for_c2s_SUITE}.
{suites, "tests", cluster_commands_SUITE}.
{suites, "tests", component_SUITE}.
{suites, "tests", dynamic_domains_SUITE}.
{suites, "tests", graphql_server_SUITE}.
{suites, "tests", last_SUITE}.
Expand Down
225 changes: 115 additions & 110 deletions big_tests/tests/component_SUITE.erl

Large diffs are not rendered by default.

65 changes: 28 additions & 37 deletions big_tests/tests/component_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
disconnect_component/2,
disconnect_components/2,
connect_component_subdomain/1,
get_components/1, get_components/2, get_components/3,
spec/2,
common/1,
common/2,
name/1
get_components/1,
second_port/1,
spec/1,
host/1
]).

-export([component_start_stream/2,
Expand Down Expand Up @@ -128,33 +127,34 @@ component_start_stream_subdomain(Conn = #client{props = Props}, []) ->
connect_component_subdomain(Component) ->
connect_component(Component, component_start_stream_subdomain).

spec(component_on_2, Config) ->
[{component, <<"yet_another_service">>}] ++ common(Config, mim2_service_port());
spec(component_duplicate, Config) ->
[{component, <<"another_service">>}] ++ common(Config, mim2_service_port());
spec(hidden_component, Config) ->
[{component, <<"hidden_component">>}] ++ common(Config, hidden_service_port());
spec(kicking_component, Config) ->
[{component, <<"kicking_component">>}] ++ common(Config, kicking_service_port());
spec(Other, Config) ->
[name(Other) | proplists:get_value(Other, Config, [])].

common(Config) ->
common(Config, service_port()).
host(CompSpec) ->
proplists:get_value(component, CompSpec).

second_port(Spec) ->
lists:keyreplace(port, 1, Spec, {port, mim2_service_port()}).

spec(vjud_component) ->
[{component, <<"vjud">>} | common(mim2_service_port())];
spec(component_on_2) ->
[{component, <<"yet_another_service">>} | common(mim2_service_port())];
spec(component_on_2) ->
[{component, <<"yet_another_service">>} | common(mim2_service_port())];
spec(hidden_component) ->
[{component, <<"hidden_component">>} | common(hidden_service_port())];
spec(kicking_component) ->
[{component, <<"kicking_component">>} | common(kicking_service_port())];
spec(Other) ->
Prefix = integer_to_binary(erlang:unique_integer([monotonic, positive])),
Name = <<Prefix/binary, "_", (atom_to_binary(Other))/binary>>,
[{component, Name} | common(service_port())].

service_port() ->
ct:get_config({hosts, mim, service_port}).

get_components(Config) ->
Opts = common(Config),
get_components(Opts, Config).

get_components(Opts, Config) ->
Opts = common(service_port()),
Components = [component1, component2, vjud_component],
get_components(Opts, Components, Config).

get_components(Opts, Components, Config) ->
[ {C, Opts ++ spec(C, Config)} || C <- Components ] ++ Config.
[ {C, Opts ++ spec(C)} || C <- Components ] ++ Config.

kicking_service_port() ->
ct:get_config({hosts, mim, kicking_service_port}).
Expand All @@ -165,17 +165,8 @@ hidden_service_port() ->
mim2_service_port() ->
ct:get_config({hosts, mim2, service_port}).

common(_Config, Port) ->
common(Port) ->
[{server, ct:get_config({hosts, mim, domain})},
{host, ct:get_config({hosts, mim, domain})},
{host, <<"localhost">>},
{password, <<"secret">>},
{port, Port}].

name(component1) ->
{component, <<"test_service">>};
name(component2) ->
{component, <<"another_service">>};
name(vjud_component) ->
{component, <<"vjud">>};
name(kicking_component) ->
{component, <<"kicking_component">>}.
18 changes: 2 additions & 16 deletions big_tests/tests/disco_and_caps_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ caps_test_cases() ->
user_can_query_server_caps_via_disco].

extra_feature_test_cases() ->
[user_can_query_extra_domains,
user_can_query_server_info].
[user_can_query_server_info].

init_per_suite(C) ->
instrument_helper:start(instrument_helper:declared_events(mod_disco)),
Expand Down Expand Up @@ -155,15 +154,6 @@ user_cannot_query_friend_resources_with_unknown_node(Config) ->
escalus:assert(is_stanza_from, [BobJid], Stanza)
end).

user_can_query_extra_domains(Config) ->
escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
Server = escalus_client:server(Alice),
escalus:send(Alice, escalus_stanza:service_discovery(Server)),
Stanza = escalus:wait_for_stanza(Alice),
escalus:assert(has_service, [extra_domain()], Stanza),
escalus:assert(is_stanza_from, [domain()], Stanza)
end).

user_can_query_server_features(Config) ->
escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
Server = escalus_client:server(Alice),
Expand Down Expand Up @@ -209,8 +199,7 @@ required_modules(disco_with_extra_features) ->
[{mod_disco, mod_config(mod_disco, extra_disco_opts())}].

extra_disco_opts() ->
#{extra_domains => [extra_domain()],
server_info => [server_info(abuse, #{}),
#{server_info => [server_info(abuse, #{}),
server_info(admin, #{modules => [mod_disco]}),
server_info(sales, #{modules => [mod_pubsub]})]}.

Expand All @@ -219,9 +208,6 @@ get_form_fields(Stanza) ->
{element_with_ns, <<"x">>, ?NS_DATA_FORMS},
{element, <<"field">>}]).

extra_domain() ->
<<"eXtra.example.com">>.

server_info(Type, Extra) ->
maps:merge(#{name => name(Type), urls => urls(Type)}, Extra).

Expand Down
2 changes: 1 addition & 1 deletion big_tests/tests/mod_global_distrib_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ test_components_in_different_regions(_Config) ->
%% Ordinary user is not able to discover the hidden component from GD
test_hidden_component_disco_in_different_region(Config) ->
%% Hidden component from component_SUITE connects to mim1/europe_node1
HiddenComponentConfig = component_helper:spec(hidden_component, Config),
HiddenComponentConfig = component_helper:spec(hidden_component),
{_HiddenComp, HiddenAddr, _} = component_helper:connect_component(HiddenComponentConfig),

escalus:fresh_story(
Expand Down
19 changes: 6 additions & 13 deletions big_tests/tests/service_mongoose_system_metrics_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@
instance_id = <<>>,
app_secret = <<>>}).

-import(distributed_helper, [mim/0, mim2/0, mim3/0, rpc/4,
require_rpc_nodes/1
]).

-import(component_helper, [connect_component/1,
disconnect_component/2,
get_components/1]).
-import(distributed_helper, [mim/0, mim2/0, mim3/0, rpc/4, require_rpc_nodes/1]).

-import(domain_helper, [host_type/0]).
-import(config_parser_helper, [mod_config/2, config/2]).
Expand Down Expand Up @@ -120,9 +114,8 @@ init_per_testcase(system_metrics_are_reported_to_configurable_google_analytics,
Config;
init_per_testcase(xmpp_components_are_reported, Config) ->
create_events_collection(),
Config1 = get_components(Config),
enable_system_metrics(mim()),
Config1;
Config;
init_per_testcase(xmpp_stanzas_counts_are_reported = CN, Config) ->
create_events_collection(),
enable_system_metrics(mim()),
Expand Down Expand Up @@ -248,12 +241,12 @@ mongoose_version_is_reported(_Config) ->
cluster_uptime_is_reported(_Config) ->
wait_helper:wait_until(fun is_cluster_uptime_reported/0, true).

xmpp_components_are_reported(Config) ->
CompOpts = ?config(component1, Config),
{Component, Addr, _} = connect_component(CompOpts),
xmpp_components_are_reported(_Config) ->
CompOpts = component_helper:spec(component1),
{Component, Addr, _} = component_helper:connect_component(CompOpts),
wait_helper:wait_until(fun are_xmpp_components_reported/0, true),
wait_helper:wait_until(fun more_than_one_component_is_reported/0, true),
disconnect_component(Component, Addr).
component_helper:disconnect_component(Component, Addr).

api_are_reported(_Config) ->
wait_helper:wait_until(fun is_api_reported/0, true).
Expand Down
4 changes: 2 additions & 2 deletions doc/developers-guide/Stanza-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ The default behaviour is the following:

* `mongoose_router_global`: runs a global `filter_packet` hook.
* `mongoose_router_localdomain`: if there is a local route registered for the destination domain (i.e. there is an entry in the `mongoose_router` ETS table), routes the stanza to it. When the recipient's domain is checked for the first time, the corresponding route is not registered yet, because the routes are added lazily - see `mongoose_router_dynamic_domains`.
* `mongoose_router_external_localnode`: if there is an external component registered for the destination domain on the current node, routes the stanza to it. Such components are stored in the Mnesia table `external_component`, which is not replicated in the cluster.
* `mongoose_router_external`: if there is an external component registered for the destination domain on any node in the cluster, routes the stanza to it. Such components are stored in the Mnesia table `external_component_global`, which is replicated among all cluster nodes.
* `mongoose_router_external_localnode`: if there is an external component registered for the destination domain on the current node, routes the stanza to it.
* `mongoose_router_external`: if there is an external component registered for the destination domain on any node in the cluster, routes the stanza to it.
* `mongoose_router_dynamic_domains`: if the recipient's domain is hosted by the local server, a route is added for it, and the stanza is routed locally.
* `ejabberd_s2s`: tries to find or establish a connection to another server and send the stanza there.

Expand Down
8 changes: 5 additions & 3 deletions doc/listeners/listen-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ Interface for external services acting as XMPP components ([XEP-0114: Jabber Com

According to [XEP-0114: Jabber Component Protocol](http://xmpp.org/extensions/xep-0114.html) the component's hostname should be given in the <stream:stream> element.

!!! warning
This interface does not support [dynamic domains](../configuration/general.md#generalhost_types).
Do not use them both at the same time.
!!! Note
The component might register _any_ domain, which might not necessarily be a static nor a dynamic domain, nor subdomain, recognised by the MongooseIM router. For routing to work, the modules `mongoose_router_external_localnode` and `mongoose_router_external` must be enabled in the [`general.routing_modules`](../configuration/general.md#generalrouting_modules) section.

Note the order of the routing modules: if a component is meant to supplant a domain served regularly by the MongooseIM server, the external routers should be ordered with higher priority.


## Configuration options

Expand Down
9 changes: 0 additions & 9 deletions doc/modules/mod_disco.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ Implements [XEP-0030: Service Discovery](http://xmpp.org/extensions/xep-0030.htm
Strategy to handle incoming stanzas. For details, please refer to
[IQ processing policies](../configuration/Modules.md#iq-processing-policies).

### `modules.mod_disco.extra_domains`
* **Syntax:** array of strings, valid domain names
* **Default:** no extra domains
* **Example:** `extra_domains = ["custom_domain"]`

Adds domains that are not registered with other means to a local item announcement (response to `http://jabber.org/protocol/disco#items` IQ get).
Please note that `mod_disco` doesn't verify these domains, so if no handlers are registered later for them, a client will receive a `service-unavailable` error for every stanza sent to one of these hosts.

### `modules.mod_disco.server_info`
* **Syntax:** array of tables described below
* **Default:** no additional server info
Expand Down Expand Up @@ -50,7 +42,6 @@ will still receive full disco results.
```toml
[modules.mod_disco]
iqdisc.type = "one_queue"
extra_domains = ["some_domain", "another_domain"]
server_info = [
{name = "abuse-address", urls = ["[email protected]"]},
{name = "friendly-spirits", urls = ["spirit1@localhost", "spirit2@localhost"], modules = ["mod_muc", "mod_disco"]}
Expand Down
48 changes: 25 additions & 23 deletions src/component/mongoose_component_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
{next_state, wait_for_stream, StateData1, state_timeout(LOpts)};
handle_event(internal, #xmlstreamstart{name = Name, attrs = Attrs}, wait_for_stream, StateData) ->
StreamStart = #xmlel{name = Name, attrs = Attrs},
execute_element_event(component_element_in, StreamStart, StateData),
handle_stream_start(StateData, StreamStart);
handle_event(internal, #xmlel{name = <<"handshake">>} = El, wait_for_handshake, StateData) ->
execute_element_event(component_element_in, El, StateData),
Expand Down Expand Up @@ -176,10 +175,10 @@
end.

-spec handle_socket_elements(data(), [exml_stream:element()], non_neg_integer()) -> fsm_res().
handle_socket_elements(StateData = #component_data{shaper = Shaper}, Elements, Size) ->
handle_socket_elements(StateData = #component_data{lserver = LServer, shaper = Shaper}, Elements, Size) ->
{NewShaper, Pause} = mongoose_shaper:update(Shaper, Size),
[mongoose_instrument:execute(
component_xmpp_element_size_in, #{}, #{byte_size => exml:xml_size(El)})
component_xmpp_element_size_in, #{}, #{byte_size => exml:xml_size(El), lserver => LServer})
|| El <- Elements],
NewStateData = StateData#component_data{shaper = NewShaper},
MaybePauseTimeout = maybe_pause(NewStateData, Pause),
Expand Down Expand Up @@ -212,6 +211,7 @@
IsSubdomain = <<"true">> =:= exml_query:attr(StreamStart, <<"is_subdomain">>, <<>>),
S1 = S0#component_data{lserver = LServer, is_subdomain = IsSubdomain},
send_header(S1),
execute_element_event(component_element_in, StreamStart, S1),

Check warning on line 214 in src/component/mongoose_component_connection.erl

View check run for this annotation

Codecov / codecov/patch

src/component/mongoose_component_connection.erl#L214

Added line #L214 was not covered by tests
{next_state, wait_for_handshake, S1, state_timeout(S1)};
{?NS_COMPONENT_ACCEPT, error} ->
stream_start_error(S0, mongoose_xmpp_errors:host_unknown());
Expand Down Expand Up @@ -285,24 +285,6 @@
end,
keep_state_and_data.

-spec register_routes(data()) -> any().
register_routes(StateData) ->
#component_data{listener_opts = #{hidden_components := AreHidden}} = StateData,
Routes = get_routes(StateData),
Handler = mongoose_packet_handler:new(mongoose_component, #{pid => self()}),
mongoose_component:register_components(Routes, node(), Handler, AreHidden).

-spec get_routes(data()) -> [jid:lserver()].
get_routes(#component_data{lserver = Subdomain, is_subdomain = true}) ->
Hosts = mongoose_config:get_opt(hosts),
component_routes(Subdomain, Hosts);
get_routes(#component_data{lserver = Host}) ->
[Host].

-spec component_routes(binary(), [jid:lserver()]) -> [jid:lserver()].
component_routes(Subdomain, Hosts) ->
[<<Subdomain/binary, ".", Host/binary>> || Host <- Hosts].

-spec try_register_routes(data(), retries()) -> fsm_res().
try_register_routes(StateData, Retries) ->
case register_routes(StateData) of
Expand All @@ -320,6 +302,26 @@
handle_registration_conflict(ConflictBehaviour, RoutesInfo, StateData, Retries)
end.

-spec register_routes(data()) -> any().
register_routes(StateData) ->
#component_data{listener_opts = #{hidden_components := AreHidden}} = StateData,
Routes = get_routes(StateData),
Handler = mongoose_packet_handler:new(mongoose_component, #{pid => self()}),
mongoose_component:register_components(Routes, node(), Handler, AreHidden).

Check warning on line 310 in src/component/mongoose_component_connection.erl

View check run for this annotation

Codecov / codecov/patch

src/component/mongoose_component_connection.erl#L307-L310

Added lines #L307 - L310 were not covered by tests

-spec get_routes(data()) -> [jid:lserver()].
get_routes(#component_data{lserver = Subdomain, is_subdomain = true}) ->
StaticDomains = mongoose_domain_api:get_all_static(),
DynamicDomains = mongoose_domain_api:get_all_dynamic(),
[ component_route(Subdomain, Domain) || {Domain, _} <- StaticDomains ]
++ [ component_route(Subdomain, Domain) || {Domain, _} <- DynamicDomains ];

Check warning on line 317 in src/component/mongoose_component_connection.erl

View check run for this annotation

Codecov / codecov/patch

src/component/mongoose_component_connection.erl#L314-L317

Added lines #L314 - L317 were not covered by tests
get_routes(#component_data{lserver = Host}) ->
[Host].

Check warning on line 319 in src/component/mongoose_component_connection.erl

View check run for this annotation

Codecov / codecov/patch

src/component/mongoose_component_connection.erl#L319

Added line #L319 was not covered by tests

-spec component_route(binary(), jid:lserver()) -> jid:lserver().
component_route(Subdomain, Host) ->
<<Subdomain/binary, ".", Host/binary>>.

Check warning on line 323 in src/component/mongoose_component_connection.erl

View check run for this annotation

Codecov / codecov/patch

src/component/mongoose_component_connection.erl#L323

Added line #L323 was not covered by tests

handle_registration_conflict(kick_old, RoutesInfo, StateData, Retries) when Retries > 0 ->
%% see lookup_routes
Pids = lists:usort(routes_info_to_pids(RoutesInfo)),
Expand Down Expand Up @@ -424,11 +426,11 @@
-spec send_xml(data(), exml_stream:element() | [exml_stream:element()]) -> maybe_ok().
send_xml(Data, XmlElement) when is_tuple(XmlElement) ->
send_xml(Data, [XmlElement]);
send_xml(#component_data{socket = Socket} = StateData, XmlElements) when is_list(XmlElements) ->
send_xml(#component_data{lserver = LServer, socket = Socket} = StateData, XmlElements) when is_list(XmlElements) ->
[ begin
execute_element_event(component_element_out, El, StateData),
mongoose_instrument:execute(
component_xmpp_element_size_out, #{}, #{byte_size => exml:xml_size(El)})
component_xmpp_element_size_out, #{}, #{byte_size => exml:xml_size(El), lserver => LServer})
end || El <- XmlElements],
mongoose_component_socket:send_xml(Socket, XmlElements).

Expand Down
3 changes: 0 additions & 3 deletions src/domain/mongoose_domain_api.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@
%% For testing
-export([get_all_dynamic/0]).

-ignore_xref([get_all_static/0]).
-ignore_xref([get_all_dynamic/0]).

-type status() :: enabled | disabled | deleting.
-type domain() :: jid:lserver().
-type host_type() :: mongooseim:host_type().
Expand Down
Loading