| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- %%--------------------------------------------------------------------
- %% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
- %%
- %% Licensed under the Apache License, Version 2.0 (the "License");
- %% you may not use this file except in compliance with the License.
- %% You may obtain a copy of the License at
- %%
- %% http://www.apache.org/licenses/LICENSE-2.0
- %%
- %% Unless required by applicable law or agreed to in writing, software
- %% distributed under the License is distributed on an "AS IS" BASIS,
- %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- %% See the License for the specific language governing permissions and
- %% limitations under the License.
- %%--------------------------------------------------------------------
- -module(emqx_ft_api_SUITE).
- -compile(export_all).
- -compile(nowarn_export_all).
- -include_lib("common_test/include/ct.hrl").
- -include_lib("stdlib/include/assert.hrl").
- -import(emqx_dashboard_api_test_helpers, [host/0, uri/1]).
- all() -> emqx_common_test_helpers:all(?MODULE).
- suite() ->
- [{timetrap, {seconds, 90}}].
- init_per_suite(Config) ->
- WorkDir = ?config(priv_dir, Config),
- Cluster = mk_cluster_specs(Config),
- Nodes = [Node1 | _] = emqx_cth_cluster:start(Cluster, #{work_dir => WorkDir}),
- {ok, App} = erpc:call(Node1, emqx_common_test_http, create_default_app, []),
- [{cluster_nodes, Nodes}, {api, App} | Config].
- end_per_suite(Config) ->
- ok = emqx_cth_cluster:stop(?config(cluster_nodes, Config)).
- mk_cluster_specs(_Config) ->
- Apps = [
- emqx_conf,
- {emqx, #{override_env => [{boot_modules, [broker, listeners]}]}},
- {emqx_ft, "file_transfer { enable = true }"},
- {emqx_management, #{}}
- ],
- DashboardConfig =
- "dashboard { \n"
- " listeners.http { enable = true, bind = 0 } \n"
- " default_username = \"\" \n"
- " default_password = \"\" \n"
- "}\n",
- [
- {emqx_ft_api_SUITE1, #{
- role => core,
- apps => Apps ++
- [
- {emqx_dashboard, DashboardConfig ++ "dashboard.listeners.http.bind = 18083"}
- ]
- }},
- {emqx_ft_api_SUITE2, #{
- role => core,
- apps => Apps ++ [{emqx_dashboard, DashboardConfig}]
- }},
- {emqx_ft_api_SUITE3, #{
- role => replicant,
- apps => Apps ++ [{emqx_dashboard, DashboardConfig}]
- }}
- ].
- init_per_testcase(Case, Config) ->
- [{tc, Case} | Config].
- end_per_testcase(_Case, Config) ->
- ok = reset_ft_config(Config, true),
- ok.
- %%--------------------------------------------------------------------
- %% Tests
- %%--------------------------------------------------------------------
- t_list_files(Config) ->
- ClientId = client_id(Config),
- FileId = <<"f1">>,
- Node = lists:last(test_nodes(Config)),
- ok = emqx_ft_test_helpers:upload_file(ClientId, FileId, "f1", <<"data">>, Node),
- {ok, 200, #{<<"files">> := Files}} =
- request_json(get, uri(["file_transfer", "files"]), Config),
- ?assertMatch(
- [#{<<"clientid">> := ClientId, <<"fileid">> := <<"f1">>}],
- [File || File = #{<<"clientid">> := CId} <- Files, CId == ClientId]
- ),
- {ok, 200, #{<<"files">> := FilesTransfer}} =
- request_json(get, uri(["file_transfer", "files", ClientId, FileId]), Config),
- ?assertMatch(
- [#{<<"clientid">> := ClientId, <<"fileid">> := <<"f1">>}],
- FilesTransfer
- ),
- ?assertMatch(
- {ok, 404, #{<<"code">> := <<"FILES_NOT_FOUND">>}},
- request_json(get, uri(["file_transfer", "files", ClientId, <<"no-such-file">>]), Config)
- ).
- t_download_transfer(Config) ->
- ClientId = client_id(Config),
- FileId = <<"f1">>,
- Nodes = [Node | _] = test_nodes(Config),
- NodeUpload = lists:last(Nodes),
- ok = emqx_ft_test_helpers:upload_file(ClientId, FileId, "f1", <<"data">>, NodeUpload),
- ?assertMatch(
- {ok, 400, #{<<"code">> := <<"BAD_REQUEST">>}},
- request_json(
- get,
- uri(["file_transfer", "file"]) ++ query(#{fileref => FileId}),
- Config
- )
- ),
- ?assertMatch(
- {ok, 503, _},
- request(
- get,
- uri(["file_transfer", "file"]) ++
- query(#{fileref => FileId, node => <<"nonode@nohost">>}),
- Config
- )
- ),
- ?assertMatch(
- {ok, 404, _},
- request(
- get,
- uri(["file_transfer", "file"]) ++
- query(#{fileref => <<"unknown_file">>, node => Node}),
- Config
- )
- ),
- ?assertMatch(
- {ok, 404, #{<<"message">> := <<"Invalid query parameter", _/bytes>>}},
- request_json(
- get,
- uri(["file_transfer", "file"]) ++
- query(#{fileref => <<>>, node => Node}),
- Config
- )
- ),
- ?assertMatch(
- {ok, 404, #{<<"message">> := <<"Invalid query parameter", _/bytes>>}},
- request_json(
- get,
- uri(["file_transfer", "file"]) ++
- query(#{fileref => <<"/etc/passwd">>, node => Node}),
- Config
- )
- ),
- {ok, 200, #{<<"files">> := [File]}} =
- request_json(get, uri(["file_transfer", "files", ClientId, FileId]), Config),
- {ok, 200, Response} = request(get, host() ++ maps:get(<<"uri">>, File), Config),
- ?assertEqual(
- <<"data">>,
- Response
- ).
- t_list_files_paging(Config) ->
- ClientId = client_id(Config),
- NFiles = 20,
- Nodes = test_nodes(Config),
- Uploads = [
- {mk_file_id("file:", N), mk_file_name(N), pick(N, Nodes)}
- || N <- lists:seq(1, NFiles)
- ],
- ok = lists:foreach(
- fun({FileId, Name, Node}) ->
- ok = emqx_ft_test_helpers:upload_file(ClientId, FileId, Name, <<"data">>, Node)
- end,
- Uploads
- ),
- ?assertMatch(
- {ok, 200, #{<<"files">> := [_, _, _], <<"cursor">> := _}},
- request_json(get, uri(["file_transfer", "files"]) ++ query(#{limit => 3}), Config)
- ),
- {ok, 200, #{<<"files">> := Files}} =
- request_json(get, uri(["file_transfer", "files"]) ++ query(#{limit => 100}), Config),
- ?assert(length(Files) >= NFiles),
- ?assertNotMatch(
- {ok, 200, #{<<"cursor">> := _}},
- request_json(get, uri(["file_transfer", "files"]) ++ query(#{limit => 100}), Config)
- ),
- ?assertMatch(
- {ok, 400, #{<<"code">> := <<"BAD_REQUEST">>}},
- request_json(get, uri(["file_transfer", "files"]) ++ query(#{limit => 0}), Config)
- ),
- ?assertMatch(
- {ok, 400, #{<<"code">> := <<"BAD_REQUEST">>}},
- request_json(get, uri(["file_transfer", "files"]) ++ query(#{following => <<>>}), Config)
- ),
- ?assertMatch(
- {ok, 400, #{<<"code">> := <<"BAD_REQUEST">>}},
- request_json(
- get, uri(["file_transfer", "files"]) ++ query(#{following => <<"{\"\":}">>}), Config
- )
- ),
- ?assertMatch(
- {ok, 400, #{<<"code">> := <<"BAD_REQUEST">>}},
- request_json(
- get,
- uri(["file_transfer", "files"]) ++ query(#{following => <<"whatsthat!?">>}),
- Config
- )
- ),
- PageThrough = fun PageThrough(Query, Acc) ->
- case request_json(get, uri(["file_transfer", "files"]) ++ query(Query), Config) of
- {ok, 200, #{<<"files">> := FilesPage, <<"cursor">> := Cursor}} ->
- PageThrough(Query#{following => Cursor}, Acc ++ FilesPage);
- {ok, 200, #{<<"files">> := FilesPage}} ->
- Acc ++ FilesPage
- end
- end,
- ?assertEqual(Files, PageThrough(#{limit => 1}, [])),
- ?assertEqual(Files, PageThrough(#{limit => 8}, [])),
- ?assertEqual(Files, PageThrough(#{limit => NFiles}, [])).
- t_ft_disabled(Config) ->
- ?assertMatch(
- {ok, 200, _},
- request_json(get, uri(["file_transfer", "files"]), Config)
- ),
- ?assertMatch(
- {ok, 400, _},
- request_json(
- get,
- uri(["file_transfer", "file"]) ++ query(#{fileref => <<"f1">>}),
- Config
- )
- ),
- ok = reset_ft_config(Config, false),
- ?assertMatch(
- {ok, 503, _},
- request_json(get, uri(["file_transfer", "files"]), Config)
- ),
- ?assertMatch(
- {ok, 503, _},
- request_json(
- get,
- uri(["file_transfer", "file"]) ++ query(#{fileref => <<"f1">>, node => node()}),
- Config
- )
- ).
- t_configure(Config) ->
- ?assertMatch(
- {ok, 200, #{<<"enable">> := true, <<"storage">> := #{}}},
- request_json(get, uri(["file_transfer"]), Config)
- ),
- ?assertMatch(
- {ok, 200, #{<<"enable">> := false}},
- request_json(put, uri(["file_transfer"]), #{<<"enable">> => false}, Config)
- ),
- ?assertMatch(
- {ok, 200, #{<<"enable">> := false}},
- request_json(get, uri(["file_transfer"]), Config)
- ),
- ?assertMatch(
- {ok, 200, #{}},
- request_json(
- put,
- uri(["file_transfer"]),
- #{
- <<"enable">> => true,
- <<"storage">> => emqx_ft_test_helpers:local_storage(Config)
- },
- Config
- )
- ),
- ?assertMatch(
- {ok, 400, _},
- request(
- put,
- uri(["file_transfer"]),
- #{
- <<"enable">> => true,
- <<"storage">> => #{
- <<"local">> => #{},
- <<"remote">> => #{}
- }
- },
- Config
- )
- ),
- ?assertMatch(
- {ok, 400, _},
- request(
- put,
- uri(["file_transfer"]),
- #{
- <<"enable">> => true,
- <<"storage">> => #{
- <<"local">> => #{
- <<"gc">> => #{<<"interval">> => -42}
- }
- }
- },
- Config
- )
- ),
- S3Exporter = #{
- <<"host">> => <<"localhost">>,
- <<"port">> => 9000,
- <<"bucket">> => <<"emqx">>,
- <<"transport_options">> => #{
- <<"ssl">> => #{
- <<"enable">> => true,
- <<"certfile">> => emqx_ft_test_helpers:pem_privkey(),
- <<"keyfile">> => emqx_ft_test_helpers:pem_privkey()
- }
- }
- },
- ?assertMatch(
- {ok, 200, #{
- <<"enable">> := true,
- <<"storage">> := #{
- <<"local">> := #{
- <<"exporter">> := #{
- <<"s3">> := #{
- <<"transport_options">> := #{
- <<"ssl">> := #{
- <<"enable">> := true,
- <<"certfile">> := <<"/", _CertFilepath/bytes>>,
- <<"keyfile">> := <<"/", _KeyFilepath/bytes>>
- }
- }
- }
- }
- }
- }
- }},
- request_json(
- put,
- uri(["file_transfer"]),
- #{
- <<"enable">> => true,
- <<"storage">> => #{
- <<"local">> => #{
- <<"exporter">> => #{
- <<"s3">> => S3Exporter
- }
- }
- }
- },
- Config
- )
- ),
- ?assertMatch(
- {ok, 400, _},
- request_json(
- put,
- uri(["file_transfer"]),
- #{
- <<"enable">> => true,
- <<"storage">> => #{
- <<"local">> => #{
- <<"exporter">> => #{
- <<"s3">> => emqx_utils_maps:deep_put(
- [<<"transport_options">>, <<"ssl">>, <<"keyfile">>],
- S3Exporter,
- <<>>
- )
- }
- }
- }
- },
- Config
- )
- ),
- ?assertMatch(
- {ok, 200, #{}},
- request_json(
- put,
- uri(["file_transfer"]),
- #{
- <<"enable">> => true,
- <<"storage">> => #{
- <<"local">> => #{
- <<"exporter">> => #{
- <<"s3">> => emqx_utils_maps:deep_put(
- [<<"transport_options">>, <<"ssl">>, <<"enable">>],
- S3Exporter,
- false
- )
- }
- }
- }
- },
- Config
- )
- ),
- ok.
- %%--------------------------------------------------------------------
- %% Helpers
- %%--------------------------------------------------------------------
- test_nodes(Config) ->
- ?config(cluster_nodes, Config).
- client_id(Config) ->
- iolist_to_binary(io_lib:format("~s.~s", [?config(group, Config), ?config(tc, Config)])).
- mk_file_id(Prefix, N) ->
- iolist_to_binary([Prefix, integer_to_list(N)]).
- mk_file_name(N) ->
- "file." ++ integer_to_list(N).
- request(Method, Url, Config) ->
- request(Method, Url, [], Config).
- request(Method, Url, Body, Config) ->
- Opts = #{compatible_mode => true, httpc_req_opts => [{body_format, binary}]},
- request(Method, Url, Body, Opts, Config).
- request(Method, Url, Body, Opts, Config) ->
- emqx_mgmt_api_test_util:request_api(Method, Url, [], auth_header(Config), Body, Opts).
- request_json(Method, Url, Body, Config) ->
- case request(Method, Url, Body, Config) of
- {ok, Code, RespBody} ->
- {ok, Code, json(RespBody)};
- Otherwise ->
- Otherwise
- end.
- request_json(Method, Url, Config) ->
- request_json(Method, Url, [], Config).
- json(Body) when is_binary(Body) ->
- emqx_utils_json:decode(Body, [return_maps]).
- query(Params) ->
- KVs = lists:map(fun({K, V}) -> uri_encode(K) ++ "=" ++ uri_encode(V) end, maps:to_list(Params)),
- "?" ++ string:join(KVs, "&").
- auth_header(Config) ->
- #{api_key := ApiKey, api_secret := Secret} = ?config(api, Config),
- emqx_common_test_http:auth_header(binary_to_list(ApiKey), binary_to_list(Secret)).
- uri_encode(T) ->
- emqx_http_lib:uri_encode(to_list(T)).
- to_list(A) when is_atom(A) ->
- atom_to_list(A);
- to_list(A) when is_integer(A) ->
- integer_to_list(A);
- to_list(B) when is_binary(B) ->
- binary_to_list(B);
- to_list(L) when is_list(L) ->
- L.
- pick(N, List) ->
- lists:nth(1 + (N rem length(List)), List).
- reset_ft_config(Config, Enable) ->
- [Node | _] = test_nodes(Config),
- LocalConfig =
- #{
- <<"enable">> => Enable,
- <<"storage">> => #{
- <<"local">> => #{
- <<"enable">> => true
- }
- }
- },
- {ok, _} = rpc:call(Node, emqx_ft_conf, update, [LocalConfig]),
- ok.
|