From d29b47512a39ada02fb371521994576cd9815a6c Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Mon, 19 Jun 2023 10:36:29 +0200 Subject: [PATCH] Upgrade: fix replica agreement The upgrade checks the replication agreements to ensure that some attributes are excluded from replication. The agreements are stored in entries like cn=serverToreplica,cn=replica,cn=_suffix_,cn=mapping tree,cn=config but those entries are managed by the replication topology plugin and should not be updated directly. The consequence is that the update of the attributes fails and ipa-server-update prints an error message: Error caught updating nsDS5ReplicatedAttributeList: Server is unwilling to perform: Entry and attributes are managed by topology plugin.No direct modifications allowed. Error caught updating nsDS5ReplicatedAttributeListTotal: Server is unwilling to perform: Entry and attributes are managed by topology plugin.No direct modifications allowed. The upgrade continues but the replication is not excluding passwordgraceusertime. Instead of editing the agreements, perform the modifications on the topology segments. Fixes: https://pagure.io/freeipa/issue/9385 Signed-off-by: Florence Blanc-Renaud Reviewed-By: Rob Crittenden --- .../install/plugins/fix_replica_agreements.py | 80 +++++++++---------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/ipaserver/install/plugins/fix_replica_agreements.py b/ipaserver/install/plugins/fix_replica_agreements.py index c0cdd3eb1..d963753d0 100644 --- a/ipaserver/install/plugins/fix_replica_agreements.py +++ b/ipaserver/install/plugins/fix_replica_agreements.py @@ -22,6 +22,7 @@ import logging from ipaserver.install import replication from ipalib import Registry from ipalib import Updater +from ipalib import errors logger = logging.getLogger(__name__) @@ -41,35 +42,42 @@ class update_replica_attribute_lists(Updater): def execute(self, **options): # We need an LDAPClient connection to the backend logger.debug("Start replication agreement exclude list update task") - conn = self.api.Backend.ldap2 - repl = replication.ReplicationManager(self.api.env.realm, - self.api.env.host, - None, conn=conn) - - # We need to update only IPA replica agreements, not winsync - ipa_replicas = repl.find_ipa_replication_agreements() - - logger.debug("Found %d agreement(s)", len(ipa_replicas)) - - for replica in ipa_replicas: - for desc in replica.get('description', []): - logger.debug('%s', desc) - - self._update_attr(repl, replica, - 'nsDS5ReplicatedAttributeList', - replication.EXCLUDES, template=EXCLUDE_TEMPLATE) - self._update_attr(repl, replica, - 'nsDS5ReplicatedAttributeListTotal', - replication.TOTAL_EXCLUDES, template=EXCLUDE_TEMPLATE) - self._update_attr(repl, replica, - 'nsds5ReplicaStripAttrs', replication.STRIP_ATTRS) + # Find suffixes + suffixes = self.api.Command.topologysuffix_find()['result'] + for suffix in suffixes: + suffix_name = suffix['cn'][0] + # Find segments + sgmts = self.api.Command.topologysegment_find( + suffix_name, all=True)['result'] + for segment in sgmts: + updates = {} + updates = self._update_attr( + segment, updates, + 'nsds5replicatedattributelist', + replication.EXCLUDES, template=EXCLUDE_TEMPLATE) + updates = self._update_attr( + segment, updates, + 'nsds5replicatedattributelisttotal', + replication.TOTAL_EXCLUDES, template=EXCLUDE_TEMPLATE) + updates = self._update_attr( + segment, updates, + 'nsds5replicastripattrs', replication.STRIP_ATTRS) + if updates: + try: + self.api.Command.topologysegment_mod( + suffix_name, segment['cn'][0], + **updates) + except errors.EmptyModlist: + # No update done + logger.debug("No update required for the segment %s", + segment['cn'][0]) logger.debug("Done updating agreements") return False, [] # No restart, no updates - def _update_attr(self, repl, replica, attribute, values, template='%s'): + def _update_attr(self, segment, updates, attribute, values, template='%s'): """Add or update an attribute of a replication agreement If the attribute doesn't already exist, it is added and set to @@ -77,27 +85,21 @@ class update_replica_attribute_lists(Updater): If the attribute does exist, `values` missing from it are just appended to the end, also space-separated. - :param repl: Replication manager - :param replica: Replica agreement + :param: updates: dict containing the updates + :param segment: dict containing segment information :param attribute: Attribute to add or update :param values: List of values the attribute should hold :param template: Template to use when adding attribute """ - attrlist = replica.single_value.get(attribute) + attrlist = segment.get(attribute) if attrlist is None: logger.debug("Adding %s", attribute) # Need to add it altogether - replica[attribute] = [template % " ".join(values)] - - try: - repl.conn.update_entry(replica) - logger.debug("Updated") - except Exception as e: - logger.error("Error caught updating replica: %s", str(e)) + updates[attribute] = template % " ".join(values) else: - attrlist_normalized = attrlist.lower().split() + attrlist_normalized = attrlist[0].lower().split() missing = [a for a in values if a.lower() not in attrlist_normalized] @@ -105,14 +107,8 @@ class update_replica_attribute_lists(Updater): logger.debug("%s needs updating (missing: %s)", attribute, ', '.join(missing)) - replica[attribute] = [ - '%s %s' % (attrlist, ' '.join(missing))] + updates[attribute] = '%s %s' % (attrlist[0], ' '.join(missing)) - try: - repl.conn.update_entry(replica) - logger.debug("Updated %s", attribute) - except Exception as e: - logger.error("Error caught updating %s: %s", - attribute, str(e)) else: logger.debug("%s: No update necessary", attribute) + return updates -- 2.41.0 From 93d97b59600c15e5028ee39b0e98450544165158 Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Mon, 19 Jun 2023 10:36:59 +0200 Subject: [PATCH] Integration tests: add a test to ipa-server-upgrade Add an integration test ensuring that the upgrade properly updates the attributes to be excluded from replication. Related: https://pagure.io/freeipa/issue/9385 Signed-off-by: Florence Blanc-Renaud Reviewed-By: Rob Crittenden --- .../test_simple_replication.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ipatests/test_integration/test_simple_replication.py b/ipatests/test_integration/test_simple_replication.py index 17092a499..d1e65ef7c 100644 --- a/ipatests/test_integration/test_simple_replication.py +++ b/ipatests/test_integration/test_simple_replication.py @@ -23,8 +23,10 @@ import pytest from ipaplatform.paths import paths from ipapython.dn import DN +from ipaserver.install.replication import EXCLUDES from ipatests.pytest_ipa.integration import tasks from ipatests.test_integration.base import IntegrationTest +from ipatests.test_integration.test_topology import find_segment def check_replication(source_host, dest_host, login): @@ -104,6 +106,34 @@ class TestSimpleReplication(IntegrationTest): [paths.IPA_CUSTODIA_CHECK, self.master.hostname] ) + def test_fix_agreements(self): + """Test that upgrade fixes the list of attributes excluded from repl + + Test for ticket 9385 + """ + # Prepare the server by removing some values from + # from the nsDS5ReplicatedAttributeList + segment = find_segment(self.master, self.replicas[0], "domain") + self.master.run_command([ + "ipa", "topologysegment-mod", "domain", segment, + "--replattrs", + "(objectclass=*) $ EXCLUDE memberof idnssoaserial entryusn"]) + # Run the upgrade + result = self.master.run_command(["ipa-server-upgrade"]) + # Ensure that the upgrade updated the attribute without error + errmsg = "Error caught updating nsDS5ReplicatedAttributeList" + assert errmsg not in result.stdout_text + # Check the updated value + suffix = DN(self.master.domain.basedn) + dn = DN(('cn', str(suffix)), ('cn', 'mapping tree'), ('cn', 'config')) + result = tasks.ldapsearch_dm(self.master, str(dn), + ["nsDS5ReplicatedAttributeList"]) + output = result.stdout_text.lower() + + template = 'nsDS5ReplicatedAttributeList: (objectclass=*) $ EXCLUDE %s' + expected_value = template % " ".join(EXCLUDES) + assert expected_value.lower() in output + def test_replica_removal(self): """Test replica removal""" result = self.master.run_command(['ipa-replica-manage', 'list']) -- 2.41.0