diff options
-rw-r--r-- | src/chttpd/src/chttpd_misc.erl | 39 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_util.erl | 12 | ||||
-rw-r--r-- | src/chttpd/test/eunit/chttpd_db_test.erl | 3 | ||||
-rw-r--r-- | 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 6d119572d..ca1794cba 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 ca4ffc8a7..955beca57 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 07661733f..c76b31581 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 b4027eead..71ee86bc9 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 |