Commit ae3464e9 authored by jan.koester's avatar jan.koester
Browse files

test

parent b8eaf9e7
Loading
Loading
Loading
Loading
Loading
+61 −13
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
#include <algorithm>
#include <thread>
#include <chrono>
#include <future>
#include <map>
#include <tuple>
#include <shared_mutex>
@@ -294,12 +295,58 @@ namespace authdb {
        auto &client = g_Cluster->getReadClient();
        if (!client) return;

        // Single retrieve: manifest contains header + magic + entity keys + full record data
        // Single retrieve with timeout: manifest contains header + magic + entity keys + full record data
        std::vector<uint8_t> manifest;

        // Check if a previous timed-out fetch completed in the background
        if (cache->pending_retrieve.valid()) {
            auto st = cache->pending_retrieve.wait_for(std::chrono::milliseconds(0));
            if (st == std::future_status::ready) {
                try {
            manifest = client->retrieve(dgid);
                    manifest = cache->pending_retrieve.get();
                    cache->last_fetch = now;
                    cache->recovery_epoch = cur_epoch;
                } catch (const std::exception &e) {
                    std::cerr << "ClusterBackend::fetchFromCluster: background retrieve failed: "
                              << e.what() << std::endl;
                    _ClusterDataExists.store(true);
                    return;
                } catch (...) {
                    _ClusterDataExists.store(true);
                    return;
                }
            } else {
                // Previous fetch still running — use cached data, don't start another
                if (!cache->buffer.empty()) {
                    _Buffer = cache->buffer;
                    _ClusterDataExists.store(_Buffer.size() > sizeof(AuthHeader));
                }
                return;
            }
        } else {
            // No pending fetch — start a new async retrieve with timeout
            try {
                auto *raw_client = client.get();
                cache->pending_retrieve = std::async(std::launch::async,
                    [raw_client, dgid]() {
                        return raw_client->retrieve(dgid);
                    });

                static constexpr auto RETRIEVE_TIMEOUT = std::chrono::seconds(5);
                if (cache->pending_retrieve.wait_for(RETRIEVE_TIMEOUT) == std::future_status::ready) {
                    manifest = cache->pending_retrieve.get();
                    cache->last_fetch = now;
                    cache->recovery_epoch = cur_epoch;
                } else {
                    // Timed out — future stays in cache, will be checked next call
                    std::cerr << "[CLUSTER-BE] domain=" << _Domain
                              << " retrieve timed out after 5s, using cached data" << std::endl;
                    if (!cache->buffer.empty()) {
                        _Buffer = cache->buffer;
                        _ClusterDataExists.store(_Buffer.size() > sizeof(AuthHeader));
                    }
                    return;
                }
            } catch (const std::exception &e) {
                std::cerr << "ClusterBackend::fetchFromCluster: retrieve failed: "
                          << e.what() << std::endl;
@@ -309,6 +356,7 @@ namespace authdb {
                _ClusterDataExists.store(true);
                return;
            }
        }

        if (manifest.size() < sizeof(AuthHeader) + sizeof(uint32_t) + sizeof(uint32_t)) {
            if (!manifest.empty())
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
#include <cstdint>
#include <atomic>
#include <chrono>
#include <future>
#include <mutex>
#include <map>
#include <set>
@@ -55,6 +56,7 @@ namespace authdb {
        std::chrono::steady_clock::time_point last_fetch{};
        uint64_t recovery_epoch{0};
        std::mutex fetch_mutex;
        std::future<std::vector<uint8_t>> pending_retrieve;
    };

    class VISIBILITY ClusterBackend : public AuthBackendApi {
+4 −0
Original line number Diff line number Diff line
@@ -544,11 +544,15 @@ namespace authdb {

                if (health.nodes_online >= n) {
                    std::cerr << "Cluster: all nodes online" << std::endl;
                    critical_ = false;
                    degraded_ = false;
                    return health.nodes_online;
                }
                if (health.nodes_online >= k) {
                    std::cerr << "Cluster: DEGRADED — " << health.nodes_online
                              << "/" << n << " nodes online, continuing" << std::endl;
                    critical_ = false;
                    degraded_ = true;
                    return health.nodes_online;
                }
            } catch (const std::exception &e) {
+61 −21
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include <chrono>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
@@ -19,7 +20,20 @@
#include <string>
#include <thread>
#include <vector>

#ifdef _WIN32
#include <process.h>
#include <io.h>
#include <windows.h>
#include <psapi.h>
#define test_getpid  _getpid
#define test_unlink  _unlink
#else
#include <unistd.h>
#define test_getpid  getpid
#define test_unlink  unlink
#endif

#include <uuidp.h>

#include "backend.h"
@@ -56,6 +70,12 @@ using authdb::RecordIndex;
// ═════════════════════════════════════════════════════════════

static size_t getRSSKb() {
#ifdef _WIN32
    PROCESS_MEMORY_COUNTERS pmc;
    if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
        return pmc.WorkingSetSize / 1024;
    return 0;
#else
    std::ifstream ifs("/proc/self/status");
    std::string line;
    while (std::getline(ifs, line)) {
@@ -66,6 +86,7 @@ static size_t getRSSKb() {
        }
    }
    return 0;
#endif
}

using Clock = std::chrono::high_resolution_clock;
@@ -102,8 +123,26 @@ static void printStats(const char *label, const TimingStats &s, size_t n) {
static std::string g_certFile;
static std::string g_keyFile;

static std::string tempDir() {
#ifdef _WIN32
    const char *tmp = std::getenv("TEMP");
    if (!tmp) tmp = std::getenv("TMP");
    return tmp ? std::string(tmp) + "\\" : "C:\\Temp\\";
#else
    return "/tmp/";
#endif
}

static std::string devNull() {
#ifdef _WIN32
    return "NUL";
#else
    return "/dev/null";
#endif
}

static bool generateTestCerts() {
    std::string prefix = "/tmp/authdb_cluster_test_" + std::to_string(getpid());
    std::string prefix = tempDir() + "authdb_cluster_test_" + std::to_string(test_getpid());
    g_keyFile  = prefix + ".key";
    g_certFile = prefix + ".crt";

@@ -111,7 +150,7 @@ static bool generateTestCerts() {
    std::string cmd =
        "openssl req -x509 -newkey rsa:2048 "
        "-keyout " + g_keyFile + " -out " + g_certFile +
        " -days 1 -nodes -subj '/CN=authdb-test' -batch 2>/dev/null";
        " -days 1 -nodes -subj \"/CN=authdb-test\" -batch 2>" + devNull();

    int ret = system(cmd.c_str());
    if (ret != 0) {
@@ -121,7 +160,7 @@ static bool generateTestCerts() {

    // Convert key to DER (paritypp/QUIC expects DER private key)
    std::string derKey = prefix + ".key.der";
    cmd = "openssl rsa -in " + g_keyFile + " -outform DER -out " + derKey + " 2>/dev/null";
    cmd = "openssl rsa -in " + g_keyFile + " -outform DER -out " + derKey + " 2>" + devNull();
    ret = system(cmd.c_str());
    if (ret != 0) {
        std::cerr << "Failed to convert key to DER\n";
@@ -133,14 +172,14 @@ static bool generateTestCerts() {
}

static void cleanupTestCerts() {
    if (!g_certFile.empty()) unlink(g_certFile.c_str());
    if (!g_keyFile.empty()) unlink(g_keyFile.c_str());
    if (!g_certFile.empty()) test_unlink(g_certFile.c_str());
    if (!g_keyFile.empty()) test_unlink(g_keyFile.c_str());
    // Also clean up PEM key
    std::string pemKey = g_keyFile;
    auto pos = pemKey.rfind(".der");
    if (pos != std::string::npos) {
        pemKey = pemKey.substr(0, pos);
        unlink(pemKey.c_str());
        test_unlink(pemKey.c_str());
    }
}

@@ -167,11 +206,11 @@ protected:
        }

        // Allocate 3 consecutive ports based on PID
        int basePort = 14433 + (getpid() % 10000);
        int basePort = 14433 + (test_getpid() % 10000);
        for (int i = 0; i < NUM_NODES; ++i)
            basePorts[i] = basePort + i;

        fileDbPath = "/tmp/authdb_cluster_perf_" + std::to_string(getpid()) + ".db";
        fileDbPath = tempDir() + "authdb_cluster_perf_" + std::to_string(test_getpid()) + ".db";

        // Build common peer list (all 3 nodes)
        std::vector<ClusterNode> allPeers;
@@ -226,8 +265,8 @@ protected:
                clusters[i] = nullptr;
            }
        }
        unlink(fileDbPath.c_str());
        unlink((fileDbPath + ".lock").c_str());
        test_unlink(fileDbPath.c_str());
        test_unlink((fileDbPath + ".lock").c_str());
        cleanupTestCerts();
    }

@@ -389,15 +428,14 @@ TEST_F(ClusterPerformanceTest, GroupCreateReadBenchmark) {
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, ClusterSessionBenchmark) {
    // Wait up to 15s for the health monitor to clear the critical flag.
    // On startup the first node briefly goes CRITICAL before the other
    // two nodes are reachable; the monitor cycles every 3s in that state.
    // The waitForPeers() fix now clears critical_ before returning,
    // so the cluster should be ready.  Allow 3s grace for slow CI.
    if (authdb::g_Cluster) {
        for (int wait = 0; wait < 15 && authdb::g_Cluster->isCritical(); ++wait)
        for (int wait = 0; wait < 3 && authdb::g_Cluster->isCritical(); ++wait)
            std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    if (authdb::g_Cluster && authdb::g_Cluster->isCritical())
        GTEST_SKIP() << "Cluster is still critical after 15 s — cannot run session tests";
        GTEST_SKIP() << "Cluster is still critical after 3 s — cannot run session tests";

    constexpr size_t N = 50;

@@ -532,7 +570,7 @@ TEST_F(ClusterPerformanceTest, ClusterVsFileComparison) {
    std::vector<double> fileReadTimes, clusterReadTimes;

    // Use a separate file backend for comparison
    std::string cmpDbPath = "/tmp/authdb_cmp_" + std::to_string(getpid()) + ".db";
    std::string cmpDbPath = tempDir() + "authdb_cmp_" + std::to_string(test_getpid()) + ".db";
    AuthBackend cmpFile(authdb::File, cmpDbPath, "cmpdomain");

    std::vector<uuid::uuid> fids(N), cids(N);
@@ -562,7 +600,9 @@ TEST_F(ClusterPerformanceTest, ClusterVsFileComparison) {
        fileReadTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    // ── Cluster backend ──
    // ── Cluster backend (fresh domain for fair comparison) ──
    AuthBackend cmpCluster(authdb::ClusterStore, "", "cmpdomain");

    for (size_t i = 0; i < N; ++i) {
        cids[i].generate();
        class authdb::UserData udat(cids[i]);
@@ -570,7 +610,7 @@ TEST_F(ClusterPerformanceTest, ClusterVsFileComparison) {
        udat.setPwHash("pw");

        auto t0 = Clock::now();
        user.create(*clusterBackend, &udat);
        user.create(cmpCluster, &udat);
        auto t1 = Clock::now();
        clusterCreateTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }
@@ -580,7 +620,7 @@ TEST_F(ClusterPerformanceTest, ClusterVsFileComparison) {
        size_t pos = sizeof(AuthHeader);

        auto t0 = Clock::now();
        user.info(*clusterBackend, udat, pos);
        user.info(cmpCluster, udat, pos);
        auto t1 = Clock::now();
        clusterReadTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }
@@ -601,6 +641,6 @@ TEST_F(ClusterPerformanceTest, ClusterVsFileComparison) {
              << "    Ratio   = " << (cRead.avg_us / std::max(fRead.avg_us, 0.001)) << "x\n";

    // Cleanup comparison DB
    unlink(cmpDbPath.c_str());
    unlink((cmpDbPath + ".lock").c_str());
    test_unlink(cmpDbPath.c_str());
    test_unlink((cmpDbPath + ".lock").c_str());
}
+34 −3
Original line number Diff line number Diff line
@@ -9,13 +9,27 @@
 */
#include <gtest/gtest.h>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

#ifdef _WIN32
#include <process.h>
#include <io.h>
#include <windows.h>
#include <psapi.h>
#define test_getpid  _getpid
#define test_unlink  _unlink
#else
#include <unistd.h>
#define test_getpid  getpid
#define test_unlink  unlink
#endif

#include <uuidp.h>

#include "backend.h"
@@ -46,6 +60,12 @@ using authdb::LocalSession;
// ═════════════════════════════════════════════════════════════

static size_t getRSSKb() {
#ifdef _WIN32
    PROCESS_MEMORY_COUNTERS pmc;
    if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
        return pmc.WorkingSetSize / 1024;
    return 0;
#else
    std::ifstream ifs("/proc/self/status");
    std::string line;
    while (std::getline(ifs, line)) {
@@ -56,6 +76,7 @@ static size_t getRSSKb() {
        }
    }
    return 0;
#endif
}

static size_t getFileSize(const std::string &path) {
@@ -95,20 +116,30 @@ static void printStats(const char *label, const TimingStats &s, size_t n) {
//  Fixture
// ═════════════════════════════════════════════════════════════

static std::string tempDir() {
#ifdef _WIN32
    const char *tmp = std::getenv("TEMP");
    if (!tmp) tmp = std::getenv("TMP");
    return tmp ? std::string(tmp) + "\\" : "C:\\Temp\\";
#else
    return "/tmp/";
#endif
}

class PerformanceTest : public ::testing::Test {
protected:
    std::string dbPath;
    AuthBackend *backend = nullptr;

    void SetUp() override {
        dbPath = "/tmp/authdb_perf_" + std::to_string(getpid()) + ".db";
        dbPath = tempDir() + "authdb_perf_" + std::to_string(test_getpid()) + ".db";
        backend = new AuthBackend(authdb::File, dbPath, "perfdomain");
    }

    void TearDown() override {
        delete backend;
        unlink(dbPath.c_str());
        unlink((dbPath + ".lock").c_str());
        test_unlink(dbPath.c_str());
        test_unlink((dbPath + ".lock").c_str());
    }
};