You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by fd...@apache.org on 2010/08/24 13:21:30 UTC
svn commit: r988485 - in /couchdb/branches/new_replicator:
share/www/script/test/new_replication.js src/couchdb/couch_api_wrap.erl
src/couchdb/couch_api_wrap_httpc.erl src/couchdb/couch_httpd_rep.erl
src/couchdb/couch_replicate.erl
Author: fdmanana
Date: Tue Aug 24 11:21:29 2010
New Revision: 988485
URL: http://svn.apache.org/viewvc?rev=988485&view=rev
Log:
New replicator:
- simplify error handling in general;
- deal with HTTP 401 error codes (unauthorized);
- add tests for replication under a non-admin context
Modified:
couchdb/branches/new_replicator/share/www/script/test/new_replication.js
couchdb/branches/new_replicator/src/couchdb/couch_api_wrap.erl
couchdb/branches/new_replicator/src/couchdb/couch_api_wrap_httpc.erl
couchdb/branches/new_replicator/src/couchdb/couch_httpd_rep.erl
couchdb/branches/new_replicator/src/couchdb/couch_replicate.erl
Modified: couchdb/branches/new_replicator/share/www/script/test/new_replication.js
URL: http://svn.apache.org/viewvc/couchdb/branches/new_replicator/share/www/script/test/new_replication.js?rev=988485&r1=988484&r2=988485&view=diff
==============================================================================
--- couchdb/branches/new_replicator/share/www/script/test/new_replication.js (original)
+++ couchdb/branches/new_replicator/share/www/script/test/new_replication.js Tue Aug 24 11:21:29 2010
@@ -741,7 +741,154 @@ couchTests.new_replication = function(de
}
+ //
+ // test replication triggered by non admins
+ //
+
+ // case 1) user triggering the replication is not a DB admin of the target DB
+ var joeUserDoc = CouchDB.prepareUserDoc({
+ name: "joe",
+ roles: ["erlanger"]
+ }, "erly");
+ var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
+ var server_config = [
+ {
+ section: "couch_httpd_auth",
+ key: "authentication_db",
+ value: usersDb.name
+ }
+ ];
+
+ docs = makeDocs(1, 6);
+ docs.push({
+ _id: "_design/foo",
+ language: "javascript"
+ });
+
+ dbPairs = [
+ {
+ source: sourceDb.name,
+ target: targetDb.name
+ },
+ {
+ source: "http://" + host + "/" + sourceDb.name,
+ target: targetDb.name
+ },
+ {
+ source: sourceDb.name,
+ target: "http://joe:erly@" + host + "/" + targetDb.name
+ },
+ {
+ source: "http://" + host + "/" + sourceDb.name,
+ target: "http://joe:erly@" + host + "/" + targetDb.name
+ }
+ ];
+
+ for (i = 0; i < dbPairs.length; i++) {
+ usersDb.deleteDb();
+ populateDb(sourceDb, docs);
+ populateDb(targetDb, []);
+
+ T(targetDb.setSecObj({
+ admins: {
+ names: ["superman"],
+ roles: ["god"]
+ }
+ }).ok);
+
+ run_on_modified_server(server_config, function() {
+ delete joeUserDoc._rev;
+ T(usersDb.save(joeUserDoc).ok);
+
+ T(CouchDB.login("joe", "erly").ok);
+ T(CouchDB.session().userCtx.name === "joe");
+
+ repResult = CouchDB.new_replicate(dbPairs[i].source, dbPairs[i].target);
+
+ T(CouchDB.logout().ok);
+
+ T(repResult.ok === true);
+ T(repResult.history[0].docs_read === docs.length);
+ T(repResult.history[0].docs_written === (docs.length - 1)); // 1 ddoc
+ T(repResult.history[0].doc_write_failures === 1);
+ });
+
+ for (j = 0; j < docs.length; j++) {
+ doc = docs[j];
+ copy = targetDb.open(doc._id);
+
+ if (doc._id.indexOf("_design/") === 0) {
+ T(copy === null);
+ } else {
+ T(copy !== null);
+ T(compareObjects(doc, copy) === true);
+ }
+ }
+ }
+
+ // case 2) user triggering the replication is not a reader (nor admin) of the
+ // source DB
+ dbPairs = [
+ {
+ source: sourceDb.name,
+ target: targetDb.name
+ },
+ {
+ source: "http://joe:erly@" + host + "/" + sourceDb.name,
+ target: targetDb.name
+ },
+ {
+ source: sourceDb.name,
+ target: "http://" + host + "/" + targetDb.name
+ },
+ {
+ source: "http://joe:erly@" + host + "/" + sourceDb.name,
+ target: "http://" + host + "/" + targetDb.name
+ }
+ ];
+
+ for (i = 0; i < dbPairs.length; i++) {
+ usersDb.deleteDb();
+ populateDb(sourceDb, docs);
+ populateDb(targetDb, []);
+
+ T(sourceDb.setSecObj({
+ admins: {
+ names: ["superman"],
+ roles: ["god"]
+ },
+ readers: {
+ names: ["john"],
+ roles: ["secret"]
+ }
+ }).ok);
+
+ run_on_modified_server(server_config, function() {
+ delete joeUserDoc._rev;
+ T(usersDb.save(joeUserDoc).ok);
+
+ T(CouchDB.login("joe", "erly").ok);
+ T(CouchDB.session().userCtx.name === "joe");
+
+ try {
+ CouchDB.new_replicate(dbPairs[i].source, dbPairs[i].target);
+ T(false, "should have raised an exception");
+ } catch (x) {
+ }
+
+ T(CouchDB.logout().ok);
+ });
+
+ for (j = 0; j < docs.length; j++) {
+ doc = docs[j];
+ copy = targetDb.open(doc._id);
+ T(copy === null);
+ }
+ }
+
+
// cleanup
+ usersDb.deleteDb();
sourceDb.deleteDb();
targetDb.deleteDb();
}
\ No newline at end of file
Modified: couchdb/branches/new_replicator/src/couchdb/couch_api_wrap.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/new_replicator/src/couchdb/couch_api_wrap.erl?rev=988485&r1=988484&r2=988485&view=diff
==============================================================================
--- couchdb/branches/new_replicator/src/couchdb/couch_api_wrap.erl (original)
+++ couchdb/branches/new_replicator/src/couchdb/couch_api_wrap.erl Tue Aug 24 11:21:29 2010
@@ -43,7 +43,8 @@
open_doc/4,
open_doc_revs/6,
update_doc/4,
- changes_since/5
+ changes_since/5,
+ db_uri/1
]).
-import(couch_api_wrap_httpc, [
@@ -58,6 +59,13 @@
]).
+db_uri(#httpdb{url = Url}) ->
+ couch_api_wrap_httpc:strip_creds(Url);
+
+db_uri(#db{name = Name}) ->
+ ?b2l(Name).
+
+
db_open(Db, Options) ->
db_open(Db, Options, false).
@@ -72,6 +80,8 @@ db_open(#httpdb{} = Db, _Options, Create
send_req(Db2, [{method, head}],
fun(200, _, _) ->
{ok, Db2};
+ (401, _, _) ->
+ throw({unauthorized, ?l2b(db_uri(Db))});
(_, _, _) ->
throw({db_not_found, ?l2b(Db2#httpdb.url)})
end);
@@ -87,11 +97,13 @@ db_open(DbName, Options, Create) ->
ok
end
end,
- case couch_db:open(DbName, Options) of
+ case (catch couch_db:open(DbName, Options)) of
{not_found, _Reason} ->
throw({db_not_found, DbName});
{ok, _Db2} = Success ->
- Success
+ Success;
+ {unauthorized, _} ->
+ throw({unauthorized, DbName})
end.
db_close(#httpdb{}) ->
@@ -287,10 +299,17 @@ update_doc(#httpdb{} = HttpDb, #doc{id =
[{method, put}, {path, url_encode(DocId)}, {direct, true},
{qs, QArgs}, {headers, Headers}, {body, {SendFun, Len}}],
fun(Code, _, {Props}) when Code =:= 200 orelse Code =:= 201 ->
- {ok, couch_doc:parse_rev(get_value(<<"rev">>, Props))}
+ {ok, couch_doc:parse_rev(get_value(<<"rev">>, Props))};
+ (401, _, _) ->
+ {error, unauthorized}
end);
update_doc(Db, Doc, Options, Type) ->
- couch_db:update_doc(Db, Doc, Options, Type).
+ try
+ couch_db:update_doc(Db, Doc, Options, Type)
+ catch
+ throw:{unauthorized, _} ->
+ {error, unauthorized}
+ end.
changes_since(#httpdb{} = HttpDb, Style, StartSeq, UserFun, Options) ->
QArgs = changes_q_args(
Modified: couchdb/branches/new_replicator/src/couchdb/couch_api_wrap_httpc.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/new_replicator/src/couchdb/couch_api_wrap_httpc.erl?rev=988485&r1=988484&r2=988485&view=diff
==============================================================================
--- couchdb/branches/new_replicator/src/couchdb/couch_api_wrap_httpc.erl (original)
+++ couchdb/branches/new_replicator/src/couchdb/couch_api_wrap_httpc.erl Tue Aug 24 11:21:29 2010
@@ -17,7 +17,7 @@
-include("../ibrowse/ibrowse.hrl").
-export([httpdb_setup/1]).
--export([send_req/3]).
+-export([send_req/3, strip_creds/1]).
-import(couch_util, [
get_value/2,
Modified: couchdb/branches/new_replicator/src/couchdb/couch_httpd_rep.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/new_replicator/src/couchdb/couch_httpd_rep.erl?rev=988485&r1=988484&r2=988485&view=diff
==============================================================================
--- couchdb/branches/new_replicator/src/couchdb/couch_httpd_rep.erl (original)
+++ couchdb/branches/new_replicator/src/couchdb/couch_httpd_rep.erl Tue Aug 24 11:21:29 2010
@@ -35,7 +35,12 @@ handle_req(#httpd{method='POST'} = Req)
Options = convert_options(PostBody),
try couch_replicate:replicate(SrcDb, TgtDb, Options, Req#httpd.user_ctx) of
{error, Reason} ->
- send_json(Req, 500, {[{error, Reason}]});
+ try
+ send_json(Req, 500, {[{error, Reason}]})
+ catch
+ exit:{json_encode, _} ->
+ send_json(Req, 500, {[{error, couch_util:to_binary(Reason)}]})
+ end;
{ok, {cancelled, RepId}} ->
send_json(Req, 200, {[{ok, true}, {<<"_local_id">>, RepId}]});
{ok, {HistoryResults}} ->
Modified: couchdb/branches/new_replicator/src/couchdb/couch_replicate.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/new_replicator/src/couchdb/couch_replicate.erl?rev=988485&r1=988484&r2=988485&view=diff
==============================================================================
--- couchdb/branches/new_replicator/src/couchdb/couch_replicate.erl (original)
+++ couchdb/branches/new_replicator/src/couchdb/couch_replicate.erl Tue Aug 24 11:21:29 2010
@@ -71,8 +71,14 @@ replicate(Src, Tgt, Options, UserCtx) ->
end_replication(RepId);
false ->
{ok, Listener} = rep_result_listener(RepId),
- {ok, _Pid} = start_replication(RepId, Src, Tgt, Options, UserCtx),
- wait_for_result(RepId, Listener)
+ Result = case start_replication(RepId, Src, Tgt, Options, UserCtx) of
+ {ok, _RepPid} ->
+ wait_for_result(RepId);
+ Error ->
+ Error
+ end,
+ couch_replication_notifier:stop(Listener),
+ Result
end.
@@ -87,15 +93,15 @@ start_replication({BaseId, Extension} =
worker,
[?MODULE]
},
- RepPid = case supervisor:start_child(couch_rep_sup, ChildSpec) of
+ case supervisor:start_child(couch_rep_sup, ChildSpec) of
{ok, Pid} ->
?LOG_INFO("starting new replication ~p at ~p", [RepChildId, Pid]),
- Pid;
+ {ok, Pid};
{error, already_present} ->
case supervisor:restart_child(couch_rep_sup, RepChildId) of
{ok, Pid} ->
?LOG_INFO("starting replication ~p at ~p", [RepChildId, Pid]),
- Pid;
+ {ok, Pid};
{error, running} ->
%% this error occurs if multiple replicators are racing
%% each other to start and somebody else won. Just grab
@@ -104,19 +110,16 @@ start_replication({BaseId, Extension} =
supervisor:start_child(couch_rep_sup, ChildSpec),
?LOG_DEBUG("replication ~p already running at ~p",
[RepChildId, Pid]),
- Pid;
- {error, {db_not_found, DbUrl}} ->
- throw({db_not_found, <<"could not open ", DbUrl/binary>>})
+ {ok, Pid};
+ {error, _} = Err ->
+ Err
end;
{error, {already_started, Pid}} ->
?LOG_DEBUG("replication ~p already running at ~p", [RepChildId, Pid]),
- Pid;
- {error, {{db_not_found, DbUrl}, _}} ->
- throw({db_not_found, <<"could not open ", DbUrl/binary>>});
- {error, Error} ->
- throw({error, Error})
- end,
- {ok, RepPid}.
+ {ok, Pid};
+ {error, {Error, _}} ->
+ {error, Error}
+ end.
rep_result_listener(RepId) ->
@@ -129,21 +132,19 @@ rep_result_listener(RepId) ->
end).
-wait_for_result(RepId, Listener) ->
- wait_for_result(RepId, Listener, ?MAX_RESTARTS).
+wait_for_result(RepId) ->
+ wait_for_result(RepId, ?MAX_RESTARTS).
-wait_for_result(RepId, Listener, RetriesLeft) ->
+wait_for_result(RepId, RetriesLeft) ->
receive
{finished, RepId, RepResult} ->
- couch_replication_notifier:stop(Listener),
{ok, RepResult};
{error, RepId, Reason} ->
case RetriesLeft > 0 of
true ->
- wait_for_result(RepId, Listener, RetriesLeft - 1);
+ wait_for_result(RepId, RetriesLeft - 1);
false ->
- couch_replication_notifier:stop(Listener),
- {error, couch_util:to_binary(Reason)}
+ {error, Reason}
end
end.
@@ -163,8 +164,8 @@ init(InitArgs) ->
try
do_init(InitArgs)
catch
- throw:{db_not_found, DbUrl} ->
- {stop, {db_not_found, DbUrl}}
+ throw:Error ->
+ {stop, Error}
end.
do_init([RepId, Src, Tgt, Options, UserCtx]) ->
@@ -536,8 +537,15 @@ doc_handler({ok, Doc}, Target, Cp) ->
case couch_api_wrap:update_doc(Target, Doc, [], replicated_changes) of
{ok, _} ->
Cp ! {add_stat, {#stats.docs_written, 1}};
- _Error ->
- Cp ! {add_stat, {#stats.doc_write_failures, 1}}
+ Error ->
+ Cp ! {add_stat, {#stats.doc_write_failures, 1}},
+ case Error of
+ {error, unauthorized} ->
+ ?LOG_ERROR("Replicator: unauthorized to write document ~s to ~s",
+ [?b2l(Doc#doc.id), couch_api_wrap:db_uri(Target)]);
+ _ ->
+ ok
+ end
end;
doc_handler(_, _, _) ->
ok.