Skip to content

Commit

Permalink
Merge pull request #4235 from esl/pools/per_host_config
Browse files Browse the repository at this point in the history
Pools/per host config
  • Loading branch information
chrzaszcz authored Mar 8, 2024
2 parents fd0720d + daf4c01 commit e3e9381
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 137 deletions.
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.
78 changes: 42 additions & 36 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 @@ -91,7 +92,7 @@ root() ->
defaults = general_defaults()},
<<"listen">> => Listen#section{include = always},
<<"auth">> => Auth#section{include = always},
<<"outgoing_pools">> => outgoing_pools(),
<<"outgoing_pools">> => outgoing_pools(global_config),
<<"internal_databases">> => internal_databases(),
<<"services">> => services(),
<<"modules">> => Modules#section{include = always},
Expand Down Expand Up @@ -136,6 +137,7 @@ host_config() ->
<<"general">> => general(),
<<"auth">> => auth(),
<<"modules">> => modules(),
<<"outgoing_pools">> => outgoing_pools(host_config),
<<"acl">> => acl(),
<<"access">> => access(),
<<"s2s">> => s2s()
Expand Down Expand Up @@ -236,10 +238,10 @@ domain_cert() ->

%% 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 @@ listener_common() ->
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 @@ xmpp_listener_common() ->
<<"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 @@ xmpp_listener_extra(c2s) ->
<<"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 @@ -437,22 +439,29 @@ internal_database_mnesia() ->
#section{}.

%% path: outgoing_pools
outgoing_pools() ->
%% path: (host_config[].)outgoing_pools
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,15 +483,13 @@ wpool(ExtraDefaults) ->
<<"strategy">> => best_worker,
<<"call_timeout">> => 5000}, ExtraDefaults)}.

outgoing_pool_extra(Type) ->
#section{items = #{<<"scope">> => #option{type = atom,
validate = {enum, [global,
host_type, single_host_type,
host, single_host]}}, %% TODO deprecated
<<"host_type">> => #option{type = binary,
validate = non_empty},
<<"host">> => #option{type = binary, %% TODO deprecated
validate = non_empty},
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 host is deprecated
#section{items = #{<<"scope">> => #option{type = atom, validate = {enum, Scopes}},
<<"connection">> => outgoing_pool_connection(Type)
},
process = fun ?MODULE:process_pool/2,
Expand Down Expand Up @@ -1081,26 +1088,25 @@ check_auth_method(Method, Opts) ->
false -> error(#{what => missing_section_for_auth_method, auth_method => Method})
end.

process_pool([Tag, Type|_], AllOpts = #{scope := ScopeIn, connection := Connection}) ->
Scope = pool_scope(ScopeIn, maps:get(host_type, AllOpts, maps:get(host, AllOpts, none))),
process_pool([Tag, Type | _], AllOpts = #{scope := ScopeIn, connection := Connection}) ->
Scope = pool_scope(ScopeIn),
Opts = maps:without([scope, host, connection], AllOpts),
#{type => b2a(Type),
scope => Scope,
tag => b2a(Tag),
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;
pool_scope(host, none) -> host_type;
pool_scope(host_type, none) -> host_type;
pool_scope(global, none) -> global.
process_host_config_pool([Tag, Type, _Pools, {host, HT} | _], AllOpts = #{connection := Connection}) ->
#{type => b2a(Type),
scope => HT,
tag => b2a(Tag),
opts => maps:remove(connection, AllOpts),
conn_opts => Connection}.

pool_scope(host) -> host_type;
pool_scope(host_type) -> host_type;
pool_scope(global) -> global.

process_ldap_connection(ConnOpts = #{port := _}) -> ConnOpts;
process_ldap_connection(ConnOpts = #{tls := _}) -> ConnOpts#{port => 636};
Expand Down
60 changes: 31 additions & 29 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) ->
[call_callback(init, PoolType, []) || PoolType <- get_unique_types(PoolsIn)],
Pools = expand_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, HostTypeSpecific)],
[start(Pool) || Pool <- Pools].

-spec start(pool_map()) -> start_result().
Expand Down Expand Up @@ -357,29 +353,35 @@ 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}) ->
%% Rename "scope" field to "host_type" and change wpool opts to a KV list
Pool1 = maps:remove(scope, Pool),
Pool1#{host_type => HT, opts => maps:to_list(Opts)}.

-spec get_unique_types([pool_map_in()]) -> [pool_type()].
get_unique_types(Pools) ->
lists:usort([maps:get(type, Pool) || Pool <- Pools]).
-spec get_unique_types([pool_map_in()], [pool_map_in()]) -> [pool_type()].
get_unique_types(Pools, HostTypeSpecific) ->
lists:usort([maps:get(type, Pool) || Pool <- Pools ++ HostTypeSpecific]).

-spec get_pool(pool_type(), host_type_or_global(), tag()) -> pool_record_result().
get_pool(PoolType, HostType, Tag) ->
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

0 comments on commit e3e9381

Please sign in to comment.