2737e7
From f86e77a8e6fd9e57472e6d1b63246254143f706d Mon Sep 17 00:00:00 2001
2737e7
From: Christian Heimes <cheimes@redhat.com>
2737e7
Date: Tue, 3 Jul 2018 19:40:05 +0200
2737e7
Subject: [PATCH] Tune DS replication settings
2737e7
2737e7
Tune 389-DS replication settings to improve performance and avoid
2737e7
timeouts. During installation of a replica, the value of
2737e7
nsDS5ReplicaBindDnGroupCheckInterval is reduced to 2 seconds. At the end
2737e7
of the installation, the value is increased sensible production
2737e7
settings. This avoids long delays during replication.
2737e7
2737e7
See: https://pagure.io/freeipa/issue/7617
2737e7
Signed-off-by: Christian Heimes <cheimes@redhat.com>
2737e7
Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
2737e7
---
2737e7
 ipaserver/install/cainstance.py               |  14 ++-
2737e7
 ipaserver/install/dsinstance.py               |  48 +++++---
2737e7
 ipaserver/install/replication.py              |  92 +++++++++++----
2737e7
 ipaserver/install/server/replicainstall.py    |   2 +
2737e7
 ipaserver/install/server/upgrade.py           |  12 +-
2737e7
 ipatests/test_integration/test_external_ca.py | 109 ++++++------------
2737e7
 6 files changed, 157 insertions(+), 120 deletions(-)
2737e7
2737e7
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
2737e7
index eefc30b6e01dcf744703b8607cbe169fbec5d859..e72e706ffd2d9163a283f6bb09815e5aa60c889c 100644
2737e7
--- a/ipaserver/install/cainstance.py
2737e7
+++ b/ipaserver/install/cainstance.py
2737e7
@@ -388,8 +388,13 @@ class CAInstance(DogtagInstance):
2737e7
             self.step("set up CRL publishing", self.__enable_crl_publish)
2737e7
             self.step("enable PKIX certificate path discovery and validation", self.enable_pkix)
2737e7
             if promote:
2737e7
-                self.step("destroying installation admin user", self.teardown_admin)
2737e7
-            self.step("starting certificate server instance", self.start_instance)
2737e7
+                self.step("destroying installation admin user",
2737e7
+                          self.teardown_admin)
2737e7
+            self.step("starting certificate server instance",
2737e7
+                      self.start_instance)
2737e7
+            if promote:
2737e7
+                self.step("Finalize replication settings",
2737e7
+                          self.finalize_replica_config)
2737e7
         # Step 1 of external is getting a CSR so we don't need to do these
2737e7
         # steps until we get a cert back from the external CA.
2737e7
         if self.external != 1:
2737e7
@@ -1196,13 +1201,16 @@ class CAInstance(DogtagInstance):
2737e7
         api.Backend.ldap2.add_entry(entry)
2737e7
 
2737e7
     def __setup_replication(self):
2737e7
-
2737e7
         repl = replication.CAReplicationManager(self.realm, self.fqdn)
2737e7
         repl.setup_cs_replication(self.master_host)
2737e7
 
2737e7
         # Activate Topology for o=ipaca segments
2737e7
         self.__update_topology()
2737e7
 
2737e7
+    def finalize_replica_config(self):
2737e7
+        repl = replication.CAReplicationManager(self.realm, self.fqdn)
2737e7
+        repl.finalize_replica_config(self.master_host)
2737e7
+
2737e7
     def __enable_instance(self):
2737e7
         basedn = ipautil.realm_to_suffix(self.realm)
2737e7
         if not self.clone:
2737e7
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
2737e7
index 923f483340a26a614001701ce6c235dd73501501..7adaabd3c1280709150329003130f70233de37f4 100644
2737e7
--- a/ipaserver/install/dsinstance.py
2737e7
+++ b/ipaserver/install/dsinstance.py
2737e7
@@ -417,6 +417,20 @@ class DsInstance(service.Service):
2737e7
 
2737e7
         self.start_creation(runtime=30)
2737e7
 
2737e7
+    def _get_replication_manager(self):
2737e7
+        # Always connect to self over ldapi
2737e7
+        ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm)
2737e7
+        conn = ipaldap.LDAPClient(ldap_uri)
2737e7
+        conn.external_bind()
2737e7
+        repl = replication.ReplicationManager(
2737e7
+            self.realm, self.fqdn, self.dm_password, conn=conn
2737e7
+        )
2737e7
+        if self.dm_password is not None and not self.promote:
2737e7
+            bind_dn = DN(('cn', 'Directory Manager'))
2737e7
+            bind_pw = self.dm_password
2737e7
+        else:
2737e7
+            bind_dn = bind_pw = None
2737e7
+        return repl, bind_dn, bind_pw
2737e7
 
2737e7
     def __setup_replica(self):
2737e7
         """
2737e7
@@ -433,26 +447,24 @@ class DsInstance(service.Service):
2737e7
             self.realm,
2737e7
             self.dm_password)
2737e7
 
2737e7
-        # Always connect to self over ldapi
2737e7
-        ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm)
2737e7
-        conn = ipaldap.LDAPClient(ldap_uri)
2737e7
-        conn.external_bind()
2737e7
-        repl = replication.ReplicationManager(self.realm,
2737e7
-                                              self.fqdn,
2737e7
-                                              self.dm_password, conn=conn)
2737e7
-
2737e7
-        if self.dm_password is not None and not self.promote:
2737e7
-            bind_dn = DN(('cn', 'Directory Manager'))
2737e7
-            bind_pw = self.dm_password
2737e7
-        else:
2737e7
-            bind_dn = bind_pw = None
2737e7
-
2737e7
-        repl.setup_promote_replication(self.master_fqdn,
2737e7
-                                       r_binddn=bind_dn,
2737e7
-                                       r_bindpw=bind_pw,
2737e7
-                                       cacert=self.ca_file)
2737e7
+        repl, bind_dn, bind_pw = self._get_replication_manager()
2737e7
+        repl.setup_promote_replication(
2737e7
+            self.master_fqdn,
2737e7
+            r_binddn=bind_dn,
2737e7
+            r_bindpw=bind_pw,
2737e7
+            cacert=self.ca_file
2737e7
+        )
2737e7
         self.run_init_memberof = repl.needs_memberof_fixup()
2737e7
 
2737e7
+    def finalize_replica_config(self):
2737e7
+        repl, bind_dn, bind_pw = self._get_replication_manager()
2737e7
+        repl.finalize_replica_config(
2737e7
+            self.master_fqdn,
2737e7
+            r_binddn=bind_dn,
2737e7
+            r_bindpw=bind_pw,
2737e7
+            cacert=self.ca_file
2737e7
+        )
2737e7
+
2737e7
     def __configure_sasl_mappings(self):
2737e7
         # we need to remove any existing SASL mappings in the directory as otherwise they
2737e7
         # they may conflict.
2737e7
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
2737e7
index e0055b792ce5b51fd411a7ea1f4316bb017984ba..3a76b70038bf6f739fe63aeac0233ccbfda2f016 100644
2737e7
--- a/ipaserver/install/replication.py
2737e7
+++ b/ipaserver/install/replication.py
2737e7
@@ -69,6 +69,20 @@ STRIP_ATTRS = ('modifiersName',
2737e7
                'internalModifiersName',
2737e7
                'internalModifyTimestamp')
2737e7
 
2737e7
+# settings for cn=replica,cn=$DB,cn=mapping tree,cn=config
2737e7
+# during replica installation
2737e7
+REPLICA_CREATION_SETTINGS = {
2737e7
+    "nsds5ReplicaReleaseTimeout": ["20"],
2737e7
+    "nsds5ReplicaBackoffMax": ["3"],
2737e7
+    "nsDS5ReplicaBindDnGroupCheckInterval": ["2"]
2737e7
+}
2737e7
+# after replica installation
2737e7
+REPLICA_FINAL_SETTINGS = {
2737e7
+    "nsds5ReplicaReleaseTimeout": ["60"],
2737e7
+    "nsds5ReplicaBackoffMax": ["300"],  # default
2737e7
+    "nsDS5ReplicaBindDnGroupCheckInterval": ["60"]
2737e7
+}
2737e7
+
2737e7
 
2737e7
 def replica_conn_check(master_host, host_name, realm, check_ca,
2737e7
                        dogtag_master_ds_port, admin_password=None,
2737e7
@@ -192,9 +206,13 @@ def wait_for_entry(connection, dn, timeout=7200, attr='', quiet=True):
2737e7
 
2737e7
 
2737e7
 class ReplicationManager(object):
2737e7
-    """Manage replication agreements between DS servers, and sync
2737e7
-    agreements with Windows servers"""
2737e7
-    def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False, conn=None):
2737e7
+    """Manage replication agreements
2737e7
+
2737e7
+    between DS servers, and sync  agreements with Windows servers
2737e7
+    """
2737e7
+
2737e7
+    def __init__(self, realm, hostname, dirman_passwd=None, port=PORT,
2737e7
+                 starttls=False, conn=None):
2737e7
         self.hostname = hostname
2737e7
         self.port = port
2737e7
         self.dirman_passwd = dirman_passwd
2737e7
@@ -471,22 +489,16 @@ class ReplicationManager(object):
2737e7
         except errors.NotFound:
2737e7
             pass
2737e7
         else:
2737e7
-            managers = {DN(m) for m in entry.get('nsDS5ReplicaBindDN', [])}
2737e7
-
2737e7
-            mods = []
2737e7
-            if replica_binddn not in managers:
2737e7
+            binddns = entry.setdefault('nsDS5ReplicaBindDN', [])
2737e7
+            if replica_binddn not in {DN(m) for m in binddns}:
2737e7
                 # Add the new replication manager
2737e7
-                mods.append(
2737e7
-                    (ldap.MOD_ADD, 'nsDS5ReplicaBindDN', replica_binddn)
2737e7
-                )
2737e7
-            if 'nsds5replicareleasetimeout' not in entry:
2737e7
-                # See https://pagure.io/freeipa/issue/7488
2737e7
-                mods.append(
2737e7
-                    (ldap.MOD_ADD, 'nsds5replicareleasetimeout', ['60'])
2737e7
-                )
2737e7
-
2737e7
-            if mods:
2737e7
-                conn.modify_s(dn, mods)
2737e7
+                binddns.append(replica_binddn)
2737e7
+            for key, value in REPLICA_CREATION_SETTINGS.items():
2737e7
+                entry[key] = value
2737e7
+            try:
2737e7
+                conn.update_entry(entry)
2737e7
+            except errors.EmptyModlist:
2737e7
+                pass
2737e7
 
2737e7
             self.set_replica_binddngroup(conn, entry)
2737e7
 
2737e7
@@ -505,9 +517,8 @@ class ReplicationManager(object):
2737e7
             nsds5flags=["1"],
2737e7
             nsds5replicabinddn=[replica_binddn],
2737e7
             nsds5replicabinddngroup=[self.repl_man_group_dn],
2737e7
-            nsds5replicabinddngroupcheckinterval=["60"],
2737e7
-            nsds5replicareleasetimeout=["60"],
2737e7
             nsds5replicalegacyconsumer=["off"],
2737e7
+            **REPLICA_CREATION_SETTINGS
2737e7
         )
2737e7
         conn.add_entry(entry)
2737e7
 
2737e7
@@ -533,6 +544,47 @@ class ReplicationManager(object):
2737e7
         except errors.DuplicateEntry:
2737e7
             return
2737e7
 
2737e7
+    def _finalize_replica_settings(self, conn):
2737e7
+        """Change replica settings to final values
2737e7
+
2737e7
+        During replica installation, some settings are configured for faster
2737e7
+        replication.
2737e7
+        """
2737e7
+        dn = self.replica_dn()
2737e7
+        entry = conn.get_entry(dn)
2737e7
+        for key, value in REPLICA_FINAL_SETTINGS.items():
2737e7
+            entry[key] = value
2737e7
+        try:
2737e7
+            conn.update_entry(entry)
2737e7
+        except errors.EmptyModlist:
2737e7
+            pass
2737e7
+
2737e7
+    def finalize_replica_config(self, r_hostname, r_binddn=None,
2737e7
+                                r_bindpw=None, cacert=paths.IPA_CA_CRT):
2737e7
+        """Apply final cn=replica settings
2737e7
+
2737e7
+        replica_config() sets several attribute to fast cache invalidation
2737e7
+        and fast reconnects to optimize replicat installation. For
2737e7
+        production, longer timeouts and less aggressive cache invalidation
2737e7
+        is sufficient. finalize_replica_config() sets the values on new
2737e7
+        replica and the master.
2737e7
+
2737e7
+        When installing multiple replicas in parallel, one replica may
2737e7
+        finalize the values while another is still installing.
2737e7
+
2737e7
+        See https://pagure.io/freeipa/issue/7617
2737e7
+        """
2737e7
+        self._finalize_replica_settings(self.conn)
2737e7
+
2737e7
+        ldap_uri = ipaldap.get_ldap_uri(r_hostname)
2737e7
+        r_conn = ipaldap.LDAPClient(ldap_uri, cacert=cacert)
2737e7
+        if r_bindpw:
2737e7
+            r_conn.simple_bind(r_binddn, r_bindpw)
2737e7
+        else:
2737e7
+            r_conn.gssapi_bind()
2737e7
+        self._finalize_replica_settings(r_conn)
2737e7
+        r_conn.close()
2737e7
+
2737e7
     def setup_chaining_backend(self, conn):
2737e7
         chaindn = DN(('cn', 'chaining database'), ('cn', 'plugins'), ('cn', 'config'))
2737e7
         benamebase = "chaindb"
2737e7
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
2737e7
index 2ecc0abe0c5918cf5aefccc1bd6f09e70503baab..27e5e9cde7788b8c9b78b27ed47f5748f34c90ff 100644
2737e7
--- a/ipaserver/install/server/replicainstall.py
2737e7
+++ b/ipaserver/install/server/replicainstall.py
2737e7
@@ -1504,6 +1504,8 @@ def install(installer):
2737e7
     # Apply any LDAP updates. Needs to be done after the replica is synced-up
2737e7
     service.print_msg("Applying LDAP updates")
2737e7
     ds.apply_updates()
2737e7
+    service.print_msg("Finalize replication settings")
2737e7
+    ds.finalize_replica_config()
2737e7
 
2737e7
     if kra_enabled:
2737e7
         kra.install(api, config, options, custodia=custodia)
2737e7
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
2737e7
index bf603acb5f931a4194320795874859f5bdc94647..793092be86da687fd21cf2c0ef3608f32fcf9f16 100644
2737e7
--- a/ipaserver/install/server/upgrade.py
2737e7
+++ b/ipaserver/install/server/upgrade.py
2737e7
@@ -50,6 +50,7 @@ from ipaserver.install import dnskeysyncinstance
2737e7
 from ipaserver.install import dogtaginstance
2737e7
 from ipaserver.install import krbinstance
2737e7
 from ipaserver.install import adtrustinstance
2737e7
+from ipaserver.install import replication
2737e7
 from ipaserver.install.upgradeinstance import IPAUpgrade
2737e7
 from ipaserver.install.ldapupdate import BadSyntax
2737e7
 
2737e7
@@ -1575,11 +1576,14 @@ def update_replica_config(db_suffix):
2737e7
     except ipalib.errors.NotFound:
2737e7
         return  # entry does not exist until a replica is installed
2737e7
 
2737e7
-    if 'nsds5replicareleasetimeout' not in entry:
2737e7
-        # See https://pagure.io/freeipa/issue/7488
2737e7
-        root_logger.info("Adding nsds5replicaReleaseTimeout=60 to %s", dn)
2737e7
-        entry['nsds5replicareleasetimeout'] = '60'
2737e7
+    for key, value in replication.REPLICA_FINAL_SETTINGS.items():
2737e7
+        entry[key] = value
2737e7
+    try:
2737e7
         api.Backend.ldap2.update_entry(entry)
2737e7
+    except ipalib.errors.EmptyModlist:
2737e7
+        pass
2737e7
+    else:
2737e7
+        root_logger.info("Updated entry %s", dn)
2737e7
 
2737e7
 
2737e7
 def upgrade_configuration():
2737e7
diff --git a/ipatests/test_integration/test_external_ca.py b/ipatests/test_integration/test_external_ca.py
2737e7
index 7fc89cc949ca6a7ccddde17aca434cf55f2918ed..b22fa05206204e3dc382dc2b02c23a73e5c1b80b 100644
2737e7
--- a/ipatests/test_integration/test_external_ca.py
2737e7
+++ b/ipatests/test_integration/test_external_ca.py
2737e7
@@ -31,86 +31,45 @@ class TestExternalCA(IntegrationTest):
2737e7
     """
2737e7
     Test of FreeIPA server installation with exernal CA
2737e7
     """
2737e7
-    def test_external_ca(self):
2737e7
-        # Step 1 of ipa-server-install
2737e7
-        self.master.run_command([
2737e7
-            'ipa-server-install', '-U',
2737e7
-            '-a', self.master.config.admin_password,
2737e7
-            '-p', self.master.config.dirman_password,
2737e7
-            '--setup-dns', '--no-forwarders',
2737e7
-            '-n', self.master.domain.name,
2737e7
-            '-r', self.master.domain.realm,
2737e7
-            '--domain-level=%i' % self.master.config.domain_level,
2737e7
-            '--external-ca'
2737e7
-        ])
2737e7
-
2737e7
-        nss_db = os.path.join(self.master.config.test_dir, 'testdb')
2737e7
-        external_cert_file = os.path.join(nss_db, 'ipa.crt')
2737e7
-        external_ca_file = os.path.join(nss_db, 'ca.crt')
2737e7
-        noisefile = os.path.join(self.master.config.test_dir, 'noise.txt')
2737e7
-        pwdfile = os.path.join(self.master.config.test_dir, 'pwdfile.txt')
2737e7
-
2737e7
-        # Create noise and password files for NSS database
2737e7
-        self.master.run_command('date | sha256sum > %s' % noisefile)
2737e7
-        self.master.run_command('echo %s > %s' %
2737e7
-                                (self.master.config.admin_password, pwdfile))
2737e7
-
2737e7
-        # Create NSS database
2737e7
-        self.master.run_command(['mkdir', nss_db])
2737e7
-        self.master.run_command([
2737e7
-            'certutil', '-N',
2737e7
-            '-d', nss_db,
2737e7
-            '-f', pwdfile
2737e7
-        ])
2737e7
-
2737e7
-        # Create external CA
2737e7
-        self.master.run_command([
2737e7
-            'certutil', '-S',
2737e7
-            '-d', nss_db,
2737e7
-            '-f', pwdfile,
2737e7
-            '-n', 'external',
2737e7
-            '-s', 'CN=External CA, O=%s' % self.master.domain.name,
2737e7
-            '-x',
2737e7
-            '-t', 'CTu,CTu,CTu',
2737e7
-            '-g', '2048',
2737e7
-            '-m', '0',
2737e7
-            '-v', '60',
2737e7
-            '-z', noisefile,
2737e7
-            '-2', '-1', '-5', '--extSKID'
2737e7
-        ], stdin_text='5\n9\nn\ny\n10\ny\n{}\nn\n5\n6\n7\n9\nn\n'
2737e7
-                      ''.format(EXTERNAL_CA_KEY_ID))
2737e7
+    num_replicas = 1
2737e7
 
2737e7
-        # Sign IPA cert request using the external CA
2737e7
-        self.master.run_command([
2737e7
-            'certutil', '-C',
2737e7
-            '-d', nss_db,
2737e7
-            '-f', pwdfile,
2737e7
-            '-c', 'external',
2737e7
-            '-m', '1',
2737e7
-            '-v', '60',
2737e7
-            '-2', '-1', '-3', '--extSKID',
2737e7
-            '-i', '/root/ipa.csr',
2737e7
-            '-o', external_cert_file,
2737e7
-            '-a'
2737e7
-        ], stdin_text='0\n1\n5\n9\ny\ny\n\ny\ny\n{}\n-1\n\nn\n{}\nn\n'
2737e7
-                      ''.format(EXTERNAL_CA_KEY_ID, IPA_CA_KEY_ID))
2737e7
+    @tasks.collect_logs
2737e7
+    def test_external_ca(self):
2737e7
+        # Step 1 of ipa-server-install.
2737e7
+        result = install_server_external_ca_step1(self.master)
2737e7
+        assert result.returncode == 0
2737e7
 
2737e7
-        # Export external CA file
2737e7
-        self.master.run_command(
2737e7
-            'certutil -L -d %s -n "external" -a > %s' %
2737e7
-            (nss_db, external_ca_file)
2737e7
-        )
2737e7
+        # Sign CA, transport it to the host and get ipa a root ca paths.
2737e7
+        root_ca_fname, ipa_ca_fname = tasks.sign_ca_and_transport(
2737e7
+            self.master, paths.ROOT_IPA_CSR, ROOT_CA, IPA_CA)
2737e7
 
2737e7
-        # Step 2 of ipa-server-install
2737e7
-        self.master.run_command([
2737e7
-            'ipa-server-install',
2737e7
-            '-a', self.master.config.admin_password,
2737e7
-            '-p', self.master.config.dirman_password,
2737e7
-            '--external-cert-file', external_cert_file,
2737e7
-            '--external-cert-file', external_ca_file
2737e7
-        ])
2737e7
+        # Step 2 of ipa-server-install.
2737e7
+        result = install_server_external_ca_step2(
2737e7
+            self.master, ipa_ca_fname, root_ca_fname)
2737e7
+        assert result.returncode == 0
2737e7
 
2737e7
         # Make sure IPA server is working properly
2737e7
         tasks.kinit_admin(self.master)
2737e7
         result = self.master.run_command(['ipa', 'user-show', 'admin'])
2737e7
         assert 'User login: admin' in result.stdout_text
2737e7
+
2737e7
+        # check that we can also install replica
2737e7
+        tasks.install_replica(self.master, self.replicas[0])
2737e7
+
2737e7
+        # check that nsds5ReplicaReleaseTimeout option was set
2737e7
+        result = self.master.run_command([
2737e7
+            'ldapsearch',
2737e7
+            '-x',
2737e7
+            '-D',
2737e7
+            'cn=directory manager',
2737e7
+            '-w', self.master.config.dirman_password,
2737e7
+            '-b', 'cn=mapping tree,cn=config',
2737e7
+            '(cn=replica)',
2737e7
+            '-LLL',
2737e7
+            '-o',
2737e7
+            'ldif-wrap=no'])
2737e7
+        # case insensitive match
2737e7
+        text = result.stdout_text.lower()
2737e7
+        # see ipaserver.install.replication.REPLICA_FINAL_SETTINGS
2737e7
+        assert 'nsds5ReplicaReleaseTimeout: 60'.lower() in text
2737e7
+        assert 'nsDS5ReplicaBindDnGroupCheckInterval: 60'.lower() in text
2737e7
-- 
2737e7
2.17.1
2737e7