diff --git a/CHANGELOG.md b/CHANGELOG.md index 021a2f960e..66424d1efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Enhancements * (PR [#????](https://github.com/realm/realm-core/pull/????)) -* None. +* Enable automatic client reset recovery for audit Realm files ([PR #8072](https://github.com/realm/realm-core/pull/8072)). ### Fixed * ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) diff --git a/src/realm/object-store/audit.mm b/src/realm/object-store/audit.mm index 85313cf96c..49dda63a94 100644 --- a/src/realm/object-store/audit.mm +++ b/src/realm/object-store/audit.mm @@ -840,6 +840,7 @@ explicit AuditRealmPool(Private, std::shared_ptr user, const AuditConf RealmConfig config; config.path = db->get_path(); config.sync_config = std::make_shared(m_user, prefixed_partition(partition)); + config.sync_config->client_resync_mode = ClientResyncMode::Recover; wait_for_upload(m_user->sync_manager()->get_session(db, config)); return; } @@ -897,7 +898,7 @@ explicit AuditRealmPool(Private, std::shared_ptr user, const AuditConf std::string partition = ObjectId::gen().to_string(); auto sync_config = std::make_shared(m_user, prefixed_partition(partition)); sync_config->apply_server_changes = false; - sync_config->client_resync_mode = ClientResyncMode::Manual; + sync_config->client_resync_mode = ClientResyncMode::Recover; sync_config->recovery_directory = std::string("io.realm.audit"); sync_config->stop_policy = SyncSessionStopPolicy::Immediately; sync_config->error_handler = [error_handler = m_error_handler, weak_self = weak_from_this()](auto, diff --git a/test/object-store/audit.cpp b/test/object-store/audit.cpp index 3e3432f015..e4ebd9f4f7 100644 --- a/test/object-store/audit.cpp +++ b/test/object-store/audit.cpp @@ -22,9 +22,11 @@ #include #include -#include -#include #include +#include +#include +#include +#include #include #include @@ -41,10 +43,7 @@ #include #include -#include - #include - #include using namespace realm; @@ -70,6 +69,14 @@ struct AuditEvent { std::map metadata; }; +std::ostream& operator<<(std::ostream& os, const std::vector& events) +{ + for (auto& event : events) { + util::format(os, "%1: %2\n", event.event, event.data); + } + return os; +} + util::Optional to_optional_string(StringData sd) { return sd ? util::Optional(sd) : none; @@ -1455,6 +1462,7 @@ TEST_CASE("audit management", "[sync][pbs][audit]") { audit->wait_for_completion(); auto events = get_audit_events(test_session); + INFO(events); REQUIRE(events.size() == 6); std::string str = events[0].data.dump(); // initial @@ -1928,6 +1936,96 @@ TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") { } } + SECTION("creating audit event while offline uploads event when logged back in") { + auto sync_user = session.app()->current_user(); + auto creds = create_user_and_log_in(session.app()); + auto audit_user = session.app()->current_user(); + config.audit_config->audit_user = audit_user; + config.audit_config->sync_error_handler = [&](SyncError error) { + REALM_ASSERT(ErrorCodes::error_categories(error.status.code()).test(ErrorCategory::app_error)); + }; + auto realm = Realm::get_shared_realm(config); + + audit_user->log_out(); + generate_event(realm); + log_in_user(session.app(), creds); + + REQUIRE(get_audit_events_from_baas(session, *sync_user, 1).size() == 1); + } + + SECTION("files with invalid client file idents are recovered") { + auto sync_user = session.app()->current_user(); + auto creds = create_user_and_log_in(session.app()); + auto audit_user = session.app()->current_user(); + config.audit_config->audit_user = audit_user; + config.audit_config->sync_error_handler = [&](SyncError error) { + REALM_ASSERT(ErrorCodes::error_categories(error.status.code()).test(ErrorCategory::app_error)); + }; + auto realm = Realm::get_shared_realm(config); + audit_user->log_out(); + + auto audit = realm->audit_context(); + REQUIRE(audit); + + // Set a small shard size so that we don't have to write an absurd + // amount of data to test this + audit_test_hooks::set_maximum_shard_size(32 * 1024); + auto cleanup = util::make_scope_exit([]() noexcept { + audit_test_hooks::set_maximum_shard_size(256 * 1024 * 1024); + }); + + realm->begin_transaction(); + auto table = realm->read_group().get_table("class_object"); + std::vector objects; + for (int i = 0; i < 2000; ++i) + objects.push_back(table->create_object_with_primary_key(i)); + realm->commit_transaction(); + + // Write a lot of audit scopes while unable to sync + for (int i = 0; i < 50; ++i) { + auto scope = audit->begin_scope(util::format("scope %1", i)); + Results(realm, table->where()).snapshot(); + audit->end_scope(scope, assert_no_error); + } + audit->wait_for_completion(); + + // Client file idents aren't reread while a session is active, so we need + // to close all of the open audit Realms awaiting upload + realm->close(); + realm = nullptr; + auto sync_manager = session.sync_manager(); + for (auto& session : sync_manager->get_all_sessions()) { + session->shutdown_and_wait(); + } + + // Set the client file ident for all pending Realms to an invalid one so + // that they'll get client resets + auto root = util::format("%1/realm-audit/%2/%3/audit", *session.config().storage_path, + session.app()->app_id(), audit_user->user_id()); + std::string file_name; + util::DirScanner dir(root); + while (dir.next(file_name)) { + if (!StringData(file_name).ends_with(".realm") || StringData(file_name).contains(".backup.")) + continue; + sync::ClientReplication repl; + auto db = DB::create(repl, root + "/" + file_name); + static_cast(repl._get_history_write())->set_client_file_ident({123, 456}, false); + } + + // Log the user back in and reopen the parent Realm to start trying to upload the audit data + log_in_user(session.app(), creds); + realm = Realm::get_shared_realm(config); + audit = realm->audit_context(); + REQUIRE(audit); + audit->wait_for_uploads(); + + auto events = get_audit_events_from_baas(session, *sync_user, 50); + REQUIRE(events.size() == 50); + for (int i = 0; i < 50; ++i) { + REQUIRE(events[i].activity == util::format("scope %1", i)); + } + } + #if 0 // This test takes ~10 minutes to run SECTION("large audit scope") { auto realm = Realm::get_shared_realm(config);