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.