fix(directory): dedupe access/admin groups on repeated promotion; stop self-healing on every GET
Three independent copies of the same bug: routes/discovery.js's POST /discovery/promote/:slug (the actual "Promote" button in the UI) and services/discovery_reconciler.js's autoPromote path both called ResourceGroup.create() directly with no existence check -- unlike routes/api_directory_admin.js's own ensureResourceGroup, which already carried a comment describing this exact "groups appear 3x" bug and fixing it, just not everywhere it occurred. ResourceGroup has no DB unique constraint on (resourceId, groupCn), so a resource promoted more than once (retried UI click, or the same LXC discovered from multiple Proxmox cluster nodes) silently accumulated duplicate access/admin rows every time. Added ResourceGroup.ensure() (the existing check-then-create pattern, now on the model) and switched all three call sites to it. New regression test in tests/reconciler.test.js. Also: GET /api/directory-admin/resources ran a full group-model self-heal fan-out (ensureSiteGroups per site + provisionResourceGroups per resource, each several sequential LDAP round-trips) unconditionally on every single list -- confirmed via code read as the actual bottleneck once a directory has more than a handful of resources, not data volume. Moved healing to where resources actually change instead (POST/PUT /resources, POST /discovery/promote/:slug -- PUT had none at all before this), and added POST /resources/heal-groups as an explicit on-demand equivalent for backfilling a directory seeded before this change.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
const crypto = require('crypto');
|
||||
const { Model } = require('@simpleworkjs/orm');
|
||||
|
||||
const { Group } = require('./group_ldap');
|
||||
@@ -228,6 +229,18 @@ class ResourceGroup extends Model {
|
||||
groupCn: { type: 'string', isRequired: true },
|
||||
accessLevel: { type: 'string', isRequired: true }
|
||||
};
|
||||
|
||||
// No DB-level unique constraint on (resourceId, groupCn) exists, so callers
|
||||
// MUST check-then-create rather than relying on a constraint violation to
|
||||
// catch a dupe. A caller that skips this (raw ResourceGroup.create()) and
|
||||
// runs more than once for the same resource -- e.g. discovery reconciling
|
||||
// the same LXC from multiple Proxmox cluster nodes -- silently accumulates
|
||||
// duplicate access/admin rows every pass, with no error to notice it by.
|
||||
static async ensure(resourceId, groupCn, accessLevel) {
|
||||
const existing = await this.list({ where: { resourceId, groupCn } });
|
||||
if (existing.length) return existing[0];
|
||||
return this.create({ id: crypto.randomUUID(), resourceId, groupCn, accessLevel });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user