diff options
author | jiangphcn <jiangph@cn.ibm.com> | 2017-11-24 22:41:37 +0800 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2017-11-28 10:33:23 -0500 |
commit | 3e511b37bde8238918edc18c1bed9ab7ca1cbc5f (patch) | |
tree | d77b9a9d7bb66524905fcf2da14785d742819b0f | |
parent | 27dcd6b3eb85017d4103c89182fd11f82d1a7752 (diff) | |
download | couchdb-3e511b37bde8238918edc18c1bed9ab7ca1cbc5f.tar.gz |
Allow replicator documents to include params for db creation
- specify q in "create_target_params": {"q": "1", ...}
issue-887
4 files changed, 177 insertions, 8 deletions
diff --git a/src/couch_replicator/src/couch_replicator_api_wrap.erl b/src/couch_replicator/src/couch_replicator_api_wrap.erl index ab8eb7f29..b5ea57c3c 100644 --- a/src/couch_replicator/src/couch_replicator_api_wrap.erl +++ b/src/couch_replicator/src/couch_replicator_api_wrap.erl @@ -24,7 +24,7 @@ -export([ db_open/2, - db_open/3, + db_open/4, db_close/1, get_db_info/1, get_pending_count/2, @@ -68,20 +68,21 @@ db_uri(Db) -> db_open(Db, Options) -> - db_open(Db, Options, false). + db_open(Db, Options, false, []). -db_open(#httpdb{} = Db1, _Options, Create) -> +db_open(#httpdb{} = Db1, _Options, Create, CreateParams) -> {ok, Db} = couch_replicator_httpc:setup(Db1), try case Create of false -> ok; true -> - send_req(Db, [{method, put}], + Db2 = maybe_append_create_query_params(Db, CreateParams), + send_req(Db2, [{method, put}], fun(401, _, _) -> - throw({unauthorized, ?l2b(db_uri(Db))}); + throw({unauthorized, ?l2b(db_uri(Db2))}); (403, _, _) -> - throw({forbidden, ?l2b(db_uri(Db))}); + throw({forbidden, ?l2b(db_uri(Db2))}); (_, _, _) -> ok end) @@ -118,7 +119,7 @@ db_open(#httpdb{} = Db1, _Options, Create) -> db_close(Db), erlang:exit(Error) end; -db_open(DbName, Options, Create) -> +db_open(DbName, Options, Create, _CreateParams) -> try case Create of false -> @@ -1020,6 +1021,14 @@ normalize_db(<<DbName/binary>>) -> DbName. +maybe_append_create_query_params(Db, []) -> + Db; + +maybe_append_create_query_params(Db, CreateParams) -> + NewUrl = Db#httpdb.url ++ "?" ++ mochiweb_util:urlencode(CreateParams), + Db#httpdb{url = NewUrl}. + + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). diff --git a/src/couch_replicator/src/couch_replicator_docs.erl b/src/couch_replicator/src/couch_replicator_docs.erl index d22b85f89..6666cba53 100644 --- a/src/couch_replicator/src/couch_replicator_docs.erl +++ b/src/couch_replicator/src/couch_replicator_docs.erl @@ -499,6 +499,11 @@ convert_options([{<<"create_target">>, V} | _R]) when not is_boolean(V)-> throw({bad_request, <<"parameter `create_target` must be a boolean">>}); convert_options([{<<"create_target">>, V} | R]) -> [{create_target, V} | convert_options(R)]; +convert_options([{<<"create_target_params">>, V} | _R]) when not is_tuple(V) -> + throw({bad_request, + <<"parameter `create_target_params` must be a JSON object">>}); +convert_options([{<<"create_target_params">>, V} | R]) -> + [{create_target_params, V} | convert_options(R)]; convert_options([{<<"continuous">>, V} | _R]) when not is_boolean(V)-> throw({bad_request, <<"parameter `continuous` must be a boolean">>}); convert_options([{<<"continuous">>, V} | R]) -> diff --git a/src/couch_replicator/src/couch_replicator_scheduler_job.erl b/src/couch_replicator/src/couch_replicator_scheduler_job.erl index e2d8fb6d6..0438249be 100644 --- a/src/couch_replicator/src/couch_replicator_scheduler_job.erl +++ b/src/couch_replicator/src/couch_replicator_scheduler_job.erl @@ -587,8 +587,9 @@ init_state(Rep) -> % Adjust minimum number of http source connections to 2 to avoid deadlock Src = adjust_maxconn(Src0, BaseId), {ok, Source} = couch_replicator_api_wrap:db_open(Src, [{user_ctx, UserCtx}]), + {CreateTargetParams} = get_value(create_target_params, Options, {[]}), {ok, Target} = couch_replicator_api_wrap:db_open(Tgt, [{user_ctx, UserCtx}], - get_value(create_target, Options, false)), + get_value(create_target, Options, false), CreateTargetParams), {ok, SourceInfo} = couch_replicator_api_wrap:get_db_info(Source), {ok, TargetInfo} = couch_replicator_api_wrap:get_db_info(Target), diff --git a/src/couch_replicator/test/couch_replicator_create_target_with_options_tests.erl b/src/couch_replicator/test/couch_replicator_create_target_with_options_tests.erl new file mode 100644 index 000000000..31bfd48c7 --- /dev/null +++ b/src/couch_replicator/test/couch_replicator_create_target_with_options_tests.erl @@ -0,0 +1,154 @@ +% 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(couch_replicator_create_target_with_options_tests). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). +-include_lib("couch_replicator/src/couch_replicator.hrl"). + + +setup(_) -> + Ctx1 = test_util:start_couch([fabric, mem3, couch_replicator]), + Ctx2 = chttpd_test_util:start_couch(), + Source = ?tempdb(), + Target = ?tempdb(), + {Ctx1, Ctx2, {Source, Target}}. + + +teardown(_, {Ctx1, Ctx2, {_Source, _Target}}) -> + ok = test_util:stop_couch(Ctx1), + ok = chttpd_test_util:stop_couch(Ctx2). + + +create_target_with_options_replication_test_() -> + Ps = [{local, remote}, {remote, remote}], + { + "Create target with range partitions tests", + { + foreachx, + fun setup/1, fun teardown/2, + [{P, fun should_create_target_with_q_4/2} || P <- Ps] ++ + [{P, fun should_create_target_with_q_2_n_1/2} || P <- Ps] ++ + [{P, fun should_create_target_with_default/2} || P <- Ps] ++ + [{P, fun should_not_create_target_with_q_any/2} || P <- Ps] + } + }. + + +should_create_target_with_q_4({From, To}, {_Ctx1, _Ctx2, {Source, Target}}) -> + RepObject = {[ + {<<"source">>, db_url(From, Source)}, + {<<"target">>, db_url(To, Target)}, + {<<"create_target">>, true}, + {<<"create_target_params">>, {[{<<"q">>, <<"4">>}]}} + ]}, + create_db(From, Source), + create_doc(From, Source), + {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER), + + {ok, TargetInfo} = fabric:get_db_info(Target), + {ClusterInfo} = couch_util:get_value(cluster, TargetInfo), + delete_db(From, Source), + delete_db(To, Target), + ?_assertEqual(4, couch_util:get_value(q, ClusterInfo)). + + +should_create_target_with_q_2_n_1( + {From, To}, {_Ctx1, _Ctx2, {Source, Target}}) -> + RepObject = {[ + {<<"source">>, db_url(From, Source)}, + {<<"target">>, db_url(To, Target)}, + {<<"create_target">>, true}, + {<<"create_target_params">>, + {[{<<"q">>, <<"2">>}, {<<"n">>, <<"1">>}]}} + ]}, + create_db(From, Source), + create_doc(From, Source), + {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER), + + {ok, TargetInfo} = fabric:get_db_info(Target), + {ClusterInfo} = couch_util:get_value(cluster, TargetInfo), + delete_db(From, Source), + delete_db(To, Target), + [ + ?_assertEqual(2, couch_util:get_value(q, ClusterInfo)), + ?_assertEqual(1, couch_util:get_value(n, ClusterInfo)) + ]. + + +should_create_target_with_default( + {From, To}, {_Ctx1, _Ctx2, {Source, Target}}) -> + RepObject = {[ + {<<"source">>, db_url(From, Source)}, + {<<"target">>, db_url(To, Target)}, + {<<"create_target">>, true} + ]}, + create_db(From, Source), + create_doc(From, Source), + {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER), + + {ok, TargetInfo} = fabric:get_db_info(Target), + {ClusterInfo} = couch_util:get_value(cluster, TargetInfo), + Q = config:get("cluster", "q", "8"), + delete_db(From, Source), + delete_db(To, Target), + ?_assertEqual(list_to_integer(Q), couch_util:get_value(q, ClusterInfo)). + + +should_not_create_target_with_q_any( + {From, To}, {_Ctx1, _Ctx2, {Source, Target}}) -> + RepObject = {[ + {<<"source">>, db_url(From, Source)}, + {<<"target">>, db_url(To, Target)}, + {<<"create_target">>, false}, + {<<"create_target_params">>, {[{<<"q">>, <<"1">>}]}} + ]}, + create_db(From, Source), + create_doc(From, Source), + {error, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER), + DbExist = is_list(catch mem3:shards(Target)), + delete_db(From, Source), + ?_assertEqual(false, DbExist). + + +create_doc(local, DbName) -> + {ok, Db} = couch_db:open_int(DbName, [?ADMIN_CTX]), + Body = {[{<<"foo">>, <<"bar">>}]}, + NewDoc = #doc{body = Body}, + {ok, _} = couch_db:update_doc(Db, NewDoc, []), + couch_db:close(Db); +create_doc(remote, DbName) -> + Body = {[{<<"foo">>, <<"bar">>}]}, + NewDoc = #doc{body = Body}, + {ok, _} = fabric:update_doc(DbName, NewDoc, [?ADMIN_CTX]). + + +create_db(local, DbName) -> + {ok, Db} = couch_db:create(DbName, [?ADMIN_CTX]), + ok = couch_db:close(Db); +create_db(remote, DbName) -> + ok = fabric:create_db(DbName, [?ADMIN_CTX]). + + +delete_db(local, DbName) -> + ok = couch_server:delete(DbName, [?ADMIN_CTX]); +delete_db(remote, DbName) -> + ok = fabric:delete_db(DbName, [?ADMIN_CTX]). + + +db_url(local, DbName) -> + DbName; +db_url(remote, DbName) -> + Addr = config:get("chttpd", "bind_address", "127.0.0.1"), + Port = mochiweb_socket_server:get(chttpd, port), + ?l2b(io_lib:format("http://~s:~b/~s", [Addr, Port, DbName])). |