From f86e77a8e6fd9e57472e6d1b63246254143f706d 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 --- ipaserver/install/cainstance.py | 14 ++- 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 | 109 ++++++------------ 6 files changed, 157 insertions(+), 120 deletions(-) diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index eefc30b6e01dcf744703b8607cbe169fbec5d859..e72e706ffd2d9163a283f6bb09815e5aa60c889c 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -388,8 +388,13 @@ class CAInstance(DogtagInstance): self.step("set up CRL publishing", self.__enable_crl_publish) self.step("enable PKIX certificate path discovery and validation", self.enable_pkix) if promote: - self.step("destroying installation admin user", self.teardown_admin) - self.step("starting certificate server instance", self.start_instance) + self.step("destroying installation admin user", + 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: @@ -1196,13 +1201,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 923f483340a26a614001701ce6c235dd73501501..7adaabd3c1280709150329003130f70233de37f4 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -417,6 +417,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): """ @@ -433,26 +447,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 e0055b792ce5b51fd411a7ea1f4316bb017984ba..3a76b70038bf6f739fe63aeac0233ccbfda2f016 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -69,6 +69,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, @@ -192,9 +206,13 @@ def wait_for_entry(connection, dn, timeout=7200, attr='', quiet=True): 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 @@ -471,22 +489,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) @@ -505,9 +517,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) @@ -533,6 +544,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 2ecc0abe0c5918cf5aefccc1bd6f09e70503baab..27e5e9cde7788b8c9b78b27ed47f5748f34c90ff 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -1504,6 +1504,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 bf603acb5f931a4194320795874859f5bdc94647..793092be86da687fd21cf2c0ef3608f32fcf9f16 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -50,6 +50,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 @@ -1575,11 +1576,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 - root_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: + root_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 7fc89cc949ca6a7ccddde17aca434cf55f2918ed..b22fa05206204e3dc382dc2b02c23a73e5c1b80b 100644 --- a/ipatests/test_integration/test_external_ca.py +++ b/ipatests/test_integration/test_external_ca.py @@ -31,86 +31,45 @@ class TestExternalCA(IntegrationTest): """ Test of FreeIPA server installation with exernal CA """ - def test_external_ca(self): - # Step 1 of ipa-server-install - self.master.run_command([ - 'ipa-server-install', '-U', - '-a', self.master.config.admin_password, - '-p', self.master.config.dirman_password, - '--setup-dns', '--no-forwarders', - '-n', self.master.domain.name, - '-r', self.master.domain.realm, - '--domain-level=%i' % self.master.config.domain_level, - '--external-ca' - ]) - - nss_db = os.path.join(self.master.config.test_dir, 'testdb') - external_cert_file = os.path.join(nss_db, 'ipa.crt') - external_ca_file = os.path.join(nss_db, 'ca.crt') - noisefile = os.path.join(self.master.config.test_dir, 'noise.txt') - pwdfile = os.path.join(self.master.config.test_dir, 'pwdfile.txt') - - # Create noise and password files for NSS database - self.master.run_command('date | sha256sum > %s' % noisefile) - self.master.run_command('echo %s > %s' % - (self.master.config.admin_password, pwdfile)) - - # Create NSS database - self.master.run_command(['mkdir', nss_db]) - self.master.run_command([ - 'certutil', '-N', - '-d', nss_db, - '-f', pwdfile - ]) - - # Create external CA - self.master.run_command([ - 'certutil', '-S', - '-d', nss_db, - '-f', pwdfile, - '-n', 'external', - '-s', 'CN=External CA, O=%s' % self.master.domain.name, - '-x', - '-t', 'CTu,CTu,CTu', - '-g', '2048', - '-m', '0', - '-v', '60', - '-z', noisefile, - '-2', '-1', '-5', '--extSKID' - ], stdin_text='5\n9\nn\ny\n10\ny\n{}\nn\n5\n6\n7\n9\nn\n' - ''.format(EXTERNAL_CA_KEY_ID)) + num_replicas = 1 - # Sign IPA cert request using the external CA - self.master.run_command([ - 'certutil', '-C', - '-d', nss_db, - '-f', pwdfile, - '-c', 'external', - '-m', '1', - '-v', '60', - '-2', '-1', '-3', '--extSKID', - '-i', '/root/ipa.csr', - '-o', external_cert_file, - '-a' - ], stdin_text='0\n1\n5\n9\ny\ny\n\ny\ny\n{}\n-1\n\nn\n{}\nn\n' - ''.format(EXTERNAL_CA_KEY_ID, IPA_CA_KEY_ID)) + @tasks.collect_logs + def test_external_ca(self): + # Step 1 of ipa-server-install. + result = install_server_external_ca_step1(self.master) + assert result.returncode == 0 - # Export external CA file - self.master.run_command( - 'certutil -L -d %s -n "external" -a > %s' % - (nss_db, external_ca_file) - ) + # Sign CA, transport it to the host and get ipa a root ca paths. + root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport( + self.master, paths.ROOT_IPA_CSR, ROOT_CA, IPA_CA) - # Step 2 of ipa-server-install - self.master.run_command([ - 'ipa-server-install', - '-a', self.master.config.admin_password, - '-p', self.master.config.dirman_password, - '--external-cert-file', external_cert_file, - '--external-cert-file', external_ca_file - ]) + # Step 2 of ipa-server-install. + result = install_server_external_ca_step2( + self.master, ipa_ca_fname, root_ca_fname) + assert result.returncode == 0 # Make sure IPA server is working properly tasks.kinit_admin(self.master) 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 -- 2.17.1