emqx_conf_schema_tests.erl 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2023-2024 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_conf_schema_tests).
  17. -include_lib("eunit/include/eunit.hrl").
  18. %% erlfmt-ignore
  19. -define(BASE_CONF,
  20. "
  21. node {
  22. name = \"emqx1@127.0.0.1\"
  23. cookie = \"emqxsecretcookie\"
  24. data_dir = \"data\"
  25. max_ports = 2048
  26. process_limit = 10240
  27. }
  28. cluster {
  29. name = emqxcl
  30. discovery_strategy = static
  31. static.seeds = ~p
  32. core_nodes = ~p
  33. }
  34. ").
  35. array_nodes_test() ->
  36. ensure_acl_conf(),
  37. ExpectNodes = ['emqx1@127.0.0.1', 'emqx2@127.0.0.1'],
  38. lists:foreach(
  39. fun(Nodes) ->
  40. ConfFile = to_bin(?BASE_CONF, [Nodes, Nodes]),
  41. {ok, Conf} = hocon:binary(ConfFile, #{format => richmap}),
  42. ConfList = hocon_tconf:generate(emqx_conf_schema, Conf),
  43. VMArgs = proplists:get_value(vm_args, ConfList),
  44. ProcLimit = proplists:get_value('+P', VMArgs),
  45. MaxPort = proplists:get_value('+Q', VMArgs),
  46. ?assertEqual(2048, MaxPort),
  47. ?assertEqual(MaxPort * 2, ProcLimit),
  48. ClusterDiscovery = proplists:get_value(
  49. cluster_discovery, proplists:get_value(ekka, ConfList)
  50. ),
  51. ?assertEqual(
  52. {static, [{seeds, ExpectNodes}]},
  53. ClusterDiscovery,
  54. Nodes
  55. ),
  56. ?assertEqual(
  57. ExpectNodes,
  58. proplists:get_value(core_nodes, proplists:get_value(mria, ConfList)),
  59. Nodes
  60. )
  61. end,
  62. [["emqx1@127.0.0.1", "emqx2@127.0.0.1"], "emqx1@127.0.0.1, emqx2@127.0.0.1"]
  63. ),
  64. ok.
  65. %% erlfmt-ignore
  66. -define(OUTDATED_LOG_CONF,
  67. "
  68. log.console_handler {
  69. burst_limit {
  70. enable = true
  71. max_count = 10000
  72. window_time = 1s
  73. }
  74. chars_limit = unlimited
  75. drop_mode_qlen = 3000
  76. enable = true
  77. flush_qlen = 8000
  78. formatter = text
  79. level = warning
  80. max_depth = 100
  81. overload_kill {
  82. enable = true
  83. mem_size = \"30MB\"
  84. qlen = 20000
  85. restart_after = \"5s\"
  86. }
  87. single_line = true
  88. supervisor_reports = error
  89. sync_mode_qlen = 100
  90. time_offset = \"+02:00\"
  91. }
  92. log.file_handlers {
  93. default {
  94. burst_limit {
  95. enable = true
  96. max_count = 10000
  97. window_time = 1s
  98. }
  99. chars_limit = unlimited
  100. drop_mode_qlen = 3000
  101. enable = true
  102. file = \"log/my-emqx.log\"
  103. flush_qlen = 8000
  104. formatter = text
  105. level = debug
  106. max_depth = 100
  107. max_size = \"1024MB\"
  108. overload_kill {
  109. enable = true
  110. mem_size = \"30MB\"
  111. qlen = 20000
  112. restart_after = \"5s\"
  113. }
  114. rotation {count = 20, enable = true}
  115. single_line = true
  116. supervisor_reports = error
  117. sync_mode_qlen = 100
  118. time_offset = \"+01:00\"
  119. }
  120. }
  121. "
  122. ).
  123. -define(FORMATTER(TimeOffset),
  124. {emqx_logger_textfmt, #{
  125. chars_limit => unlimited,
  126. depth => 100,
  127. single_line => true,
  128. template => ["[", level, "] ", msg, "\n"],
  129. time_offset => TimeOffset,
  130. timestamp_format => auto
  131. }}
  132. ).
  133. -define(FILTERS, [{drop_progress_reports, {fun logger_filters:progress/2, stop}}]).
  134. -define(LOG_CONFIG, #{
  135. burst_limit_enable => true,
  136. burst_limit_max_count => 10000,
  137. burst_limit_window_time => 1000,
  138. drop_mode_qlen => 3000,
  139. flush_qlen => 8000,
  140. overload_kill_enable => true,
  141. overload_kill_mem_size => 31457280,
  142. overload_kill_qlen => 20000,
  143. overload_kill_restart_after => 5000,
  144. sync_mode_qlen => 100
  145. }).
  146. outdated_log_test() ->
  147. validate_log(?OUTDATED_LOG_CONF).
  148. validate_log(Conf) ->
  149. ensure_acl_conf(),
  150. BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
  151. Conf0 = <<BaseConf/binary, (list_to_binary(Conf))/binary>>,
  152. {ok, ConfMap0} = hocon:binary(Conf0, #{format => richmap}),
  153. ConfList = hocon_tconf:generate(emqx_conf_schema, ConfMap0),
  154. Kernel = proplists:get_value(kernel, ConfList),
  155. ?assertEqual(silent, proplists:get_value(error_logger, Kernel)),
  156. ?assertEqual(debug, proplists:get_value(logger_level, Kernel)),
  157. Loggers = proplists:get_value(logger, Kernel),
  158. FileHandlers = lists:filter(fun(L) -> element(3, L) =:= logger_disk_log_h end, Loggers),
  159. FileHandler = lists:keyfind(default, 2, FileHandlers),
  160. ?assertEqual(
  161. {handler, default, logger_disk_log_h, #{
  162. config => ?LOG_CONFIG#{
  163. type => wrap,
  164. file => "log/my-emqx.log",
  165. max_no_bytes => 1073741824,
  166. max_no_files => 20
  167. },
  168. filesync_repeat_interval => no_repeat,
  169. filters => ?FILTERS,
  170. formatter => ?FORMATTER("+01:00"),
  171. level => debug
  172. }},
  173. FileHandler
  174. ),
  175. %% audit is an EE-only feature
  176. ?assertNot(lists:keyfind(emqx_audit, 2, FileHandlers)),
  177. ConsoleHandler = lists:keyfind(logger_std_h, 3, Loggers),
  178. ?assertEqual(
  179. {handler, console, logger_std_h, #{
  180. config => ?LOG_CONFIG#{type => standard_io},
  181. filters => ?FILTERS,
  182. formatter => ?FORMATTER("+02:00"),
  183. level => warning
  184. }},
  185. ConsoleHandler
  186. ).
  187. %% erlfmt-ignore
  188. -define(FILE_LOG_BASE_CONF,
  189. "
  190. log.file.default {
  191. enable = true
  192. file = \"log/xx-emqx.log\"
  193. formatter = text
  194. level = debug
  195. rotation_count = ~s
  196. rotation_size = ~s
  197. time_offset = \"+01:00\"
  198. }
  199. "
  200. ).
  201. file_log_infinity_rotation_size_test_() ->
  202. ensure_acl_conf(),
  203. BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
  204. Gen = fun(#{count := Count, size := Size}) ->
  205. Conf0 = to_bin(?FILE_LOG_BASE_CONF, [Count, Size]),
  206. Conf1 = [BaseConf, Conf0],
  207. {ok, Conf} = hocon:binary(Conf1, #{format => richmap}),
  208. ConfList = hocon_tconf:generate(emqx_conf_schema, Conf),
  209. Kernel = proplists:get_value(kernel, ConfList),
  210. Loggers = proplists:get_value(logger, Kernel),
  211. FileHandlers = lists:filter(fun(L) -> element(3, L) =:= logger_disk_log_h end, Loggers),
  212. lists:keyfind(default, 2, FileHandlers)
  213. end,
  214. [
  215. {"base conf: finite log (type = wrap)",
  216. ?_assertMatch(
  217. {handler, default, logger_disk_log_h, #{
  218. config := #{
  219. type := wrap,
  220. max_no_bytes := 1073741824,
  221. max_no_files := 20
  222. }
  223. }},
  224. Gen(#{count => "20", size => "\"1024MB\""})
  225. )},
  226. {"rotation size = infinity (type = halt)",
  227. ?_assertMatch(
  228. {handler, default, logger_disk_log_h, #{
  229. config := #{
  230. type := halt,
  231. max_no_bytes := infinity,
  232. max_no_files := 9
  233. }
  234. }},
  235. Gen(#{count => "9", size => "\"infinity\""})
  236. )}
  237. ].
  238. %% erlfmt-ignore
  239. -define(KERNEL_LOG_CONF,
  240. "
  241. log.console {
  242. enable = true
  243. formatter = text
  244. level = warning
  245. time_offset = \"+02:00\"
  246. }
  247. log.file {
  248. enable = false
  249. file = \"log/xx-emqx.log\"
  250. formatter = text
  251. level = debug
  252. rotation_count = 20
  253. rotation_size = \"1024MB\"
  254. time_offset = \"+01:00\"
  255. }
  256. log.file_handlers.default {
  257. enable = true
  258. file = \"log/my-emqx.log\"
  259. }
  260. "
  261. ).
  262. log_test() ->
  263. validate_log(?KERNEL_LOG_CONF).
  264. %% erlfmt-ignore
  265. log_rotation_count_limit_test() ->
  266. ensure_acl_conf(),
  267. Format =
  268. "
  269. log.file {
  270. enable = true
  271. path = \"log/emqx.log\"
  272. formatter = text
  273. level = debug
  274. rotation = {count = ~w}
  275. rotation_size = \"1024MB\"
  276. }
  277. ",
  278. BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
  279. lists:foreach(fun({Conf, Count}) ->
  280. Conf0 = <<BaseConf/binary, Conf/binary>>,
  281. {ok, ConfMap0} = hocon:binary(Conf0, #{format => richmap}),
  282. ConfList = hocon_tconf:generate(emqx_conf_schema, ConfMap0),
  283. Kernel = proplists:get_value(kernel, ConfList),
  284. Loggers = proplists:get_value(logger, Kernel),
  285. ?assertMatch(
  286. {handler, default, logger_disk_log_h, #{
  287. config := #{max_no_files := Count}
  288. }},
  289. lists:keyfind(default, 2, Loggers)
  290. )
  291. end,
  292. [{to_bin(Format, [1]), 1}, {to_bin(Format, [128]), 128}]),
  293. lists:foreach(fun({Conf, Count}) ->
  294. Conf0 = <<BaseConf/binary, Conf/binary>>,
  295. {ok, ConfMap0} = hocon:binary(Conf0, #{format => richmap}),
  296. ?assertThrow({emqx_conf_schema,
  297. [#{kind := validation_error,
  298. mismatches := #{"handler_name" :=
  299. #{kind := validation_error,
  300. path := "log.file.default.rotation_count",
  301. reason := #{expected := "1..128"},
  302. value := Count}
  303. }}]},
  304. hocon_tconf:generate(emqx_conf_schema, ConfMap0))
  305. end, [{to_bin(Format, [0]), 0}, {to_bin(Format, [129]), 129}]).
  306. %% erlfmt-ignore
  307. -define(BASE_AUTHN_ARRAY,
  308. "
  309. authentication = [
  310. {backend = \"http\"
  311. body {password = \"${password}\", username = \"${username}\"}
  312. connect_timeout = \"15s\"
  313. enable_pipelining = 100
  314. headers {\"content-type\" = \"application/json\"}
  315. mechanism = \"password_based\"
  316. method = \"~p\"
  317. pool_size = 8
  318. request_timeout = \"5s\"
  319. ssl {enable = ~p, verify = \"verify_peer\"}
  320. url = \"~ts\"
  321. }
  322. ]
  323. "
  324. ).
  325. -define(ERROR(Error),
  326. {emqx_conf_schema, [
  327. #{
  328. kind := validation_error,
  329. reason := #{error := Error}
  330. }
  331. ]}
  332. ).
  333. authn_validations_test() ->
  334. ensure_acl_conf(),
  335. BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
  336. OKHttps = to_bin(?BASE_AUTHN_ARRAY, [post, true, <<"https://127.0.0.1:8080">>]),
  337. Conf0 = <<BaseConf/binary, OKHttps/binary>>,
  338. {ok, ConfMap0} = hocon:binary(Conf0, #{format => richmap}),
  339. {_, Res0} = hocon_tconf:map_translate(emqx_conf_schema, ConfMap0, #{format => richmap}),
  340. Headers0 = authentication_headers(Res0),
  341. ?assertEqual(<<"application/json">>, maps:get(<<"content-type">>, Headers0)),
  342. %% accept from converter
  343. ?assertNot(maps:is_key(<<"accept">>, Headers0)),
  344. OKHttp = to_bin(?BASE_AUTHN_ARRAY, [post, false, <<"http://127.0.0.1:8080">>]),
  345. Conf1 = <<BaseConf/binary, OKHttp/binary>>,
  346. {ok, ConfMap1} = hocon:binary(Conf1, #{format => richmap}),
  347. {_, Res1} = hocon_tconf:map_translate(emqx_conf_schema, ConfMap1, #{format => richmap}),
  348. Headers1 = authentication_headers(Res1),
  349. ?assertEqual(<<"application/json">>, maps:get(<<"content-type">>, Headers1), Headers1),
  350. ?assertNot(maps:is_key(<<"accept">>, Headers1)),
  351. DisableSSLWithHttps = to_bin(?BASE_AUTHN_ARRAY, [post, false, <<"https://127.0.0.1:8080">>]),
  352. Conf2 = <<BaseConf/binary, DisableSSLWithHttps/binary>>,
  353. {ok, ConfMap2} = hocon:binary(Conf2, #{format => richmap}),
  354. ?assertThrow(
  355. ?ERROR(invalid_ssl_opts),
  356. hocon_tconf:map_translate(emqx_conf_schema, ConfMap2, #{format => richmap})
  357. ),
  358. BadHeader = to_bin(?BASE_AUTHN_ARRAY, [get, true, <<"https://127.0.0.1:8080">>]),
  359. Conf3 = <<BaseConf/binary, BadHeader/binary>>,
  360. {ok, ConfMap3} = hocon:binary(Conf3, #{format => richmap}),
  361. {_, Res3} = hocon_tconf:map_translate(emqx_conf_schema, ConfMap3, #{format => richmap}),
  362. Headers3 = authentication_headers(Res3),
  363. %% remove the content-type header when get method
  364. ?assertNot(maps:is_key(<<"content-type">>, Headers3), Headers3),
  365. ?assertNot(maps:is_key(<<"accept">>, Headers3), Headers3),
  366. BadHeaderWithTuple = binary:replace(BadHeader, [<<"[">>, <<"]">>], <<"">>, [global]),
  367. Conf4 = <<BaseConf/binary, BadHeaderWithTuple/binary>>,
  368. {ok, ConfMap4} = hocon:binary(Conf4, #{format => richmap}),
  369. {_, Res4} = hocon_tconf:map_translate(emqx_conf_schema, ConfMap4, #{}),
  370. Headers4 = authentication_headers(Res4),
  371. ?assertNot(maps:is_key(<<"content-type">>, Headers4), Headers4),
  372. ?assertNot(maps:is_key(<<"accept">>, Headers4), Headers4),
  373. ok.
  374. %% erlfmt-ignore
  375. -define(LISTENERS,
  376. "
  377. listeners.ssl.default.bind = 9999
  378. listeners.wss.default.bind = 9998
  379. listeners.wss.default.ssl_options.cacertfile = \"mytest/certs/cacert.pem\"
  380. listeners.wss.new.bind = 9997
  381. listeners.wss.new.websocket.mqtt_path = \"/my-mqtt\"
  382. "
  383. ).
  384. listeners_test() ->
  385. ensure_acl_conf(),
  386. BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
  387. Conf = <<BaseConf/binary, ?LISTENERS>>,
  388. {ok, ConfMap0} = hocon:binary(Conf, #{format => richmap}),
  389. {_, ConfMap} = hocon_tconf:map_translate(emqx_conf_schema, ConfMap0, #{format => richmap}),
  390. #{<<"listeners">> := Listeners} = hocon_util:richmap_to_map(ConfMap),
  391. #{
  392. <<"tcp">> := #{<<"default">> := Tcp},
  393. <<"ws">> := #{<<"default">> := Ws},
  394. <<"wss">> := #{<<"default">> := DefaultWss, <<"new">> := NewWss},
  395. <<"ssl">> := #{<<"default">> := Ssl}
  396. } = Listeners,
  397. DefaultCacertFile = <<"${EMQX_ETC_DIR}/certs/cacert.pem">>,
  398. DefaultCertFile = <<"${EMQX_ETC_DIR}/certs/cert.pem">>,
  399. DefaultKeyFile = <<"${EMQX_ETC_DIR}/certs/key.pem">>,
  400. ?assertMatch(
  401. #{
  402. <<"bind">> := {{0, 0, 0, 0}, 1883},
  403. <<"enable">> := true
  404. },
  405. Tcp
  406. ),
  407. ?assertMatch(
  408. #{
  409. <<"bind">> := {{0, 0, 0, 0}, 8083},
  410. <<"enable">> := true,
  411. <<"websocket">> := #{<<"mqtt_path">> := "/mqtt"}
  412. },
  413. Ws
  414. ),
  415. ?assertMatch(
  416. #{
  417. <<"bind">> := 9999,
  418. <<"ssl_options">> := #{
  419. <<"cacertfile">> := DefaultCacertFile,
  420. <<"certfile">> := DefaultCertFile,
  421. <<"keyfile">> := DefaultKeyFile
  422. }
  423. },
  424. Ssl
  425. ),
  426. ?assertMatch(
  427. #{
  428. <<"bind">> := 9998,
  429. <<"websocket">> := #{<<"mqtt_path">> := "/mqtt"},
  430. <<"ssl_options">> :=
  431. #{
  432. <<"cacertfile">> := <<"mytest/certs/cacert.pem">>,
  433. <<"certfile">> := DefaultCertFile,
  434. <<"keyfile">> := DefaultKeyFile
  435. }
  436. },
  437. DefaultWss
  438. ),
  439. ?assertMatch(
  440. #{
  441. <<"bind">> := 9997,
  442. <<"websocket">> := #{<<"mqtt_path">> := "/my-mqtt"},
  443. <<"ssl_options">> :=
  444. #{
  445. <<"cacertfile">> := DefaultCacertFile,
  446. <<"certfile">> := DefaultCertFile,
  447. <<"keyfile">> := DefaultKeyFile
  448. }
  449. },
  450. NewWss
  451. ),
  452. ok.
  453. authentication_headers(Conf) ->
  454. [#{<<"headers">> := Headers}] = hocon_maps:get("authentication", Conf),
  455. Headers.
  456. doc_gen_test() ->
  457. ensure_acl_conf(),
  458. %% the json file too large to encode.
  459. {
  460. timeout,
  461. 60,
  462. fun() ->
  463. Dir = "tmp",
  464. ok = emqx_conf:dump_schema(Dir, emqx_conf_schema)
  465. end
  466. }.
  467. to_bin(Format, Args) ->
  468. iolist_to_binary(io_lib:format(Format, Args)).
  469. ensure_acl_conf() ->
  470. File = emqx_schema:naive_env_interpolation(<<"${EMQX_ETC_DIR}/acl.conf">>),
  471. ok = filelib:ensure_dir(filename:dirname(File)),
  472. case filelib:is_regular(File) of
  473. true -> ok;
  474. false -> file:write_file(File, <<"">>)
  475. end.
  476. log_path_test_() ->
  477. Fh = fun(Path) ->
  478. #{<<"log">> => #{<<"file_handlers">> => #{<<"name1">> => #{<<"file">> => Path}}}}
  479. end,
  480. Assert = fun(Name, Path, Conf) ->
  481. ?assertMatch(#{log := #{file := #{Name := #{path := Path}}}}, Conf)
  482. end,
  483. [
  484. {"default-values", fun() -> Assert(default, "log/emqx.log", check(#{})) end},
  485. {"file path with space", fun() -> Assert(name1, "a /b", check(Fh(<<"a /b">>))) end},
  486. {"windows", fun() -> Assert(name1, "c:\\a\\ b\\", check(Fh(<<"c:\\a\\ b\\">>))) end},
  487. {"unicoded", fun() -> Assert(name1, "路 径", check(Fh(<<"路 径"/utf8>>))) end},
  488. {"bad utf8", fun() ->
  489. ?assertThrow(
  490. {emqx_conf_schema, [
  491. #{
  492. kind := validation_error,
  493. mismatches :=
  494. #{
  495. "handler_name" :=
  496. #{
  497. kind := validation_error,
  498. path := "log.file.name1.path",
  499. reason := {"bad_file_path_string", _}
  500. }
  501. }
  502. }
  503. ]},
  504. check(Fh(<<239, 32, 132, 47, 117, 116, 102, 56>>))
  505. )
  506. end},
  507. {"not string", fun() ->
  508. ?assertThrow(
  509. {emqx_conf_schema, [
  510. #{
  511. kind := validation_error,
  512. mismatches :=
  513. #{
  514. "handler_name" :=
  515. #{
  516. kind := validation_error,
  517. path := "log.file.name1.path",
  518. reason := {"not_string", _}
  519. }
  520. }
  521. }
  522. ]},
  523. check(Fh(#{<<"foo">> => <<"bar">>}))
  524. )
  525. end}
  526. ].
  527. check(Config) ->
  528. Schema = emqx_conf_schema,
  529. {_, Conf} = hocon_tconf:map(Schema, Config, [log], #{
  530. atom_key => false, required => false, format => map
  531. }),
  532. emqx_utils_maps:unsafe_atom_key_map(Conf).
  533. with_file(Path, Content, F) ->
  534. ok = file:write_file(Path, Content),
  535. try
  536. F()
  537. after
  538. file:delete(Path)
  539. end.
  540. load_and_check_test_() ->
  541. [
  542. {"non-existing file", fun() ->
  543. File = "/tmp/nonexistingfilename.hocon",
  544. ?assertEqual(
  545. {error, {enoent, File}},
  546. emqx_hocon:load_and_check(emqx_conf_schema, File)
  547. )
  548. end},
  549. {"bad syntax", fun() ->
  550. %% use abs path to match error return
  551. File = "/tmp/emqx-conf-bad-syntax-test.hocon",
  552. with_file(
  553. File,
  554. "{",
  555. fun() ->
  556. ?assertMatch(
  557. {error, #{file := File}},
  558. emqx_hocon:load_and_check(emqx_conf_schema, File)
  559. )
  560. end
  561. )
  562. end},
  563. {"type-check failure", fun() ->
  564. File = "emqx-conf-type-check-failure.hocon",
  565. %% typecheck fail because cookie is required field
  566. with_file(
  567. File,
  568. "node {}",
  569. fun() ->
  570. ?assertMatch(
  571. {error, #{
  572. kind := validation_error,
  573. path := "node.cookie",
  574. reason := required_field
  575. }},
  576. emqx_hocon:load_and_check(emqx_conf_schema, File)
  577. )
  578. end
  579. )
  580. end},
  581. {"ok load", fun() ->
  582. File = "emqx-conf-test-tmp-file-load-ok.hocon",
  583. with_file(File, "plugins: {}", fun() ->
  584. ?assertMatch({ok, _}, emqx_hocon:load_and_check(emqx_conf_schema, File))
  585. end)
  586. end}
  587. ].
  588. %% erlfmt-ignore
  589. dns_record_conf(NodeName, DnsRecordType) ->
  590. "
  591. node {
  592. name = \"" ++ NodeName ++ "\"
  593. data_dir = \"data\"
  594. cookie = cookie
  595. max_ports = 2048
  596. process_limit = 10240
  597. }
  598. cluster {
  599. name = emqxcl
  600. discovery_strategy = dns
  601. dns.record_type = " ++ atom_to_list(DnsRecordType) ++"
  602. }
  603. ".
  604. a_record_with_non_ip_node_name_test_() ->
  605. Test = fun(DnsRecordType) ->
  606. {ok, ConfMap} = hocon:binary(dns_record_conf("emqx@local.host", DnsRecordType), #{
  607. format => map
  608. }),
  609. ?assertThrow(
  610. {emqx_conf_schema, [
  611. #{
  612. reason := integrity_validation_failure,
  613. result := #{domain := "local.host"},
  614. kind := validation_error,
  615. validation_name := check_node_name_and_discovery_strategy
  616. }
  617. ]},
  618. hocon_tconf:check_plain(emqx_conf_schema, ConfMap, #{required => false}, [node, cluster])
  619. )
  620. end,
  621. [
  622. {"a record", fun() -> Test(a) end},
  623. {"aaaa record", fun() -> Test(aaaa) end}
  624. ].
  625. dns_record_type_incompatiblie_with_node_host_ip_format_test_() ->
  626. Test = fun(Ip, DnsRecordType) ->
  627. {ok, ConfMap} = hocon:binary(dns_record_conf("emqx@" ++ Ip, DnsRecordType), #{format => map}),
  628. ?assertThrow(
  629. {emqx_conf_schema, [
  630. #{
  631. reason := integrity_validation_failure,
  632. result := #{
  633. record_type := DnsRecordType,
  634. address_type := _
  635. },
  636. kind := validation_error,
  637. validation_name := check_node_name_and_discovery_strategy
  638. }
  639. ]},
  640. hocon_tconf:check_plain(emqx_conf_schema, ConfMap, #{required => false}, [node, cluster])
  641. )
  642. end,
  643. [
  644. {"ipv4 address", fun() -> Test("::1", a) end},
  645. {"ipv6 address", fun() -> Test("127.0.0.1", aaaa) end}
  646. ].
  647. dns_srv_record_is_ok_test() ->
  648. {ok, ConfMap} = hocon:binary(dns_record_conf("emqx@local.host", srv), #{format => map}),
  649. ?assertMatch(
  650. Value when is_map(Value),
  651. hocon_tconf:check_plain(emqx_conf_schema, ConfMap, #{required => false}, [node, cluster])
  652. ).