emqx_prometheus_api_SUITE.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-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_prometheus_api_SUITE).
  17. -compile(export_all).
  18. -compile(nowarn_export_all).
  19. -include_lib("eunit/include/eunit.hrl").
  20. -include_lib("common_test/include/ct.hrl").
  21. %%--------------------------------------------------------------------
  22. %% Setups
  23. %%--------------------------------------------------------------------
  24. all() ->
  25. [
  26. {group, new_config},
  27. {group, legacy_config}
  28. ].
  29. groups() ->
  30. [
  31. {new_config, [sequence], [t_stats_auth_api, t_stats_no_auth_api, t_prometheus_api]},
  32. {legacy_config, [sequence], [t_stats_no_auth_api, t_legacy_prometheus_api]}
  33. ].
  34. init_per_suite(Config) ->
  35. Apps = emqx_cth_suite:start(
  36. [
  37. emqx,
  38. emqx_conf,
  39. emqx_management,
  40. {emqx_prometheus, #{start => false}},
  41. {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"},
  42. {emqx_license, "license.key = default"}
  43. ],
  44. #{work_dir => emqx_cth_suite:work_dir(Config)}
  45. ),
  46. {ok, _} = emqx_common_test_http:create_default_app(),
  47. [{suite_apps, Apps} | Config].
  48. end_per_suite(Config) ->
  49. ok = emqx_cth_suite:stop(?config(suite_apps, Config)).
  50. init_per_group(new_config, Config) ->
  51. Apps = emqx_cth_suite:start_app(
  52. emqx_prometheus,
  53. #{config => emqx_prometheus_SUITE:config(default)}
  54. ),
  55. [{group_apps, Apps} | Config];
  56. init_per_group(legacy_config, Config) ->
  57. Apps = emqx_cth_suite:start_app(
  58. emqx_prometheus,
  59. #{config => emqx_prometheus_SUITE:config(legacy)}
  60. ),
  61. [{group_apps, Apps} | Config].
  62. end_per_group(_Group, Config) ->
  63. ok = emqx_cth_suite:stop_apps(?config(group_apps, Config)).
  64. %%--------------------------------------------------------------------
  65. %% Cases
  66. %%--------------------------------------------------------------------
  67. %% we return recommend config for prometheus even if prometheus is legacy.
  68. t_legacy_prometheus_api(_) ->
  69. Path = emqx_mgmt_api_test_util:api_path(["prometheus"]),
  70. Auth = emqx_mgmt_api_test_util:auth_header_(),
  71. {ok, Response} = emqx_mgmt_api_test_util:request_api(get, Path, "", Auth),
  72. OldConf = emqx:get_raw_config([prometheus]),
  73. Conf = emqx_utils_json:decode(Response, [return_maps]),
  74. %% Always return new config.
  75. ?assertMatch(
  76. #{
  77. <<"collectors">> :=
  78. #{
  79. <<"mnesia">> := <<"disabled">>,
  80. <<"vm_dist">> := <<"disabled">>,
  81. <<"vm_memory">> := <<"disabled">>,
  82. <<"vm_msacc">> := <<"disabled">>,
  83. <<"vm_statistics">> := <<"disabled">>,
  84. <<"vm_system_info">> := <<"disabled">>
  85. },
  86. <<"enable_basic_auth">> := false,
  87. <<"push_gateway">> :=
  88. #{
  89. <<"enable">> := true,
  90. <<"headers">> := #{<<"Authorization">> := <<"some-authz-tokens">>},
  91. <<"interval">> := <<"1s">>,
  92. <<"job_name">> := <<"${name}~${host}">>,
  93. <<"url">> := <<"http://127.0.0.1:9091">>
  94. }
  95. },
  96. Conf
  97. ),
  98. #{<<"push_gateway">> := #{<<"enable">> := Enable}} = Conf,
  99. ?assertEqual(Enable, undefined =/= erlang:whereis(emqx_prometheus)),
  100. NewConf = OldConf#{
  101. <<"interval">> => <<"2s">>,
  102. <<"vm_statistics_collector">> => <<"enabled">>,
  103. <<"headers">> => #{
  104. <<"test-str1">> => <<"test-value">>,
  105. <<"test-str2">> => <<"42">>
  106. }
  107. },
  108. {ok, Response2} = emqx_mgmt_api_test_util:request_api(put, Path, "", Auth, NewConf),
  109. Conf2 = emqx_utils_json:decode(Response2, [return_maps]),
  110. ?assertEqual(NewConf, Conf2),
  111. EnvCollectors = env_collectors(),
  112. PromCollectors = all_collectors(),
  113. ?assertEqual(lists:sort(EnvCollectors), lists:sort(PromCollectors)),
  114. ?assert(lists:member(prometheus_vm_statistics_collector, EnvCollectors), EnvCollectors),
  115. lists:foreach(
  116. fun({C, Enabled}) ->
  117. ?assertEqual(Enabled, lists:member(C, EnvCollectors), EnvCollectors)
  118. end,
  119. [
  120. {prometheus_vm_dist_collector, false},
  121. {prometheus_vm_system_info_collector, false},
  122. {prometheus_vm_memory_collector, false},
  123. {prometheus_mnesia_collector, false},
  124. {prometheus_vm_msacc_collector, false},
  125. {prometheus_vm_statistics_collector, true}
  126. ]
  127. ),
  128. ?assertMatch(
  129. #{
  130. <<"headers">> := #{
  131. <<"test-str1">> := <<"test-value">>,
  132. <<"test-str2">> := <<"42">>
  133. }
  134. },
  135. emqx_config:get_raw([prometheus])
  136. ),
  137. ?assertMatch(
  138. #{
  139. headers := [
  140. {"test-str2", "42"},
  141. {"test-str1", "test-value"}
  142. ]
  143. },
  144. emqx_config:get([prometheus])
  145. ),
  146. NewConf1 = OldConf#{<<"enable">> => (not Enable)},
  147. {ok, _Response3} = emqx_mgmt_api_test_util:request_api(put, Path, "", Auth, NewConf1),
  148. ?assertEqual((not Enable), undefined =/= erlang:whereis(emqx_prometheus)),
  149. ConfWithoutScheme = OldConf#{<<"push_gateway_server">> => "127.0.0.1:8081"},
  150. ?assertMatch(
  151. {error, {"HTTP/1.1", 400, _}},
  152. emqx_mgmt_api_test_util:request_api(put, Path, "", Auth, ConfWithoutScheme)
  153. ),
  154. ok.
  155. t_prometheus_api(_) ->
  156. Path = emqx_mgmt_api_test_util:api_path(["prometheus"]),
  157. Auth = emqx_mgmt_api_test_util:auth_header_(),
  158. {ok, Response} = emqx_mgmt_api_test_util:request_api(get, Path, "", Auth),
  159. Conf = emqx_utils_json:decode(Response, [return_maps]),
  160. ?assertMatch(
  161. #{
  162. <<"push_gateway">> := #{},
  163. <<"collectors">> := _,
  164. <<"enable_basic_auth">> := _
  165. },
  166. Conf
  167. ),
  168. #{
  169. <<"push_gateway">> :=
  170. #{<<"url">> := Url, <<"enable">> := Enable} = PushGateway,
  171. <<"collectors">> := Collector
  172. } = Conf,
  173. Pid = erlang:whereis(emqx_prometheus),
  174. ?assertEqual(Enable, undefined =/= Pid, {Url, Pid}),
  175. NewConf = Conf#{
  176. <<"push_gateway">> => PushGateway#{
  177. <<"interval">> => <<"2s">>,
  178. <<"headers">> => #{
  179. <<"test-str1">> => <<"test-value">>,
  180. <<"test-str2">> => <<"42">>
  181. }
  182. },
  183. <<"collectors">> => Collector#{
  184. <<"vm_dist">> => <<"enabled">>,
  185. <<"vm_system_info">> => <<"enabled">>,
  186. <<"vm_memory">> => <<"enabled">>,
  187. <<"vm_msacc">> => <<"enabled">>,
  188. <<"mnesia">> => <<"enabled">>,
  189. <<"vm_statistics">> => <<"enabled">>
  190. }
  191. },
  192. {ok, Response2} = emqx_mgmt_api_test_util:request_api(put, Path, "", Auth, NewConf),
  193. Conf2 = emqx_utils_json:decode(Response2, [return_maps]),
  194. ?assertMatch(NewConf, Conf2),
  195. EnvCollectors = env_collectors(),
  196. PromCollectors = all_collectors(),
  197. ?assertEqual(lists:sort(EnvCollectors), lists:sort(PromCollectors)),
  198. ?assert(lists:member(prometheus_vm_statistics_collector, EnvCollectors), EnvCollectors),
  199. lists:foreach(
  200. fun({C, Enabled}) ->
  201. ?assertEqual(Enabled, lists:member(C, EnvCollectors), EnvCollectors)
  202. end,
  203. [
  204. {prometheus_vm_dist_collector, true},
  205. {prometheus_vm_system_info_collector, true},
  206. {prometheus_vm_memory_collector, true},
  207. {prometheus_mnesia_collector, true},
  208. {prometheus_vm_msacc_collector, true},
  209. {prometheus_vm_statistics_collector, true}
  210. ]
  211. ),
  212. ?assertMatch(
  213. #{
  214. <<"push_gateway">> := #{
  215. <<"headers">> := #{
  216. <<"test-str1">> := <<"test-value">>,
  217. <<"test-str2">> := <<"42">>
  218. }
  219. }
  220. },
  221. emqx_config:get_raw([prometheus])
  222. ),
  223. ?assertMatch(
  224. #{
  225. push_gateway := #{
  226. headers := [
  227. {"test-str2", "42"},
  228. {"test-str1", "test-value"}
  229. ]
  230. }
  231. },
  232. emqx_config:get([prometheus])
  233. ),
  234. NewConf1 = Conf#{<<"push_gateway">> => PushGateway#{<<"enable">> => false}},
  235. {ok, _Response3} = emqx_mgmt_api_test_util:request_api(put, Path, "", Auth, NewConf1),
  236. ?assertEqual(undefined, erlang:whereis(emqx_prometheus)),
  237. ConfWithoutScheme = Conf#{
  238. <<"push_gateway">> => PushGateway#{<<"url">> => <<"127.0.0.1:8081">>}
  239. },
  240. ?assertMatch(
  241. {error, {"HTTP/1.1", 400, _}},
  242. emqx_mgmt_api_test_util:request_api(put, Path, "", Auth, ConfWithoutScheme)
  243. ),
  244. ok.
  245. t_stats_no_auth_api(_) ->
  246. %% undefined is legacy prometheus
  247. case emqx:get_config([prometheus, enable_basic_auth], undefined) of
  248. true ->
  249. {ok, _} = emqx:update_config([prometheus, enable_basic_auth], false);
  250. _ ->
  251. ok
  252. end,
  253. emqx_dashboard_listener:regenerate_minirest_dispatch(),
  254. Headers = accept_josn_header(),
  255. request_stats(Headers, []).
  256. t_stats_auth_api(_) ->
  257. {ok, _} = emqx:update_config([prometheus, enable_basic_auth], true),
  258. emqx_dashboard_listener:regenerate_minirest_dispatch(),
  259. Auth = emqx_mgmt_api_test_util:auth_header_(),
  260. Headers = [Auth | accept_josn_header()],
  261. request_stats(Headers, Auth),
  262. ok.
  263. accept_josn_header() ->
  264. [{"accept", "application/json"}].
  265. request_stats(Headers, Auth) ->
  266. Path = emqx_mgmt_api_test_util:api_path(["prometheus", "stats"]),
  267. {ok, Response} = emqx_mgmt_api_test_util:request_api(get, Path, "", Headers),
  268. Data = emqx_utils_json:decode(Response, [return_maps]),
  269. ?assertMatch(#{<<"client">> := _, <<"delivery">> := _}, Data),
  270. {ok, _} = emqx_mgmt_api_test_util:request_api(get, Path, "", Auth),
  271. ok = meck:expect(mria_rlog, backend, fun() -> rlog end),
  272. {ok, _} = emqx_mgmt_api_test_util:request_api(get, Path, "", Auth).
  273. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  274. %%% Internal Functions
  275. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  276. env_collectors() ->
  277. do_env_collectors(application:get_env(prometheus, collectors, []), []).
  278. do_env_collectors([], Acc) ->
  279. lists:reverse(Acc);
  280. do_env_collectors([{_Registry, Collector} | Rest], Acc) when is_atom(Collector) ->
  281. do_env_collectors(Rest, [Collector | Acc]);
  282. do_env_collectors([Collector | Rest], Acc) when is_atom(Collector) ->
  283. do_env_collectors(Rest, [Collector | Acc]).
  284. all_collectors() ->
  285. emqx_prometheus_config:all_collectors().