diff --git a/.github/workflows/run_emqx_app_tests.yaml b/.github/workflows/run_emqx_app_tests.yaml index 24c3d2b42..67d58a179 100644 --- a/.github/workflows/run_emqx_app_tests.yaml +++ b/.github/workflows/run_emqx_app_tests.yaml @@ -76,5 +76,5 @@ jobs: - uses: actions/upload-artifact@v3 if: failure() with: - name: logs + name: logs-${{ matrix.runs-on }} path: apps/emqx/_build/test/logs diff --git a/apps/emqx/src/emqx_config.erl b/apps/emqx/src/emqx_config.erl index 9434cdc52..6e36e9fa0 100644 --- a/apps/emqx/src/emqx_config.erl +++ b/apps/emqx/src/emqx_config.erl @@ -855,4 +855,4 @@ maybe_update_zone([RootName | _T] = Path, Value) -> -spec zone_roots() -> [atom()]. zone_roots() -> - lists:map(fun atom/1, emqx_zone_schema:roots()). + lists:map(fun list_to_atom/1, emqx_zone_schema:roots()). diff --git a/apps/emqx/test/emqx_config_SUITE.erl b/apps/emqx/test/emqx_config_SUITE.erl index a55531c2d..4f034676f 100644 --- a/apps/emqx/test/emqx_config_SUITE.erl +++ b/apps/emqx/test/emqx_config_SUITE.erl @@ -19,6 +19,7 @@ -compile(export_all). -compile(nowarn_export_all). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). all() -> emqx_common_test_helpers:all(?MODULE). @@ -96,3 +97,408 @@ t_unknown_rook_keys(_) -> end ), ok. + +t_init_load_emqx_schema(Config) -> + emqx_config:erase_all(), + %% Given empty config file + ConfFile = prepare_conf_file(?FUNCTION_NAME, <<"">>, Config), + application:set_env(emqx, config_files, [ConfFile]), + %% When load emqx_schema + ?assertEqual(ok, emqx_config:init_load(emqx_schema)), + %% Then default zone is injected with all global defaults + Default = emqx_config:get([zones, default]), + ?assertMatch( + #{ + mqtt := _, + stats := _, + flapping_detect := _, + force_shutdown := _, + conn_congestion := _, + force_gc := _, + overload_protection := _ + }, + Default + ). + +t_init_zones_load_emqx_schema_no_default(Config) -> + emqx_config:erase_all(), + %% Given empty config file + ConfFile = prepare_conf_file(?FUNCTION_NAME, <<"">>, Config), + application:set_env(emqx, config_files, [ConfFile]), + %% When load emqx_schema + ?assertEqual(ok, emqx_config:init_load(emqx_schema)), + %% Then read for none existing zone should throw error + ?assertError( + {config_not_found, [zones, no_exists]}, + emqx_config:get([zones, no_exists]) + ). + +t_init_zones_load_other_schema(Config) -> + emqx_config:erase_all(), + %% Given empty config file + ConfFile = prepare_conf_file(?FUNCTION_NAME, <<"">>, Config), + application:set_env(emqx, config_files, [ConfFile]), + %% When load schema other than emqx_schema + %% Then load should success + ?assertEqual(ok, emqx_config:init_load(emqx_limiter_schema)), + %% Then no default zone is loaded. + ?assertError( + {config_not_found, [zones, default]}, + emqx_config:get([zones, default]) + ). + +t_init_zones_with_user_defined_default_zone(Config) -> + emqx_config:erase_all(), + %% Given user defined config for default zone + ConfFile = prepare_conf_file( + ?FUNCTION_NAME, <<"zones.default.mqtt.max_topic_alias=1024">>, Config + ), + application:set_env(emqx, config_files, [ConfFile]), + %% When load schema + ?assertEqual(ok, emqx_config:init_load(emqx_schema)), + %% Then user defined value is set and others are defaults + + ?assertMatch( + #{ + conn_congestion := + #{enable_alarm := true, min_alarm_sustain_duration := 60000}, + flapping_detect := + #{ban_time := 300000, max_count := 15, window_time := disabled}, + force_gc := + #{bytes := 16777216, count := 16000, enable := true}, + force_shutdown := + #{ + enable := true, + max_heap_size := 4194304, + max_mailbox_size := 1000 + }, + mqtt := + #{ + await_rel_timeout := 300000, + exclusive_subscription := false, + idle_timeout := 15000, + ignore_loop_deliver := false, + keepalive_backoff := 0.75, + keepalive_multiplier := 1.5, + max_awaiting_rel := 100, + max_clientid_len := 65535, + max_inflight := 32, + max_mqueue_len := 1000, + max_packet_size := 1048576, + max_qos_allowed := 2, + max_subscriptions := infinity, + %% <=== here! + max_topic_alias := 1024, + max_topic_levels := 128, + mqueue_default_priority := lowest, + mqueue_priorities := disabled, + mqueue_store_qos0 := true, + peer_cert_as_clientid := disabled, + peer_cert_as_username := disabled, + response_information := [], + retain_available := true, + retry_interval := 30000, + server_keepalive := disabled, + session_expiry_interval := 7200000, + shared_subscription := true, + strict_mode := false, + upgrade_qos := false, + use_username_as_clientid := false, + wildcard_subscription := true + }, + overload_protection := + #{ + backoff_delay := 1, + backoff_gc := false, + backoff_hibernation := true, + backoff_new_conn := true, + enable := false + }, + stats := #{enable := true} + }, + emqx_config:get([zones, default]) + ). + +t_init_zones_with_user_defined_other_zone(Config) -> + emqx_config:erase_all(), + %% Given user defined config for default zone + ConfFile = prepare_conf_file( + ?FUNCTION_NAME, <<"zones.myzone.mqtt.max_topic_alias=1024">>, Config + ), + application:set_env(emqx, config_files, [ConfFile]), + %% When load schema + ?assertEqual(ok, emqx_config:init_load(emqx_schema)), + %% Then user defined value is set and others are defaults + ?assertMatch( + #{ + conn_congestion := + #{enable_alarm := true, min_alarm_sustain_duration := 60000}, + flapping_detect := + #{ban_time := 300000, max_count := 15, window_time := disabled}, + force_gc := + #{bytes := 16777216, count := 16000, enable := true}, + force_shutdown := + #{ + enable := true, + max_heap_size := 4194304, + max_mailbox_size := 1000 + }, + mqtt := + #{ + await_rel_timeout := 300000, + exclusive_subscription := false, + idle_timeout := 15000, + ignore_loop_deliver := false, + keepalive_backoff := 0.75, + keepalive_multiplier := 1.5, + max_awaiting_rel := 100, + max_clientid_len := 65535, + max_inflight := 32, + max_mqueue_len := 1000, + max_packet_size := 1048576, + max_qos_allowed := 2, + max_subscriptions := infinity, + %% <=== here! + max_topic_alias := 1024, + max_topic_levels := 128, + mqueue_default_priority := lowest, + mqueue_priorities := disabled, + mqueue_store_qos0 := true, + peer_cert_as_clientid := disabled, + peer_cert_as_username := disabled, + response_information := [], + retain_available := true, + retry_interval := 30000, + server_keepalive := disabled, + session_expiry_interval := 7200000, + shared_subscription := true, + strict_mode := false, + upgrade_qos := false, + use_username_as_clientid := false, + wildcard_subscription := true + }, + overload_protection := + #{ + backoff_delay := 1, + backoff_gc := false, + backoff_hibernation := true, + backoff_new_conn := true, + enable := false + }, + stats := #{enable := true} + }, + emqx_config:get([zones, myzone]) + ), + + %% Then default zone still have the defaults + ?assertMatch( + #{ + conn_congestion := + #{enable_alarm := true, min_alarm_sustain_duration := 60000}, + flapping_detect := + #{ban_time := 300000, max_count := 15, window_time := disabled}, + force_gc := + #{bytes := 16777216, count := 16000, enable := true}, + force_shutdown := + #{ + enable := true, + max_heap_size := 4194304, + max_mailbox_size := 1000 + }, + mqtt := + #{ + await_rel_timeout := 300000, + exclusive_subscription := false, + idle_timeout := 15000, + ignore_loop_deliver := false, + keepalive_backoff := 0.75, + keepalive_multiplier := 1.5, + max_awaiting_rel := 100, + max_clientid_len := 65535, + max_inflight := 32, + max_mqueue_len := 1000, + max_packet_size := 1048576, + max_qos_allowed := 2, + max_subscriptions := infinity, + max_topic_alias := 65535, + max_topic_levels := 128, + mqueue_default_priority := lowest, + mqueue_priorities := disabled, + mqueue_store_qos0 := true, + peer_cert_as_clientid := disabled, + peer_cert_as_username := disabled, + response_information := [], + retain_available := true, + retry_interval := 30000, + server_keepalive := disabled, + session_expiry_interval := 7200000, + shared_subscription := true, + strict_mode := false, + upgrade_qos := false, + use_username_as_clientid := false, + wildcard_subscription := true + }, + overload_protection := + #{ + backoff_delay := 1, + backoff_gc := false, + backoff_hibernation := true, + backoff_new_conn := true, + enable := false + }, + stats := #{enable := true} + }, + emqx_config:get([zones, default]) + ). + +t_init_zones_with_cust_root_mqtt(Config) -> + emqx_config:erase_all(), + %% Given user defined non default mqtt schema in config file + ConfFile = prepare_conf_file(?FUNCTION_NAME, <<"mqtt.retry_interval=600000">>, Config), + application:set_env(emqx, config_files, [ConfFile]), + %% When emqx_schema is loaded + ?assertEqual(ok, emqx_config:init_load(emqx_schema)), + %% Then the value is reflected in default `zone' and other fields under mqtt are default. + ?assertMatch( + #{ + await_rel_timeout := 300000, + exclusive_subscription := false, + idle_timeout := 15000, + ignore_loop_deliver := false, + keepalive_backoff := 0.75, + keepalive_multiplier := 1.5, + max_awaiting_rel := 100, + max_clientid_len := 65535, + max_inflight := 32, + max_mqueue_len := 1000, + max_packet_size := 1048576, + max_qos_allowed := 2, + max_subscriptions := infinity, + max_topic_alias := 65535, + max_topic_levels := 128, + mqueue_default_priority := lowest, + mqueue_priorities := disabled, + mqueue_store_qos0 := true, + peer_cert_as_clientid := disabled, + peer_cert_as_username := disabled, + response_information := [], + retain_available := true, + %% <=== here + retry_interval := 600000, + server_keepalive := disabled, + session_expiry_interval := 7200000, + shared_subscription := true, + strict_mode := false, + upgrade_qos := false, + use_username_as_clientid := false, + wildcard_subscription := true + }, + emqx_config:get([zones, default, mqtt]) + ). + +t_default_zone_is_updated_after_global_defaults_updated(Config) -> + emqx_config:erase_all(), + %% Given user defined non default mqtt schema in config file + ConfFile = prepare_conf_file(?FUNCTION_NAME, <<"">>, Config), + application:set_env(emqx, config_files, [ConfFile]), + ?assertEqual(ok, emqx_config:init_load(emqx_schema)), + ?assertNotEqual(900000, emqx_config:get([zones, default, mqtt, retry_interval])), + %% When emqx_schema is loaded + emqx_config:put([mqtt, retry_interval], 900000), + %% Then the value is reflected in default `zone' and other fields under mqtt are default. + ?assertMatch( + #{ + await_rel_timeout := 300000, + exclusive_subscription := false, + idle_timeout := 15000, + ignore_loop_deliver := false, + keepalive_backoff := 0.75, + keepalive_multiplier := 1.5, + max_awaiting_rel := 100, + max_clientid_len := 65535, + max_inflight := 32, + max_mqueue_len := 1000, + max_packet_size := 1048576, + max_qos_allowed := 2, + max_subscriptions := infinity, + max_topic_alias := 65535, + max_topic_levels := 128, + mqueue_default_priority := lowest, + mqueue_priorities := disabled, + mqueue_store_qos0 := true, + peer_cert_as_clientid := disabled, + peer_cert_as_username := disabled, + response_information := [], + retain_available := true, + %% <=== here + retry_interval := 900000, + server_keepalive := disabled, + session_expiry_interval := 7200000, + shared_subscription := true, + strict_mode := false, + upgrade_qos := false, + use_username_as_clientid := false, + wildcard_subscription := true + }, + emqx_config:get([zones, default, mqtt]) + ). + +t_other_zone_is_updated_after_global_defaults_updated(Config) -> + emqx_config:erase_all(), + %% Given user defined non default mqtt schema in config file + ConfFile = prepare_conf_file(?FUNCTION_NAME, <<"zones.myzone.mqtt.max_inflight=32">>, Config), + application:set_env(emqx, config_files, [ConfFile]), + ?assertEqual(ok, emqx_config:init_load(emqx_schema)), + ?assertNotEqual(900000, emqx_config:get([zones, myzone, mqtt, retry_interval])), + %% When emqx_schema is loaded + emqx_config:put([mqtt, retry_interval], 900000), + %% Then the value is reflected in default `zone' and other fields under mqtt are default. + ?assertMatch( + #{ + await_rel_timeout := 300000, + exclusive_subscription := false, + idle_timeout := 15000, + ignore_loop_deliver := false, + keepalive_backoff := 0.75, + keepalive_multiplier := 1.5, + max_awaiting_rel := 100, + max_clientid_len := 65535, + max_inflight := 32, + max_mqueue_len := 1000, + max_packet_size := 1048576, + max_qos_allowed := 2, + max_subscriptions := infinity, + max_topic_alias := 65535, + max_topic_levels := 128, + mqueue_default_priority := lowest, + mqueue_priorities := disabled, + mqueue_store_qos0 := true, + peer_cert_as_clientid := disabled, + peer_cert_as_username := disabled, + response_information := [], + retain_available := true, + %% <=== here + retry_interval := 900000, + server_keepalive := disabled, + session_expiry_interval := 7200000, + shared_subscription := true, + strict_mode := false, + upgrade_qos := false, + use_username_as_clientid := false, + wildcard_subscription := true + }, + emqx_config:get([zones, myzone, mqtt]) + ). + +%%% +%%% Helpers +%%% +prepare_conf_file(Name, Content, CTConfig) -> + Filename = tc_conf_file(Name, CTConfig), + filelib:ensure_dir(Filename), + ok = file:write_file(Filename, Content), + Filename. + +tc_conf_file(TC, Config) -> + DataDir = ?config(data_dir, Config), + filename:join([DataDir, TC, 'emqx.conf']).