diff --git a/big_tests/tests/dynamic_services.erl b/big_tests/tests/dynamic_services.erl new file mode 100644 index 00000000000..c9ac0c847c0 --- /dev/null +++ b/big_tests/tests/dynamic_services.erl @@ -0,0 +1,59 @@ +-module(dynamic_services). + +-compile([export_all, nowarn_export_all]). + +-import(distributed_helper, [mim/0, rpc/4]). + +save_services(Config) -> + save_services(mim(), Config). + +%% Save services from Node, overwriting previously saved services +save_services(Nodes, Config) when is_list(Nodes) -> + lists:foldl(fun save_services/2, Config, Nodes); +save_services(Node = #{node := NodeName}, Config) -> + Key = {saved_services, NodeName}, + Value = get_current_services(Node), + lists:keystore(Key, 1, Config, {Key, Value}). + +get_saved_config(Service, Config) -> + get_saved_config(mim(), Service, Config). + +get_saved_config(#{node := NodeName}, Service, Config) -> + SavedServices = proplists:get_value({saved_services, NodeName}, Config), + maps:get(Service, SavedServices). + +restore_services(Config) -> + restore_services(#{}, Config). + +restore_services(RPCSpec, Config) when is_map(RPCSpec) -> + [restore_saved_services(RPCSpec#{node => NodeName}, SavedServices) + || {{saved_services, NodeName}, SavedServices} <- Config], + Config. + +restore_saved_services(Node, SavedServices) -> + CurrentServices = get_current_services(Node), + ToStop = maps:keys(CurrentServices) -- maps:keys(SavedServices), + rpc(Node, mongoose_service, replace_services, [ToStop, SavedServices]). + +get_current_services() -> + get_current_services(mim()). + +get_current_services(Node) -> + rpc(Node, mongoose_service, loaded_services_with_opts, []). + +ensure_services(Node, RequiredServices) -> + ToStop = [M || {M, stopped} <- RequiredServices], + ToEnsure = maps:without(ToStop, maps:from_list(RequiredServices)), + rpc(Node, mongoose_service, replace_services, [ToStop, ToEnsure]). + +ensure_stopped(Service) -> + ensure_stopped(mim(), Service). + +ensure_stopped(Node, Service) -> + rpc(Node, mongoose_service, ensure_stopped, [Service]). + +ensure_started(Service, Opts) -> + ensure_started(mim(), Service, Opts). + +ensure_started(Node, Service, Opts) -> + rpc(Node, mongoose_service, ensure_started, [Service, Opts]). diff --git a/big_tests/tests/mongooseimctl_SUITE.erl b/big_tests/tests/mongooseimctl_SUITE.erl index 199d600f5a7..7f92fc04a50 100644 --- a/big_tests/tests/mongooseimctl_SUITE.erl +++ b/big_tests/tests/mongooseimctl_SUITE.erl @@ -416,7 +416,7 @@ real_upload(Config, ContentType) -> ?assertMatch({_, 0}, Ret), ok. %%-------------------------------------------------------------------- -%% mod_admin_extra_accounts tests +%% service_admin_extra_accounts tests %%-------------------------------------------------------------------- change_password(Config) -> @@ -528,7 +528,7 @@ delete_old_users_vhost(Config) -> true = (ErrCode =/= 0). %% Must return code other than 0 %%-------------------------------------------------------------------- -%% mod_admin_extra_accounts tests +%% service_admin_extra_accounts tests %%-------------------------------------------------------------------- %% Checks both num_resources and resource_num @@ -611,7 +611,7 @@ set_presence(Config) -> end). %%-------------------------------------------------------------------- -%% mod_admin_extra_vcard tests +%% service_admin_extra_vcard tests %%-------------------------------------------------------------------- vcard_rw(Config) -> @@ -647,7 +647,7 @@ vcard2_multi_rw(Config) -> true = (lists:member("sales", OrgUnits) andalso lists:member("marketing", OrgUnits)). %%-------------------------------------------------------------------- -%% mod_admin_extra_vcard tests +%% service_admin_extra_vcard tests %%-------------------------------------------------------------------- rosteritem_rw(Config) -> @@ -940,7 +940,7 @@ push_roster_alltoall(Config) -> end). %%-------------------------------------------------------------------- -%% mod_admin_extra_last tests +%% service_admin_extra_last tests %%-------------------------------------------------------------------- set_last(Config) -> @@ -977,7 +977,7 @@ set_last(Config) -> end). %%-------------------------------------------------------------------- -%% mod_admin_extra_private tests +%% service_admin_extra_private tests %%-------------------------------------------------------------------- private_rw(Config) -> @@ -995,7 +995,7 @@ private_rw(Config) -> children = [#xmlcdata{ content = <<"1">> }]}} = exml:parse(list_to_binary(Result)). %%-------------------------------------------------------------------- -%% mod_admin_extra_stanza tests +%% service_admin_extra_stanza tests %%-------------------------------------------------------------------- send_message(Config) -> @@ -1075,7 +1075,7 @@ create_stanza(Name1, JID2) -> exml:to_binary(escalus_stanza:from(escalus_stanza:chat_to(Name1, "Hi"), JID2)). %%-------------------------------------------------------------------- -%% mod_admin_extra_stats tests +%% service_admin_extra_stats tests %%-------------------------------------------------------------------- stats_global(Config) -> diff --git a/big_tests/tests/service_domain_db_SUITE.erl b/big_tests/tests/service_domain_db_SUITE.erl index 676642deba2..0a80e739750 100644 --- a/big_tests/tests/service_domain_db_SUITE.erl +++ b/big_tests/tests/service_domain_db_SUITE.erl @@ -1,7 +1,6 @@ -module(service_domain_db_SUITE). -include_lib("common_test/include/ct.hrl"). --include_lib("eunit/include/eunit.hrl"). -compile([export_all, nowarn_export_all]). -import(distributed_helper, [mim/0, mim2/0, mim3/0, require_rpc_nodes/1, rpc/4]). @@ -26,6 +25,7 @@ stop_listener/1]). -import(domain_helper, [domain/0]). +-import(config_parser_helper, [config/2]). -define(INV_PWD, <<"basic auth provided, invalid password">>). -define(NO_PWD, <<"basic auth is required">>). @@ -167,15 +167,12 @@ rest_cases() -> rest_enable_domain_fails_if_service_disabled, rest_delete_domain_cleans_data_from_mam]. --define(APPS, [inets, crypto, ssl, ranch, cowlib, cowboy]). - %%-------------------------------------------------------------------- %% Suite configuration %%-------------------------------------------------------------------- init_per_suite(Config) -> - Conf1 = store_conf(mim()), - Conf2 = store_conf(mim2()), - Conf3 = store_conf(mim3()), + Config0 = dynamic_services:save_services(all_nodes(), Config), + Config1 = dynamic_modules:save_modules(dummy_auth_host_type(), Config0), ensure_nodes_know_each_other(), service_disabled(mim()), service_disabled(mim2()), @@ -183,31 +180,20 @@ init_per_suite(Config) -> prepare_test_queries(mim()), prepare_test_queries(mim2()), erase_database(mim()), - Config1 = ejabberd_node_utils:init(mim(), Config), - Config2 = dynamic_modules:save_modules(dummy_auth_host_type(), Config1), - escalus:init_per_suite([{mim_conf1, Conf1}, - {mim_conf2, Conf2}, - {mim_conf3, Conf3}, - {service_setup, per_testcase} | Config2]). - -store_conf(Node) -> - Loaded = rpc(Node, mongoose_service, is_loaded, [service_domain_db]), - ServiceOpts = rpc(Node, mongoose_service, get_service_opts, [service_domain_db]), - CoreOpts = rpc(Node, mongoose_domain_core, get_start_args, []), - #{loaded => Loaded, service_opts => ServiceOpts, core_opts => CoreOpts}. + Config2 = ejabberd_node_utils:init(mim(), Config1), + escalus:init_per_suite([{service_setup, per_testcase} | Config2]). end_per_suite(Config) -> - Conf1 = proplists:get_value(mim_conf1, Config), - Conf2 = proplists:get_value(mim_conf2, Config), - Conf3 = proplists:get_value(mim_conf3, Config), - restore_conf(mim(), Conf1), - restore_conf(mim2(), Conf2), - restore_conf(mim3(), Conf3), + [restart_domain_core(Node) || Node <- all_nodes()], + dynamic_services:restore_services(Config), domain_helper:insert_configured_domains(), dynamic_modules:restore_modules(Config), escalus_fresh:clean(), escalus:end_per_suite(Config). +all_nodes() -> + [mim(), mim2(), mim3()]. + %%-------------------------------------------------------------------- %% Init & teardown %%-------------------------------------------------------------------- @@ -225,7 +211,7 @@ init_per_group(rest_without_auth, Config) -> init_per_group(GroupName, Config) -> Config1 = save_service_setup_option(GroupName, Config), case ?config(service_setup, Config) of - per_group -> setup_service([], Config1); + per_group -> setup_service(#{}, Config1); per_testcase -> ok end, Config1. @@ -242,7 +228,7 @@ end_per_group(_GroupName, Config) -> init_per_testcase(db_crash_on_initial_load_restarts_service, Config) -> maybe_setup_meck(db_crash_on_initial_load_restarts_service), - init_with(mim(), [], []), + restart_domain_core(mim(), [], []), Config; init_per_testcase(TestcaseName, Config) -> maybe_setup_meck(TestcaseName), @@ -253,9 +239,9 @@ init_per_testcase(TestcaseName, Config) -> init_per_testcase2(TestcaseName, Config). service_opts(db_events_table_gets_truncated) -> - [{event_cleaning_interval, 1}, {event_max_age, 3}]; + #{event_cleaning_interval => 1, event_max_age => 3}; service_opts(_) -> - []. + #{}. end_per_testcase(TestcaseName, Config) -> end_per_testcase2(TestcaseName, Config), @@ -288,14 +274,14 @@ end_per_testcase2(_, Config) -> setup_service(Opts, Config) -> ServiceEnabled = proplists:get_value(service, Config, false), Pairs1 = [{<<"example.cfg">>, <<"type1">>}, - {<<"erlang-solutions.com">>, <<"type2">>}, - {<<"erlang-solutions.local">>, <<"type2">>}], - init_with(mim(), Pairs1, host_types_for_mim()), - init_with(mim2(), [], host_types_for_mim2()), + {<<"erlang-solutions.com">>, <<"type2">>}, + {<<"erlang-solutions.local">>, <<"type2">>}], + restart_domain_core(mim(), Pairs1, host_types_for_mim()), + restart_domain_core(mim2(), [], host_types_for_mim2()), case ServiceEnabled of true -> service_enabled(mim(), Opts), - service_enabled(mim2(), []); + service_enabled(mim2(), #{}); false -> ok end. @@ -492,7 +478,7 @@ db_records_are_restored_on_mim_restart(_) -> ok = insert_domain(mim(), <<"example.com">>, <<"type1">>), %% Simulate MIM restart service_disabled(mim()), - init_with(mim(), [], [<<"type1">>]), + restart_domain_core(mim(), [], [<<"type1">>]), {error, not_found} = get_host_type(mim(), <<"example.com">>), service_enabled(mim()), %% DB still contains data @@ -507,7 +493,7 @@ db_record_is_ignored_if_domain_static(_) -> %% Simulate MIM restart service_disabled(mim()), %% Only one domain is static - init_with(mim(), [{<<"example.com">>, <<"cfggroup">>}], [<<"dbgroup">>, <<"cfggroup">>]), + restart_domain_core(mim(), [{<<"example.com">>, <<"cfggroup">>}], [<<"dbgroup">>, <<"cfggroup">>]), service_enabled(mim()), %% DB still contains data {ok, #{host_type := <<"dbgroup">>, enabled := true}} = @@ -1050,20 +1036,25 @@ rest_delete_domain_cleans_data_from_mam(Config) -> %%-------------------------------------------------------------------- service_enabled(Node) -> - service_enabled(Node, []), + service_enabled(Node, #{}), sync_local(Node). -service_enabled(Node, Opts) -> - rpc(Node, mongoose_service, ensure_started, [service_domain_db, Opts]), +service_enabled(Node, ExtraOpts) -> + Opts = config([services, service_domain_db], ExtraOpts), + dynamic_services:ensure_started(Node, service_domain_db, Opts), true = rpc(Node, service_domain_db, enabled, []). service_disabled(Node) -> - rpc(Node, mongoose_service, ensure_stopped, [service_domain_db]), + dynamic_services:ensure_stopped(Node, service_domain_db), false = rpc(Node, service_domain_db, enabled, []). -init_with(Node, Pairs, AllowedHostTypes) -> - rpc(Node, mongoose_domain_core, stop, []), - rpc(Node, mongoose_domain_core, start, [Pairs, AllowedHostTypes]). +restart_domain_core(Node, Pairs, AllowedHostTypes) -> + ok = rpc(Node, mongoose_domain_core, stop, []), + ok = rpc(Node, mongoose_domain_core, start, [Pairs, AllowedHostTypes]). + +restart_domain_core(Node) -> + ok = rpc(Node, mongoose_domain_core, stop, []), + ok = rpc(Node, mongoose_domain_core, start, []). insert_domain(Node, Domain, HostType) -> rpc(Node, mongoose_domain_api, insert_domain, [Domain, HostType]). @@ -1088,13 +1079,13 @@ erase_database(Node) -> case mongoose_helper:is_rdbms_enabled(domain()) of true -> prepare_test_queries(Node), - rpc(Node, mongoose_domain_sql, erase_database, []); + rpc(Node, mongoose_domain_sql, erase_database, [global]); false -> ok end. prepare_test_queries(Node) -> case mongoose_helper:is_rdbms_enabled(domain()) of - true -> rpc(Node, mongoose_domain_sql, prepare_test_queries, []); + true -> rpc(Node, mongoose_domain_sql, prepare_test_queries, [global]); false -> ok end. @@ -1158,17 +1149,6 @@ sync_local(Node) -> force_check_for_updates(Node) -> ok = rpc(Node, service_domain_db, force_check_for_updates, []). -restore_conf(Node, #{loaded := Loaded, service_opts := ServiceOpts, core_opts := CoreOpts}) -> - rpc(Node, mongoose_service, ensure_stopped, [service_domain_db]), - [Pairs, AllowedHostTypes] = CoreOpts, - init_with(Node, Pairs, AllowedHostTypes), - case Loaded of - true -> - rpc(Node, mongoose_service, ensure_started, [service_domain_db, ServiceOpts]); - _ -> - ok - end. - %% Needed for pg2 group to work %% So, multiple node tests work ensure_nodes_know_each_other() -> diff --git a/big_tests/tests/service_mongoose_system_metrics_SUITE.erl b/big_tests/tests/service_mongoose_system_metrics_SUITE.erl index 39c2cb93d12..a99f74018b0 100644 --- a/big_tests/tests/service_mongoose_system_metrics_SUITE.erl +++ b/big_tests/tests/service_mongoose_system_metrics_SUITE.erl @@ -74,20 +74,15 @@ groups() -> %% Suite configuration %%-------------------------------------------------------------------- init_per_suite(Config) -> - case system_metrics_service_is_enabled(mim()) of - false -> - ct:fail("service_mongoose_system_metrics is not running"); - true -> - [ {ok, _} = application:ensure_all_started(App) || App <- ?APPS ], - http_helper:start(8765, "/[...]", fun handler_init/1), - Config1 = escalus:init_per_suite(Config), - ejabberd_node_utils:init(Config1) - end. + [ {ok, _} = application:ensure_all_started(App) || App <- ?APPS ], + http_helper:start(8765, "/[...]", fun handler_init/1), + Config1 = escalus:init_per_suite(Config), + Config2 = dynamic_services:save_services([mim(), mim2()], Config1), + ejabberd_node_utils:init(Config2). end_per_suite(Config) -> http_helper:stop(), - Args = [{initial_report, timer:seconds(20)}, {periodic_report, timer:minutes(5)}], - [start_system_metrics_service(Node, Args) || Node <- [mim(), mim2()]], + dynamic_services:restore_services(Config), escalus:end_per_suite(Config). %%-------------------------------------------------------------------- @@ -308,7 +303,7 @@ in_config_unmodified_logs_request_for_agreement(_Config) -> in_config_with_explicit_no_report_goes_off_silently(_Config) -> %% WHEN logger_ct_backend:capture(warning), - start_system_metrics_service(mim(), [{no_report, true}]), + start_system_metrics_service(mim(), #{report => false}), logger_ct_backend:stop_capture(), %% THEN FilterFun = fun(warning, Msg) -> @@ -322,7 +317,7 @@ in_config_with_explicit_no_report_goes_off_silently(_Config) -> in_config_with_explicit_reporting_goes_on_silently(_Config) -> %% WHEN logger_ct_backend:capture(warning), - start_system_metrics_service(mim(), [{report, true}]), + start_system_metrics_service(mim(), #{report => true}), logger_ct_backend:stop_capture(), %% THEN FilterFun = fun(warning, Msg) -> @@ -419,22 +414,23 @@ get_events_collection_size() -> ets:info(?ETS_TABLE, size). enable_system_metrics(Node) -> - enable_system_metrics(Node, [{initial_report, 100}, {periodic_report, 100}]). + enable_system_metrics(Node, #{initial_report => 100, periodic_report => 100}). -enable_system_metrics(Node, Timers) -> +enable_system_metrics_with_configurable_tracking_id(Node) -> + enable_system_metrics(Node, #{initial_report => 100, periodic_report => 100, + tracking_id => ?TRACKING_ID_EXTRA}). + +enable_system_metrics(Node, Opts) -> UrlArgs = [google_analytics_url, ?SERVER_URL], ok = mongoose_helper:successful_rpc(Node, mongoose_config, set_opt, UrlArgs), - start_system_metrics_service(Node, Timers). - -enable_system_metrics_with_configurable_tracking_id(Node) -> - enable_system_metrics(Node, [{initial_report, 100}, {periodic_report, 100}, {tracking_id, ?TRACKING_ID_EXTRA}]). + start_system_metrics_service(Node, Opts). -start_system_metrics_service(Node, Args) -> - distributed_helper:rpc( - Node, mongoose_service, ensure_started, [service_mongoose_system_metrics, Args]). +start_system_metrics_service(Node, ExtraOpts) -> + Opts = config([services, service_mongoose_system_metrics], ExtraOpts), + dynamic_services:ensure_started(Node, service_mongoose_system_metrics, Opts). disable_system_metrics(Node) -> - distributed_helper:rpc(Node, mongoose_service, ensure_stopped, [service_mongoose_system_metrics]), + dynamic_services:ensure_stopped(Node, service_mongoose_system_metrics), mongoose_helper:successful_rpc(Node, mongoose_config, unset_opt, [ google_analytics_url ]). delete_prev_client_id(Node) -> diff --git a/rel/files/mongooseim.toml b/rel/files/mongooseim.toml index bcc4e6d0464..129c0b00ef2 100644 --- a/rel/files/mongooseim.toml +++ b/rel/files/mongooseim.toml @@ -209,12 +209,8 @@ {{/outgoing_pools}} [services.service_admin_extra] - submods = ["node", "accounts", "sessions", "vcard", "gdpr", "upload", - "roster", "last", "private", "stanza", "stats", "domain"] [services.service_mongoose_system_metrics] - initial_report = 300_000 - periodic_report = 10_800_000 {{#service_domain_db}} [services.service_domain_db] diff --git a/src/admin_extra/service_admin_extra.erl b/src/admin_extra/service_admin_extra.erl index 413beeaeccb..098a935976f 100644 --- a/src/admin_extra/service_admin_extra.erl +++ b/src/admin_extra/service_admin_extra.erl @@ -41,12 +41,13 @@ %%% gen_mod %%% -start(Opts) -> - Submods = gen_mod:get_opt(submods, Opts, ?SUBMODS), +-spec start(mongoose_service:options()) -> ok. +start(#{submods := Submods}) -> lists:foreach(fun(Submod) -> ejabberd_commands:register_commands((mod_name(Submod)):commands()) end, Submods). +-spec stop() -> ok. stop() -> lists:foreach(fun(Submod) -> ejabberd_commands:unregister_commands((mod_name(Submod)):commands()) @@ -58,9 +59,10 @@ config_spec() -> items = #{<<"submods">> => #list{items = #option{type = atom, validate = {enum, ?SUBMODS}}, validate = unique} - } + }, + defaults = #{<<"submods">> => ?SUBMODS}, + format_items = map }. mod_name(ModAtom) -> list_to_existing_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(ModAtom)). - diff --git a/src/domain/mongoose_domain_api.erl b/src/domain/mongoose_domain_api.erl index 5f0a9458b51..1b5476e6711 100644 --- a/src/domain/mongoose_domain_api.erl +++ b/src/domain/mongoose_domain_api.erl @@ -31,15 +31,12 @@ -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 = mongoose_config:get_opt(host_types), - mongoose_domain_core:start(Pairs, AllowedHostTypes), + mongoose_domain_core:start(), mongoose_subdomain_core:start(), mongoose_lazy_routing:start(). @@ -183,11 +180,6 @@ check_domain(Domain, HostType) -> ok end. -%% Domains should be nameprepped using `jid:nameprep' --spec get_static_pairs() -> [pair()]. -get_static_pairs() -> - [{H, H} || H <- mongoose_config:get_opt(hosts)]. - -spec register_subdomain(host_type(), subdomain_pattern(), mongoose_packet_handler:t()) -> ok | {error, already_registered | subdomain_already_exists}. diff --git a/src/domain/mongoose_domain_core.erl b/src/domain/mongoose_domain_core.erl index 346091ef6c9..3cd55e5f45d 100644 --- a/src/domain/mongoose_domain_core.erl +++ b/src/domain/mongoose_domain_core.erl @@ -8,7 +8,7 @@ %% required for ets:fun2ms/1 pseudo function -include_lib("stdlib/include/ms_transform.hrl"). --export([start/2, stop/0]). +-export([start/0, start/2, stop/0]). -export([start_link/2]). -export([get_host_type/1]). -export([is_static/1]). @@ -34,13 +34,19 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --ignore_xref([get_start_args/0, start_link/2, stop/0]). +-ignore_xref([get_start_args/0, start_link/2, start/2, stop/0]). -define(TABLE, ?MODULE). -define(HOST_TYPE_TABLE, mongoose_domain_core_host_types). -type host_type() :: mongooseim:host_type(). -type domain() :: mongooseim:domain_name(). +-type pair() :: {domain(), host_type()}. + +start() -> + Pairs = get_static_pairs(), + AllowedHostTypes = mongoose_config:get_opt(host_types), + start(Pairs, AllowedHostTypes). -ifdef(TEST). @@ -176,6 +182,11 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %% internal functions %%-------------------------------------------------------------------- +%% Domains should be nameprepped using `jid:nameprep' +-spec get_static_pairs() -> [pair()]. +get_static_pairs() -> + [{H, H} || H <- mongoose_config:get_opt(hosts)]. + for_each_selected_domain('$end_of_table', _) -> ok; for_each_selected_domain({MatchList, Continuation}, Func) -> [safely:apply_and_log(Func, Args, log_context(Args)) || Args <- MatchList], diff --git a/src/domain/mongoose_domain_db_cleaner.erl b/src/domain/mongoose_domain_db_cleaner.erl index 708791e1475..17fc6c1813e 100644 --- a/src/domain/mongoose_domain_db_cleaner.erl +++ b/src/domain/mongoose_domain_db_cleaner.erl @@ -16,15 +16,10 @@ %% --------------------------------------------------------------------------- %% Config -default_cleaning_interval() -> - 1800. %% 30 minutes - -default_max_age() -> - 7200. %% 2 hours - %% --------------------------------------------------------------------------- %% Client code +-spec start(mongoose_service:options()) -> ok. start(Opts) -> ChildSpec = {?MODULE, @@ -33,6 +28,7 @@ start(Opts) -> supervisor:start_child(ejabberd_sup, ChildSpec), ok. +-spec stop() -> ok. stop() -> supervisor:terminate_child(ejabberd_sup, ?MODULE), supervisor:delete_child(ejabberd_sup, ?MODULE), @@ -44,9 +40,7 @@ start_link(Opts) -> %% --------------------------------------------------------------------------- %% Server callbacks -init(Opts) -> - Interval = proplists:get_value(event_cleaning_interval, Opts, default_cleaning_interval()), - MaxAge = proplists:get_value(event_max_age, Opts, default_max_age()), +init(#{event_cleaning_interval := Interval, event_max_age := MaxAge}) -> ?LOG_INFO(#{what => domain_cleaner_start, cleaning_interval => Interval, max_age => MaxAge}), State = #{max_age => MaxAge}, self() ! schedule_removal, diff --git a/src/domain/mongoose_domain_sql.erl b/src/domain/mongoose_domain_sql.erl index 8a9efde9075..c7d3e7eb824 100644 --- a/src/domain/mongoose_domain_sql.erl +++ b/src/domain/mongoose_domain_sql.erl @@ -18,16 +18,14 @@ insert_dummy_event/1]). %% interfaces only for integration tests --export([prepare_test_queries/0, - erase_database/0, +-export([prepare_test_queries/1, + erase_database/1, insert_full_event/2, insert_domain_settings_without_event/2]). --ignore_xref([erase_database/0, prepare_test_queries/0, get_enabled_dynamic/0, +-ignore_xref([erase_database/1, prepare_test_queries/1, get_enabled_dynamic/0, insert_full_event/2, insert_domain_settings_without_event/2]). --include("mongoose_logger.hrl"). - -import(mongoose_rdbms, [prepare/4, execute_successfully/3]). -type event_id() :: non_neg_integer(). @@ -35,9 +33,8 @@ -type row() :: {event_id(), domain(), mongooseim:host_type() | null}. -export_type([row/0]). -start(_Opts) -> +start(#{db_pool := Pool}) -> {LimitSQL, LimitMSSQL} = rdbms_queries:get_db_specific_limits_binaries(), - Pool = get_db_pool(), True = sql_true(Pool), %% Settings prepare(domain_insert_settings, domain_settings, [domain, host_type], @@ -85,8 +82,7 @@ start(_Opts) -> " ORDER BY domain_events.id ">>), ok. -prepare_test_queries() -> - Pool = get_db_pool(), +prepare_test_queries(Pool) -> True = sql_true(Pool), prepare(domain_erase_settings, domain_settings, [], <<"DELETE FROM domain_settings">>), @@ -160,8 +156,8 @@ select_from(FromId, Limit) -> Rows. get_enabled_dynamic() -> - prepare_test_queries(), Pool = get_db_pool(), + prepare_test_queries(Pool), {selected, Rows} = execute_successfully(Pool, domain_get_enabled_dynamic, []), Rows. @@ -232,8 +228,7 @@ insert_full_event_mssql(EventId, Domain) -> %% ---------------------------------------------------------------------------- %% For testing -erase_database() -> - Pool = get_db_pool(), +erase_database(Pool) -> execute_successfully(Pool, domain_erase_events, []), execute_successfully(Pool, domain_erase_settings, []). @@ -298,10 +293,7 @@ row_to_map({HostType, Enabled}) -> #{host_type => HostType, enabled => mongoose_rdbms:to_bool(Enabled)}. get_db_pool() -> - proplists:get_value(db_pool, get_service_opts(), global). - -get_service_opts() -> - mongoose_service:get_service_opts(service_domain_db). + mongoose_config:get_opt([services, service_domain_db, db_pool]). transaction(F) -> transaction(F, 3, []). diff --git a/src/domain/service_domain_db.erl b/src/domain/service_domain_db.erl index b37d0dab1ba..a2c1e467b6b 100644 --- a/src/domain/service_domain_db.erl +++ b/src/domain/service_domain_db.erl @@ -27,6 +27,7 @@ %% --------------------------------------------------------------------------- %% Client code +-spec start(mongoose_service:options()) -> ok. start(Opts) -> mongoose_domain_sql:start(Opts), ChildSpec = @@ -37,6 +38,7 @@ start(Opts) -> mongoose_domain_db_cleaner:start(Opts), ok. +-spec stop() -> ok. stop() -> mongoose_domain_db_cleaner:stop(), supervisor:terminate_child(ejabberd_sup, ?MODULE), @@ -54,14 +56,17 @@ restart() -> -spec config_spec() -> mongoose_config_spec:config_section(). config_spec() -> - #section{items = #{ - <<"event_cleaning_interval">> => #option{type = integer, - validate = positive}, - <<"event_max_age">> => #option{type = integer, - validate = positive}, - <<"db_pool">> => #option{type = atom, - validate = pool_name} - }}. + #section{items = #{<<"event_cleaning_interval">> => #option{type = integer, + validate = positive}, + <<"event_max_age">> => #option{type = integer, + validate = positive}, + <<"db_pool">> => #option{type = atom, + validate = pool_name} + }, + defaults = #{<<"event_cleaning_interval">> => 1800, % 30 minutes + <<"event_max_age">> => 7200, % 2 hours + <<"db_pool">> => global}, + format_items = map}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). diff --git a/src/mongoose_cluster.erl b/src/mongoose_cluster.erl index d072d0ed6f2..79e31d343e0 100644 --- a/src/mongoose_cluster.erl +++ b/src/mongoose_cluster.erl @@ -2,7 +2,7 @@ %% This is a library module for cluster management: joining / leaving a cluster. -%% TODO: it might make sense to expose this stuff as mod_admin_extra_cluster +%% TODO: it might make sense to expose this stuff as service_admin_extra_cluster -export([join/1, leave/0, remove_from_cluster/1, is_node_alive/1]). diff --git a/src/mongoose_service.erl b/src/mongoose_service.erl index 6c90728b85c..c00a1ff784c 100644 --- a/src/mongoose_service.erl +++ b/src/mongoose_service.erl @@ -24,16 +24,15 @@ config_spec/1, get_deps/1, is_loaded/1, - assert_loaded/1, - get_service_opts/1]). + assert_loaded/1]). %% Shell utilities -export([loaded_services_with_opts/0]). %% Service management utilities for tests --export([ensure_stopped/1, ensure_started/2]). +-export([replace_services/2, ensure_stopped/1, ensure_started/2]). --ignore_xref([loaded_services_with_opts/0, ensure_stopped/1, ensure_started/2]). +-ignore_xref([loaded_services_with_opts/0, replace_services/2, ensure_stopped/1, ensure_started/2]). -type service() :: module(). -type opt_key() :: atom(). @@ -53,16 +52,41 @@ -optional_callbacks([deps/0]). +%% @doc Start all configured services in the dependency order. -spec start() -> ok. start() -> [start_service(Service, Opts) || {Service, Opts} <- sorted_services()], ok. +%% @doc Stop all configured services in the reverse dependency order +%% to avoid stopping services which have other services dependent on them. -spec stop() -> ok. stop() -> [stop_service(Service) || {Service, _Opts} <- lists:reverse(sorted_services())], ok. +%% @doc Replace services at runtime - only for testing and debugging. +%% Running services from ToStop are stopped and services from ToEnsure are (re)started when needed. +%% Unused dependencies are stopped if no running services depend on them anymore. +%% To prevent an unused dependency from being stopped, you need to include it in ToEnsure. +-spec replace_services([service()], service_map()) -> ok. +replace_services(ToStop, ToEnsure) -> + Current = loaded_services_with_opts(), + Old = maps:with(ToStop ++ maps:keys(ToEnsure), Current), + OldWithDeps = mongoose_service_deps:resolve_deps(Old), + SortedOldWithDeps = mongoose_service_deps:sort_deps(OldWithDeps), + WithoutOld = maps:without(maps:keys(OldWithDeps), Current), + WithNew = maps:merge(WithoutOld, ToEnsure), + Target = mongoose_service_deps:resolve_deps(WithNew), + + %% Stop each affected service if it is not in Target (stop deps first) + [ensure_stopped(Service) || {Service, _} <- lists:reverse(SortedOldWithDeps), + not maps:is_key(Service, Target)], + + %% Ensure each service from Target + [ensure_started(Service, Opts) || {Service, Opts} <- mongoose_service_deps:sort_deps(Target)], + ok. + -spec config_spec(service()) -> mongoose_config_spec:config_section(). config_spec(Service) -> Service:config_spec(). @@ -173,10 +197,6 @@ is_loaded(Service) -> {error, not_found} -> false end. --spec get_service_opts(service()) -> options(). -get_service_opts(Service) -> - mongoose_config:get_opt([services, Service], []). - -spec loaded_services_with_opts() -> service_map(). loaded_services_with_opts() -> mongoose_config:get_opt(services). diff --git a/src/system_metrics/service_mongoose_system_metrics.erl b/src/system_metrics/service_mongoose_system_metrics.erl index a381783e52b..72e1f34da1c 100644 --- a/src/system_metrics/service_mongoose_system_metrics.erl +++ b/src/system_metrics/service_mongoose_system_metrics.erl @@ -6,8 +6,6 @@ -include("mongoose_config_spec.hrl"). --define(DEFAULT_INITIAL_REPORT, timer:minutes(5)). --define(DEFAULT_REPORT_AFTER, timer:hours(3)). -ifdef(PROD_NODE). -define(TRACKING_ID, "UA-151671255-3"). -else. @@ -25,9 +23,6 @@ handle_info/2, terminate/2]). -%% config spec callbacks --export([process_report_option/1]). - -export([verify_if_configured/0]). -ignore_xref([start_link/1]). @@ -56,9 +51,9 @@ verify_if_configured() -> ok end. --spec start(proplists:proplist()) -> {ok, pid()}. -start(Args) -> - Spec = {?MODULE, {?MODULE, start_link, [Args]}, temporary, brutal_kill, worker, [?MODULE]}, +-spec start(mongoose_service:options()) -> {ok, pid()}. +start(Opts) -> + Spec = {?MODULE, {?MODULE, start_link, [Opts]}, temporary, brutal_kill, worker, [?MODULE]}, {ok, _} = ejabberd_sup:start_child(Spec). -spec stop() -> ok. @@ -72,29 +67,28 @@ config_spec() -> validate = non_negative}, <<"periodic_report">> => #option{type = integer, validate = non_negative}, - <<"report">> => #option{type = boolean, - process = fun ?MODULE:process_report_option/1, - wrap = item}, + <<"report">> => #option{type = boolean}, <<"tracking_id">> => #option{type = string, validate = non_empty} - } + }, + defaults = #{<<"initial_report">> => timer:minutes(5), + <<"periodic_report">> => timer:hours(3)}, + format_items = map }. -process_report_option(true) -> report; -process_report_option(false) -> no_report. - --spec start_link(proplists:proplist()) -> {ok, pid()}. -start_link(Args) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []). +-spec start_link(mongoose_service:options()) -> {ok, pid()}. +start_link(Opts) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []). --spec init(proplists:proplist()) -> {ok, system_metrics_state()}. -init(Args) -> - case report_transparency(Args) of +-spec init(mongoose_service:options()) -> {ok, system_metrics_state()}. +init(Opts) -> + case report_transparency(Opts) of skip -> ignore; continue -> - {InitialReport, ReportAfter, TrackingIds} = metrics_module_config(Args), + #{initial_report := InitialReport, periodic_report := PeriodicReport} = Opts, + TrackingIds = tracking_ids(Opts), erlang:send_after(InitialReport, self(), spawn_reporter), - {ok, #system_metrics_state{report_after = ReportAfter, + {ok, #system_metrics_state{report_after = PeriodicReport, tracking_ids = TrackingIds}} end. @@ -142,41 +136,29 @@ get_client_id() -> {ok, ID} when is_binary(ID) -> {ok, binary_to_list(ID)} end. --spec metrics_module_config(list()) -> {non_neg_integer(), non_neg_integer(), [tracking_id()]}. -metrics_module_config(Args) -> - {InitialReport, ReportAfter, TrackingId} = get_config(Args, os:getenv("CI")), - TrackingIds = case proplists:lookup(tracking_id, Args) of - none -> [TrackingId]; - {_, ExtraTrackingId} -> [TrackingId, ExtraTrackingId] - end, - {InitialReport, ReportAfter, TrackingIds}. - -get_config(Args, "true") -> - I = proplists:get_value(initial_report, Args, timer:seconds(20)), - R = proplists:get_value(periodic_report, Args, timer:minutes(5)), - {I, R, ?TRACKING_ID_CI}; -get_config(Args, _) -> - I = proplists:get_value(initial_report, Args, ?DEFAULT_INITIAL_REPORT), - R = proplists:get_value(periodic_report, Args, ?DEFAULT_REPORT_AFTER), - {I, R, ?TRACKING_ID}. - --spec report_transparency(proplists:proplist()) -> skip | continue. -report_transparency(Args) -> - case {explicit_no_report(Args), explicit_gathering_agreement(Args)} of - {true, _} -> skip; - {_, true} -> continue; - {_, _} -> - File = mongoose_system_metrics_file:location(), - Text = iolist_to_binary(io_lib:format(msg_accept_terms_and_conditions(), [File])), - ?LOG_WARNING(#{what => report_transparency, - text => Text, report_filename => File}), - continue +-spec tracking_ids(mongoose_service:options()) -> [tracking_id()]. +tracking_ids(#{tracking_id := ExtraTrackingId}) -> + [predefined_tracking_id(), ExtraTrackingId]; +tracking_ids(#{}) -> + [predefined_tracking_id()]. + +-spec predefined_tracking_id() -> tracking_id(). +predefined_tracking_id() -> + case os:getenv("CI") of + "true" -> ?TRACKING_ID_CI; + _ -> ?TRACKING_ID end. -explicit_no_report(Args) -> - proplists:get_value(no_report, Args, false). -explicit_gathering_agreement(Args) -> - proplists:get_value(report, Args, false). +-spec report_transparency(mongoose_service:options()) -> skip | continue. +report_transparency(#{report := false}) -> + skip; +report_transparency(#{report := true}) -> + continue; +report_transparency(#{}) -> + File = mongoose_system_metrics_file:location(), + Text = iolist_to_binary(io_lib:format(msg_accept_terms_and_conditions(), [File])), + ?LOG_WARNING(#{what => report_transparency, text => Text, report_filename => File}), + continue. % %%----------------------------------------- % %% Unused diff --git a/test/common/config_parser_helper.erl b/test/common/config_parser_helper.erl index 7c154ac0dad..ecbe52f542b 100644 --- a/test/common/config_parser_helper.erl +++ b/test/common/config_parser_helper.erl @@ -21,7 +21,9 @@ options("host_types") -> {rdbms_server_type, generic}, {registration_timeout, 600}, {routing_modules, mongoose_router:default_routing_modules()}, - {services, #{}}, + {services, #{service_domain_db => config([services, service_domain_db], + #{event_cleaning_interval => 1000, + event_max_age => 5000})}}, {sm_backend, {mnesia, []}}, {{s2s, <<"another host type">>}, default_s2s()}, {{s2s, <<"localhost">>}, default_s2s()}, @@ -80,10 +82,10 @@ options("miscellaneous") -> {routing_modules, [mongoose_router_global, mongoose_router_localdomain]}, {services, - #{service_mongoose_system_metrics => [{initial_report, 300000}, - {periodic_report, 10800000}, - {report, true}, - {tracking_id, "UA-123456789"}]}}, + #{service_mongoose_system_metrics => #{initial_report => 20000, + periodic_report => 300000, + report => true, + tracking_id => "UA-123456789"}}}, {{s2s, <<"anonymous.localhost">>}, default_s2s()}, {{s2s, <<"localhost">>}, default_s2s()}, {sm_backend, {mnesia, []}}, @@ -246,13 +248,11 @@ options("mongooseim-pgsql") -> {routing_modules, mongoose_router:default_routing_modules()}, {services, #{service_admin_extra => - [{submods, - [node, accounts, sessions, vcard, gdpr, upload, roster, last, private, - stanza, stats]}], + #{submods => [node, accounts, sessions, vcard, gdpr, upload, + roster, last, private, stanza, stats]}, service_mongoose_system_metrics => - [{initial_report, 300000}, - {periodic_report, 10800000}] - }}, + #{initial_report => 300000, + periodic_report => 10800000}}}, {sm_backend, {mnesia, []}}, {{auth, <<"anonymous.localhost">>}, (default_auth())#{anonymous => #{allow_multiple_connections => true, @@ -1167,7 +1167,17 @@ default_config([modules, mod_vcard, ldap]) -> % included when backend => ldap {<<"Organization Name">>, <<"ORGNAME">>}, {<<"Organization Unit">>, <<"ORGUNIT">>}], search_operator => 'and', - binary_search_fields => []}. + binary_search_fields => []}; +default_config([services, service_admin_extra]) -> + #{submods => [node, accounts, sessions, vcard, roster, last, + private, stanza, stats, gdpr, upload, domain]}; +default_config([services, service_domain_db]) -> + #{event_cleaning_interval => 1800, + event_max_age => 7200, + db_pool => global}; +default_config([services, service_mongoose_system_metrics]) -> + #{initial_report => timer:minutes(5), + periodic_report => timer:hours(3)}. common_mam_config() -> #{no_stanzaid_element => false, diff --git a/test/config_parser_SUITE.erl b/test/config_parser_SUITE.erl index 623f06c7c05..6ecbca6c7da 100644 --- a/test/config_parser_SUITE.erl +++ b/test/config_parser_SUITE.erl @@ -259,6 +259,7 @@ groups() -> modules_without_config, incorrect_module]}, {services, [parallel], [service_admin_extra, + service_domain_db, service_mongoose_system_metrics]} ]. @@ -3103,34 +3104,39 @@ incorrect_module(_Config) -> %% Services service_admin_extra(_Config) -> + P = [services, service_admin_extra], T = fun(Opts) -> #{<<"services">> => #{<<"service_admin_extra">> => Opts}} end, - ?cfg(servopts(service_admin_extra, [{submods, [node]}]), - T(#{<<"submods">> => [<<"node">>]})), + ?cfg(P, default_config(P), T(#{})), + ?cfg(P ++ [submods], [node], T(#{<<"submods">> => [<<"node">>]})), ?err(T(#{<<"submods">> => 1})), ?err(T(#{<<"submods">> => [1]})), - ?err(T(#{<<"submods">> => [<<"nodejshaha">>]})), - ok. + ?err(T(#{<<"submods">> => [<<"nodejshaha">>]})). + +service_domain_db(_Config) -> + P = [services, service_domain_db], + T = fun(Opts) -> #{<<"services">> => #{<<"service_domain_db">> => Opts}} end, + ?cfg(P, default_config(P), T(#{})), + ?cfg(P ++ [event_cleaning_interval], 1000, T(#{<<"event_cleaning_interval">> => 1000})), + ?cfg(P ++ [event_max_age], 5000, T(#{<<"event_max_age">> => 5000})), + ?cfg(P ++ [db_pool], my_pool, T(#{<<"db_pool">> => <<"my_pool">>})), + ?err(T(#{<<"event_cleaning_interval">> => 0})), + ?err(T(#{<<"event_max_age">> => 0})), + ?err(T(#{<<"db_pool">> => 10})). service_mongoose_system_metrics(_Config) -> - M = service_mongoose_system_metrics, + P = [services, service_mongoose_system_metrics], T = fun(Opts) -> #{<<"services">> => #{<<"service_mongoose_system_metrics">> => Opts}} end, - ?cfg(servopts(M, [{initial_report, 5000}]), - T(#{<<"initial_report">> => 5000})), - ?cfg(servopts(M, [{periodic_report, 5000}]), - T(#{<<"periodic_report">> => 5000})), - ?cfg(servopts(M, [{tracking_id, "UA-123456789"}]), - T(#{<<"tracking_id">> => <<"UA-123456789">>})), - ?cfg(servopts(M, [{report, true}]), - T(#{<<"report">> => true})), - ?cfg(servopts(M, [{no_report, true}]), - T(#{<<"report">> => false})), - %% error cases + ?cfg(P, default_config(P), T(#{})), + ?cfg(P ++ [initial_report], 5000, T(#{<<"initial_report">> => 5000})), + ?cfg(P ++ [periodic_report], 5000, T(#{<<"periodic_report">> => 5000})), + ?cfg(P ++ [tracking_id], "UA-123456789", T(#{<<"tracking_id">> => <<"UA-123456789">>})), + ?cfg(P ++ [report], true, T(#{<<"report">> => true})), ?err(T(#{<<"initial_report">> => <<"forever">>})), ?err(T(#{<<"periodic_report">> => <<"forever">>})), ?err(T(#{<<"initial_report">> => -1})), ?err(T(#{<<"periodic_report">> => -1})), ?err(T(#{<<"tracking_id">> => 666})), - ok. + ?err(T(#{<<"report">> => <<"maybe">>})). %% Helpers for module tests diff --git a/test/config_parser_SUITE_data/host_types.toml b/test/config_parser_SUITE_data/host_types.toml index 8cac8700493..b6ad50657f8 100644 --- a/test/config_parser_SUITE_data/host_types.toml +++ b/test/config_parser_SUITE_data/host_types.toml @@ -16,6 +16,10 @@ [modules.mod_amp] +[services.service_domain_db] + event_cleaning_interval = 1000 + event_max_age = 5000 + [[host_config]] host_type = "this is host type" ## this resets the modules for this host diff --git a/test/config_parser_SUITE_data/miscellaneous.toml b/test/config_parser_SUITE_data/miscellaneous.toml index 4f1cbe64bcd..6578b87df8d 100644 --- a/test/config_parser_SUITE_data/miscellaneous.toml +++ b/test/config_parser_SUITE_data/miscellaneous.toml @@ -82,6 +82,6 @@ [services.service_mongoose_system_metrics] report = true - initial_report = 300_000 - periodic_report = 10_800_000 + initial_report = 20_000 + periodic_report = 300_000 tracking_id = "UA-123456789" diff --git a/test/mongoose_service_SUITE.erl b/test/mongoose_service_SUITE.erl index 896aa1217bb..a8454e7a7cc 100644 --- a/test/mongoose_service_SUITE.erl +++ b/test/mongoose_service_SUITE.erl @@ -8,7 +8,11 @@ all() -> [starts_and_stops_services, ensures_service, reverts_config_when_service_fails_to_start, - does_not_change_config_when_service_fails_to_stop]. + does_not_change_config_when_service_fails_to_stop, + replaces_services, + replaces_services_with_new_deps, + replaces_services_with_old_deps, + replaces_services_with_same_deps]. init_per_suite(C) -> C. @@ -64,6 +68,68 @@ does_not_change_config_when_service_fails_to_stop(_Config) -> ?assertError(something_awful, mongoose_service:ensure_stopped(service_a)), ?assertEqual(#{service_a => #{}}, get_services()). +replaces_services(_Config) -> + set_services(Services = #{service_a => #{}, service_b => #{opt => val}, service_c => #{}}), + ok = mongoose_service:start(), + check_started(maps:to_list(Services)), + + %% Stop service_a, change opts for service_b, do not change service_c, start service_d + NewServices = #{service_b => #{new_opt => new_val}, service_c => #{}, service_d => #{}}, + ok = mongoose_service:replace_services([service_a], NewServices), + check_stopped([service_a, service_b]), + check_not_stopped([service_c]), + check_started([{service_b, #{new_opt => new_val}}, {service_d, #{}}]), + ?assertEqual(NewServices, get_services()), + + ok = mongoose_service:stop(), + check_stopped([service_b, service_c, service_d]). + +replaces_services_with_new_deps(_Config) -> + set_deps(#{service_b => [service_c]}), + set_services(Services = #{service_a => #{}}), + ok = mongoose_service:start(), + check_started(maps:to_list(Services)), + + %% Start service_b, which depends on service_c + ok = mongoose_service:replace_services([], #{service_b => #{}}), + check_not_stopped([service_a]), + check_started([{service_b, #{}}, {service_c, #{}}]), + ?assertEqual(Services#{service_b => #{}, service_c => #{}}, get_services()), + + ok = mongoose_service:stop(), + check_stopped([service_a, service_b, service_c]). + +replaces_services_with_old_deps(_Config) -> + set_deps(#{service_a => [service_c]}), + set_services(Services = #{service_a => #{}, service_c => #{}}), + ok = mongoose_service:start(), + check_started(maps:to_list(Services)), + + %% Stop service_a, which depends on service_c, and start service_b + ok = mongoose_service:replace_services([service_a], #{service_b => #{}}), + check_stopped([service_a, service_c]), + check_started([{service_b, #{}}]), + ?assertEqual(#{service_b => #{}}, get_services()), + + ok = mongoose_service:stop(), + check_stopped([service_b]). + +replaces_services_with_same_deps(_Config) -> + set_deps(#{service_a => [service_c], service_b => [service_c]}), + set_services(Services = #{service_a => #{}, service_c => #{}}), + ok = mongoose_service:start(), + check_started(maps:to_list(Services)), + + %% Stop service_a, and start service_b, both depending on service_c + ok = mongoose_service:replace_services([service_a], #{service_b => #{}}), + check_stopped([service_a]), + check_not_stopped([service_c]), + check_started([{service_b, #{}}]), + ?assertEqual(#{service_b => #{}, service_c => #{}}, get_services()), + + ok = mongoose_service:stop(), + check_stopped([service_b]). + %% Helpers set_services(Services) -> @@ -82,10 +148,19 @@ check_stopped(Services) -> ?assert(meck:called(Service, stop, [])) end, Services). +check_not_stopped(Services) -> + lists:foreach(fun(Service) -> + ?assertNot(meck:called(Service, stop, [])) + end, Services). + mock_service(Service) -> meck:new(Service, [non_strict]), meck:expect(Service, start, fun(_) -> start_result end), meck:expect(Service, stop, fun() -> ok end). +set_deps(DepsMap) -> + maps:fold(fun(Service, Deps, _) -> meck:expect(Service, deps, fun() -> Deps end) end, + undefined, DepsMap). + test_services() -> [service_a, service_b, service_c, service_d].