Skip to content

Enable automatic client reset handling for audit Realms #8072

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Enhancements
* <New feature description> (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
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
Expand Down
3 changes: 2 additions & 1 deletion src/realm/object-store/audit.mm
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,7 @@ explicit AuditRealmPool(Private, std::shared_ptr<SyncUser> user, const AuditConf
RealmConfig config;
config.path = db->get_path();
config.sync_config = std::make_shared<SyncConfig>(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;
}
Expand Down Expand Up @@ -897,7 +898,7 @@ explicit AuditRealmPool(Private, std::shared_ptr<SyncUser> user, const AuditConf
std::string partition = ObjectId::gen().to_string();
auto sync_config = std::make_shared<SyncConfig>(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,
Expand Down
108 changes: 103 additions & 5 deletions test/object-store/audit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
#include <util/sync/baas_admin_api.hpp>
#include <util/sync/flx_sync_harness.hpp>

#include <realm/set.hpp>
#include <realm/list.hpp>
#include <realm/dictionary.hpp>
#include <realm/list.hpp>
#include <realm/set.hpp>
#include <realm/sync/noinst/client_history_impl.hpp>
#include <realm/util/logger.hpp>

#include <realm/object-store/audit.hpp>
#include <realm/object-store/audit_serializer.hpp>
Expand All @@ -41,10 +43,7 @@
#include <realm/object-store/sync/mongo_database.hpp>
#include <realm/object-store/sync/mongo_collection.hpp>

#include <realm/util/logger.hpp>

#include <catch2/catch_all.hpp>

#include <external/json/json.hpp>

using namespace realm;
Expand All @@ -70,6 +69,14 @@ struct AuditEvent {
std::map<std::string, std::string> metadata;
};

std::ostream& operator<<(std::ostream& os, const std::vector<AuditEvent>& events)
{
for (auto& event : events) {
util::format(os, "%1: %2\n", event.event, event.data);
}
return os;
}

util::Optional<std::string> to_optional_string(StringData sd)
{
return sd ? util::Optional<std::string>(sd) : none;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Obj> 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<sync::ClientHistory*>(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);
Expand Down
Loading