00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include <config.h>
00025
00026 #include "api_replicate.h"
00027
00028 #include <xapian.h>
00029
00030 #include "apitest.h"
00031 #include "dbcheck.h"
00032 #include "safeerrno.h"
00033 #include "safefcntl.h"
00034 #include "safesysstat.h"
00035 #include "safeunistd.h"
00036 #include "str.h"
00037 #include "testsuite.h"
00038 #include "testutils.h"
00039 #include "utils.h"
00040 #include "unixcmds.h"
00041
00042 #include <sys/types.h>
00043
00044 #include <cstdlib>
00045 #include <string>
00046
00047 #include <stdlib.h>
00048
00049 using namespace std;
00050
00051 static void rmtmpdir(const string & path) {
00052 rm_rf(path);
00053 }
00054
00055 static void mktmpdir(const string & path) {
00056 rmtmpdir(path);
00057 if (mkdir(path, 0700) == -1 && errno != EEXIST) {
00058 FAIL_TEST("Can't make temporary directory");
00059 }
00060 }
00061
00062 static off_t file_size(const string & path) {
00063 struct stat sb;
00064 if (stat(path.c_str(), &sb)) {
00065 FAIL_TEST("Can't stat '" + path + "'");
00066 }
00067 return sb.st_size;
00068 }
00069
00070 static size_t do_read(int fd, char * p, size_t desired)
00071 {
00072 size_t total = 0;
00073 while (desired) {
00074 ssize_t c = read(fd, p, desired);
00075 if (c == 0) return total;
00076 if (c < 0) {
00077 if (errno == EINTR) continue;
00078 FAIL_TEST("Error reading from file");
00079 }
00080 p += c;
00081 total += c;
00082 desired -= c;
00083 }
00084 return total;
00085 }
00086
00087 static void do_write(int fd, const char * p, size_t n)
00088 {
00089 while (n) {
00090 ssize_t c = write(fd, p, n);
00091 if (c < 0) {
00092 if (errno == EINTR) continue;
00093 FAIL_TEST("Error writing to file");
00094 }
00095 p += c;
00096 n -= c;
00097 }
00098 }
00099
00100
00101 static off_t
00102 truncated_copy(const string & srcpath, const string & destpath, off_t tocopy)
00103 {
00104 int fdin = open(srcpath.c_str(), O_RDONLY);
00105 if (fdin == -1) {
00106 FAIL_TEST("Open failed (when opening '" + srcpath + "')");
00107 }
00108
00109 int fdout = open(destpath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
00110 if (fdout == -1) {
00111 FAIL_TEST("Open failed (when creating '" + destpath + "')");
00112 }
00113
00114 #define BUFSIZE 1024
00115 char buf[BUFSIZE];
00116 size_t total_bytes = 0;
00117 while (tocopy > 0) {
00118 size_t thiscopy = tocopy > BUFSIZE ? BUFSIZE : tocopy;
00119 size_t bytes = do_read(fdin, buf, thiscopy);
00120 if (thiscopy != bytes) {
00121 FAIL_TEST("Couldn't read desired number of bytes from changeset");
00122 }
00123 tocopy -= bytes;
00124 total_bytes += bytes;
00125 do_write(fdout, buf, bytes);
00126 }
00127 #undef BUFSIZE
00128
00129 close(fdin);
00130 close(fdout);
00131
00132 return total_bytes;
00133 }
00134
00135
00136
00137 static void
00138 get_changeset(const string & changesetpath,
00139 Xapian::DatabaseMaster & master,
00140 Xapian::DatabaseReplica & replica,
00141 int expected_changesets,
00142 int expected_fullcopies,
00143 bool expected_changed)
00144 {
00145 int fd = open(changesetpath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
00146 if (fd == -1) {
00147 FAIL_TEST("Open failed (when creating a new changeset file at '"
00148 + changesetpath + "')");
00149 }
00150 fdcloser fdc(fd);
00151 Xapian::ReplicationInfo info1;
00152 master.write_changesets_to_fd(fd, replica.get_revision_info(), &info1);
00153
00154 TEST_EQUAL(info1.changeset_count, expected_changesets);
00155 TEST_EQUAL(info1.fullcopy_count, expected_fullcopies);
00156 TEST_EQUAL(info1.changed, expected_changed);
00157 }
00158
00159 static int
00160 apply_changeset(const string & changesetpath,
00161 Xapian::DatabaseReplica & replica,
00162 int expected_changesets,
00163 int expected_fullcopies,
00164 bool expected_changed)
00165 {
00166 int fd = open(changesetpath.c_str(), O_RDONLY);
00167 if (fd == -1) {
00168 FAIL_TEST("Open failed (when reading changeset file at '"
00169 + changesetpath + "')");
00170 }
00171 fdcloser fdc(fd);
00172
00173 int count = 1;
00174 replica.set_read_fd(fd);
00175 Xapian::ReplicationInfo info1;
00176 Xapian::ReplicationInfo info2;
00177 bool client_changed = false;
00178 while (replica.apply_next_changeset(&info2, 0)) {
00179 ++count;
00180 info1.changeset_count += info2.changeset_count;
00181 info1.fullcopy_count += info2.fullcopy_count;
00182 if (info2.changed)
00183 client_changed = true;
00184 }
00185 info1.changeset_count += info2.changeset_count;
00186 info1.fullcopy_count += info2.fullcopy_count;
00187 if (info2.changed)
00188 client_changed = true;
00189
00190 TEST_EQUAL(info1.changeset_count, expected_changesets);
00191 TEST_EQUAL(info1.fullcopy_count, expected_fullcopies);
00192 TEST_EQUAL(client_changed, expected_changed);
00193 return count;
00194 }
00195
00196 static int
00197 replicate(Xapian::DatabaseMaster & master,
00198 Xapian::DatabaseReplica & replica,
00199 const string & tempdir,
00200 int expected_changesets,
00201 int expected_fullcopies,
00202 bool expected_changed)
00203 {
00204 string changesetpath = tempdir + "/changeset";
00205 get_changeset(changesetpath, master, replica,
00206 expected_changesets,
00207 expected_fullcopies,
00208 expected_changed);
00209 return apply_changeset(changesetpath, replica,
00210 expected_changesets,
00211 expected_fullcopies,
00212 expected_changed);
00213 }
00214
00215
00216 static void
00217 check_equal_dbs(const string & masterpath, const string & replicapath)
00218 {
00219 Xapian::Database master(masterpath);
00220 Xapian::Database replica(replicapath);
00221
00222 TEST_EQUAL(master.get_uuid(), master.get_uuid());
00223 dbcheck(replica, master.get_doccount(), master.get_lastdocid());
00224
00225 for (Xapian::TermIterator t = master.allterms_begin();
00226 t != master.allterms_end(); ++t) {
00227 TEST_EQUAL(postlist_to_string(master, *t),
00228 postlist_to_string(replica, *t));
00229 }
00230 }
00231
00232 #if 0 // Dynamic version which we don't currently need.
00233 static void
00234 set_max_changesets(int count) {
00235 #ifdef __WIN32__
00236 _putenv_s("XAPIAN_MAX_CHANGESETS", str(count).c_str());
00237 #elif defined HAVE_SETENV
00238 setenv("XAPIAN_MAX_CHANGESETS", str(count).c_str(), 1);
00239 #else
00240 static char buf[64] = "XAPIAN_MAX_CHANGESETS=";
00241 sprintf(buf + CONST_STRLEN("XAPIAN_MAX_CHANGESETS="), "%d", count);
00242 putenv(buf);
00243 #endif
00244 }
00245 #endif
00246
00247 #ifdef __WIN32__
00248 # define set_max_changesets(N) _putenv_s("XAPIAN_MAX_CHANGESETS", #N)
00249 #elif defined HAVE_SETENV
00250 # define set_max_changesets(N) setenv("XAPIAN_MAX_CHANGESETS", #N, 1)
00251 #else
00252 # define set_max_changesets(N) putenv(const_cast<char*>("XAPIAN_MAX_CHANGESETS="#N))
00253 #endif
00254
00255
00256
00257
00258
00259 DEFINE_TESTCASE(replicate1, replicas) {
00260 string tempdir = ".replicatmp";
00261 mktmpdir(tempdir);
00262 string masterpath = get_named_writable_database_path("master");
00263
00264 set_max_changesets(10);
00265
00266 Xapian::WritableDatabase orig(get_named_writable_database("master"));
00267 Xapian::DatabaseMaster master(masterpath);
00268 string replicapath = tempdir + "/replica";
00269 Xapian::DatabaseReplica replica(replicapath);
00270
00271
00272 Xapian::Document doc1;
00273 doc1.set_data(string("doc1"));
00274 doc1.add_posting("doc", 1);
00275 doc1.add_posting("one", 1);
00276 orig.add_document(doc1);
00277 orig.commit();
00278
00279
00280
00281 int count = replicate(master, replica, tempdir, 0, 1, 1);
00282 TEST_EQUAL(count, 1);
00283 {
00284 Xapian::Database dbcopy(replicapath);
00285 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00286 }
00287
00288
00289
00290 count = replicate(master, replica, tempdir, 0, 0, 0);
00291 TEST_EQUAL(count, 1);
00292 {
00293 Xapian::Database dbcopy(replicapath);
00294 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00295 }
00296
00297
00298
00299 replica.close();
00300 replica = Xapian::DatabaseReplica(replicapath);
00301 count = replicate(master, replica, tempdir, 0, 0, 0);
00302 TEST_EQUAL(count, 1);
00303 {
00304 Xapian::Database dbcopy(replicapath);
00305 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00306 }
00307
00308 orig.add_document(doc1);
00309 orig.commit();
00310 orig.add_document(doc1);
00311 orig.commit();
00312
00313 count = replicate(master, replica, tempdir, 2, 0, 1);
00314 TEST_EQUAL(count, 3);
00315 {
00316 Xapian::Database dbcopy(replicapath);
00317 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00318 }
00319
00320 check_equal_dbs(masterpath, replicapath);
00321
00322
00323
00324 replica.close();
00325 rmtmpdir(tempdir);
00326 return true;
00327 }
00328
00329
00330 DEFINE_TESTCASE(replicate2, replicas) {
00331 SKIP_TEST_FOR_BACKEND("brass");
00332
00333 string tempdir = ".replicatmp";
00334 mktmpdir(tempdir);
00335 string masterpath = get_named_writable_database_path("master");
00336
00337 set_max_changesets(10);
00338
00339 Xapian::WritableDatabase orig(get_named_writable_database("master"));
00340 Xapian::DatabaseMaster master(masterpath);
00341 string replicapath = tempdir + "/replica";
00342 Xapian::DatabaseReplica replica(replicapath);
00343
00344 Xapian::DatabaseMaster master2(replicapath);
00345 string replica2path = tempdir + "/replica2";
00346 Xapian::DatabaseReplica replica2(replica2path);
00347
00348
00349 Xapian::Document doc1;
00350 doc1.set_data(string("doc1"));
00351 doc1.add_posting("doc", 1);
00352 doc1.add_posting("one", 1);
00353 orig.add_document(doc1);
00354 orig.commit();
00355
00356
00357
00358 TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, 1), 1);
00359 check_equal_dbs(masterpath, replicapath);
00360
00361
00362 TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 1, 1), 1);
00363 check_equal_dbs(masterpath, replica2path);
00364
00365 orig.add_document(doc1);
00366 orig.commit();
00367 orig.add_document(doc1);
00368 orig.commit();
00369
00370
00371 TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 0, 0), 1);
00372 check_equal_dbs(replicapath, replica2path);
00373
00374
00375 TEST_EQUAL(replicate(master, replica, tempdir, 2, 0, 1), 3);
00376 check_equal_dbs(masterpath, replicapath);
00377 TEST_EQUAL(replicate(master2, replica2, tempdir, 2, 0, 1), 3);
00378 check_equal_dbs(masterpath, replica2path);
00379
00380
00381 set_max_changesets(0);
00382 orig.close();
00383 orig = get_writable_database_again();
00384 orig.add_document(doc1);
00385 orig.commit();
00386
00387
00388 TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, 1), 1);
00389 check_equal_dbs(masterpath, replicapath);
00390 TEST_EQUAL(replicate(master2, replica2, tempdir, 0, 1, 1), 1);
00391 check_equal_dbs(masterpath, replica2path);
00392
00393
00394
00395 set_max_changesets(1);
00396 orig.close();
00397 orig = get_writable_database_again();
00398 orig.add_document(doc1);
00399 orig.commit();
00400
00401
00402 TEST_EQUAL(replicate(master, replica, tempdir, 1, 0, 1), 2);
00403 check_equal_dbs(masterpath, replicapath);
00404 TEST_EQUAL(replicate(master2, replica2, tempdir, 1, 0, 1), 2);
00405 check_equal_dbs(masterpath, replica2path);
00406
00407
00408 orig.add_document(doc1);
00409 orig.commit();
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422 replica.close();
00423 replica2.close();
00424 rmtmpdir(tempdir);
00425 return true;
00426 }
00427
00428 static void
00429 replicate_with_brokenness(Xapian::DatabaseMaster & master,
00430 Xapian::DatabaseReplica & replica,
00431 const string & tempdir,
00432 int expected_changesets,
00433 int expected_fullcopies,
00434 bool expected_changed)
00435 {
00436 string changesetpath = tempdir + "/changeset";
00437 get_changeset(changesetpath, master, replica,
00438 1, 0, 1);
00439
00440
00441 string brokenchangesetpath = tempdir + "/changeset_broken";
00442 off_t filesize = file_size(changesetpath);
00443 off_t len = 10;
00444 off_t copylen;
00445 while (len < filesize) {
00446 copylen = truncated_copy(changesetpath, brokenchangesetpath, len);
00447 TEST_EQUAL(copylen, len);
00448 tout << "Trying replication with a changeset truncated to " << len <<
00449 " bytes, from " << filesize << " bytes\n";
00450 TEST_EXCEPTION(Xapian::NetworkError,
00451 apply_changeset(brokenchangesetpath, replica,
00452 expected_changesets, expected_fullcopies,
00453 expected_changed));
00454 if (len < 30 || len >= filesize - 10) {
00455
00456 len += 1;
00457 } else {
00458
00459
00460 len += 1000;
00461 if (len >= filesize - 10) {
00462 len = filesize - 10;
00463 }
00464 }
00465 }
00466 return;
00467 }
00468
00469
00470 DEFINE_TESTCASE(replicate3, replicas) {
00471 string tempdir = ".replicatmp";
00472 mktmpdir(tempdir);
00473 string masterpath = get_named_writable_database_path("master");
00474
00475 set_max_changesets(10);
00476
00477 Xapian::WritableDatabase orig(get_named_writable_database("master"));
00478 Xapian::DatabaseMaster master(masterpath);
00479 string replicapath = tempdir + "/replica";
00480 Xapian::DatabaseReplica replica(replicapath);
00481
00482
00483 Xapian::Document doc1;
00484 doc1.set_data(string("doc1"));
00485 doc1.add_posting("doc", 1);
00486 doc1.add_posting("one", 1);
00487 orig.add_document(doc1);
00488 orig.commit();
00489
00490 TEST_EQUAL(replicate(master, replica, tempdir, 0, 1, 1), 1);
00491 check_equal_dbs(masterpath, replicapath);
00492
00493
00494 orig.add_document(doc1);
00495 orig.commit();
00496
00497 replicate_with_brokenness(master, replica, tempdir, 1, 0, 1);
00498
00499
00500
00501 check_equal_dbs(masterpath, replicapath);
00502
00503
00504
00505 orig.add_document(doc1);
00506 orig.commit();
00507 TEST_EQUAL(replicate(master, replica, tempdir, 1, 0, 1), 2);
00508
00509
00510
00511 replica.close();
00512 rmtmpdir(tempdir);
00513 return true;
00514 }
00515
00516
00517 DEFINE_TESTCASE(replicate4, replicas) {
00518 string tempdir = ".replicatmp";
00519 mktmpdir(tempdir);
00520 string masterpath = get_named_writable_database_path("master");
00521
00522 set_max_changesets(1);
00523
00524 Xapian::WritableDatabase orig(get_named_writable_database("master"));
00525 Xapian::DatabaseMaster master(masterpath);
00526 string replicapath = tempdir + "/replica";
00527 Xapian::DatabaseReplica replica(replicapath);
00528
00529
00530 Xapian::Document doc1;
00531 doc1.set_data(string("doc1"));
00532 doc1.add_term("nopos");
00533 orig.add_document(doc1);
00534 orig.commit();
00535
00536
00537
00538 int count = replicate(master, replica, tempdir, 0, 1, 1);
00539 TEST_EQUAL(count, 1);
00540 {
00541 Xapian::Database dbcopy(replicapath);
00542 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00543 }
00544
00545
00546 doc1.add_posting("pos", 1);
00547 orig.add_document(doc1);
00548 orig.commit();
00549
00550
00551 count = replicate(master, replica, tempdir, 1, 0, 1);
00552 TEST_EQUAL(count, 2);
00553 {
00554 Xapian::Database dbcopy(replicapath);
00555 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00556 }
00557 check_equal_dbs(masterpath, replicapath);
00558
00559
00560 Xapian::Document doc2;
00561 doc2.set_data(string("doc2"));
00562 doc2.add_term("nopos");
00563 orig.add_document(doc2);
00564 orig.commit();
00565
00566
00567 count = replicate(master, replica, tempdir, 1, 0, 1);
00568 TEST_EQUAL(count, 2);
00569 {
00570 Xapian::Database dbcopy(replicapath);
00571 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00572 }
00573 check_equal_dbs(masterpath, replicapath);
00574 TEST(!file_exists(masterpath + "/changes1"));
00575
00576
00577 set_max_changesets(0);
00578
00579
00580 Xapian::Document doc3;
00581 doc3.set_data(string("doc3"));
00582 doc3.add_term("nonopos");
00583 orig.add_document(doc3);
00584 orig.commit();
00585
00586
00587 count = replicate(master, replica, tempdir, 0, 1, 1);
00588 TEST_EQUAL(count, 1);
00589 {
00590 Xapian::Database dbcopy(replicapath);
00591 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00592 }
00593
00594 check_equal_dbs(masterpath, replicapath);
00595 TEST(!file_exists(masterpath + "/changes3"));
00596
00597
00598
00599
00600 replica.close();
00601 rmtmpdir(tempdir);
00602 return true;
00603 }
00604
00605
00606
00607 DEFINE_TESTCASE(replicate5, replicas) {
00608 SKIP_TEST_FOR_BACKEND("chert");
00609 SKIP_TEST_FOR_BACKEND("flint");
00610 string tempdir = ".replicatmp";
00611 mktmpdir(tempdir);
00612 string masterpath = get_named_writable_database_path("master");
00613
00614 set_max_changesets(2);
00615
00616 Xapian::WritableDatabase orig(get_named_writable_database("master"));
00617 Xapian::DatabaseMaster master(masterpath);
00618 string replicapath = tempdir + "/replica";
00619 Xapian::DatabaseReplica replica(replicapath);
00620
00621
00622 Xapian::Document doc1;
00623 doc1.set_data(string("doc1"));
00624 doc1.add_term("nopos");
00625 orig.add_document(doc1);
00626 orig.commit();
00627
00628
00629
00630 int count = replicate(master, replica, tempdir, 0, 1, 1);
00631 TEST_EQUAL(count, 1);
00632 {
00633 Xapian::Database dbcopy(replicapath);
00634 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00635 }
00636
00637
00638 doc1.add_posting("pos", 1);
00639 orig.add_document(doc1);
00640 orig.commit();
00641
00642
00643 count = replicate(master, replica, tempdir, 1, 0, 1);
00644 TEST_EQUAL(count, 2);
00645 {
00646 Xapian::Database dbcopy(replicapath);
00647 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00648 }
00649 check_equal_dbs(masterpath, replicapath);
00650
00651
00652 Xapian::Document doc2;
00653 doc2.set_data(string("doc2"));
00654 doc2.add_term("nopos");
00655 orig.add_document(doc2);
00656 orig.commit();
00657
00658
00659 count = replicate(master, replica, tempdir, 1, 0, 1);
00660 TEST_EQUAL(count, 2);
00661 {
00662 Xapian::Database dbcopy(replicapath);
00663 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00664 }
00665 check_equal_dbs(masterpath, replicapath);
00666
00667
00668 Xapian::Document doc3;
00669 doc3.set_data(string("doc3"));
00670 doc3.add_term("nonopos");
00671 orig.add_document(doc3);
00672 orig.commit();
00673
00674
00675 count = replicate(master, replica, tempdir, 1, 0, 1);
00676 TEST_EQUAL(count, 2);
00677 {
00678 Xapian::Database dbcopy(replicapath);
00679 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00680 }
00681 check_equal_dbs(masterpath, replicapath);
00682
00683
00684 TEST(!file_exists(masterpath + "/changes1"));
00685 TEST(file_exists(masterpath + "/changes2"));
00686 TEST(file_exists(masterpath + "/changes3"));
00687
00688 set_max_changesets(3);
00689 masterpath = get_named_writable_database_path("master");
00690
00691
00692 Xapian::Document doc4;
00693 doc4.set_data(string("doc4"));
00694 doc4.add_term("nononopos");
00695 orig.add_document(doc4);
00696 orig.commit();
00697
00698
00699 count = replicate(master, replica, tempdir, 1, 0, 1);
00700 TEST_EQUAL(count, 2);
00701 {
00702 Xapian::Database dbcopy(replicapath);
00703 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00704 }
00705 check_equal_dbs(masterpath, replicapath);
00706
00707
00708 Xapian::Document doc5;
00709 doc5.set_data(string("doc5"));
00710 doc5.add_term("nonononopos");
00711 orig.add_document(doc5);
00712 orig.commit();
00713
00714
00715 count = replicate(master, replica, tempdir, 1, 0, 1);
00716 TEST_EQUAL(count, 2);
00717 {
00718 Xapian::Database dbcopy(replicapath);
00719 TEST_EQUAL(orig.get_uuid(), dbcopy.get_uuid());
00720 }
00721 check_equal_dbs(masterpath, replicapath);
00722
00723 TEST(!file_exists(masterpath + "/changes2"));
00724 TEST(file_exists(masterpath + "/changes3"));
00725 TEST(file_exists(masterpath + "/changes4"));
00726 TEST(file_exists(masterpath + "/changes5"));
00727
00728
00729
00730 replica.close();
00731 rmtmpdir(tempdir);
00732 return true;
00733 }