Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ for bid, backup in backups.items():
instances = rdb.hget(f"cluster/backup/{bid}", "instances") or ""
for mid in instances.split():
module_uuid = rdb.hget("cluster/module_uuid", mid)
if module_uuid is None:
# Skip stale references to modules removed from the cluster
continue
path_prefix = agent.get_image_name_from_url(rdb.hget(f"module/{mid}/environment", "IMAGE_URL") or "/:")
status = backup_statuses.get((bid, mid))
if status is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ rdb.delete(f'cluster/authorizations/module/{module_id}')
rdb.hdel('cluster/module_node', module_id)
rdb.hdel('cluster/module_uuid', module_id)

# Remove the module from any backup schedule and purge its backup status
for kbackup in rdb.scan_iter('cluster/backup/*'):
instances = (rdb.hget(kbackup, 'instances') or '').split()
if module_id in instances:
instances.remove(module_id)
rdb.hset(kbackup, 'instances', ' '.join(instances))
for kstatus in rdb.scan_iter('node/*/backup_status/*'):
rdb.hdel(kstatus, module_id)

# Revoke module permissions, clean up permissions of other modules
rdb.delete(f'roles/module/{module_id}')
permpipe = rdb.pipeline()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,25 @@ for xmodule_id, xnode_id in rdb.hgetall('cluster/module_node').items():
# Save ACLs on the disk and propagate to worker nodes
cluster.grants.save_acls(rdb)

# Collect the backup schedule/status changes needed to forget the removed modules
backup_updates = {} # {cluster/backup/<id> key: trimmed "instances" string}
backup_status_keys = []
if remove_module_set:
for kbackup in rdb.scan_iter('cluster/backup/*'):
instances_set = set((rdb.hget(kbackup, 'instances') or '').split())
if instances_set.intersection(remove_module_set):
backup_updates[kbackup] = ' '.join(sorted(instances_set.difference(remove_module_set)))
backup_status_keys = list(rdb.scan_iter('node/*/backup_status/*'))

trx = rdb.pipeline()
if remove_module_set:
trx.hdel("cluster/module_node", *remove_module_set)
trx.hdel("cluster/module_domains", *remove_module_set)
trx.hdel("cluster/module_uuid", *remove_module_set)
for kbackup, instances in backup_updates.items():
trx.hset(kbackup, 'instances', instances)
for kstatus in backup_status_keys:
trx.hdel(kstatus, *remove_module_set)
for xmodule_id in remove_module_set:
trx.publish('cluster/event/module-removed', json.dumps({
'module': xmodule_id,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python3

#
# Copyright (C) 2026 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-3.0-or-later
#

import agent

#
# Bug NethServer/dev#8094: modules removed from the cluster before backup
# schedule cleanup was implemented were left dangling in backup schedules,
# backup status entries and cluster/module_uuid. Prune them, using
# cluster/module_node as the authoritative source of live module ids.
#

rdb = agent.redis_connect(privileged=True)
live_modules = set(rdb.hkeys('cluster/module_node'))

trx = rdb.pipeline()

dangling_mids = [mid for mid in rdb.hkeys('cluster/module_uuid') if mid not in live_modules]
if dangling_mids:
trx.hdel('cluster/module_uuid', *dangling_mids)

for kbackup in rdb.scan_iter('cluster/backup/*'):
instances_set = set((rdb.hget(kbackup, 'instances') or '').split())
if not instances_set.issubset(live_modules):
trx.hset(kbackup, 'instances', ' '.join(sorted(instances_set.intersection(live_modules))))

for kstatus in rdb.scan_iter('node/*/backup_status/*'):
dangling_mid_hkeys = [mid for mid in rdb.hkeys(kstatus) if mid not in live_modules]
if dangling_mid_hkeys:
trx.hdel(kstatus, *dangling_mid_hkeys)

trx.execute()
Loading