From 80969e43e5a981641346faddf43e191f90aa54f7 Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Wed, 6 Jul 2022 22:36:52 +0200 Subject: [PATCH 01/18] Loglevel Debug is sufficient as it gets retried (#337) --- src/storage-rbox/rbox-storage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/storage-rbox/rbox-storage.cpp b/src/storage-rbox/rbox-storage.cpp index 83685b5c..8950c230 100644 --- a/src/storage-rbox/rbox-storage.cpp +++ b/src/storage-rbox/rbox-storage.cpp @@ -946,7 +946,7 @@ int rbox_storage_mailbox_delete(struct mailbox *box) { FUNC_START(); int ret = index_storage_mailbox_delete(box); if (ret < 0) { - i_error("while processing index_storage_mailbox_delete: %d", ret); + i_debug("while processing index_storage_mailbox_delete: %d", ret); return ret; } struct rbox_storage *r_storage = (struct rbox_storage *)box->storage; @@ -958,7 +958,7 @@ int rbox_storage_mailbox_delete(struct mailbox *box) { ret = rbox_open_rados_connection(box, false); if (ret < 0) { - i_error("rbox_storage_mailbox_delete: Opening rados connection : %d", ret); + i_debug("rbox_storage_mailbox_delete: Opening rados connection : %d", ret); return ret; } if (r_storage->config->is_user_mapping()) { // From 690127db63fed7617e6c2ff6d0e338af08dcb812 Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Thu, 25 Aug 2022 21:40:35 +0200 Subject: [PATCH 02/18] Bugfix/339 rados config timeout (#340) * 339 retry 10 times with random wait. retry read for simple read e.g. rados_config. fail with assert in case we can't find rados_config --- CHANGELOG.md | 4 ++++ configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/librmb/rados-ceph-config.cpp | 18 +++++++++++++++- src/librmb/rados-metadata-storage-ima.cpp | 16 +++++++++++++- src/storage-rbox/rbox-copy.cpp | 1 + src/storage-rbox/rbox-mail.cpp | 26 ++++++++--------------- src/storage-rbox/rbox-storage.cpp | 4 +++- src/storage-rbox/rbox-sync.cpp | 3 +++ 9 files changed, 54 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0169b21..71f992ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [0.0.39](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.39) (2022-08-25) +- #339 fail with assert if rados_config cannot be found due to network/connection issue + retry ceph read operations / read / xattr with timeout + ## [0.0.38](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.38) (2022-06-24) - Fix losing \r when saving mail from \n source diff --git a/configure.ac b/configure.ac index 0a95c5c3..f09b4af8 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.38], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.39], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index 77e97955..e10c1939 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -15,7 +15,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.38 +Version: 0.0.39 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/librmb/rados-ceph-config.cpp b/src/librmb/rados-ceph-config.cpp index dc9612b1..dae707a7 100644 --- a/src/librmb/rados-ceph-config.cpp +++ b/src/librmb/rados-ceph-config.cpp @@ -12,6 +12,7 @@ #include "rados-ceph-config.h" #include #include +#include namespace librmb { @@ -111,7 +112,22 @@ int RadosCephConfig::read_object(const std::string &oid, librados::bufferlist *b if (io_ctx == nullptr) { return -1; } - return io_ctx->read(oid, *buffer, max, 0); + // retry max times to read the object. + int max_retry = 10; + int ret_read = -1; + + for(int i = 0;iread(oid, *buffer, max, 0); + if(ret_read >= 0 || ret_read == -ENOENT ){ + // exit here if the file does not exist, or we were successful + break; + } + buffer->clear(); + // wait random time before try again!! + usleep(((rand() % 5) + 1) * 10000); + } + + return ret_read; } void RadosCephConfig::set_io_ctx_namespace(const std::string &namespace_) { diff --git a/src/librmb/rados-metadata-storage-ima.cpp b/src/librmb/rados-metadata-storage-ima.cpp index 011cdda3..7aa98c8c 100644 --- a/src/librmb/rados-metadata-storage-ima.cpp +++ b/src/librmb/rados-metadata-storage-ima.cpp @@ -13,6 +13,7 @@ #include "rados-util.h" #include #include +#include std::string librmb::RadosMetadataStorageIma::module_name = "ima"; std::string librmb::RadosMetadataStorageIma::keyword_key = "K"; @@ -65,7 +66,18 @@ int RadosMetadataStorageIma::load_metadata(RadosMail *mail) { } std::map attr; - int ret = io_ctx->getxattrs(*mail->get_oid(), attr); + // retry mechanism .. + int max_retry = 10; + int ret = -1; + for(int i=0;igetxattrs(*mail->get_oid(), attr); + if(ret >= 0){ + break; + } + // wait random time before try again!! + usleep(((rand() % 5) + 1) * 10000); + } + if (ret < 0) { return ret; } @@ -180,6 +192,8 @@ bool RadosMetadataStorageIma::update_metadata(const std::string &oid, std::list< // write update save_metadata(&write_op, &obj); librados::AioCompletion *completion = librados::Rados::aio_create_completion(); + + //TODO: do we need a retry mechanism here? int ret = io_ctx->aio_operate(oid, completion, &write_op); completion->wait_for_complete(); completion->release(); diff --git a/src/storage-rbox/rbox-copy.cpp b/src/storage-rbox/rbox-copy.cpp index 1e20ead3..ccce7f4f 100644 --- a/src/storage-rbox/rbox-copy.cpp +++ b/src/storage-rbox/rbox-copy.cpp @@ -307,6 +307,7 @@ int rbox_mail_storage_copy(struct mail_save_context *ctx, struct mail *mail) { if (rbox_open_rados_connection(dest_mbox, alt_storage) < 0) { FUNC_END_RET("ret == -1, connection to rados failed"); + i_error("ERROR, cannot open rados connection (rbox_mail_storage_copy)"); return -1; } diff --git a/src/storage-rbox/rbox-mail.cpp b/src/storage-rbox/rbox-mail.cpp index 4693f729..274c2592 100644 --- a/src/storage-rbox/rbox-mail.cpp +++ b/src/storage-rbox/rbox-mail.cpp @@ -20,6 +20,7 @@ #include #include #include +#include extern "C" { @@ -158,21 +159,6 @@ static int rbox_mail_metadata_get(struct rbox_mail *rmail, enum rbox_metadata_ke //i_debug("Errorcode: process %d returned with %d cannot get x_attr(%s,%c) from rados_object: %s",getpid(), ret_load_metadata, // metadata_key.c_str(), key, rmail->rados_mail != NULL ? rmail->rados_mail->to_string(" ").c_str() : " no rados_mail"); rbox_mail_set_expunged(rmail); - } else if(ret_load_metadata == -ETIMEDOUT) { - int max_retry = 10; - for(int i=0;ims->get_storage()->load_metadata(rmail->rados_mail); - if(ret_load_metadata>=0){ - i_error("READ TIMEOUT %d reading mail object %s ", ret_load_metadata,rmail->rados_mail != NULL ? rmail->rados_mail->to_string(" ").c_str() : " no rados_mail"); - break; - } - i_warning("READ TIMEOUT retry(%d) %d reading mail object %s ",i, ret_load_metadata,rmail->rados_mail != NULL ? rmail->rados_mail->to_string(" ").c_str() : " no rados_mail"); - } - if(ret_load_metadata<0){ - FUNC_END(); - return -1; - } - } else { i_error("Errorcode: process %d returned with %d cannot get x_attr(%s,%c) from rados_object: %s",getpid(), ret_load_metadata, @@ -300,6 +286,7 @@ static int rbox_mail_get_save_date(struct mail *_mail, time_t *date_r) { if (rbox_open_rados_connection(_mail->box, alt_storage) < 0) { FUNC_END_RET("ret == -1; connection to rados failed"); + i_error("ERROR, cannot open rados connection (rbox_mail_get_save_date)"); return -1; } @@ -465,6 +452,7 @@ static int read_mail_from_storage(librmb::RadosStorage *rados_storage, read_mail->read(0, INT_MAX, rmail->rados_mail->get_mail_buffer(), &read_err); read_mail->stat(psize, save_date, &stat_err); + //TODO: refactore to use operate instead of aio_operate. librados::AioCompletion *completion = librados::Rados::aio_create_completion(); int ret = rados_storage->get_io_ctx().aio_operate(*rmail->rados_mail->get_oid(), completion, read_mail, rmail->rados_mail->get_mail_buffer()); @@ -489,6 +477,7 @@ static int rbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, s if (data->stream == NULL) { if (rbox_open_rados_connection(_mail->box, alt_storage) < 0) { FUNC_END_RET("ret == -1; connection to rados failed"); + i_error("ERROR, cannot open rados connection (rbox_mail_get_stream)"); return -1; } @@ -552,6 +541,8 @@ static int rbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, s break; } i_warning("READ TIMEOUT retry(%d) %d reading mail object %s ",i, ret,rmail->rados_mail != NULL ? rmail->rados_mail->to_string(" ").c_str() : " no rados_mail"); + // wait random time before try again!! + usleep(((rand() % 5) + 1) * 10000); } if(ret <0){ @@ -652,8 +643,9 @@ static int rbox_get_cached_metadata(struct rbox_mail *mail, enum rbox_metadata_k unsigned int order = 0; string_t *str = str_new(imail->mail.data_pool, 64); - if (mail_cache_lookup_field(imail->mail.mail.transaction->cache_view, str, imail->mail.mail.seq, - ibox->cache_fields[cache_field].idx) > 0) { + if (mail_cache_lookup_field(imail->mail.mail.transaction->cache_view, + str, imail->mail.mail.seq, + ibox->cache_fields[cache_field].idx) > 0) { if (cache_field == MAIL_CACHE_POP3_ORDER) { i_assert(str_len(str) == sizeof(order)); memcpy(&order, str_data(str), sizeof(order)); diff --git a/src/storage-rbox/rbox-storage.cpp b/src/storage-rbox/rbox-storage.cpp index 8950c230..3a0821d0 100644 --- a/src/storage-rbox/rbox-storage.cpp +++ b/src/storage-rbox/rbox-storage.cpp @@ -499,7 +499,9 @@ int rbox_open_rados_connection(struct mailbox *box, bool alt_storage) { ret = rbox->storage->config->save_default_rados_config(); } if (ret < 0) { - i_error("unable to read rados_config return value : %d", ret); + // connection seems to be up, but read to object store is not okay. We can only fail hard! + i_error("unrecoverable, we cannot proceed without rados_config ceph returned : %d", ret); + assert(ret == 0); return ret; } rbox->storage->ms->create_metadata_storage(&rbox->storage->s->get_io_ctx(), rbox->storage->config); diff --git a/src/storage-rbox/rbox-sync.cpp b/src/storage-rbox/rbox-sync.cpp index 759edb40..1a4ab209 100644 --- a/src/storage-rbox/rbox-sync.cpp +++ b/src/storage-rbox/rbox-sync.cpp @@ -12,6 +12,7 @@ #include #include #include +#include extern "C" { #include "dovecot-all.h" @@ -474,6 +475,8 @@ static int rbox_sync_object_expunge(struct rbox_sync_context *ctx, struct expung break; } i_warning("rbox_sync (retry %d) deletion failed with %d during oid (%s) deletion, mail stays in object store.",i, ret_remove, oid); + // wait random time before try again!! + usleep(((rand() % 5) + 1) * 10000); } } From dde2fb3afaf64c2f62de65380ae79ee480a82e7e Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Thu, 22 Sep 2022 13:33:49 +0200 Subject: [PATCH 03/18] Feature/342 object search (#343) * Develop (#331) * #328: fix copy move (#330) * Develop (#334) * 217: fix unused code. * 217: unused code and warning * disabled man pages for rmb * Bugfix/283 virtual mailbox fetch metadata (#284) * Develop (#282) * 217: fix unused code. * 217: unused code and warning * disabled man pages for rmb * #283: fetch metadata for mails in virtual mailbox * #283: fix metadata date.saved. date.received virtual mailbox * #283: version * Feature/286 use guid from UUID string (#287) * #256: use guid_128_from_uuid_string instead of guid_128_from_string, to support older uuid formats like RECORD or MICROSOFT, always use compact for printing * version 0.0.25 preparations * #286: build issue * merge * version * #286: prefere cached mail guid. * #286: determine if uuid has - hyphon, if true preseve it. * fix non void return * Feature/289 GitHub actions (#290) * #289: build * #289: build plugin. * #289: submodules * submodules * upgrade git * clean outdated repos * Bugfix/UUID record format (#293) * bugfix initialisaction rados_mail->deprecated_uid * rados_mail creation and default value check * #295: update index after full object has been moved (metadata) (#296) * Bugfix/298 295 bugfixes (#300) * #289: fix force-resync (always use INBOX) #295: rbox_mail_set_expunge (play save) * #298: move ceph objects the standard way. * clean up log. * #298: log warning if client connection times out. * Feature/302 retry expunge connection timeout (#303) * #302: retry in case of ceph connection timeout * rpm * #302: max_retry = 10 * max 10 retry, in case object not available, fail with error * #304: use last know uid to restore email flags (#305) * #306: re-assign unreferenced mail objects to inbox (#309) * #310: read osd_max_object_size and fail if mail.size > osd_max_object_size (#311) * version: 0.0.31 * #313: fix imap append crash (#314) * version 0.0.32 * Bugfix/316 mailbox save (#317) * #316: imap append (remove index entry twice in case of error) * #316: changed storage for big attachments. (sync write) * #316: operate api * #316: fix operate call * #316: release preparations * #316: fixed some legacy unit tests * 316: review results * #316: fix * Bugfix/test output stream check (#323) * init output stream and free mailbuffer * build * extracted write chunks * mailsize -1; * fix * debug messages * init stream any case * added deprecation comment, writing chunks now default. new config param rbox_chunk_size default 10240 bytes * missing mock class * version * disabled (temporarily storage tests) * disabled storage tests( api change) * Feature/alternative save methods (#326) * #322: alternative save methods * version * Feature/319 force resync immediatelly assign bugfix/328 segmentation fault copy from virtual mailbox (#321) * #319: automatically add lost objects to inbox (if no mailbox guid exist) * #319: version 0.0.34 * catch error in case mailbox does not have obox header: e.v. Virtual Mailbox * check for virtual mailbox when moving or copying, (virtual mailbox not allowed as origin) * version * #328: fix copy move (#330) * Bugfix/332 quota and move (#333) * 291: failed save mock test * 291: fixed most of the tests * 332 fix notify messages number and type * Develop (#336) * 217: fix unused code. * 217: unused code and warning * disabled man pages for rmb * Bugfix/283 virtual mailbox fetch metadata (#284) * Develop (#282) * 217: fix unused code. * 217: unused code and warning * disabled man pages for rmb * #283: fetch metadata for mails in virtual mailbox * #283: fix metadata date.saved. date.received virtual mailbox * #283: version * Feature/286 use guid from UUID string (#287) * #256: use guid_128_from_uuid_string instead of guid_128_from_string, to support older uuid formats like RECORD or MICROSOFT, always use compact for printing * version 0.0.25 preparations * #286: build issue * merge * version * #286: prefere cached mail guid. * #286: determine if uuid has - hyphon, if true preseve it. * fix non void return * Feature/289 GitHub actions (#290) * #289: build * #289: build plugin. * #289: submodules * submodules * upgrade git * clean outdated repos * Bugfix/UUID record format (#293) * bugfix initialisaction rados_mail->deprecated_uid * rados_mail creation and default value check * #295: update index after full object has been moved (metadata) (#296) * Bugfix/298 295 bugfixes (#300) * #289: fix force-resync (always use INBOX) #295: rbox_mail_set_expunge (play save) * #298: move ceph objects the standard way. * clean up log. * #298: log warning if client connection times out. * Feature/302 retry expunge connection timeout (#303) * #302: retry in case of ceph connection timeout * rpm * #302: max_retry = 10 * max 10 retry, in case object not available, fail with error * #304: use last know uid to restore email flags (#305) * #306: re-assign unreferenced mail objects to inbox (#309) * #310: read osd_max_object_size and fail if mail.size > osd_max_object_size (#311) * version: 0.0.31 * #313: fix imap append crash (#314) * version 0.0.32 * Bugfix/316 mailbox save (#317) * #316: imap append (remove index entry twice in case of error) * #316: changed storage for big attachments. (sync write) * #316: operate api * #316: fix operate call * #316: release preparations * #316: fixed some legacy unit tests * 316: review results * #316: fix * Bugfix/test output stream check (#323) * init output stream and free mailbuffer * build * extracted write chunks * mailsize -1; * fix * debug messages * init stream any case * added deprecation comment, writing chunks now default. new config param rbox_chunk_size default 10240 bytes * missing mock class * version * disabled (temporarily storage tests) * disabled storage tests( api change) * Feature/alternative save methods (#326) * #322: alternative save methods * version * Feature/319 force resync immediatelly assign bugfix/328 segmentation fault copy from virtual mailbox (#321) * #319: automatically add lost objects to inbox (if no mailbox guid exist) * #319: version 0.0.34 * catch error in case mailbox does not have obox header: e.v. Virtual Mailbox * check for virtual mailbox when moving or copying, (virtual mailbox not allowed as origin) * version * #328: fix copy move (#330) * Bugfix/332 quota and move (#333) * 291: failed save mock test * 291: fixed most of the tests * 332 fix notify messages number and type * #332: fix append additional error mssage in case connection can't be opend (#335) * Develop (#341) * Loglevel Debug is sufficient as it gets retried (#337) * Bugfix/339 rados config timeout (#340) * 339 retry 10 times with random wait. retry read for simple read e.g. rados_config. fail with assert in case we can't find rados_config * #342: simple multithreading object search * #342: version 0.0.40 Co-authored-by: Ewald Dieterich --- CHANGELOG.md | 8 + configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/librmb/rados-ceph-config.h | 2 +- src/librmb/rados-cluster-impl.cpp | 53 ++++- src/librmb/rados-cluster-impl.h | 3 + src/librmb/rados-cluster.h | 7 + src/librmb/rados-dovecot-ceph-cfg-impl.h | 2 + src/librmb/rados-dovecot-ceph-cfg.h | 3 + src/librmb/rados-dovecot-config.cpp | 13 +- src/librmb/rados-dovecot-config.h | 6 + src/librmb/rados-storage-impl.cpp | 58 ++++- src/librmb/rados-storage-impl.h | 6 + src/librmb/rados-storage.h | 6 + src/librmb/rados-util.cpp | 72 ++++++ src/librmb/rados-util.h | 16 ++ src/storage-rbox/rbox-sync-rebuild.cpp | 47 +++- src/storage-rbox/rbox-sync-rebuild.h | 5 +- src/tests/Makefile.am | 6 + src/tests/mocks/mock_test.h | 9 + .../storage-mock-rbox/test_repair_rbox.cpp | 214 ++++++++++++++++++ 21 files changed, 517 insertions(+), 23 deletions(-) create mode 100644 src/tests/storage-mock-rbox/test_repair_rbox.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f992ad..6c52d7ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [0.0.40](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.39) (2022-09-22) +- #342 multithreading object search for doveadm force-resync (feature toggle) + new config params: + # search method default = 0 | 1 multithreading + rbox_object_search_method=1 + # number of threads to use in case of search_method=1 + rbox_object_search_threads=4 + ## [0.0.39](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.39) (2022-08-25) - #339 fail with assert if rados_config cannot be found due to network/connection issue retry ceph read operations / read / xattr with timeout diff --git a/configure.ac b/configure.ac index f09b4af8..b8fb59ef 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.39], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.40], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index e10c1939..aa5f1b3b 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -15,7 +15,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.39 +Version: 0.0.40 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/librmb/rados-ceph-config.h b/src/librmb/rados-ceph-config.h index a1a55959..e9cbccde 100644 --- a/src/librmb/rados-ceph-config.h +++ b/src/librmb/rados-ceph-config.h @@ -58,7 +58,7 @@ class RadosCephConfig { void set_update_attributes(const std::string &update_attributes_) { config.set_update_attributes(update_attributes_); } - + void update_mail_attribute(const char *value) { config.update_mail_attribute(value); } void update_updateable_attribute(const char *value) { config.update_updateable_attribute(value); } diff --git a/src/librmb/rados-cluster-impl.cpp b/src/librmb/rados-cluster-impl.cpp index 1a68bfb7..00820073 100644 --- a/src/librmb/rados-cluster-impl.cpp +++ b/src/librmb/rados-cluster-impl.cpp @@ -17,7 +17,7 @@ #include "rados-dictionary-impl.h" #include "rados-storage-impl.h" - +#include "rados-util.h" using std::list; using std::pair; using std::string; @@ -72,6 +72,57 @@ int RadosClusterImpl::init(const std::string &clustername, const std::string &ra return ret; } + +std::vector RadosClusterImpl::list_pgs_for_pool(std::string &pool_name) { + std::cout << " ola " << RadosClusterImpl::cluster << std::endl; + + if(is_connected()){ + std::cout << " is connected YES" << std::endl; + }else{ + std::cout << " is connected NO" << std::endl; + connect(); + } + + const string pool = "mail_storage"; + const string cmd = + "{" + "\"prefix\": \"pg ls-by-pool\", " + "\"poolstr\": \"" + pool + "\"" + "}"; + + std::cout << "cmd: " << cmd << std::endl; + + librados::bufferlist inbl; + librados::bufferlist outbl; + int res = RadosClusterImpl::cluster->mon_command(cmd, inbl, &outbl, nullptr); + std::cout << "inbl command " << inbl < list = RadosUtils::extractPgs(std::string(outbl.c_str())); + + for (auto const &token: list) { + std::cout << token << std::endl; + } + return list; +} + +std::map> RadosClusterImpl::list_pgs_osd_for_pool(std::string &pool_name) { + + if(!is_connected()){ + connect(); + } + + const string cmd = + "{" + "\"prefix\": \"pg ls-by-pool\", " + "\"poolstr\": \"" + pool_name + "\"" + "}"; + + librados::bufferlist inbl; + librados::bufferlist outbl; + RadosClusterImpl::cluster->mon_command(cmd, inbl, &outbl, nullptr); + return RadosUtils::extractPgAndPrimaryOsd(std::string(outbl.c_str())); +} int RadosClusterImpl::initialize() { int ret = 0; diff --git a/src/librmb/rados-cluster-impl.h b/src/librmb/rados-cluster-impl.h index ca9678f3..8786e023 100644 --- a/src/librmb/rados-cluster-impl.h +++ b/src/librmb/rados-cluster-impl.h @@ -40,6 +40,9 @@ class RadosClusterImpl : public RadosCluster { librados::Rados &get_cluster() { return *cluster; } void set_config_option(const char *option, const char *value); + std::vector list_pgs_for_pool(std::string &pool_name) override; + std::map> list_pgs_osd_for_pool(std::string &pool_name) override; + private: int initialize(); diff --git a/src/librmb/rados-cluster.h b/src/librmb/rados-cluster.h index dac4c59a..60dbb79a 100644 --- a/src/librmb/rados-cluster.h +++ b/src/librmb/rados-cluster.h @@ -76,6 +76,13 @@ class RadosCluster { * @return true if connected */ virtual bool is_connected() = 0; + + /*! get placement groups for mailbox storage pool + */ + virtual std::vector list_pgs_for_pool(std::string &pool_name) = 0; + virtual std::map> list_pgs_osd_for_pool(std::string &pool_name) = 0; + + }; } // namespace librmb diff --git a/src/librmb/rados-dovecot-ceph-cfg-impl.h b/src/librmb/rados-dovecot-ceph-cfg-impl.h index 811bc197..25a3e279 100644 --- a/src/librmb/rados-dovecot-ceph-cfg-impl.h +++ b/src/librmb/rados-dovecot-ceph-cfg-impl.h @@ -58,6 +58,8 @@ class RadosDovecotCephCfgImpl : public RadosDovecotCephCfg { rados_cfg.set_cfg_object_name(dovecot_cfg.get_rbox_cfg_object_name()); } } + int get_object_search_method() override { return std::stoi(dovecot_cfg.get_object_search_method()); } + int get_object_search_threads() override { return std::stoi(dovecot_cfg.get_object_search_threads()); } void set_rbox_cfg_object_name(const std::string &value) override { dovecot_cfg.set_rbox_cfg_object_name(value); } diff --git a/src/librmb/rados-dovecot-ceph-cfg.h b/src/librmb/rados-dovecot-ceph-cfg.h index b7dd7a08..c73bf0c4 100644 --- a/src/librmb/rados-dovecot-ceph-cfg.h +++ b/src/librmb/rados-dovecot-ceph-cfg.h @@ -44,6 +44,9 @@ class RadosDovecotCephCfg { virtual int get_chunk_size() = 0; virtual int get_write_method() = 0; + virtual int get_object_search_method() = 0; + virtual int get_object_search_threads() = 0; + virtual const std::string &get_pool_name_metadata_key() = 0; virtual const std::string &get_update_attributes_key() = 0; virtual const std::string &get_mail_attributes_key() = 0; diff --git a/src/librmb/rados-dovecot-config.cpp b/src/librmb/rados-dovecot-config.cpp index 5233db4b..874df8eb 100644 --- a/src/librmb/rados-dovecot-config.cpp +++ b/src/librmb/rados-dovecot-config.cpp @@ -29,7 +29,9 @@ RadosConfig::RadosConfig() rbox_ceph_aio_wait_for_safe_and_cb("rbox_ceph_aio_wait_for_safe_and_cb"), rbox_ceph_write_chunks("rbox_ceph_write_chunks"), rbox_chunk_size("rbox_chunk_size"), - rbox_write_method("rbox_write_method") { + rbox_write_method("rbox_write_method"), + rbox_object_search_method("rbox_object_search_method"), + rbox_object_search_threads("rbox_object_search_threads") { config[pool_name] = "mail_storage"; @@ -43,6 +45,9 @@ RadosConfig::RadosConfig() config[rbox_ceph_write_chunks] = "false"; config[rbox_chunk_size] = "10240"; config[rbox_write_method] = "0"; + config[rbox_object_search_method] = "0"; + config[rbox_object_search_threads] = "4"; + is_valid = false; } @@ -79,8 +84,10 @@ std::string RadosConfig::to_string() { ss << " " << rbox_ceph_aio_wait_for_safe_and_cb << "=" << config[rbox_ceph_aio_wait_for_safe_and_cb] << std::endl; ss << " " << rbox_ceph_write_chunks << "=" << config[rbox_ceph_write_chunks] << std::endl; ss << " " << rbox_write_method << "=" << config[rbox_write_method] << std::endl; - ss << " " << rbox_chunk_size << "=" << config[rbox_chunk_size] - << std::endl; + ss << " " << rbox_chunk_size << "=" << config[rbox_chunk_size] << std::endl; + ss << " " << rbox_object_search_method << "=" << config[rbox_object_search_method] << std::endl; + ss << " " << rbox_object_search_threads << "=" << config[rbox_object_search_threads] << std::endl; + return ss.str(); } diff --git a/src/librmb/rados-dovecot-config.h b/src/librmb/rados-dovecot-config.h index c469d2e8..89900a5e 100644 --- a/src/librmb/rados-dovecot-config.h +++ b/src/librmb/rados-dovecot-config.h @@ -49,6 +49,10 @@ class RadosConfig { const std::string &get_rbox_cluster_name() { return config[rbox_cluster_name]; } const std::string &get_rados_username() { return config[rados_username]; } + + const std::string &get_object_search_method() { return config[rbox_object_search_method]; } + const std::string &get_object_search_threads() { return config[rbox_object_search_threads]; } + void update_metadata(const std::string &key, const char *value_); bool is_ceph_posix_bugfix_enabled() { return config[bugfix_cephfs_posix_hardlinks].compare("true") == 0 ? true : false; @@ -87,6 +91,8 @@ class RadosConfig { std::string rbox_ceph_write_chunks; std::string rbox_chunk_size; std::string rbox_write_method; + std::string rbox_object_search_method; + std::string rbox_object_search_threads; bool is_valid; }; diff --git a/src/librmb/rados-storage-impl.cpp b/src/librmb/rados-storage-impl.cpp index 1dc9b719..5525bf36 100644 --- a/src/librmb/rados-storage-impl.cpp +++ b/src/librmb/rados-storage-impl.cpp @@ -16,8 +16,13 @@ #include #include #include +#include +#include + +#include "rados-util.h" #include + #include "encoding.h" #include "limits.h" @@ -173,7 +178,6 @@ librados::NObjectIterator RadosStorageImpl::find_mails(const RadosMetadata *attr } if (attr != nullptr) { - // int hashpos = get_io_ctx().get_object_hash_position("t1_u"); std::string filter_name = PLAIN_FILTER_NAME; ceph::bufferlist filter_bl; @@ -186,7 +190,57 @@ librados::NObjectIterator RadosStorageImpl::find_mails(const RadosMetadata *attr return get_io_ctx().nobjects_begin(); } } - +/** + * POC Implementation: + * + * see in prod how it behaves. + * + **/ +std::set RadosStorageImpl::find_mails_async(const RadosMetadata *attr, + std::string &pool_name, + int num_threads){ + + std::set oid_list; + std::mutex oid_list_mutex; + + // Define a Lambda Expression + auto f = [](const std::vector &list, std::mutex &oid_mutex, std::set &oids, librados::IoCtx *io_ctx) { + + std::lock_guard guard(oid_mutex); + for (auto const &pg: list) { + uint64_t ppool; + uint32_t pseed; + int r = sscanf(pg.c_str(), "%llu.%x", (long long unsigned *)&ppool, &pseed); + + librados::NObjectIterator iter= io_ctx->nobjects_begin(pseed); + + while (iter != librados::NObjectIterator::__EndObjectIterator) { + std::string oid = iter->get_oid(); + oids.insert(oid); + iter++; + } + } + }; + + //std::string pool_mame = "mail_storage"; + std::map> osd_pg_map = cluster->list_pgs_osd_for_pool(pool_name); + std::vector threads; + + for (const auto& x : osd_pg_map) + { + if(threads.size() == num_threads){ + for (auto const &thread: threads) { + thread.join(); + } + threads.clear(); + } + threads.push_back(std::thread(f, std::ref(x.second),std::ref(oid_list_mutex),std::ref(oid_list), &get_io_ctx())); + } + for (auto const &thread: threads) { + thread.join(); + } + return oid_list; +} librados::IoCtx &RadosStorageImpl::get_io_ctx() { return io_ctx; } int RadosStorageImpl::open_connection(const std::string &poolname, const std::string &clustername, diff --git a/src/librmb/rados-storage-impl.h b/src/librmb/rados-storage-impl.h index 3712f217..8984524f 100644 --- a/src/librmb/rados-storage-impl.h +++ b/src/librmb/rados-storage-impl.h @@ -18,6 +18,8 @@ #include #include #include +#include + #include #include @@ -50,6 +52,9 @@ class RadosStorageImpl : public RadosStorage { int aio_operate(librados::IoCtx *io_ctx_, const std::string &oid, librados::AioCompletion *c, librados::ObjectWriteOperation *op) override; librados::NObjectIterator find_mails(const RadosMetadata *attr) override; + + std::set find_mails_async(const RadosMetadata *attr, std::string &pool_name, int num_threads) override; + int open_connection(const std::string &poolname) override; int open_connection(const std::string &poolname, const std::string &clustername, const std::string &rados_username) override; @@ -72,6 +77,7 @@ class RadosStorageImpl : public RadosStorage { void free_rados_mail(librmb::RadosMail *mail) override; + private: int create_connection(const std::string &poolname); diff --git a/src/librmb/rados-storage.h b/src/librmb/rados-storage.h index e723e772..f5234026 100644 --- a/src/librmb/rados-storage.h +++ b/src/librmb/rados-storage.h @@ -104,6 +104,11 @@ class RadosStorage { * * @return object iterator or librados::NObjectIterator::__EndObjectIterator */ virtual librados::NObjectIterator find_mails(const RadosMetadata *attr) = 0; + + + virtual std::set find_mails_async(const RadosMetadata *attr, std::string &pool_name, int num_threads) = 0; + + /*! open the rados connections with default cluster and username * @param[in] poolname the poolname to connect to, in case this one does not exists, it will be created. * */ @@ -199,6 +204,7 @@ class RadosStorage { * */ virtual void free_rados_mail(librmb::RadosMail *mail) = 0; + }; } // namespace librmb diff --git a/src/librmb/rados-util.cpp b/src/librmb/rados-util.cpp index b2df83d0..be637ab6 100644 --- a/src/librmb/rados-util.cpp +++ b/src/librmb/rados-util.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include "encoding.h" namespace librmb { @@ -292,4 +294,74 @@ int RadosUtils::copy_to_alt(std::string &src_oid, std::string &dest_oid, RadosSt return success ? 0 : 1; } +static std::vector RadosUtils::extractPgs(const std::string& str) +{ + std::vector tokens; + + std::stringstream ss(str); + std::string token; + while (std::getline(ss, token, '\n')) { + std::string pgs = token.substr(0, 10); //take first 10 chars for pgids + // trim the result. + pgs.erase(std::remove_if(pgs.begin(), pgs.end(), ::isspace), pgs.end()); + tokens.push_back(pgs); + } + //skip first line (header) + tokens.erase(tokens.begin()); + //remove last element (footer) + tokens.pop_back(); + + return tokens; +} + +static std::map> RadosUtils::extractPgAndPrimaryOsd(const std::string& str) +{ + std::map> tokens; + + std::stringstream ss(str); + std::string token; + bool first_line = true; + while (std::getline(ss, token, '\n')) { + if(first_line){ + first_line = false; + continue; + } + std::vector line = split(token,' '); + if(line.size() < 14){ + continue; + } + std::string tmp_primary_osd = split(line[13],',')[0]; + std::string primary_osd = tmp_primary_osd.erase(0,1); + std::string pgs = line[0]; + + auto it = tokens.find(primary_osd); + if(it!=tokens.end()){ + tokens[primary_osd].push_back(pgs); + }else{ + std::vector t; + t.push_back(pgs); + tokens.insert({primary_osd,t}); + } + + } + + return tokens; +} + +static std::vector RadosUtils::split(std::string str_to_split, char delimiter) { + std::vector tokens; + std::stringstream stream(str_to_split); + std::string token; + + while(getline(stream, token, delimiter)) { + token.erase(std::remove_if(token.begin(), token.end(), ::isspace), token.end()); + if(token.length() > 0) { + tokens.push_back(token); + } + + } + + return tokens; +} + } // namespace librmb diff --git a/src/librmb/rados-util.h b/src/librmb/rados-util.h index 566d7c26..e6b019dc 100644 --- a/src/librmb/rados-util.h +++ b/src/librmb/rados-util.h @@ -16,6 +16,11 @@ #include #include +#include +#include +#include +#include + #include #include #include @@ -180,6 +185,17 @@ class RadosUtils { * @return the metadata value */ static void get_metadata(rbox_metadata_key key, std::map *metadata, char **value); + + + /** + * POC Implemnentation to extract pgs and primary osds from mon_command output! + **/ + static std::vector extractPgs(const std::string& str); + + static std::map> extractPgAndPrimaryOsd(const std::string& str); + + static std::vector split(std::string str_to_split, char delimiter); + }; } // namespace librmb diff --git a/src/storage-rbox/rbox-sync-rebuild.cpp b/src/storage-rbox/rbox-sync-rebuild.cpp index 7d6a998e..6ae4eea8 100644 --- a/src/storage-rbox/rbox-sync-rebuild.cpp +++ b/src/storage-rbox/rbox-sync-rebuild.cpp @@ -102,14 +102,19 @@ int rbox_sync_add_object(struct index_rebuild_context *ctx, const std::string &o std::map> load_rados_mail_metadata( bool alt_storage, struct rbox_storage *r_storage, - librados::NObjectIterator &iter) { + std::set &mail_list) { std::map> rados_mails; - while (iter != librados::NObjectIterator::__EndObjectIterator) { + std::set::iterator it; + + for(it=mail_list.begin(); it!=mail_list.end(); ++it){ + + //while (iter != librados::NObjectIterator::__EndObjectIterator) { librmb::RadosMail mail_object; - mail_object.set_oid((*iter).get_oid()); + + mail_object.set_oid((*it)); int load_metadata_ret; if (alt_storage) { @@ -121,7 +126,7 @@ std::map> load_rados_mail_metadata( if (load_metadata_ret < 0 || !librmb::RadosUtils::validate_metadata(mail_object.get_metadata())) { i_warning("metadata for object : %s is not valid, skipping object ", mail_object.get_oid()->c_str()); - ++iter; + //++iter; continue; } @@ -141,7 +146,8 @@ std::map> load_rados_mail_metadata( rados_mails[mailbox_guid]= list_mail_objects; } } - ++iter; + + // ++iter; } return rados_mails; } @@ -416,6 +422,9 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage const struct mailbox_info *info; int ret = 0; + + + iter = mailbox_list_iter_init(ns->list, "*", static_cast(MAILBOX_LIST_ITER_RAW_LIST | MAILBOX_LIST_ITER_RETURN_NO_FLAGS)); while ((info = mailbox_list_iter_next(iter)) != NULL) { @@ -424,7 +433,7 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage struct mailbox *box = mailbox_alloc(ns->list, info->vname, MAILBOX_FLAG_SAVEONLY); if (box->storage != &r_storage->storage || - box->virtual_vfuncs != NULL) { + box->virtual_vfuncs != NULL) { /* the namespace has multiple storages. or is virtual box */ mailbox_free(&box); return 0; @@ -443,11 +452,29 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage FUNC_END(); return -1; } - i_info("Ceph connection established using namespace: %s",r_storage->s->get_namespace().c_str()); - i_info("Loading mails... "); - librados::NObjectIterator *iter_guid = new librados::NObjectIterator(r_storage->s->find_mails(nullptr)); + + + std::set mail_list; + std::string pool_name = r_storage->s->get_pool_name(); + if( r_storage->config->get_object_search_method() == 1) { + mail_list = r_storage->s->find_mails_async(nullptr, + pool_name, + r_storage->config->get_object_search_threads()); + i_info("multithreading done"); + }else{ + i_info("Ceph connection established using namespace: %s",r_storage->s->get_namespace().c_str()); + i_info("Loading mails... "); + librados::NObjectIterator iter_guid = r_storage->s->find_mails(nullptr); + while (iter_guid != librados::NObjectIterator::__EndObjectIterator) { + mail_list.insert((*iter_guid).get_oid()); + i_debug("iter mail list %s",(*iter_guid).get_oid().c_str()); + iter_guid++; + } + + } + i_info("Loading mail metadata..."); - rados_mails = load_rados_mail_metadata(false,r_storage,*iter_guid); + rados_mails = load_rados_mail_metadata(false,r_storage, mail_list); i_info("Mails completely loaded "); std::map>::iterator it; for(it=rados_mails.begin(); it!=rados_mails.end(); ++it){ diff --git a/src/storage-rbox/rbox-sync-rebuild.h b/src/storage-rbox/rbox-sync-rebuild.h index 81dc26a5..16bed509 100644 --- a/src/storage-rbox/rbox-sync-rebuild.h +++ b/src/storage-rbox/rbox-sync-rebuild.h @@ -43,13 +43,10 @@ extern int rbox_sync_index_rebuild(struct rbox_mailbox *rbox, bool force, std::m extern int rbox_storage_rebuild_in_context(struct rbox_storage *r_storage, bool force, bool firstTry); extern int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage *r_storage, std::map> &rados_mails); -extern std::map> load_rados_mail_metadata(bool alt_storage, struct rbox_storage *r_storage, librados::NObjectIterator &iter); +extern std::map> load_rados_mail_metadata(bool alt_storage, struct rbox_storage *r_storage, std::list &mail_list); extern int find_default_mailbox_guid(struct mail_namespace *ns, std::string *mailbox_guid); extern int find_inbox_mailbox_guid(struct mail_namespace *ns, std::string *mailbox_guid); - - -extern std::map> load_rados_mail_metadata(bool alt_storage, struct rbox_storage *r_storage, librados::NObjectIterator &iter); #endif // SRC_STORAGE_RBOX_RBOX_SYNC_REBUILD_H_ diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index b8399f9f..d90a281c 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -67,6 +67,11 @@ test_storage_mock_rbox_bugs_SOURCES = storage-mock-rbox/test_storage_mock_rbox_b test_storage_mock_rbox_bugs_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDOVECOT_INCLUDE) test_storage_mock_rbox_bugs_LDADD = $(storage_shlibs) $(gtest_shlibs) +TESTS += test_repair_rbox +test_repair_rbox_SOURCES = storage-mock-rbox/test_repair_rbox.cpp storage-mock-rbox/TestCase.cpp storage-mock-rbox/TestCase.h mocks/mock_test.h test-utils/it_utils.cpp test-utils/it_utils.h +test_repair_rbox_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDOVECOT_INCLUDE) +test_repair_rbox_LDADD = $(storage_shlibs) $(gtest_shlibs) + TESTS += test_librmb_utils test_librmb_utils_SOURCES = librmb/test_librmb_utils.cpp @@ -74,6 +79,7 @@ test_librmb_utils_LDADD = $(rmb_shlibs) $(top_builddir)/src/librmb/tools/rmb/ls_ if BUILD_INTEGRATION_TESTS + TESTS += it_test_librmb it_test_librmb_SOURCES = librmb/it_test_librmb.cpp it_test_librmb_LDADD = $(rmb_shlibs) $(top_builddir)/src/librmb/tools/rmb/ls_cmd_parser.o $(top_builddir)/src/librmb/tools/rmb/rmb-commands.o $(top_builddir)/src/librmb/tools/rmb/mailbox_tools.o $(gtest_shlibs) diff --git a/src/tests/mocks/mock_test.h b/src/tests/mocks/mock_test.h index b9a9cfb2..e9e53bb9 100644 --- a/src/tests/mocks/mock_test.h +++ b/src/tests/mocks/mock_test.h @@ -52,6 +52,9 @@ class RadosStorageMock : public RadosStorage { librados::ObjectWriteOperation *op)); MOCK_METHOD1(find_mails, librados::NObjectIterator(const RadosMetadata *attr)); MOCK_METHOD1(open_connection, int(const std::string &poolname)); + + MOCK_METHOD3(find_mails_async, std::set(const RadosMetadata *attr, std::string &pool_name,int num_threads)); + MOCK_METHOD3(open_connection, int(const std::string &poolname, const std::string &clustername, const std::string &rados_username)); MOCK_METHOD0(close_connection, void()); @@ -135,6 +138,8 @@ class RadosClusterMock : public RadosCluster { MOCK_METHOD2(get_config_option, int(const char *option, std::string *value)); MOCK_METHOD0(is_connected, bool()); MOCK_METHOD2(set_config_option, void(const char *option, const char *value)); + MOCK_METHOD1(list_pgs_for_pool, std::vector(std::string &pool_name)); + MOCK_METHOD1(list_pgs_osd_for_pool, std::map> (std::string &pool_name)); }; using librmb::RadosDovecotCephCfg; @@ -155,6 +160,10 @@ class RadosDovecotCephCfgMock : public RadosDovecotCephCfg { MOCK_METHOD0(get_chunk_size,int()); MOCK_METHOD0(get_write_method,int()); + MOCK_METHOD0(get_object_search_method,int()); + + MOCK_METHOD0(get_object_search_threads,int()); + MOCK_METHOD1(update_mail_attributes, void(const char *value)); MOCK_METHOD1(update_updatable_attributes, void(const char *value)); MOCK_METHOD1(update_pool_name_metadata, void(const char *value)); diff --git a/src/tests/storage-mock-rbox/test_repair_rbox.cpp b/src/tests/storage-mock-rbox/test_repair_rbox.cpp new file mode 100644 index 00000000..b435689d --- /dev/null +++ b/src/tests/storage-mock-rbox/test_repair_rbox.cpp @@ -0,0 +1,214 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Copyright (c) 2017-2018 Tallence AG and the authors + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + */ + +#include "../storage-mock-rbox/TestCase.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" // turn off warnings for Dovecot :-( +#pragma GCC diagnostic ignored "-Wundef" // turn off warnings for Dovecot :-( +#pragma GCC diagnostic ignored "-Wredundant-decls" // turn off warnings for Dovecot :-( +#ifndef __cplusplus +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" // turn off warnings for Dovecot :-( +#endif + +extern "C" { +#include "lib.h" +#include "mail-user.h" +#include "mail-storage.h" +#include "mail-storage-service.h" +#include "mail-namespace.h" +#include "mailbox-list.h" +#include "ioloop.h" +#include "istream.h" +#include "mail-search-build.h" +#include "ostream.h" +#include "libdict-rados-plugin.h" +} + + +#include "dovecot-ceph-plugin-config.h" +#include "../test-utils/it_utils.h" + +#include "rbox-storage.hpp" +#include "rbox-save.h" + +#include "../mocks/mock_test.h" +#include "rados-util.h" + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Matcher; +using ::testing::Return; +using ::testing::ReturnRef; +#pragma GCC diagnostic pop + +#if DOVECOT_PREREQ(2, 3) +#define mailbox_get_last_internal_error(box, error_r) mailbox_get_last_internal_error(box, error_r) +#else +#define mailbox_get_last_internal_error(box, error_r) mailbox_get_last_error(box, error_r) +#endif + +#ifndef i_zero +#define i_zero(p) memset(p, 0, sizeof(*(p))) +#endif + +/** + * Parse ls-by-pool output command for pg ids + * + */ +TEST_F(StorageTest, ParseLsByPoolOutput) { + + const std::string lsByPoolOutPut = + "mon command outbl: PG OBJECTS DEGRADED MISPLACED UNFOUND BYTES OMAP_BYTES* OMAP_KEYS* LOG STATE SINCE VERSION REPORTED UP ACTING SCRUB_STAMP DEEP_SCRUB_STAMP\n\ + 10.0 568 0 0 0 841115 0 0 1165 active+clean 26h 3641'18906 3656:51938 [3,1,4]p3 [3,1,4]p3 2022-09-12T15:41:31.269234+0000 2022-09-12T15:41:31.269234+0000\n\ + 10.1 550 0 0 0 33248442 0 0 1167 active+clean 25h 3641'22959 3656:56961 [3,2,4]p3 [3,2,4]p3 2022-09-12T17:02:53.075147+0000 2022-09-12T17:02:53.075147+0000\n\ + 10.2 597 0 0 0 1159934 0 0 1323 active+clean 14h 3641'28591 3656:60048 [4,3,2]p4 [4,3,2]p4 2022-09-13T04:19:15.413172+0000 2022-09-11T22:05:48.081770+0000\n\ + 10.3 604 0 0 0 33341614 0 0 1438 active+clean 2h 3641'22933 3657:55462 [1,3,2]p1 [1,3,2]p1 2022-09-13T16:08:56.004228+0000 2022-09-11T09:09:04.723301+0000\n\ + 10.4 562 0 0 0 868945 0 0 1379 active+clean 4h 3641'20673 3656:52381 [1,4,3]p1 [1,4,3]p1 2022-09-13T14:36:42.995722+0000 2022-09-13T14:36:42.995722+0000\n\ + 10.5 603 0 0 0 158821233 0 0 1167 active+clean 18h 3641'117859 3656:149450 [3,4,2]p3 [3,4,2]p3 2022-09-13T00:09:30.875802+0000 2022-09-11T17:26:47.059142+0000\n\ + 10.6 644 0 0 0 933092 0 0 1292 active+clean 43m 3641'22101 3657:54557 [2,1,3]p2 [2,1,3]p2 2022-09-13T17:57:17.071685+0000 2022-09-13T17:57:17.071685+0000\n\ + 10.7 626 0 0 0 95786518 0 0 1193 active+clean 3h 3641'20186 3657:54334 [3,4,1]p3 [3,4,1]p3 2022-09-13T15:22:51.015523+0000 2022-09-12T14:50:10.304250+0000\n\ + 10.8 590 0 0 0 1360275 0 0 1150 active+clean 3h 3641'19029 3657:49696 [3,1,2]p3 [3,1,2]p3 2022-09-13T15:22:28.040315+0000 2022-09-07T06:21:41.088829+0000\n\ + 10.9 575 0 0 0 845156 0 0 1314 active+clean 16h 3641'19322 3656:50842 [2,4,3]p2 [2,4,3]p2 2022-09-13T01:52:08.817572+0000 2022-09-13T01:52:08.817572+0000\n\ + 10.a 567 0 0 0 805954 0 0 1219 active+clean 25h 3641'37553 3656:68282 [3,1,2]p3 [3,1,2]p3 2022-09-12T16:42:57.697431+0000 2022-09-10T10:07:31.790317+0000\n\ + 10.b 582 0 0 0 885900 0 0 1298 active+clean 25h 3641'19280 3656:52838 [2,4,3]p2 [2,4,3]p2 2022-09-12T16:46:16.220323+0000 2022-09-06T17:38:36.977892+0000\n\ + 10.c 549 0 0 0 751071 0 0 1304 active+clean 11h 3641'19066 3656:50539 [2,3,4]p2 [2,3,4]p2 2022-09-13T06:44:32.983447+0000 2022-09-07T18:30:45.260115+0000\n\ + 10.d 650 0 0 0 933800 0 0 1250 active+clean 23h 3641'103478 3656:135352 [2,4,3]p2 [2,4,3]p2 2022-09-12T18:48:06.113121+0000 2022-09-06T20:18:07.792822+0000\n\ + 10.e 566 0 0 0 851780 0 0 1249 active+clean 2h 3532'19569 3657:51205 [2,4,3]p2 [2,4,3]p2 2022-09-13T16:13:32.829316+0000 2022-09-08T14:10:17.974007+0000\n\ + 10.f 597 0 0 0 826336 0 0 1169 active+clean 13h 3641'18988 3656:50480 [3,1,2]p3 [3,1,2]p3 2022-09-13T05:09:49.851413+0000 2022-09-11T19:36:45.701069+0000\n\ + 10.10 562 0 0 0 862878 0 0 1192 active+clean 13h 3641'19245 3656:318916 [3,2,4]p3 [3,2,4]p3 2022-09-13T05:29:59.129259+0000 2022-09-13T05:29:59.129259+0000\n\ + 10.11 547 0 0 0 797004 0 0 1372 active+clean 16h 3641'18587 3656:50156 [1,4,2]p1 [1,4,2]p1 2022-09-13T02:40:21.127749+0000 2022-09-08T08:06:48.604409+0000\n\ + 10.12 530 0 0 0 1052374 0 0 1155 active+clean 28h 3641'20746 3656:51482 [3,1,2]p3 [3,1,2]p3 2022-09-12T14:24:27.828869+0000 2022-09-11T13:33:05.018892+0000\n\ + 10.13 572 0 0 0 847499 0 0 1346 active+clean 15h 3641'28749 3656:60112 [4,3,2]p4 [4,3,2]p4 2022-09-13T03:01:34.151958+0000 2022-09-10T16:11:36.949992+0000\n\ + 10.14 599 0 0 0 1169276 0 0 1279 active+clean 9h 3641'20130 3656:50579 [4,3,1]p4 [4,3,1]p4 2022-09-13T08:48:29.616926+0000 2022-09-13T08:48:29.616926+0000\n\ + 10.15 586 0 0 0 866197 0 0 1279 active+clean 13h 3641'116658 3656:147540 [4,3,2]p4 [4,3,2]p4 2022-09-13T05:36:45.359570+0000 2022-09-08T08:10:42.049640+0000\n\ + 10.16 587 0 0 0 1227527 0 0 1256 active+clean 27h 3641'19293 3656:51660 [2,4,3]p2 [2,4,3]p2 2022-09-12T15:19:09.641685+0000 2022-09-08T21:50:48.032555+0000\n\ + 10.17 566 0 0 0 158772753 0 0 1183 active+clean 7h 3641'34532 3656:65851 [3,1,2]p3 [3,1,2]p3 2022-09-13T10:47:45.614204+0000 2022-09-12T01:21:42.220916+0000\n\ + 10.18 559 0 0 0 63859162 0 0 1354 active+clean 23h 3641'25544 3656:57788 [4,1,2]p4 [4,1,2]p4 2022-09-12T19:35:08.476390+0000 2022-09-11T15:04:20.133155+0000\n\ + 10.19 613 0 0 0 842769 0 0 1166 active+clean 30h 3641'38205 3656:71482 [3,2,4]p3 [3,2,4]p3 2022-09-12T12:21:42.007023+0000 2022-09-06T01:01:59.844062+0000\n\ + 10.1a 587 0 0 0 845231 0 0 1250 active+clean 29h 3641'24127 3656:55136 [2,3,1]p2 [2,3,1]p2 2022-09-12T12:52:01.529860+0000 2022-09-11T07:24:15.003205+0000\n\ + 10.1b 549 0 0 0 76692836 0 0 1316 active+clean 25h 3641'26875 3656:57767 [4,2,3]p4 [4,2,3]p4 2022-09-12T17:06:52.309745+0000 2022-09-08T05:40:28.503848+0000\n\ + 10.1c 604 0 0 0 1114372 0 0 1329 active+clean 62m 3641'18755 3657:50579 [2,3,1]p2 [2,3,1]p2 2022-09-13T17:37:36.331891+0000 2022-09-13T17:37:36.331891+0000\n\ + 10.1d 607 0 0 0 191148409 0 0 1271 active+clean 26h 3641'19526 3656:51525 [2,1,3]p2 [2,1,3]p2 2022-09-12T16:25:02.581960+0000 2022-09-12T16:25:02.581960+0000\n\ + 10.1e 593 0 0 0 848897 0 0 1413 active+clean 20h 3641'19318 3656:51479 [1,3,4]p1 [1,3,4]p1 2022-09-12T22:13:27.455449+0000 2022-09-12T22:13:27.455449+0000\n\ + 10.1f 589 0 0 0 827645 0 0 1288 active+clean 13h 3641'20726 3656:53355 [2,4,1]p2 [2,4,1]p2 2022-09-13T05:09:00.823199+0000 2022-09-07T23:42:54.081441+0000\n"; + + std::vector list = librmb::RadosUtils::extractPgs(lsByPoolOutPut); + + /*for (auto const &token: list) { + std::cout << token << std::endl; + }*/ + + EXPECT_EQ("10.0", list[0]); + EXPECT_EQ("10.1",list[1]); + EXPECT_EQ("10.2",list[2]); + EXPECT_EQ("10.3",list[3]); + EXPECT_EQ("10.4",list[4]); + EXPECT_EQ("10.5",list[5]); + EXPECT_EQ("10.6",list[6]); + EXPECT_EQ("10.7",list[7]); + EXPECT_EQ("10.8",list[8]); + EXPECT_EQ("10.9",list[9]); + EXPECT_EQ("10.a",list[10]); + EXPECT_EQ("10.b",list[11]); + EXPECT_EQ("10.c",list[12]); + EXPECT_EQ("10.d",list[13]); + EXPECT_EQ("10.e",list[14]); + EXPECT_EQ("10.f",list[15]); + EXPECT_EQ("10.10",list[16]); + EXPECT_EQ("10.11",list[17]); + EXPECT_EQ("10.12",list[18]); + EXPECT_EQ("10.13",list[19]); + EXPECT_EQ("10.14",list[20]); + EXPECT_EQ("10.15",list[21]); + EXPECT_EQ("10.16",list[22]); + EXPECT_EQ("10.17",list[23]); + EXPECT_EQ("10.18",list[24]); + EXPECT_EQ("10.19",list[25]); + EXPECT_EQ("10.1a",list[26]); + EXPECT_EQ("10.1b",list[27]); + EXPECT_EQ("10.1c",list[28]); + EXPECT_EQ("10.1d",list[29]); + EXPECT_EQ("10.1e",list[30]); + EXPECT_EQ("10.1f",list[31]); + +} +TEST_F(StorageTest, scanForPg) { + + const std::string header = + "mon command outbl: PG OBJECTS DEGRADED MISPLACED UNFOUND BYTES OMAP_BYTES* OMAP_KEYS* LOG STATE SINCE VERSION REPORTED UP ACTING SCRUB_STAMP DEEP_SCRUB_STAMP\n"; + + const std::string row ="10.0 568 0 0 0 841115 0 0 1165 active+clean 26h 3641'18906 3656:51938 [3,1,4]p3 [3,1,4]p3 2022-09-12T15:41:31.269234+0000 2022-09-12T15:41:31.269234+0000\n"; + + std::vector list = librmb::RadosUtils::split(row,' '); + EXPECT_EQ(17,list.size()); + + std::vector list2= librmb::RadosUtils::split(header,' '); + EXPECT_EQ(20,list2.size()); + +} +TEST_F(StorageTest, extractPrimaryOsd) { + + const std::string lsByPoolOutPut = + "mon command outbl: PG OBJECTS DEGRADED MISPLACED UNFOUND BYTES OMAP_BYTES* OMAP_KEYS* LOG STATE SINCE VERSION REPORTED UP ACTING SCRUB_STAMP DEEP_SCRUB_STAMP\n\ + 10.0 568 0 0 0 841115 0 0 1165 active+clean 26h 3641'18906 3656:51938 [3,1,4]p3 [3,1,4]p3 2022-09-12T15:41:31.269234+0000 2022-09-12T15:41:31.269234+0000\n\ + 10.1 550 0 0 0 33248442 0 0 1167 active+clean 25h 3641'22959 3656:56961 [3,2,4]p3 [3,2,4]p3 2022-09-12T17:02:53.075147+0000 2022-09-12T17:02:53.075147+0000\n\ + 10.2 597 0 0 0 1159934 0 0 1323 active+clean 14h 3641'28591 3656:60048 [4,3,2]p4 [4,3,2]p4 2022-09-13T04:19:15.413172+0000 2022-09-11T22:05:48.081770+0000\n\ + 10.3 604 0 0 0 33341614 0 0 1438 active+clean 2h 3641'22933 3657:55462 [1,3,2]p1 [1,3,2]p1 2022-09-13T16:08:56.004228+0000 2022-09-11T09:09:04.723301+0000\n\ + 10.4 562 0 0 0 868945 0 0 1379 active+clean 4h 3641'20673 3656:52381 [1,4,3]p1 [1,4,3]p1 2022-09-13T14:36:42.995722+0000 2022-09-13T14:36:42.995722+0000\n\ + 10.5 603 0 0 0 158821233 0 0 1167 active+clean 18h 3641'117859 3656:149450 [3,4,2]p3 [3,4,2]p3 2022-09-13T00:09:30.875802+0000 2022-09-11T17:26:47.059142+0000\n\ + 10.6 644 0 0 0 933092 0 0 1292 active+clean 43m 3641'22101 3657:54557 [2,1,3]p2 [2,1,3]p2 2022-09-13T17:57:17.071685+0000 2022-09-13T17:57:17.071685+0000\n\ + 10.7 626 0 0 0 95786518 0 0 1193 active+clean 3h 3641'20186 3657:54334 [3,4,1]p3 [3,4,1]p3 2022-09-13T15:22:51.015523+0000 2022-09-12T14:50:10.304250+0000\n\ + 10.8 590 0 0 0 1360275 0 0 1150 active+clean 3h 3641'19029 3657:49696 [3,1,2]p3 [3,1,2]p3 2022-09-13T15:22:28.040315+0000 2022-09-07T06:21:41.088829+0000\n\ + 10.9 575 0 0 0 845156 0 0 1314 active+clean 16h 3641'19322 3656:50842 [2,4,3]p2 [2,4,3]p2 2022-09-13T01:52:08.817572+0000 2022-09-13T01:52:08.817572+0000\n\ + 10.a 567 0 0 0 805954 0 0 1219 active+clean 25h 3641'37553 3656:68282 [3,1,2]p3 [3,1,2]p3 2022-09-12T16:42:57.697431+0000 2022-09-10T10:07:31.790317+0000\n\ + 10.b 582 0 0 0 885900 0 0 1298 active+clean 25h 3641'19280 3656:52838 [2,4,3]p2 [2,4,3]p2 2022-09-12T16:46:16.220323+0000 2022-09-06T17:38:36.977892+0000\n\ + 10.c 549 0 0 0 751071 0 0 1304 active+clean 11h 3641'19066 3656:50539 [2,3,4]p2 [2,3,4]p2 2022-09-13T06:44:32.983447+0000 2022-09-07T18:30:45.260115+0000\n\ + 10.d 650 0 0 0 933800 0 0 1250 active+clean 23h 3641'103478 3656:135352 [2,4,3]p2 [2,4,3]p2 2022-09-12T18:48:06.113121+0000 2022-09-06T20:18:07.792822+0000\n\ + 10.e 566 0 0 0 851780 0 0 1249 active+clean 2h 3532'19569 3657:51205 [2,4,3]p2 [2,4,3]p2 2022-09-13T16:13:32.829316+0000 2022-09-08T14:10:17.974007+0000\n\ + 10.f 597 0 0 0 826336 0 0 1169 active+clean 13h 3641'18988 3656:50480 [3,1,2]p3 [3,1,2]p3 2022-09-13T05:09:49.851413+0000 2022-09-11T19:36:45.701069+0000\n\ + 10.10 562 0 0 0 862878 0 0 1192 active+clean 13h 3641'19245 3656:318916 [3,2,4]p3 [3,2,4]p3 2022-09-13T05:29:59.129259+0000 2022-09-13T05:29:59.129259+0000\n\ + 10.11 547 0 0 0 797004 0 0 1372 active+clean 16h 3641'18587 3656:50156 [1,4,2]p1 [1,4,2]p1 2022-09-13T02:40:21.127749+0000 2022-09-08T08:06:48.604409+0000\n\ + 10.12 530 0 0 0 1052374 0 0 1155 active+clean 28h 3641'20746 3656:51482 [3,1,2]p3 [3,1,2]p3 2022-09-12T14:24:27.828869+0000 2022-09-11T13:33:05.018892+0000\n\ + 10.13 572 0 0 0 847499 0 0 1346 active+clean 15h 3641'28749 3656:60112 [4,3,2]p4 [4,3,2]p4 2022-09-13T03:01:34.151958+0000 2022-09-10T16:11:36.949992+0000\n\ + 10.14 599 0 0 0 1169276 0 0 1279 active+clean 9h 3641'20130 3656:50579 [4,3,1]p4 [4,3,1]p4 2022-09-13T08:48:29.616926+0000 2022-09-13T08:48:29.616926+0000\n\ + 10.15 586 0 0 0 866197 0 0 1279 active+clean 13h 3641'116658 3656:147540 [4,3,2]p4 [4,3,2]p4 2022-09-13T05:36:45.359570+0000 2022-09-08T08:10:42.049640+0000\n\ + 10.16 587 0 0 0 1227527 0 0 1256 active+clean 27h 3641'19293 3656:51660 [2,4,3]p2 [2,4,3]p2 2022-09-12T15:19:09.641685+0000 2022-09-08T21:50:48.032555+0000\n\ + 10.17 566 0 0 0 158772753 0 0 1183 active+clean 7h 3641'34532 3656:65851 [3,1,2]p3 [3,1,2]p3 2022-09-13T10:47:45.614204+0000 2022-09-12T01:21:42.220916+0000\n\ + 10.18 559 0 0 0 63859162 0 0 1354 active+clean 23h 3641'25544 3656:57788 [4,1,2]p4 [4,1,2]p4 2022-09-12T19:35:08.476390+0000 2022-09-11T15:04:20.133155+0000\n\ + 10.19 613 0 0 0 842769 0 0 1166 active+clean 30h 3641'38205 3656:71482 [3,2,4]p3 [3,2,4]p3 2022-09-12T12:21:42.007023+0000 2022-09-06T01:01:59.844062+0000\n\ + 10.1a 587 0 0 0 845231 0 0 1250 active+clean 29h 3641'24127 3656:55136 [2,3,1]p2 [2,3,1]p2 2022-09-12T12:52:01.529860+0000 2022-09-11T07:24:15.003205+0000\n\ + 10.1b 549 0 0 0 76692836 0 0 1316 active+clean 25h 3641'26875 3656:57767 [4,2,3]p4 [4,2,3]p4 2022-09-12T17:06:52.309745+0000 2022-09-08T05:40:28.503848+0000\n\ + 10.1c 604 0 0 0 1114372 0 0 1329 active+clean 62m 3641'18755 3657:50579 [2,3,1]p2 [2,3,1]p2 2022-09-13T17:37:36.331891+0000 2022-09-13T17:37:36.331891+0000\n\ + 10.1d 607 0 0 0 191148409 0 0 1271 active+clean 26h 3641'19526 3656:51525 [2,1,3]p2 [2,1,3]p2 2022-09-12T16:25:02.581960+0000 2022-09-12T16:25:02.581960+0000\n\ + 10.1e 593 0 0 0 848897 0 0 1413 active+clean 20h 3641'19318 3656:51479 [1,3,4]p1 [1,3,4]p1 2022-09-12T22:13:27.455449+0000 2022-09-12T22:13:27.455449+0000\n\ + 10.1f 589 0 0 0 827645 0 0 1288 active+clean 13h 3641'20726 3656:53355 [2,4,1]p2 [2,4,1]p2 2022-09-13T05:09:00.823199+0000 2022-09-07T23:42:54.081441+0000\n"; + + std::map> list = librmb::RadosUtils::extractPgAndPrimaryOsd(lsByPoolOutPut); + + for (const auto& x : list) + { + std::cout << "first: " << x.first << ", second: " << x.second.size() << std::endl; + } + + EXPECT_EQ(4,list["1"].size()); + EXPECT_EQ(11,list["2"].size()); + EXPECT_EQ(11,list["3"].size()); + EXPECT_EQ(6,list["4"].size()); + +} + +int main(int argc, char **argv) { + ::testing::InitGoogleMock(&argc, argv); + return RUN_ALL_TESTS(); +} From 30798cd263309ee93c05f1f4156518e7f66573c3 Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Tue, 27 Sep 2022 21:46:30 +0200 Subject: [PATCH 04/18] Bugfix/342 logging multithreading (#344) * #342 fix threading and additional logs * version --- CHANGELOG.md | 5 +++- configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/librmb/rados-storage-impl.cpp | 41 ++++++++++++++++---------- src/librmb/rados-storage-impl.h | 2 +- src/librmb/rados-storage.h | 5 +++- src/storage-rbox/rbox-sync-rebuild.cpp | 8 ++++- src/tests/mocks/mock_test.h | 2 +- 8 files changed, 44 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c52d7ca..77c8a08a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Change Log -## [0.0.40](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.39) (2022-09-22) +## [0.0.41](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.41) (2022-09-27) +- #342 multithreading bugfix and additional logging + +## [0.0.40](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.40) (2022-09-22) - #342 multithreading object search for doveadm force-resync (feature toggle) new config params: # search method default = 0 | 1 multithreading diff --git a/configure.ac b/configure.ac index b8fb59ef..7b7b1295 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.40], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.41], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index aa5f1b3b..e6adde8f 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -15,7 +15,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.40 +Version: 0.0.41 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/librmb/rados-storage-impl.cpp b/src/librmb/rados-storage-impl.cpp index 5525bf36..2e883afd 100644 --- a/src/librmb/rados-storage-impl.cpp +++ b/src/librmb/rados-storage-impl.cpp @@ -198,45 +198,54 @@ librados::NObjectIterator RadosStorageImpl::find_mails(const RadosMetadata *attr **/ std::set RadosStorageImpl::find_mails_async(const RadosMetadata *attr, std::string &pool_name, - int num_threads){ + int num_threads, + void (*ptr)(std::string&)){ std::set oid_list; std::mutex oid_list_mutex; // Define a Lambda Expression - auto f = [](const std::vector &list, std::mutex &oid_mutex, std::set &oids, librados::IoCtx *io_ctx) { + auto f = [](const std::vector &list, std::mutex &oid_mutex, std::set &oids, librados::IoCtx *io_ctx, + void (*ptr)(std::string&), std::string osd) { - std::lock_guard guard(oid_mutex); for (auto const &pg: list) { + uint64_t ppool; uint32_t pseed; int r = sscanf(pg.c_str(), "%llu.%x", (long long unsigned *)&ppool, &pseed); librados::NObjectIterator iter= io_ctx->nobjects_begin(pseed); - + while (iter != librados::NObjectIterator::__EndObjectIterator) { std::string oid = iter->get_oid(); - oids.insert(oid); + { + std::lock_guard guard(oid_mutex); + oids.insert(oid); + } iter++; - } - } + } + std::string t = "osd "+ osd +" pg done " + pg; + (*ptr)(t); + } + std::string t = "done with osd "+ osd ; + (*ptr)(t); }; //std::string pool_mame = "mail_storage"; std::map> osd_pg_map = cluster->list_pgs_osd_for_pool(pool_name); std::vector threads; - for (const auto& x : osd_pg_map) - { - if(threads.size() == num_threads){ - for (auto const &thread: threads) { - thread.join(); - } - threads.clear(); + for (const auto& x : osd_pg_map){ + if(threads.size() == num_threads){ + threads[0].join(); + threads.erase(threads.begin()); } - threads.push_back(std::thread(f, std::ref(x.second),std::ref(oid_list_mutex),std::ref(oid_list), &get_io_ctx())); + threads.push_back(std::thread(f, std::ref(x.second),std::ref(oid_list_mutex),std::ref(oid_list), &get_io_ctx(), ptr, x.first)); + std::string create_msg = "creating thread for osd: "+ x.first; + (*ptr)(create_msg); } - for (auto const &thread: threads) { + + for (auto const &thread: threads) { thread.join(); } return oid_list; diff --git a/src/librmb/rados-storage-impl.h b/src/librmb/rados-storage-impl.h index 8984524f..17c37cd8 100644 --- a/src/librmb/rados-storage-impl.h +++ b/src/librmb/rados-storage-impl.h @@ -53,7 +53,7 @@ class RadosStorageImpl : public RadosStorage { librados::ObjectWriteOperation *op) override; librados::NObjectIterator find_mails(const RadosMetadata *attr) override; - std::set find_mails_async(const RadosMetadata *attr, std::string &pool_name, int num_threads) override; + std::set find_mails_async(const RadosMetadata *attr, std::string &pool_name, int num_threads, void (*ptr)(std::string&)) override; int open_connection(const std::string &poolname) override; int open_connection(const std::string &poolname, const std::string &clustername, diff --git a/src/librmb/rados-storage.h b/src/librmb/rados-storage.h index f5234026..6a1ade87 100644 --- a/src/librmb/rados-storage.h +++ b/src/librmb/rados-storage.h @@ -106,7 +106,10 @@ class RadosStorage { virtual librados::NObjectIterator find_mails(const RadosMetadata *attr) = 0; - virtual std::set find_mails_async(const RadosMetadata *attr, std::string &pool_name, int num_threads) = 0; + virtual std::set find_mails_async(const RadosMetadata *attr, + std::string &pool_name, + int num_threads, + void (*ptr)(std::string&)) = 0; /*! open the rados connections with default cluster and username diff --git a/src/storage-rbox/rbox-sync-rebuild.cpp b/src/storage-rbox/rbox-sync-rebuild.cpp index 6ae4eea8..33855223 100644 --- a/src/storage-rbox/rbox-sync-rebuild.cpp +++ b/src/storage-rbox/rbox-sync-rebuild.cpp @@ -415,6 +415,9 @@ int find_inbox_mailbox_guid(struct mail_namespace *ns, std::string *mailbox_guid return ret; } +void cb(std::string &pg){ + i_debug("processing: %s",pg.c_str()); +} int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage *r_storage, std::map> &rados_mails) { FUNC_START(); @@ -447,6 +450,7 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage mail_index_lock_sync(box->index, "LOCKED_FOR_REPAIR"); if(rados_mails.size() == 0) { + if (rbox_open_rados_connection(box, false) < 0) { i_error("rbox_sync_index_rebuild_objects: cannot open rados connection"); FUNC_END(); @@ -456,10 +460,12 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage std::set mail_list; std::string pool_name = r_storage->s->get_pool_name(); + if( r_storage->config->get_object_search_method() == 1) { mail_list = r_storage->s->find_mails_async(nullptr, pool_name, - r_storage->config->get_object_search_threads()); + r_storage->config->get_object_search_threads(), + &cb); i_info("multithreading done"); }else{ i_info("Ceph connection established using namespace: %s",r_storage->s->get_namespace().c_str()); diff --git a/src/tests/mocks/mock_test.h b/src/tests/mocks/mock_test.h index e9e53bb9..04d852a3 100644 --- a/src/tests/mocks/mock_test.h +++ b/src/tests/mocks/mock_test.h @@ -53,7 +53,7 @@ class RadosStorageMock : public RadosStorage { MOCK_METHOD1(find_mails, librados::NObjectIterator(const RadosMetadata *attr)); MOCK_METHOD1(open_connection, int(const std::string &poolname)); - MOCK_METHOD3(find_mails_async, std::set(const RadosMetadata *attr, std::string &pool_name,int num_threads)); + MOCK_METHOD4(find_mails_async, std::set(const RadosMetadata *attr, std::string &pool_name,int num_threads, void (*ptr)(std::string&))); MOCK_METHOD3(open_connection, int(const std::string &poolname, const std::string &clustername, const std::string &rados_username)); From a58c73527f39d274149fc1d40ddb54280e31a06b Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Thu, 20 Oct 2022 08:13:43 +0200 Subject: [PATCH 05/18] Bugfix/rbox copy context (#347) * 346; fix rbox-copy seg fault always init rbox_mail * #346 fix segmentation fault rbox_copy --- CHANGELOG.md | 3 ++ configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/librmb/rados-storage-impl.cpp | 47 +++++++++++++++++++------- src/storage-rbox/rbox-copy.cpp | 12 +++---- src/storage-rbox/rbox-sync-rebuild.cpp | 19 +++++++++-- 6 files changed, 61 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c8a08a..d0c094e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change Log +## [0.0.42](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.42) (2022-10-18) +- #346 segmentation fault (rbox_copy) if rbox_mail is null + ## [0.0.41](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.41) (2022-09-27) - #342 multithreading bugfix and additional logging diff --git a/configure.ac b/configure.ac index 7b7b1295..1b8df95d 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.41], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.42], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index e6adde8f..8c25e761 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -15,7 +15,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.41 +Version: 0.0.42 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/librmb/rados-storage-impl.cpp b/src/librmb/rados-storage-impl.cpp index 2e883afd..0c964752 100644 --- a/src/librmb/rados-storage-impl.cpp +++ b/src/librmb/rados-storage-impl.cpp @@ -203,44 +203,67 @@ std::set RadosStorageImpl::find_mails_async(const RadosMetadata *at std::set oid_list; std::mutex oid_list_mutex; - - // Define a Lambda Expression - auto f = [](const std::vector &list, std::mutex &oid_mutex, std::set &oids, librados::IoCtx *io_ctx, - void (*ptr)(std::string&), std::string osd) { - + + // Thread!! + auto f = [](const std::vector &list, + std::mutex &oid_mutex, + std::set &oids, + librados::IoCtx *io_ctx, + void (*ptr)(std::string&), + std::string osd, + ceph::bufferlist &filter) { + + int total_count = 0; for (auto const &pg: list) { uint64_t ppool; uint32_t pseed; int r = sscanf(pg.c_str(), "%llu.%x", (long long unsigned *)&ppool, &pseed); - librados::NObjectIterator iter= io_ctx->nobjects_begin(pseed); - + librados::NObjectIterator iter= io_ctx->nobjects_begin(pseed, filter); + int count = 0; while (iter != librados::NObjectIterator::__EndObjectIterator) { std::string oid = iter->get_oid(); { std::lock_guard guard(oid_mutex); oids.insert(oid); + count++; } iter++; } - std::string t = "osd "+ osd +" pg done " + pg; + total_count+=count; + std::string t = "osd "+ osd +" pg done " + pg + " objects " + std::to_string(count); (*ptr)(t); } - std::string t = "done with osd "+ osd ; + std::string t = "done with osd "+ osd + " total: " + std::to_string(total_count); (*ptr)(t); - }; + }; //std::string pool_mame = "mail_storage"; std::map> osd_pg_map = cluster->list_pgs_osd_for_pool(pool_name); std::vector threads; - + + std::string filter_name = PLAIN_FILTER_NAME; + ceph::bufferlist filter_bl; + + encode(filter_name, filter_bl); + encode("_" + attr->key, filter_bl); + encode(attr->bl.to_str(), filter_bl); + std::string msg_1 = "buffer set"; + (*ptr)(msg_1); + for (const auto& x : osd_pg_map){ if(threads.size() == num_threads){ threads[0].join(); threads.erase(threads.begin()); } - threads.push_back(std::thread(f, std::ref(x.second),std::ref(oid_list_mutex),std::ref(oid_list), &get_io_ctx(), ptr, x.first)); + + if(x.first == "oon") { + std::string create_msg = "skipping thread for osd: "+ x.first; + (*ptr)(create_msg); + continue; + } + threads.push_back(std::thread(f, std::ref(x.second),std::ref(oid_list_mutex),std::ref(oid_list), &get_io_ctx(), ptr, x.first, std::ref(filter_bl))); std::string create_msg = "creating thread for osd: "+ x.first; (*ptr)(create_msg); } diff --git a/src/storage-rbox/rbox-copy.cpp b/src/storage-rbox/rbox-copy.cpp index ccce7f4f..5fb6dad0 100644 --- a/src/storage-rbox/rbox-copy.cpp +++ b/src/storage-rbox/rbox-copy.cpp @@ -236,16 +236,14 @@ static int rbox_mail_storage_try_copy(struct mail_save_context **_ctx, struct ma if (get_src_dest_namespace(r_storage, mail, dest_mbox, &ns_src, &ns_dest) < 0) { return -1; } - + // always make sure rados mail is valid. + if(rmail->rados_mail == nullptr){ + rmail->rados_mail = r_storage->s->alloc_rados_mail(); + } #ifdef DEBUG i_debug("namespaces: src=%s, dst=%s", ns_src.c_str(), ns_dest.c_str()); #endif - if(ctx->copy_src_mail->box->virtual_vfuncs != NULL) { - i_debug("copy from virtual mailbox requires us to create the rados mail structure here!"); - if(rmail->rados_mail == nullptr){ - rmail->rados_mail = r_storage->s->alloc_rados_mail(); - } - } + if (r_ctx->copying == TRUE) { if (rbox_get_index_record(mail) < 0) { diff --git a/src/storage-rbox/rbox-sync-rebuild.cpp b/src/storage-rbox/rbox-sync-rebuild.cpp index 33855223..03fd2921 100644 --- a/src/storage-rbox/rbox-sync-rebuild.cpp +++ b/src/storage-rbox/rbox-sync-rebuild.cpp @@ -18,6 +18,8 @@ extern "C" { #include "data-stack.h" } +#include + #include "rbox-sync-rebuild.h" #include "rbox-storage.hpp" @@ -187,7 +189,7 @@ int rbox_sync_rebuild_entry(struct index_rebuild_context *ctx, std::mapget_oid(), &(*it), rebuild_ctx->alt_storage, rebuild_ctx->next_uid); - i_info("re-adding mail oid:(%s) with uid: %d to mailbox %s (%s) ", it->get_oid()->c_str(), rebuild_ctx->next_uid, mailbox_guid.c_str(), ctx->box->name ); + i_debug("re-adding mail oid:(%s) with uid: %d to mailbox %s (%s) ", it->get_oid()->c_str(), rebuild_ctx->next_uid, mailbox_guid.c_str(), ctx->box->name ); if (sync_add_objects_ret < 0) { i_error("sync_add_object: oid(%s), alt_storage(%d),uid(%d)", it->get_oid()->c_str(), @@ -462,11 +464,22 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage std::string pool_name = r_storage->s->get_pool_name(); if( r_storage->config->get_object_search_method() == 1) { - mail_list = r_storage->s->find_mails_async(nullptr, + librmb::RadosMetadata filter(rbox_metadata_key::RBOX_METADATA_ORIG_MAILBOX, "INBOX"); + + long milli_time, seconds, useconds; + struct timeval start_time, end_time; + gettimeofday(&start_time, NULL); + + mail_list = r_storage->s->find_mails_async(&filter, pool_name, r_storage->config->get_object_search_threads(), &cb); - i_info("multithreading done"); + gettimeofday(&end_time, NULL); + seconds = end_time.tv_sec - start_time.tv_sec; //seconds + useconds = end_time.tv_usec - start_time.tv_usec; //milliseconds + milli_time = ((seconds) * 1000 + useconds/1000.0); + + i_info("multithreading done : took: %ld ms", (milli_time)); }else{ i_info("Ceph connection established using namespace: %s",r_storage->s->get_namespace().c_str()); i_info("Loading mails... "); From 574fa4a2c93503ea49a217da85e9761e38d7549d Mon Sep 17 00:00:00 2001 From: Jan Radon Date: Fri, 28 Oct 2022 00:00:20 +0200 Subject: [PATCH 06/18] #364 and code cleanup --- .gitignore | 1 + CHANGELOG.md | 3 + configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/storage-rbox/rbox-copy.cpp | 7 +- src/storage-rbox/rbox-mail.cpp | 2 +- src/storage-rbox/rbox-storage.cpp | 10 -- src/storage-rbox/rbox-sync-rebuild.cpp | 123 ++++++--------- src/storage-rbox/rbox-sync.cpp | 70 +++------ src/tests/it_test_storage_rbox_2 | 210 ------------------------- 10 files changed, 83 insertions(+), 347 deletions(-) delete mode 100755 src/tests/it_test_storage_rbox_2 diff --git a/.gitignore b/.gitignore index 2456eb17..da08ac48 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dovecot-rados-plugin-config.h .project .cproject .autotools +**/cscope* # http://www.gnu.org/software/automake diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c094e4..f5ed27c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change Log +## [0.0.43](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.43) (2022-10-27) +- #346 segmentation fault (rbox_copy) if rbox_mail is null + ## [0.0.42](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.42) (2022-10-18) - #346 segmentation fault (rbox_copy) if rbox_mail is null diff --git a/configure.ac b/configure.ac index 48e6b540..225a523b 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.42], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.43], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) AC_CONFIG_SRCDIR([src]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index ac68df99..6f391918 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -15,7 +15,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.42 +Version: 0.0.43 Release: 0%{?dist} diff --git a/src/storage-rbox/rbox-copy.cpp b/src/storage-rbox/rbox-copy.cpp index 5fb6dad0..4cb8e976 100644 --- a/src/storage-rbox/rbox-copy.cpp +++ b/src/storage-rbox/rbox-copy.cpp @@ -119,6 +119,9 @@ static void set_mailbox_metadata(struct mail_save_context *ctx, std::listmailbox_guid)); metadata_update->push_back(metadata_mailbox_guid); + librmb::RadosMetadata metadata_mbn(rbox_metadata_key::RBOX_METADATA_RECEIVED_TIME, ctx->data.received_date); + metadata_update->push_back(metadata_mbn); + if (r_storage->config->is_update_attributes()) { if (r_storage->config->is_updateable_attribute(rbox_metadata_key::RBOX_METADATA_ORIG_MAILBOX)) { // updates the plain text mailbox name @@ -265,7 +268,9 @@ static int rbox_mail_storage_try_copy(struct mail_save_context **_ctx, struct ma return -1; } - index_copy_cache_fields(ctx, mail, r_ctx->seq); + if(!ctx->transaction->box->disable_reflink_copy_to) { + index_copy_cache_fields(ctx, mail, r_ctx->seq); + } if (ctx->dest_mail != NULL) { mail_set_seq_saving(ctx->dest_mail, r_ctx->seq); } diff --git a/src/storage-rbox/rbox-mail.cpp b/src/storage-rbox/rbox-mail.cpp index 274c2592..ed04d4b3 100644 --- a/src/storage-rbox/rbox-mail.cpp +++ b/src/storage-rbox/rbox-mail.cpp @@ -180,7 +180,7 @@ static int rbox_mail_metadata_get(struct rbox_mail *rmail, enum rbox_metadata_ke return 0; } -static int rbox_mail_get_received_date(struct mail *_mail, time_t *date_r) { +int rbox_mail_get_received_date(struct mail *_mail, time_t *date_r) { FUNC_START(); struct rbox_mail *rmail = (struct rbox_mail *)_mail; struct index_mail_data *data = &rmail->imail.data; diff --git a/src/storage-rbox/rbox-storage.cpp b/src/storage-rbox/rbox-storage.cpp index 3a0821d0..d217f0dd 100644 --- a/src/storage-rbox/rbox-storage.cpp +++ b/src/storage-rbox/rbox-storage.cpp @@ -108,17 +108,10 @@ static const char *rbox_storage_find_root_dir(const struct mail_namespace *ns) { if (ns->owner != NULL && mail_user_get_home(ns->owner, &home) > 0) { const char *path = t_strconcat(home, "/rbox", NULL); if (access(path, R_OK | W_OK | X_OK) == 0) { -#ifdef DEBUG - i_debug("rbox: root exists (%s)", path); -#endif FUNC_END(); return path; } -#ifdef DEBUG - i_debug("rbox: access(%s, rwx): failed: %m", path); -#endif } - FUNC_END(); return NULL; } @@ -136,7 +129,6 @@ bool rbox_storage_autodetect(const struct mail_namespace *ns, struct mailbox_lis #ifdef DEBUG i_debug("rbox: couldn't find root dir"); #endif - FUNC_END(); return FALSE; } @@ -277,7 +269,6 @@ static int rbox_mailbox_alloc_index(struct rbox_mailbox *rbox) { i_zero(&hdr); guid_128_generate(hdr.mailbox_guid); mail_index_set_ext_init_data(rbox->box.index, rbox->hdr_ext_id, &hdr, sizeof(hdr)); - // memcpy(rbox->mailbox_guid, hdr.mailbox_guid, sizeof(rbox->mailbox_guid)); // register index record holding the mail guid rbox->ext_id = mail_index_ext_register(rbox->box.index, "obox", 0, sizeof(struct obox_mail_index_record), 1); @@ -629,7 +620,6 @@ int rbox_mailbox_create_indexes(struct mailbox *box, const struct mailbox_update if (new_trans != NULL) { if (mail_index_transaction_commit(&new_trans) < 0) { mailbox_set_index_error(box); - FUNC_END_RET("ret == -1"); return -1; } diff --git a/src/storage-rbox/rbox-sync-rebuild.cpp b/src/storage-rbox/rbox-sync-rebuild.cpp index 665a6c81..8207e577 100644 --- a/src/storage-rbox/rbox-sync-rebuild.cpp +++ b/src/storage-rbox/rbox-sync-rebuild.cpp @@ -58,9 +58,6 @@ int rbox_sync_add_object(struct index_rebuild_context *ctx, const std::string &o } T_END; -#ifdef DEBUG - i_debug("added to index %d", next_uid); -#endif /* save the 128bit GUID/OID to index record */ struct obox_mail_index_record rec; i_zero(&rec); @@ -94,9 +91,7 @@ int rbox_sync_add_object(struct index_rebuild_context *ctx, const std::string &o if (!r_storage->ms->get_storage()->update_metadata(s_oid, to_update)) { i_warning("update of MAIL_UID failed: for object: %s , uid: %d", mail_obj->get_oid()->c_str(), next_uid); } -#ifdef DEBUG - i_debug("rebuilding %s , with uid=%d", oi.c_str(), next_uid); -#endif + FUNC_END(); return 0; } @@ -107,49 +102,40 @@ std::map> load_rados_mail_metadata( std::set &mail_list) { std::map> rados_mails; - std::set::iterator it; for(it=mail_list.begin(); it!=mail_list.end(); ++it){ - - //while (iter != librados::NObjectIterator::__EndObjectIterator) { - librmb::RadosMail mail_object; - + librmb::RadosMail mail_object; mail_object.set_oid((*it)); - - int load_metadata_ret; + if (alt_storage) { r_storage->ms->get_storage()->set_io_ctx(&r_storage->alt->get_io_ctx()); - load_metadata_ret = r_storage->ms->get_storage()->load_metadata(&mail_object); - } else { - load_metadata_ret = r_storage->ms->get_storage()->load_metadata(&mail_object); } - + + int load_metadata_ret = r_storage->ms->get_storage()->load_metadata(&mail_object); if (load_metadata_ret < 0 || !librmb::RadosUtils::validate_metadata(mail_object.get_metadata())) { i_warning("metadata for object : %s is not valid, skipping object ", mail_object.get_oid()->c_str()); //++iter; continue; } - if (load_metadata_ret >= 0) { - char *mailbox_guid = NULL; - librmb::RadosUtils::get_metadata(librmb::RBOX_METADATA_MAILBOX_GUID, - mail_object.get_metadata(), - &mailbox_guid - ); - std::string mails_guid(mailbox_guid); - - if(rados_mails.count(mailbox_guid)){ - rados_mails[mailbox_guid].push_back(mail_object); - }else{ - std::list list_mail_objects; - list_mail_objects.push_back(mail_object); - rados_mails[mailbox_guid]= list_mail_objects; - } + char *mailbox_guid = NULL; + librmb::RadosUtils::get_metadata(librmb::RBOX_METADATA_MAILBOX_GUID, + mail_object.get_metadata(), + &mailbox_guid + ); + std::string mails_guid(mailbox_guid); + + if(rados_mails.count(mailbox_guid)){ + rados_mails[mailbox_guid].push_back(mail_object); } - - // ++iter; + else{ + std::list list_mail_objects; + list_mail_objects.push_back(mail_object); + rados_mails[mailbox_guid]= list_mail_objects; + } + } return rados_mails; } @@ -201,7 +187,6 @@ int rbox_sync_rebuild_entry(struct index_rebuild_context *ctx, std::mapto_string(" ").c_str()); rebuild_ctx->next_uid++; - } if (sync_add_objects_ret < 0) { @@ -305,21 +290,20 @@ int rbox_storage_rebuild_in_context(struct rbox_storage *r_storage, bool force, } } i_info("total unassigned mails %ld", count_not_assigned); - i_info("Total assigned mails %ld", count_assigned); i_info("Total unassigned mails %ld", count_not_assigned); - if(count_not_assigned > 0) - { + if(count_not_assigned > 0) { + std::string last_known_mailbox_guid; struct mail_namespace *ns_second = mail_namespace_find_inbox(user->namespaces); - if(find_default_mailbox_guid(ns_second, &last_known_mailbox_guid) < 0) - { + if(find_default_mailbox_guid(ns_second, &last_known_mailbox_guid) < 0){ // bad no default mailbox found i_warning("unable to find inbox guid => unreferenced mails can not automatically be re-assigned "); return 0; } + int unassigned_counter = 0; for(it=rados_mails.begin(); it!=rados_mails.end(); ++it) { @@ -339,15 +323,15 @@ int rbox_storage_rebuild_in_context(struct rbox_storage *r_storage, bool force, write_mail_uid.setxattr(metadata.key.c_str(), metadata.bl); if (r_storage->s->get_io_ctx().operate(*list_it->get_oid(), &write_mail_uid) < 0) { - i_info("Unable to reset metadata to guid : %s",last_known_mailbox_guid.c_str()); + i_debug("Unable to reset metadata to guid : %s",last_known_mailbox_guid.c_str()); }else { - i_info("(%d) Mailbox guid for mail (oid=%s) restored to %s (INBOX) => re-run force-resync to assign them ",unassigned_counter, list_it->get_oid()->c_str(),last_known_mailbox_guid.c_str()); + i_debug("(%d) Mailbox guid for mail (oid=%s) restored to %s (INBOX) => re-run force-resync to assign them ",unassigned_counter, list_it->get_oid()->c_str(),last_known_mailbox_guid.c_str()); } unassigned_counter++; list_it->set_lost_object(true); } } - if(unassigned_counter>0){ + if(unassigned_counter > 0){ if(firstTry){ // try again.... but only once, as we do not want to end up in an endless loop. return rbox_storage_rebuild_in_context(r_storage,force, false); @@ -355,9 +339,7 @@ int rbox_storage_rebuild_in_context(struct rbox_storage *r_storage, bool force, i_error("still unassigned mail objects in ceph namespace, manual intervention required."); return -1; } - } - } FUNC_END(); @@ -423,14 +405,11 @@ void cb(std::string &pg){ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage *r_storage, std::map> &rados_mails) { FUNC_START(); - struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; int ret = 0; - - - - iter = mailbox_list_iter_init(ns->list, "*", static_cast(MAILBOX_LIST_ITER_RAW_LIST | + struct mailbox_list_iterate_context *iter = mailbox_list_iter_init(ns->list, "*", static_cast(MAILBOX_LIST_ITER_RAW_LIST | MAILBOX_LIST_ITER_RETURN_NO_FLAGS)); while ((info = mailbox_list_iter_next(iter)) != NULL) { @@ -452,21 +431,23 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage mail_index_lock_sync(box->index, "LOCKED_FOR_REPAIR"); if(rados_mails.size() == 0) { - if (rbox_open_rados_connection(box, false) < 0) { i_error("rbox_sync_index_rebuild_objects: cannot open rados connection"); FUNC_END(); return -1; } - std::set mail_list; std::string pool_name = r_storage->s->get_pool_name(); + i_info("Ceph connection established using namespace: %s",r_storage->s->get_namespace().c_str()); + i_info("Loading mails... "); + if( r_storage->config->get_object_search_method() == 1) { + //all mail first end up in the inbox librmb::RadosMetadata filter(rbox_metadata_key::RBOX_METADATA_ORIG_MAILBOX, "INBOX"); - + long milli_time, seconds, useconds; struct timeval start_time, end_time; gettimeofday(&start_time, NULL); @@ -476,34 +457,32 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage r_storage->config->get_object_search_threads(), &cb); gettimeofday(&end_time, NULL); - seconds = end_time.tv_sec - start_time.tv_sec; //seconds - useconds = end_time.tv_usec - start_time.tv_usec; //milliseconds + seconds = end_time.tv_sec - start_time.tv_sec; + useconds = end_time.tv_usec - start_time.tv_usec; milli_time = ((seconds) * 1000 + useconds/1000.0); - i_info("multithreading done : took: %ld ms", (milli_time)); - }else{ - i_info("Ceph connection established using namespace: %s",r_storage->s->get_namespace().c_str()); - i_info("Loading mails... "); + i_debug("multithreading done : took: %ld ms", (milli_time)); + } + else { librados::NObjectIterator iter_guid = r_storage->s->find_mails(nullptr); while (iter_guid != librados::NObjectIterator::__EndObjectIterator) { mail_list.insert((*iter_guid).get_oid()); - i_debug("iter mail list %s",(*iter_guid).get_oid().c_str()); iter_guid++; } - } - + i_info("Loading mail metadata..."); rados_mails = load_rados_mail_metadata(false,r_storage, mail_list); i_info("Mails completely loaded "); - std::map>::iterator it; - for(it=rados_mails.begin(); it!=rados_mails.end(); ++it){ - i_info("Found mails for mailbox_guid: %s: mails : %ld",it->first.c_str(), it->second.size()); - } + #ifdef DEBUG + std::map>::iterator it; + for(it=rados_mails.begin(); it!=rados_mails.end(); ++it){ + i_debug("Found mails for mailbox_guid: %s: mails : %ld", it->first.c_str(), it->second.size()); + } + #endif } ret = rbox_sync_index_rebuild((struct rbox_mailbox *)box, force, rados_mails); - if (ret < 0) { i_error("error resync (%s), error(%d), force(%d)", info->vname, ret, force); } @@ -541,12 +520,9 @@ int rbox_sync_index_rebuild(struct rbox_mailbox *rbox, bool force, std::mapbox), - guid_128_to_string(rbox->mailbox_guid), rbox->box.name, rbox->box.list->set.alt_dir); + i_debug("rbox %s: Rebuilding index, guid: %s , mailbox_name: %s, alt_storage: %s", mailbox_get_path(&rbox->box), + guid_128_to_string(rbox->mailbox_guid), rbox->box.name, rbox->box.list->set.alt_dir); view = mail_index_view_open(rbox->box.index); @@ -556,9 +532,6 @@ int rbox_sync_index_rebuild(struct rbox_mailbox *rbox, bool force, std::mapsync_view, seq1, &uid); - /* TODO: */ + const struct mail_index_record *rec; rec = mail_index_lookup(ctx->sync_view, seq1); if (rec == NULL) { @@ -102,13 +102,11 @@ static int update_extended_metadata(struct rbox_sync_context *ctx, uint32_t seq1 i_error("rbox_sync_object_expunge: connection to rados failed. alt_storage(%d)", alt_storage); return -1; } - if (alt_storage) { - r_storage->ms->get_storage()->set_io_ctx(&r_storage->alt->get_io_ctx()); - } else { - r_storage->ms->get_storage()->set_io_ctx(&r_storage->s->get_io_ctx()); - } - /* END TODO*/ - + + r_storage->ms->get_storage()->set_io_ctx(alt_storage ? + &r_storage->alt->get_io_ctx() : + &r_storage->s->get_io_ctx() ); + guid_128_t index_oid; if (rbox_get_oid_from_index(ctx->sync_view, seq1, ((struct rbox_mailbox *)box)->ext_id, &index_oid) >= 0) { const char *oid = guid_128_to_string(index_oid); @@ -117,7 +115,8 @@ static int update_extended_metadata(struct rbox_sync_context *ctx, uint32_t seq1 std::string ext_key = std::to_string(keyword_idx); if (remove) { ret = r_storage->ms->get_storage()->remove_keyword_metadata(key_oid, ext_key); - } else { + } + else { unsigned int count; const char *const *keywords = array_get(&ctx->sync_view->index->keywords, &count); if (keywords == NULL) { @@ -150,12 +149,8 @@ static int move_to_alt(struct rbox_sync_context *ctx, uint32_t seq1, uint32_t se } for (; seq1 <= seq2; seq1++) { guid_128_t index_oid; - if (rbox_get_oid_from_index(ctx->sync_view, seq1, ((struct rbox_mailbox *)&ctx->rbox->box)->ext_id, &index_oid) >= - 0) { + if (rbox_get_oid_from_index(ctx->sync_view, seq1, ((struct rbox_mailbox *)&ctx->rbox->box)->ext_id, &index_oid) >= 0) { std::string oid = guid_128_to_string(index_oid); -#ifdef DEBUG - i_debug("found oid: %s", oid.c_str()); -#endif ret = librmb::RadosUtils::move_to_alt(oid, r_storage->s, r_storage->alt, r_storage->ms, inverse); if (ret >= 0) { if (inverse) { @@ -192,12 +187,11 @@ static int update_flags(struct rbox_sync_context *ctx, uint32_t seq1, uint32_t s i_error("update_flags: connection to rados failed (alt_storage(%d))", alt_storage); return -1; } - if (alt_storage) { - r_storage->ms->get_storage()->set_io_ctx(&r_storage->alt->get_io_ctx()); - } else { - r_storage->ms->get_storage()->set_io_ctx(&r_storage->s->get_io_ctx()); - } - + + r_storage->ms->get_storage()->set_io_ctx( alt_storage ? + &r_storage->alt->get_io_ctx() : + &r_storage->s->get_io_ctx() ); + guid_128_t index_oid; if (rbox_get_oid_from_index(ctx->sync_view, seq1, ((struct rbox_mailbox *)box)->ext_id, &index_oid) >= 0) { const char *oid = guid_128_to_string(index_oid); @@ -273,30 +267,20 @@ static int rbox_sync_index(struct rbox_sync_context *ctx) { struct rbox_storage *r_storage = (struct rbox_storage *)box->storage; switch (sync_rec.type) { - case MAIL_INDEX_SYNC_TYPE_EXPUNGE: - i_debug("DELETE: rbox_sync_expunge!"); + case MAIL_INDEX_SYNC_TYPE_EXPUNGE: rbox_sync_expunge(ctx, seq1, seq2); break; case MAIL_INDEX_SYNC_TYPE_FLAGS: - if (is_alternate_storage_set(sync_rec.add_flags) && is_alternate_pool_valid(box)) { - // type = SDBOX_SYNC_ENTRY_TYPE_MOVE_TO_ALT; - // move object from mail_storage to apternative_storage. int ret = move_to_alt(ctx, seq1, seq2, false); -#ifdef DEBUG - i_debug("setting move to alt flag! %d", ret); -#endif if (ret < 0) { i_error("Error moving seq (%d) from alt storage", seq1); } - } else if (is_alternate_storage_set(sync_rec.remove_flags) && is_alternate_pool_valid(box)) { + } + else if (is_alternate_storage_set(sync_rec.remove_flags) && is_alternate_pool_valid(box)) { // type = SDBOX_SYNC_ENTRY_TYPE_MOVE_FROM_ALT; - int ret = move_to_alt(ctx, seq1, seq2, true); -#ifdef DEBUG - i_debug("Removing alt flag! %d", ret); -#endif if (ret < 0) { i_error("Error moving seq (%d) to alt storage", seq1); } @@ -388,8 +372,8 @@ int rbox_sync_begin(struct rbox_mailbox *rbox, struct rbox_sync_context **ctx_r, i_array_init(&ctx->expunged_items, 32); int ret = 0; - bool success = false; if (rebuild) { + bool success = false; for (int i = 0; i < RBOX_REBUILD_COUNT; i++) { /* do a full resync and try again. */ ret = rbox_storage_rebuild_in_context(rbox->storage, force_rebuild, true); @@ -579,6 +563,7 @@ int rbox_sync(struct rbox_mailbox *rbox, enum rbox_sync_flags flags) { struct mailbox_sync_context *rbox_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags) { FUNC_START(); + struct rbox_mailbox *rbox = (struct rbox_mailbox *)box; int ret = 0; @@ -587,30 +572,19 @@ struct mailbox_sync_context *rbox_storage_sync_init(struct mailbox *box, enum ma ret = -1; } } - + if (mail_index_reset_fscked(box->index)) { rbox_set_mailbox_corrupted(box); } + if (ret == 0 && (index_mailbox_want_full_sync(&rbox->box, flags) || rbox->storage->corrupted_rebuild_count != 0)) { uint8_t rbox_sync_flags = 0x0; if ((flags & MAILBOX_SYNC_FLAG_FORCE_RESYNC) != 0) { rbox_sync_flags |= RBOX_SYNC_FLAG_FORCE_REBUILD; -#ifdef DEBUG - i_debug("setting FORCE_REBUILD FLAG"); -#endif } -#ifdef DEBUG - else { - i_debug("FLAG DOES NOT HAVE FORCE_REBUILD FLAG"); - } -#endif ret = rbox_sync(rbox, static_cast(rbox_sync_flags)); } -#ifdef DEBUG - else { - i_debug("NO FLAG EVALUATION"); - } -#endif + FUNC_END(); return index_mailbox_sync_init(box, flags, ret < 0); } diff --git a/src/tests/it_test_storage_rbox_2 b/src/tests/it_test_storage_rbox_2 deleted file mode 100755 index d0dd4c4f..00000000 --- a/src/tests/it_test_storage_rbox_2 +++ /dev/null @@ -1,210 +0,0 @@ -#! /bin/bash - -# it_test_storage_rbox_2 - temporary wrapper script for .libs/it_test_storage_rbox_2 -# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-2 -# -# The it_test_storage_rbox_2 program cannot be directly executed until all the libtool -# libraries that it depends on are installed. -# -# This wrapper script should never be moved out of the build directory. -# If it is, it will not operate correctly. - -# Sed substitution that helps us do robust quoting. It backslashifies -# metacharacters that are still active within double-quoted strings. -sed_quote_subst='s|\([`"$\\]\)|\\\1|g' - -# Be Bourne compatible -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then - emulate sh - NULLCMD=: - # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac -fi -BIN_SH=xpg4; export BIN_SH # for Tru64 -DUALCASE=1; export DUALCASE # for MKS sh - -# The HP-UX ksh and POSIX shell print the target directory to stdout -# if CDPATH is set. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH - -relink_command="" - -# This environment variable determines our operation mode. -if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then - # install mode needs the following variables: - generated_by_libtool_version='2.4.6' - notinst_deplibs=' ../../src/storage-rbox/libstorage_rbox_plugin.la ../../src/librmb/librmb.la' -else - # When we are sourced in execute mode, $file and $ECHO are already set. - if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then - file="$0" - -# A function that is used when there is no print builtin or printf. -func_fallback_echo () -{ - eval 'cat <<_LTECHO_EOF -$1 -_LTECHO_EOF' -} - ECHO="printf %s\\n" - fi - -# Very basic option parsing. These options are (a) specific to -# the libtool wrapper, (b) are identical between the wrapper -# /script/ and the wrapper /executable/ that is used only on -# windows platforms, and (c) all begin with the string --lt- -# (application programs are unlikely to have options that match -# this pattern). -# -# There are only two supported options: --lt-debug and -# --lt-dump-script. There is, deliberately, no --lt-help. -# -# The first argument to this parsing function should be the -# script's ../../libtool value, followed by no. -lt_option_debug= -func_parse_lt_options () -{ - lt_script_arg0=$0 - shift - for lt_opt - do - case "$lt_opt" in - --lt-debug) lt_option_debug=1 ;; - --lt-dump-script) - lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'` - test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=. - lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'` - cat "$lt_dump_D/$lt_dump_F" - exit 0 - ;; - --lt-*) - $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2 - exit 1 - ;; - esac - done - - # Print the debug banner immediately: - if test -n "$lt_option_debug"; then - echo "it_test_storage_rbox_2:it_test_storage_rbox_2:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-2" 1>&2 - fi -} - -# Used when --lt-debug. Prints its arguments to stdout -# (redirection is the responsibility of the caller) -func_lt_dump_args () -{ - lt_dump_args_N=1; - for lt_arg - do - $ECHO "it_test_storage_rbox_2:it_test_storage_rbox_2:$LINENO: newargv[$lt_dump_args_N]: $lt_arg" - lt_dump_args_N=`expr $lt_dump_args_N + 1` - done -} - -# Core function for launching the target application -func_exec_program_core () -{ - - if test -n "$lt_option_debug"; then - $ECHO "it_test_storage_rbox_2:it_test_storage_rbox_2:$LINENO: newargv[0]: $progdir/$program" 1>&2 - func_lt_dump_args ${1+"$@"} 1>&2 - fi - exec "$progdir/$program" ${1+"$@"} - - $ECHO "$0: cannot exec $program $*" 1>&2 - exit 1 -} - -# A function to encapsulate launching the target application -# Strips options in the --lt-* namespace from $@ and -# launches target application with the remaining arguments. -func_exec_program () -{ - case " $* " in - *\ --lt-*) - for lt_wr_arg - do - case $lt_wr_arg in - --lt-*) ;; - *) set x "$@" "$lt_wr_arg"; shift;; - esac - shift - done ;; - esac - func_exec_program_core ${1+"$@"} -} - - # Parse options - func_parse_lt_options "$0" ${1+"$@"} - - # Find the directory that this script lives in. - thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'` - test "x$thisdir" = "x$file" && thisdir=. - - # Follow symbolic links until we get to the real thisdir. - file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'` - while test -n "$file"; do - destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'` - - # If there was a directory component, then change thisdir. - if test "x$destdir" != "x$file"; then - case "$destdir" in - [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;; - *) thisdir="$thisdir/$destdir" ;; - esac - fi - - file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'` - file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'` - done - - # Usually 'no', except on cygwin/mingw when embedded into - # the cwrapper. - WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no - if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then - # special case for '.' - if test "$thisdir" = "."; then - thisdir=`pwd` - fi - # remove .libs from thisdir - case "$thisdir" in - *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;; - .libs ) thisdir=. ;; - esac - fi - - # Try to get the absolute directory name. - absdir=`cd "$thisdir" && pwd` - test -n "$absdir" && thisdir="$absdir" - - program='it_test_storage_rbox_2' - progdir="$thisdir/.libs" - - - if test -f "$progdir/$program"; then - # Add our own library path to LD_LIBRARY_PATH - LD_LIBRARY_PATH="/repo/src/storage-rbox/.libs:/usr/local/lib/dovecot:/repo/src/librmb/.libs:$LD_LIBRARY_PATH" - - # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH - # The second colon is a workaround for a bug in BeOS R4 sed - LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'` - - export LD_LIBRARY_PATH - - if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then - # Run the actual program with our arguments. - func_exec_program ${1+"$@"} - fi - else - # The program doesn't exist. - $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2 - $ECHO "This script is just a wrapper for $program." 1>&2 - $ECHO "See the libtool documentation for more information." 1>&2 - exit 1 - fi -fi From 364e18da04297ca60af7c03cbc2dd713b230e01a Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Mon, 21 Nov 2022 14:17:42 +0100 Subject: [PATCH 07/18] Feature/349 ceph object index (#350) * #349: initial storage api * #349 doveadm rmb create ceph index force resync strategy * allow refresh of ceph object index via -r param * #349 save ceph index objects to separate pool * #349 poc impl ceph index done * #349 new config setting rbox_index_pool_name * #349 force reload of all objects, config setting defines if append of oid to ceph index happens * #349 add , to every entry or entry list * #349 add , to every entry or entry list, and test * repair invalid object not debug level log * #349 changed the remove function, initial index creation: append after each mailbox * #349 fix unit tests * #349 version --- CHANGELOG.md | 2 + configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 5 +- src/librmb/rados-cluster-impl.cpp | 27 +- src/librmb/rados-cluster-impl.h | 2 + src/librmb/rados-cluster.h | 6 + src/librmb/rados-dovecot-ceph-cfg-impl.h | 1 + src/librmb/rados-dovecot-ceph-cfg.h | 2 + src/librmb/rados-dovecot-config.cpp | 5 +- src/librmb/rados-dovecot-config.h | 4 +- src/librmb/rados-storage-impl.cpp | 79 ++- src/librmb/rados-storage-impl.h | 19 +- src/librmb/rados-storage.h | 69 +- src/librmb/rados-util.cpp | 593 +++++++++--------- src/librmb/rados-util.h | 5 + src/librmb/tools/rmb/rmb-commands.cpp | 20 + src/librmb/tools/rmb/rmb-commands.h | 4 + src/storage-rbox/doveadm-rbox-plugin.cpp | 169 ++++- src/storage-rbox/doveadm-rbox-plugin.h | 6 + src/storage-rbox/doveadm-rbox.c | 1 + src/storage-rbox/rbox-save.cpp | 9 +- src/storage-rbox/rbox-storage.cpp | 5 +- src/storage-rbox/rbox-sync-rebuild.cpp | 36 +- src/tests/mocks/mock_test.h | 16 + .../storage-mock-rbox/test_repair_rbox.cpp | 32 +- .../test_storage_mock_rbox.cpp | 12 +- .../test_storage_mock_rbox_bugs.cpp | 9 +- src/tests/test_repair_rbox | 210 +++++++ 28 files changed, 1028 insertions(+), 322 deletions(-) create mode 100755 src/tests/test_repair_rbox diff --git a/CHANGELOG.md b/CHANGELOG.md index f5ed27c3..434d9726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Change Log +## [0.0.44](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.44) (2022-11-21) +- #349 additional recovery method (ceph index object) ## [0.0.43](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.43) (2022-10-27) - #346 segmentation fault (rbox_copy) if rbox_mail is null diff --git a/configure.ac b/configure.ac index 225a523b..01d96dc6 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.43], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.44], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) AC_CONFIG_SRCDIR([src]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index f90c387f..00a660d5 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,10 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins - -Version: 0.0.43 - - +Version: 0.0.44 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/librmb/rados-cluster-impl.cpp b/src/librmb/rados-cluster-impl.cpp index 00820073..64d5347f 100644 --- a/src/librmb/rados-cluster-impl.cpp +++ b/src/librmb/rados-cluster-impl.cpp @@ -82,22 +82,17 @@ std::vector RadosClusterImpl::list_pgs_for_pool(std::string &pool_n std::cout << " is connected NO" << std::endl; connect(); } - - const string pool = "mail_storage"; + const string cmd = "{" "\"prefix\": \"pg ls-by-pool\", " - "\"poolstr\": \"" + pool + "\"" + "\"poolstr\": \"" + pool_name + "\"" "}"; - - std::cout << "cmd: " << cmd << std::endl; - + librados::bufferlist inbl; librados::bufferlist outbl; - int res = RadosClusterImpl::cluster->mon_command(cmd, inbl, &outbl, nullptr); - std::cout << "inbl command " << inbl <mon_command(cmd, inbl, &outbl, nullptr); + std::vector list = RadosUtils::extractPgs(std::string(outbl.c_str())); for (auto const &token: list) { @@ -227,6 +222,18 @@ int RadosClusterImpl::io_ctx_create(const string &pool, librados::IoCtx *io_ctx) } return ret; } +int RadosClusterImpl::recovery_index_io_ctx(const std::string &pool, + librados::IoCtx *io_ctx) { + if(!is_connected()) { + return -1; + } + // pool exists? else create + int ret = pool_create(pool); + if (ret == 0) { + ret = RadosClusterImpl::cluster->ioctx_create(pool.c_str(), *io_ctx); + } + return ret; +} int RadosClusterImpl::get_config_option(const char *option, string *value) { return RadosClusterImpl::cluster->conf_get(option, *value); diff --git a/src/librmb/rados-cluster-impl.h b/src/librmb/rados-cluster-impl.h index 8786e023..5fd57016 100644 --- a/src/librmb/rados-cluster-impl.h +++ b/src/librmb/rados-cluster-impl.h @@ -33,6 +33,8 @@ class RadosClusterImpl : public RadosCluster { int pool_create(const std::string &pool) override; int io_ctx_create(const std::string &pool, librados::IoCtx *io_ctx) override; + int recovery_index_io_ctx(const std::string &pool, librados::IoCtx *io_ctx) override; + int get_config_option(const char *option, std::string *value) override; int dictionary_create(const std::string &pool, const std::string &username, const std::string &oid, RadosDictionary **dictionary); diff --git a/src/librmb/rados-cluster.h b/src/librmb/rados-cluster.h index 60dbb79a..b57a10b4 100644 --- a/src/librmb/rados-cluster.h +++ b/src/librmb/rados-cluster.h @@ -60,6 +60,12 @@ class RadosCluster { * @return linux error code or 0 if successful * */ + + /** + * creates or returns the recovery index io ctx + */ + virtual int recovery_index_io_ctx(const std::string &pool,librados::IoCtx *io_ctx) = 0; + virtual int get_config_option(const char *option, std::string *value) = 0; /*! diff --git a/src/librmb/rados-dovecot-ceph-cfg-impl.h b/src/librmb/rados-dovecot-ceph-cfg-impl.h index 25a3e279..56358877 100644 --- a/src/librmb/rados-dovecot-ceph-cfg-impl.h +++ b/src/librmb/rados-dovecot-ceph-cfg-impl.h @@ -40,6 +40,7 @@ class RadosDovecotCephCfgImpl : public RadosDovecotCephCfg { int get_chunk_size() override { return std::stoi(dovecot_cfg.get_chunk_size());} std::string &get_pool_name() override { return dovecot_cfg.get_pool_name(); } + std::string &get_index_pool_name() override { return dovecot_cfg.get_index_pool_name(); }; std::string &get_key_prefix_keywords() override { return dovecot_cfg.get_key_prefix_keywords(); } void update_metadata(const std::string &key, const char *value_) override { diff --git a/src/librmb/rados-dovecot-ceph-cfg.h b/src/librmb/rados-dovecot-ceph-cfg.h index c73bf0c4..4d6e0b7b 100644 --- a/src/librmb/rados-dovecot-ceph-cfg.h +++ b/src/librmb/rados-dovecot-ceph-cfg.h @@ -58,6 +58,8 @@ class RadosDovecotCephCfg { virtual std::map *get_config() = 0; virtual std::string &get_pool_name() = 0; + virtual std::string &get_index_pool_name() = 0; + virtual bool is_update_attributes() = 0; virtual void set_rbox_cfg_object_name(const std::string &value) = 0; diff --git a/src/librmb/rados-dovecot-config.cpp b/src/librmb/rados-dovecot-config.cpp index 874df8eb..81f84a08 100644 --- a/src/librmb/rados-dovecot-config.cpp +++ b/src/librmb/rados-dovecot-config.cpp @@ -19,6 +19,7 @@ namespace librmb { RadosConfig::RadosConfig() : pool_name("rbox_pool_name"), + index_pool_name("rbox_index_pool_name"), rbox_cfg_object_name("rbox_cfg_object_name"), rbox_cluster_name("rbox_cluster_name"), rados_username("rados_user_name"), @@ -34,7 +35,7 @@ RadosConfig::RadosConfig() rbox_object_search_threads("rbox_object_search_threads") { config[pool_name] = "mail_storage"; - + config[index_pool_name] = "object_recovery"; config[rbox_cfg_object_name] = "rbox_cfg"; config[rbox_cluster_name] = "ceph"; config[rados_username] = "client.admin"; @@ -75,6 +76,8 @@ void RadosConfig::update_metadata(const std::string &key, const char *value_) { std::string RadosConfig::to_string() { std::stringstream ss; ss << "Dovecot configuration: (90-plugin.conf)" << std::endl; + ss << " " << pool_name << "=" << config[pool_name] << std::endl; + ss << " " << index_pool_name << "=" << config[index_pool_name] << std::endl; ss << " " << rbox_cfg_object_name << "=" << config[rbox_cfg_object_name] << std::endl; ss << " " << rbox_cluster_name << "=" << config[rbox_cluster_name] << std::endl; ss << " " << rados_username << "=" << config[rados_username] << std::endl; diff --git a/src/librmb/rados-dovecot-config.h b/src/librmb/rados-dovecot-config.h index 89900a5e..a9785911 100644 --- a/src/librmb/rados-dovecot-config.h +++ b/src/librmb/rados-dovecot-config.h @@ -36,6 +36,8 @@ class RadosConfig { std::map *get_config() { return &config; } std::string &get_pool_name() { return config[pool_name]; } + std::string &get_index_pool_name() { return config[index_pool_name]; }; + const std::string &get_rados_save_log_file() { return config[save_log]; } bool is_config_valid() { return is_valid; } void set_config_valid(bool is_valid_) { this->is_valid = is_valid_; } @@ -79,7 +81,7 @@ class RadosConfig { private: std::map config; std::string pool_name; - + std::string index_pool_name; std::string rbox_cfg_object_name; std::string rbox_cluster_name; std::string rados_username; diff --git a/src/librmb/rados-storage-impl.cpp b/src/librmb/rados-storage-impl.cpp index ce13958b..37e57274 100644 --- a/src/librmb/rados-storage-impl.cpp +++ b/src/librmb/rados-storage-impl.cpp @@ -221,7 +221,7 @@ std::set RadosStorageImpl::find_mails_async(const RadosMetadata *at uint32_t pseed; int r = sscanf(pg.c_str(), "%llu.%x", (long long unsigned *)&ppool, &pseed); - librados::NObjectIterator iter= io_ctx->nobjects_begin(pseed, filter); + librados::NObjectIterator iter= io_ctx->nobjects_begin(pseed); int count = 0; while (iter != librados::NObjectIterator::__EndObjectIterator) { std::string oid = iter->get_oid(); @@ -277,8 +277,10 @@ std::set RadosStorageImpl::find_mails_async(const RadosMetadata *at return oid_list; } librados::IoCtx &RadosStorageImpl::get_io_ctx() { return io_ctx; } +librados::IoCtx &RadosStorageImpl::get_recovery_io_ctx() { return recovery_io_ctx; } -int RadosStorageImpl::open_connection(const std::string &poolname, const std::string &clustername, +int RadosStorageImpl::open_connection(const std::string &poolname, const std::string &index_pool, + const std::string &clustername, const std::string &rados_username) { if (cluster->is_connected() && io_ctx_created) { // cluster is already connected! @@ -288,22 +290,46 @@ int RadosStorageImpl::open_connection(const std::string &poolname, const std::st if (cluster->init(clustername, rados_username) < 0) { return -1; } - return create_connection(poolname); + return create_connection(poolname, index_pool); +} +int RadosStorageImpl::open_connection(const std::string &poolname, + const std::string &clustername, + const std::string &rados_username) { + if (cluster->is_connected() && io_ctx_created) { + // cluster is already connected! + return 1; + } + + if (cluster->init(clustername, rados_username) < 0) { + return -1; + } + return create_connection(poolname, poolname); +} +int RadosStorageImpl::open_connection(const string &poolname, const string &index_pool) { + if (cluster->init() < 0) { + return -1; + } + return create_connection(poolname, index_pool); } int RadosStorageImpl::open_connection(const string &poolname) { if (cluster->init() < 0) { return -1; } - return create_connection(poolname); + return create_connection(poolname, poolname); } -int RadosStorageImpl::create_connection(const std::string &poolname) { +int RadosStorageImpl::create_connection(const std::string &poolname, const std::string &index_pool){ // pool exists? else create int err = cluster->io_ctx_create(poolname, &io_ctx); if (err < 0) { return err; } + + err = cluster->recovery_index_io_ctx(index_pool, &recovery_io_ctx); + if (err < 0) { + return err; + } string max_write_size_str; err = cluster->get_config_option(RadosStorageImpl::CFG_OSD_MAX_WRITE_SIZE, &max_write_size_str); if (err < 0) { @@ -540,10 +566,53 @@ bool RadosStorageImpl::save_mail(RadosMail *mail) { } return save_mail(&write_op_xattr, mail); } + librmb::RadosMail *RadosStorageImpl::alloc_rados_mail() { return new librmb::RadosMail(); } + void RadosStorageImpl::free_rados_mail(librmb::RadosMail *mail) { if (mail != nullptr) { delete mail; mail = nullptr; } } + +int RadosStorageImpl::ceph_index_append(const std::string &oid) { + librados::bufferlist bl; + bl.append(RadosUtils::convert_to_ceph_index(oid)); + return get_recovery_io_ctx().append( get_namespace(),bl, bl.length()); +} + +int RadosStorageImpl::ceph_index_append(const std::set &oids) { + librados::bufferlist bl; + bl.append(RadosUtils::convert_to_ceph_index(oids)); + return get_recovery_io_ctx().append( get_namespace(),bl, bl.length()); +} +int RadosStorageImpl::ceph_index_overwrite(const std::set &oids) { + librados::bufferlist bl; + bl.append(RadosUtils::convert_to_ceph_index(oids)); + return get_recovery_io_ctx().write_full( get_namespace(),bl); +} +std::set RadosStorageImpl::ceph_index_read() { + std::set index; + librados::bufferlist bl; + size_t max = INT_MAX; + int64_t psize; + time_t pmtime; + get_recovery_io_ctx().stat(get_namespace(), &psize, &pmtime); + if(psize <=0){ + return index; + } + //std::cout << " NAMESPACE: " << get_namespace() << " exist? " << exist << " size : " << psize << std::endl; + int ret = get_recovery_io_ctx().read(get_namespace(),bl, max,0); + + + if(ret < 0){ + return index; + } + index = RadosUtils::ceph_index_to_set(bl.c_str()); + return index; +} +int RadosStorageImpl::ceph_index_delete() { + return get_recovery_io_ctx().remove(get_namespace()); +} + diff --git a/src/librmb/rados-storage-impl.h b/src/librmb/rados-storage-impl.h index 17c37cd8..11ab2856 100644 --- a/src/librmb/rados-storage-impl.h +++ b/src/librmb/rados-storage-impl.h @@ -33,6 +33,8 @@ class RadosStorageImpl : public RadosStorage { virtual ~RadosStorageImpl(); librados::IoCtx &get_io_ctx() override; + librados::IoCtx &get_recovery_io_ctx() override; + int stat_mail(const std::string &oid, uint64_t *psize, time_t *pmtime) override; void set_namespace(const std::string &_nspace) override; std::string get_namespace() override { return nspace; } @@ -56,6 +58,12 @@ class RadosStorageImpl : public RadosStorage { std::set find_mails_async(const RadosMetadata *attr, std::string &pool_name, int num_threads, void (*ptr)(std::string&)) override; int open_connection(const std::string &poolname) override; + int open_connection(const std::string &poolname, const std::string &index_pool) override; + + + int open_connection(const std::string &poolname, const std::string &index_pool, + const std::string &clustername, + const std::string &rados_username) override; int open_connection(const std::string &poolname, const std::string &clustername, const std::string &rados_username) override; void close_connection() override; @@ -77,9 +85,14 @@ class RadosStorageImpl : public RadosStorage { void free_rados_mail(librmb::RadosMail *mail) override; - + int ceph_index_append(const std::string &oid) override; + int ceph_index_append(const std::set &oids) override; + int ceph_index_overwrite(const std::set &oids) override; + std::set ceph_index_read() override; + int ceph_index_delete() override; + private: - int create_connection(const std::string &poolname); + int create_connection(const std::string &poolname,const std::string &index_pool); private: RadosCluster *cluster; @@ -87,6 +100,8 @@ class RadosStorageImpl : public RadosStorage { int max_object_size; std::string nspace; librados::IoCtx io_ctx; + librados::IoCtx recovery_io_ctx; + bool io_ctx_created; std::string pool_name; enum rbox_ceph_aio_wait_method wait_method; diff --git a/src/librmb/rados-storage.h b/src/librmb/rados-storage.h index 6a1ade87..c3d36fe9 100644 --- a/src/librmb/rados-storage.h +++ b/src/librmb/rados-storage.h @@ -35,6 +35,13 @@ class RadosStorage { * if connected, return the valid ioCtx */ virtual librados::IoCtx &get_io_ctx() = 0; + + /*! + * if connected, return the valid ioCtx for recovery index + */ + virtual librados::IoCtx &get_recovery_io_ctx() = 0; + + /*! get the object size and object save date * @param[in] oid unique ident for the object * @param[out] psize size of the object @@ -49,7 +56,19 @@ class RadosStorage { * */ virtual std::string get_namespace() = 0; /*! get the pool name - * @return copy of the current pool name + * @return copy of the current p + +TEST_F(StorageTest, scanForPg) { + +librmbtest::RadosClusterMock mock_test = librmbtest::RadosClusterMock(); +mock. +librmb::RadosStorageImpl underTest = librmbtest::RadosStorageImpl(mock_test); + +underTest.ceph_index_append() +underTest.ceph_index_add("dkfkjdf") + +} + ool name * */ virtual std::string get_pool_name() = 0; @@ -126,6 +145,25 @@ class RadosStorage { * */ virtual int open_connection(const std::string &poolname, const std::string &clustername, const std::string &rados_username) = 0; + + /*! open the rados connections with default cluster and username + * @param[in] poolname the poolname to connect to, in case this one does not exists, it will be created. + * @param[in] index_pool the poolname to store recovery index objects to. + * */ + virtual int open_connection(const std::string &poolname, const std::string &index_pool) = 0; + + /*! open the rados connection with given user and clustername + * + * @param[in] poolname the poolname to connect to, in case this one does not exists, it will be created. + * @param[in] index_pool the poolname to store recovery index objects to. + * @param[in] clustername custom clustername + * @param[in] rados_username custom username (client.xxx) + * + * @return linux error code or 0 if successful. + * */ + virtual int open_connection(const std::string &poolname, const std::string &index_pool, + const std::string &clustername, + const std::string &rados_username) = 0; /*! * close the connection. (clean up structures to allow reconnect) */ @@ -154,6 +192,35 @@ class RadosStorage { * @return linux errorcode or 0 if successful * */ virtual int save_mail(const std::string &oid, librados::bufferlist &buffer) = 0; + + /** + * append oid to index object + */ + virtual int ceph_index_append(const std::string &oid) = 0; + + /** + * append oids to index object + */ + virtual int ceph_index_append(const std::set &oids) = 0; + + /** + * overwrite ceph index object + */ + virtual int ceph_index_overwrite(const std::set &oids) = 0; + + /** + * get the ceph index object as list of oids + * 32 + */ + virtual std::set ceph_index_read() = 0; + + + /** + * remove oids from index object + */ + virtual int ceph_index_delete() = 0; + + /*! read the complete mail object into bufferlist * * @param[in] oid unique object identifier diff --git a/src/librmb/rados-util.cpp b/src/librmb/rados-util.cpp index be637ab6..2668bbb9 100644 --- a/src/librmb/rados-util.cpp +++ b/src/librmb/rados-util.cpp @@ -26,342 +26,367 @@ namespace librmb { -RadosUtils::RadosUtils() {} + RadosUtils::RadosUtils() {} -RadosUtils::~RadosUtils() {} + RadosUtils::~RadosUtils() {} -bool RadosUtils::convert_str_to_time_t(const std::string &date, time_t *val) { - struct tm tm = {0}; - if (strptime(date.c_str(), "%Y-%m-%d %H:%M:%S", &tm)) { - tm.tm_isdst = -1; - time_t t = mktime(&tm); - *val = t; - return true; - } - *val = 0; - return false; -} - -bool RadosUtils::convert_string_to_date(const std::string &date_string, std::string *date) { - time_t t; - if (convert_str_to_time_t(date_string, &t)) { - *date = std::to_string(t); - return true; - } - return false; -} - -bool RadosUtils::is_numeric(const char *s) { - if (s == NULL) { + bool RadosUtils::convert_str_to_time_t(const std::string &date, time_t *val) { + struct tm tm = {0}; + if (strptime(date.c_str(), "%Y-%m-%d %H:%M:%S", &tm)) { + tm.tm_isdst = -1; + time_t t = mktime(&tm); + *val = t; + return true; + } + *val = 0; return false; } - bool is_numeric = true; - int len = strlen(s); - for (int i = 0; i < len; i++) { - if (!std::isdigit(s[i])) { - is_numeric = false; - break; + + bool RadosUtils::convert_string_to_date(const std::string &date_string, std::string *date) { + time_t t; + if (convert_str_to_time_t(date_string, &t)) { + *date = std::to_string(t); + return true; } + return false; } - return is_numeric; -} - -bool RadosUtils::is_date_attribute(const rbox_metadata_key &key) { - return (key == RBOX_METADATA_OLDV1_SAVE_TIME || key == RBOX_METADATA_RECEIVED_TIME); -} + bool RadosUtils::is_numeric(const char *s) { + if (s == NULL) { + return false; + } + bool is_numeric = true; + int len = strlen(s); + for (int i = 0; i < len; i++) { + if (!std::isdigit(s[i])) { + is_numeric = false; + break; + } + } -int RadosUtils::convert_time_t_to_str(const time_t &t, std::string *ret_val) { - char buffer[256]; - if (t == -1) { - *ret_val = "invalid date"; - return -1; + return is_numeric; } - struct tm timeinfo; - localtime_r(&t, &timeinfo); - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo); - *ret_val = std::string(buffer); - return 0; -} -bool RadosUtils::flags_to_string(const uint8_t &flags_, std::string *flags_str) { - std::stringstream sstream; - sstream << std::hex << flags_; - sstream >> *flags_str; - return true; -} - -bool RadosUtils::string_to_flags(const std::string &flags_, uint8_t *flags) { - std::istringstream in(flags_); - - if (in >> std::hex >> *flags) { - return true; + + bool RadosUtils::is_date_attribute(const rbox_metadata_key &key) { + return (key == RBOX_METADATA_OLDV1_SAVE_TIME || key == RBOX_METADATA_RECEIVED_TIME); } - return false; -} -void RadosUtils::find_and_replace(std::string *source, std::string const &find, std::string const &replace) { - for (std::string::size_type i = 0; source != nullptr && (i = source->find(find, i)) != std::string::npos;) { - source->replace(i, find.length(), replace); - i += replace.length(); + int RadosUtils::convert_time_t_to_str(const time_t &t, std::string *ret_val) { + char buffer[256]; + if (t == -1) { + *ret_val = "invalid date"; + return -1; + } + struct tm timeinfo; + localtime_r(&t, &timeinfo); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo); + *ret_val = std::string(buffer); + return 0; } -} - -int RadosUtils::get_all_keys_and_values(librados::IoCtx *io_ctx, const std::string &oid, - std::map *kv_map) { - int err = 0; - librados::ObjectReadOperation first_read; - std::set extended_keys; -#ifdef DOVECOT_CEPH_PLUGIN_HAVE_OMAP_GET_KEYS_2 - first_read.omap_get_keys2("", LONG_MAX, &extended_keys, nullptr, &err); -#else - first_read.omap_get_keys("", LONG_MAX, &extended_keys, &err); -#endif - int ret = io_ctx->operate(oid.c_str(), &first_read, NULL); - if (ret < 0) { - return ret; + bool RadosUtils::flags_to_string(const uint8_t &flags_, std::string *flags_str) { + std::stringstream sstream; + sstream << std::hex << flags_; + sstream >> *flags_str; + return true; } - return io_ctx->omap_get_vals_by_keys(oid, extended_keys, kv_map); -} -void RadosUtils::resolve_flags(const uint8_t &flags, std::string *flat) { - std::stringbuf buf; - std::ostream os(&buf); + bool RadosUtils::string_to_flags(const std::string &flags_, uint8_t *flags) { + std::istringstream in(flags_); - if ((flags & 0x01) != 0) { - os << "\\Answered "; - } - if ((flags & 0x02) != 0) { - os << "\\Flagged "; - } - if ((flags & 0x04) != 0) { - os << "\\Deleted "; + if (in >> std::hex >> *flags) { + return true; + } + return false; } - if ((flags & 0x08) != 0) { - os << "\\Seen "; + + void RadosUtils::find_and_replace(std::string *source, std::string const &find, std::string const &replace) { + for (std::string::size_type i = 0; source != nullptr && (i = source->find(find, i)) != std::string::npos;) { + source->replace(i, find.length(), replace); + i += replace.length(); + } } - if ((flags & 0x10) != 0) { - os << "\\Draft "; + + int RadosUtils::get_all_keys_and_values(librados::IoCtx *io_ctx, const std::string &oid, + std::map *kv_map) { + int err = 0; + librados::ObjectReadOperation first_read; + std::set extended_keys; + #ifdef DOVECOT_CEPH_PLUGIN_HAVE_OMAP_GET_KEYS_2 + first_read.omap_get_keys2("", LONG_MAX, &extended_keys, nullptr, &err); + #else + first_read.omap_get_keys("", LONG_MAX, &extended_keys, &err); + #endif + int ret = io_ctx->operate(oid.c_str(), &first_read, NULL); + if (ret < 0) { + return ret; + } + return io_ctx->omap_get_vals_by_keys(oid, extended_keys, kv_map); } - if ((flags & 0x20) != 0) { - os << "\\Recent "; + + void RadosUtils::resolve_flags(const uint8_t &flags, std::string *flat) { + std::stringbuf buf; + std::ostream os(&buf); + + if ((flags & 0x01) != 0) { + os << "\\Answered "; + } + if ((flags & 0x02) != 0) { + os << "\\Flagged "; + } + if ((flags & 0x04) != 0) { + os << "\\Deleted "; + } + if ((flags & 0x08) != 0) { + os << "\\Seen "; + } + if ((flags & 0x10) != 0) { + os << "\\Draft "; + } + if ((flags & 0x20) != 0) { + os << "\\Recent "; + } + *flat = buf.str(); } - *flat = buf.str(); -} -int RadosUtils::osd_add(librados::IoCtx *ioctx, const std::string &oid, const std::string &key, - long long value_to_add) { - librados::bufferlist in, out; - encode(key, in); + int RadosUtils::osd_add(librados::IoCtx *ioctx, const std::string &oid, const std::string &key, + long long value_to_add) { + librados::bufferlist in, out; + encode(key, in); - std::stringstream stream; - stream << value_to_add; + std::stringstream stream; + stream << value_to_add; - encode(stream.str(), in); + encode(stream.str(), in); - return ioctx->exec(oid, "numops", "add", in, out); -} + return ioctx->exec(oid, "numops", "add", in, out); + } -int RadosUtils::osd_sub(librados::IoCtx *ioctx, const std::string &oid, const std::string &key, - long long value_to_subtract) { - return osd_add(ioctx, oid, key, -value_to_subtract); -} + int RadosUtils::osd_sub(librados::IoCtx *ioctx, const std::string &oid, const std::string &key, + long long value_to_subtract) { + return osd_add(ioctx, oid, key, -value_to_subtract); + } -/*! - * @return reference to all write operations related with this object - */ + /*! + * @return reference to all write operations related with this object + */ -void RadosUtils::get_metadata(const std::string &key, std::map *metadata, char **value) { - if (metadata->find(key) != metadata->end()) { - *value = (*metadata)[key].c_str(); - return; + void RadosUtils::get_metadata(const std::string &key, std::map *metadata, char **value) { + if (metadata->find(key) != metadata->end()) { + *value = (*metadata)[key].c_str(); + return; + } + *value = NULL; } - *value = NULL; -} -void RadosUtils::get_metadata(rbox_metadata_key key, std::map *metadata, char **value) { - string str_key(librmb::rbox_metadata_key_to_char(key)); - get_metadata(str_key, metadata, value); -} -bool RadosUtils::is_numeric_optional(const char *text) { - if (text == NULL) { - return true; // optional + void RadosUtils::get_metadata(rbox_metadata_key key, std::map *metadata, char **value) { + string str_key(librmb::rbox_metadata_key_to_char(key)); + get_metadata(str_key, metadata, value); } - return is_numeric(text); -} - -bool RadosUtils::validate_metadata(map *metadata) { - char *uid = NULL; - get_metadata(RBOX_METADATA_MAIL_UID, metadata, &uid); - char *recv_time_str = NULL; - get_metadata(RBOX_METADATA_RECEIVED_TIME, metadata, &recv_time_str); - char *p_size = NULL; - get_metadata(RBOX_METADATA_PHYSICAL_SIZE, metadata, &p_size); - char *v_size = NULL; - get_metadata(RBOX_METADATA_VIRTUAL_SIZE, metadata, &v_size); - - char *rbox_version; - get_metadata(RBOX_METADATA_VERSION, metadata, &rbox_version); - char *mailbox_guid = NULL; - get_metadata(RBOX_METADATA_MAILBOX_GUID, metadata, &mailbox_guid); - char *mail_guid = NULL; - get_metadata(RBOX_METADATA_GUID, metadata, &mail_guid); - char *mb_orig_name = NULL; - get_metadata(RBOX_METADATA_ORIG_MAILBOX, metadata, &mb_orig_name); - - char *flags = NULL; - get_metadata(RBOX_METADATA_OLDV1_FLAGS, metadata, &flags); - char *pvt_flags = NULL; - get_metadata(RBOX_METADATA_PVT_FLAGS, metadata, &pvt_flags); - char *from_envelope = NULL; - get_metadata(RBOX_METADATA_FROM_ENVELOPE, metadata, &from_envelope); - - int test = 0; - test += is_numeric(uid) ? 0 : 1; - test += is_numeric(recv_time_str) ? 0 : 1; - test += is_numeric(p_size) ? 0 : 1; - test += is_numeric(v_size) ? 0 : 1; - - test += is_numeric_optional(flags) ? 0 : 1; - test += is_numeric_optional(pvt_flags) ? 0 : 1; - - test += mailbox_guid == NULL ? 1 : 0; - test += mail_guid == NULL ? 1 : 0; - return test == 0; -} -// assumes that destination is open and initialized with uses namespace -int RadosUtils::move_to_alt(std::string &oid, RadosStorage *primary, RadosStorage *alt_storage, - RadosMetadataStorage *metadata, bool inverse) { - int ret = -1; - ret = copy_to_alt(oid, oid, primary, alt_storage, metadata, inverse); - if (ret > 0) { - if (inverse) { - ret = alt_storage->get_io_ctx().remove(oid); - } else { - ret = primary->get_io_ctx().remove(oid); + bool RadosUtils::is_numeric_optional(const char *text) { + if (text == NULL) { + return true; // optional } + return is_numeric(text); } - return ret; -} -int RadosUtils::copy_to_alt(std::string &src_oid, std::string &dest_oid, RadosStorage *primary, - RadosStorage *alt_storage, RadosMetadataStorage *metadata, bool inverse) { - int ret = 0; - - // TODO(jrse) check that storage is connected and open. - if (primary == nullptr || alt_storage == nullptr) { - return 0; + + bool RadosUtils::validate_metadata(map *metadata) { + char *uid = NULL; + get_metadata(RBOX_METADATA_MAIL_UID, metadata, &uid); + char *recv_time_str = NULL; + get_metadata(RBOX_METADATA_RECEIVED_TIME, metadata, &recv_time_str); + char *p_size = NULL; + get_metadata(RBOX_METADATA_PHYSICAL_SIZE, metadata, &p_size); + char *v_size = NULL; + get_metadata(RBOX_METADATA_VIRTUAL_SIZE, metadata, &v_size); + + char *rbox_version; + get_metadata(RBOX_METADATA_VERSION, metadata, &rbox_version); + char *mailbox_guid = NULL; + get_metadata(RBOX_METADATA_MAILBOX_GUID, metadata, &mailbox_guid); + char *mail_guid = NULL; + get_metadata(RBOX_METADATA_GUID, metadata, &mail_guid); + char *mb_orig_name = NULL; + get_metadata(RBOX_METADATA_ORIG_MAILBOX, metadata, &mb_orig_name); + + char *flags = NULL; + get_metadata(RBOX_METADATA_OLDV1_FLAGS, metadata, &flags); + char *pvt_flags = NULL; + get_metadata(RBOX_METADATA_PVT_FLAGS, metadata, &pvt_flags); + char *from_envelope = NULL; + get_metadata(RBOX_METADATA_FROM_ENVELOPE, metadata, &from_envelope); + + int test = 0; + test += is_numeric(uid) ? 0 : 1; + test += is_numeric(recv_time_str) ? 0 : 1; + test += is_numeric(p_size) ? 0 : 1; + test += is_numeric(v_size) ? 0 : 1; + + test += is_numeric_optional(flags) ? 0 : 1; + test += is_numeric_optional(pvt_flags) ? 0 : 1; + + test += mailbox_guid == NULL ? 1 : 0; + test += mail_guid == NULL ? 1 : 0; + return test == 0; } + // assumes that destination is open and initialized with uses namespace + int RadosUtils::move_to_alt(std::string &oid, RadosStorage *primary, RadosStorage *alt_storage, + RadosMetadataStorage *metadata, bool inverse) { + int ret = -1; + ret = copy_to_alt(oid, oid, primary, alt_storage, metadata, inverse); + if (ret > 0) { + if (inverse) { + ret = alt_storage->get_io_ctx().remove(oid); + } else { + ret = primary->get_io_ctx().remove(oid); + } + } + return ret; + } + int RadosUtils::copy_to_alt(std::string &src_oid, std::string &dest_oid, RadosStorage *primary, + RadosStorage *alt_storage, RadosMetadataStorage *metadata, bool inverse) { + int ret = 0; - RadosMail mail; - mail.set_oid(src_oid); + // TODO(jrse) check that storage is connected and open. + if (primary == nullptr || alt_storage == nullptr) { + return 0; + } - librados::bufferlist *bl = new librados::bufferlist(); - mail.set_mail_buffer(bl); + RadosMail mail; + mail.set_oid(src_oid); - if (inverse) { - ret = alt_storage->read_mail(src_oid, mail.get_mail_buffer()); - metadata->get_storage()->set_io_ctx(&alt_storage->get_io_ctx()); - } else { - ret = primary->read_mail(src_oid, mail.get_mail_buffer()); - } + librados::bufferlist *bl = new librados::bufferlist(); + mail.set_mail_buffer(bl); - if (ret < 0) { - metadata->get_storage()->set_io_ctx(&primary->get_io_ctx()); - return ret; - } - mail.set_mail_size(mail.get_mail_buffer()->length()); + if (inverse) { + ret = alt_storage->read_mail(src_oid, mail.get_mail_buffer()); + metadata->get_storage()->set_io_ctx(&alt_storage->get_io_ctx()); + } else { + ret = primary->read_mail(src_oid, mail.get_mail_buffer()); + } - // load the metadata; - ret = metadata->get_storage()->load_metadata(&mail); - if (ret < 0) { - return ret; - } + if (ret < 0) { + metadata->get_storage()->set_io_ctx(&primary->get_io_ctx()); + return ret; + } + mail.set_mail_size(mail.get_mail_buffer()->length()); + + // load the metadata; + ret = metadata->get_storage()->load_metadata(&mail); + if (ret < 0) { + return ret; + } + + mail.set_oid(dest_oid); - mail.set_oid(dest_oid); + librados::ObjectWriteOperation write_op; // = new librados::ObjectWriteOperation(); + metadata->get_storage()->save_metadata(&write_op, &mail); - librados::ObjectWriteOperation write_op; // = new librados::ObjectWriteOperation(); - metadata->get_storage()->save_metadata(&write_op, &mail); + bool success; + if (inverse) { + success = primary->save_mail(&write_op, &mail); + } else { + success = alt_storage->save_mail(&write_op, &mail); + } + + if (!success) { + return 0; + } - bool success; - if (inverse) { - success = primary->save_mail(&write_op, &mail); - } else { - success = alt_storage->save_mail(&write_op, &mail); + return success ? 0 : 1; } - if (!success) { - return 0; + static std::vector RadosUtils::extractPgs(const std::string& str) + { + std::vector tokens; + + std::stringstream ss(str); + std::string token; + while (std::getline(ss, token, '\n')) { + std::string pgs = token.substr(0, 10); //take first 10 chars for pgids + // trim the result. + pgs.erase(std::remove_if(pgs.begin(), pgs.end(), ::isspace), pgs.end()); + tokens.push_back(pgs); + } + //skip first line (header) + tokens.erase(tokens.begin()); + //remove last element (footer) + tokens.pop_back(); + + return tokens; } - return success ? 0 : 1; -} + static std::map> RadosUtils::extractPgAndPrimaryOsd(const std::string& str) + { + std::map> tokens; + + std::stringstream ss(str); + std::string token; + bool first_line = true; + while (std::getline(ss, token, '\n')) { + if(first_line){ + first_line = false; + continue; + } + std::vector line = split(token,' '); + if(line.size() < 14){ + continue; + } + std::string tmp_primary_osd = split(line[13],',')[0]; + std::string primary_osd = tmp_primary_osd.erase(0,1); + std::string pgs = line[0]; + + auto it = tokens.find(primary_osd); + if(it!=tokens.end()){ + tokens[primary_osd].push_back(pgs); + }else{ + std::vector t; + t.push_back(pgs); + tokens.insert({primary_osd,t}); + } + + } + + return tokens; + } -static std::vector RadosUtils::extractPgs(const std::string& str) -{ + static std::vector RadosUtils::split(std::string str_to_split, char delimiter) { std::vector tokens; - - std::stringstream ss(str); + std::stringstream stream(str_to_split); std::string token; - while (std::getline(ss, token, '\n')) { - std::string pgs = token.substr(0, 10); //take first 10 chars for pgids - // trim the result. - pgs.erase(std::remove_if(pgs.begin(), pgs.end(), ::isspace), pgs.end()); - tokens.push_back(pgs); - } - //skip first line (header) - tokens.erase(tokens.begin()); - //remove last element (footer) - tokens.pop_back(); - - return tokens; -} - -static std::map> RadosUtils::extractPgAndPrimaryOsd(const std::string& str) -{ - std::map> tokens; - std::stringstream ss(str); - std::string token; - bool first_line = true; - while (std::getline(ss, token, '\n')) { - if(first_line){ - first_line = false; - continue; - } - std::vector line = split(token,' '); - if(line.size() < 14){ - continue; - } - std::string tmp_primary_osd = split(line[13],',')[0]; - std::string primary_osd = tmp_primary_osd.erase(0,1); - std::string pgs = line[0]; - - auto it = tokens.find(primary_osd); - if(it!=tokens.end()){ - tokens[primary_osd].push_back(pgs); - }else{ - std::vector t; - t.push_back(pgs); - tokens.insert({primary_osd,t}); - } - + while(getline(stream, token, delimiter)) { + token.erase(std::remove_if(token.begin(), token.end(), ::isspace), token.end()); + if(token.length() > 0) { + tokens.push_back(token); + } + } - + return tokens; -} + } -static std::vector RadosUtils::split(std::string str_to_split, char delimiter) { - std::vector tokens; - std::stringstream stream(str_to_split); - std::string token; + static std::string RadosUtils::convert_to_ceph_index(const std::set &list){ + std::ostringstream str; + std::copy(list.begin(), list.end(), std::ostream_iterator(str, ",")); + return str.str(); + } - while(getline(stream, token, delimiter)) { - token.erase(std::remove_if(token.begin(), token.end(), ::isspace), token.end()); - if(token.length() > 0) { - tokens.push_back(token); - } - + static std::string RadosUtils::convert_to_ceph_index(const std::string &str) { + return str + ","; } - return tokens; -} + static std::set RadosUtils::ceph_index_to_set(const std::string &str) { + std::set index; + std::stringstream ss(str); + while (ss.good()) { + std::string substr; + getline(ss, substr, ','); + + if(substr.length() != 0){ + index.insert(substr); + } + + } + return index; + } } // namespace librmb diff --git a/src/librmb/rados-util.h b/src/librmb/rados-util.h index e6b019dc..b3ae010f 100644 --- a/src/librmb/rados-util.h +++ b/src/librmb/rados-util.h @@ -196,6 +196,11 @@ class RadosUtils { static std::vector split(std::string str_to_split, char delimiter); + + static std::string convert_to_ceph_index(const std::set &list); + static std::string convert_to_ceph_index(const std::string &str); + + static std::set ceph_index_to_set(const std::string &str); }; } // namespace librmb diff --git a/src/librmb/tools/rmb/rmb-commands.cpp b/src/librmb/tools/rmb/rmb-commands.cpp index d0b28ad2..4bfb3def 100644 --- a/src/librmb/tools/rmb/rmb-commands.cpp +++ b/src/librmb/tools/rmb/rmb-commands.cpp @@ -423,6 +423,26 @@ static void aio_cb(rados_completion_t cb, void *arg) { stat->mail_objects->push_back(stat->mail); delete stat; } + +int RmbCommands::overwrite_ceph_object_index(std::set &mail_oids){ + return storage->ceph_index_overwrite(mail_oids); +} +std::set RmbCommands::load_objects(){ + std::set mail_list; + librados::NObjectIterator iter_guid =storage->find_mails(nullptr); + while (iter_guid != librados::NObjectIterator::__EndObjectIterator) { + mail_list.insert((*iter_guid).get_oid()); + iter_guid++; + } + return mail_list; +} +int RmbCommands::remove_ceph_object_index(){ + return storage->ceph_index_delete(); +} +int RmbCommands::append_ceph_object_index(const std::set &mail_oids){ + return storage->ceph_index_append(mail_oids); +} + int RmbCommands::load_objects(librmb::RadosStorageMetadataModule *ms, std::list &mail_objects, std::string &sort_string, bool load_metadata) { time_t begin = time(NULL); diff --git a/src/librmb/tools/rmb/rmb-commands.h b/src/librmb/tools/rmb/rmb-commands.h index 11c6834d..85bdcff1 100644 --- a/src/librmb/tools/rmb/rmb-commands.h +++ b/src/librmb/tools/rmb/rmb-commands.h @@ -65,6 +65,10 @@ class RmbCommands { void set_output_path(librmb::CmdLineParser *parser); + int overwrite_ceph_object_index(std::set &mail_oids); + std::set load_objects(); + int remove_ceph_object_index(); + int append_ceph_object_index(const std::set &mail_oids); private: std::map *opts; librmb::RadosStorage *storage; diff --git a/src/storage-rbox/doveadm-rbox-plugin.cpp b/src/storage-rbox/doveadm-rbox-plugin.cpp index b3620138..9680a2a9 100644 --- a/src/storage-rbox/doveadm-rbox-plugin.cpp +++ b/src/storage-rbox/doveadm-rbox-plugin.cpp @@ -60,6 +60,7 @@ extern "C" { #include "rbox-storage.hpp" int check_namespace_mailboxes(const struct mail_namespace *ns, const std::list &mail_objects); +static int iterate_list_objects(struct mail_namespace* ns, const struct mailbox_info *info, std::set &object_list); class RboxDoveadmPlugin { public: @@ -85,7 +86,9 @@ class RboxDoveadmPlugin { int open_connection() { return (config == nullptr) ? -1 - : storage->open_connection(config->get_pool_name(), config->get_rados_cluster_name(), + : storage->open_connection(config->get_pool_name(), + config->get_index_pool_name(), + config->get_rados_cluster_name(), config->get_rados_username()); } @@ -772,6 +775,142 @@ static int cmd_rmb_check_indices_run(struct doveadm_mail_cmd_context *ctx, struc return 0; } +static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { + int ret = 0; + struct create_ceph_index_cmd_context *ctx = (struct create_ceph_index_cmd_context *)_ctx; + std::set mail_objects; + + RboxDoveadmPlugin plugin; + plugin.read_plugin_configuration(user); + + int open = open_connection_load_config(&plugin); + if (open < 0) { + i_error("Error opening rados connection. Errorcode: %d", open); + return open; + } + i_info("connection to rados open"); + std::map opts; + opts["namespace"] = user->username; + librmb::RmbCommands rmb_cmds(plugin.storage, plugin.cluster, &opts); + + std::string uid; + librmb::RadosCephConfig *cfg = (static_cast(plugin.config))->get_rados_ceph_cfg(); + + librmb::RadosStorageMetadataModule *ms = rmb_cmds.init_metadata_storage_module(*cfg, &uid); + if (ms == nullptr) { + i_error(" Error initializing metadata module"); + delete ms; + return -1; + } + if(rmb_cmds.remove_ceph_object_index() < 0){ + i_error(" Error overwriting ceph object index"); + delete ms; + return -1; + } + + if (user->namespaces != NULL) { + struct mail_namespace *ns = mail_namespace_find_inbox(user->namespaces); + + if(!ctx->full_refresh){ + for (; ns != NULL; ns = ns->next) { + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + + iter = mailbox_list_iter_init(ns->list, "*", static_cast( + MAILBOX_LIST_ITER_RAW_LIST | MAILBOX_LIST_ITER_RETURN_NO_FLAGS)); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + if ((info->flags & (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) == 0) { + + ret = iterate_list_objects(ns, info, mail_objects); + + if (ret < 0) { + ret = -1; + break; + } + + //append to index. + i_info("found %d mails in namespace",mail_objects.size()); + if(rmb_cmds.append_ceph_object_index(mail_objects) < 0){ + i_error(" Error overwriting ceph object index"); + delete ms; + return -1; + } + mail_objects.clear(); + } + } + } // end of for + }else{ + mail_objects = rmb_cmds.load_objects(); + if(rmb_cmds.overwrite_ceph_object_index(mail_objects) < 0){ + i_error(" Error overwriting ceph object index"); + delete ms; + return -1; + } + i_info("found %d mails in namespace",mail_objects.size()); + } + + i_info("index created"); + + delete ms; + } + return ret; +} +static int iterate_list_objects(struct mail_namespace* ns, const struct mailbox_info *info, std::set &object_list){ + + struct mailbox_transaction_context *mailbox_transaction; + struct mail_search_context *search_ctx; + struct mail_search_args *search_args; + struct mail *mail; + struct mailbox *box = mailbox_alloc(ns->list, info->vname, MAILBOX_FLAG_READONLY); + + if( box->virtual_vfuncs != NULL) { + i_info("skipping virtual box for object scan"); + mailbox_free(&box); + return -1; + } + + if (mailbox_open(box) < 0) { + i_error("Error opening mailbox %s", info->vname); + mailbox_free(&box); + return -1; + } + // lock box + mail_index_lock_sync(box->index, "LOCKED_FOR_INDEX_CREATION"); + + mailbox_transaction = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, "ceph_index_creation"); + + search_args = mail_search_build_init(); + mail_search_build_add(search_args, SEARCH_ALL); + + search_ctx = mailbox_search_init(mailbox_transaction, search_args, NULL, static_cast(0), NULL); + mail_search_args_unref(&search_args); + + while (mailbox_search_next(search_ctx, &mail)) { + const struct obox_mail_index_record *obox_rec; + const void *rec_data; + mail_index_lookup_ext(mail->transaction->view, mail->seq, ((struct rbox_mailbox *)box)->ext_id, &rec_data, NULL); + obox_rec = static_cast(rec_data); + + if (obox_rec == nullptr) { + std::cerr << "no valid extended header for mail with uid: " << mail->uid << std::endl; + continue; + } + std::string oid = guid_128_to_string(obox_rec->oid); + //TODO: add the oid to the ceph index!!!! + object_list.insert(oid); + } + if (mailbox_search_deinit(&search_ctx) < 0) { + return -1; + } + if (mailbox_transaction_commit(&mailbox_transaction) < 0) { + return -1; + } + mail_index_unlock(box->index, "UNLOCKED_FOR_INDEX_CREATION"); + + mailbox_free(&box); + + return 0; +} static int i_strcmp_reverse_p(const char *const *s1, const char *const *s2) { return -strcmp(*s1, *s2); } static int get_child_mailboxes(struct mail_user *user, ARRAY_TYPE(const_string) * mailboxes, const char *name) { struct mailbox_list_iterate_context *iter; @@ -932,6 +1071,11 @@ static void cmd_rmb_check_indices_init(struct doveadm_mail_cmd_context *ctx ATTR doveadm_mail_help_name("rmb check indices"); } } +static void cmd_rmb_create_ceph_index_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, const char *const args[]) { + if (args[0] != NULL) { + doveadm_mail_help_name("rmb create ceph index"); + } +} static void cmd_rmb_mailbox_delete_init(struct doveadm_mail_cmd_context *_ctx ATTR_UNUSED, const char *const args[]) { struct delete_cmd_context *ctx = (struct delete_cmd_context *)_ctx; const char *name; @@ -1012,6 +1156,18 @@ static bool cmd_check_indices_parse_arg(struct doveadm_mail_cmd_context *_ctx, i return true; } +static bool cmd_create_ceph_index_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { + struct create_ceph_index_cmd_context *ctx = (struct create_ceph_index_cmd_context *)_ctx; + + switch (c) { + case 'r': + ctx->full_refresh = true; + break; + default: + break; + } + return true; +} struct doveadm_mail_cmd_context *cmd_rmb_check_indices_alloc(void) { struct check_indices_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct check_indices_cmd_context); @@ -1022,6 +1178,17 @@ struct doveadm_mail_cmd_context *cmd_rmb_check_indices_alloc(void) { return &ctx->ctx; } + +struct doveadm_mail_cmd_context *cmd_rmb_create_ceph_index_alloc(void) { + struct create_ceph_index_cmd_context *ctx; + ctx = doveadm_mail_cmd_alloc(struct create_ceph_index_cmd_context); + ctx->ctx.v.run = cmd_rmb_create_ceph_index_run; + ctx->ctx.v.init = cmd_rmb_create_ceph_index_init; + ctx->ctx.v.parse_arg = cmd_create_ceph_index_parse_arg; + ctx->ctx.getopt_args = "r"; + return &ctx->ctx; +} + static bool cmd_mailbox_delete_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct delete_cmd_context *ctx = (struct delete_cmd_context *)_ctx; diff --git a/src/storage-rbox/doveadm-rbox-plugin.h b/src/storage-rbox/doveadm-rbox-plugin.h index 97693d7f..1e7cc2fa 100644 --- a/src/storage-rbox/doveadm-rbox-plugin.h +++ b/src/storage-rbox/doveadm-rbox-plugin.h @@ -17,6 +17,11 @@ struct check_indices_cmd_context { bool delete_not_referenced_objects; }; +struct create_ceph_index_cmd_context { + struct doveadm_mail_cmd_context ctx; + bool full_refresh; +}; + struct delete_cmd_context { struct doveadm_mail_cmd_context ctx; ARRAY_TYPE(const_string) mailboxes; @@ -53,6 +58,7 @@ extern int cmd_rmb_version(int argc, char *argv[]); extern struct doveadm_mail_cmd_context *cmd_rmb_save_log_alloc(void); extern struct doveadm_mail_cmd_context *cmd_rmb_check_indices_alloc(void); +extern struct doveadm_mail_cmd_context *cmd_rmb_create_ceph_index_alloc(void); extern struct doveadm_mail_cmd_context *cmd_rmb_mailbox_delete_alloc(void); #endif // SRC_DOVEADM_RBOX_PLUGIN_H_ diff --git a/src/storage-rbox/doveadm-rbox.c b/src/storage-rbox/doveadm-rbox.c index c4673367..740bf091 100644 --- a/src/storage-rbox/doveadm-rbox.c +++ b/src/storage-rbox/doveadm-rbox.c @@ -34,6 +34,7 @@ static struct doveadm_mail_cmd rmb_commands[] = { {cmd_rmb_rename_alloc, "rmb rename", "new username"}, {cmd_rmb_revert_log_alloc, "rmb revert", "path to save_log"}, {cmd_rmb_check_indices_alloc, "rmb check indices", "-d"}, + {cmd_rmb_create_ceph_index_alloc, "rmb create ceph index", "-d"}, {cmd_rmb_mailbox_delete_alloc, "rmb mailbox delete", "-r [...]"}}; struct doveadm_cmd doveadm_cmd_rbox[] = {{(void *)cmd_rmb_config_show, "rmb config show", NULL}, diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 05703658..7826bb03 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -894,12 +894,19 @@ int rbox_save_finish(struct mail_save_context *_ctx) { if (r_ctx->failed) { i_error("saved mail: %s failed. Metadata_count %ld, mail_size (%d)", r_ctx->rados_mail->get_oid()->c_str(), r_ctx->rados_mail->get_metadata()->size(), r_ctx->rados_mail->get_mail_size()); + }else{ + + if( r_storage->config->get_object_search_method() == 2){ + // ceph config schalter an oder aus! + r_storage->s->ceph_index_append(*r_ctx->rados_mail->get_oid()); + } } if (r_storage->save_log->is_open()) { r_storage->save_log->append( librmb::RadosSaveLogEntry(*r_ctx->rados_mail->get_oid(), r_storage->s->get_namespace(), - r_storage->s->get_pool_name(), librmb::RadosSaveLogEntry::op_save())); + r_storage->s->get_pool_name(), librmb::RadosSaveLogEntry::op_save())); } + } } clean_up_write_finish(_ctx); diff --git a/src/storage-rbox/rbox-storage.cpp b/src/storage-rbox/rbox-storage.cpp index d217f0dd..f7f19b50 100644 --- a/src/storage-rbox/rbox-storage.cpp +++ b/src/storage-rbox/rbox-storage.cpp @@ -448,11 +448,14 @@ int rbox_open_rados_connection(struct mailbox *box, bool alt_storage) { : librmb::WAIT_FOR_COMPLETE_AND_CB); /* open connection to primary and alternative storage */ ret = rados_storage->open_connection(rbox->storage->config->get_pool_name(), + rbox->storage->config->get_index_pool_name(), rbox->storage->config->get_rados_cluster_name(), rbox->storage->config->get_rados_username()); if (alt_storage) { - ret = rbox->storage->alt->open_connection(box->list->set.alt_dir, rbox->storage->config->get_rados_cluster_name(), + ret = rbox->storage->alt->open_connection(box->list->set.alt_dir, + rbox->storage->config->get_index_pool_name(), + rbox->storage->config->get_rados_cluster_name(), rbox->storage->config->get_rados_username()); rbox->storage->alt->set_ceph_wait_method(rbox->storage->config->is_ceph_aio_wait_for_safe_and_cb() diff --git a/src/storage-rbox/rbox-sync-rebuild.cpp b/src/storage-rbox/rbox-sync-rebuild.cpp index 8207e577..1abc4889 100644 --- a/src/storage-rbox/rbox-sync-rebuild.cpp +++ b/src/storage-rbox/rbox-sync-rebuild.cpp @@ -115,8 +115,7 @@ std::map> load_rados_mail_metadata( int load_metadata_ret = r_storage->ms->get_storage()->load_metadata(&mail_object); if (load_metadata_ret < 0 || !librmb::RadosUtils::validate_metadata(mail_object.get_metadata())) { - i_warning("metadata for object : %s is not valid, skipping object ", mail_object.get_oid()->c_str()); - //++iter; + i_debug("metadata for object : %s is not valid, skipping object ", mail_object.get_oid()->c_str()); continue; } @@ -463,6 +462,23 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage i_debug("multithreading done : took: %ld ms", (milli_time)); } + else if( r_storage->config->get_object_search_method() == 2){ + + long milli_time, seconds, useconds; + struct timeval start_time, end_time; + gettimeofday(&start_time, NULL); + i_info("looking for ceph_index_read..."); + mail_list = r_storage->s->ceph_index_read(); + if(mail_list.size() == 0){ + i_warning("no mails found try doveadm create ceph index -r to create a ceph index for this mailbox"); + } + gettimeofday(&end_time, NULL); + seconds = end_time.tv_sec - start_time.tv_sec; + useconds = end_time.tv_usec - start_time.tv_usec; + milli_time = ((seconds) * 1000 + useconds/1000.0); + + i_debug("processing ceph index done : took: %ld ms", (milli_time)); + } else { librados::NObjectIterator iter_guid = r_storage->s->find_mails(nullptr); while (iter_guid != librados::NObjectIterator::__EndObjectIterator) { @@ -480,6 +496,22 @@ int repair_namespace(struct mail_namespace *ns, bool force, struct rbox_storage i_debug("Found mails for mailbox_guid: %s: mails : %ld", it->first.c_str(), it->second.size()); } #endif + + if( r_storage->config->get_object_search_method() == 2){ + //TODO: make this more efficient : restore the valid objects + std::set valid_objects; + for(std::map>::iterator boxes = rados_mails.begin(); boxes != rados_mails.end(); ++boxes) { + for (librmb::RadosMail const& m : boxes->second) { + valid_objects.insert(*m.get_oid()); + } + } + + if(r_storage->s->ceph_index_overwrite(valid_objects) < 0 ) { + i_warning("ceph index object could not be overwritten"); + } + + i_info("unique objects %d", valid_objects.size()); + } } ret = rbox_sync_index_rebuild((struct rbox_mailbox *)box, force, rados_mails); diff --git a/src/tests/mocks/mock_test.h b/src/tests/mocks/mock_test.h index 04d852a3..e465f636 100644 --- a/src/tests/mocks/mock_test.h +++ b/src/tests/mocks/mock_test.h @@ -32,6 +32,8 @@ using librmb::RadosStorageMetadataModule; class RadosStorageMock : public RadosStorage { public: MOCK_METHOD0(get_io_ctx, librados::IoCtx &()); + MOCK_METHOD0(get_recovery_io_ctx, librados::IoCtx &()); + MOCK_METHOD3(stat_mail, int(const std::string &oid, uint64_t *psize, time_t *pmtime)); MOCK_METHOD1(set_namespace, void(const std::string &nspace)); MOCK_METHOD0(get_namespace, std::string()); @@ -52,9 +54,13 @@ class RadosStorageMock : public RadosStorage { librados::ObjectWriteOperation *op)); MOCK_METHOD1(find_mails, librados::NObjectIterator(const RadosMetadata *attr)); MOCK_METHOD1(open_connection, int(const std::string &poolname)); + MOCK_METHOD2(open_connection, int(const std::string &poolname, const std::string &index_pool)); MOCK_METHOD4(find_mails_async, std::set(const RadosMetadata *attr, std::string &pool_name,int num_threads, void (*ptr)(std::string&))); + MOCK_METHOD4(open_connection, + int(const std::string &poolname, const std::string &index_pool, const std::string &clustername, const std::string &rados_username)); + MOCK_METHOD3(open_connection, int(const std::string &poolname, const std::string &clustername, const std::string &rados_username)); MOCK_METHOD0(close_connection, void()); @@ -77,6 +83,12 @@ class RadosStorageMock : public RadosStorage { MOCK_METHOD0(create_anker, int()); + + MOCK_METHOD1(ceph_index_append,int(const std::string &oid)); + MOCK_METHOD1(ceph_index_append,int(const std::set &oids)); + MOCK_METHOD1(ceph_index_overwrite,int(const std::set &oids)); + MOCK_METHOD0(ceph_index_read,std::set()); + MOCK_METHOD0(ceph_index_delete,int()); }; class RadosStorageMetadataMock : public RadosStorageMetadataModule { @@ -134,6 +146,8 @@ class RadosClusterMock : public RadosCluster { MOCK_METHOD0(deinit, void()); MOCK_METHOD1(pool_create, int(const std::string &pool)); + MOCK_METHOD2(recovery_index_io_ctx, int(const std::string &pool,librados::IoCtx *io_ctx)); + MOCK_METHOD2(io_ctx_create, int(const std::string &pool, librados::IoCtx *io_ctx)); MOCK_METHOD2(get_config_option, int(const char *option, std::string *value)); MOCK_METHOD0(is_connected, bool()); @@ -175,6 +189,8 @@ class RadosDovecotCephCfgMock : public RadosDovecotCephCfg { MOCK_METHOD0(get_config, std::map *()); MOCK_METHOD0(get_pool_name, std::string &()); + MOCK_METHOD0(get_index_pool_name, std::string &()); + MOCK_METHOD0(is_update_attributes, bool()); MOCK_METHOD2(update_metadata, void(const std::string &key, const char *value_)); diff --git a/src/tests/storage-mock-rbox/test_repair_rbox.cpp b/src/tests/storage-mock-rbox/test_repair_rbox.cpp index b435689d..39ac0e01 100644 --- a/src/tests/storage-mock-rbox/test_repair_rbox.cpp +++ b/src/tests/storage-mock-rbox/test_repair_rbox.cpp @@ -207,7 +207,37 @@ TEST_F(StorageTest, extractPrimaryOsd) { EXPECT_EQ(6,list["4"].size()); } - + +TEST_F(StorageTest,create_read_index) { + + std::set test_mails; + test_mails.insert("1"); + test_mails.insert("2"); + test_mails.insert("3"); + test_mails.insert("4"); + test_mails.insert("5"); + std::string test_string = librmb::RadosUtils::convert_to_ceph_index(test_mails); + EXPECT_EQ("1,2,3,4,5,",test_string); + + std::set test_mails_read = librmb::RadosUtils::ceph_index_to_set(test_string); + EXPECT_EQ(5,test_mails_read.size()); + auto my_vect = std::vector(test_mails_read.begin(), test_mails_read.end()); // O[n] + + EXPECT_EQ("1",my_vect[0]); + EXPECT_EQ("2",my_vect[1]); + EXPECT_EQ("3",my_vect[2]); + EXPECT_EQ("4",my_vect[3]); + EXPECT_EQ("5",my_vect[4]); + + + std::string test_string1 = librmb::RadosUtils::convert_to_ceph_index("abd"); + EXPECT_EQ("abd,",test_string1); + + +} + + + int main(int argc, char **argv) { ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); diff --git a/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp b/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp index 6d5aeb22..be3176cb 100644 --- a/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp +++ b/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp @@ -248,7 +248,8 @@ TEST_F(StorageTest, save_mail_fail_test) { std::string cluster = "ceph"; std::string pool = "mail_storage"; std::string suffix = "_u"; - + EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); + EXPECT_CALL(*cfg_mock, get_object_search_method()).WillRepeatedly(Return(0)); EXPECT_CALL(*cfg_mock, get_rados_username()).WillRepeatedly(ReturnRef(user)); EXPECT_CALL(*cfg_mock, get_rados_cluster_name()).WillRepeatedly(ReturnRef(cluster)); EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); @@ -396,7 +397,8 @@ TEST_F(StorageTest, write_op_fails) { std::string cluster = "ceph"; std::string pool = "mail_storage"; std::string suffix = "_u"; - + EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); + EXPECT_CALL(*cfg_mock, get_object_search_method()).WillRepeatedly(Return(0)); EXPECT_CALL(*cfg_mock, get_rados_username()).WillRepeatedly(ReturnRef(user)); EXPECT_CALL(*cfg_mock, get_rados_cluster_name()).WillRepeatedly(ReturnRef(cluster)); EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); @@ -633,7 +635,8 @@ TEST_F(StorageTest, mock_copy_failed_due_to_rados_err) { librmbtest::RadosStorageMetadataMock ms_mock; EXPECT_CALL(*ms_p_mock, get_storage()).WillRepeatedly(Return(&ms_mock)); EXPECT_CALL(ms_mock, set_metadata(_, _)).WillRepeatedly(Return(0)); - + EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); + EXPECT_CALL(*cfg_mock, get_object_search_method()).WillRepeatedly(Return(0)); EXPECT_CALL(*cfg_mock, is_config_valid()).WillRepeatedly(Return(true)); EXPECT_CALL(*cfg_mock, is_write_chunks()).WillRepeatedly(Return(false)); EXPECT_CALL(*cfg_mock, is_ceph_posix_bugfix_enabled()).WillRepeatedly(Return(false)); @@ -763,7 +766,8 @@ TEST_F(StorageTest, save_mail_cancel) { std::string cluster = "ceph"; std::string pool = "mail_storage"; std::string suffix = "_u"; - + EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); + EXPECT_CALL(*cfg_mock, get_object_search_method()).WillRepeatedly(Return(0)); EXPECT_CALL(*cfg_mock, get_rados_username()).WillRepeatedly(ReturnRef(user)); EXPECT_CALL(*cfg_mock, get_rados_cluster_name()).WillRepeatedly(ReturnRef(cluster)); EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); diff --git a/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp b/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp index 74af9841..9f65a03e 100644 --- a/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp +++ b/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp @@ -115,7 +115,7 @@ TEST_F(StorageTest, save_mail_rados_connection_failed) { EXPECT_CALL(*storage_mock, close_connection()).Times(0); - EXPECT_CALL(*storage_mock, open_connection("mail_storage", "ceph", "client.admin")) + EXPECT_CALL(*storage_mock, open_connection("mail_storage",_, "ceph", "client.admin")) .Times(AtLeast(1)) .WillRepeatedly(Return(0)); @@ -153,6 +153,8 @@ TEST_F(StorageTest, save_mail_rados_connection_failed) { std::string pool = "mail_storage"; std::string suffix = "_u"; + EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); + EXPECT_CALL(*cfg_mock, get_object_search_method()).WillRepeatedly(Return(0)); EXPECT_CALL(*cfg_mock, get_rados_username()).WillRepeatedly(ReturnRef(user)); EXPECT_CALL(*cfg_mock, get_rados_cluster_name()).WillRepeatedly(ReturnRef(cluster)); EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); @@ -273,7 +275,7 @@ TEST_F(StorageTest, save_mail_success) { EXPECT_CALL(*storage_mock, close_connection()).Times(1); - EXPECT_CALL(*storage_mock, open_connection("mail_storage", "ceph", "client.admin")) + EXPECT_CALL(*storage_mock, open_connection("mail_storage", _, "ceph", "client.admin")) .Times(AtLeast(1)) .WillRepeatedly(Return(0)); @@ -309,7 +311,8 @@ TEST_F(StorageTest, save_mail_success) { std::string cluster = "ceph"; std::string pool = "mail_storage"; std::string suffix = "_u"; - + EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); + EXPECT_CALL(*cfg_mock, get_object_search_method()).WillRepeatedly(Return(0)); EXPECT_CALL(*cfg_mock, get_rados_username()).WillRepeatedly(ReturnRef(user)); EXPECT_CALL(*cfg_mock, get_rados_cluster_name()).WillRepeatedly(ReturnRef(cluster)); EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); diff --git a/src/tests/test_repair_rbox b/src/tests/test_repair_rbox new file mode 100755 index 00000000..b55d33b7 --- /dev/null +++ b/src/tests/test_repair_rbox @@ -0,0 +1,210 @@ +#! /bin/bash + +# test_repair_rbox - temporary wrapper script for .libs/test_repair_rbox +# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-14 +# +# The test_repair_rbox program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s|\([`"$\\]\)|\\\1|g' + +# Be Bourne compatible +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command="" + +# This environment variable determines our operation mode. +if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then + # install mode needs the following variables: + generated_by_libtool_version='2.4.6' + notinst_deplibs=' ../../src/storage-rbox/libstorage_rbox_plugin.la ../../src/librmb/librmb.la' +else + # When we are sourced in execute mode, $file and $ECHO are already set. + if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then + file="$0" + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' +} + ECHO="printf %s\\n" + fi + +# Very basic option parsing. These options are (a) specific to +# the libtool wrapper, (b) are identical between the wrapper +# /script/ and the wrapper /executable/ that is used only on +# windows platforms, and (c) all begin with the string --lt- +# (application programs are unlikely to have options that match +# this pattern). +# +# There are only two supported options: --lt-debug and +# --lt-dump-script. There is, deliberately, no --lt-help. +# +# The first argument to this parsing function should be the +# script's ../../libtool value, followed by no. +lt_option_debug= +func_parse_lt_options () +{ + lt_script_arg0=$0 + shift + for lt_opt + do + case "$lt_opt" in + --lt-debug) lt_option_debug=1 ;; + --lt-dump-script) + lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'` + test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=. + lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'` + cat "$lt_dump_D/$lt_dump_F" + exit 0 + ;; + --lt-*) + $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2 + exit 1 + ;; + esac + done + + # Print the debug banner immediately: + if test -n "$lt_option_debug"; then + echo "test_repair_rbox:test_repair_rbox:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-14" 1>&2 + fi +} + +# Used when --lt-debug. Prints its arguments to stdout +# (redirection is the responsibility of the caller) +func_lt_dump_args () +{ + lt_dump_args_N=1; + for lt_arg + do + $ECHO "test_repair_rbox:test_repair_rbox:$LINENO: newargv[$lt_dump_args_N]: $lt_arg" + lt_dump_args_N=`expr $lt_dump_args_N + 1` + done +} + +# Core function for launching the target application +func_exec_program_core () +{ + + if test -n "$lt_option_debug"; then + $ECHO "test_repair_rbox:test_repair_rbox:$LINENO: newargv[0]: $progdir/$program" 1>&2 + func_lt_dump_args ${1+"$@"} 1>&2 + fi + exec "$progdir/$program" ${1+"$@"} + + $ECHO "$0: cannot exec $program $*" 1>&2 + exit 1 +} + +# A function to encapsulate launching the target application +# Strips options in the --lt-* namespace from $@ and +# launches target application with the remaining arguments. +func_exec_program () +{ + case " $* " in + *\ --lt-*) + for lt_wr_arg + do + case $lt_wr_arg in + --lt-*) ;; + *) set x "$@" "$lt_wr_arg"; shift;; + esac + shift + done ;; + esac + func_exec_program_core ${1+"$@"} +} + + # Parse options + func_parse_lt_options "$0" ${1+"$@"} + + # Find the directory that this script lives in. + thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'` + test "x$thisdir" = "x$file" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'` + while test -n "$file"; do + destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'` + + # If there was a directory component, then change thisdir. + if test "x$destdir" != "x$file"; then + case "$destdir" in + [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;; + *) thisdir="$thisdir/$destdir" ;; + esac + fi + + file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'` + file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'` + done + + # Usually 'no', except on cygwin/mingw when embedded into + # the cwrapper. + WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no + if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then + # special case for '.' + if test "$thisdir" = "."; then + thisdir=`pwd` + fi + # remove .libs from thisdir + case "$thisdir" in + *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;; + .libs ) thisdir=. ;; + esac + fi + + # Try to get the absolute directory name. + absdir=`cd "$thisdir" && pwd` + test -n "$absdir" && thisdir="$absdir" + + program='test_repair_rbox' + progdir="$thisdir/.libs" + + + if test -f "$progdir/$program"; then + # Add our own library path to LD_LIBRARY_PATH + LD_LIBRARY_PATH="/repo/src/storage-rbox/.libs:/usr/local/lib/dovecot:/repo/src/librmb/.libs:$LD_LIBRARY_PATH" + + # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH + # The second colon is a workaround for a bug in BeOS R4 sed + LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'` + + export LD_LIBRARY_PATH + + if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then + # Run the actual program with our arguments. + func_exec_program ${1+"$@"} + fi + else + # The program doesn't exist. + $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2 + $ECHO "This script is just a wrapper for $program." 1>&2 + $ECHO "See the libtool documentation for more information." 1>&2 + exit 1 + fi +fi From f0741a62624c27c5b299a8ef5de430bf0a514948 Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Tue, 22 Nov 2022 20:57:46 +0100 Subject: [PATCH 08/18] #349 bugfix return code doveadm rmb (#351) --- CHANGELOG.md | 3 +++ configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/storage-rbox/doveadm-rbox-plugin.cpp | 9 ++++++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 434d9726..ada64cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Change Log +## [0.0.45](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) +- #349 bugfix doveadm rmb return code not set + ## [0.0.44](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.44) (2022-11-21) - #349 additional recovery method (ceph index object) diff --git a/configure.ac b/configure.ac index 01d96dc6..9dbd69a2 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.44], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.45], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) AC_CONFIG_SRCDIR([src]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index 00a660d5..2d426132 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,7 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.44 +Version: 0.0.45 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/storage-rbox/doveadm-rbox-plugin.cpp b/src/storage-rbox/doveadm-rbox-plugin.cpp index 9680a2a9..9de9cfc3 100644 --- a/src/storage-rbox/doveadm-rbox-plugin.cpp +++ b/src/storage-rbox/doveadm-rbox-plugin.cpp @@ -786,6 +786,7 @@ static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, int open = open_connection_load_config(&plugin); if (open < 0) { i_error("Error opening rados connection. Errorcode: %d", open); + _ctx->exit_code = open; return open; } i_info("connection to rados open"); @@ -800,11 +801,13 @@ static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, if (ms == nullptr) { i_error(" Error initializing metadata module"); delete ms; + _ctx->exit_code = -1; return -1; } if(rmb_cmds.remove_ceph_object_index() < 0){ i_error(" Error overwriting ceph object index"); delete ms; + _ctx->exit_code = -1; return -1; } @@ -833,6 +836,7 @@ static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, if(rmb_cmds.append_ceph_object_index(mail_objects) < 0){ i_error(" Error overwriting ceph object index"); delete ms; + _ctx->exit_code = -1; return -1; } mail_objects.clear(); @@ -848,11 +852,10 @@ static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, } i_info("found %d mails in namespace",mail_objects.size()); } - - i_info("index created"); - delete ms; } + i_info("index created"); + _ctx->exit_code = ret; return ret; } static int iterate_list_objects(struct mail_namespace* ns, const struct mailbox_info *info, std::set &object_list){ From 4efa9f115fc05f84d68743f075a9292e41be726f Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Sat, 26 Nov 2022 23:35:49 +0100 Subject: [PATCH 09/18] #349 validate object (#353) --- CHANGELOG.md | 3 +++ configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/librmb/rados-storage-impl.cpp | 7 +++++++ src/librmb/rados-storage-impl.h | 1 + src/librmb/rados-storage.h | 4 ++++ src/librmb/tools/rmb/rmb-commands.cpp | 17 +++++++++++++---- src/librmb/tools/rmb/rmb-commands.h | 2 +- src/storage-rbox/doveadm-rbox-plugin.cpp | 13 +++++-------- src/storage-rbox/rbox-save.cpp | 5 +++++ src/storage-rbox/rbox-sync.cpp | 3 +++ src/tests/mocks/mock_test.h | 1 + 12 files changed, 45 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ada64cdb..285093ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Change Log +## [0.0.46](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) +- #349 bugfix doveadm rmb create ceph index validate object metadata + ## [0.0.45](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) - #349 bugfix doveadm rmb return code not set diff --git a/configure.ac b/configure.ac index 9c451fac..c7dac368 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.45], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.46], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index 2d426132..7b7a04c4 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,7 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.45 +Version: 0.0.46 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/librmb/rados-storage-impl.cpp b/src/librmb/rados-storage-impl.cpp index 37e57274..f2b38993 100644 --- a/src/librmb/rados-storage-impl.cpp +++ b/src/librmb/rados-storage-impl.cpp @@ -576,6 +576,13 @@ void RadosStorageImpl::free_rados_mail(librmb::RadosMail *mail) { } } +uint64_t RadosStorageImpl::ceph_index_size(){ + uint64_t psize; + time_t pmtime; + get_recovery_io_ctx().stat(get_namespace(), &psize, &pmtime); + return psize; +} + int RadosStorageImpl::ceph_index_append(const std::string &oid) { librados::bufferlist bl; bl.append(RadosUtils::convert_to_ceph_index(oid)); diff --git a/src/librmb/rados-storage-impl.h b/src/librmb/rados-storage-impl.h index 11ab2856..c4320859 100644 --- a/src/librmb/rados-storage-impl.h +++ b/src/librmb/rados-storage-impl.h @@ -85,6 +85,7 @@ class RadosStorageImpl : public RadosStorage { void free_rados_mail(librmb::RadosMail *mail) override; + uint64_t ceph_index_size() override; int ceph_index_append(const std::string &oid) override; int ceph_index_append(const std::set &oids) override; int ceph_index_overwrite(const std::set &oids) override; diff --git a/src/librmb/rados-storage.h b/src/librmb/rados-storage.h index c3d36fe9..fcc0690d 100644 --- a/src/librmb/rados-storage.h +++ b/src/librmb/rados-storage.h @@ -220,6 +220,10 @@ underTest.ceph_index_add("dkfkjdf") */ virtual int ceph_index_delete() = 0; + /** + * returns the ceph index size + */ + virtual uint64_t ceph_index_size() = 0; /*! read the complete mail object into bufferlist * diff --git a/src/librmb/tools/rmb/rmb-commands.cpp b/src/librmb/tools/rmb/rmb-commands.cpp index 4bfb3def..58c1dd15 100644 --- a/src/librmb/tools/rmb/rmb-commands.cpp +++ b/src/librmb/tools/rmb/rmb-commands.cpp @@ -427,12 +427,21 @@ static void aio_cb(rados_completion_t cb, void *arg) { int RmbCommands::overwrite_ceph_object_index(std::set &mail_oids){ return storage->ceph_index_overwrite(mail_oids); } -std::set RmbCommands::load_objects(){ +std::set RmbCommands::load_objects(librmb::RadosStorageMetadataModule *ms){ std::set mail_list; - librados::NObjectIterator iter_guid =storage->find_mails(nullptr); + librados::NObjectIterator iter_guid = storage->find_mails(nullptr); while (iter_guid != librados::NObjectIterator::__EndObjectIterator) { - mail_list.insert((*iter_guid).get_oid()); - iter_guid++; + librmb::RadosMail mail; + mail.set_oid((*iter_guid).get_oid()); + + int load_metadata_ret = ms->load_metadata(&mail); + if (load_metadata_ret < 0 || !librmb::RadosUtils::validate_metadata(mail.get_metadata())) { + std::cerr << "metadata for object : " << mail.get_oid()->c_str() << " is not valid, skipping object " << std::endl; + iter_guid++; + continue; + } + mail_list.insert((*iter_guid).get_oid()); + iter_guid++; } return mail_list; } diff --git a/src/librmb/tools/rmb/rmb-commands.h b/src/librmb/tools/rmb/rmb-commands.h index 85bdcff1..d33c9b2f 100644 --- a/src/librmb/tools/rmb/rmb-commands.h +++ b/src/librmb/tools/rmb/rmb-commands.h @@ -66,7 +66,7 @@ class RmbCommands { void set_output_path(librmb::CmdLineParser *parser); int overwrite_ceph_object_index(std::set &mail_oids); - std::set load_objects(); + std::set load_objects(librmb::RadosStorageMetadataModule *ms); int remove_ceph_object_index(); int append_ceph_object_index(const std::set &mail_oids); private: diff --git a/src/storage-rbox/doveadm-rbox-plugin.cpp b/src/storage-rbox/doveadm-rbox-plugin.cpp index fa716669..e9d585c7 100644 --- a/src/storage-rbox/doveadm-rbox-plugin.cpp +++ b/src/storage-rbox/doveadm-rbox-plugin.cpp @@ -805,10 +805,7 @@ static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, return -1; } if(rmb_cmds.remove_ceph_object_index() < 0){ - i_error(" Error overwriting ceph object index"); - delete ms; - _ctx->exit_code = -1; - return -1; + i_debug(" Error overwriting ceph object index"); } if (user->namespaces != NULL) { @@ -832,7 +829,7 @@ static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, } //append to index. - i_info("found %d mails in namespace",mail_objects.size()); + i_info("found %s mails in namespace %d",info->vname, mail_objects.size()); if(rmb_cmds.append_ceph_object_index(mail_objects) < 0){ i_error(" Error overwriting ceph object index"); delete ms; @@ -842,9 +839,10 @@ static int cmd_rmb_create_ceph_index_run(struct doveadm_mail_cmd_context *_ctx, mail_objects.clear(); } } - } // end of for + } // end of for + }else{ - mail_objects = rmb_cmds.load_objects(); + mail_objects = rmb_cmds.load_objects(ms); if(rmb_cmds.overwrite_ceph_object_index(mail_objects) < 0){ i_error(" Error overwriting ceph object index"); delete ms; @@ -900,7 +898,6 @@ static int iterate_list_objects(struct mail_namespace* ns, const struct mailbox_ continue; } std::string oid = guid_128_to_string(obox_rec->oid); - //TODO: add the oid to the ceph index!!!! object_list.insert(oid); } if (mailbox_search_deinit(&search_ctx) < 0) { diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 7826bb03..3f002e12 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -899,6 +899,11 @@ int rbox_save_finish(struct mail_save_context *_ctx) { if( r_storage->config->get_object_search_method() == 2){ // ceph config schalter an oder aus! r_storage->s->ceph_index_append(*r_ctx->rados_mail->get_oid()); + uint64_t index_size = r_storage->s->ceph_index_size(); + // WARN if index reaches 80% of max object size + if((r_storage->s->get_max_object_size() / index_size) > 80) { + i_warning("ceph_index file(%d) close to exceed max_object size(%d), recalc index !",r_storage->s->get_max_object_size(), index_size ); + } } } if (r_storage->save_log->is_open()) { diff --git a/src/storage-rbox/rbox-sync.cpp b/src/storage-rbox/rbox-sync.cpp index f4ab9736..8758acaa 100644 --- a/src/storage-rbox/rbox-sync.cpp +++ b/src/storage-rbox/rbox-sync.cpp @@ -367,6 +367,9 @@ int rbox_sync_begin(struct rbox_mailbox *rbox, struct rbox_sync_context **ctx_r, rebuild = (hdr->flags & MAIL_INDEX_HDR_FLAG_FSCKD) != 0; #endif + //TODO: check ceph index size, if threshold size has been reached, stat mails + // and repair mailbox + ctx = i_new(struct rbox_sync_context, 1); ctx->rbox = rbox; i_array_init(&ctx->expunged_items, 32); diff --git a/src/tests/mocks/mock_test.h b/src/tests/mocks/mock_test.h index e465f636..0e3f5f0a 100644 --- a/src/tests/mocks/mock_test.h +++ b/src/tests/mocks/mock_test.h @@ -83,6 +83,7 @@ class RadosStorageMock : public RadosStorage { MOCK_METHOD0(create_anker, int()); + MOCK_METHOD0(ceph_index_size,uint64_t()); MOCK_METHOD1(ceph_index_append,int(const std::string &oid)); MOCK_METHOD1(ceph_index_append,int(const std::set &oids)); From d2366a2ba65542e2d24772dbcc0b5ed26ee6f6cc Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Mon, 5 Dec 2022 20:31:27 +0100 Subject: [PATCH 10/18] Bugfix/355 fix gzip trailer for empty stream (#357) * #355 fix write gzip trailer even the stream is empty. * #355 minor improvements for quota messaging * #355 fix gzip trailer for empty stream --- CHANGELOG.md | 4 +++- configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/storage-rbox/rbox-save.cpp | 5 ++--- src/storage-rbox/rbox-sync.cpp | 39 ++++++++++++++++------------------ 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 876edf4c..ecb39608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log +## [0.0.47](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.47) (2022-12-05) +- #355 fix gzip trailer when stream is empty -## [0.0.46](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) +## [0.0.46](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.46) (2022-11-22) - #349 bugfix doveadm rmb create ceph index validate object metadata ## [0.0.45](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) diff --git a/configure.ac b/configure.ac index c7dac368..65393adf 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.46], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.47], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index 7b7a04c4..ddc4e641 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,7 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.46 +Version: 0.0.47 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 3f002e12..08e0ef6a 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -784,9 +784,8 @@ int rbox_save_finish(struct mail_save_context *_ctx) { // clean stream if still open #if DOVECOT_PREREQ(2, 3) int ret = 0; - - if (r_ctx->ctx.data.output != r_ctx->output_stream && - r_ctx->ctx.data.output != NULL) { + //#355 we need to write zipl trailer even the stream is empty! + if (r_ctx->ctx.data.output != r_ctx->output_stream ) { /* e.g. zlib plugin had changed this. make sure we successfully write the trailer. */ i_info("check state2 %ld",r_ctx->ctx.data.output); diff --git a/src/storage-rbox/rbox-sync.cpp b/src/storage-rbox/rbox-sync.cpp index 8758acaa..3374b99c 100644 --- a/src/storage-rbox/rbox-sync.cpp +++ b/src/storage-rbox/rbox-sync.cpp @@ -50,24 +50,24 @@ static void rbox_sync_expunge(struct rbox_sync_context *ctx, uint32_t seq1, uint FUNC_START(); struct mailbox *box = &ctx->rbox->box; uint32_t uid = -1; + uint32_t seq; - for (; seq1 <= seq2; seq1++) { - mail_index_lookup_uid(ctx->sync_view, seq1, &uid); - if (!mail_index_transaction_is_expunged(ctx->trans, seq1)) { + for (seq = seq1; seq <= seq2; seq++) { + mail_index_lookup_uid(ctx->sync_view, seq, &uid); + const struct mail_index_record *rec; + rec = mail_index_lookup(ctx->sync_view, seq); + if (rec == NULL) { + i_error("rbox_sync_expunge: mail_index_lookup failed! for %d uid(%d)", seq1, uid); + continue; // skip further processing. + } + if (!mail_index_transaction_is_expunged(ctx->trans, seq)) { /* todo load flags and set alt_storage flag */ - const struct mail_index_record *rec; - rec = mail_index_lookup(ctx->sync_view, seq1); - if (rec == NULL) { - i_error("rbox_sync_expunge: mail_index_lookup failed! for %d uid(%d)", seq1, uid); - continue; // skip further processing. - } - - mail_index_expunge(ctx->trans, seq1); + mail_index_expunge(ctx->trans, seq); struct expunged_item *item = p_new(default_pool, struct expunged_item, 1); i_zero(item); item->uid = uid; - if (rbox_get_oid_from_index(ctx->sync_view, seq1, ((struct rbox_mailbox *)box)->ext_id, &item->oid) < 0) { + if (rbox_get_oid_from_index(ctx->sync_view, seq, ((struct rbox_mailbox *)box)->ext_id, &item->oid) < 0) { // continue anyway } else { item->alt_storage = is_alternate_storage_set(rec->flags) && is_alternate_pool_valid(box); @@ -409,6 +409,7 @@ int rbox_sync_begin(struct rbox_mailbox *rbox, struct rbox_sync_context **ctx_r, } } if (ret <= 0) { + index_storage_expunging_deinit(&ctx->rbox->box); array_delete(&ctx->expunged_items, array_count(&ctx->expunged_items) - 1, 1); array_free(&ctx->expunged_items); i_free(ctx); @@ -475,6 +476,8 @@ static int rbox_sync_object_expunge(struct rbox_sync_context *ctx, struct expung item->alt_storage); } } + // directly notify + mailbox_sync_notify(box, item->uid, MAILBOX_SYNC_TYPE_EXPUNGE); FUNC_END(); return ret_remove; @@ -493,17 +496,12 @@ static void rbox_sync_expunge_rbox_objects(struct rbox_sync_context *ctx) { T_BEGIN { item = items[i]; rbox_sync_object_expunge(ctx, item); - // directly notify - if (ctx->rbox->box.v.sync_notify != NULL) { - ctx->rbox->box.v.sync_notify(&ctx->rbox->box, item->uid, MAILBOX_SYNC_TYPE_EXPUNGE); - } } T_END; } } - if (ctx->rbox->box.v.sync_notify != NULL){ - ctx->rbox->box.v.sync_notify(&ctx->rbox->box, 0, static_cast(0)); - } + mailbox_sync_notify(&ctx->rbox->box, 0, 0); + FUNC_END(); } @@ -530,7 +528,7 @@ int rbox_sync_finish(struct rbox_sync_context **_ctx, bool success) { } else { mail_index_sync_rollback(&ctx->index_sync_ctx); } - + i_info("EXPUNGE: calling deinit"); index_storage_expunging_deinit(&ctx->rbox->box); if (array_is_created(&ctx->expunged_items)) { @@ -544,7 +542,6 @@ int rbox_sync_finish(struct rbox_sync_context **_ctx, bool success) { } array_free(&ctx->expunged_items); } - i_free(ctx); FUNC_END(); return ret; From 3be0cff566b49e11c90e3aae90b1f0d8754d43ef Mon Sep 17 00:00:00 2001 From: Jan Radon Date: Mon, 5 Dec 2022 20:46:38 +0100 Subject: [PATCH 11/18] #349 fix threshold calc for ceph index file --- src/storage-rbox/rbox-save.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 08e0ef6a..4670a41d 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -900,8 +900,8 @@ int rbox_save_finish(struct mail_save_context *_ctx) { r_storage->s->ceph_index_append(*r_ctx->rados_mail->get_oid()); uint64_t index_size = r_storage->s->ceph_index_size(); // WARN if index reaches 80% of max object size - if((r_storage->s->get_max_object_size() / index_size) > 80) { - i_warning("ceph_index file(%d) close to exceed max_object size(%d), recalc index !",r_storage->s->get_max_object_size(), index_size ); + if( ((index_size/r_storage->s->get_max_object_size()) * 100) > 80) { + i_warning("ceph_index file(%d) close to exceed max_object size(%d), recalc index !", index_size, r_storage->s->get_max_object_size() ); } } } From 0cbb2c03003b2e4a537d2064f92812a7514154f9 Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Mon, 5 Dec 2022 22:19:32 +0100 Subject: [PATCH 12/18] #355 fix buffersize for write method 1 and 2 (#360) --- CHANGELOG.md | 2 ++ src/librmb/rados-storage-impl.cpp | 2 +- src/storage-rbox/rbox-save.cpp | 9 ++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c5eb577..1b1afa27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # Change Log ## [0.0.47](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.47) (2022-12-05) - #355 fix gzip trailer when stream is empty +- fix save_method 1+2 buffersize (1 byte short) +## [0.0.46](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) - #349 bugfix doveadm rmb create ceph index validate object metadata ## [0.0.45](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) diff --git a/src/librmb/rados-storage-impl.cpp b/src/librmb/rados-storage-impl.cpp index f2b38993..4bbe5e54 100644 --- a/src/librmb/rados-storage-impl.cpp +++ b/src/librmb/rados-storage-impl.cpp @@ -57,7 +57,7 @@ int RadosStorageImpl::split_buffer_and_exec_op(RadosMail *current_object, } int ret_val = 0; - uint64_t write_buffer_size = current_object->get_mail_size() -1; + uint64_t write_buffer_size = current_object->get_mail_size(); assert(max_write > 0); diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 4670a41d..74371621 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -476,7 +476,7 @@ int save_mail_async(RadosStorage *rados_storage, librados::AioCompletion *completion = librados::Rados::aio_create_completion(); int ret_val = 0; - uint64_t write_buffer_size = current_object->get_mail_size() -1; // write buffer size needs to be length -1 + uint64_t write_buffer_size = current_object->get_mail_size(); assert(max_write > 0); @@ -563,7 +563,7 @@ int save_mail_sync(RadosStorage *rados_storage, const uint64_t &max_write) { int ret_val = 0; - uint64_t write_buffer_size = current_object->get_mail_size() -1; // write buffer size needs to be length -1 + uint64_t write_buffer_size = current_object->get_mail_size(); assert(max_write > 0); @@ -788,13 +788,12 @@ int rbox_save_finish(struct mail_save_context *_ctx) { if (r_ctx->ctx.data.output != r_ctx->output_stream ) { /* e.g. zlib plugin had changed this. make sure we successfully write the trailer. */ - i_info("check state2 %ld",r_ctx->ctx.data.output); - + i_debug("check state2 %ld",r_ctx->ctx.data.output); ret = o_stream_finish(r_ctx->ctx.data.output); } else if(r_ctx->ctx.data.output != NULL) { //TODO: check for error in the stream before using flush /* no plugins - flush the output so far */ - + i_debug("no plugins flish the output so far %ld",r_ctx->ctx.data.output); ret = o_stream_flush(r_ctx->ctx.data.output); } From e6e6685af101b2cf51e26c7f5b278372b9621a6d Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Fri, 16 Dec 2022 20:56:57 +0100 Subject: [PATCH 13/18] Bugfix 355 fix buffersize write method (#363) * #355 fix buffersize for write method 1 and 2 * 355 only use append write, clear read buffer in case of read timeout * #355 check for gzip and fix invalid trailer * #355 fix unit tests. * #355 fix call to operate * 355 stream size for uncompressed mails * #355 additional tests --- CHANGELOG.md | 4 +- src/librmb/rados-storage-impl.cpp | 20 ++ src/librmb/rados-storage-impl.h | 4 + src/librmb/rados-storage.h | 15 + src/storage-rbox/rbox-mail.cpp | 123 ++++++-- src/storage-rbox/rbox-mail.h | 9 + src/storage-rbox/rbox-save.cpp | 276 ++---------------- src/storage-rbox/rbox-sync.cpp | 1 - src/tests/mocks/mock_test.h | 4 + .../test_storage_mock_rbox.cpp | 143 +++++---- .../test_storage_mock_rbox_bugs.cpp | 33 +-- .../testdata/gzip_invalid_trailer.mail | Bin 0 -> 64524 bytes .../testdata/gzip_valid_trailer.mail | Bin 0 -> 64525 bytes src/tests/test-utils/it_utils.cpp | 2 + 14 files changed, 284 insertions(+), 350 deletions(-) create mode 100644 src/tests/storage-mock-rbox/testdata/gzip_invalid_trailer.mail create mode 100644 src/tests/storage-mock-rbox/testdata/gzip_valid_trailer.mail diff --git a/CHANGELOG.md b/CHANGELOG.md index e18f58ac..a8536b6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Change Log ## [0.0.47](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.47) (2022-12-05) - #355 fix gzip trailer when stream is empty -- fix save_method 1+2 buffersize (1 byte short) + fix save_method 1+2 buffersize (1 byte short) + bugfix-355-fix-buffersize-write-method +## [0.0.46](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) - #349 bugfix doveadm rmb create ceph index validate object metadata ## [0.0.45](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.45) (2022-11-22) diff --git a/src/librmb/rados-storage-impl.cpp b/src/librmb/rados-storage-impl.cpp index 4bbe5e54..250aa0ce 100644 --- a/src/librmb/rados-storage-impl.cpp +++ b/src/librmb/rados-storage-impl.cpp @@ -147,6 +147,26 @@ int RadosStorageImpl::delete_mail(const std::string &oid) { return get_io_ctx().remove(oid); } +bool RadosStorageImpl::execute_operation(std::string &oid, librados::ObjectWriteOperation *write_op_xattr) { + if (!cluster->is_connected() || !io_ctx_created) { + return false; + } + return get_io_ctx().operate(oid, write_op_xattr) >=0 ? true : false; +} + +bool RadosStorageImpl::append_to_object(std::string &oid, librados::bufferlist &bufferlist, int length) { + if (!cluster->is_connected() || !io_ctx_created) { + return false; + } + return get_io_ctx().append(oid, bufferlist, length) >=0 ? true : false; +} +int RadosStorageImpl::read_operate(const std::string &oid, librados::ObjectReadOperation *read_operation, librados::bufferlist *bufferlist) { +if (!cluster->is_connected() || !io_ctx_created) { + return -1; + } + return get_io_ctx().operate(oid, read_operation, bufferlist); +} + int RadosStorageImpl::aio_operate(librados::IoCtx *io_ctx_, const std::string &oid, librados::AioCompletion *c, librados::ObjectWriteOperation *op) { if (!cluster->is_connected() || !io_ctx_created) { diff --git a/src/librmb/rados-storage-impl.h b/src/librmb/rados-storage-impl.h index c4320859..c070b43d 100644 --- a/src/librmb/rados-storage-impl.h +++ b/src/librmb/rados-storage-impl.h @@ -92,6 +92,10 @@ class RadosStorageImpl : public RadosStorage { std::set ceph_index_read() override; int ceph_index_delete() override; + bool execute_operation(std::string &oid, librados::ObjectWriteOperation *write_op_xattr) override; + bool append_to_object(std::string &oid, librados::bufferlist &bufferlist, int length) override; + int read_operate(const std::string &oid, librados::ObjectReadOperation *read_operation, librados::bufferlist *bufferlist) override; + private: int create_connection(const std::string &poolname,const std::string &index_pool); diff --git a/src/librmb/rados-storage.h b/src/librmb/rados-storage.h index fcc0690d..909e036d 100644 --- a/src/librmb/rados-storage.h +++ b/src/librmb/rados-storage.h @@ -232,6 +232,16 @@ underTest.ceph_index_add("dkfkjdf") * @return linux errorcode or 0 if successful * */ virtual int read_mail(const std::string &oid, librados::bufferlist *buffer) = 0; + + /*! read the complete mail object into bufferlist + * + * @param[in] oid unique object identifier + * @param[in] read_operation read operation + * @param[out] buffer valid ptr to bufferlist. + * @return linux errorcode or 0 if successful + * */ + virtual int read_operate(const std::string &oid, librados::ObjectReadOperation *read_operation, librados::bufferlist *bufferlist) = 0; + /*! move a object from the given namespace to the other, updates the metadata given in to_update list * * @param[in] src_oid unique identifier of source object @@ -268,6 +278,11 @@ underTest.ceph_index_add("dkfkjdf") * */ virtual bool save_mail(librados::ObjectWriteOperation *write_op_xattr, RadosMail *mail) = 0; + + virtual bool execute_operation(std::string &oid, librados::ObjectWriteOperation *write_op_xattr) = 0; + + virtual bool append_to_object(std::string &oid, librados::bufferlist &bufferlist, int length) = 0; + /*! create a new RadosMail * create new rados Mail Object. * return pointer to mail object or nullptr diff --git a/src/storage-rbox/rbox-mail.cpp b/src/storage-rbox/rbox-mail.cpp index ed04d4b3..e78371fe 100644 --- a/src/storage-rbox/rbox-mail.cpp +++ b/src/storage-rbox/rbox-mail.cpp @@ -452,13 +452,9 @@ static int read_mail_from_storage(librmb::RadosStorage *rados_storage, read_mail->read(0, INT_MAX, rmail->rados_mail->get_mail_buffer(), &read_err); read_mail->stat(psize, save_date, &stat_err); - //TODO: refactore to use operate instead of aio_operate. - librados::AioCompletion *completion = librados::Rados::aio_create_completion(); - int ret = rados_storage->get_io_ctx().aio_operate(*rmail->rados_mail->get_oid(), completion, read_mail, + int ret = rados_storage->read_operate(*rmail->rados_mail->get_oid(), read_mail, rmail->rados_mail->get_mail_buffer()); - completion->wait_for_complete_and_cb(); - ret = completion->get_return_value(); - completion->release(); + delete read_mail; return ret; @@ -506,19 +502,6 @@ static int rbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, s ret = read_mail_from_storage(rados_storage, rmail,&psize,&save_date); - /* duplicate code: get_attribute - librados::ObjectReadOperation *read_mail = new librados::ObjectReadOperation(); - read_mail->read(0, INT_MAX, rmail->rados_mail->get_mail_buffer(), &read_err); - read_mail->stat(&psize, &save_date, &stat_err); - - librados::AioCompletion *completion = librados::Rados::aio_create_completion(); - ret = rados_storage->get_io_ctx().aio_operate(*rmail->rados_mail->get_oid(), completion, read_mail, - rmail->rados_mail->get_mail_buffer()); - completion->wait_for_complete_and_cb(); - ret = completion->get_return_value(); - completion->release(); - delete read_mail; - */ if (ret < 0) { if (ret == -ENOENT) { // This can happen, if we have more then 2 processes running at the same time. @@ -533,7 +516,7 @@ static int rbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, s return -1; } else if(ret == -ETIMEDOUT) { - int max_retry = 10; + int max_retry = 10; //TODO FIX for(int i=0;i= 0){ @@ -543,6 +526,8 @@ static int rbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, s i_warning("READ TIMEOUT retry(%d) %d reading mail object %s ",i, ret,rmail->rados_mail != NULL ? rmail->rados_mail->to_string(" ").c_str() : " no rados_mail"); // wait random time before try again!! usleep(((rand() % 5) + 1) * 10000); + // clear the read buffer in case of timeout + rmail->rados_mail->get_mail_buffer()->clear(); } if(ret <0){ @@ -582,11 +567,33 @@ static int rbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, s return -1; } + i_debug("reading stream for oid: %s, phy: %d, buffer: %d", rmail->rados_mail->get_oid()->c_str(), + physical_size, + rmail->rados_mail->get_mail_buffer()->length()); + // validates if object is in zlib format (first 2 byte) + bool isGzip = check_is_zlib(rmail->rados_mail->get_mail_buffer()); + if(isGzip) { + uint32_t result = zlib_trailer_msg_length(rmail->rados_mail->get_mail_buffer(),physical_size); + + // get mails real physical size and compare against trailer length + uoff_t real_physical_size; + rbox_mail_get_physical_size(_mail, &real_physical_size); + // in case we have corrupt trailer, + if(result-real_physical_size > zlib_header_length(rmail->rados_mail->get_mail_buffer())) { + i_warning("zlib size check failed %d trailer not as expected, fixing by adding 0x00 to msb",(result-real_physical_size)); + rmail->rados_mail->get_mail_buffer()->append(0x00); + physical_size+=1; + } + } + if (get_mail_stream(rmail, rmail->rados_mail->get_mail_buffer(), physical_size, &input) < 0) { + i_debug("get mail failed"); FUNC_END_RET("ret == -1"); delete rmail->rados_mail->get_mail_buffer(); return -1; } + + i_debug("get mail failed retval of get_mail_stream %d",ret); data->stream = input; index_mail_set_read_buffer_size(_mail, input); @@ -595,6 +602,82 @@ static int rbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, s FUNC_END(); return ret; } +uint32_t zlib_trailer_msg_length(librados::bufferlist* mail_buffer, int physical_size) { + unsigned char gzip_size[] = { + mail_buffer->c_str()[physical_size-1], + mail_buffer->c_str()[physical_size-2], + mail_buffer->c_str()[physical_size-3], + mail_buffer->c_str()[physical_size-4] + }; + uint32_t result = (gzip_size[0] << 24 | gzip_size[1] << 16 | gzip_size[2] << 8 | gzip_size[3]); + + i_debug("length of message(%d) last byte of trailer %d / %d / %d / %d sizeof(char %d), sizeof(unsingned int: %d) ", + result, + gzip_size[0], gzip_size[1], gzip_size[2], gzip_size[3], + sizeof(char), sizeof(unsigned int)); + return result; + +} +bool check_is_zlib(librados::bufferlist* mail_buffer) { + + unsigned char magic1 = mail_buffer->c_str()[0]; + unsigned char magic2 = mail_buffer->c_str()[1]; + + i_debug("checking for z_lib header magic bytes check %x : %x compared to %x : %x", + magic1,magic2, + 0x1f,0x8b); + + if(magic1 == 0x1f && magic2 == 0x8b){ + i_debug("magic bytes 0x1f 0x8b found"); + return true; + } + i_debug("magic bytes 0x1f 0x8b not found"); + return false; +} + +int zlib_header_length(librados::bufferlist* mail_buffer) { + + int header_length = 11; + const unsigned char FLG=mail_buffer->c_str()[3]; + + switch (FLG){ + //ETEXT + case (unsigned char) 0x01: + header_length=10; + break; + case (unsigned char) 0x02: + header_length=10 + 2; + break; + //FXTERA + case (unsigned char) 0x04: + header_length=header_extra_size(mail_buffer->c_str()); + break; + case (unsigned char) 0x08: + header_length=header_dynamic_size(mail_buffer->c_str()); + break; + case (unsigned char) 0x10: + header_length=header_dynamic_size(mail_buffer->c_str()); + break; + default: + break; + } + i_debug("found header Type %x header size is %d", FLG, header_length); + + return header_length; +} +int header_dynamic_size(const unsigned char *data){ + + int i=10 - 1; + do{ + i++; + }while(data[i]!=(unsigned char) 0 ); + return i++; +} + +int header_extra_size(const unsigned char *data){ + int extera_part_size= int(data[10] + data[11]); + return 10 + 2 + extera_part_size; +} // guid is saved in the obox header, and should be available when rbox_mail does exist. (rbox_get_index_record) int rbox_get_guid_metadata(struct rbox_mail *mail, const char **value_r) { diff --git a/src/storage-rbox/rbox-mail.h b/src/storage-rbox/rbox-mail.h index db5727f1..8201f1bc 100644 --- a/src/storage-rbox/rbox-mail.h +++ b/src/storage-rbox/rbox-mail.h @@ -40,4 +40,13 @@ extern int read_mail_from_storage(librmb::RadosStorage *rados_storage, struct rbox_mail *rmail, uint64_t *psize, time_t *save_date); + + +extern bool check_is_zlib(librados::bufferlist* mail_buffer); +extern int zlib_header_length(librados::bufferlist* mail_buffer); +extern uint32_t zlib_trailer_msg_length(librados::bufferlist* mail_buffer, int physical_size); + +extern int header_dynamic_size(const unsigned char *data); +extern int header_extra_size(const unsigned char *data); + #endif // SRC_STORAGE_RBOX_RBOX_MAIL_H_ diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 74371621..456d31a4 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -468,165 +468,6 @@ static void clean_up_write_finish(struct mail_save_context *_ctx) { FUNC_END(); } -int save_mail_async(RadosStorage *rados_storage, - RadosMail *current_object, - librados::ObjectWriteOperation *write_op_xattr, - const uint64_t &max_write) { - - librados::AioCompletion *completion = librados::Rados::aio_create_completion(); - - int ret_val = 0; - uint64_t write_buffer_size = current_object->get_mail_size(); - - assert(max_write > 0); - - if (write_buffer_size == 0 || max_write == 0) { - ret_val = -1; - i_debug("write_buffer_size == 0 or max_write <=0 < -1" ); - return ret_val; - } - - ret_val = rados_storage->aio_operate(&rados_storage->get_io_ctx(),*current_object->get_oid(), completion, write_op_xattr); - - if(ret_val< 0){ - i_debug("write metadata did not work: %d",ret_val); - ret_val = -1; - return ret_val; - } - - uint64_t rest = write_buffer_size % max_write; - int div = write_buffer_size / max_write + (rest > 0 ? 1 : 0); - for (int i = 0; i < div; ++i) { - - // split the buffer. - librados::bufferlist tmp_buffer; - - librados::ObjectWriteOperation write_op; - - int offset = i * max_write; - - uint64_t length = max_write; - if (write_buffer_size < ((i + 1) * length)) { - length = rest; - } - - if (div == 1) { - write_op.write(0, *current_object->get_mail_buffer()); - } else { - i_debug("write chunk size %d, offset=%d,lenght=%d",write_buffer_size,offset,length); - if(offset + length > write_buffer_size){ - i_error("offset and length (%d) is bigger then write_buffer size (%d)", (offset+length), write_buffer_size); - return -1; - }else{ - tmp_buffer.substr_of(*current_object->get_mail_buffer(), offset, length); - } - - if(tmp_buffer.length() == 0){ - i_info("save buffer from %d to %d is empty => done",offset, length); - break; - } - write_op.write(offset, tmp_buffer); - - } - - ret_val = rados_storage->aio_operate(&rados_storage->get_io_ctx(),*current_object->get_oid(), completion, &write_op); - - i_debug("append mail (aio_operate) return value: %d",ret_val); - if(ret_val < 0){ - ret_val = -1; - break; - } - } - - bool failed = rados_storage->wait_for_write_operations_complete(completion,nullptr); - - if(ret_val >= 0){ - ret_val = failed ? -1 : 0; - i_debug("completion return value %d",ret_val); - } - - // deprecated unused - current_object->set_write_operation(nullptr); - current_object->set_completion(nullptr); - current_object->set_active_op(0); - // free mail's buffer cause we don't need it anymore - librados::bufferlist *mail_buffer = current_object->get_mail_buffer(); - delete mail_buffer; - current_object->set_mail_buffer(nullptr); - - return ret_val; -} - -int save_mail_sync(RadosStorage *rados_storage, - RadosMail *current_object, - librados::ObjectWriteOperation *write_op_xattr, - const uint64_t &max_write) { - - int ret_val = 0; - uint64_t write_buffer_size = current_object->get_mail_size(); - - assert(max_write > 0); - - if (write_buffer_size == 0 || max_write == 0) { - ret_val = -1; - i_debug("write_buffer_size == 0 or max_write <=0 < -1" ); - return ret_val; - } - - ret_val = rados_storage->get_io_ctx().operate(*current_object->get_oid(), write_op_xattr); - - if(ret_val< 0){ - i_debug("write metadata did not work: %d",ret_val); - ret_val = -1; - return ret_val; - } - - uint64_t rest = write_buffer_size % max_write; - int div = write_buffer_size / max_write + (rest > 0 ? 1 : 0); - for (int i = 0; i < div; ++i) { - - // split the buffer. - librados::bufferlist tmp_buffer; - librados::ObjectWriteOperation write_op; - - int offset = i * max_write; - - uint64_t length = max_write; - if (write_buffer_size < ((i + 1) * length)) { - length = rest; - } - - if (div == 1) { - write_op.write(0, *current_object->get_mail_buffer()); - } else { - i_debug("write chunk size %d, offset=%d,lenght=%d",write_buffer_size,offset,length); - if(offset + length > write_buffer_size){ - i_error("offset and length (%d) is bigger then write_buffer size (%d)", (offset+length), write_buffer_size); - return -1; - }else{ - tmp_buffer.substr_of(*current_object->get_mail_buffer(), offset, length); - } - write_op.write(offset, tmp_buffer); - } - ret_val = rados_storage->get_io_ctx().operate(*current_object->get_oid(), &write_op); - i_debug("append mail (operate) return value: %d",ret_val); - if(ret_val < 0){ - ret_val = -1; - break; - } - } - // deprecated unused - current_object->set_write_operation(nullptr); - current_object->set_completion(nullptr); - current_object->set_active_op(0); - - i_debug("freeing mailbuffer"); - // free mail's buffer cause we don't need it anymore - librados::bufferlist *mail_buffer = current_object->get_mail_buffer(); - delete mail_buffer; - - return ret_val; -} int save_mail_write_append(RadosStorage *rados_storage, RadosMail *current_object, @@ -634,7 +475,7 @@ int save_mail_write_append(RadosStorage *rados_storage, const uint64_t &max_write) { int ret_val = 0; - uint64_t write_buffer_size = current_object->get_mail_size() -1; + uint64_t write_buffer_size = current_object->get_mail_size(); assert(max_write > 0); @@ -644,7 +485,7 @@ int save_mail_write_append(RadosStorage *rados_storage, return ret_val; } - ret_val = rados_storage->get_io_ctx().operate(*current_object->get_oid(), write_op_xattr); + ret_val = rados_storage->execute_operation(*current_object->get_oid(), write_op_xattr); if(ret_val< 0){ i_debug("write metadata did not work: %d",ret_val); @@ -670,18 +511,25 @@ int save_mail_write_append(RadosStorage *rados_storage, if (div == 1) { write_op.write(0, *current_object->get_mail_buffer()); - ret_val = rados_storage->get_io_ctx().operate(*current_object->get_oid(), &write_op); - + ret_val = rados_storage->execute_operation(*current_object->get_oid(), &write_op) ? 0 : -1; } else { i_debug("write chunk size %d, offset=%d,lenght=%d",write_buffer_size,offset,length); if(offset + length > write_buffer_size){ i_error("offset and length (%d) is bigger then write_buffer size (%d)", (offset+length), write_buffer_size); return -1; }else{ - tmp_buffer.substr_of(*current_object->get_mail_buffer(), offset, length); + i_debug("trying to get substring of : mailsize %d, offset: %d, length %d", + current_object->get_mail_buffer()->length(), offset, length ); + if(offset + length > current_object->get_mail_buffer()->length() ){ + i_debug("new offset : %d",current_object->get_mail_buffer()->length()-offset); + tmp_buffer.substr_of(*current_object->get_mail_buffer(), offset,current_object->get_mail_buffer()->length() - offset ); + }else{ + tmp_buffer.substr_of(*current_object->get_mail_buffer(), offset, length); + } + } i_debug("tmp_buffer %d ",tmp_buffer.length()); - ret_val = rados_storage->get_io_ctx().append(*current_object->get_oid(), tmp_buffer, length); + ret_val = rados_storage->append_to_object(*current_object->get_oid(), tmp_buffer, length) ? 0 : -1; } i_debug("append mail (append) return value: %d",ret_val); if(ret_val < 0){ @@ -701,79 +549,6 @@ int save_mail_write_append(RadosStorage *rados_storage, return ret_val; } -int save_mail_write_ioctx(RadosStorage *rados_storage, - RadosMail *current_object, - librados::ObjectWriteOperation *write_op_xattr, - const uint64_t &max_write) { - - int ret_val = 0; - uint64_t write_buffer_size = current_object->get_mail_size() -1; - - assert(max_write > 0); - - if (write_buffer_size == 0 || max_write == 0) { - ret_val = -1; - i_debug("write_buffer_size == 0 or max_write <=0 < -1" ); - return ret_val; - } - - ret_val = rados_storage->get_io_ctx().operate(*current_object->get_oid(), write_op_xattr); - - if(ret_val< 0){ - i_debug("write metadata did not work: %d",ret_val); - ret_val = -1; - return ret_val; - } - - uint64_t rest = write_buffer_size % max_write; - int div = write_buffer_size / max_write + (rest > 0 ? 1 : 0); - for (int i = 0; i < div; ++i) { - - // split the buffer. - librados::bufferlist tmp_buffer; - - librados::ObjectWriteOperation write_op; - - int offset = i * max_write; - - uint64_t length = max_write; - if (write_buffer_size < ((i + 1) * length)) { - length = rest; - } - - if (div == 1) { - write_op.write(0, *current_object->get_mail_buffer()); - ret_val = rados_storage->get_io_ctx().operate(*current_object->get_oid(), &write_op); - - } else { - i_debug("write chunk size %d, offset=%d,lenght=%d",write_buffer_size,offset,length); - if(offset + length > write_buffer_size){ - i_error("offset and length (%d) is bigger then write_buffer size (%d)", (offset+length), write_buffer_size); - return -1; - }else{ - tmp_buffer.substr_of(*current_object->get_mail_buffer(), offset, length); - } - i_debug("tmp_buffer %d ",tmp_buffer.length()); - ret_val = rados_storage->get_io_ctx().write(*current_object->get_oid(), tmp_buffer, length, offset); - } - i_debug("append mail (io_ctx->write) return value: %d",ret_val); - if(ret_val < 0){ - ret_val = -1; - break; - } - } - // deprecated unused - current_object->set_write_operation(nullptr); - current_object->set_completion(nullptr); - current_object->set_active_op(0); - - i_debug("freeing mailbuffer"); - // free mail's buffer cause we don't need it anymore - librados::bufferlist *mail_buffer = current_object->get_mail_buffer(); - delete mail_buffer; - - return ret_val; -} int rbox_save_finish(struct mail_save_context *_ctx) { FUNC_START(); @@ -839,7 +614,7 @@ int rbox_save_finish(struct mail_save_context *_ctx) { if (!zlib_plugin_active) { // write \0 to ceph (length()+1) if stream is not binary - r_ctx->rados_mail->set_mail_size(r_ctx->output_stream->offset + 1); + r_ctx->rados_mail->set_mail_size(r_ctx->output_stream->offset); } else { // binary stream, do not modify the length of stream. @@ -854,7 +629,7 @@ int rbox_save_finish(struct mail_save_context *_ctx) { r_storage->ms->get_storage()->save_metadata(&write_op, r_ctx->rados_mail); int max_object_size = r_storage->s->get_max_object_size(); - i_debug("max_object_size %d mail_size %d",max_object_size, r_ctx->rados_mail->get_mail_size() ); + i_debug("oid: %s, max_object_size %d mail_size %d",r_ctx->rados_mail->get_oid()->c_str(), max_object_size, r_ctx->rados_mail->get_mail_size() ); if(max_object_size < r_ctx->rados_mail->get_mail_size()) { i_error("configured CEPH Object size %d < then mail size %d ", r_storage->s->get_max_object_size(), r_ctx->rados_mail->get_mail_size() ); mail_set_critical(r_ctx->ctx.dest_mail, "write(%s) failed: %s", o_stream_get_name(r_ctx->ctx.data.output),"MAX OBJECT SIZE REACHED"); @@ -868,24 +643,9 @@ int rbox_save_finish(struct mail_save_context *_ctx) { if(config_chunk_size > r_storage->s->get_max_write_size_bytes()){ config_chunk_size = r_storage->s->get_max_write_size_bytes(); } - i_debug("max chunk write size: %d ", config_chunk_size ); - uint32_t write_method = r_storage->config->get_write_method(); - - int ret = 0; - i_debug("write method: %d",write_method); - if(write_method == 0) { - ret = save_mail_sync(r_storage->s,r_ctx->rados_mail, &write_op, config_chunk_size); - }else if (write_method == 1){ - ret = save_mail_async(r_storage->s,r_ctx->rados_mail, &write_op, config_chunk_size); - }else if (write_method == 2){ - ret = save_mail_write_append(r_storage->s,r_ctx->rados_mail, &write_op, config_chunk_size); - }else if (write_method == 3){ - ret = save_mail_write_ioctx(r_storage->s,r_ctx->rados_mail, &write_op, config_chunk_size); - }else { - r_ctx->failed = true; - i_error("SAVE_MAIL result: %d no save method set", r_ctx->failed); - } - + + int ret = save_mail_write_append(r_storage->s,r_ctx->rados_mail, &write_op, config_chunk_size); + r_ctx->failed = ret < 0; i_debug("SAVE_MAIL result: %d", r_ctx->failed); } diff --git a/src/storage-rbox/rbox-sync.cpp b/src/storage-rbox/rbox-sync.cpp index 3374b99c..e86054ef 100644 --- a/src/storage-rbox/rbox-sync.cpp +++ b/src/storage-rbox/rbox-sync.cpp @@ -528,7 +528,6 @@ int rbox_sync_finish(struct rbox_sync_context **_ctx, bool success) { } else { mail_index_sync_rollback(&ctx->index_sync_ctx); } - i_info("EXPUNGE: calling deinit"); index_storage_expunging_deinit(&ctx->rbox->box); if (array_is_created(&ctx->expunged_items)) { diff --git a/src/tests/mocks/mock_test.h b/src/tests/mocks/mock_test.h index 0e3f5f0a..ec0c8f7c 100644 --- a/src/tests/mocks/mock_test.h +++ b/src/tests/mocks/mock_test.h @@ -44,6 +44,9 @@ class RadosStorageMock : public RadosStorage { MOCK_METHOD0(get_max_write_size_bytes, int()); MOCK_METHOD0(get_max_object_size, int()); + MOCK_METHOD2(execute_operation, bool(std::string &oid, librados::ObjectWriteOperation *write_op_xattr)); + + MOCK_METHOD3(append_to_object, bool(std::string &oid, librados::bufferlist &bufferlist, int length)); MOCK_METHOD3(split_buffer_and_exec_op, int(RadosMail *current_object, librados::ObjectWriteOperation *write_op_xattr, const uint64_t &max_write)); @@ -55,6 +58,7 @@ class RadosStorageMock : public RadosStorage { MOCK_METHOD1(find_mails, librados::NObjectIterator(const RadosMetadata *attr)); MOCK_METHOD1(open_connection, int(const std::string &poolname)); MOCK_METHOD2(open_connection, int(const std::string &poolname, const std::string &index_pool)); + MOCK_METHOD3(read_operate, int(const std::string &oid, librados::ObjectReadOperation *read_operation,librados::bufferlist *bufferlist)); MOCK_METHOD4(find_mails_async, std::set(const RadosMetadata *attr, std::string &pool_name,int num_threads, void (*ptr)(std::string&))); diff --git a/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp b/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp index be3176cb..0152e5a7 100644 --- a/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp +++ b/src/tests/storage-mock-rbox/test_storage_mock_rbox.cpp @@ -12,7 +12,7 @@ #include "../storage-mock-rbox/TestCase.h" #include "gtest/gtest.h" #include "gmock/gmock.h" - +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" // turn off warnings for Dovecot :-( #pragma GCC diagnostic ignored "-Wundef" // turn off warnings for Dovecot :-( @@ -41,7 +41,7 @@ extern "C" { #include "rbox-storage.hpp" #include "rbox-save.h" - +#include "rbox-mail.h" #include "../mocks/mock_test.h" #include "rados-dovecot-ceph-cfg-impl.h" #include "../../storage-rbox/istream-bufferlist.h" @@ -76,6 +76,69 @@ TEST_F(StorageTest, create_rados_mail) { delete mail; } } + +TEST_F(StorageTest, test_gzip_read_valid) { + + std::streampos size; + char *message; + + librados::bufferlist bl; + char buffer[256]; + char *val = getcwd(buffer, sizeof(buffer)); + if (val) { + std::cout << buffer << std::endl; + } + std::ifstream fileReader("/src/tests/storage-mock-rbox/testdata/gzip_valid_trailer.mail",std::ios::binary|std::ios::ate); + if (fileReader){ + auto fileSize = fileReader.tellg(); + fileReader.seekg(std::ios::beg); + std::string content(fileSize,0); + fileReader.read(&content[0],fileSize); + bl.append(content); + } + bool test = check_is_zlib(&bl); + ASSERT_EQ(true, test); + + int header_length = zlib_header_length(&bl); + ASSERT_EQ(11, header_length); + + uint32_t trailer_msg_length = zlib_trailer_msg_length(&bl,bl.length()); + ASSERT_EQ(118536, trailer_msg_length); +} +TEST_F(StorageTest, test_gzip_read_invalid) { + + std::streampos size; + char *message; + + + librados::bufferlist bl; + + std::ifstream fileReader("/src/tests/storage-mock-rbox/testdata/gzip_invalid_trailer.mail",std::ios::binary|std::ios::ate); + if (fileReader){ + auto fileSize = fileReader.tellg(); + fileReader.seekg(std::ios::beg); + std::string content(fileSize,0); + fileReader.read(&content[0],fileSize); + bl.append(content); + } + bool test = check_is_zlib(&bl); + ASSERT_EQ(true, test); + + int header_length = zlib_header_length(&bl); + ASSERT_EQ(11, header_length); + + uint32_t trailer_msg_length = zlib_trailer_msg_length(&bl,bl.length()); + ASSERT_EQ(true, trailer_msg_length > 118536); + + // FIX Trailer by adding 0x00 as last byte. + bl.append(0x00); + + trailer_msg_length = zlib_trailer_msg_length(&bl,bl.length()); + ASSERT_EQ(118536, trailer_msg_length); + +} + + /** * Error test: * - open_connection to rados will fail with -1 . @@ -112,7 +175,7 @@ TEST_F(StorageTest, mail_save_to_inbox_storage_mock_no_rados_available) { librmbtest::RadosStorageMock *storage_mock = new librmbtest::RadosStorageMock(); // first call to open_connection will fail! - EXPECT_CALL(*storage_mock, open_connection("mail_storage", "ceph", "client.admin")) + EXPECT_CALL(*storage_mock, open_connection("mail_storage",_, "ceph", "client.admin")) .Times(AtLeast(1)) .WillOnce(Return(-1)); @@ -210,7 +273,7 @@ TEST_F(StorageTest, save_mail_fail_test) { librados::IoCtx test_ioctx; EXPECT_CALL(*storage_mock, get_io_ctx()).WillRepeatedly(ReturnRef(test_ioctx)); - EXPECT_CALL(*storage_mock, open_connection("mail_storage", "ceph", "client.admin")) + EXPECT_CALL(*storage_mock, open_connection("mail_storage",_, "ceph", "client.admin")) .Times(AtLeast(1)) .WillRepeatedly(Return(0)); @@ -222,9 +285,9 @@ TEST_F(StorageTest, save_mail_fail_test) { .Times(AtLeast(1)) .WillRepeatedly(Return(65000)); - - EXPECT_CALL(*storage_mock, aio_operate(_,_,_,_)).Times(AtLeast(1)).WillRepeatedly(Return(0)); - EXPECT_CALL(*storage_mock, wait_for_write_operations_complete(_,_)).WillRepeatedly(Return(true));//failed = false + EXPECT_CALL(*storage_mock, execute_operation(_,_)).WillRepeatedly(Return(true)); + // save will fail. + EXPECT_CALL(*storage_mock, append_to_object(_,_,_)).WillRepeatedly(Return(false)); librmb::RadosMail *test_obj = new librmb::RadosMail(); test_obj->set_mail_buffer(nullptr); @@ -306,14 +369,7 @@ TEST_F(StorageTest, save_mail_fail_test) { i_stream_unref(&input); mailbox_free(&box); - if (test_obj->get_mail_buffer() != nullptr) { - delete test_obj->get_mail_buffer(); - } - delete test_obj; - if (test_obj2->get_mail_buffer() != nullptr) { - delete test_obj2->get_mail_buffer(); - } - delete test_obj2; + } /** * Error test: @@ -355,7 +411,7 @@ TEST_F(StorageTest, write_op_fails) { librados::IoCtx test_ioctx; EXPECT_CALL(*storage_mock, get_io_ctx()).WillRepeatedly(ReturnRef(test_ioctx)); - EXPECT_CALL(*storage_mock, open_connection("mail_storage", "ceph", "client.admin")) + EXPECT_CALL(*storage_mock, open_connection("mail_storage",_, "ceph", "client.admin")) .Times(AtLeast(1)) .WillRepeatedly(Return(0)); @@ -368,12 +424,9 @@ TEST_F(StorageTest, write_op_fails) { .Times(AtLeast(1)) .WillRepeatedly(Return(65000)); - - EXPECT_CALL(*storage_mock, aio_operate(_,_,_,_)).Times(AtLeast(1)).WillRepeatedly(Return(0)); - EXPECT_CALL(*storage_mock, wait_for_write_operations_complete(_,_)).WillRepeatedly(Return(false));//failed = false - - - EXPECT_CALL(*storage_mock, read_mail(_, _)).WillRepeatedly(Return(-2)); + EXPECT_CALL(*storage_mock, execute_operation(_,_)).WillRepeatedly(Return(false)); + // save will fail. + EXPECT_CALL(*storage_mock, append_to_object(_,_,_)).WillRepeatedly(Return(true)); librmb::RadosMail *test_obj = new librmb::RadosMail(); test_obj->set_mail_buffer(nullptr); @@ -472,20 +525,9 @@ TEST_F(StorageTest, write_op_fails) { EXPECT_GE(ret, -1); } - i_info("unref"); i_stream_unref(&input); - i_info("pre mailbox freed"); mailbox_free(&box); - i_info("mailbox freed"); - if (test_obj->get_mail_buffer() != nullptr) { - delete test_obj->get_mail_buffer(); - } - delete test_obj; - if (test_obj2->get_mail_buffer() != nullptr) { - delete test_obj2->get_mail_buffer(); - } - delete test_obj2; - i_info("done"); + SUCCEED() << "should be ok here"; } @@ -571,21 +613,20 @@ TEST_F(StorageTest, mock_copy_failed_due_to_rados_err) { .WillOnce(Return(test_obj_save)) .WillOnce(Return(test_obj_save2)); - EXPECT_CALL(*storage_mock, aio_operate(_,_,_,_)).Times(AtLeast(1)).WillRepeatedly(Return(0)); - EXPECT_CALL(*storage_mock, wait_for_write_operations_complete(_,_)).WillRepeatedly(Return(false));//failed = false - // testdata - testutils::ItUtils::add_mail(message, mailbox, StorageTest::s_test_mail_user->namespaces, storage_mock); + EXPECT_CALL(*storage_mock, open_connection("mail_storage",_, "ceph", "client.admin")) + .Times(AtLeast(1)) + .WillRepeatedly(Return(0)); - if (test_obj_save->get_mail_buffer() != nullptr) { - delete test_obj_save->get_mail_buffer(); - } - delete test_obj_save; - if (test_obj_save2->get_mail_buffer() != nullptr) { - delete test_obj_save2->get_mail_buffer(); - } - delete test_obj_save2; + EXPECT_CALL(*storage_mock, execute_operation(_,_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*storage_mock, append_to_object(_,_,_)).WillRepeatedly(Return(true)); + + i_info("adding mail"); + // testdata + testutils::ItUtils::add_mail(message, mailbox, StorageTest::s_test_mail_user->namespaces, storage_mock); + i_info("adding mail done"); + search_args = mail_search_build_init(); sarg = mail_search_build_add(search_args, SEARCH_ALL); ASSERT_NE(sarg, nullptr); @@ -650,12 +691,11 @@ TEST_F(StorageTest, mock_copy_failed_due_to_rados_err) { EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); EXPECT_CALL(*cfg_mock, get_user_suffix()).WillRepeatedly(ReturnRef(suffix)); EXPECT_CALL(*cfg_mock, get_write_method()).WillRepeatedly(Return(1)); - EXPECT_CALL(*cfg_mock, get_chunk_size()).WillOnce(Return(100)); storage->ns_mgr->set_config(cfg_mock); storage->config = cfg_mock; - + i_info("mailbox open"); if (mailbox_open(box) < 0) { i_error("######################### Opening mailbox %s failed: %s", mailbox, mailbox_get_last_internal_error(box, NULL)); FAIL() << " Forcing a resync on mailbox INBOX Failed"; @@ -668,7 +708,7 @@ TEST_F(StorageTest, mock_copy_failed_due_to_rados_err) { memset(reason, '\0', sizeof(reason)); desttrans = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, reason); #endif - + i_info("mailbox search init "); search_ctx = mailbox_search_init(desttrans, search_args, NULL, static_cast(0), NULL); mail_search_args_unref(&search_args); int ret2 = 0; @@ -694,7 +734,7 @@ TEST_F(StorageTest, mock_copy_failed_due_to_rados_err) { i_info("closing mailbox"); // mail should be marked as expunged!!! mailbox_free(&box); - + i_info("mailbox closed freeing buffer"); if (test_object->get_mail_buffer() != nullptr) { delete test_object->get_mail_buffer(); } @@ -704,6 +744,8 @@ TEST_F(StorageTest, mock_copy_failed_due_to_rados_err) { } delete test_object2; i_info("#########################done."); + + } @@ -773,7 +815,6 @@ TEST_F(StorageTest, save_mail_cancel) { EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); EXPECT_CALL(*cfg_mock, get_user_suffix()).WillRepeatedly(ReturnRef(suffix)); EXPECT_CALL(*cfg_mock, get_write_method()).WillRepeatedly(Return(1)); - EXPECT_CALL(*cfg_mock, get_chunk_size()).WillOnce(Return(100)); storage->ns_mgr->set_config(cfg_mock); diff --git a/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp b/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp index 9f65a03e..2798be4f 100644 --- a/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp +++ b/src/tests/storage-mock-rbox/test_storage_mock_rbox_bugs.cpp @@ -100,9 +100,7 @@ TEST_F(StorageTest, save_mail_rados_connection_failed) { delete storage->s; librmbtest::RadosStorageMock *storage_mock = new librmbtest::RadosStorageMock(); - EXPECT_CALL(*storage_mock, aio_operate(_,_,_,_)).Times(AtLeast(1)).WillRepeatedly(Return(0)); - EXPECT_CALL(*storage_mock, wait_for_write_operations_complete(_,_)).WillRepeatedly(Return(false));//failed = false - + librados::IoCtx test_ioctx; EXPECT_CALL(*storage_mock, get_io_ctx()).WillRepeatedly(ReturnRef(test_ioctx)); @@ -111,10 +109,14 @@ TEST_F(StorageTest, save_mail_rados_connection_failed) { EXPECT_CALL(*storage_mock, set_namespace(_)).Times(1); EXPECT_CALL(*storage_mock, get_namespace()).Times(0); - EXPECT_CALL(*storage_mock, delete_mail(Matcher(_))).Times(0); + EXPECT_CALL(*storage_mock, delete_mail(Matcher(_))).Times(1); EXPECT_CALL(*storage_mock, close_connection()).Times(0); + EXPECT_CALL(*storage_mock, execute_operation(_,_)).WillRepeatedly(Return(false)); + EXPECT_CALL(*storage_mock, append_to_object(_,_,_)).WillRepeatedly(Return(false)); + + EXPECT_CALL(*storage_mock, open_connection("mail_storage",_, "ceph", "client.admin")) .Times(AtLeast(1)) .WillRepeatedly(Return(0)); @@ -126,7 +128,6 @@ TEST_F(StorageTest, save_mail_rados_connection_failed) { EXPECT_CALL(*storage_mock, get_max_write_size_bytes()) .Times(AtLeast(1)) .WillRepeatedly(Return(10)); - librmb::RadosMail *test_obj = new librmb::RadosMail(); @@ -205,21 +206,14 @@ TEST_F(StorageTest, save_mail_rados_connection_failed) { } else { ret = 0; } - - i_info("ok done."); + i_info("ok done. %d",ret); EXPECT_GE(ret, -1); } + i_info("unref stream!"); i_stream_unref(&input); + i_info("mailbox free!"); mailbox_free(&box); - if (test_obj->get_mail_buffer() != nullptr) { - delete test_obj->get_mail_buffer(); - } - delete test_obj; - if (test_obj2->get_mail_buffer() != nullptr) { - delete test_obj2->get_mail_buffer(); - } - delete test_obj2; } /** @@ -260,9 +254,10 @@ TEST_F(StorageTest, save_mail_success) { librmbtest::RadosStorageMock *storage_mock = new librmbtest::RadosStorageMock(); - EXPECT_CALL(*storage_mock, aio_operate(_,_,_,_)).Times(AtLeast(1)).WillRepeatedly(Return(0)); EXPECT_CALL(*storage_mock, wait_for_write_operations_complete(_,_)).WillRepeatedly(Return(false));//failed = false librados::IoCtx io_ctx; + EXPECT_CALL(*storage_mock, execute_operation(_,_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*storage_mock, append_to_object(_,_,_)).WillRepeatedly(Return(true)); EXPECT_CALL(*storage_mock, get_io_ctx()).WillRepeatedly(ReturnRef(io_ctx)); @@ -312,7 +307,6 @@ TEST_F(StorageTest, save_mail_success) { std::string pool = "mail_storage"; std::string suffix = "_u"; EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); - EXPECT_CALL(*cfg_mock, get_object_search_method()).WillRepeatedly(Return(0)); EXPECT_CALL(*cfg_mock, get_rados_username()).WillRepeatedly(ReturnRef(user)); EXPECT_CALL(*cfg_mock, get_rados_cluster_name()).WillRepeatedly(ReturnRef(cluster)); EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); @@ -364,12 +358,13 @@ TEST_F(StorageTest, save_mail_success) { ret = 0; } - i_info("ok done."); + i_info("ok done. %d",ret); EXPECT_GE(ret, 0); } i_stream_unref(&input); + i_info("stream unref"); mailbox_free(&box); -i_info("mailbox free done"); + i_info("mailbox free done"); i_info("next delete test_obj"); delete test_obj; diff --git a/src/tests/storage-mock-rbox/testdata/gzip_invalid_trailer.mail b/src/tests/storage-mock-rbox/testdata/gzip_invalid_trailer.mail new file mode 100644 index 0000000000000000000000000000000000000000..bbef2efcaf582437956ee982d3435342e5a86253 GIT binary patch literal 64524 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 RckjRV-v0+g&er(|&jDLN|C0a! literal 0 HcmV?d00001 diff --git a/src/tests/storage-mock-rbox/testdata/gzip_valid_trailer.mail b/src/tests/storage-mock-rbox/testdata/gzip_valid_trailer.mail new file mode 100644 index 0000000000000000000000000000000000000000..9a76d23b087807d7388c17ce3e54ce36cbd9e2d5 GIT binary patch literal 64525 zcmV)4K+3-#iwFP!000001B|_0b0SNV=XpJ4R^OqlcQ1M@_jCYC@p5{5Y$8d7kU%67 zNJXvf1t2N~2qA$;1Z%UOeShwe0o3VPv(fX|p@7Vc2oHb#yL)gy{oJnK{z^ulFMt2| zKR>6N&;LIC8ZF;nr~futE&toMc=|fart9hRU;WkJKRz!mzTHnJQ$BkB`^WF~YWbsF zDUZj+;$&2=7JvNf|5Yqi{;k0OivRb2{q|qR`#-26 zxoCD*^IppyJ{yzQ-nakquhX||`nr0b{-p!_=SlJ8->WrWTwMIOfB*3`ef~dw6e>UB z>EuVLP%8Z>7XA*4e_xb-1O@)SrQ6x_e-x*sa6B!I3zhL`QY{xQM%8Jt#Mjg6^m#h1 zeg7s`RmECXR7X`k|@-I6_mA%T z^zE_pqch!XM(Om&zv{jGuT%*u|MmMf+4Hl9mWtu!1OfoKcep7F1Gi>|M-_;xf+(LVYT*Gv0C6?wTtS-Uw){t`X7H^)C$E)@n61w`}tS> z?%}WQdX~=KMxU$oU&+hrZTi>Ub~&D||Ne1K6o0XwzRv0kYWy(`xk^8fU1TTx)sz3< ztL-~t^Vs>zU-{?Szkd^^;6Z<}fd8dc{gYmH5VyLFVC)DkKQ)F zr|Z8OZ@EHKM`RxwZDar<9+-uG{V#cT8a1z5HHj%3b1=2cRE=4i zs=kknwQa6w^InE=tJ_W4m=QYo+yRcaD3Z>(^b32u(P#$x%J;?WZCnInjtI7;?I?|j^xWZ8`g`X4My{d3A zl+@4Nd*%Q9FRnV8Gpj&2#Qkwc8tYtmUl$Qkq2Y--g1b&X|Nc$yNKRuP=OA|`%GZC) z+KnV5Sbw+7mOo~v>+)l`_xc2Qk5~O(_`?0=V>1iBQSLlGTi2_yrV^r`yIu8Mt?QO& zR&{Lt+#K?;^nC&cC1?mnaP-i*C||(mdL8ou^FfJg|(r5 zNor`TgIU|CWjp%B5i~ELeUs|Ub}EbsP3t|;##>Vxn`_H-?$BdNwP&ML_J3q9g`0z} z?Y2d)WkG7bS~8rq-MJ}cW?=S1ZetIExw%eb#*7W`LwRC3sm*M6ZT6YL(=)rBy%mBc zZ9ZY_XvmY$tu>Jl#$?t|Q1v)M!2f#|-Gg4MFt+NBTE#SE-=PyWVkjE#CWWlY*j&ge z7$Ht&pH-)_??c@^;Wae7P|mJo44xS?JKjSa#&CSEcRKErIj}}iS?n0ZV2s$rvM#>i zNw>m#^)%iq@2S>O{#FafbSh_``c38ksMnN>m4g`DbD`=l zm0z8ulS;T%XRV)yPSzM(EM-=`bz>_{xq?+VO{m%!^5^fNeN*V-EQi`V)g&Tg{7cs- zv){=&c@i?0${4v;hQDP!PWQ@nXbvaeXnb}f*WW^!wX>;w?j&R`?^QGMURTE}PZnN7 zI#v6)*ON>!OWj{*$J9#c2)cf?kX4Z}G`^!TngC7bbf?bK?beZk`jJYhzdrXJ^a{5N z`l@=`Fvnj-S2Mswe2jR*EXG@h|)zd*=o$gVZD@QnuuaxqBiRUrpfBpW*_o^9% z*UrAlZN}NIIqua}`9H@8?#bNk_R~pW?x*UyRNiIXxx~oWs>fA|l^!A`a)r(|m#Lhu zbK2|Nl)oMCx!paE?c_M;3};jHiX+E(3!s`p7odB?pVx}I&-%%6*N*!xJf@#3r^vr^ zU8C{$Kkjf|r&7@Go~M+`qT@A{EVW~QTq*iWpD%XZkClIL8e1vn$IdBsMv+|Cc-!H) zSLNH$JeRRE4sdhu^6vTEdmrAn$YoFFEWDQ6&Z8|=9m11Tr{)~w?6&H0p@%;4=&!j? zR095>!x?9|C+2$2ajlN6{L6YgKZ{52Jsq#_+^f^dUI%F`nAGmTlrVl={bfC@>=Lm?R0Kixyb9(+~Runw9$DCgsR zSG?SN8awTs%f0Hpb-aIal-7Lzy~T8&=f0xO-uWfU=Wai}wt_Co-=1P#YaQj= z)AlGsUSl|0a+1oEe6uI2Q{A)mirf!Z3VP0Q5^}a%UTTlH!%>d?taAG$cPb{3?}dzX z;$GqNoI-h|rh9UxJ3SXWjzaG%g`NHvnyW{Dwm$jB>9MmX}wG?E+8Y z*|Oh9O{lA3j?Q;JMpr&I@RH*8S@%2V5HI&UQS`p*In@JPBf7<}>qt}8H5&8yF+HVp zzt3$7GZ(MQd$PJv<#)#^r&vH&<3>2ybH>j3*R407mHKJ%vPV|4*dleZ zUe!L1JG~t{-*?C7x(7~Eo_u7rPpSdDwsm?;*M0JQj~;^a0R7lUzCP3EImKlEy;SBh zrd-WPIiEvK?<1df&#l5=sZVh6O72rReXbVnq+7)QR{2)#bJV^k`OeV=9otjDae&4h zPG{y)d)kv+pKHwI{)AgpXDW9dEnLof{)wI+9M_(#v0r!Yr#hPIuJCmACLJAABk*}1 z`H6fV;}V^R_@_DMakASkF5^z+d$)>Q=W@OBTCP(?ud6=Gr81Y8JhJoGIZ4sT<{Wo8 zY62h0`>}E>ES%)6lkWFXr=Ibz=Q+o{@&w0gF51W?#95HshI*~z?@)?4EZ5sJKJvJe zYAe+PLUZ8=ziWBqqH}S&#&M3ft*0l*=fdZXg`MXso(MM{s-d5BdH>UvY(x z36!tFy7*rAn^!PR#l+lPgx3_qQ`Fl%TRB8IL`a@sk!#)@D%zEDeX(rZUudh&3U2R(-{g3d+dUAflT zF`cdMpVtI_|DjMW_xWBNy%e_x`FA~ z6-u1s-&-v=d*wA9<4il{_R7uPYa!J+$MyO>6W`&aEBrGZXXJWb#?CziZ@=8$oNTD0 zVeY}7af(QUvn|K5bGeX_9G^SQ?>U5h_sHDI#wa)YxiPknfsfY5+wYvWat`rpJgwVl zP|-DvEz+3#MqUFrjgU(vbYb^$ZgqEh8T2vH$u6Jx+)2bA+DIZ^_56=|%}Ic_F3wl* zc089gKR4BkM{Vt5U}mnn=Jwr3Y;M1m=H9!``QCAqaG0aHqou~-UVin}X|0@Yk{`@> z&vCEw9fXhbXv%5)v-tC*=Z=~pmv6a8cuhDyf4+}}avxbW<564dlX&up2bJb}#o4n} zBRF}^eTP5ocCI0hacE9UeUh8KlZad+I@@|~2^@u0zVkcIH9#&?ZcZ*H&^^hq?LYL| z+3q;YpX(Z@8BcfkJ#lti^4~`pI#J4Vgla43>HP8B)}A=V=f55I3EdsWr90q!CH?17 zgPiE1b5uVyw2u*7oTDSPf4Db%lA=N9=XtIKvs3;rzH1eZ(}JudHa9UzI-5XPDI@$ht-1Rd4gEQkeTt`M|0nRa5GdJoYD)Jj%DXsyb`G zJ8n01RgQylJLWme$0a9CpcLfK(boIt-dA*Sn6uw!c_Ob%oV0qbRC&&QiF3~&*D*qK z$6G2XUK23(*?ZOeXh!A9oEp418>+s)ql`*~W|IEUgseiXudc@NqEh-awwDw~vE1_M zK)I*;4^1IYbTo&Cx!>Xt46if&oOEpGb@;K5GUhCrUyt%Ve~u57VrNhGShlmR$FZSE zt}C5JfDY%GREKQ1Co1KXlDckh0d)AY=s|TWcj%M-+?@7ybjf{(;}aaM|KQd$E6(1(<7TQq;#T2zF}KcM?oS?{s~P0tll^x3+~W}WUK}5O z)GclV8#x<2X_XMN7~x9AX$ z;d)-v-E!WT&e6SR>@&ON$MU=7B38Abj_1ztHr?+d=j*foAHHOyAYF~4?8ysLDamyv|2p&NJc@9nV6Hj+Tx1u${m!GMW0c`6!f7u=*5uD= zNR=$d=kg&E@Sk_d=@i{9|M!Qy`+UiX*W^iR@&5Qba?;U49^14{y)@!4a*U6LeT#Xy+Jeu^p{-i>ou-A>bG3> z{y{mBUePW2Za4{1YwPJCvlD(k!`+-$mdk_aRPRlRtmVAqH3qMzb-R6bJg3sC`dn>B z?$L;>d2W;|jvA29ePri3MrnTT@hC63zawvXZ`1E{?)kC0BhDnVR_o%tjGQx$yQvS< zCwu*-7V(VNj=r(mjdOlJ_tTur=}3yNo&3`O#HIPJ{Zrok>bz#s_xbGocjvF39%7|^ zB!A3Vc+@|S356nVpU*9T&enO~Dtkxy_QR-F^7{I?Te+t2`|bEc?7$y0KSeM{-RktX zA5-TfeoZd_=XpUUz}QODv$(`t0Nn#;5sr3Fc+Ev9C#|f!=6r?|C+U;Vyb_&*@|tph z<4V0Vk4u~_b+h$WNx9n7LF~6mf>7#DZ_4R)nXTiD6UUsSSY_*nY+rqDe12q^sg&}(B>e0s^@sg(PxP^| z?vd>GQD5b{!|OB0d+>peLshOl9|(WQe)~_nEO|S2nhSH#y*ODdr#W?O&&SS&{t+YO zTI*W^Bv!{k+Wc)nIy%PGXI%J+^Q=W}&Z;&;ny z9e0KIvi#isIme@i?X>NgY^l%er1PJ$rxw9c*z@`6lT$8IET3DaleJR>?>R_yi{H;b zb*qo)^0A!%4qbA6F7MCw8(wl6LS~g)h}@QFY{YEc6ZCtPV&QWqukH%fN-AS+lY~;j zi{5_wt#cIhn#p~7r1>#&b=pb470T)ylo!=cR~gGCL}W}ndhvWb*XCR-tID<3G0I~9 zoqV7C6*tCFa&*vKYdL?-uR=#sn&YrZ7cCGMGCD`}m*c3$y1q6Nyf zQ(oy7qW9-Px`g<$*(kKvX?;R>Ow_ea?EzXkoqCblO&axV zqhg9#xo2i`^FWuAsZ`7-y;`Oen~y9Q*{(d{x@S9~Ezs*^o4e@yx4pe?*k|d)s;$f= zec_qun$p^=lFHVMDz#2lo|%`qxy`C2^EEg3bc3186pCC8SoiOtEvEL#_E%9qZ3Olq zjW(&>+V+7iFFMUCYctPkb+d1qR#GjS;=!cyc}@CivLxup6?B%-H`cPhY}B-k(6&;$ zw9(4mYF}O76caO(nge#RB;8U_ddiNKnQg*4%Z!m!t7gA8NfJQGvH8l_gP_fmZP_+g z_C1XUvhJ@mhH@!1zqIkv-laB6qlI*VWm&K{m9|L}de;sI)5@wtQ|z1PEVwn7ZP`uw z$SO)7#@_FQCz`S=J)!+o{$AK3wF_I9t~hp$|8mg-VX~}hV4E^_UpN7cKnGe^c&e1p zF9sc|33CYg>>9mntYVi0TXR|A6~5TF*gd;|=Jejd-wS)y6V6x&rC7*LIO0-zx}a1c zF)dT2uZ&JR6U63l>_w&@EHHa*`Jw-AXX&*2u(1eSoXXgGM@6}}C4F@C%gy8!#%(aR zEY-@GJxjOS$~KfcS?|y$*6xKK{IM{4+Y<93dk_jQLb>nXSm#1|(U>LAJeW)AsG~2A zC#E7`F7vXJ_Y)a`o;GyLPHd`Tx!QBRlb$S|$!vY^wZ5v?#X}%Fl9tK>wSN-1mtJH> z;A5Fw3`JB6Bfr{lM+ddadq?h|OANnGdfDWAHqhmXs6t|F?vy@fdeqoG`kj|fcglO^ zPCCjFX&qQKgHA+x_FOOCRcdFlE-0h??E1&Fb9$p)+wwW9;&b?1JL_`{8`I0<`12Tt ztm}(iOQAK~%QvuDZVBCL^m5T_^rCtUm)Y^8PL2VfJKK3jwfZq=LhaF3eV)^t-E#i7 z?kC?kZ^}jVK6+xHBO2jklaX?lHZiWsv9Rt8u<`9`T)F*#Gd6blyst-1Adk%2`N_S3QQUe4Bew z>W4WCr&pf&h~lR>Yvue{vGc;`r})cRrBf`#d+&v4Jbtv!xrJ04dWz8gIhI=bb05N6 zO4+p&&Bf~a8mfG3@qBdbGw;*q50oC~`B3MtC@1J_l`@_yecU1*ukMB8hx7ebZghT$ zdOqrnxYv9Ro^mSRIqs1ABgag+ygx@vybtYY?K}eYsl0zK>kuwI<=S1`dG2kgA17l9 zCrE@ZyryjNKZ*5JavYyK%6UKTxC+l}C)zqrasFG5b?D>W-yheV_+0o{|2$3ouE}RP zFUmiilpf>LoaRTL>CAW1rwIjf#KOlVyn3EfUr}l7q|+la|D=$5mwKI(cZWwgKlLQn zjuTE-IyyKS>l2*J>63)!eoXIWy4h;HnbSJ=9rX_7pxjqx3fc-1zgaa4X;EV=LFW z+2^>a-z}9+f9E;ByNJi>7AH%&WVm%XK0ienUjE^fQ{{mA$exdne)ln2cCnGKQ~X0h z@^w4i;iOXHts_mneCM%|QZ!%1k>UAAUpXfA$qhuZ{o_NtwT50a? z^jO#F3Hg(}o|9R4d*-;-%UG`Qa=cXO0PUR|EA2gZs%$w_<%r$OEOV>t@AQ(OGAMM& zt&i5N=DG-~Ihy)eo^h>4?)m=bQu&WqS^c#ND z#%?aYj@Q`|XI=CgFER4$eBYfdJ!vcNQwXY`u(m3!%Js&N1hD%`H@T=x950 zIdF?aGJDb8T zie3{AkZ1a_lp~a5l-ES|a(k`ocm9lipYQjPQYVTWtyAu|ai`aNULWK=ntr$3F3Gwa zXXu>dFQ+J3Pj@Wv=Ot`wJro zzvd|?yXW+rmo>+oUSdRhIh0HWlpRw}flCD)IpY}Bh(co`IIXwGhd7U0b{Z3@J_FWJ z%BT;>#llnWbPn%jD8DQJJNXxSI6Ee@aD48yvz#5m$soF0x%bT_JVDQ+a=VwGQ^Y=M zbX*b+U^nzE5vR}eBn-#*CyXn9@BKC6u9nk6JfU0xso@+O;_tT%%m-`(~O+QyJFT! zEij>t%4sw)m-C>N5$o@Ib?}uZ6U9NDB%06Oc&6jNGRn=a{ zrYjNsB}*1sU&P=q>{d98m}|on+5xCS4_@00Z8xigW)z!}g!c13r|f)z)Yy8_UftUE zI5;F1U>>Wel-8@}b6)$E)JAqH_*y4pw)3`WgC7Y>wo5La%u5*6)Aqu=^lSGZb@o>n z-K6!@tSiAFZ4#vS>{nI*<1(WaFirOf46Aybg=PCQj5zt|!DQ=z_3!NzeitqcTJ_A{ zafVP_Yp2&My9mM8rb(q~ex^1}I|KW1sEv|~0^)LYW$y-N-LF;BtChVNMo|_l&F@vT zvE7Ef>(`>JiXG!jpr{Q~n{B(+O9J>>>W<4@PFo5dvI}P0H{C4UnYVFmnG~0%GOqR$ zV)d!W_hoW9F^hgs$;vyBndlOH$qrY@S!WO>t@-KRMFKpw(-o|of(ubW6MYnB* z*LuKmod?bWj4FcA^=$l^J}hlDg~p91%YJv{%%rzDb1Abe2avhaz-)!r)>&y31eNMA zx#$JExSD0pK`rZ-`q^MpT~-SB*+W`;T!(MjdoM`gV%z+%n-!qkEAm}m4DUW{aT4F9 zw|!fy$CtfpW;qZ_&67ea*vv~$$=pS0yL$$W-vtz~NgJY~#w=J7J)IWCJ z?MvKg)WV0U4M%Y~eRz%r)i`c7-_!mg>g}T6SvKQIh0A2Xx@#}(-9wjyAP3`8FPjc( z%eeeIfvL;371&=N^(&ivyj+#zM(cWJ$DMk!)yiyh8|=p%|GCTx3)8M#tdjdttyn47 zl3g#D$CY8)YSfe6^*a4okCwfgotw^sa>)sW70?Ijzt?&{{SPm@-;I+*Vc+w!WK zR0{3nd0GqRl}dVBk1ki&9s6qQm0h=@)mf*zLi}KFkum#gW5ci>*+$8%$7P!> zO2J!P-e0DJF6`j63PW3TpuCHEip{4u;VuIHIY|Yon~R zFu&ThLh|)t>h0Q7cDXcPa-v&xYu|_Qo9*;$p%rs9XWE_GbuSu;Pp+@kRPjihhgvd22)uX(LPTHulh~ z*V3c}@+w#~$C&k^LfUB9ECgw5kL;uH`8eg+uBM!QR&JUst~HbLz>LOdpNm`b^H6!p zK1bl_)p_!@H^2Iv8HOa8Ry?r@z5`JH)x!QP+uE+)%&HUfa~|}Q&uZ-h6J z^^)Ey7{--nTVF;Ws}>vth8@54?tiA0M*Ny`3~_l=|43Vp>F6Q44(r9_vTh~^_I+0h zs^iK_auEn!uH{^|y5uZu%Hjqq(cd$%GT`J>vpWR)WWNjc^C~j657z6-Q&Mc95o&Ec zBMqwzO3K~b36{PQ0Q*ZUAr>wbr2S$H0$Zt?S>HTnwT3BGu;XP`?qpBd8(IMch|P` z5a0Ih_O`IAFM8J#J1W_5#W{OLG=E(F$QGmE_aVGZKBmEIyVA}s1b0~U;DGC)ou!R; z3%|E_jj?^$+RI-3*8CMF)N7>vR&@DDAmCYL%wQVl!>#osC zr@g3Vn>T6tRBwjORoZ#7uafQg+5~U&i{HuPZS5xBPqGHKq?%Rs-RxmkTh1@PM@FK}&? ztm+TxRoztLS|!;HgKFjCSN8l=yBHVC*~7lpn_n+)vz^4 zUwifOuyvWfY@_|I`78ZZi0*pz8{3-N&s|jFWJ9P%W`#2g2h7Th60&)rp!=7%rpg(I za3yv<_{c7M<|PikkRa0=2SIiym{y+yvWLm#%)F0lP3)B^;nB%Cishj#rPu?UAR}i1 zP6gACX2P8@$v4UE6l{IMy75aliJ3?itCm!jW^rH+_=;p2Njp8+4o*SJ!H&;qRI%6i zF}vtVvN`uXDh{!gxA1o~Nw40}kHhUoK=y$(^h7=`G=ydHvn3LDspc}{T3u6GIc=VV9l}uoet1E z*vHj=c9;g`17q&C=DAY6%nmK|8lF&PV^(X^OY00`Eq?64A`-E!H_bs}gNDd$CpLSD z2{`StZ{wE43O6=d$|;z|q`H?}EBMYK6cp*?$XZ!Pj|?}nsTT3h^8eEZg|Ew18S z+6wJTWUTche(X}POtQO&49fi)*QQyyW!~E2_2_!U0$g^PWeq!43J&v1IayDFLI1Lu zEIYwqU4BnKW&QlVWzgrfI*|FUrGWI5H(~V7tXKOnLbCU#3^L_}{Vs){p7g=G6ml@2H^3tqg zEJC?t-Yda035D0TaV<MK1B?Lvo6@X*+=$?W38jK$hql+9#b zn`fn&8MSMrXOVkeo!oNi5*6F;=%vyv7TnhPFhjH zl4HlV^4hTDVf3ChhmzW!rdQ<2TTyHqBRi1Tpez3(c&-xjVq!?dvK#Oiyu?|hXpF47 zF}tBMflx4JL(a4keX&UCYD(adeM!&4(wgeu2U?eEQk%0-25HE z3+vVZSkk4KMX8M7kI0-Tx=-#ocn}NtoL~t#Pxdj9%7Kcxp$@`)N#6ffDgp{)^N`?w z5@)6vwFgAU(~LVSbLt<+-V@Psj%wL_AyFxrb~4RBZG0T~loE)psNcqJlVO6>vhcEMEVpb5m|c!sEzt<-hMY(=+BQvxc8^!?j{6Lh5- zfdUfzz82*GL@8JZ?syU`fHxne2wIQ?;Ewv9ssKg)lLfB$w-;rmr zCFsA2pe5*c@Rp(7f||LpSvQNpAW2<|!Nh`$OR!3oSr4E~JNGaaNt1or4NccC1 za#l08bYySg4>Sd1b6H1r3*Nbqya?Ke8j_AkC47znrrboh7n$RfXTX_Ih{WEAEkQ-g z0n_%~f-h0$fQND`G+oQCfN617^ONin5(E}T)lyQcgLz2JiPV_1c#H@V6sr;w34VyB z!wS!(?xQChM72uGTvP-t3eD@>;L~(uM z!PIx$tStU~UGzC|kn~x={=c;9NVA#}AmUZpjeP>Cw9p)#^IM)U+}Rhw-KfqfB?0

x%c)>jFM{E=k+HOXOU+5`9s_I}G4&qo`#z?C0~t%k!Ox_M&m>h>ZDr(Bn8TWkp;dACBJKik`l9rmJuSQ&7nj=d>h zg~TFKiX`6jHMBpNCrd>XOtsA_Q|w)kp!XPiH3yw3hGsAhF0*im?Whd|J(|bCtN5qc zxHinfrP&^8(*%!TaR|Z$UurvsQwM_S1(ksPM8k&!R``tYaVx!^fyhD58+c0?@rGEb z1>3auM3bOpx8gIs_2O9yyI^WoX)?9TRlR9DBU{?le_Px{jQcJHdiaeMf)4h!PkmRR zX}01cZ?fXd%*T~q$z!Wlh%d8bP^ir+rK_Y@3VzNj^W<$Be6?#?R_NN#VT6PX?b8mV zvIVvmJ+vA>ZM$qQckyF-Q?->d^@%}gj@Ql zc+0E$bK08NweWj=YX@MNVs8e$`oMN4d?>zxNK)-OyBvT&97@RpzC(36&!*XJp?Zg8 z$*e2wOU_>3nA+;9n$BkR#_pzOH`_Yk%fc>hG0E|lz1vB2A+ezC7~;!i+M5svU>$EB z>{AOgVE~G<#!o43&F@~cO|Lg*xkletiO<4jVtadNP9#kM*%OS=cugDo;G$AN;%4@- z70(2LMd36o=t4JbzGC{US+%<9$JAEaXvLT8>Y>`}fBoE6_T?7{vYk$=QNMJb&7OkI zx^|QHAL=i|?vvei>fNyUGkts_5WdFoxI@%QTkG^Uzh~0MCw%>=ZO31CUH0a_vR-#ft?E@=Xk{N1C?10Q zv{j0dutCJrv|r+{Vfg|(oSQq5prc%dB-s_3c( zRN9AX_H7uq(ypp^_=d{9HHbynq~fdgoNar-c3vZ*o!NBNsHa5P%VDExpUTnK3e;j} ze@&vS)f%SzUOgQ)hxp&Y?YvM@^woRMmeLdg3t8m8C3>}VBD!WfKeWyh_EFoF71(R{Z{`ziR^bJ}Kj!N;2& z@oerykAJN|Pr#2^3KgwWt%=P*QL;nrHQDXWeZNKoAm`ox9#_lRg*5@grp+xT32&2$ zy;&rrHL_2FlAwPJ!>c#j3+=-=7-!pu;7RaQ5Xe?70}V^kSx}NF;}V}yDj%sf9j<`8 z8jLO82ob;p1D-x?qQ_zLB7Fq&hyXU8(;o0{h^|Dpeyy>(JYh8swn>OrjTX)VYDHtL zG*yvR_ZP<(m@v)Yc=f4CVJ^L zmbO*31F?J@|~l~T6p1%(3{2LP5yt6sG?OU53yY)Kr(&Jec_ zPY>T=FMccLk05O+@QxM!j&}7~diMbS6r<N3rjjtOCVzCdSfZA@U*rnpNV3y*_uo|LB@+2eEKbVPNKVNuZ z5~N&-Ub{}48}s{6eNHx8)07;|(^K@54^!0W%MfuP6Z0wAy)ZS*>zBwRA#|4Bh&~71 z43n?hpwO;#vPCE8&&wi1;O4|*5{L1V9f*l}ZwFw!OOwoN#EKx+!fQ`y^TF1J&7yrS zMa5Qgk&=7?4I0~@i|DaOVx|Xx2IMNTMy&V>Zw9ZG@>TXFd7}3nQ6ZKjX*CY^P=ZB! zSN6Fa)DBF90X}9Yj&cX@Y zi}GGq>uK+X(0L*3^wa*_W6T=86z45MS_ueQH?CCxb;*ZTD%ty!>5gmo#wE!JKVx-? zaN}XxC!03}3n=fo4bLb6LR&%emYiWXT7$p=9^QM#y$KG34qmgd?xb37#@5|VyAYnOKP{W z-#zNiT@S+Vthykg3(9zv_7?X*un=T#W3LPNOazMHET{s!tzq=qYe}x=tsFJP%bZQB zh4!J6w7S)Y_1AD)TUMVBrFsVIKCJ*-p1Kh}1+*MRVULN-pjz}xtjb^;y|k`hB~J#_ z1RpQ0*wj?Y_}6p`)LLd%2?`kqW*AdNU+$P!!MJ|Gb>i9olJJH9V@697jDYEnq!_=M zl4&7~$5F1@k^H_3ze01dHc$N06}(2ox|GNjX>6mVOsP%&JE#lBNxs+p(n|(o5dmbH zdp=0T{*){?_8SkcV6zn*gI77*Mbz6*O&Zs#+2%GVjW3_F*F`WngpbLhO8%hS$qu*X zcO2l&Nj%mkzm3yv3%$mjeb`60!$!?^KdiBJe8>_WAW_ln6E>4f)OA;)3$g-%Xm50? z9P(+gm1?K-C=mD`l$dpvk#~>A2;og?G zn_B-^I#}qQh|w)y$z|2d;e@0(HT`kTB!`u$_sN47Kg?ra@U{;#ZP(HaOn+7}FA}H2 z_sE#|OyFzRVD#9A=uL1EqK7?7(GJt-S4!TWP%{v5Ay7^v0|T#hrQhH~u)R?dZUiNR z&Y|hGLBoh-R%_-$gabNKL6&4g<5Vz>75>f5hRkApleT5uY085;3%lFJMG0R(B*Bds zQz|C}kDx3fsgaXh6VZYzE}}MG;r%zR^FPAaqyOvQ}ezKh$caaWxv5HKLp#u>%gnh-yfTj%@xZC z(hfgcoImIY_iEMnWyWeku>Gut?;vB`2!-ICA@v)Q^&tHC5IzU{K$Ria;#*M7Kqm^> zzm?#sdqIc4fBQ(UpUCe=bMda=7_dy--=VZ!rP8T{836#;2{bqm)TkqUH^|%tGRN4U z+N10;$k#KItOh92&U}G}OFod@`kB?xB|-;T(Vt>g|(FHV;jvUaCvd}t~xj;!g zwVrf^-O4(tlmNSZFzb2kHM{6Yw2l9U{gyls6sX${NTlFvMDo|^ZNsisL^3ycK(>CB z;?zx2vRw!wt3ps4wktLi9Q;;7&KdP9f)Y|oGZUPJJ@_!oaqyIt7iJR&X?B^Kk2t_@ zp|+-i7h=%ovP;iWGxgFopAseB&8Zmz(*zZxwXAL*T5K6m*^{8cc%2~8^DJzbO$FW3 z&$jhoGsfH4^h|?1aP}3L=Xvm3Fummt6MT0|rme9_e-`j1u70HtRF3T8S1a*1F@WUz zA7nR(g29ie?~&g&ENF(cDY*Jrc9Xh*AQeDNDu*n5A-1HKl^e2a?Sxf#6G~)L7g5W^@zc=_FgxQm^)!VFZU@v4Gzb^;+ReUlH$@9P0gVnlBCefOBUAxVQ&n_(nsX4#{CJr5@cO_6EYDT(y zu}{w_T#4r!3!eHG2Q#URs-;nuc1RxX8XZvr&nv1q+LAB16zpo#43d7~ExG%se)M-~ z5;v>Q?foI^1=tbbx@aF=^;-Aob`<{{CLhwOiIyifX9=BO!WNNS&pyrr)*A%(aX3q+ z_1a+^wzJnDSjONFm(YC39(2Q~wY#d=pKwdmw^w&*Z-j8e&vY?Lgq|#HFc*9Mwjt+S z{>;AcnOg8UUJ9jIzf+jMw;~Q@e{UI{j1qF*or5cu0`lADSvv4uLaY;+#c~KHLVQ+| z+LP67?Q&h5C->}kHJg8h$<1xG7#DZRd=(U8^OPn}(Qree(WaNhT2#>m|E^lX^iN1D+pf5MlQwysD(#4z)M7nS~{j#N`$h1Hq_r z*-J!9$Wm7Cq$ibBGb!qcT?~OhK>>e;JS=K5+W;?nV1CAuCmfn(8*NM}!;Sb%*J)=J z-S3D?hMRgPB#OrCq<|*M%FAHeXT0{Ta#7h)#t284oCd;URhFMqR)Sq$=_Vn857jU z5))L-bG!PG>n8Cwrp01%C9W#f-~&fS$kO>&-cBo zV{2FA(q*?m4H zrcoojx=g{cGvy%WmSLM-BOJd)JLo4!FB}m zc$Hoi^jev^Oe#OXQN^${qIR!wZy%P?%?{GsEih^k8JRLwYE&m4W>m~!$3C*J&^(W8 zhXmZL-otl5hvNAN4)6w zP%J^gEGjin$_G4Jk_IFmjl`cIm!I>>GFuF5)jq$`{hr(}YK=Cr$?t8@u9P#7cC2n3 zr@!m<>)y>WeP7le0X$Oy;les7S{bcRzqrlSFn=L?O1c?aAsE`@2ZcVtZ6|;*$u$x!Dl48fq59BIv zy++i52uk#^tG7uG04YG$zccn&vXocy3&H^pA+8%nGb&x9sCQLJf37UWkPFbW;G=zU zoBS#f3hWC>r&Vhn&`k5}^&==$g5MIJ8bRvjx1@E+Z>vB`&cEun!1EcDbdl2|N*x z_rfAHcla%~*dhvg>_ngi*PJsqx5!W*DGY}u57!-63vk6V$H|m0C@3 zmMQu>4k?1NKZj9Gs?iR(Ica>0=Tk4F?Ty{E>NRpYtox%?H{x0%M%(1_!F=?Crc~jO z!->(1;-8aABQ&ETQV>i-bp!P<=2~RzGbwcO^lG0;)(m!sYMfjy>|IDWLjbU$A~5A_ z0dg8-aW)pDewGmWbMlv^HiaRv^bDga*|oG z3uZ?wSS7S|#qmz3h$Xsh{)eUGiB7 z7X5NLd!13aT#2%;nHi3QmYgIops*_KZt-v%qqIkr^-k(jCCFM!9g9>1kW`YB0H!Mo zN>~>U_gC85+prgJZRf*Yw4gl^?{r51syRtA$RAPxw~(rva3C2*Vn5CI)}#stUYtpk z{aQiq5mo;@)UJ{;UZ~{WsU9p#R7j0jOR|-j)bsucI)bB9OIac>MsXZM ze0KFZUk$RqQR)}y0A9^AQZOb zw{2{xnZvRXhdqYmAg?G@97ZO+DwB(o{2wCF0~MyQgi3w#<&5>?~z2MKwFVIKsixDvkSYzaELQ_x_ z4jgDn*-c4ul-O;`2-d{mwT_%pQOhdCC1$N(Z&<0TfPcF%~ssw?HH68gzkk(N70C2{UCXCO$gpX4RA-+hwcObu*f+jxgb?LMXI!> z3QMsg{sI8u6cPf2K(K;a>b~GLdV0{MocJPDDn#b=*%;C1n}gshT7~x|`i{EfURIl! zi-XknXzq%fh1B(c5)tbOkcHO~H>C3OGNEz;%%1+4fpjKuh6>diYw3BSTWBsL3g!Wx z#91jr!SXnTqA5rPJxpXf)Bzm{aKirLvV_Pv)`;};g(YLcNombc2{tO)d2xSgD$<8ya2;W{cD{q(+QtE3jPZIH*37T6j~D zDeMGUDD7?ka+P%D+zrW>qF3h(yaSsOJuDoPRrjEPfywAM;5?HGISZi-xeSn5Dt~2; zbSlw_#418`P~(<$V};;G(nmvblUE<|ib+B@igw21K|KcG4^qe2=l9_4dk>sR_+0io zB(Ab|g7RXG-s4~(w5?U}E(=fQXQlQ#30r~=g;{YZF~L<*q3kk>(|uq@^WanR28(%M>58(6Kj00rM4it-8Jqc zt8=j~SzAbb zB7HVpW?yS$tma`}t0%?4TuVpqO;T!@pKI!ErOtQ^&BGBHu|yYgdASNxuQyhNf8sbHm6ioU>hM#o)xko-w18j{Mmg`i|fQrZTGL**vRZmA%EYEKz;#+$fuk$^mX ztj%vS4%7j}n-uu**AVCJxd&W9esDaCR;y;y4wg3P!C4KcMiz$*8tVXY|I8{Q(Ve3N z9gFrscW$*LO(1rhBf9+-!iLmi60(UMd&iE6@g^|~+KcWG<+f;nbu_y6QO>O?NmiS> z54_CX0p8oS%y?d%WCw7j;Au>!*|1Z)oL?l_JmfXpXX^*kml%M~L#9}fqRXlrh(%?^ zy}crKsz>)LJdZoure2NGEvXS@SbCba_HhOD&>D!r+)I7(tcM}FVvgFegd?>E9j=Pc zBsNplOU*NUE?A}bYgOrbB;P`16n0Kh;pmuq>6gSSdlHZEEyY*7NZW7#K85Jc{;*z_ z3b#ey%rYtmKIfo-iv`);@GTkk=n5*7vWG$Ky&v8rYvNEM)LIwx7f`ZsnLc%*v`2l% zbQ`@6uW&J>he_H?3Br3+4iUjKkze)>f3?Th?Ub}(g$P#auqlJPm-;<2Dosug6Heje zqm5*Ls##47F}q}isGN{UXvDf_ZQ_p7(Au}Az_9SB18vt2dGZYnjjbP-5AMs*=AK|5QKt_X4x zAd-NxRJ4;(5np0~^%ak2-iqiq7@CI|ZY_l14wvGuQQwlT!)dm#v80_f!~%AO|-`JSsa!@c&J_`?;pg%my_(R6O<0MMMk_zJ_#91 zQEIOt=}}~AE+t3ge?PSjJ&+Eq>$Ke<|0|s-^r5WK?-a>=w4~lgV!=^}xBzfJ4yBI( ztuzEJYs`pTQLE0QAIf=i73r%f?#)xb_LflDzwC2&kbRXvzYQQie9Q2U0gY`IwB9+zkQCW+&NmiL1$4t=T`R)4Y)U{(V?qi_U?Zj}k>aOI;r^AgjES zPKHKLa5sqs@s=q9QvCD^%YZJG%yCmsv=JEAo=!U;bGY>hJXsQey@Soc$U+Y}vIPU$!U zo<|V}Wbf=j{1>WJwBKk#3QV%woJ04E`X^|6_)FNF7SO4I(5 zSnx*b5UFDT-~}2^Wj_hVhg9r?Sm4oKs;3i^y{GaM8Ke#gIFD_X3hn!M?oi0*; zO)r}X=eBvjWy&@QWG5!J+ z;X9{|GTH`jixx5<80@zcL*TE_N7WUa6k1ckPOe5g_@Y+w>g*3vWz0#A;1gTh;!H&F z7_p(mI0>~hW-S#Fd5#%ui+F6GU>OE6{@F#r*aN$?-kr7@j&-27FvlA@tR|RDkJfCWEs7#e<)99LBZMh5LVW~9 z1XQW8ZoF>RM>iD6jGUj!I`x$RgQ>G23eHZ1_vp+Dg<_nd#(AP5V_+F{(5~4Xx}BH< zT~1BGyvX}MDD9HeB3S4ve* zxecYzTD%86=t=hkye1rv@5lZ0M^W5LF{|tbJT?>BV-E#CmXEYcihemc0#1|PD1{zQDt~a_S6qf-lp%XR3)^e6B)vfXgT_JQ>F;d=%Ro9bP5e^J2kDxTI!$(k6GfLIV^C0015UnywteaPL|@W>#HqNlPGk^tSWhJ7 zz3@3Fzc7N@VkN~aPA8n=%c6yY^G56O>71q|ze zV$@^GR#RI;_aC)0Ra1a-ghSvp@HhN4@`X}u%dFdJy(_$?Qi(3rp46?-6ilZp`Wug6 zB3K`t8Fb#NW<*oOhVX|8OAOo+T~Z_!B3Ks`(j{={g;o$CjjWS=15;-AMD{>UDOaG@ z$o2GqKmn1B#4gbnM}-gosPs*-@8}pJr5&?HOTo?11>~OmiNvE+-;wth+_*Cr8d9$~ zmNR=n;0}VTEo7ENGSFJA5^0xipI$D?2C(yd71Pzll*zWR?9VZ z%6{{ZD=QJ*Tu^ZOD+I&FwlD|!s=$*uQCQBzVt=osCiTf6*C3F_8pW7*K2d>52BsC=17oC;t9zHYO6M~XM56XPy z924wSDrc<20eXTml%WT6E4GB)gK~0$G_{1tH>YdCK^4K1OXeWH12H_Duwp-v{TnL$ zteZU&gXZsxT?3J?NDnrk8-)wP#dL6x?UsBfSPqR0#K1!;|KN9=d-^nR52@r{~e5(Yp{_o#jr6OWkLF>e~!Wje@;NZ$>5 zotjCZx%T%;e`GArCPt9%-FPJ@V^pMDySp|3XlMlK_N!76I5tEdUBRd552bg3Q?mpe z)VNFfMTIjc@+gU{)j&n1w}&UvSxl>ebdF=KkSt~{nIn3R*##U6crPX%0yw}}iwQRWhhq+pHdVi%tReh^-hXhI^jh!e`h zUWvxnmTsD`L`&nGA#2o55fF*cW7ml)&=k21g^ycOI{|LYDI@%@fnYvM(cg5v(ydL` zHpsO4#)v++I$fR&Ldk&_Cq?eY*SCN19!zF%Wm#hl2nGz z#R_4U24eZ4sb3}? zZ}UMc;aIks8WS>0QdLYH8`%K7O(GoRTWrR&^wz`2CWB)RL*Ys^0X_HdweS`dLJP4q zoK`XrpOV;tYKA-q&2Egvc4%Ka=g)0L8>5x6b2tU~2vT+dWmRe!VIcJ2gb=~**-xzw zCl3H5+?2ct$4F?`_DYaD1rm+$Du$8CmpdXJ#x2E9u0s*jn2?Dt$On(i82v(`7@b>Io;*~}N0TL~`smc#ND@ z0Z>XN8NY=yU}P4lbmxPD+~sxH1O%kx2!s;O0YLQOTJ2dxdC;k(WEGm@r@)|`{zrX> z^nDXwzUl8c^g#>68__H-=a@*pf%{DbG%3-R*l#EUC5R@~FTs?FW@NVTKL!dG7vuu3 z8qZgX)5XmoR@Bf$^cvO`&fp9~y8g75M(niwPKQ{0=K9w9mn8JJQzQ@UeuaTpk9b3)WQ|j_2LfwE zB0d#HnIK8G|&AoQftf@KCsvI80x$!Wsh+`)ely)e8FC3Ut!zYSC+A zpIyw~ma!%hU*hTVYYaLkETMQlXk&MNsGi8{Oq}W#u^j@KD9u$u(0F1q-I(}<@RoQ0 z61OQwVTGly(#Y?Z@GqiCa_@|(`r4lht51MJ`0Jv*9OZm;$!aAoVcqPn_??iM6B3|| zo^s0in7!nKp{RQ$YQ|4fyQjaDp}BWZ-+X<1q7)%E)F(I^%c-v-A>?Ysp@xDijE$7~ zHD@+-YxxGfFV>jZgE#2zFb7^4A^&9nJ!6E=<$K0v7VHL;L8hdtSQer`NP=kV6aAAV z^X51SJ)#^XH3#krA+!Ed{*Ysy?amF>Rh-d7-}6g9XeSw)e5kBjX6es`*7y3GDm+|c zEM7h1@~Zz7n)AXR!Ml9InD}L^ODV<^{F%kFi%^a~G=`oeA=ks_Je&3S_3A?YKdSDn ztBJPR9{%}%jP*%!5WqmlVTZ7ZfCLF5o&jP6#K+Z{O)|v5tMzF@mJ=Ta;4qX4${{4Uak?SYQak<94y%%v^Ip!O4^WJltw{KHP=7`P06wJYDWccv&c=OiG5U&~0a^fwgU2{?d6m5%Zv~MT-ILyO)hg@6F?{Cj1?q&zEN@ZPr zsDFaKCWy}abNj+)(+?%X@t)gE$MKTi%nPZp50@_2uAKtDUS8bULt1BLv$Oirc>aUD zPkU`4HZZ-qdt;~zPaQH?=QipSo1FKb9Gl*^`xg@xZpzyX zu0$`HEJ`-9P*LeFHiWkU5NgHdTfQH+b; z7I%`zE*;8NQgqz8t@7i;=S;kIDMRIw<^&Wd;kKunlvr6t2NmI&l)Xocd<2P=zvQ{m z;43#2r@qJMbpn>ti(&NVx|l-IAh&2}`5DPuFn&cLsfJX{JyCxyay10#0x(qC^wn5Xa*Z%G2JXEzU3QWvZ4z zc?tVGq7rj$$b>h0mgCPkeUvEkDFkvc0>thqzBbU2-DJ=H7Fa>5X%<8F_uGlb`ECuv z9m7B033p7+CwB01@zoLusu#gHu~g#llsZEq{SN+GvW5?fo(QMk`5irlI>JpfdU%W+ z`iiMeN9Ud%{id~6yHKb=+1Ff90-OqQ4wPUVFk2C>odQ=d=eZx_3<1cc;2?LJnQ(W! z4rlv*yZJrFP{x6z4|_}91nvN_xNdn5^zMId@qV5zI_sAklf|!<&ZoBz)5(w1LH|;< zWic-`AMn{i16^j|iUwdVJdzGgT0 zxonC<0J2*2fZJ@kdVu^#s>MOqoasKR;Lx)&+l_m+o&!v=yK8T$D}oz})?L!7TyFggK}Yf?InUlQ znDdHBA#S|IL*S|TxkTLo+{yOY#K2c!_0dH-DK6sqCx@~|q!$Og z7Cvltulk*x=ImrN9Us3n&#zF=ou7;@n~VO&{Ago)DCx*t(wY{YVfx8Y|h;x_nut)^rgd?9Xf;^EIdcn)2}Q_fFzH!SfV9FQ72KR8yH ze@zX@a|B!CX3G8bFN~|ZI~u%&+b_CUTMQhntWlfs26o-n&%o^KV;e1A&rOdbtND$; zHJNeM;_4k=s>y{h$B0qta@5FdI^iweFZvSZIbGz<-PYIT&eLo=UY8qx7I)*#U(<)x zmn-Xg%Y05Q zXtJRFC2E$toCBkCRz6IrIXBL74^Sf-+^Q48zQWh!Qn3hg@Uvm-J@7a)E_NRTVgSO_{i<6iBAB@wZ^O-$;Kr3DAaE-YxeoBISGS3+?v7bX!UBb)9F7vzML$E`23__6DEha-M3f3R}2UW4=^lRz+NRF2|$e+4cT#Biht2XajmE z^w;?_Q4+uIi46NMPMFEgY;e?Hy_o#GNGj2p9h?ov^PSG@?1`ls=a3s%kGH`j^`hZI zvDI(5(4<5go^|<~(2IUJ)&c;p@6YkpR+ML9PAKNEJZ; zt=Znq%3!_m6q74Tjb&Cb_QFYYRigE19T-@13;=vy!sORxot4}5yT#Sh;N^B@zBt(! ze7arfWd?Qo>raz6&hq*p%nnYQ*K^O^ZN6TJ9|bEHr^9P{cAgQ**;z9w?Ij0h*Ei-m zH5)%*^9pi+{WY6SP1@tP2u1JdqhB0#*JL>IIz4(rlSpsd08tja{b z5J?!G1i3Oeb;9`0!L-+=`o?Z%U&HIcSHl@etiWrY#^IIXCN2y(v%GTLWqBj%z?zB# zZGfliwc3_%@Z-2X@)?8|suQG&YnaraZPal$S3jX;0ockbhVqKE2VNeuy>nX0=+&KC8rHC-WJzNSl=D8)A`LCp6f;|`}BFd!I!{M%C`2bQVB%}SY4_+pFLymwFY z^8cYJ?!h(Vk{Zx~IZj)fgNC*!SRrpP%iG|J9B1R=9E>ww#lhcT0;%r1rhL8XCjao3 zLct2Dfn`7sQtxhYrWPd$f(3U8vhnYa!On9>ahJDP7`R7tcQwZp-Kx04%A%e>h=!WH z3>MbE-)dWme=JK@7lM8U1+rt6rZ>4S@^*IRmfxiKQ06x(5s=KbZh+6@{mBxG*At~e zo_jOZAM^g9#7N=kL88^gAke9Uw>)&Uxm1j*o(&CBxns?>@&S%pbxwFS=jzp4p0rTU>Y!}QSK8XUaXIpC!e6Y~yWuJn$cPnoYs`(U z+&9@Y2OLQ812PZduGXOUoIf=#M6m!m7Wkh?V{tpFUk;4dmKtTT&CV{cW(teVb6 zJ{bltCr+(~(`wcAVR|olpJwO9`&)D`!TgeC;glq6E_MnRD%DKYki-tAySFAo3tC{Z zLxEJPk8r0`x;9x}3<;`6NQ)l`szMCB>mgDR~w9^)^w*G?fF<~vyP+hC+7ALc-CMRp4K zEqs8XKzSaBL<&<)xeWEp&P)@PM!rn$C9@WqM9~m5!M^6L6h`Tq)_2N_fxc_x%t~*E z=XfekFdp^)waf!u48|Gz`271ZHFveAY9oD@|mZYj$bpSNg!y>S7r1UZ}Uq4LNho%I8sAC?}k) z=9u+sY~R^<_HlQ6oZXuGQQd5wiWDYA&N0ueYAsd_7t|kUU~vR-1EGZJoDS3S_t@E) z=Y@q;W!aod{{5+*Lsxn`fltW4*ro0=Gpq!aW$0+m8i}^^3#Tmo&`nT-U48CxBICH} zx$R0&G8HArYc$ygtLg=2ioF<>-6n_K&t9ev-9&< za9!dTEoQb;#6i7{-<+NH^F&#Dr5Ah4-O9tuffDu;?wm8z@Yx}7qYv7hQ=MG54tr6! zlK6_{?~WN%j_`IuT^GhfbFAipT0AU2Ri9q@{ci^t{94?aI>+Mn7-d3uYUv47{L}QD zPv1Q!dpwL)9CyO}4Sz4E6J|5MHe@gwYdk&Y)@k>rs&1akp8Bw`mh21^r|E#vd@XML zEml(M3?mw^ZerxWV|Gw*C^ZYoyL>eBT?XU5+t2iVdxPl>biA=L5_OxUPGRjPQZF&@ zs(V3COTEYJ*?t8K3Q{|Et!8~eJnsfMFTsOiLvJ=SQIr_o2a;C~5 z6mGYuBVGGrFlwBV^L~d$5%$6VJhOB?p6hf7*5EX3>fiA5@b1kIOh6}P&QL`a+!9Kp zMNd}I=hs3H`B+|Oq6{4PhQd-c08=V~qBzB#+Iyq!-n?*aYuTq^NjB&(Edmw)d6{6DUj$@#ca@G`u7jIIPjAh2Z=wdpprYX)6*_9q{% z;<>(;vH#62v*j~UR9Sr#XY{|(p2C^FPL}uFNd{|+b#u#x8a#EVZ&j4m7fbvWIE4_! z`cArnEh?i^X$UuTub56whRVZu)+Qv^@ddy?m~BymdDFci&5wGCZ9xv`|G5ORu?&IA z$Lh8+JLk>)c@D{lZ|X_+Sk0Y7K=#z3ok6cdxgZxQi2GC~cl$KBp04#Lmq+}UbS}HN z>in8gz3PBtvTH~q7Fdq-9V5vY@uTY{M*cDe*sAC7QGJ?y=38^V{dsnPGj_1~Yx0NJ z#FTp&ujW1775Bd#Uc=G0~*UvNKckojaOP z($nOQCac*~IaV#$nhkf4w|~#1h;+{Otvi~vwbfJuo~Cbildq?Pw|?((^2qliI`NMe zO>YeA`5P3p8=n%ysULQT8E;QMOq^y{Qk(dyNgp>FNcEmoi-i~N=z8Are&+cNy>C>W znzl{jfC91E&nk6(;ReGLD(COvd^X!maxP#p`u2MK7aHYjM7tFA&#E3TUvmo!wW8UMqAwS1k zkF%YpL~`m1qVLq^aGNEk)^9dmQC8KO?H%`-5q3VVuh(H4TeTRD$q_6g>Q1^@`&u(w z2Gc|M#^|i*D(rENAhBi$^Uw3^B!KU70I7zaii~)CZpQatteEJ6O4(_HpA{O$-decP zr_~t&cW|=2>->GxckuUV=SP2iZ}L2b&~>n^=dKNdpQYpA<{VDS(TCFTNAzK6VEtKq zsFGS)C?RIeL}$@OF9vdhmfzBWmN*l`!xjmfVwE)fHo?ER#5kf{!ZsY)DAg4=j%9KaQ&iu}dwGq9cWnGO+L zoj=V)=HIJEff3h>yAVh5`4&$h;Caj*1T}`Dv@N=9WUN)z)MV&uR9MdQ{2Uq|)lt+e zXq~uYhSvfWU+1}ynY9{R@mutsJ)X66q0z8)_z5l`c7Go^(enI*;pu$A^1M`V7u}m% zei!SQp^01SY=jd}XWJKCq*NSY3$@%@PhFIziO0U`?PZrbsCp);EQ<&#^NqH}c|^U7 zriiAkMkArcc7$$ZSrE%$KPmo;uGN+Ey=TrIUQtgr>fEv$@`EGPnqAfA6j-9=n_-in`{*T37r!(zu^d^6fIv*bU7n8+9 zm#>fhYnmj^Xp|35JS8kwuYmpGr#YM;!?sMNC3ssp>E3r=b#Nv&LA<^~Fzm{w zVl3Cz+TbQ`fWM=(#2nyD!<8bCK>`p9(vKy6TPe+bhnKB&VTSGHxpiAeD&aRF&U-!d zKOHZ2$6KG`M??>NKcTQY?Dsw{u8#&^4_3b|el(rE(fWAu$871`>md9)s9oGs2j3nJz=8);vZ^n<(RBSXvsFUu7fn0 zyyrm&V=`SL6FUc;+&eo>o`w@1F0>d*aVEV>Ws9=m-PzuWRhfnb)_Z!Jxt1t2T#TrE z_j5AP0fVo(;1>i_mH_5N+1Nt-hccHNz+Zm+K| zo%x9N{Csi8%kYujm3tCzLw+4wb|as_)moi@YxeP8S#f)X>b2?2aT9iQF*xRayc>QC z#xCCyaZ56H*@aGJd>(M<8dVR!c|RAKLMWx2PR!kS;{8cRE7pj&qpmC0-eb_P=`yxI zV}62i0?rBcug%$k@6SQu>LQKq+}^HDbg$*Ks><`H z*ZKLz)0D%RdsvUpUG<;r)JUY#nYwnQS>)F)%S$!h9!_?@wkjRDM z%w};ZRsu^#-OB~|T+ZA?7t6Cy(p^<7*+Ow+MI)~{5x%F|Ckn0oP4Bzc$x57fWq2$6 ziqb0wW-pkrcf{F9?Nx?OHGxhMhq_lx<}kYTLb6-kLBn;uwPSXs;+IXM21n41!WPz~ zpP*>!=ed;9T;g{y(v76~o=8yL@MkzUPX5h4=T0sxzY{ab`Q^NEQ^J-8Q!$;TAYJM$ z0pmgR<^Xe*JPj{hQ8kFASuG;Q*LLU0KjfDyiLMK)pWoVzpu+l_TMQTRNAE-jj0b{F zQ~1+Cc5%L3(K5-RPA>c3ze@^Xl^J|5xLh&7Z1(tA7{DnTCY zRtAj%qjX}=K?AK?dJ>>ZB0>kRXl#-kxi9+Q?B&|m(Rj$OVL48Eox>lU2!F5CXj#AL z6mdQ6+25}_&;5w6W_CJI zd5AnDAGMl`E6z!q!*=#Hc{a7C9LV99=TVEpfn9I?SA+z z@P?(@oSlKf9*ZZ2BLa+%!CUURAO;^@&-M`auq7^Cys#dVzKN>i1!kT9OeaoB%NN8B zohZ#U&y9k7oRx6A=&L%&x1(jEOt^_xjG3WdN4iLHOjurY4=#DSu3XF8Hm@ds!y!AR zr-;XX6x5CQ{L8tzC!&$8@HQPVu(Y@_9L76@t_TJe6^VVFx>>)u_}Kr!3`llJZuA&O zA0BVepoh3OTt)>3XENrT>bPm;UJ1YW73D0s;k76O4hKX@5Fd?02`~ad^AHZ%E&a$?ImfIndXBg*zh8d9)h79nrx*apv3}%|1V+ zZ_$r`oOVT}_iOGRsQg*DnN8anIe_w7fCf3Hh_KmsR_c#tt)VuJ8)In8q;4bu{(C@07 zVfDoI*1NEouOz(Xfa3@VpGgWj$PW0_a|+3Uew~jnDE#dttk?+jnaa{(_)gHI-n8>+ z(SPWkrv`1ms(t*h`a~+P#XalWade>0?aMd-B^ud0UOdk`y3{YT?`=Qo>H5d|jv1*g z)ko=?^fFmldo1__hgd1k)3F`Mx*_#O?i z1!&AQDZ{1=Ud&?v?x+@fA9Pq0PQxeT=dt6rV*?b0*=Rn5JenN3AO;QM<$4!iJ+&Ko zXi!mksQ6qj4X5)fCwXJemBO1pPQPo4*~|#%;DXm*{4ShNNE3Lw<-T&-y}TSu7hG6A z-wwiW^S-b9x~y#M$Bq6f{v~r2)B-dc^PF$bbSy3<_BXBQW<*AD&(wSUq4hbCnF_T! zIRO^w9%b*q=(F+2yY#lqy4N>&91V=z-@U^6!on?Y$9oudoJMz!<$j_mJj~EU`Sz*S z^8f$x7T?&{?hh+zA(Q@e+kc$AjJqGFR>^#VWpsHkxaY(fFarF1mqrV{)cHBRJY?pu zCQ$Erh{+<)9L*#(#i6~`b}A?WYor1LLKX2j z4ihs`9@V_n)9p1i87rl{1|X2ogYv>U3l^ZqgkGOmr@{7B9=N2Y?(hB47;~e@KAt*I z^)`eXr^h2~Qx<`(wAFn+80gov(LD)cKr}WDi<=+G+{q_{mFvl-a7ic+L9jC*4j-4B z*xAY+~J2P#YuiUZ|l=cP`cWTMmde|uuGG5LMY!o?<9RKU<#j)9r_1zQ<{wH=_LM69oxXbApQ(1~1?9}at--dox3(N`Ilw{uzE2-J_H~Tj z(Li+3>3*9IuEN^+o=%0b&3XFJxq<}**}}=ln6IaT^t}gR78X83%VwN(v8(YE*OCKi zajY*B-{h2`09*WZIltr_E1e{%>LletNDaA}lOS4EfBPj!9UVL$fb>q16Vi6Ua7T$1 zp`%&fM|nH?;!gD=QIp`9ZSFbwzh@6svytTRA~;Adym@BnVOFOude=CG<~eUtdO!{@ z@z7AUlZUI#I(Um~!#`55+IYLFicAtK!l5uBWs`{=B-cF1dqJL9>(OXN7s->D?Pz%) zKC98G-Edtp5d~&S#eDbDn||cE%Y7vEkKl@_7x9G_Gvg4Y>Y)oJSNFd;Yg8wGP1f$Z zhqsun&hKBT`)`MRba5H}vCfn6Mcv?88J|>jGgzJ=$eOA?@X5M{fW(BTQAZZ1h>2OS z9=j7B0~gR@Bb7@g^?$bn>)@xu@S5?5rIfR(B>>neH5lO)*Un*QbgmJSvqY4Wjwd#!%0=E{L|xW7Rd2a8$Y;FR=&5*6I5AY@%AJHK*1Hy0X+&{59%w={6&9;e`#X zC?{5c9*`-}c&-C&=SQ*&l|%|0%&&0{f7M=mpR5Z}I4(5z^2A@Nmw0S^8qBH{oN z&W%G-R&gFcp>$(k^T~Vx^otJXpJE<|f`dVmB{$qvBL zNtJRF?>rdSSt2}cw)GjYSCZhIQ|3L4Lv4a*vr1b| zK=n^Gc$l7WGAAXzCZ2#OH(DjJp?DO=7j+l?3}5J#j5V$ix8w8tLPIZ}x#{5zh2Y!Yd3!%~9y(;POb5VFy?e=%&4AS1L z@6Lort&u^H1XdaQ)S@@sefU0D3UUE{Yk{|nHRX8icjg?nmYgVA18!6~31j@>VTQb< z)~@DEI8eehio4>uv`(~PV~WlyZh=HN0irxJ=O8jzSmUs=?A|yhr7NOIj<&_l5p{3I z!_EPoPTX?B^DGhE+c+7T&G6Rl+_MveJJwhK%c+M_@qy+yYiNBY1Wd5dd`RJcoc^An(`2W;TLC)yHe?bvFB5} z7$%!D37pe*`izW6ld)hBV58NDK@sQ)!@?60Ez9Rb-6+~kjH&c{537!(BuKM!-`X zz5mn=!gq^`wbh`;C$`n~`y_XbDUkX<#k`*b2qObk&5jqre;Umw!IRwJ%5h3a<=p?A zTcyFyJ17ckDd_#b;eTg(CSHqrvoyyLXcI7RN%Z0EmCCnr-{M-?AzDUgJ%u|7uIRJu z->%{R-}#!Y1~W?PT+1hwlD4>Wr7-q&DT>3xZhyja|NEC#51V(9d#c~$Arjt`zgx~| zX>v=8oK=^x6;0;(-JPc>7oK_w*K*hHpg6Q9uoqd+otVjI)N>mN6SUqI=4vqjtBacF zuDkXw-0eGO))jo*J3qBmr{`|M%dNU+ePYXa^>f9(tZVvHf4fqyZLI2f{9VuMxsvPW z>r%+)`To0RJA(D|-d^Up*_joV&ng{kjZCYQcK7a~W{QlxRL$j~kLiT!0>Zx#^la@$6G0#uQ9H9ZRCo<`%nMeK1F&7<(lRDP@kN6 z{%?dbw#G6e;wP-78kzRqau4~<>`HlSQ8@>@&lCUebG9?5{e*BQ^W4Rutas7Q-nzC= z&YiboTH0eL0Bq+UFFAal+RyU5Z~5Dwx_f_@+B;*Ou`I29*0DeJT&^i1uoK#%!iCiV z=rvh&M}%7r5*z7<5DYt^dAuT=!q%-lRFqktRCCWKTU+%O>)P*wk(CoKPcx*QJLL&e z@hJBVXkmCuP5}4d6Xa)kORD^QFECvpe#yt|2wc(lFO|okl@X?+_YILVjmnu)#cA5E zE$_MR$-S6aBCgC|cYzy-nO}TsH&|~`Bvk&$#lOa4sFhPI#hf#nbAH$R%nFEp#QQGZ zgZ<3NsRzY#0;|>uAU=1-9JK2o?qcowwr|NM4Ms7T>yv7Iw<|~8jDL$Pd$==KgCB0v zKnz~uz^hZE`dC;OFnLuCOec=;iIL!|UovZZ;b9X3=p}+me4OZm1L=l5G?DS#bI;Nr zH?@jUs#(R~&@BpItNa9=4E&lB`8wCQ>=akl$*DhlFl-E6%n-GA{bIxDq<6Mo$0;Lh zQ#`I@%aSm?igVzIm0=%WxZvW@88vs6Tq|*jef%trWyPg}JCzHlzwsToe{zE@FqQ@; zok_{s;?)boFpp_GuDbkF_2W{t*CjAnGsV@$Bw4iXfRhdA*`x2L5)og?JgbwQ6s+0o7IoT|O2x7q3NYCigt zpj9t&9VpGlTb*zo&pJOwD-R0|f^RPW?o9UPnx5+IpRT8qL<9Mpr#8yx$^qBk=~~2|dig!|s=8G_NPWmxImz+H!H)8Jt|MUN26~ zmV>p$~ux*%z~AcsAeJnVqf;$H!7Aj^Q{n z-fu?~jp5hZ;6*>^Emq>!6g{}UCVg@I=0)T>@!K0-1O?`UuI*X)$MzJAPu))CAJyj^ zhcFIiuzf2-JTK}9cXSk9Er(LRD9v z9=sn_lB_W`M}2^oY4!aCH%(>jn$NveI=vrmoyWzV&Hx7kkF&FJpabjgt}$A^f9LW2 zreS&1wfe)d(|z1f#d+!6KW=O;{^~}NUPvI}x0E9$SDr?<+kKvC;du<8fSqN3{bq4} zj0x z!;SOZ&DrT|%qz;n_1lO``{KIu{c&SYC+*JNNM&ic(f#@8?;QS4H1J=11r4P3L$$8)NIR2|eoIwX7WZ_=?kOI_gw0T>{ zTk1x@hxckeEE7U|l|Ehks#2TdOCrbBZ%R&-ozD-e8&JQb&L2fBF7OuE`>Ng9_ipf&UHd-I-MLA<2$m&7jW4d;Z?&htoVdkP$kzm9a{-N~(Qi{1187QO zCK$YG-ldb?NZHT%TvQ(Vl$squgDsJ{#b4|^{?y9eMbw|^M zLY1Q#;|9W0<63YgW=ua9kr4JFHK^VTQRaP3%ygQVaPxQz3Pxe5PLQx~NJkVDSQw=G zF|U)+4v|jcx4M*IQOCPCd#Xx`I?#FMF-k6E&JKe68}!J0=9oT@L5JRdPT!zsa{gyM zxC12^^Tgvg6(BD<$)LgB+V`-Dp$Q!Hfi9Kls3+<>PQ%F%a`DK+)^jJZ4;$n9AlzT`#vsVYd;;dK$iQ#FTSS|*Ou=3Df{ytioVsu= z;#pXTg%vUFnB*`JXG<|=ux|4=j90#*dOz}W@>PO;s*+$mc(_-=83eJ`_jYqubps=n zO0qlzVu!`T!;t#vGofuKx;tJI{BDlK#p~X+- zOH;j&P!&~5jTBvUUVwRCD<_@{vn{H)x9)Ss$IQXjvK|^Slle-u$=j^@DPrX7FdDM5 zxRv5686MV(kqY)%F=SSh?D8W;i))K?euXV-@xqBQBQShZX_D4KWTC+684@%+zPgZl zsHt(08)RRzXTS?-F1_!ZLj14IrMT@3qbTlR#Go)A01WmixyO1;D$N$;u7 zPZ6IJ;fOvkQ8xyZli@WPoOPhb2<>8aZPa4lpQnyOKZfei?BA+ctP2ma{SS|GFS&>2 zJ5>0+CSP((oqZrxN4b00M{3YioaPeWE&i|(zCaIVx=!RLRI#bq`{EW6{RO&3s$r|= z#oSMx7LO#^wq2Jp!P?Th5F^MbuClVO+YZ)YG+j_}YtMO>Zr)C*4z*mX$vx(_6BPWQ zpMIJrY1;FxSUGb1aIA*qv}!(O21Ns)m&WWsAI4mp*A;&;e0uu8ob=Q$M(UE{$XTKi zV=lrxWH{C5KC?8Hr@rn>qnzK-9H*9Ura(hWF|20cZN-C|T0)+EVh;_28_}@Sy@~^t zh#0%Lu=|_lEN{z+Z#T9p*|#W*qg7NF$KNXNaS5^U2p`@0UOU%YW)OZ0d_gs59v_~Q z$vVAjoqY6D(eUkD;2~p1Y?bC*YH0=GP^O-MIfh#kji&vI{*xj}01~~yM&cI>ZU-MZ zc#KtRcg8Y|cd1i|5{=J=JI~B0ztkJgo$80o4NDqcthXVAI<*=s1`HATTHdqO>K$P< zWUe{X{lW21#S9{4J>Svl!O`r5Zq@$2k_vJ&<8$KFRrkl&7Qd7Z8pi~Wi{6_zj+Wal#nkL}xetqo+nHaf4e(5hT65_UYT*W#|?3LtM3-P9-n2GsrH~ zpvyZ)UXtt*md&9yydEc<^?R2+Dl ztsT$&Fp=yyoC_$4p3LQXtaon-*ncH%`}*$V!&NZ-ptq?P)BTQ)hG7n8pNkY^HxlWI zr9K2_;{kR(>CJ<`6mtNYK>rYH$ga>OvfizL419 zd3NjC+|x$)lec4ecYkj(Kkgh%*UlHSv(EByz`y6P%jw9=-DqCNXx;~BG+rYI>?Yqm zS?pU7r#~Ta*>zOVobY|bDP>#u49ovsZJ=b?k`4u8h$j}B7zzb5)W>b6@* z+{UJH;9MmVaw`AH`xTe#zgB*m&RCf4+d$qn&paRD81KmU(ThHp!CwxPU&xF*8gDDQECw{Q^bMyl3SuTCMoiqola zey;MmXRhh?X{Ly}^zOrfydq-a;dsB_*}v?aO@3{stJucmWreEM+QRrl3w)kw`F!5v zk+eP&gYGX!>$CB7Sj9qZ=Q#Cxv+J%!o52@F+ly@Gq7q%i%uSwI=AvthWL4``l2fa| zshV*_2r_N=5y=*~4$xP?2HNBlb~ri~V4B%eA?^0HgY@;5HQ&nEol3@9tw!2c#bC}a z(+JCKIGunz%7qiD)reAphKshd z2mjCi!{l@8R(?))8kbJ^O7L;(c|SZK@v5Ct7ml3ce$<1=s){LNp11qAXv3bOyHbsq z3VKyJ{LMk$0g-sliQsElWaa}mRPj#mHT7;;qUYe5k*Z}n1B6{k(*VKXUxW)>&JgL< z+d3sU5#@^V(Ci(4QjAnyIj&hF=7QB9x%%{Y#45;wrrN*DD=B%XldY8_m)nr^w~Aur zu~EJb>9>{lT~#mt=M(MDg};cI62TdX+P8dsF<`vKs>P^c%50-)N_1K7*5&7-%IS)A0)#R#Dni0w;RmDa9Hx8`nV8yLJZ`EGwOu5U?r%vj*{g+#++N!(u2}J_*x6`<4 zu64_mhqd>XI(TY1nQK+0bXVnsQdQghoqLHYDtTC*QyiBYlI(~eDnG$Acz*2T7$Q|a3NP1p%)++TSdK?ELo5c& zKM_F9Ygk_P{S;)w-f>Uv?IQ2HJdkQ&eaBs87s}g-3Mvc=y$p-52rY`+j;}p;I7hDS zEr|4FD(~lb31hIW$e8Hl!l`gAS(o?B#h})Myi9J;a-pqXCx9#}oOY+Kyu(^DBhpJC zn1Dp8_>1Y;8&|^eF?sJXFipo7lUHslLk$D2?Ykw&tudC#nJLv(s@|HIvY1ffw~y)rp{?udP!=cpmgN@2>V{t=XFjb8&+02xhzyN?*`iEBkv!E{cUr z$#4v;7#R)3M&+^k)7k&B#-PCdt2!S*BFxJ0wElCg57(yUd%&Y=4Yrl@)QcNqUO-VEY3mL3z^#QYz@1z-K$-| z^?ezDDeu~uAE`O}4d1Dqz#Q|ZYSUHsZ+DE;=-=-?m2}Z&tuZ4~eU^Bk)sA1ihwX$= zqU?|@kH?PTf0fa|Woq-w8R}Xv9q4CvALke}q@K&Vx892>(ysT~3GI~Ci=eKU9zoe< zafs>>a@R1B^zBJ?o*CIqYE`@lwxZVm#M(eVvGqm)n^2)Jpky*UMJULF zK&q%?tnSt4GNsmM(dhY> zpT{B(&cxY8&L0Hs_%-ME)r5DceVm^vsAyA zIL|U!UU2y#j!4@RvMkezBZ|Bvz2P~Ddpbb@@dRpqjxRKQf2WrCO5c@JNErog5g|Oo3-c8+# znZorp8^x=4iD*R|5-Xx|yRn?fMueO$>n@DT&R05bUaiHo5OvXLQu}4?QOrs~M7(;e z#{-cTTZLjH`MDD!UO~JCXcVuLsjSAO!5oh9=d;O8~+@FKEar`lcYV2*ty38%-m2t?9NXejPVLyW}=0?&9 zD847vhs#*aSTvfPh+*^IGBr2c z&&;)Stg>=~bqTO>PNJL3K8HIya(?x&RuUPfW)Xal$9VB~c$}Tluu)KQj{Y`X|2g^n z()o43_u%iG3-V^4e@XK=4iLyDBfY2}XP+*@Rv?mGYu?UB4>QUXdFh{=PVkjsl!t#W z%?EKTKJxBt7h-X~wchMUtN3$1QaSDru$XZESjYPCKn;osRZ-|YR5)jgYKr}xxje($fE2UO za-!kOa+i{11Lp#r7j2z)?JamO+C=RrbK4%QKK^T^L^(|d;UB5q!vH7^ zWjgf!QLTR-?>^3MH@1j<_Gib~^zo#|G1%x_J+53WwwIfq-`@#lpOGH` z)l4@|AoCFq2EOn#UDdOdYPT?+u&3j$bst&IeWHuw;~{O=Nlr}}tqL0iBhlE6RVr^Y z*It~T6MhYp(WmA9YQv0*^<}w3aVN~Z>@-oVj4x%9U!OMn{gtQ5x@!OOxMOiFY~Ln#H7Kg8PjIH!XjAo%6O1%* z_l;{>l;j6N+)obpqNAoe%+6+0VZreh9VO^2h}{}okq^`Q_2m2cVA=1zEPnSocm2)z zWJSNh7;GF~#kXl=2Bv(QZ(laYVe!DBC{;W#kHf%kYO!#$a0dC8aBaTkh%i~(kBakZ zvHH}#xZS*-{C(*j9t?h{iwAGTIY_)F3PjQBHNSh5SLQoG=BMW4UGoRJpIS4pMyc3& zeL#`ol$QC4U*jESjs0A#-o(XNUC_?>7=4r(v2&CQl`X78PXokoIS^R<~VhhzH#ouFxU&vV=q1j_R#&TI!-q} zL1>cdv>v>F=T3fDpX1K((|8={Ai#0k>1+S@cBeQd!LvM7T zpGWgBr*Y$At`^IWY$KRhFfN*VcY|Ycor;6IriyxV3L772x3`7+E=MZ7MP)rffhsO; zLNqY0wx_^lAL|+ z;Wa75QZ<)HZOd#e=5HZc@{{zUhJJf%_SUU7vJYE09pXoTPc`X;p4v`Wt)ehoi{x+I zIugmd_Ema}L*AW6Z;Kw3*R`W*xSvw_(P%EXJ2yM8VdV*Wxbr-A7L(||gXKO z46n6~?(qRnByr4RpT9p{Klc$?BscdMiO@xJI@@l>x)pG6o9*SfO<{S5Xi^`ZphcWs z4ly{Rv~!lq8K^;m%IYh zraUZI2MmB#4{N75On5F0j4U_p2W^Khb!U+n`q=)Sla`H4@hu3?>P<7Ci{F znvEQ2HD`<8^!m^)wC2f3!n`?mHW~Z)&TU+UQM|U`&vbU6^G%RhJ50Ed>i%+oS@(_K zWN?=G8@3u`N6j{|M&i_TK=1L1%-@4ThujO8s&EFqJ!X=XH(p=-f_)+?=zlD6g`&oSg^Ny2$CH7X#oc9O?43L?GnbQZ*QiFD`N6-P%i3 z9)8C??J<1KwWsc{()8U08^XRWU(L*O-&u)Ufz^n~t$(I0KRv!|S!xxyV^?7ms7X7& z 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 S-TUvo_x}O0aChAZ&jA1+pb82A literal 0 HcmV?d00001 diff --git a/src/tests/test-utils/it_utils.cpp b/src/tests/test-utils/it_utils.cpp index e1da9c16..2e7f9ebb 100644 --- a/src/tests/test-utils/it_utils.cpp +++ b/src/tests/test-utils/it_utils.cpp @@ -82,10 +82,12 @@ void ItUtils::add_mail(const char *message, const char *mailbox, struct mail_nam std::string pool = "mail_storage"; std::string suffix = "_u"; + EXPECT_CALL(*cfg_mock, get_index_pool_name()).WillRepeatedly(ReturnRef(pool)); EXPECT_CALL(*cfg_mock, get_rados_username()).WillRepeatedly(ReturnRef(user)); EXPECT_CALL(*cfg_mock, get_rados_cluster_name()).WillRepeatedly(ReturnRef(cluster)); EXPECT_CALL(*cfg_mock, get_pool_name()).WillRepeatedly(ReturnRef(pool)); EXPECT_CALL(*cfg_mock, get_user_suffix()).WillRepeatedly(ReturnRef(suffix)); + EXPECT_CALL(*cfg_mock, get_chunk_size()).WillOnce(Return(100)); storage->ns_mgr->set_config(cfg_mock); storage->config = cfg_mock; From 582f0a1810ffd1684ef9696e3176db70220dc527 Mon Sep 17 00:00:00 2001 From: Jan Radon Date: Thu, 26 Jan 2023 08:55:20 +0100 Subject: [PATCH 14/18] delete ceph index if doveadm mailbox delete -u <> INBOX --- CHANGELOG.md | 3 +++ configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/storage-rbox/rbox-storage.cpp | 12 +++++++++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8536b6f..cf539d57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Change Log +## [0.0.48](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.48) (2023-01-26) +- cleanup ceph-index in case of mailbox INBOX delete + ## [0.0.47](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.47) (2022-12-05) - #355 fix gzip trailer when stream is empty fix save_method 1+2 buffersize (1 byte short) diff --git a/configure.ac b/configure.ac index bcb784e5..84472ffc 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.47], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.48], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index ddc4e641..b4981d60 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,7 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.47 +Version: 0.0.48 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/storage-rbox/rbox-storage.cpp b/src/storage-rbox/rbox-storage.cpp index f7f19b50..42cb4102 100644 --- a/src/storage-rbox/rbox-storage.cpp +++ b/src/storage-rbox/rbox-storage.cpp @@ -957,11 +957,21 @@ int rbox_storage_mailbox_delete(struct mailbox *box) { return ret; } if (r_storage->config->is_user_mapping()) { // - struct rbox_mailbox *rbox = (struct rbox_mailbox *)box; ret = check_users_mailbox_delete_ns_object(rbox->storage->storage.user, r_storage->config, r_storage->ns_mgr, r_storage->s); } + + if( r_storage->config->get_object_search_method() == 2 && + strcpy(box->name,'INBOX') == 0 ){ + + if(r_storage->s->ceph_index_delete()<1){ + i_warning("ceph_index delete failed, ceph index still exists"); + }else { + i_debug("rbox_storage_mailbox_delete: deleting ceph index: %d", ret); + } + } + FUNC_END(); return ret; From 32aa297f263a464738465149f98bd9a33c44111a Mon Sep 17 00:00:00 2001 From: Jan Fabian Radon Date: Sun, 29 Jan 2023 23:15:44 +0100 Subject: [PATCH 15/18] Fix/ceph index (#367) * fix ceph index size calculation * delete ceph-index in case its activated * version string --- CHANGELOG.md | 3 +++ configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/storage-rbox/rbox-save.cpp | 5 ++-- src/storage-rbox/rbox-storage.cpp | 38 ++++++++++++++++++------------- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf539d57..70682fc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Change Log +## [0.0.49](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.49) (2023-01-29) +- fix: cleanup ceph-index in case of mailbox INBOX delete + ## [0.0.48](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.48) (2023-01-26) - cleanup ceph-index in case of mailbox INBOX delete diff --git a/configure.ac b/configure.ac index 84472ffc..0cfe9584 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.48], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.49], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index b4981d60..ed5407bd 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,7 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.48 +Version: 0.0.49 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 456d31a4..6a6bc313 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -657,9 +657,10 @@ int rbox_save_finish(struct mail_save_context *_ctx) { if( r_storage->config->get_object_search_method() == 2){ // ceph config schalter an oder aus! r_storage->s->ceph_index_append(*r_ctx->rados_mail->get_oid()); - uint64_t index_size = r_storage->s->ceph_index_size(); + uint64_t index_size = r_storage->s->ceph_index_size(); + double ceph_index_size_percent = ((double)index_size / (double)r_storage->s->get_max_object_size()) *(double)100.0; // WARN if index reaches 80% of max object size - if( ((index_size/r_storage->s->get_max_object_size()) * 100) > 80) { + if( ceph_index_size_percent > 0.80) { i_warning("ceph_index file(%d) close to exceed max_object size(%d), recalc index !", index_size, r_storage->s->get_max_object_size() ); } } diff --git a/src/storage-rbox/rbox-storage.cpp b/src/storage-rbox/rbox-storage.cpp index 42cb4102..5fd17b42 100644 --- a/src/storage-rbox/rbox-storage.cpp +++ b/src/storage-rbox/rbox-storage.cpp @@ -939,40 +939,46 @@ int check_users_mailbox_delete_ns_object(struct mail_user *user, librmb::RadosDo int rbox_storage_mailbox_delete(struct mailbox *box) { FUNC_START(); + int ret = index_storage_mailbox_delete(box); if (ret < 0) { i_debug("while processing index_storage_mailbox_delete: %d", ret); return ret; } + struct rbox_storage *r_storage = (struct rbox_storage *)box->storage; - // 90 plugin konfigurierbar! read_plugin_configuration(box); - if (!r_storage->config->is_rbox_check_empty_mailboxes()) { - return ret; - } - + ret = rbox_open_rados_connection(box, false); if (ret < 0) { i_debug("rbox_storage_mailbox_delete: Opening rados connection : %d", ret); return ret; } - if (r_storage->config->is_user_mapping()) { // - struct rbox_mailbox *rbox = (struct rbox_mailbox *)box; - ret = check_users_mailbox_delete_ns_object(rbox->storage->storage.user, r_storage->config, r_storage->ns_mgr, - r_storage->s); - } - + i_debug("clean: deleting mailbox %s check if ceph index need to be deleted. %ld , %d, compare %d box='%s' with '%s'", + box->name, r_storage->config, + r_storage->config->get_object_search_method(), + strcmp(box->name,"INBOX") == 0, + box->name, "INBOX"); + if( r_storage->config->get_object_search_method() == 2 && - strcpy(box->name,'INBOX') == 0 ){ - - if(r_storage->s->ceph_index_delete()<1){ - i_warning("ceph_index delete failed, ceph index still exists"); + strcmp(box->name,"INBOX") == 0 ){ + int ceph_index_delete_ret = r_storage->s->ceph_index_delete(); + if(ceph_index_delete_ret<0){ + i_warning("ceph_index delete failed, ceph index still exists : ret = ", ceph_index_delete_ret); }else { i_debug("rbox_storage_mailbox_delete: deleting ceph index: %d", ret); } } - + if (!r_storage->config->is_rbox_check_empty_mailboxes()) { + return ret; + } + if (r_storage->config->is_user_mapping()) { // + struct rbox_mailbox *rbox = (struct rbox_mailbox *)box; + ret = check_users_mailbox_delete_ns_object(rbox->storage->storage.user, r_storage->config, r_storage->ns_mgr, + r_storage->s); + } + FUNC_END(); return ret; } From 012aac7bc150edb12749c20df895c688dddebea9 Mon Sep 17 00:00:00 2001 From: Jan Radon Date: Thu, 2 Feb 2023 11:01:19 +0100 Subject: [PATCH 16/18] fix ceph-index-file-size calculation --- .github/workflows/build.yml | 4 +++- CHANGELOG.md | 3 ++- configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/librmb/rados-util.cpp | 10 ++++++++++ src/librmb/rados-util.h | 2 ++ src/storage-rbox/rbox-save.cpp | 14 +++++++------- 7 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e846267..090ebce8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,5 +35,7 @@ jobs: run: ./autogen.sh && ./configure --with-dovecot=/usr/local/lib/dovecot --enable-maintainer-mode --enable-debug --with-integration-tests --enable-valgrind --enable-debug - name: build run: make clean install - - name: tests + - name: test_storage_mock_rbox run: valgrind src/tests/test_storage_mock_rbox_bugs + - name: test_utils + run: valgrind src/tests/test_librmb_utils diff --git a/CHANGELOG.md b/CHANGELOG.md index e4bcb076..b6d618df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Change Log - +## [0.0.50](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.50) (2023-02-02) +- fix: cleanup ceph-index size calculation ## [0.0.49](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.49) (2023-01-29) - fix: cleanup ceph-index in case of mailbox INBOX delete diff --git a/configure.ac b/configure.ac index aa69df9d..c31b53ac 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.49], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.50], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index 1bc18dfd..26c90843 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,7 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.49 +Version: 0.0.50 Release: 0%{?dist} diff --git a/src/librmb/rados-util.cpp b/src/librmb/rados-util.cpp index 2668bbb9..9f84adbc 100644 --- a/src/librmb/rados-util.cpp +++ b/src/librmb/rados-util.cpp @@ -389,4 +389,14 @@ namespace librmb { } return index; } + + static double RadosUtils::object_size_percent(const double object_size, const double max_object_size) { + double ceph_index_size_percent = ( object_size / max_object_size ) * (double) 100.0; + return ceph_index_size_percent; + } + + static bool RadosUtils::object_size_close_to_reach_max(const double object_size, const double max_object_size) { + return RadosUtils::object_size_percent(object_size, max_object_size) > 80; + } + } // namespace librmb diff --git a/src/librmb/rados-util.h b/src/librmb/rados-util.h index b3ae010f..e7cdf5cc 100644 --- a/src/librmb/rados-util.h +++ b/src/librmb/rados-util.h @@ -201,6 +201,8 @@ class RadosUtils { static std::string convert_to_ceph_index(const std::string &str); static std::set ceph_index_to_set(const std::string &str); + static double object_size_percent(const double object_size, const double max_object_size); + static bool object_size_close_to_reach_max(const double object_size, const double max_object_size); }; } // namespace librmb diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index 6a6bc313..a17a9c59 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -653,18 +653,18 @@ int rbox_save_finish(struct mail_save_context *_ctx) { i_error("saved mail: %s failed. Metadata_count %ld, mail_size (%d)", r_ctx->rados_mail->get_oid()->c_str(), r_ctx->rados_mail->get_metadata()->size(), r_ctx->rados_mail->get_mail_size()); }else{ - if( r_storage->config->get_object_search_method() == 2){ // ceph config schalter an oder aus! - r_storage->s->ceph_index_append(*r_ctx->rados_mail->get_oid()); - uint64_t index_size = r_storage->s->ceph_index_size(); - double ceph_index_size_percent = ((double)index_size / (double)r_storage->s->get_max_object_size()) *(double)100.0; - // WARN if index reaches 80% of max object size - if( ceph_index_size_percent > 0.80) { - i_warning("ceph_index file(%d) close to exceed max_object size(%d), recalc index !", index_size, r_storage->s->get_max_object_size() ); + r_storage->s->ceph_index_append(*r_ctx->rados_mail->get_oid()); + if( librmb::RadosUtils::object_size_close_to_reach_max( + (double)r_storage->s->ceph_index_size(), + (double) r_storage->s->get_max_object_size()) + ) { + i_warning("ceph_index file(%d) close to exceed max_object size(%d) 80%, recalc index !", r_storage->s->ceph_index_size(), r_storage->s->get_max_object_size() ); } } } + if (r_storage->save_log->is_open()) { r_storage->save_log->append( librmb::RadosSaveLogEntry(*r_ctx->rados_mail->get_oid(), r_storage->s->get_namespace(), From 311335ebd2d89023e866c2862f45d2f9a4d0bf5a Mon Sep 17 00:00:00 2001 From: Jan Radon Date: Thu, 2 Feb 2023 21:00:32 +0100 Subject: [PATCH 17/18] added unit test for object calculation --- src/tests/librmb/test_librmb_utils.cpp | 86 ++++++++------------------ 1 file changed, 25 insertions(+), 61 deletions(-) diff --git a/src/tests/librmb/test_librmb_utils.cpp b/src/tests/librmb/test_librmb_utils.cpp index 2f780c4a..85d498b0 100644 --- a/src/tests/librmb/test_librmb_utils.cpp +++ b/src/tests/librmb/test_librmb_utils.cpp @@ -290,57 +290,6 @@ __attribute__((noreturn)) static void *write_to_save_file(void *threadid) { pthread_exit(NULL); } -TEST(librmb, append_to_existing_file_multi_threading) { - std::string test_file_name = "test1.log"; - int rc; - void *status; - pthread_attr_t attr; - pthread_t threads[5]; - // Initialize and set thread joinable - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - - for (uintptr_t i = 0; i < 5; i++) { - rc = pthread_create(&threads[i], NULL, write_to_save_file, (void *)i); - } - sleep(1); - std::cout << " threads created " << std::endl; - // free attribute and wait for the other threads - pthread_attr_destroy(&attr); - for (int i = 0; i < 5; i++) { - rc = pthread_join(threads[i], &status); - if (rc) { - std::cout << "Error:unable to join," << rc << std::endl; - exit(-1); - } - - std::cout << "Main: completed thread id :" << i; - std::cout << " exiting with status :" << status << std::endl; - } - sleep(1); - int line_count = 0; - /** check content **/ - std::ifstream read(test_file_name); - while (true) { - librmb::RadosSaveLogEntry entry; - read >> entry; - - if (read.eof()) { - break; - } - EXPECT_EQ(entry.oid, "abc"); - EXPECT_EQ(entry.ns, "ns_1"); - EXPECT_EQ(entry.pool, "mail_storage"); - EXPECT_EQ(entry.op, "save"); - line_count++; - } - EXPECT_EQ(25, line_count); - read.close(); - std::remove(test_file_name.c_str()); - - std::cout << " exiting main " << std::endl; - // pthread_exit(NULL); -} TEST(librmb, test_mvn_option) { std::list metadata; @@ -377,18 +326,33 @@ TEST(librmb, test_mvn_option) { std::remove(test_file_name.c_str()); } -/*TEST(librmb, test_if) { - int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +TEST(librmb, test_max_object_size_calculation) { + + bool result = librmb::RadosUtils::object_size_close_to_reach_max(10.0,100.0); + EXPECT_EQ(result, false); + + result = librmb::RadosUtils::object_size_close_to_reach_max(81.0,100.0); + EXPECT_EQ(result, true); + + result = librmb::RadosUtils::object_size_close_to_reach_max(112820433.0 + ,134217728.0); + EXPECT_EQ(result, true); + + result = librmb::RadosUtils::object_size_close_to_reach_max(13646193.0 + ,134217728.0); + EXPECT_EQ(result, false); + + result = librmb::RadosUtils::object_size_close_to_reach_max(13646226.0 + ,134217728.0); + EXPECT_EQ(result, false); + + result = librmb::RadosUtils::object_size_close_to_reach_max(13646259.0 + ,134217728.0); + EXPECT_EQ(result, false); + +} - for (int i = 0; i < 10; ++i) { - std::cout << " value: " << arr[i] << std::endl; - } - for (int i = 0; i < 10; i++) { - std::cout << " value: " << arr[i] << std::endl; - } - EXPECT_EQ(1, 2); -}*/ TEST(librmb, mock_obj) {} int main(int argc, char **argv) { ::testing::InitGoogleMock(&argc, argv); From 4292fdc33d13da10f0ed7eaa19d5ba8dfd08c250 Mon Sep 17 00:00:00 2001 From: Jan Radon Date: Thu, 9 Feb 2023 17:48:39 +0100 Subject: [PATCH 18/18] fix not escaped % i_warning --- CHANGELOG.md | 2 ++ configure.ac | 2 +- rpm/dovecot-ceph-plugin.spec | 2 +- src/storage-rbox/rbox-save.cpp | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f643812c..08d5dc77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Change Log +## [0.0.51](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.51) (2023-02-09) +- fix: not escaped % in i_warning. ## [0.0.50](https://github.com/ceph-dovecot/dovecot-ceph-plugin/tree/0.0.50) (2023-02-02) - fix: cleanup ceph-index size calculation diff --git a/configure.ac b/configure.ac index c31b53ac..a6e6a00c 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ AC_PREREQ([2.59]) -AC_INIT([dovecot-ceph-plugin], [0.0.50], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) +AC_INIT([dovecot-ceph-plugin], [0.0.51], [https://github.com/ceph-dovecot/dovecot-ceph-plugin/issues/new], ,[https://github.com/ceph-dovecot/dovecot-ceph-plugin]) AC_CONFIG_AUX_DIR([.]) diff --git a/rpm/dovecot-ceph-plugin.spec b/rpm/dovecot-ceph-plugin.spec index b3ddf224..fe1ecd45 100644 --- a/rpm/dovecot-ceph-plugin.spec +++ b/rpm/dovecot-ceph-plugin.spec @@ -14,7 +14,7 @@ Name: dovecot-ceph-plugin Summary: Dovecot Ceph RADOS plugins -Version: 0.0.50 +Version: 0.0.51 Release: 0%{?dist} URL: https://github.com/ceph-dovecot/dovecot-ceph-plugin diff --git a/src/storage-rbox/rbox-save.cpp b/src/storage-rbox/rbox-save.cpp index a17a9c59..90bbbe66 100644 --- a/src/storage-rbox/rbox-save.cpp +++ b/src/storage-rbox/rbox-save.cpp @@ -660,7 +660,7 @@ int rbox_save_finish(struct mail_save_context *_ctx) { (double)r_storage->s->ceph_index_size(), (double) r_storage->s->get_max_object_size()) ) { - i_warning("ceph_index file(%d) close to exceed max_object size(%d) 80%, recalc index !", r_storage->s->ceph_index_size(), r_storage->s->get_max_object_size() ); + i_warning("ceph index file (%lu) close to exceed max object size(%d) 80%%",r_storage->s->ceph_index_size(), r_storage->s->get_max_object_size()); } } }