emqx_config.erl 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_config).
  17. -compile({no_auto_import, [get/0, get/1, put/2, erase/1]}).
  18. -elvis([{elvis_style, god_modules, disable}]).
  19. -include("logger.hrl").
  20. -include("emqx.hrl").
  21. -include("emqx_schema.hrl").
  22. -include_lib("snabbkaffe/include/snabbkaffe.hrl").
  23. -export([
  24. init_load/1,
  25. init_load/2,
  26. read_override_conf/1,
  27. has_deprecated_file/0,
  28. delete_override_conf_files/0,
  29. check_config/2,
  30. fill_defaults/1,
  31. fill_defaults/2,
  32. fill_defaults/3,
  33. save_configs/5,
  34. save_to_app_env/1,
  35. save_to_config_map/2,
  36. save_to_override_conf/3,
  37. config_files/0,
  38. include_dirs/0
  39. ]).
  40. -export([merge_envs/2]).
  41. -export([
  42. get_root/1,
  43. get_root_raw/1
  44. ]).
  45. -export([get_default_value/1]).
  46. -export([
  47. get/1,
  48. get/2,
  49. find/1,
  50. find_raw/1,
  51. put/1,
  52. put/2,
  53. force_put/2,
  54. force_put/3,
  55. erase/1
  56. ]).
  57. -export([
  58. get_raw/1,
  59. get_raw/2,
  60. put_raw/1,
  61. put_raw/2
  62. ]).
  63. -export([
  64. save_schema_mod_and_names/1,
  65. get_schema_mod/0,
  66. get_schema_mod/1,
  67. get_root_names/0
  68. ]).
  69. -export([
  70. get_zone_conf/2,
  71. get_zone_conf/3,
  72. put_zone_conf/3
  73. ]).
  74. -export([
  75. get_listener_conf/3,
  76. get_listener_conf/4,
  77. put_listener_conf/4,
  78. find_listener_conf/3
  79. ]).
  80. -export([
  81. add_handlers/0,
  82. remove_handlers/0
  83. ]).
  84. -export([ensure_atom_conf_path/2]).
  85. -export([load_config_files/2]).
  86. -ifdef(TEST).
  87. -export([erase_all/0, backup_and_write/2]).
  88. -endif.
  89. -include("logger.hrl").
  90. -include_lib("hocon/include/hoconsc.hrl").
  91. -define(CONF, conf).
  92. -define(RAW_CONF, raw_conf).
  93. -define(PERSIS_SCHEMA_MODS, {?MODULE, schema_mods}).
  94. -define(PERSIS_KEY(TYPE, ROOT), {?MODULE, TYPE, ROOT}).
  95. -define(ZONE_CONF_PATH(ZONE, PATH), [zones, ZONE | PATH]).
  96. -define(LISTENER_CONF_PATH(TYPE, LISTENER, PATH), [listeners, TYPE, LISTENER | PATH]).
  97. -define(MAX_KEEP_BACKUP_CONFIGS, 10).
  98. -export_type([
  99. update_request/0,
  100. raw_config/0,
  101. config/0,
  102. app_envs/0,
  103. update_opts/0,
  104. update_cmd/0,
  105. update_args/0,
  106. update_error/0,
  107. update_result/0,
  108. runtime_config_key_path/0
  109. ]).
  110. -type update_request() :: term().
  111. -type update_cmd() :: {update, update_request()} | remove.
  112. -type update_opts() :: #{
  113. %% rawconf_with_defaults:
  114. %% fill the default values into the `raw_config` field of the return value
  115. %% defaults to `false`
  116. rawconf_with_defaults => boolean(),
  117. %% persistent:
  118. %% save the updated config to the emqx_override.conf file
  119. %% defaults to `true`
  120. persistent => boolean(),
  121. override_to => local | cluster
  122. }.
  123. -type update_args() :: {update_cmd(), Opts :: update_opts()}.
  124. -type update_stage() :: pre_config_update | post_config_update.
  125. -type update_error() :: {update_stage(), module(), term()} | {save_configs, term()} | term().
  126. -type update_result() :: #{
  127. config => emqx_config:config(),
  128. raw_config => emqx_config:raw_config(),
  129. post_config_update => #{module() => any()}
  130. }.
  131. %% raw_config() is the config that is NOT parsed and translated by hocon schema
  132. -type raw_config() :: #{binary() => term()} | list() | undefined.
  133. %% config() is the config that is parsed and translated by hocon schema
  134. -type config() :: #{atom() => term()} | list() | undefined.
  135. -type app_envs() :: [proplists:property()].
  136. -type runtime_config_key_path() :: [atom()].
  137. %% @doc For the given path, get root value enclosed in a single-key map.
  138. -spec get_root(emqx_utils_maps:config_key_path()) -> map().
  139. get_root([RootName | _]) ->
  140. #{RootName => do_get(?CONF, [RootName], #{})}.
  141. %% @doc For the given path, get raw root value enclosed in a single-key map.
  142. %% key is ensured to be binary.
  143. get_root_raw([RootName | _]) ->
  144. #{bin(RootName) => get_raw([RootName], #{})}.
  145. %% @doc Get a config value for the given path.
  146. %% The path should at least include root config name.
  147. -spec get(runtime_config_key_path()) -> term().
  148. get(KeyPath) -> do_get(?CONF, KeyPath).
  149. -spec get(runtime_config_key_path(), term()) -> term().
  150. get(KeyPath, Default) -> do_get(?CONF, KeyPath, Default).
  151. -spec find(runtime_config_key_path()) ->
  152. {ok, term()} | {not_found, emqx_utils_maps:config_key_path(), term()}.
  153. find([]) ->
  154. case do_get(?CONF, [], ?CONFIG_NOT_FOUND_MAGIC) of
  155. ?CONFIG_NOT_FOUND_MAGIC -> {not_found, []};
  156. Res -> {ok, Res}
  157. end;
  158. find(AtomKeyPath) ->
  159. emqx_utils_maps:deep_find(AtomKeyPath, get_root(AtomKeyPath)).
  160. -spec find_raw(emqx_utils_maps:config_key_path()) ->
  161. {ok, term()} | {not_found, emqx_utils_maps:config_key_path(), term()}.
  162. find_raw([]) ->
  163. case do_get_raw([], ?CONFIG_NOT_FOUND_MAGIC) of
  164. ?CONFIG_NOT_FOUND_MAGIC -> {not_found, []};
  165. Res -> {ok, Res}
  166. end;
  167. find_raw(KeyPath) ->
  168. emqx_utils_maps:deep_find([bin(Key) || Key <- KeyPath], get_root_raw(KeyPath)).
  169. -spec get_zone_conf(atom(), emqx_utils_maps:config_key_path()) -> term().
  170. get_zone_conf(Zone, KeyPath) ->
  171. ?MODULE:get(?ZONE_CONF_PATH(Zone, KeyPath)).
  172. -spec get_zone_conf(atom(), emqx_utils_maps:config_key_path(), term()) -> term().
  173. get_zone_conf(Zone, KeyPath, Default) ->
  174. ?MODULE:get(?ZONE_CONF_PATH(Zone, KeyPath), Default).
  175. -spec put_zone_conf(atom(), emqx_utils_maps:config_key_path(), term()) -> ok.
  176. put_zone_conf(Zone, KeyPath, Conf) ->
  177. ?MODULE:put(?ZONE_CONF_PATH(Zone, KeyPath), Conf).
  178. -spec get_listener_conf(atom(), atom(), emqx_utils_maps:config_key_path()) -> term().
  179. get_listener_conf(Type, Listener, KeyPath) ->
  180. ?MODULE:get(?LISTENER_CONF_PATH(Type, Listener, KeyPath)).
  181. -spec get_listener_conf(atom(), atom(), emqx_utils_maps:config_key_path(), term()) -> term().
  182. get_listener_conf(Type, Listener, KeyPath, Default) ->
  183. ?MODULE:get(?LISTENER_CONF_PATH(Type, Listener, KeyPath), Default).
  184. -spec put_listener_conf(atom(), atom(), emqx_utils_maps:config_key_path(), term()) -> ok.
  185. put_listener_conf(Type, Listener, KeyPath, Conf) ->
  186. ?MODULE:put(?LISTENER_CONF_PATH(Type, Listener, KeyPath), Conf).
  187. -spec find_listener_conf(atom(), atom(), emqx_utils_maps:config_key_path()) ->
  188. {ok, term()} | {not_found, emqx_utils_maps:config_key_path(), term()}.
  189. find_listener_conf(Type, Listener, KeyPath) ->
  190. find(?LISTENER_CONF_PATH(Type, Listener, KeyPath)).
  191. -spec put(map()) -> ok.
  192. put(Config) ->
  193. put_with_order(Config).
  194. put1(Config) ->
  195. maps:fold(
  196. fun(RootName, RootValue, _) ->
  197. ?MODULE:put([atom(RootName)], RootValue)
  198. end,
  199. ok,
  200. Config
  201. ).
  202. erase(RootName) ->
  203. persistent_term:erase(?PERSIS_KEY(?CONF, atom(RootName))),
  204. persistent_term:erase(?PERSIS_KEY(?RAW_CONF, bin(RootName))),
  205. ok.
  206. -spec put(emqx_utils_maps:config_key_path(), term()) -> ok.
  207. put(KeyPath, Config) ->
  208. Putter = fun(_Path, Map, Value) ->
  209. maybe_update_zone(KeyPath, Map, Value)
  210. end,
  211. do_put(?CONF, Putter, KeyPath, Config).
  212. %% Puts value into configuration even if path doesn't exist
  213. %% For paths of non-existing atoms use force_put(KeyPath, Config, unsafe)
  214. -spec force_put(emqx_utils_maps:config_key_path(), term()) -> ok.
  215. force_put(KeyPath, Config) ->
  216. force_put(KeyPath, Config, safe).
  217. -spec force_put(emqx_utils_maps:config_key_path(), term(), safe | unsafe) -> ok.
  218. force_put(KeyPath0, Config, Safety) ->
  219. KeyPath =
  220. case Safety of
  221. safe -> KeyPath0;
  222. unsafe -> [unsafe_atom(Key) || Key <- KeyPath0]
  223. end,
  224. Putter = fun(Path, Map, Value) ->
  225. emqx_utils_maps:deep_force_put(Path, Map, Value)
  226. end,
  227. do_put(?CONF, Putter, KeyPath, Config).
  228. -spec get_default_value(emqx_utils_maps:config_key_path()) -> {ok, term()} | {error, term()}.
  229. get_default_value([RootName | _] = KeyPath) ->
  230. BinKeyPath = [bin(Key) || Key <- KeyPath],
  231. case find_raw([RootName]) of
  232. {ok, RawConf} ->
  233. RawConf1 = emqx_utils_maps:deep_remove(BinKeyPath, #{bin(RootName) => RawConf}),
  234. try fill_defaults(get_schema_mod(RootName), RawConf1, #{}) of
  235. FullConf ->
  236. case emqx_utils_maps:deep_find(BinKeyPath, FullConf) of
  237. {not_found, _, _} -> {error, no_default_value};
  238. {ok, Val} -> {ok, Val}
  239. end
  240. catch
  241. error:Reason -> {error, Reason}
  242. end;
  243. {not_found, _, _} ->
  244. {error, {rootname_not_found, RootName}}
  245. end.
  246. -spec get_raw(emqx_utils_maps:config_key_path()) -> term().
  247. get_raw([Root | _] = KeyPath) when is_binary(Root) -> do_get_raw(KeyPath);
  248. get_raw([Root | T]) -> get_raw([bin(Root) | T]);
  249. get_raw([]) -> do_get_raw([]).
  250. -spec get_raw(emqx_utils_maps:config_key_path(), term()) -> term().
  251. get_raw([Root | _] = KeyPath, Default) when is_binary(Root) -> do_get_raw(KeyPath, Default);
  252. get_raw([Root | T], Default) -> get_raw([bin(Root) | T], Default);
  253. get_raw([], Default) -> do_get_raw([], Default).
  254. -spec put_raw(map()) -> ok.
  255. put_raw(Config) ->
  256. maps:fold(
  257. fun(RootName, RootV, _) ->
  258. ?MODULE:put_raw([bin(RootName)], RootV)
  259. end,
  260. ok,
  261. hocon_maps:ensure_plain(Config)
  262. ).
  263. -spec put_raw(emqx_utils_maps:config_key_path(), term()) -> ok.
  264. put_raw(KeyPath0, Config) ->
  265. KeyPath = [bin(K) || K <- KeyPath0],
  266. Putter = fun(Path, Map, Value) ->
  267. emqx_utils_maps:deep_force_put(Path, Map, Value)
  268. end,
  269. do_put(?RAW_CONF, Putter, KeyPath, Config).
  270. %%============================================================================
  271. %% Load/Update configs From/To files
  272. %%============================================================================
  273. init_load(SchemaMod) ->
  274. init_load(SchemaMod, config_files()).
  275. %% @doc Initial load of the given config files.
  276. %% NOTE: The order of the files is significant, configs from files ordered
  277. %% in the rear of the list overrides prior values.
  278. -spec init_load(module(), [string()] | binary() | hocon:config()) -> ok.
  279. init_load(SchemaMod, Conf) when is_list(Conf) orelse is_binary(Conf) ->
  280. ok = save_schema_mod_and_names(SchemaMod),
  281. HasDeprecatedFile = has_deprecated_file(),
  282. RawConf0 = load_config_files(HasDeprecatedFile, Conf),
  283. warning_deprecated_root_key(RawConf0),
  284. RawConf1 =
  285. case HasDeprecatedFile of
  286. true ->
  287. overlay_v0(SchemaMod, RawConf0);
  288. false ->
  289. overlay_v1(SchemaMod, RawConf0)
  290. end,
  291. RawConf = fill_defaults_for_all_roots(SchemaMod, RawConf1),
  292. %% check configs against the schema
  293. {AppEnvs, CheckedConf} = check_config(SchemaMod, RawConf, #{}),
  294. save_to_app_env(AppEnvs),
  295. ok = save_to_config_map(CheckedConf, RawConf),
  296. maybe_init_default_zone(),
  297. ok.
  298. %% Merge environment variable overrides on top, then merge with overrides.
  299. overlay_v0(SchemaMod, RawConf) when is_map(RawConf) ->
  300. RawConfWithEnvs = merge_envs(SchemaMod, RawConf),
  301. Overrides = read_override_confs(),
  302. hocon:deep_merge(RawConfWithEnvs, Overrides).
  303. %% Merge environment variable overrides on top.
  304. overlay_v1(SchemaMod, RawConf) when is_map(RawConf) ->
  305. merge_envs(SchemaMod, RawConf).
  306. %% @doc Read merged cluster + local overrides.
  307. read_override_confs() ->
  308. ClusterOverrides = read_override_conf(#{override_to => cluster}),
  309. LocalOverrides = read_override_conf(#{override_to => local}),
  310. hocon:deep_merge(ClusterOverrides, LocalOverrides).
  311. %% keep the raw and non-raw conf has the same keys to make update raw conf easier.
  312. fill_defaults_for_all_roots(SchemaMod, RawConf0) ->
  313. RootSchemas = hocon_schema:roots(SchemaMod),
  314. %% the roots which are missing from the loaded configs
  315. MissingRoots = lists:filtermap(
  316. fun({BinName, Sc}) ->
  317. case maps:is_key(BinName, RawConf0) orelse is_already_loaded(BinName) of
  318. true -> false;
  319. false -> {true, Sc}
  320. end
  321. end,
  322. RootSchemas
  323. ),
  324. RawConf = lists:foldl(
  325. fun({RootName, Schema}, Acc) ->
  326. Acc#{bin(RootName) => seed_default(Schema)}
  327. end,
  328. RawConf0,
  329. MissingRoots
  330. ),
  331. fill_defaults(RawConf).
  332. %% So far, this can only return true when testing.
  333. %% e.g. when testing an app, we need to load its config first
  334. %% then start emqx_conf application which will load the
  335. %% possibly empty config again (then filled with defaults).
  336. is_already_loaded(Name) ->
  337. ?MODULE:get_raw([Name], #{}) =/= #{}.
  338. %% if a root is not found in the raw conf, fill it with default values.
  339. seed_default(Schema) ->
  340. case hocon_schema:field_schema(Schema, default) of
  341. undefined ->
  342. %% so far all roots without a default value are objects
  343. #{};
  344. Value ->
  345. Value
  346. end.
  347. load_config_files(HasDeprecatedFile, Conf) ->
  348. IncDirs = include_dirs(),
  349. case do_parse_hocon(HasDeprecatedFile, Conf, IncDirs) of
  350. {ok, HoconMap} ->
  351. HoconMap;
  352. {error, Reason} ->
  353. ?SLOG(error, #{
  354. msg => "failed_to_load_config_file",
  355. reason => Reason,
  356. pwd => file:get_cwd(),
  357. include_dirs => IncDirs,
  358. config_file => Conf
  359. }),
  360. error(failed_to_load_config_file)
  361. end.
  362. do_parse_hocon(true, Conf, IncDirs) ->
  363. Opts = #{format => map, include_dirs => IncDirs},
  364. case is_binary(Conf) of
  365. true -> hocon:binary(Conf, Opts);
  366. false -> hocon:files(Conf, Opts)
  367. end;
  368. do_parse_hocon(false, Conf, IncDirs) ->
  369. Opts = #{format => map, include_dirs => IncDirs},
  370. case is_binary(Conf) of
  371. %% only use in test
  372. true ->
  373. hocon:binary(Conf, Opts);
  374. false ->
  375. ClusterFile = cluster_hocon_file(),
  376. hocon:files([ClusterFile | Conf], Opts)
  377. end.
  378. include_dirs() ->
  379. [filename:join(emqx:data_dir(), "configs")].
  380. merge_envs(SchemaMod, RawConf) ->
  381. Opts = #{
  382. required => false,
  383. format => map,
  384. apply_override_envs => true
  385. },
  386. hocon_tconf:merge_env_overrides(SchemaMod, RawConf, all, Opts).
  387. -spec check_config(hocon_schema:schema(), raw_config()) -> {AppEnvs, CheckedConf} when
  388. AppEnvs :: app_envs(), CheckedConf :: config().
  389. check_config(SchemaMod, RawConf) ->
  390. check_config(SchemaMod, RawConf, #{}).
  391. check_config(SchemaMod, RawConf, Opts0) ->
  392. try
  393. do_check_config(SchemaMod, RawConf, Opts0)
  394. catch
  395. throw:Errors:Stacktrace ->
  396. {error, Reason} = emqx_hocon:compact_errors(Errors, Stacktrace),
  397. erlang:raise(throw, Reason, Stacktrace)
  398. end.
  399. do_check_config(SchemaMod, RawConf, Opts0) ->
  400. Opts1 = #{
  401. return_plain => true,
  402. format => map
  403. },
  404. Opts = maps:merge(Opts0, Opts1),
  405. {AppEnvs, CheckedConf} =
  406. hocon_tconf:map_translate(SchemaMod, RawConf, Opts),
  407. {AppEnvs, emqx_utils_maps:unsafe_atom_key_map(CheckedConf)}.
  408. fill_defaults(RawConf) ->
  409. fill_defaults(RawConf, #{}).
  410. -spec fill_defaults(raw_config(), hocon_tconf:opts()) -> map().
  411. fill_defaults(RawConf, Opts) ->
  412. RootNames = get_root_names(),
  413. maps:fold(
  414. fun(Key, Conf, Acc) ->
  415. SubMap = #{Key => Conf},
  416. WithDefaults =
  417. case lists:member(Key, RootNames) of
  418. true -> fill_defaults(get_schema_mod(Key), SubMap, Opts);
  419. false -> SubMap
  420. end,
  421. maps:merge(Acc, WithDefaults)
  422. end,
  423. #{},
  424. RawConf
  425. ).
  426. -spec fill_defaults(module(), raw_config(), hocon_tconf:opts()) -> map().
  427. fill_defaults(SchemaMod, RawConf, Opts0) ->
  428. Opts = maps:merge(#{required => false, make_serializable => true}, Opts0),
  429. hocon_tconf:check_plain(
  430. SchemaMod,
  431. RawConf,
  432. Opts,
  433. root_names_from_conf(RawConf)
  434. ).
  435. %% @doc Only for test cleanups.
  436. %% Delete override config files.
  437. -spec delete_override_conf_files() -> ok.
  438. delete_override_conf_files() ->
  439. F1 = deprecated_conf_file(#{override_to => local}),
  440. F2 = deprecated_conf_file(#{override_to => cluster}),
  441. F3 = cluster_hocon_file(),
  442. ok = ensure_file_deleted(F1),
  443. ok = ensure_file_deleted(F2),
  444. ok = ensure_file_deleted(F3).
  445. ensure_file_deleted(F) ->
  446. case file:delete(F) of
  447. ok -> ok;
  448. {error, enoent} -> ok;
  449. {error, Reason} -> error({F, Reason})
  450. end.
  451. -spec read_override_conf(map()) -> raw_config().
  452. read_override_conf(#{} = Opts) ->
  453. File =
  454. case has_deprecated_file() of
  455. true -> deprecated_conf_file(Opts);
  456. false -> cluster_hocon_file()
  457. end,
  458. load_hocon_file(File, map).
  459. %% @doc Return `true' if this node is upgraded from older version which used cluster-override.conf for
  460. %% cluster-wide config persistence.
  461. has_deprecated_file() ->
  462. DeprecatedFile = deprecated_conf_file(#{override_to => cluster}),
  463. filelib:is_regular(DeprecatedFile).
  464. deprecated_conf_file(Opts) when is_map(Opts) ->
  465. Key =
  466. case maps:get(override_to, Opts, cluster) of
  467. local -> local_override_conf_file;
  468. cluster -> cluster_override_conf_file
  469. end,
  470. application:get_env(emqx, Key, undefined);
  471. deprecated_conf_file(Which) when is_atom(Which) ->
  472. application:get_env(emqx, Which, undefined).
  473. %% The newer version cluster-wide config persistence file.
  474. cluster_hocon_file() ->
  475. application:get_env(emqx, cluster_hocon_file, undefined).
  476. -spec save_schema_mod_and_names(module()) -> ok.
  477. save_schema_mod_and_names(SchemaMod) ->
  478. RootNames = hocon_schema:root_names(SchemaMod),
  479. OldMods = get_schema_mod(),
  480. OldNames = get_root_names(),
  481. %% map from root name to schema module name
  482. NewMods = maps:from_list([{Name, SchemaMod} || Name <- RootNames]),
  483. persistent_term:put(?PERSIS_SCHEMA_MODS, #{
  484. mods => maps:merge(OldMods, NewMods),
  485. names => lists:usort(OldNames ++ RootNames)
  486. }).
  487. -ifdef(TEST).
  488. erase_all() ->
  489. Names = get_root_names(),
  490. lists:foreach(fun erase/1, Names),
  491. persistent_term:erase(?PERSIS_SCHEMA_MODS).
  492. -endif.
  493. -spec get_schema_mod() -> #{binary() => atom()}.
  494. get_schema_mod() ->
  495. maps:get(mods, persistent_term:get(?PERSIS_SCHEMA_MODS, #{mods => #{}})).
  496. -spec get_schema_mod(atom() | binary()) -> module().
  497. get_schema_mod(RootName) ->
  498. maps:get(bin(RootName), get_schema_mod()).
  499. -spec get_root_names() -> [binary()].
  500. get_root_names() ->
  501. maps:get(names, persistent_term:get(?PERSIS_SCHEMA_MODS, #{names => []})).
  502. -spec save_configs(
  503. app_envs(), config(), raw_config(), raw_config(), update_opts()
  504. ) -> ok.
  505. save_configs(AppEnvs, Conf, RawConf, OverrideConf, Opts) ->
  506. %% We first try to save to files, because saving to files is more error prone
  507. %% than saving into memory.
  508. HasDeprecatedFile = has_deprecated_file(),
  509. ok = save_to_override_conf(HasDeprecatedFile, OverrideConf, Opts),
  510. save_to_app_env(AppEnvs),
  511. save_to_config_map(Conf, RawConf).
  512. %% we ignore kernel app env,
  513. %% because the old app env will be used in emqx_config_logger:post_config_update/5
  514. -define(IGNORE_APPS, [kernel]).
  515. -spec save_to_app_env([tuple()]) -> ok.
  516. save_to_app_env(AppEnvs0) ->
  517. AppEnvs = lists:filter(fun({App, _}) -> not lists:member(App, ?IGNORE_APPS) end, AppEnvs0),
  518. application:set_env(AppEnvs).
  519. -spec save_to_config_map(config(), raw_config()) -> ok.
  520. save_to_config_map(Conf, RawConf) ->
  521. ?MODULE:put(Conf),
  522. ?MODULE:put_raw(RawConf).
  523. -spec save_to_override_conf(boolean(), raw_config(), update_opts()) -> ok | {error, term()}.
  524. save_to_override_conf(_, undefined, _) ->
  525. ok;
  526. save_to_override_conf(true, RawConf, Opts) ->
  527. case deprecated_conf_file(Opts) of
  528. undefined ->
  529. ok;
  530. FileName ->
  531. backup_and_write(FileName, hocon_pp:do(RawConf, #{}))
  532. end;
  533. save_to_override_conf(false, RawConf, _Opts) ->
  534. case cluster_hocon_file() of
  535. undefined ->
  536. ok;
  537. FileName ->
  538. backup_and_write(FileName, hocon_pp:do(RawConf, #{}))
  539. end.
  540. %% @private This is the same human-readable timestamp format as
  541. %% hocon-cli generated app.<time>.config file name.
  542. now_time() ->
  543. Ts = os:system_time(millisecond),
  544. {{Y, M, D}, {HH, MM, SS}} = calendar:system_time_to_local_time(Ts, millisecond),
  545. Res = io_lib:format(
  546. "~0p.~2..0b.~2..0b.~2..0b.~2..0b.~2..0b.~3..0b",
  547. [Y, M, D, HH, MM, SS, Ts rem 1000]
  548. ),
  549. lists:flatten(Res).
  550. %% @private Backup the current config to a file with a timestamp suffix and
  551. %% then save the new config to the config file.
  552. backup_and_write(Path, Content) ->
  553. %% this may fail, but we don't care
  554. %% e.g. read-only file system
  555. _ = filelib:ensure_dir(Path),
  556. TmpFile = Path ++ ".tmp",
  557. case file:write_file(TmpFile, Content) of
  558. ok ->
  559. backup_and_replace(Path, TmpFile);
  560. {error, Reason} ->
  561. ?SLOG(error, #{
  562. msg => "failed_to_save_conf_file",
  563. hint =>
  564. "The updated cluster config is not saved on this node, please check the file system.",
  565. filename => TmpFile,
  566. reason => Reason
  567. }),
  568. %% e.g. read-only, it's not the end of the world
  569. ok
  570. end.
  571. backup_and_replace(Path, TmpPath) ->
  572. Backup = Path ++ "." ++ now_time() ++ ".bak",
  573. case file:rename(Path, Backup) of
  574. ok ->
  575. ok = file:rename(TmpPath, Path),
  576. ok = prune_backup_files(Path);
  577. {error, enoent} ->
  578. %% not created yet
  579. ok = file:rename(TmpPath, Path);
  580. {error, Reason} ->
  581. ?SLOG(warning, #{
  582. msg => "failed_to_backup_conf_file",
  583. filename => Backup,
  584. reason => Reason
  585. }),
  586. ok
  587. end.
  588. prune_backup_files(Path) ->
  589. Files0 = filelib:wildcard(Path ++ ".*"),
  590. Re = "\\.[0-9]{4}\\.[0-9]{2}\\.[0-9]{2}\\.[0-9]{2}\\.[0-9]{2}\\.[0-9]{2}\\.[0-9]{3}\\.bak$",
  591. Files = lists:filter(fun(F) -> re:run(F, Re) =/= nomatch end, Files0),
  592. Sorted = lists:reverse(lists:sort(Files)),
  593. {_Keeps, Deletes} = lists:split(min(?MAX_KEEP_BACKUP_CONFIGS, length(Sorted)), Sorted),
  594. lists:foreach(
  595. fun(F) ->
  596. case file:delete(F) of
  597. ok ->
  598. ok;
  599. {error, Reason} ->
  600. ?SLOG(warning, #{
  601. msg => "failed_to_delete_backup_conf_file",
  602. filename => F,
  603. reason => Reason
  604. }),
  605. ok
  606. end
  607. end,
  608. Deletes
  609. ).
  610. add_handlers() ->
  611. ok = emqx_config_logger:add_handler(),
  612. ok = emqx_config_zones:add_handler(),
  613. emqx_sys_mon:add_handler(),
  614. ok.
  615. remove_handlers() ->
  616. ok = emqx_config_logger:remove_handler(),
  617. ok = emqx_config_zones:remove_handler(),
  618. emqx_sys_mon:remove_handler(),
  619. ok.
  620. load_hocon_file(FileName, LoadType) ->
  621. case filelib:is_regular(FileName) of
  622. true ->
  623. Opts = #{include_dirs => include_dirs(), format => LoadType},
  624. case hocon:load(FileName, Opts) of
  625. {ok, Raw0} ->
  626. Raw0;
  627. {error, Reason} ->
  628. throw(#{
  629. msg => failed_to_load_conf,
  630. reason => Reason,
  631. file => FileName
  632. })
  633. end;
  634. false ->
  635. #{}
  636. end.
  637. do_get_raw(Path) ->
  638. do_get(?RAW_CONF, Path).
  639. do_get_raw(Path, Default) ->
  640. do_get(?RAW_CONF, Path, Default).
  641. do_get(Type, KeyPath) ->
  642. case do_get(Type, KeyPath, ?CONFIG_NOT_FOUND_MAGIC) of
  643. ?CONFIG_NOT_FOUND_MAGIC -> error({config_not_found, KeyPath});
  644. Res -> Res
  645. end.
  646. do_get(Type, [], Default) ->
  647. AllConf = lists:foldl(
  648. fun
  649. ({?PERSIS_KEY(Type0, RootName), Conf}, AccIn) when Type0 == Type ->
  650. AccIn#{conf_key(Type0, RootName) => Conf};
  651. (_, AccIn) ->
  652. AccIn
  653. end,
  654. #{},
  655. persistent_term:get()
  656. ),
  657. case AllConf =:= #{} of
  658. true -> Default;
  659. false -> AllConf
  660. end;
  661. do_get(Type, [RootName], Default) ->
  662. persistent_term:get(?PERSIS_KEY(Type, RootName), Default);
  663. do_get(Type, [RootName | KeyPath], Default) ->
  664. RootV = persistent_term:get(?PERSIS_KEY(Type, RootName), #{}),
  665. do_deep_get(Type, KeyPath, RootV, Default).
  666. do_put(Type, Putter, [], DeepValue) ->
  667. maps:fold(
  668. fun(RootName, Value, _Res) ->
  669. do_put(Type, Putter, [RootName], Value)
  670. end,
  671. ok,
  672. DeepValue
  673. );
  674. do_put(Type, Putter, [RootName | KeyPath], DeepValue) ->
  675. OldValue = do_get(Type, [RootName], #{}),
  676. NewValue = do_deep_put(Type, Putter, KeyPath, OldValue, DeepValue),
  677. Key = ?PERSIS_KEY(Type, RootName),
  678. persistent_term:put(Key, NewValue),
  679. put_config_post_change_actions(Key, NewValue),
  680. ok.
  681. do_deep_get(?CONF, AtomKeyPath, Map, Default) ->
  682. emqx_utils_maps:deep_get(AtomKeyPath, Map, Default);
  683. do_deep_get(?RAW_CONF, KeyPath, Map, Default) ->
  684. emqx_utils_maps:deep_get([bin(Key) || Key <- KeyPath], Map, Default).
  685. do_deep_put(?CONF, Putter, KeyPath, Map, Value) ->
  686. AtomKeyPath = ensure_atom_conf_path(KeyPath, {raise_error, {not_found, KeyPath}}),
  687. Putter(AtomKeyPath, Map, Value);
  688. do_deep_put(?RAW_CONF, Putter, KeyPath, Map, Value) ->
  689. Putter([bin(Key) || Key <- KeyPath], Map, Value).
  690. root_names_from_conf(RawConf) ->
  691. Keys = maps:keys(RawConf),
  692. [Name || Name <- get_root_names(), lists:member(Name, Keys)].
  693. unsafe_atom(Bin) when is_binary(Bin) ->
  694. binary_to_atom(Bin, utf8);
  695. unsafe_atom(Str) when is_list(Str) ->
  696. list_to_atom(Str);
  697. unsafe_atom(Atom) when is_atom(Atom) ->
  698. Atom.
  699. atom(Bin) when is_binary(Bin) ->
  700. binary_to_existing_atom(Bin, utf8);
  701. atom(Str) when is_list(Str) ->
  702. list_to_existing_atom(Str);
  703. atom(Atom) when is_atom(Atom) ->
  704. Atom.
  705. bin(Bin) when is_binary(Bin) -> Bin;
  706. bin(Str) when is_list(Str) -> list_to_binary(Str);
  707. bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
  708. warning_deprecated_root_key(RawConf) ->
  709. case maps:keys(RawConf) -- get_root_names() of
  710. [] ->
  711. ok;
  712. Keys ->
  713. Unknowns = string:join([binary_to_list(K) || K <- Keys], ","),
  714. ?tp(unknown_config_keys, #{unknown_config_keys => Unknowns}),
  715. ?SLOG(
  716. warning,
  717. #{
  718. msg => "config_key_not_recognized",
  719. unknown_config_keys => Unknowns
  720. }
  721. )
  722. end.
  723. conf_key(?CONF, RootName) ->
  724. atom(RootName);
  725. conf_key(?RAW_CONF, RootName) ->
  726. bin(RootName).
  727. ensure_atom_conf_path(Path, OnFail) ->
  728. case lists:all(fun erlang:is_atom/1, Path) of
  729. true ->
  730. %% Do not try to build new atom PATH if it already is.
  731. Path;
  732. _ ->
  733. to_atom_conf_path(Path, OnFail)
  734. end.
  735. to_atom_conf_path(Path, OnFail) ->
  736. try
  737. [atom(Key) || Key <- Path]
  738. catch
  739. error:badarg ->
  740. case OnFail of
  741. {raise_error, Err} ->
  742. error(Err);
  743. {return, V} ->
  744. V
  745. end
  746. end.
  747. %% @doc Init zones under root `zones'
  748. %% 1. ensure one `default' zone as it is referenced by listeners.
  749. %% if default zone is unset, clone all default values from `GlobalDefaults'
  750. %% if default zone is set, values are merged with `GlobalDefaults'
  751. %% 2. For any user defined zones, merge with `GlobalDefaults'
  752. %%
  753. %% note1, this should be called as post action after emqx_config terms (zones, and GlobalDefaults)
  754. %% are written in the PV storage during emqx config loading/initialization.
  755. -spec maybe_init_default_zone() -> skip | ok.
  756. maybe_init_default_zone() ->
  757. case emqx_config:get([zones], ?CONFIG_NOT_FOUND_MAGIC) of
  758. ?CONFIG_NOT_FOUND_MAGIC ->
  759. skip;
  760. Zones0 when is_map(Zones0) ->
  761. Zones =
  762. case Zones0 of
  763. #{default := _DefaultZone} = Z1 ->
  764. Z1;
  765. Z2 ->
  766. Z2#{default => #{}}
  767. end,
  768. GLD = zone_global_defaults(),
  769. NewZones = maps:map(
  770. fun(_ZoneName, ZoneVal) ->
  771. merge_with_global_defaults(GLD, ZoneVal)
  772. end,
  773. Zones
  774. ),
  775. ?MODULE:put([zones], NewZones)
  776. end.
  777. -spec merge_with_global_defaults(map(), map()) -> map().
  778. merge_with_global_defaults(GlobalDefaults, ZoneVal) ->
  779. emqx_utils_maps:deep_merge(GlobalDefaults, ZoneVal).
  780. %% @doc Update zones
  781. %% when 1) zone updates, return *new* zones
  782. %% when 2) zone global config updates, write to PT directly.
  783. %% Zone global defaults are always presented in the configmap (PT) when updating zone
  784. -spec maybe_update_zone(runtime_config_key_path(), RootValue :: map(), Val :: term()) ->
  785. NewZoneVal :: map().
  786. maybe_update_zone([zones | T], ZonesValue, Value) ->
  787. %% note, do not write to PT, return *New value* instead
  788. GLD = zone_global_defaults(),
  789. NewZonesValue0 = emqx_utils_maps:deep_put(T, ZonesValue, Value),
  790. NewZonesValue1 = emqx_utils_maps:deep_merge(#{default => GLD}, NewZonesValue0),
  791. maps:map(
  792. fun(_ZoneName, ZoneValue) ->
  793. merge_with_global_defaults(GLD, ZoneValue)
  794. end,
  795. NewZonesValue1
  796. );
  797. maybe_update_zone([RootName | T], RootValue, Value) when is_atom(RootName) ->
  798. NewRootValue = emqx_utils_maps:deep_put(T, RootValue, Value),
  799. case is_zone_root(RootName) of
  800. false ->
  801. skip;
  802. true ->
  803. %% When updates on global default roots.
  804. ExistingZones = ?MODULE:get([zones], #{}),
  805. RootNameBin = atom_to_binary(RootName),
  806. NewZones = maps:map(
  807. fun(ZoneName, ZoneVal) ->
  808. BinPath = [<<"zones">>, atom_to_binary(ZoneName), RootNameBin],
  809. case
  810. %% look for user defined value from RAWCONF
  811. ?MODULE:get_raw(
  812. BinPath,
  813. ?CONFIG_NOT_FOUND_MAGIC
  814. )
  815. of
  816. ?CONFIG_NOT_FOUND_MAGIC ->
  817. ZoneVal#{RootName => NewRootValue};
  818. RawUserZoneRoot ->
  819. UserDefinedValues = rawconf_to_conf(
  820. emqx_schema, BinPath, RawUserZoneRoot
  821. ),
  822. ZoneVal#{
  823. RootName :=
  824. emqx_utils_maps:deep_merge(
  825. NewRootValue,
  826. UserDefinedValues
  827. )
  828. }
  829. end
  830. end,
  831. ExistingZones
  832. ),
  833. ZonesKey = ?PERSIS_KEY(?CONF, zones),
  834. persistent_term:put(ZonesKey, NewZones),
  835. put_config_post_change_actions(ZonesKey, NewZones)
  836. end,
  837. NewRootValue.
  838. zone_global_defaults() ->
  839. maps:from_list([{K, ?MODULE:get([K])} || K <- zone_roots()]).
  840. -spec is_zone_root(atom) -> boolean().
  841. is_zone_root(Name) ->
  842. lists:member(Name, zone_roots()).
  843. -spec zone_roots() -> [atom()].
  844. zone_roots() ->
  845. lists:map(fun list_to_atom/1, emqx_zone_schema:roots()).
  846. %%%
  847. %%% @doc During init, ensure order of puts that zone is put after the other global defaults.
  848. %%%
  849. put_with_order(#{zones := _Zones} = Conf) ->
  850. put1(maps:without([zones], Conf)),
  851. put1(maps:with([zones], Conf));
  852. put_with_order(Conf) ->
  853. put1(Conf).
  854. %%
  855. %% @doc Helper function that converts raw conf val to runtime conf val
  856. %% with the types info from schema module
  857. -spec rawconf_to_conf(module(), RawPath :: [binary()], RawValue :: term()) -> term().
  858. rawconf_to_conf(SchemaModule, RawPath, RawValue) ->
  859. {_, RawUserDefinedValues} =
  860. check_config(
  861. SchemaModule,
  862. emqx_utils_maps:deep_put(RawPath, #{}, RawValue)
  863. ),
  864. AtomPath = to_atom_conf_path(RawPath, {raise_error, maybe_update_zone_error}),
  865. emqx_utils_maps:deep_get(AtomPath, RawUserDefinedValues).
  866. %% When the global zone change, the zones is updated with the new global zone.
  867. %% The global zone's keys is too many,
  868. %% so we don't choose to write a global zone change emqx_config_handler callback to hook
  869. put_config_post_change_actions(?PERSIS_KEY(?CONF, zones), _Zones) ->
  870. emqx_flapping:update_config(),
  871. ok;
  872. put_config_post_change_actions(_Key, _NewValue) ->
  873. ok.
  874. config_files() ->
  875. application:get_env(emqx, config_files, []).