iilyak closed pull request #1082: Introduce new _dbs_info endpoint to get info
of a list of databases
URL: https://github.com/apache/couchdb/pull/1082
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 9d6d30d076..17a9a4f3d9 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -69,6 +69,10 @@ require_valid_user = false
; List of headers that will be kept when the header Prefer: return=minimal is
included in a request.
; If Server header is left out, Mochiweb will add its own one in.
prefer_minimal = Cache-Control, Content-Length, Content-Range, Content-Type,
ETag, Server, Transfer-Encoding, Vary
+;
+; Limit maximum number of databases when tying to get detailed information
using
+; _dbs_info in a request
+max_db_number_for_dbs_info_req = 100
[database_compaction]
; larger buffer sizes can originate smaller files
diff --git a/src/chttpd/src/chttpd_auth_request.erl
b/src/chttpd/src/chttpd_auth_request.erl
index 4e2e0dbf2e..05c5e8e35d 100644
--- a/src/chttpd/src/chttpd_auth_request.erl
+++ b/src/chttpd/src/chttpd_auth_request.erl
@@ -35,6 +35,8 @@
authorize_request_int(#httpd{path_parts=[<<"favicon.ico">>|_]}=Req) ->
Req;
authorize_request_int(#httpd{path_parts=[<<"_all_dbs">>|_]}=Req) ->
Req;
+authorize_request_int(#httpd{path_parts=[<<"_dbs_info">>|_]}=Req) ->
+ Req;
authorize_request_int(#httpd{path_parts=[<<"_replicator">>],
method='PUT'}=Req) ->
require_admin(Req);
authorize_request_int(#httpd{path_parts=[<<"_replicator">>],
method='DELETE'}=Req) ->
@@ -81,6 +83,8 @@
server_authorization_check(#httpd{path_parts=[<<"_stats">>]}=Req) ->
Req;
server_authorization_check(#httpd{path_parts=[<<"_active_tasks">>]}=Req) ->
Req;
+server_authorization_check(#httpd{path_parts=[<<"_dbs_info">>]}=Req) ->
+ Req;
server_authorization_check(#httpd{method=Method,
path_parts=[<<"_utils">>|_]}=Req)
when Method =:= 'HEAD' orelse Method =:= 'GET' ->
Req;
diff --git a/src/chttpd/src/chttpd_httpd_handlers.erl
b/src/chttpd/src/chttpd_httpd_handlers.erl
index 9c3044126d..cb52e2c40d 100644
--- a/src/chttpd/src/chttpd_httpd_handlers.erl
+++ b/src/chttpd/src/chttpd_httpd_handlers.erl
@@ -18,6 +18,7 @@ url_handler(<<>>) -> fun
chttpd_misc:handle_welcome_req/1;
url_handler(<<"favicon.ico">>) -> fun chttpd_misc:handle_favicon_req/1;
url_handler(<<"_utils">>) -> fun chttpd_misc:handle_utils_dir_req/1;
url_handler(<<"_all_dbs">>) -> fun chttpd_misc:handle_all_dbs_req/1;
+url_handler(<<"_dbs_info">>) -> fun chttpd_misc:handle_dbs_info_req/1;
url_handler(<<"_active_tasks">>) -> fun chttpd_misc:handle_task_status_req/1;
url_handler(<<"_scheduler">>) -> fun
couch_replicator_httpd:handle_scheduler_req/1;
url_handler(<<"_node">>) -> fun chttpd_misc:handle_node_req/1;
diff --git a/src/chttpd/src/chttpd_misc.erl b/src/chttpd/src/chttpd_misc.erl
index 15eabbfbd1..253da233ef 100644
--- a/src/chttpd/src/chttpd_misc.erl
+++ b/src/chttpd/src/chttpd_misc.erl
@@ -14,6 +14,7 @@
-export([
handle_all_dbs_req/1,
+ handle_dbs_info_req/1,
handle_node_req/1,
handle_favicon_req/1,
handle_favicon_req/2,
@@ -37,6 +38,8 @@
[send_json/2,send_json/3,send_method_not_allowed/2,
send_chunk/2,start_chunked_response/3]).
+-define(MAX_DB_NUM_FOR_DBS_INFO, 100).
+
% httpd global handlers
handle_welcome_req(Req) ->
@@ -141,6 +144,38 @@ all_dbs_callback({error, Reason}, #vacc{resp=Resp0}=Acc) ->
{ok, Resp1} = chttpd:send_delayed_error(Resp0, Reason),
{ok, Acc#vacc{resp=Resp1}}.
+handle_dbs_info_req(#httpd{method='POST'}=Req) ->
+ chttpd:validate_ctype(Req, "application/json"),
+ Props = chttpd:json_body_obj(Req),
+ Keys = couch_mrview_util:get_view_keys(Props),
+ case Keys of
+ undefined -> throw({bad_request, "`keys` member must exist."});
+ _ -> ok
+ end,
+ MaxNumber = config:get_integer("chttpd",
+ "max_db_number_for_dbs_info_req", ?MAX_DB_NUM_FOR_DBS_INFO),
+ case length(Keys) =< MaxNumber of
+ true -> ok;
+ false -> throw({bad_request, too_many_keys})
+ end,
+ {ok, Resp} = chttpd:start_json_response(Req, 200),
+ send_chunk(Resp, "["),
+ lists:foldl(fun(DbName, AccSeparator) ->
+ case catch fabric:get_db_info(DbName) of
+ {ok, Result} ->
+ Json = ?JSON_ENCODE({[{key, DbName}, {info, {Result}}]}),
+ send_chunk(Resp, AccSeparator ++ Json);
+ _ ->
+ Json = ?JSON_ENCODE({[{key, DbName}, {error, not_found}]}),
+ send_chunk(Resp, AccSeparator ++ Json)
+ end,
+ "," % AccSeparator now has a comma
+ end, "", Keys),
+ send_chunk(Resp, "]"),
+ chttpd:end_json_response(Resp);
+handle_dbs_info_req(Req) ->
+ send_method_not_allowed(Req, "POST").
+
handle_task_status_req(#httpd{method='GET'}=Req) ->
{Replies, _BadNodes} = gen_server:multi_call(couch_task_status, all),
Response = lists:flatmap(fun({Node, Tasks}) ->
diff --git a/src/chttpd/test/chttpd_dbs_info_test.erl
b/src/chttpd/test/chttpd_dbs_info_test.erl
new file mode 100644
index 0000000000..5b61d88316
--- /dev/null
+++ b/src/chttpd/test/chttpd_dbs_info_test.erl
@@ -0,0 +1,169 @@
+% 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(chttpd_dbs_info_test).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+-define(USER, "chttpd_db_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(CONTENT_JSON, {"Content-Type", "application/json"}).
+
+
+setup() ->
+ Hashed = couch_passwords:hash_admin_password(?PASS),
+ ok = config:set("admins", ?USER, ?b2l(Hashed), _Persist=false),
+ Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
+ Port = mochiweb_socket_server:get(chttpd, port),
+ Url = lists:concat(["http://", Addr, ":", Port, "/"]),
+ Db1Url = lists:concat([Url, "db1"]),
+ create_db(Db1Url),
+ Db2Url = lists:concat([Url, "db2"]),
+ create_db(Db2Url),
+ Url.
+
+teardown(Url) ->
+ Db1Url = lists:concat([Url, "db1"]),
+ Db2Url = lists:concat([Url, "db2"]),
+ delete_db(Db1Url),
+ delete_db(Db2Url),
+ ok = config:delete("admins", ?USER, _Persist=false).
+
+create_db(Url) ->
+ {ok, Status, _, _} = test_request:put(Url, [?CONTENT_JSON, ?AUTH], "{}"),
+ ?assert(Status =:= 201 orelse Status =:= 202).
+
+delete_db(Url) ->
+ {ok, 200, _, _} = test_request:delete(Url, [?AUTH]).
+
+dbs_info_test_() ->
+ {
+ "chttpd dbs info tests",
+ {
+ setup,
+ fun chttpd_test_util:start_couch/0, fun
chttpd_test_util:stop_couch/1,
+ {
+ foreach,
+ fun setup/0, fun teardown/1,
+ [
+ fun should_return_error_for_get_db_info/1,
+ fun should_return_dbs_info_for_single_db/1,
+ fun should_return_dbs_info_for_multiple_dbs/1,
+ fun should_return_error_for_exceeded_keys/1,
+ fun should_return_error_for_missing_keys/1,
+ fun should_return_dbs_info_for_dbs_with_mixed_state/1
+ ]
+ }
+ }
+ }.
+
+
+should_return_error_for_get_db_info(Url) ->
+ ?_test(begin
+ {ok, Code, _, ResultBody} = test_request:get(Url ++ "/_dbs_info?"
+ ++ "keys=[\"db1\"]", [?CONTENT_JSON, ?AUTH]),
+ {Body} = jiffy:decode(ResultBody),
+ [
+ ?assertEqual(<<"method_not_allowed">>,
+ couch_util:get_value(<<"error">>, Body)),
+ ?assertEqual(405, Code)
+ ]
+ end).
+
+
+should_return_dbs_info_for_single_db(Url) ->
+ ?_test(begin
+ NewDoc = "{\"keys\": [\"db1\"]}",
+ {ok, _, _, ResultBody} = test_request:post(Url ++ "/_dbs_info/",
+ [?CONTENT_JSON, ?AUTH], NewDoc),
+ BodyJson = jiffy:decode(ResultBody),
+ {Db1Data} = lists:nth(1, BodyJson),
+ [
+ ?assertEqual(<<"db1">>,
+ couch_util:get_value(<<"key">>, Db1Data)),
+ ?assertNotEqual(undefined,
+ couch_util:get_value(<<"info">>, Db1Data))
+ ]
+ end).
+
+
+should_return_dbs_info_for_multiple_dbs(Url) ->
+ ?_test(begin
+ NewDoc = "{\"keys\": [\"db1\", \"db2\"]}",
+ {ok, _, _, ResultBody} = test_request:post(Url ++ "/_dbs_info/",
+ [?CONTENT_JSON, ?AUTH], NewDoc),
+ BodyJson = jiffy:decode(ResultBody),
+ {Db1Data} = lists:nth(1, BodyJson),
+ {Db2Data} = lists:nth(2, BodyJson),
+ [
+ ?assertEqual(<<"db1">>,
+ couch_util:get_value(<<"key">>, Db1Data)),
+ ?assertNotEqual(undefined,
+ couch_util:get_value(<<"info">>, Db1Data)),
+ ?assertEqual(<<"db2">>,
+ couch_util:get_value(<<"key">>, Db2Data)),
+ ?assertNotEqual(undefined,
+ couch_util:get_value(<<"info">>, Db2Data))
+ ]
+ end).
+
+
+should_return_error_for_exceeded_keys(Url) ->
+ ?_test(begin
+ NewDoc = "{\"keys\": [\"db1\", \"db2\"]}",
+ ok = config:set("chttpd", "max_db_number_for_dbs_info_req", "1"),
+ {ok, Code, _, ResultBody} = test_request:post(Url ++ "/_dbs_info/",
+ [?CONTENT_JSON, ?AUTH], NewDoc),
+ {Body} = jiffy:decode(ResultBody),
+ ok = config:delete("chttpd", "max_db_number_for_dbs_info_req"),
+ [
+ ?assertEqual(<<"bad_request">>,
+ couch_util:get_value(<<"error">>, Body)),
+ ?assertEqual(400, Code)
+ ]
+ end).
+
+
+should_return_error_for_missing_keys(Url) ->
+ ?_test(begin
+ NewDoc = "{\"missingkeys\": [\"db1\", \"db2\"]}",
+ {ok, Code, _, ResultBody} = test_request:post(Url ++ "/_dbs_info/",
+ [?CONTENT_JSON, ?AUTH], NewDoc),
+ {Body} = jiffy:decode(ResultBody),
+ [
+ ?assertEqual(<<"bad_request">>,
+ couch_util:get_value(<<"error">>, Body)),
+ ?assertEqual(400, Code)
+ ]
+ end).
+
+
+should_return_dbs_info_for_dbs_with_mixed_state(Url) ->
+ ?_test(begin
+ NewDoc = "{\"keys\": [\"db1\", \"noexisteddb\"]}",
+ {ok, _, _, ResultBody} = test_request:post(Url ++ "/_dbs_info/",
+ [?CONTENT_JSON, ?AUTH], NewDoc),
+ Json = jiffy:decode(ResultBody),
+ {Db1Data} = lists:nth(1, Json),
+ {Db2Data} = lists:nth(2, Json),
+ [
+ ?assertEqual(
+ <<"db1">>, couch_util:get_value(<<"key">>, Db1Data)),
+ ?assertNotEqual(undefined,
+ couch_util:get_value(<<"info">>, Db1Data)),
+ ?assertEqual(
+ <<"noexisteddb">>, couch_util:get_value(<<"key">>, Db2Data)),
+ ?assertEqual(undefined, couch_util:get_value(<<"info">>, Db2Data))
+ ]
+ end).
diff --git a/src/couch/test/chttpd_endpoints_tests.erl
b/src/couch/test/chttpd_endpoints_tests.erl
index 7155767138..9b74308236 100644
--- a/src/couch/test/chttpd_endpoints_tests.erl
+++ b/src/couch/test/chttpd_endpoints_tests.erl
@@ -41,6 +41,7 @@ handlers(url_handler) ->
{<<"favicon.ico">>, chttpd_misc, handle_favicon_req},
{<<"_utils">>, chttpd_misc, handle_utils_dir_req},
{<<"_all_dbs">>, chttpd_misc, handle_all_dbs_req},
+ {<<"_dbs_info">>, chttpd_misc, handle_dbs_info_req},
{<<"_active_tasks">>, chttpd_misc, handle_task_status_req},
{<<"_node">>, chttpd_misc, handle_node_req},
{<<"_reload_query_servers">>, chttpd_misc,
handle_reload_query_servers_req},
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services