You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2021/12/08 05:10:13 UTC

[couchdb] branch 3.x updated: add GET _dbs_info

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/3.x by this push:
     new 9dffb8f  add GET _dbs_info
9dffb8f is described below

commit 9dffb8fd3f7258af6b24602b036fa48bb5ffe303
Author: jiahuili <Ji...@ibm.com>
AuthorDate: Fri Nov 12 15:01:02 2021 -0600

    add GET _dbs_info
---
 src/chttpd/src/chttpd_misc.erl                 |  39 ++++--
 src/chttpd/src/chttpd_util.erl                 |  12 +-
 src/chttpd/test/eunit/chttpd_db_test.erl       |   3 +-
 src/chttpd/test/eunit/chttpd_dbs_info_test.erl | 168 ++++++++++++++++++++++---
 4 files changed, 194 insertions(+), 28 deletions(-)

diff --git a/src/chttpd/src/chttpd_misc.erl b/src/chttpd/src/chttpd_misc.erl
index 6d11957..ca1794c 100644
--- a/src/chttpd/src/chttpd_misc.erl
+++ b/src/chttpd/src/chttpd_misc.erl
@@ -115,6 +115,11 @@ handle_utils_dir_req(Req, _) ->
     send_method_not_allowed(Req, "GET,HEAD").
 
 handle_all_dbs_req(#httpd{method = 'GET'} = Req) ->
+    handle_all_dbs_info_req(Req);
+handle_all_dbs_req(Req) ->
+    send_method_not_allowed(Req, "GET,HEAD").
+
+handle_all_dbs_info_req(Req) ->
     Args = couch_mrview_http:parse_params(Req, undefined),
     ShardDbName = config:get("mem3", "shards_db", "_dbs"),
     %% shard_db is not sharded but mem3:shards treats it as an edge case
@@ -125,19 +130,18 @@ handle_all_dbs_req(#httpd{method = 'GET'} = Req) ->
     {ok, Resp} = chttpd:etag_respond(Req, Etag, fun() ->
         {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"ETag", Etag}]),
         VAcc = #vacc{req = Req, resp = Resp},
-        fabric:all_docs(ShardDbName, Options, fun all_dbs_callback/2, VAcc, Args)
+        fabric:all_docs(ShardDbName, Options, fun all_dbs_info_callback/2, VAcc, Args)
     end),
     case is_record(Resp, vacc) of
         true -> {ok, Resp#vacc.resp};
         _ -> {ok, Resp}
-    end;
-handle_all_dbs_req(Req) ->
-    send_method_not_allowed(Req, "GET,HEAD").
+    end.
 
-all_dbs_callback({meta, _Meta}, #vacc{resp = Resp0} = Acc) ->
+all_dbs_info_callback({meta, _Meta}, #vacc{resp = Resp0} = Acc) ->
     {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, "["),
     {ok, Acc#vacc{resp = Resp1}};
-all_dbs_callback({row, Row}, #vacc{resp = Resp0} = Acc) ->
+all_dbs_info_callback({row, Row}, #vacc{resp = Resp0} = Acc)
+  when Acc#vacc.req#httpd.path_parts =:= [<<"_all_dbs">>] ->
     Prepend = couch_mrview_http:prepend_val(Acc),
     case couch_util:get_value(id, Row) of
         <<"_design", _/binary>> ->
@@ -146,14 +150,31 @@ all_dbs_callback({row, Row}, #vacc{resp = Resp0} = Acc) ->
             {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, [Prepend, ?JSON_ENCODE(DbName)]),
             {ok, Acc#vacc{prepend = ",", resp = Resp1}}
     end;
-all_dbs_callback(complete, #vacc{resp = Resp0} = Acc) ->
+all_dbs_info_callback({row, Row}, #vacc{resp = Resp0} = Acc)
+  when Acc#vacc.req#httpd.path_parts =:= [<<"_dbs_info">>] ->
+    Prepend = couch_mrview_http:prepend_val(Acc),
+    DbName = couch_util:get_value(id, Row),
+    case chttpd_util:get_db_info(DbName) of
+        {ok, DbInfo} ->
+            Chunk = [Prepend, ?JSON_ENCODE({[{key, DbName}, {info, {DbInfo}}]})],
+            {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, Chunk),
+            {ok, Acc#vacc{prepend = ",", resp = Resp1}};
+        {error, database_does_not_exist} ->
+            {ok, Acc#vacc{resp = Resp0}};
+        {error, Reason} ->
+            {ok, Resp1} = chttpd:send_delayed_error(Resp0, Reason),
+            {stop, Acc#vacc{resp = Resp1}}
+    end;
+all_dbs_info_callback(complete, #vacc{resp = Resp0} = Acc) ->
     {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, "]"),
     {ok, Resp2} = chttpd:end_delayed_json_response(Resp1),
     {ok, Acc#vacc{resp = Resp2}};
-all_dbs_callback({error, Reason}, #vacc{resp = Resp0} = Acc) ->
+all_dbs_info_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='GET'}=Req) ->
+    handle_all_dbs_info_req(Req);
 handle_dbs_info_req(#httpd{method = 'POST'} = Req) ->
     chttpd:validate_ctype(Req, "application/json"),
     Props = chttpd:json_body_obj(Req),
@@ -192,7 +213,7 @@ handle_dbs_info_req(#httpd{method = 'POST'} = Req) ->
     send_chunk(Resp, "]"),
     chttpd:end_json_response(Resp);
 handle_dbs_info_req(Req) ->
-    send_method_not_allowed(Req, "POST").
+    send_method_not_allowed(Req, "GET,HEAD,POST").
 
 handle_task_status_req(#httpd{method = 'GET'} = Req) ->
     ok = chttpd:verify_is_server_admin(Req),
diff --git a/src/chttpd/src/chttpd_util.erl b/src/chttpd/src/chttpd_util.erl
index ca4ffc8..955beca 100644
--- a/src/chttpd/src/chttpd_util.erl
+++ b/src/chttpd/src/chttpd_util.erl
@@ -21,7 +21,8 @@
     get_chttpd_auth_config/2,
     get_chttpd_auth_config_integer/2,
     get_chttpd_auth_config_boolean/2,
-    maybe_add_csp_header/3
+    maybe_add_csp_header/3,
+    get_db_info/1
 ]).
 
 get_chttpd_config(Key) ->
@@ -100,3 +101,12 @@ handle_legacy_config(OriginalHeaders, DefaultHeaderValue) ->
         false ->
             OriginalHeaders
     end.
+
+get_db_info(DbName) ->
+    Timeout = fabric_util:request_timeout(),
+    IsolatedFun = fun() -> fabric:get_db_info(DbName) end,
+    try
+        fabric_util:isolate(IsolatedFun, Timeout)
+    catch
+        _Tag:Error -> {error, Error}
+    end.
diff --git a/src/chttpd/test/eunit/chttpd_db_test.erl b/src/chttpd/test/eunit/chttpd_db_test.erl
index 0766173..c76b315 100644
--- a/src/chttpd/test/eunit/chttpd_db_test.erl
+++ b/src/chttpd/test/eunit/chttpd_db_test.erl
@@ -411,7 +411,8 @@ should_not_change_db_proper_after_rewriting_shardmap(_) ->
 
             {Prop2} = ?JSON_DECODE(?JSON_ENCODE({Props})),
             Shards2 = mem3_util:build_shards(TmpDb, Prop2),
-            ?assertEqual(Shards2, Shards)
+            ?assertEqual(Shards2, Shards),
+            {ok, 200, _, _} = test_request:delete(BaseUrl, [?AUTH])
         end)}.
 
 should_succeed_on_all_docs_with_queries_keys(Url) ->
diff --git a/src/chttpd/test/eunit/chttpd_dbs_info_test.erl b/src/chttpd/test/eunit/chttpd_dbs_info_test.erl
index b4027ee..71ee86b 100644
--- a/src/chttpd/test/eunit/chttpd_dbs_info_test.erl
+++ b/src/chttpd/test/eunit/chttpd_dbs_info_test.erl
@@ -30,9 +30,12 @@ setup() ->
     create_db(Db1Url),
     Db2Url = lists:concat([Url, "db2"]),
     create_db(Db2Url),
+    mock(fabric_util),
+    mock(chttpd_util),
     Url.
 
 teardown(Url) ->
+    meck:unload(),
     Db1Url = lists:concat([Url, "db1"]),
     Db2Url = lists:concat([Url, "db2"]),
     delete_db(Db1Url),
@@ -46,6 +49,16 @@ create_db(Url) ->
 delete_db(Url) ->
     {ok, 200, _, _} = test_request:delete(Url, [?AUTH]).
 
+mock(Module) ->
+    meck:new(Module, [passthrough]).
+
+mock_timeout() ->
+    meck:expect(fabric_util, request_timeout, fun() -> 0 end).
+
+mock_db_not_exist() ->
+    meck:expect(chttpd_util, get_db_info,
+        fun(_) -> {error, database_does_not_exist} end).
+
 dbs_info_test_() ->
     {
         "chttpd dbs info tests",
@@ -58,7 +71,18 @@ dbs_info_test_() ->
                 fun setup/0,
                 fun teardown/1,
                 [
-                    fun should_return_error_for_get_db_info/1,
+                    fun get_db_info_should_return_db_info/1,
+                    fun get_db_info_should_return_error_when_db_not_exist/1,
+                    fun get_db_info_should_return_error_when_time_out/1,
+                    fun should_return_error_for_put_dbs_info/1,
+                    fun should_return_dbs_info_for_get_dbs_info/1,
+                    fun should_return_nothing_when_db_not_exist_for_get_dbs_info/1,
+                    fun should_return_500_time_out_when_time_is_not_enough_for_get_dbs_info/1,
+                    fun should_return_db2_for_get_dbs_info_with_descending/1,
+                    fun should_return_db1_for_get_dbs_info_with_limit_1/1,
+                    fun should_return_db2_for_get_dbs_info_with_skip_1/1,
+                    fun should_return_dbs_info_with_correct_start_end_key/1,
+                    fun should_return_empty_list_with_wrong_start_end_key/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,
@@ -69,22 +93,132 @@ dbs_info_test_() ->
         }
     }.
 
-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).
+
+get_db_info_should_return_db_info(_) ->
+    DbInfo = fabric:get_db_info("db1"),
+    ?_assertEqual(DbInfo, chttpd_util:get_db_info("db1")).
+
+
+get_db_info_should_return_error_when_db_not_exist(_) ->
+    ?_assertEqual({error, database_does_not_exist},
+        chttpd_util:get_db_info("db_not_exist")).
+
+
+get_db_info_should_return_error_when_time_out(_) ->
+    ?_test(
+        begin
+            mock_timeout(),
+            ?assertEqual({error, timeout}, chttpd_util:get_db_info("db1"))
+        end).
+
+
+should_return_error_for_put_dbs_info(Url) ->
+    ?_test(
+        begin
+            {ok, Code, _, ResultBody} = test_request:put(Url
+            ++ "_dbs_info", [?CONTENT_JSON, ?AUTH], ""),
+            {Body} = jiffy:decode(ResultBody),
+            ?assertEqual(405, Code),
+            ?assertEqual(<<"method_not_allowed">>,
+                couch_util:get_value(<<"error">>, Body))
+        end).
+
+
+should_return_dbs_info_for_get_dbs_info(Url) ->
+    ?_test(
+        begin
+            {ok, _, _, ResultBody} = test_request:get(Url
+            ++ "_dbs_info", [?CONTENT_JSON, ?AUTH]),
+            BodyJson = jiffy:decode(ResultBody),
+            {Db1Data} = lists:nth(1, BodyJson),
+            {Db2Data} = lists:nth(2, BodyJson),
+            ?assertEqual(2, length(BodyJson)),
+            ?assertEqual(<<"db1">>, couch_util:get_value(<<"key">>, Db1Data)),
+            ?assertEqual(<<"db2">>, couch_util:get_value(<<"key">>, Db2Data))
+        end).
+
+
+should_return_nothing_when_db_not_exist_for_get_dbs_info(Url) ->
+    ?_test(
+        begin
+            mock_db_not_exist(),
+            {ok, Code, _, ResultBody} = test_request:get(Url
+            ++ "_dbs_info", [?CONTENT_JSON, ?AUTH]),
+            BodyJson = jiffy:decode(ResultBody),
+            ?assertEqual(200, Code),
+            ?assertEqual([], BodyJson)
+        end).
+
+
+should_return_500_time_out_when_time_is_not_enough_for_get_dbs_info(Url) ->
+    ?_test(
+        begin
+            mock_timeout(),
+            {ok, Code, _, ResultBody} = test_request:get(Url ++ "_dbs_info"
+                ++ "?buffer_response=true", [?CONTENT_JSON, ?AUTH]),
+            {Body} = jiffy:decode(ResultBody),
+            ?assertEqual(500, Code),
+            ?assertEqual(<<"timeout">>, couch_util:get_value(<<"error">>, Body))
+        end).
+
+
+should_return_db2_for_get_dbs_info_with_descending(Url) ->
+    ?_test(
+        begin
+            {ok, _, _, ResultBody} = test_request:get(Url ++ "_dbs_info"
+                ++ "?descending=true", [?CONTENT_JSON, ?AUTH]),
+            BodyJson = jiffy:decode(ResultBody),
+            {Db1Data} = lists:nth(1, BodyJson),
+            {Db2Data} = lists:nth(2, BodyJson),
+            ?assertEqual(2, length(BodyJson)),
+            ?assertEqual(<<"db2">>, couch_util:get_value(<<"key">>, Db1Data)),
+            ?assertEqual(<<"db1">>, couch_util:get_value(<<"key">>, Db2Data))
+        end).
+
+
+should_return_db1_for_get_dbs_info_with_limit_1(Url) ->
+    ?_test(
+        begin
+            {ok, _, _, ResultBody} = test_request:get(Url ++ "_dbs_info"
+                ++ "?limit=1", [?CONTENT_JSON, ?AUTH]),
+            BodyJson = jiffy:decode(ResultBody),
+            {DbData} = lists:nth(1, BodyJson),
+            ?assertEqual(1, length(BodyJson)),
+            ?assertEqual(<<"db1">>, couch_util:get_value(<<"key">>, DbData))
+        end).
+
+
+should_return_db2_for_get_dbs_info_with_skip_1(Url) ->
+    ?_test(
+        begin
+            {ok, _, _, ResultBody} = test_request:get(Url ++ "_dbs_info"
+                ++ "?skip=1", [?CONTENT_JSON, ?AUTH]),
+            BodyJson = jiffy:decode(ResultBody),
+            {DbData} = lists:nth(1, BodyJson),
+            ?assertEqual(1, length(BodyJson)),
+            ?assertEqual(<<"db2">>, couch_util:get_value(<<"key">>, DbData))
+        end).
+
+
+should_return_dbs_info_with_correct_start_end_key(Url) ->
+    ?_test(
+        begin
+            {ok, _, _, ResultBody} = test_request:get(Url ++ "_dbs_info"
+                ++ "?startkey=\"db1\"&endkey=\"db2\"", [?CONTENT_JSON, ?AUTH]),
+            BodyJson = jiffy:decode(ResultBody),
+            {DbData} = lists:nth(1, BodyJson),
+            ?assertEqual(2, length(BodyJson)),
+            ?assertEqual(<<"db1">>, couch_util:get_value(<<"key">>, DbData))
+        end).
+
+
+should_return_empty_list_with_wrong_start_end_key(Url) ->
+    ?_test(
+        begin
+            {ok, _, _, ResultBody} = test_request:get(Url ++ "_dbs_info"
+                ++ "?startkey=\"db3\"&endkey=\"db4\"", [?CONTENT_JSON, ?AUTH]),
+            ?assertEqual([], jiffy:decode(ResultBody))
+        end).
 
 should_return_dbs_info_for_single_db(Url) ->
     ?_test(begin