summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjiangphcn <jiangph@cn.ibm.com>2017-11-24 22:41:37 +0800
committerNick Vatamaniuc <nickva@users.noreply.github.com>2017-11-28 10:33:23 -0500
commit3e511b37bde8238918edc18c1bed9ab7ca1cbc5f (patch)
treed77b9a9d7bb66524905fcf2da14785d742819b0f
parent27dcd6b3eb85017d4103c89182fd11f82d1a7752 (diff)
downloadcouchdb-3e511b37bde8238918edc18c1bed9ab7ca1cbc5f.tar.gz
Allow replicator documents to include params for db creation
- specify q in "create_target_params": {"q": "1", ...} issue-887
-rw-r--r--src/couch_replicator/src/couch_replicator_api_wrap.erl23
-rw-r--r--src/couch_replicator/src/couch_replicator_docs.erl5
-rw-r--r--src/couch_replicator/src/couch_replicator_scheduler_job.erl3
-rw-r--r--src/couch_replicator/test/couch_replicator_create_target_with_options_tests.erl154
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])).