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

Pools/per host config #4235

Merged
merged 8 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion doc/configuration/Services.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ It is used to synchronise dynamic domains between nodes after starting.
* **Example:** `db_pool = "my_host_type"`

By default, this service uses the RDBMS connection pool configured with the scope `"global"`.
You can put a specific host type there to use the pool with the `"host"` or `"single_host"` scope for that particular host type. See the [outgoing connections docs](outgoing-connections.md) for more information about pool scopes.
You can put a specific host type there to use the `default` pool with the `host_type` scope for that particular host type. See the [outgoing connections docs](outgoing-connections.md) for more information about pool scopes.

### `services.service_domain_db.event_cleaning_interval`

Expand Down
20 changes: 20 additions & 0 deletions doc/configuration/host_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ If we wanted to enable `mod_roster`, it would need to be repeated in `host_confi
[host_config.modules.mod_stream_management]
```

### `host_config.outgoing_pools`

This section overrides any pool with the same type and tag that was defined in the top-level [`outgoing_pools`](outgoing-connections.md) section.
If we wanted to enable a `default` `rdbms` pool only for `"host-type-basic"` for example, we could do so as follows:

```toml
[general]
host_type = ["host-type-basic", "host-type-advanced", "host-type-privacy"]

[[host_config]]
host = "host-type-basic"

[outgoing_pools.rdbms.default]
workers = 5
[outgoing_pools.rdbms.default.connection]
...
```

Configuration for such pools is all the same, except that the `scope` key is here disallowed.

### `host_config.acl`

The access classes defined here are merged with the ones defined in the top-level [`acl`](acl.md) section - when a class is defined in both places, the result is a union of both classes.
Expand Down
19 changes: 4 additions & 15 deletions doc/configuration/outgoing-connections.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,21 @@ This allows you to create multiple dedicated pools of the same type.
## General pool options

### `outgoing_pools.*.*.scope`
* **Syntax:** string, one of:`"global"`, `"host_type"`, `"single_host_type"`
* **Syntax:** string, one of:`"global"`, `"host_type"`.
* **Default:** `"global"`
* **Example:** `scope = "host_type"`

### `outgoing_pools.*.*.host_type`
* **Syntax:** string
* **Default:** no default; required if `"single_host_type"` scope is specified
* **Example:** `host_type = "basic_host_type"`

### `outgoing_pools.*.*.host`
* **Syntax:** string
* **Default:** no default; required if `"single_host"` scope is specified
* **Example:** `host = "anotherhost.com"`

`scope` can be set to:

* `global` - meaning that the pool will be started once no matter how many XMPP hosts are served by MongooseIM
* `host_type` - the pool will be started for each XMPP host or host type served by MongooseIM
* `single_host_type` - the pool will be started for the selected host or host type only (you must provide the name).
* `global` - meaning that the pool will be started once no matter how many XMPP hosts are served by MongooseIM.
* `host_type` - the pool will be started for each static XMPP host or host type served by MongooseIM.

!!! Note
A pool with scope `global` and tag `default` is used by services that are not configured by host_type, like `service_domain_db` or `service_mongoose_system_metrics`, or by modules that don't support dynamic domains, like `mod_pubsub`.
If a global default pool is not configured, these services will fail.

!!! Note
`host` and `single_host` are still supported and behave equivalent to `host_type` and `single_host_type` respectively; however, they are deprecated in favour of the latter.
The option `host` is still supported and behaves equivalent to `host_type`; however, it is deprecated in favour of the latter.

## Worker pool options

Expand Down
4 changes: 3 additions & 1 deletion doc/migrations/6.2.0_x.x.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ There is a new column in the `mam_message` table in the database, which is used

## Outgoing pools

Pools now take `host_type` and `single_host_type` instead of `host` and `single_host` in their scope, see [outgoing pools](../configuration/outgoing-connections.md) for more information.
The outgoing connections option `host` is now named `host_type`, see [outgoing pools](../configuration/outgoing-connections.md) for more information.

The option `single_host` for the scope has been deprecated, in favour of configuring the specified pools within the [`host_config`](../configuration/host_config.md) section.
67 changes: 42 additions & 25 deletions src/config/mongoose_config_spec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
process_sasl_mechanism/1,
process_auth/1,
process_pool/2,
process_host_config_pool/2,
process_ldap_connection/1,
process_iqdisc/1,
process_acl_condition/1,
Expand Down Expand Up @@ -136,6 +137,7 @@
<<"general">> => general(),
<<"auth">> => auth(),
<<"modules">> => modules(),
<<"outgoing_pools">> => host_config_outgoing_pools(),
NelsonVides marked this conversation as resolved.
Show resolved Hide resolved
<<"acl">> => acl(),
<<"access">> => access(),
<<"s2s">> => s2s()
Expand Down Expand Up @@ -236,10 +238,10 @@

%% path: listen
listen() ->
Keys = [c2s, s2s, service, http],
ListenerTypes = [<<"c2s">>, <<"s2s">>, <<"service">>, <<"http">>],
#section{
items = maps:from_list([{atom_to_binary(Key), #list{items = listener(Key), wrap = none}}
|| Key <- Keys]),
items = maps:from_list([{Listener, #list{items = listener(Listener), wrap = none}}
|| Listener <- ListenerTypes]),
process = fun mongoose_listener_config:verify_unique_listeners/1,
wrap = global_config,
format_items = list
Expand All @@ -264,7 +266,7 @@
process = fun ?MODULE:process_listener/2
}.

listener_extra(http) ->
listener_extra(<<"http">>) ->
%% tls options passed to ranch_ssl (with verify_mode translated to verify_fun)
#section{items = #{<<"tls">> => tls([server], [just_tls]),
<<"transport">> => http_transport(),
Expand Down Expand Up @@ -292,7 +294,7 @@
<<"num_acceptors">> => 100}
}.

xmpp_listener_extra(c2s) ->
xmpp_listener_extra(<<"c2s">>) ->
#section{items = #{<<"access">> => #option{type = atom,
validate = non_empty},
<<"shaper">> => #option{type = atom,
Expand All @@ -315,14 +317,14 @@
<<"reuse_port">> => false,
<<"backwards_compatible_session">> => true}
};
xmpp_listener_extra(s2s) ->
xmpp_listener_extra(<<"s2s">>) ->
TLSSection = tls([server], [fast_tls]),
#section{items = #{<<"shaper">> => #option{type = atom,
validate = non_empty},
<<"tls">> => TLSSection#section{include = always}},
defaults = #{<<"shaper">> => none}
};
xmpp_listener_extra(service) ->
xmpp_listener_extra(<<"service">>) ->
#section{items = #{<<"access">> => #option{type = atom,
validate = non_empty},
<<"shaper_rule">> => #option{type = atom,
Expand Down Expand Up @@ -438,21 +440,34 @@

%% path: outgoing_pools
outgoing_pools() ->
outgoing_pools(global_config).

%% path: (host_config[].)outgoing_pools
host_config_outgoing_pools() ->
outgoing_pools(host_config).

outgoing_pools(Scope) ->
PoolTypes = [<<"cassandra">>, <<"elastic">>, <<"http">>, <<"ldap">>,
<<"rabbit">>, <<"rdbms">>, <<"redis">>],
Items = [{Type, #section{items = #{default => outgoing_pool(Type)},
Items = [{Type, #section{items = #{default => outgoing_pool(Scope, Type)},
validate_keys = non_empty,
wrap = none,
format_items = list}} || Type <- PoolTypes],
#section{items = maps:from_list(Items),
format_items = list,
wrap = global_config,
include = always}.
wrap = Scope,
include = include_only_on_global_config(Scope)}.

include_only_on_global_config(global_config) ->
always;
include_only_on_global_config(host_config) ->
when_present.

%% path: outgoing_pools.*.*
outgoing_pool(Type) ->
outgoing_pool(Scope, Type) ->
ExtraDefaults = extra_wpool_defaults(Type),
Pool = mongoose_config_utils:merge_sections(wpool(ExtraDefaults), outgoing_pool_extra(Type)),
ExtraConfig = outgoing_pool_extra(Scope, Type),
Pool = mongoose_config_utils:merge_sections(wpool(ExtraDefaults), ExtraConfig),
Pool#section{wrap = item}.

extra_wpool_defaults(<<"cassandra">>) ->
Expand All @@ -474,11 +489,14 @@
<<"strategy">> => best_worker,
<<"call_timeout">> => 5000}, ExtraDefaults)}.

outgoing_pool_extra(Type) ->
outgoing_pool_extra(host_config, Type) ->
#section{items = #{<<"connection">> => outgoing_pool_connection(Type)},
process = fun ?MODULE:process_host_config_pool/2
};
outgoing_pool_extra(global_config, Type) ->
Scopes = [global, host_type, host], %% TODO deprecated
NelsonVides marked this conversation as resolved.
Show resolved Hide resolved
#section{items = #{<<"scope">> => #option{type = atom,
validate = {enum, [global,
host_type, single_host_type,
host, single_host]}}, %% TODO deprecated
validate = {enum, Scopes}},
<<"host_type">> => #option{type = binary,
validate = non_empty},
<<"host">> => #option{type = binary, %% TODO deprecated
NelsonVides marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -1081,7 +1099,7 @@
false -> error(#{what => missing_section_for_auth_method, auth_method => Method})
end.

process_pool([Tag, Type|_], AllOpts = #{scope := ScopeIn, connection := Connection}) ->
process_pool([Tag, Type | _], AllOpts = #{scope := ScopeIn, connection := Connection}) ->
Scope = pool_scope(ScopeIn, maps:get(host_type, AllOpts, maps:get(host, AllOpts, none))),
NelsonVides marked this conversation as resolved.
Show resolved Hide resolved
Opts = maps:without([scope, host, connection], AllOpts),
#{type => b2a(Type),
Expand All @@ -1090,14 +1108,13 @@
opts => Opts,
conn_opts => Connection}.

pool_scope(single_host_type, none) ->
error(#{what => pool_single_host_type_not_specified,
text => <<"\"host_type\" option is required if \"single_host_type\" is used.">>});
pool_scope(single_host, none) ->
error(#{what => pool_single_host_not_specified,
text => <<"\"host\" option is required if \"single_host\" is used.">>});
pool_scope(single_host_type, HostType) -> HostType;
pool_scope(single_host, Host) -> Host;
process_host_config_pool([Tag, Type, _Pools, {host, HT} | _], AllOpts = #{connection := Connection}) ->
#{type => b2a(Type),

Check warning on line 1112 in src/config/mongoose_config_spec.erl

View check run for this annotation

Codecov / codecov/patch

src/config/mongoose_config_spec.erl#L1112

Added line #L1112 was not covered by tests
scope => HT,
tag => b2a(Tag),
opts => maps:remove(connection, AllOpts),
conn_opts => Connection}.

pool_scope(host, none) -> host_type;
pool_scope(host_type, none) -> host_type;
pool_scope(global, none) -> global.
Expand Down
52 changes: 27 additions & 25 deletions src/wpool/mongoose_wpool.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,28 @@
get_pool_settings/3, get_pools/0, stats/3]).

-export([start_sup_pool/3]).
-export([start_configured_pools/0]).
-export([start_configured_pools/1]).
-export([start_configured_pools/2]).
-export([start_configured_pools/0, start_configured_pools/1,
start_configured_pools/2, start_configured_pools/3]).
-export([is_configured/1]).
-export([make_pool_name/3]).
-export([call_start_callback/2]).

%% Mostly for tests
-export([expand_pools/2]).
-export([expand_pools/3]).

-ignore_xref([call/2, cast/2, cast/3, expand_pools/2, get_worker/2,
-ignore_xref([call/2, cast/2, cast/3, expand_pools/3, get_worker/2,
send_request/2, send_request/3, send_request/4, send_request/5,
is_configured/2, is_configured/1, is_configured/1, start/2, start/3,
start/5, start_configured_pools/1, start_configured_pools/2, stats/3,
stop/1, stop/2]).
start/5, start_configured_pools/1, start_configured_pools/2, start_configured_pools/3,
stats/3, stop/1, stop/2]).

-type pool_type() :: redis | http | rdbms | cassandra | elastic | generic
| rabbit | ldap.
-type pool_type() :: redis | http | rdbms | cassandra | elastic | generic | rabbit | ldap.

%% Config scope
-type scope() :: global | host_type | mongooseim:host_type().
-type host_type_or_global() :: mongooseim:host_type_or_global().

-type tag() :: atom().

%% Name of a process
-type proc_name() :: atom().

Expand Down Expand Up @@ -80,13 +78,7 @@
-type start_result() :: {ok, pid()} | {error, term()}.
-type stop_result() :: ok | term().

-export_type([pool_type/0]).
-export_type([tag/0]).
-export_type([scope/0]).
-export_type([proc_name/0]).
-export_type([pool_name/0]).
-export_type([pool_opts/0]).
-export_type([conn_opts/0]).
-export_type([pool_type/0, tag/0, scope/0, proc_name/0, pool_name/0, pool_opts/0, conn_opts/0]).

-type callback_fun() :: init | start | is_supported_strategy | stop.

Expand Down Expand Up @@ -121,8 +113,12 @@ start_configured_pools(PoolsIn) ->
start_configured_pools(PoolsIn, ?ALL_HOST_TYPES).

start_configured_pools(PoolsIn, HostTypes) ->
HostTypeSpecific = get_host_type_specific_pools(HostTypes),
start_configured_pools(PoolsIn, HostTypeSpecific, HostTypes).

start_configured_pools(PoolsIn, HostTypeSpecific, HostTypes) ->
Pools = expand_pools(PoolsIn, HostTypeSpecific, HostTypes),
[call_callback(init, PoolType, []) || PoolType <- get_unique_types(PoolsIn)],
NelsonVides marked this conversation as resolved.
Show resolved Hide resolved
Pools = expand_pools(PoolsIn, HostTypes),
[start(Pool) || Pool <- Pools].

-spec start(pool_map()) -> start_result().
Expand Down Expand Up @@ -357,19 +353,25 @@ make_callback_module_name(PoolType) ->
Name = "mongoose_wpool_" ++ atom_to_list(PoolType),
list_to_atom(Name).

-spec expand_pools([pool_map_in()], [mongooseim:host_type()]) -> [pool_map()].
expand_pools(Pools, HostTypes) ->
%% First we select only pools for a specific vhost
HostSpecific = [{Type, HT, Tag} || #{type := Type, scope := HT, tag := Tag} <- Pools, is_binary(HT)],
%% Then we expand all pools with `host_type` as HostType parameter but using host_type specific configs
%% if they were provided
-spec get_host_type_specific_pools([mongooseim:host_type()]) -> [pool_map_in()].
get_host_type_specific_pools(HostTypes) ->
lists:append([ mongoose_config:get_opt({outgoing_pools, HostType}, [])
|| HostType <- HostTypes ]).

-spec expand_pools([pool_map_in()], [pool_map_in()], [mongooseim:host_type()]) -> [pool_map()].
expand_pools(Pools, PerHostType, HostTypes) ->
%% First we select only vhost/host_type specific pools
HostSpecific = [ {Type, HT, Tag}
|| #{type := Type, scope := HT, tag := Tag} <- PerHostType ],
%% Then we expand all pools with `host_type` as scope
%% but using host_type specific configs if they were provided
F = fun(M = #{type := PoolType, scope := host_type, tag := Tag}) ->
[M#{scope => HostType} || HostType <- HostTypes,
not lists:member({PoolType, HostType, Tag}, HostSpecific)];
(Other) -> [Other]
end,
Pools1 = lists:flatmap(F, Pools),
lists:map(fun prepare_pool_map/1, Pools1).
lists:map(fun prepare_pool_map/1, PerHostType ++ Pools1).

-spec prepare_pool_map(pool_map_in()) -> pool_map().
prepare_pool_map(Pool = #{scope := HT, opts := Opts}) ->
Expand Down
2 changes: 1 addition & 1 deletion test/auth_http_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ init_per_suite(Config) ->
% This would be started via outgoing_pools in normal case
Pool = config([outgoing_pools, http, auth], pool_opts()),
HostTypes = [?HOST_TYPE, <<"another host type">>],
mongoose_wpool:start_configured_pools([Pool], HostTypes),
mongoose_wpool:start_configured_pools([Pool], [], HostTypes),
mongoose_wpool_http:init(),
ejabberd_auth_http:start(?HOST_TYPE)
end),
Expand Down
Loading