From 4d99ab7ce65064aacf2a7429d8ebc956c5b43b34 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 3 Jul 2018 19:40:05 +0200 Subject: [PATCH] Tune DS replication settings Tune 389-DS replication settings to improve performance and avoid timeouts. During installation of a replica, the value of nsDS5ReplicaBindDnGroupCheckInterval is reduced to 2 seconds. At the end of the installation, the value is increased sensible production settings. This avoids long delays during replication. See: https://pagure.io/freeipa/issue/7617 Signed-off-by: Christian Heimes Reviewed-By: Tibor Dudlak Reviewed-By: Tibor Dudlak --- ipaserver/install/cainstance.py | 8 +- ipaserver/install/dsinstance.py | 48 ++++++---- ipaserver/install/replication.py | 92 +++++++++++++++---- ipaserver/install/server/replicainstall.py | 2 + ipaserver/install/server/upgrade.py | 12 ++- ipatests/test_integration/test_external_ca.py | 21 +++++ 6 files changed, 140 insertions(+), 43 deletions(-) diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 51fdbe9c61e06ab9d72d78aee8786f9bceca137b..8193f3da854b3a20d175de523fbc453f5c5104d8 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -410,6 +410,9 @@ class CAInstance(DogtagInstance): self.teardown_admin) self.step("starting certificate server instance", self.start_instance) + if promote: + self.step("Finalize replication settings", + self.finalize_replica_config) # Step 1 of external is getting a CSR so we don't need to do these # steps until we get a cert back from the external CA. if self.external != 1: @@ -1245,13 +1248,16 @@ class CAInstance(DogtagInstance): api.Backend.ldap2.add_entry(entry) def __setup_replication(self): - repl = replication.CAReplicationManager(self.realm, self.fqdn) repl.setup_cs_replication(self.master_host) # Activate Topology for o=ipaca segments self.__update_topology() + def finalize_replica_config(self): + repl = replication.CAReplicationManager(self.realm, self.fqdn) + repl.finalize_replica_config(self.master_host) + def __enable_instance(self): basedn = ipautil.realm_to_suffix(self.realm) if not self.clone: diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 06c1273dc21a88f99a0f543cfd12bb6563c7e214..eefbde3356e1077d490d09c4ea47d961ce3ce8e6 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -418,6 +418,20 @@ class DsInstance(service.Service): self.start_creation(runtime=30) + def _get_replication_manager(self): + # Always connect to self over ldapi + ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm) + conn = ipaldap.LDAPClient(ldap_uri) + conn.external_bind() + repl = replication.ReplicationManager( + self.realm, self.fqdn, self.dm_password, conn=conn + ) + if self.dm_password is not None and not self.promote: + bind_dn = DN(('cn', 'Directory Manager')) + bind_pw = self.dm_password + else: + bind_dn = bind_pw = None + return repl, bind_dn, bind_pw def __setup_replica(self): """ @@ -434,26 +448,24 @@ class DsInstance(service.Service): self.realm, self.dm_password) - # Always connect to self over ldapi - ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm) - conn = ipaldap.LDAPClient(ldap_uri) - conn.external_bind() - repl = replication.ReplicationManager(self.realm, - self.fqdn, - self.dm_password, conn=conn) - - if self.dm_password is not None and not self.promote: - bind_dn = DN(('cn', 'Directory Manager')) - bind_pw = self.dm_password - else: - bind_dn = bind_pw = None - - repl.setup_promote_replication(self.master_fqdn, - r_binddn=bind_dn, - r_bindpw=bind_pw, - cacert=self.ca_file) + repl, bind_dn, bind_pw = self._get_replication_manager() + repl.setup_promote_replication( + self.master_fqdn, + r_binddn=bind_dn, + r_bindpw=bind_pw, + cacert=self.ca_file + ) self.run_init_memberof = repl.needs_memberof_fixup() + def finalize_replica_config(self): + repl, bind_dn, bind_pw = self._get_replication_manager() + repl.finalize_replica_config( + self.master_fqdn, + r_binddn=bind_dn, + r_bindpw=bind_pw, + cacert=self.ca_file + ) + def __configure_sasl_mappings(self): # we need to remove any existing SASL mappings in the directory as otherwise they # they may conflict. diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py index 5ce8fa689c1a6fd3b9d4cbddbd5454d36334b729..d0f92c76c108d237104a81c72567250583ac4ff1 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -75,6 +75,20 @@ STRIP_ATTRS = ('modifiersName', 'internalModifiersName', 'internalModifyTimestamp') +# settings for cn=replica,cn=$DB,cn=mapping tree,cn=config +# during replica installation +REPLICA_CREATION_SETTINGS = { + "nsds5ReplicaReleaseTimeout": ["20"], + "nsds5ReplicaBackoffMax": ["3"], + "nsDS5ReplicaBindDnGroupCheckInterval": ["2"] +} +# after replica installation +REPLICA_FINAL_SETTINGS = { + "nsds5ReplicaReleaseTimeout": ["60"], + "nsds5ReplicaBackoffMax": ["300"], # default + "nsDS5ReplicaBindDnGroupCheckInterval": ["60"] +} + def replica_conn_check(master_host, host_name, realm, check_ca, dogtag_master_ds_port, admin_password=None, @@ -201,9 +215,13 @@ def wait_for_entry(connection, dn, timeout, attr=None, attrvalue='*', class ReplicationManager(object): - """Manage replication agreements between DS servers, and sync - agreements with Windows servers""" - def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False, conn=None): + """Manage replication agreements + + between DS servers, and sync agreements with Windows servers + """ + + def __init__(self, realm, hostname, dirman_passwd=None, port=PORT, + starttls=False, conn=None): self.hostname = hostname self.port = port self.dirman_passwd = dirman_passwd @@ -481,22 +499,16 @@ class ReplicationManager(object): except errors.NotFound: pass else: - managers = {DN(m) for m in entry.get('nsDS5ReplicaBindDN', [])} - - mods = [] - if replica_binddn not in managers: + binddns = entry.setdefault('nsDS5ReplicaBindDN', []) + if replica_binddn not in {DN(m) for m in binddns}: # Add the new replication manager - mods.append( - (ldap.MOD_ADD, 'nsDS5ReplicaBindDN', replica_binddn) - ) - if 'nsds5replicareleasetimeout' not in entry: - # See https://pagure.io/freeipa/issue/7488 - mods.append( - (ldap.MOD_ADD, 'nsds5replicareleasetimeout', ['60']) - ) - - if mods: - conn.modify_s(dn, mods) + binddns.append(replica_binddn) + for key, value in REPLICA_CREATION_SETTINGS.items(): + entry[key] = value + try: + conn.update_entry(entry) + except errors.EmptyModlist: + pass self.set_replica_binddngroup(conn, entry) @@ -515,9 +527,8 @@ class ReplicationManager(object): nsds5flags=["1"], nsds5replicabinddn=[replica_binddn], nsds5replicabinddngroup=[self.repl_man_group_dn], - nsds5replicabinddngroupcheckinterval=["60"], - nsds5replicareleasetimeout=["60"], nsds5replicalegacyconsumer=["off"], + **REPLICA_CREATION_SETTINGS ) conn.add_entry(entry) @@ -543,6 +554,47 @@ class ReplicationManager(object): except errors.DuplicateEntry: return + def _finalize_replica_settings(self, conn): + """Change replica settings to final values + + During replica installation, some settings are configured for faster + replication. + """ + dn = self.replica_dn() + entry = conn.get_entry(dn) + for key, value in REPLICA_FINAL_SETTINGS.items(): + entry[key] = value + try: + conn.update_entry(entry) + except errors.EmptyModlist: + pass + + def finalize_replica_config(self, r_hostname, r_binddn=None, + r_bindpw=None, cacert=paths.IPA_CA_CRT): + """Apply final cn=replica settings + + replica_config() sets several attribute to fast cache invalidation + and fast reconnects to optimize replicat installation. For + production, longer timeouts and less aggressive cache invalidation + is sufficient. finalize_replica_config() sets the values on new + replica and the master. + + When installing multiple replicas in parallel, one replica may + finalize the values while another is still installing. + + See https://pagure.io/freeipa/issue/7617 + """ + self._finalize_replica_settings(self.conn) + + ldap_uri = ipaldap.get_ldap_uri(r_hostname) + r_conn = ipaldap.LDAPClient(ldap_uri, cacert=cacert) + if r_bindpw: + r_conn.simple_bind(r_binddn, r_bindpw) + else: + r_conn.gssapi_bind() + self._finalize_replica_settings(r_conn) + r_conn.close() + def setup_chaining_backend(self, conn): chaindn = DN(('cn', 'chaining database'), ('cn', 'plugins'), ('cn', 'config')) benamebase = "chaindb" diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index 542e1d4d145f266d6fd9ad8e0eaffcb12e8f6bc6..8826da232a90380084b0e4f3dca783125a5500da 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -1506,6 +1506,8 @@ def install(installer): # Apply any LDAP updates. Needs to be done after the replica is synced-up service.print_msg("Applying LDAP updates") ds.apply_updates() + service.print_msg("Finalize replication settings") + ds.finalize_replica_config() if kra_enabled: kra.install(api, config, options, custodia=custodia) diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index ee3cedd78a2f45f33665bf562e9426cf68325544..4e5096e598cd10e3bd98f91946b4d26377d0de6e 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -45,6 +45,7 @@ from ipaserver.install import dnskeysyncinstance from ipaserver.install import dogtaginstance from ipaserver.install import krbinstance from ipaserver.install import adtrustinstance +from ipaserver.install import replication from ipaserver.install.upgradeinstance import IPAUpgrade from ipaserver.install.ldapupdate import BadSyntax @@ -1645,11 +1646,14 @@ def update_replica_config(db_suffix): except ipalib.errors.NotFound: return # entry does not exist until a replica is installed - if 'nsds5replicareleasetimeout' not in entry: - # See https://pagure.io/freeipa/issue/7488 - logger.info("Adding nsds5replicaReleaseTimeout=60 to %s", dn) - entry['nsds5replicareleasetimeout'] = '60' + for key, value in replication.REPLICA_FINAL_SETTINGS.items(): + entry[key] = value + try: api.Backend.ldap2.update_entry(entry) + except ipalib.errors.EmptyModlist: + pass + else: + logger.info("Updated entry %s", dn) def upgrade_configuration(): diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py index d21e6d543f51dcee1e548e322cbe01fbe0c13d48..2fc2478ae8bb49d95f7ec60270fb5bb6e582d6f5 100644 --- a/ipatests/test_integration/test_external_ca.py +++ b/ipatests/test_integration/test_external_ca.py @@ -130,6 +130,27 @@ class TestExternalCA(IntegrationTest): result = self.master.run_command(['ipa', 'user-show', 'admin']) assert 'User login: admin' in result.stdout_text + # check that we can also install replica + tasks.install_replica(self.master, self.replicas[0]) + + # check that nsds5ReplicaReleaseTimeout option was set + result = self.master.run_command([ + 'ldapsearch', + '-x', + '-D', + 'cn=directory manager', + '-w', self.master.config.dirman_password, + '-b', 'cn=mapping tree,cn=config', + '(cn=replica)', + '-LLL', + '-o', + 'ldif-wrap=no']) + # case insensitive match + text = result.stdout_text.lower() + # see ipaserver.install.replication.REPLICA_FINAL_SETTINGS + assert 'nsds5ReplicaReleaseTimeout: 60'.lower() in text + assert 'nsDS5ReplicaBindDnGroupCheckInterval: 60'.lower() in text + def test_client_installation_with_otp(self): # Test for issue 7526: client installation fails with one-time # password when the master is installed with an externally signed -- 2.17.1