489 lines
21 KiB
Diff
489 lines
21 KiB
Diff
From b6729a99f3a3d4c6ebe82d4bb60ea2a6f8727782 Mon Sep 17 00:00:00 2001
|
|
From: Simon Pichugin <spichugi@redhat.com>
|
|
Date: Fri, 27 Jun 2025 18:43:39 -0700
|
|
Subject: [PATCH] Issue 6822 - Backend creation cleanup and Database UI tab
|
|
error handling (#6823)
|
|
|
|
Description: Add rollback functionality when mapping tree creation fails
|
|
during backend creation to prevent orphaned backends.
|
|
Improve error handling in Database, Replication and Monitoring UI tabs
|
|
to gracefully handle backend get-tree command failures.
|
|
|
|
Fixes: https://github.com/389ds/389-ds-base/issues/6822
|
|
|
|
Reviewed by: @mreynolds389 (Thanks!)
|
|
---
|
|
src/cockpit/389-console/src/database.jsx | 119 ++++++++------
|
|
src/cockpit/389-console/src/monitor.jsx | 172 +++++++++++---------
|
|
src/cockpit/389-console/src/replication.jsx | 55 ++++---
|
|
src/lib389/lib389/backend.py | 18 +-
|
|
4 files changed, 210 insertions(+), 154 deletions(-)
|
|
|
|
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
|
|
index c0c4be414..276125dfc 100644
|
|
--- a/src/cockpit/389-console/src/database.jsx
|
|
+++ b/src/cockpit/389-console/src/database.jsx
|
|
@@ -478,6 +478,59 @@ export class Database extends React.Component {
|
|
}
|
|
|
|
loadSuffixTree(fullReset) {
|
|
+ const treeData = [
|
|
+ {
|
|
+ name: _("Global Database Configuration"),
|
|
+ icon: <CogIcon />,
|
|
+ id: "dbconfig",
|
|
+ },
|
|
+ {
|
|
+ name: _("Chaining Configuration"),
|
|
+ icon: <ExternalLinkAltIcon />,
|
|
+ id: "chaining-config",
|
|
+ },
|
|
+ {
|
|
+ name: _("Backups & LDIFs"),
|
|
+ icon: <CopyIcon />,
|
|
+ id: "backups",
|
|
+ },
|
|
+ {
|
|
+ name: _("Password Policies"),
|
|
+ id: "pwp",
|
|
+ icon: <KeyIcon />,
|
|
+ children: [
|
|
+ {
|
|
+ name: _("Global Policy"),
|
|
+ icon: <HomeIcon />,
|
|
+ id: "pwpolicy",
|
|
+ },
|
|
+ {
|
|
+ name: _("Local Policies"),
|
|
+ icon: <UsersIcon />,
|
|
+ id: "localpwpolicy",
|
|
+ },
|
|
+ ],
|
|
+ defaultExpanded: true
|
|
+ },
|
|
+ {
|
|
+ name: _("Suffixes"),
|
|
+ icon: <CatalogIcon />,
|
|
+ id: "suffixes-tree",
|
|
+ children: [],
|
|
+ defaultExpanded: true,
|
|
+ action: (
|
|
+ <Button
|
|
+ onClick={this.handleShowSuffixModal}
|
|
+ variant="plain"
|
|
+ aria-label="Create new suffix"
|
|
+ title={_("Create new suffix")}
|
|
+ >
|
|
+ <PlusIcon />
|
|
+ </Button>
|
|
+ ),
|
|
+ }
|
|
+ ];
|
|
+
|
|
const cmd = [
|
|
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
|
|
"backend", "get-tree",
|
|
@@ -491,58 +544,20 @@ export class Database extends React.Component {
|
|
suffixData = JSON.parse(content);
|
|
this.processTree(suffixData);
|
|
}
|
|
- const treeData = [
|
|
- {
|
|
- name: _("Global Database Configuration"),
|
|
- icon: <CogIcon />,
|
|
- id: "dbconfig",
|
|
- },
|
|
- {
|
|
- name: _("Chaining Configuration"),
|
|
- icon: <ExternalLinkAltIcon />,
|
|
- id: "chaining-config",
|
|
- },
|
|
- {
|
|
- name: _("Backups & LDIFs"),
|
|
- icon: <CopyIcon />,
|
|
- id: "backups",
|
|
- },
|
|
- {
|
|
- name: _("Password Policies"),
|
|
- id: "pwp",
|
|
- icon: <KeyIcon />,
|
|
- children: [
|
|
- {
|
|
- name: _("Global Policy"),
|
|
- icon: <HomeIcon />,
|
|
- id: "pwpolicy",
|
|
- },
|
|
- {
|
|
- name: _("Local Policies"),
|
|
- icon: <UsersIcon />,
|
|
- id: "localpwpolicy",
|
|
- },
|
|
- ],
|
|
- defaultExpanded: true
|
|
- },
|
|
- {
|
|
- name: _("Suffixes"),
|
|
- icon: <CatalogIcon />,
|
|
- id: "suffixes-tree",
|
|
- children: suffixData,
|
|
- defaultExpanded: true,
|
|
- action: (
|
|
- <Button
|
|
- onClick={this.handleShowSuffixModal}
|
|
- variant="plain"
|
|
- aria-label="Create new suffix"
|
|
- title={_("Create new suffix")}
|
|
- >
|
|
- <PlusIcon />
|
|
- </Button>
|
|
- ),
|
|
- }
|
|
- ];
|
|
+
|
|
+ let current_node = this.state.node_name;
|
|
+ if (fullReset) {
|
|
+ current_node = DB_CONFIG;
|
|
+ }
|
|
+
|
|
+ treeData[4].children = suffixData; // suffixes node
|
|
+ this.setState(() => ({
|
|
+ nodes: treeData,
|
|
+ node_name: current_node,
|
|
+ }), this.loadAttrs);
|
|
+ })
|
|
+ .fail(err => {
|
|
+ // Handle backend get-tree failure gracefully
|
|
let current_node = this.state.node_name;
|
|
if (fullReset) {
|
|
current_node = DB_CONFIG;
|
|
diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx
|
|
index ad48d1f87..91a8e3e37 100644
|
|
--- a/src/cockpit/389-console/src/monitor.jsx
|
|
+++ b/src/cockpit/389-console/src/monitor.jsx
|
|
@@ -200,6 +200,84 @@ export class Monitor extends React.Component {
|
|
}
|
|
|
|
loadSuffixTree(fullReset) {
|
|
+ const basicData = [
|
|
+ {
|
|
+ name: _("Server Statistics"),
|
|
+ icon: <ClusterIcon />,
|
|
+ id: "server-monitor",
|
|
+ type: "server",
|
|
+ },
|
|
+ {
|
|
+ name: _("Replication"),
|
|
+ icon: <TopologyIcon />,
|
|
+ id: "replication-monitor",
|
|
+ type: "replication",
|
|
+ defaultExpanded: true,
|
|
+ children: [
|
|
+ {
|
|
+ name: _("Synchronization Report"),
|
|
+ icon: <MonitoringIcon />,
|
|
+ id: "sync-report",
|
|
+ item: "sync-report",
|
|
+ type: "repl-mon",
|
|
+ },
|
|
+ {
|
|
+ name: _("Log Analysis"),
|
|
+ icon: <MonitoringIcon />,
|
|
+ id: "log-analysis",
|
|
+ item: "log-analysis",
|
|
+ type: "repl-mon",
|
|
+ }
|
|
+ ],
|
|
+ },
|
|
+ {
|
|
+ name: _("Database"),
|
|
+ icon: <DatabaseIcon />,
|
|
+ id: "database-monitor",
|
|
+ type: "database",
|
|
+ children: [], // Will be populated with treeData on success
|
|
+ defaultExpanded: true,
|
|
+ },
|
|
+ {
|
|
+ name: _("Logging"),
|
|
+ icon: <CatalogIcon />,
|
|
+ id: "log-monitor",
|
|
+ defaultExpanded: true,
|
|
+ children: [
|
|
+ {
|
|
+ name: _("Access Log"),
|
|
+ icon: <BookIcon size="sm" />,
|
|
+ id: "access-log-monitor",
|
|
+ type: "log",
|
|
+ },
|
|
+ {
|
|
+ name: _("Audit Log"),
|
|
+ icon: <BookIcon size="sm" />,
|
|
+ id: "audit-log-monitor",
|
|
+ type: "log",
|
|
+ },
|
|
+ {
|
|
+ name: _("Audit Failure Log"),
|
|
+ icon: <BookIcon size="sm" />,
|
|
+ id: "auditfail-log-monitor",
|
|
+ type: "log",
|
|
+ },
|
|
+ {
|
|
+ name: _("Errors Log"),
|
|
+ icon: <BookIcon size="sm" />,
|
|
+ id: "error-log-monitor",
|
|
+ type: "log",
|
|
+ },
|
|
+ {
|
|
+ name: _("Security Log"),
|
|
+ icon: <BookIcon size="sm" />,
|
|
+ id: "security-log-monitor",
|
|
+ type: "log",
|
|
+ },
|
|
+ ]
|
|
+ },
|
|
+ ];
|
|
+
|
|
const cmd = [
|
|
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
|
|
"backend", "get-tree",
|
|
@@ -210,83 +288,7 @@ export class Monitor extends React.Component {
|
|
.done(content => {
|
|
const treeData = JSON.parse(content);
|
|
this.processTree(treeData);
|
|
- const basicData = [
|
|
- {
|
|
- name: _("Server Statistics"),
|
|
- icon: <ClusterIcon />,
|
|
- id: "server-monitor",
|
|
- type: "server",
|
|
- },
|
|
- {
|
|
- name: _("Replication"),
|
|
- icon: <TopologyIcon />,
|
|
- id: "replication-monitor",
|
|
- type: "replication",
|
|
- defaultExpanded: true,
|
|
- children: [
|
|
- {
|
|
- name: _("Synchronization Report"),
|
|
- icon: <MonitoringIcon />,
|
|
- id: "sync-report",
|
|
- item: "sync-report",
|
|
- type: "repl-mon",
|
|
- },
|
|
- {
|
|
- name: _("Log Analysis"),
|
|
- icon: <MonitoringIcon />,
|
|
- id: "log-analysis",
|
|
- item: "log-analysis",
|
|
- type: "repl-mon",
|
|
- }
|
|
- ],
|
|
- },
|
|
- {
|
|
- name: _("Database"),
|
|
- icon: <DatabaseIcon />,
|
|
- id: "database-monitor",
|
|
- type: "database",
|
|
- children: [],
|
|
- defaultExpanded: true,
|
|
- },
|
|
- {
|
|
- name: _("Logging"),
|
|
- icon: <CatalogIcon />,
|
|
- id: "log-monitor",
|
|
- defaultExpanded: true,
|
|
- children: [
|
|
- {
|
|
- name: _("Access Log"),
|
|
- icon: <BookIcon size="sm" />,
|
|
- id: "access-log-monitor",
|
|
- type: "log",
|
|
- },
|
|
- {
|
|
- name: _("Audit Log"),
|
|
- icon: <BookIcon size="sm" />,
|
|
- id: "audit-log-monitor",
|
|
- type: "log",
|
|
- },
|
|
- {
|
|
- name: _("Audit Failure Log"),
|
|
- icon: <BookIcon size="sm" />,
|
|
- id: "auditfail-log-monitor",
|
|
- type: "log",
|
|
- },
|
|
- {
|
|
- name: _("Errors Log"),
|
|
- icon: <BookIcon size="sm" />,
|
|
- id: "error-log-monitor",
|
|
- type: "log",
|
|
- },
|
|
- {
|
|
- name: _("Security Log"),
|
|
- icon: <BookIcon size="sm" />,
|
|
- id: "security-log-monitor",
|
|
- type: "log",
|
|
- },
|
|
- ]
|
|
- },
|
|
- ];
|
|
+
|
|
let current_node = this.state.node_name;
|
|
let type = this.state.node_type;
|
|
if (fullReset) {
|
|
@@ -296,6 +298,22 @@ export class Monitor extends React.Component {
|
|
basicData[2].children = treeData; // database node
|
|
this.processReplSuffixes(basicData[1].children);
|
|
|
|
+ this.setState(() => ({
|
|
+ nodes: basicData,
|
|
+ node_name: current_node,
|
|
+ node_type: type,
|
|
+ }), this.update_tree_nodes);
|
|
+ })
|
|
+ .fail(err => {
|
|
+ // Handle backend get-tree failure gracefully
|
|
+ let current_node = this.state.node_name;
|
|
+ let type = this.state.node_type;
|
|
+ if (fullReset) {
|
|
+ current_node = "server-monitor";
|
|
+ type = "server";
|
|
+ }
|
|
+ this.processReplSuffixes(basicData[1].children);
|
|
+
|
|
this.setState(() => ({
|
|
nodes: basicData,
|
|
node_name: current_node,
|
|
diff --git a/src/cockpit/389-console/src/replication.jsx b/src/cockpit/389-console/src/replication.jsx
|
|
index fa492fd2a..aa535bfc7 100644
|
|
--- a/src/cockpit/389-console/src/replication.jsx
|
|
+++ b/src/cockpit/389-console/src/replication.jsx
|
|
@@ -177,6 +177,16 @@ export class Replication extends React.Component {
|
|
loaded: false
|
|
});
|
|
|
|
+ const basicData = [
|
|
+ {
|
|
+ name: _("Suffixes"),
|
|
+ icon: <TopologyIcon />,
|
|
+ id: "repl-suffixes",
|
|
+ children: [],
|
|
+ defaultExpanded: true
|
|
+ }
|
|
+ ];
|
|
+
|
|
const cmd = [
|
|
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
|
|
"backend", "get-tree",
|
|
@@ -199,15 +209,7 @@ export class Replication extends React.Component {
|
|
}
|
|
}
|
|
}
|
|
- const basicData = [
|
|
- {
|
|
- name: _("Suffixes"),
|
|
- icon: <TopologyIcon />,
|
|
- id: "repl-suffixes",
|
|
- children: [],
|
|
- defaultExpanded: true
|
|
- }
|
|
- ];
|
|
+
|
|
let current_node = this.state.node_name;
|
|
let current_type = this.state.node_type;
|
|
let replicated = this.state.node_replicated;
|
|
@@ -258,6 +260,19 @@ export class Replication extends React.Component {
|
|
}
|
|
|
|
basicData[0].children = treeData;
|
|
+ this.setState({
|
|
+ nodes: basicData,
|
|
+ node_name: current_node,
|
|
+ node_type: current_type,
|
|
+ node_replicated: replicated,
|
|
+ }, () => { this.update_tree_nodes() });
|
|
+ })
|
|
+ .fail(err => {
|
|
+ // Handle backend get-tree failure gracefully
|
|
+ let current_node = this.state.node_name;
|
|
+ let current_type = this.state.node_type;
|
|
+ let replicated = this.state.node_replicated;
|
|
+
|
|
this.setState({
|
|
nodes: basicData,
|
|
node_name: current_node,
|
|
@@ -905,18 +920,18 @@ export class Replication extends React.Component {
|
|
disableTree: false
|
|
});
|
|
});
|
|
- })
|
|
- .fail(err => {
|
|
- const errMsg = JSON.parse(err);
|
|
- this.props.addNotification(
|
|
- "error",
|
|
- cockpit.format(_("Error loading replication agreements configuration - $0"), errMsg.desc)
|
|
- );
|
|
- this.setState({
|
|
- suffixLoading: false,
|
|
- disableTree: false
|
|
+ })
|
|
+ .fail(err => {
|
|
+ const errMsg = JSON.parse(err);
|
|
+ this.props.addNotification(
|
|
+ "error",
|
|
+ cockpit.format(_("Error loading replication agreements configuration - $0"), errMsg.desc)
|
|
+ );
|
|
+ this.setState({
|
|
+ suffixLoading: false,
|
|
+ disableTree: false
|
|
+ });
|
|
});
|
|
- });
|
|
})
|
|
.fail(err => {
|
|
// changelog failure
|
|
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
|
index 1319fa0cd..5bff61c58 100644
|
|
--- a/src/lib389/lib389/backend.py
|
|
+++ b/src/lib389/lib389/backend.py
|
|
@@ -694,24 +694,32 @@ class Backend(DSLdapObject):
|
|
parent_suffix = properties.pop('parent', False)
|
|
|
|
# Okay, now try to make the backend.
|
|
- super(Backend, self).create(dn, properties, basedn)
|
|
+ backend_obj = super(Backend, self).create(dn, properties, basedn)
|
|
|
|
# We check if the mapping tree exists in create, so do this *after*
|
|
if create_mapping_tree is True:
|
|
- properties = {
|
|
+ mapping_tree_properties = {
|
|
'cn': self._nprops_stash['nsslapd-suffix'],
|
|
'nsslapd-state': 'backend',
|
|
'nsslapd-backend': self._nprops_stash['cn'],
|
|
}
|
|
if parent_suffix:
|
|
# This is a subsuffix, set the parent suffix
|
|
- properties['nsslapd-parent-suffix'] = parent_suffix
|
|
- self._mts.create(properties=properties)
|
|
+ mapping_tree_properties['nsslapd-parent-suffix'] = parent_suffix
|
|
+
|
|
+ try:
|
|
+ self._mts.create(properties=mapping_tree_properties)
|
|
+ except Exception as e:
|
|
+ try:
|
|
+ backend_obj.delete()
|
|
+ except Exception as cleanup_error:
|
|
+ self._instance.log.error(f"Failed to cleanup backend after mapping tree creation failure: {cleanup_error}")
|
|
+ raise e
|
|
|
|
# We can't create the sample entries unless a mapping tree was installed.
|
|
if sample_entries is not False and create_mapping_tree is True:
|
|
self.create_sample_entries(sample_entries)
|
|
- return self
|
|
+ return backend_obj
|
|
|
|
def delete(self):
|
|
"""Deletes the backend, it's mapping tree and all related indices.
|
|
--
|
|
2.49.0
|
|
|