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

test

parent f1ac6ff3
Loading
Loading
Loading
Loading
Loading
+14 −0
Changes for src/cluster.h: 14 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -128,6 +128,20 @@ namespace fenrirds {

        bool isRunning() const { return running_; }

        // RID partitioning: this node's index among the peers and the peer
        // count, so each participant allocates RIDs from a disjoint residue
        // class (rid ≡ self mod count) instead of a shared counter — no
        // coordination, no duplicate SIDs across the cluster. Returns false
        // when this node cannot identify itself in PEERS (caller falls back to
        // the single-node scheme).
        bool ridPartition(uint32_t &self, uint32_t &count) const {
            if (self_idx_ == SIZE_MAX || cfg_.peers.empty())
                return false;
            self  = static_cast<uint32_t>(self_idx_);
            count = static_cast<uint32_t>(cfg_.peers.size());
            return self < count;
        }

        // Block until at least k nodes are reachable (up to timeout_s seconds).
        // Returns number of nodes online.
        size_t waitForPeers(int timeout_s = 10);
+54 −18
Changes for src/dsdb/directory.cpp: 54 added lines, 18 removed lines.
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
#include "directory.h"
#include "schema.h"
#include "replication.h"
#include "cluster.h"

namespace fenrirds {
namespace dsdb {
@@ -181,31 +182,66 @@ Secrets Directory::secretsOf(const uuid::uuid &guid) {
    return s;
}

void Directory::initRidPartition(uint32_t k, uint32_t n) {
    /* seed _ridM so the next RID this node hands out (RID_FIRST_FREE + k + m*n)
     * is strictly above every RID it already owns. The high-water comes from a
     * scan of existing objects — not a shared counter — so no cross-node
     * coordination is needed and a lost counter write can never cause reuse. */
    _ridK = k;
    _ridN = n ? n : 1;
    uint32_t floor = RID_FIRST_FREE;
    if (Entry *head = findByDnLocked(_Meta.baseDn))
        floor = uint32_t(intValue(head->attrs, "@nextRid", RID_FIRST_FREE));

    bool found = false;
    uint32_t maxM = 0;
    for (auto &e : _Entries) {
        if (e.objectSid.empty() || !e.objectSid.inDomain(_Meta.sid))
            continue;
        uint32_t r = e.objectSid.rid();
        if (r < RID_FIRST_FREE)
            continue;                                    /* well-known RID */
        if (((r - RID_FIRST_FREE) % _ridN) != (_ridK % _ridN))
            continue;                                    /* not our class */
        uint32_t m = (r - RID_FIRST_FREE - _ridK) / _ridN;
        if (!found || m > maxM) { maxM = m; found = true; }
    }
    _ridM = found ? maxM + 1 : 0;
    /* respect any pre-seeded @nextRid floor (e.g. from provisioning) */
    while ((RID_FIRST_FREE + _ridK + _ridM * _ridN) < floor)
        ++_ridM;
    _ridInit = true;
}

uint32_t Directory::allocateRid() {
    /* the RID pool lives on the domain head (@nextRid). Grows one at a time;
     * a proper RID manager hands out pools, this is enough for one DC.
     *
     * RID safety: only a single authoritative writer may allocate, or two
     * nodes hand out the same RID (→ two principals with the same SID). In
     * master/slave replication the master is that single writer; a slave is a
     * read-only replica, so refuse here — otherwise a write on a slave would
     * burn a RID locally that the next snapshot from the master overwrites.
     * (True multi-master still needs per-node RID pools, like AD's RID master.) */
    /* RID safety. Each participant hands out RIDs from a DISJOINT residue
     * class: rid = RID_FIRST_FREE + k + m*n, where (k, n) is this node's index
     * and the peer count. Different nodes have different k → their RID sets
     * never intersect, so no two nodes ever mint the same SID — no shared
     * counter, no coordination, safe even on the eventually-consistent cluster
     * store. Single node / master-slave is simply n=1, k=0 (the classic
     * contiguous scheme). A replication slave is read-only and must not
     * allocate at all. */
    if (g_Replication && g_Replication->isSlave())
        throw std::runtime_error("RID allocation refused: this node is a replication slave (read-only); make changes on the master");

    std::lock_guard<std::mutex> lk(_Mtx);
    ensureFresh();
    Entry *head = findByDnLocked(_Meta.baseDn);
    if (!head)
    if (!findByDnLocked(_Meta.baseDn))
        throw std::runtime_error("domain not provisioned");
    uint32_t next = uint32_t(intValue(head->attrs, "@nextRid", RID_FIRST_FREE));
    AttrMap delta = head->attrs;
    setInt(delta, "@nextRid", next + 1);
    storeObject(_Backend, head->objectGuid, head->dn, delta, objectSecrets(_Backend, head->objectGuid), false);
    head->attrs = delta;
    _Revision = _Backend.getRevesion();
    return next;

    uint32_t k = 0, n = 1;
    if (g_Cluster && g_Cluster->isRunning()) {
        uint32_t s = 0, c = 0;
        if (g_Cluster->ridPartition(s, c) && c > 0) { k = s; n = c; }
    }
    /* (re)seed when first used or the cluster topology changed */
    if (!_ridInit || k != _ridK || n != _ridN)
        initRidPartition(k, n);

    uint32_t rid = RID_FIRST_FREE + _ridK + _ridM * _ridN;
    ++_ridM;
    return rid;
}

uuid::uuid Directory::create(const Dn &dn, AttrMap attrs, const Secrets *secrets) {
+12 −0
Changes for src/dsdb/directory.h: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -129,6 +129,9 @@ namespace fenrirds {
            void loadAll();                      /* (re)build the entry cache */
            void addEntryToIndex(const Entry &e, size_t idx);
            Entry *findByDnLocked(const Dn &dn);
            /* seed the per-node RID counter (_ridM) for residue class k of n
             * from the highest existing RID this node already owns */
            void initRidPartition(uint32_t k, uint32_t n);

            AuthBackend &_Backend;
            DomainMeta   _Meta;
@@ -136,6 +139,15 @@ namespace fenrirds {
            std::mutex                            _Mtx;
            size_t                                _Revision = (size_t)-1;
            std::vector<Entry>                    _Entries;

            /* RID allocation (see allocateRid): each node hands out RIDs from a
             * disjoint residue class rid = RID_FIRST_FREE + k + m*n, so cluster
             * participants never collide. k/n are this node's partition, m the
             * local counter, seeded by a scan of existing objects. */
            uint32_t                              _ridK = 0;
            uint32_t                              _ridN = 1;
            uint32_t                              _ridM = 0;
            bool                                  _ridInit = false;
            std::map<std::string, size_t>         _ByDnKey;     /* dn.key() -> index */
            std::map<std::string, size_t>         _BySid;       /* sid.binary() -> index */
        };