On 12.10.2015 12:30, Martin Babinsky wrote:
On 10/08/2015 05:58 PM, Martin Basti wrote:
The attached patches fix following tickets:
     https://fedorahosted.org/freeipa/ticket/4949
     https://fedorahosted.org/freeipa/ticket/4048
     https://fedorahosted.org/freeipa/ticket/1930

With these patches, an administrator can specify LDIF file that contains
modifications to be applied to dse.ldif right after creation of DS
instance.


Hi,

Functionally the paches work as expected. However I have following nitpicks:

in patch 318:

1.) there is a typo in ModifyLDIF class docstring:

+    Operations keep the order in whihc were specified per DN.

in patch 320:

1.) you should use 'os.path.join' to construct FS paths:


-        dse_filename = '%s/%s' % (
+        dse_filename = os.path.join(
             paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % self.serverid,
-            'dse.ldif',
+            'dse.ldif'
         )

2.) IIUC the 'config_ldif_file' knob in BaseServer holds the path to LDIF containing the mod operations to dse.ldif. However, the knob name sounds like the option accepts the path of dse.ldif itself. I propose to rename the knob to something more in-line with the supposed function, like 'dse_mods_file'.


Updated patches + CI test attached
From d2b21a949c9893895bbbf2d221c9bac52a02d47d Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Mon, 5 Oct 2015 14:37:05 +0200
Subject: [PATCH 1/3] Make offline LDIF modify more robust

* move code to installutils
* add replace_value method
* use lists instead of single values for add_value, remove_value methods

https://fedorahosted.org/freeipa/ticket/4949

Also fixes:
https://fedorahosted.org/freeipa/ticket/4048
https://fedorahosted.org/freeipa/ticket/1930
---
 ipaserver/install/installutils.py    |  98 ++++++++++++++++++++++++++++++
 ipaserver/install/upgradeinstance.py | 112 ++++-------------------------------
 2 files changed, 109 insertions(+), 101 deletions(-)

diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 58be9f23384f0c1734d1ba7a14182f60817a32a8..9b1800243a1bda80bab437d5769c823226e6ee7d 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -22,6 +22,7 @@ from __future__ import print_function
 
 import socket
 import getpass
+import ldif
 import os
 import re
 import fileinput
@@ -1107,3 +1108,100 @@ def enable_and_start_oddjobd(sstore):
         oddjobd.start()
     except Exception as e:
         root_logger.critical("Unable to start oddjobd: {0}".format(str(e)))
+
+
+class ModifyLDIF(ldif.LDIFParser):
+    """
+    Allows to modify LDIF file.
+
+    Operations keep the order in which were specified per DN.
+    Warning: only modifications of existing DNs are supported
+    """
+    def __init__(self, input_file, output_file):
+        """
+        :param input_file: an LDIF
+        :param output_file: an LDIF file
+        """
+        ldif.LDIFParser.__init__(self, input_file)
+        self.writer = ldif.LDIFWriter(output_file)
+        self.dn_updated = set()
+
+        self.modifications = {}  # keep modify operations in original order
+
+    def add_value(self, dn, attr, values):
+        """
+        Add value to LDIF.
+        :param dn: DN of entry (must exists)
+        :param attr: attribute name
+        :param value: value to be added
+        """
+        assert isinstance(values, list)
+        self.modifications.setdefault(dn, []).append(
+            dict(
+                op="add",
+                attr=attr,
+                values=values,
+            )
+        )
+
+    def remove_value(self, dn, attr, values=None):
+        """
+        Remove value from LDIF.
+        :param dn: DN of entry
+        :param attr: attribute name
+        :param value: value to be removed, if value is None, attribute will
+        be removed
+        """
+        assert values is None or isinstance(values, list)
+        self.modifications.setdefault(dn, []).append(
+            dict(
+                op="del",
+                attr=attr,
+                values=values,
+            )
+        )
+
+    def replace_value(self, dn, attr, values):
+        """
+        Replace values in LDIF with new value.
+        :param dn: DN of entry
+        :param attr: attribute name
+        :param value: new value for atribute
+        """
+        assert isinstance(values, list)
+        self.remove_value(dn, attr)
+        self.add_value(dn, attr, values)
+
+    def handle(self, dn, entry):
+        if dn in self.modifications:
+            self.dn_updated.add(dn)
+        for mod in self.modifications.get(dn, []):
+            attr_name = mod["attr"]
+            values = mod["values"]
+
+            if mod["op"] == "del":
+                # delete
+                attribute = entry.setdefault(attr_name, [])
+                if values is None:
+                    attribute = []
+                else:
+                    attribute = [v for v in attribute if v not in values]
+                if not attribute:  # empty
+                    del entry[attr_name]
+            elif mod["op"] == "add":
+                # add
+                attribute = entry.setdefault(attr_name, [])
+                attribute.extend([v for v in values if v not in attribute])
+            else:
+                assert False, "Unknown operation: %r" % mod["op"]
+
+        self.writer.unparse(dn, entry)
+
+    def parse(self):
+        ldif.LDIFParser.parse(self)
+
+        # check if there are any remaining modifications
+        remaining_changes = set(self.modifications.keys()) - self.dn_updated
+        for dn in remaining_changes:
+            root_logger.warning(
+                "DN: %s does not exists or haven't been updated", dn)
diff --git a/ipaserver/install/upgradeinstance.py b/ipaserver/install/upgradeinstance.py
index 684a3dd99e2215c86b92dcb7ba9d00ee9e17b8fb..602e6ec4930cd9d2b9e686a5ec2ed3de10cb082f 100644
--- a/ipaserver/install/upgradeinstance.py
+++ b/ipaserver/install/upgradeinstance.py
@@ -66,85 +66,6 @@ class GetEntryFromLDIF(ldif.LDIFParser):
         self.results[dn] = entry
 
 
-class ModifyLDIF(ldif.LDIFParser):
-    """
-    Allows to modify LDIF file.
-
-    Remove operations are executed before add operations
-    """
-    def __init__(self, input_file, writer):
-        """
-        :param input_file: an LDIF
-        :param writer: ldif.LDIFWriter instance where modified LDIF will
-        be written
-        """
-        ldif.LDIFParser.__init__(self, input_file)
-        self.writer = writer
-
-        self.add_dict = {}
-        self.remove_dict = {}
-
-    def add_value(self, dn, attr, value):
-        """
-        Add value to LDIF.
-        :param dn: DN of entry (must exists)
-        :param attr: attribute name
-        :param value: value to be added
-        """
-        attr = attr.lower()
-        entry = self.add_dict.setdefault(dn, {})
-        attribute = entry.setdefault(attr, [])
-        if value not in attribute:
-            attribute.append(value)
-
-    def remove_value(self, dn, attr, value=None):
-        """
-        Remove value from LDIF.
-        :param dn: DN of entry
-        :param attr: attribute name
-        :param value: value to be removed, if value is None, attribute will
-        be removed
-        """
-        attr = attr.lower()
-        entry = self.remove_dict.setdefault(dn, {})
-
-        if entry is None:
-            return
-        attribute = entry.setdefault(attr, [])
-        if value is None:
-            # remove all values
-            entry[attr] = None
-            return
-        elif attribute is None:
-            # already marked to remove all values
-            return
-        if value not in attribute:
-            attribute.append(value)
-
-    def handle(self, dn, entry):
-        if dn in self.remove_dict:
-            for name, value in self.remove_dict[dn].items():
-                if value is None:
-                    attribute = []
-                else:
-                    attribute = entry.setdefault(name, [])
-                    attribute = [v for v in attribute if v not in value]
-                entry[name] = attribute
-
-                if not attribute:  # empty
-                    del entry[name]
-
-        if dn in self.add_dict:
-            for name, value in self.add_dict[dn].items():
-                attribute = entry.setdefault(name, [])
-                attribute.extend([v for v in value if v not in attribute])
-
-        if not entry:  # empty
-            return
-
-        self.writer.unparse(dn, entry)
-
-
 class IPAUpgrade(service.Service):
     """
     Update the LDAP data in an instance by turning off all network
@@ -235,13 +156,11 @@ class IPAUpgrade(service.Service):
     def __enable_ds_global_write_lock(self):
         ldif_outfile = "%s.modified.out" % self.filename
         with open(ldif_outfile, "wb") as out_file:
-            ldif_writer = ldif.LDIFWriter(out_file)
             with open(self.filename, "rb") as in_file:
-                parser = ModifyLDIF(in_file, ldif_writer)
+                parser = installutils.ModifyLDIF(in_file, out_file)
 
-                parser.remove_value("cn=config", "nsslapd-global-backend-lock")
-                parser.add_value("cn=config", "nsslapd-global-backend-lock",
-                                 "on")
+                parser.replace_value(
+                    "cn=config", "nsslapd-global-backend-lock", ["on"])
                 parser.parse()
 
         shutil.copy2(ldif_outfile, self.filename)
@@ -253,22 +172,20 @@ class IPAUpgrade(service.Service):
 
         ldif_outfile = "%s.modified.out" % self.filename
         with open(ldif_outfile, "wb") as out_file:
-            ldif_writer = ldif.LDIFWriter(out_file)
             with open(self.filename, "rb") as in_file:
-                parser = ModifyLDIF(in_file, ldif_writer)
+                parser = installutils.ModifyLDIF(in_file, out_file)
 
                 if port is not None:
-                    parser.remove_value("cn=config", "nsslapd-port")
-                    parser.add_value("cn=config", "nsslapd-port", port)
+                    parser.replace_value("cn=config", "nsslapd-port", [port])
                 if security is not None:
-                    parser.remove_value("cn=config", "nsslapd-security")
-                    parser.add_value("cn=config", "nsslapd-security", security)
+                    parser.replace_value("cn=config", "nsslapd-security",
+                                         [security])
 
                 # disable global lock by default
                 parser.remove_value("cn=config", "nsslapd-global-backend-lock")
                 if global_lock is not None:
                     parser.add_value("cn=config", "nsslapd-global-backend-lock",
-                                     global_lock)
+                                     [global_lock])
 
                 parser.parse()
 
@@ -277,18 +194,11 @@ class IPAUpgrade(service.Service):
     def __disable_listeners(self):
         ldif_outfile = "%s.modified.out" % self.filename
         with open(ldif_outfile, "wb") as out_file:
-            ldif_writer = ldif.LDIFWriter(out_file)
             with open(self.filename, "rb") as in_file:
-                parser = ModifyLDIF(in_file, ldif_writer)
-
-                parser.remove_value("cn=config", "nsslapd-port")
-                parser.add_value("cn=config", "nsslapd-port", "0")
-
-                parser.remove_value("cn=config", "nsslapd-security")
-                parser.add_value("cn=config", "nsslapd-security", "off")
-
+                parser = installutils.ModifyLDIF(in_file, out_file)
+                parser.replace_value("cn=config", "nsslapd-port", ["0"])
+                parser.replace_value("cn=config", "nsslapd-security", ["off"])
                 parser.remove_value("cn=config", "nsslapd-ldapientrysearchbase")
-
                 parser.parse()
 
         shutil.copy2(ldif_outfile, self.filename)
-- 
2.4.3

From ea193d9c99929530a24a72ee6e46eb5378c3af72 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Wed, 7 Oct 2015 17:15:34 +0200
Subject: [PATCH 2/3] Add method to read changes from LDIF

modifications_from_ldif will read LDIF file and changes in LDIF will
be cached until parse() is called. After calling parse() method changes
will be applied into destination LDIF.

Only changetype modify is supported, the default operation is add.

https://fedorahosted.org/freeipa/ticket/4949

Also fixes:
https://fedorahosted.org/freeipa/ticket/4048
https://fedorahosted.org/freeipa/ticket/1930
---
 ipaserver/install/installutils.py | 40 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 9b1800243a1bda80bab437d5769c823226e6ee7d..9ea12df15f516e64fcb5f50185c1577b01f479b7 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -1172,6 +1172,46 @@ class ModifyLDIF(ldif.LDIFParser):
         self.remove_value(dn, attr)
         self.add_value(dn, attr, values)
 
+    def modifications_from_ldif(self, ldif_file):
+        """
+        Parse ldif file. Default operation is add, only changetypes "add"
+        and "modify" are supported.
+        :param ldif_file: an opened file for read
+        :raises: ValueError
+        """
+        parser = ldif.LDIFRecordList(ldif_file)
+        parser.parse()
+
+        last_dn = None
+        for dn, entry in parser.all_records:
+            if dn is None:
+                # ldif parser return None, if records belong to previous DN
+                dn = last_dn
+            else:
+                last_dn = dn
+
+            if "replace" in entry:
+                for attr in entry["replace"]:
+                    try:
+                        self.replace_value(dn, attr, entry[attr])
+                    except KeyError:
+                        raise ValueError("replace: {dn}, {attr}: values are "
+                                         "missing".format(dn=dn, attr=attr))
+            elif "delete" in entry:
+                for attr in entry["delete"]:
+                    self.remove_value(dn, attr, entry.get(attr, None))
+            elif "add" in entry:
+                for attr in entry["add"]:
+                    try:
+                        self.replace_value(dn, attr, entry[attr])
+                    except KeyError:
+                        raise ValueError("add: {dn}, {attr}: values are "
+                                         "missing".format(dn=dn, attr=attr))
+            else:
+                root_logger.warning("Ignoring entry: %s : only modifications "
+                                    "are allowed (missing \"changetype: "
+                                    "modify\")", dn)
+
     def handle(self, dn, entry):
         if dn in self.modifications:
             self.dn_updated.add(dn)
-- 
2.4.3

From 243bc4600886ea3edccb686bb3cdbf86daab9212 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 8 Oct 2015 10:38:44 +0200
Subject: [PATCH 3/3] Add option to specify LDIF file that contains DS
 configuration chages

This allows to user modify configuration changes of the directory server
instance during installation of DS

https://fedorahosted.org/freeipa/ticket/4949

Also fixes:
https://fedorahosted.org/freeipa/ticket/4048
https://fedorahosted.org/freeipa/ticket/1930
---
 install/tools/man/ipa-replica-install.1    |  3 +++
 install/tools/man/ipa-server-install.1     |  4 ++-
 ipaserver/install/dsinstance.py            | 43 ++++++++++++++++++++++++------
 ipaserver/install/server/common.py         | 14 ++++++++++
 ipaserver/install/server/install.py        |  6 +++--
 ipaserver/install/server/replicainstall.py |  7 ++---
 6 files changed, 63 insertions(+), 14 deletions(-)

diff --git a/install/tools/man/ipa-replica-install.1 b/install/tools/man/ipa-replica-install.1
index ff4d7d99991c09a875bff6a49070fbba3d13fb63..12a5dd7b85af30023614567e9d70073082bda9c1 100644
--- a/install/tools/man/ipa-replica-install.1
+++ b/install/tools/man/ipa-replica-install.1
@@ -74,6 +74,9 @@ Enable debug logging when more verbose output is needed
 .TP
 \fB\-U\fR, \fB\-\-unattended\fR
 An unattended installation that will never prompt for user input
+.TP
+\fB\-\-dirsrv-config-mods\fR
+The path to LDIF file that will be used to modify configuration of dse.ldif during installation of the directory server instance
 
 .SS "CERTIFICATE SYSTEM OPTIONS"
 .TP
diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1
index 2e0ff803c1b185d699f6f15dfb487e455404932e..ba43c80a6589aedea01111c9889d6710adf7d35e 100644
--- a/install/tools/man/ipa-server-install.1
+++ b/install/tools/man/ipa-server-install.1
@@ -78,7 +78,9 @@ Enable debug logging when more verbose output is needed
 .TP
 \fB\-U\fR, \fB\-\-unattended\fR
 An unattended installation that will never prompt for user input
-
+.TP
+\fB\-\-dirsrv-config-mods\fR
+The path to LDIF file that will be used to modify configuration of dse.ldif during installation of the directory server instance
 
 .SS "CERTIFICATE SYSTEM OPTIONS"
 .TP
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 312188273bbc2ddd6a0d4ff4e776cc6ad08a6f5e..87cbac5b312cd01e640b35706bee9fcc29f27586 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -192,7 +192,7 @@ info: IPA V2.0
 
 class DsInstance(service.Service):
     def __init__(self, realm_name=None, domain_name=None, dm_password=None,
-                 fstore=None, domainlevel=None):
+                 fstore=None, domainlevel=None, config_ldif=None):
         service.Service.__init__(self, "dirsrv",
             service_desc="directory server",
             dm_password=dm_password,
@@ -215,6 +215,7 @@ class DsInstance(service.Service):
         self.subject_base = None
         self.open_ports = []
         self.run_init_memberof = True
+        self.config_ldif = config_ldif  # updates for dse.ldif
         self.domainlevel = domainlevel
         if realm_name:
             self.suffix = ipautil.realm_to_suffix(self.realm)
@@ -234,6 +235,9 @@ class DsInstance(service.Service):
 
         self.step("creating directory server user", create_ds_user)
         self.step("creating directory server instance", self.__create_instance)
+        if self.config_ldif:
+            self.step("updating configuration in dse.ldif", self.__update_dse_ldif)
+        self.step("restarting directory server", self.__restart_instance)
         self.step("adding default schema", self.__add_default_schemas)
         self.step("enabling memberof plugin", self.__add_memberof_module)
         self.step("enabling winsync plugin", self.__add_winsync_module)
@@ -503,16 +507,39 @@ class DsInstance(service.Service):
         # check for open port 389 from now on
         self.open_ports.append(389)
 
-        root_logger.debug("restarting ds instance")
-        try:
-            self.__restart_instance()
-            root_logger.debug("done restarting ds instance")
-        except ipautil.CalledProcessError as e:
-            print("failed to restart ds instance", e)
-            root_logger.debug("failed to restart ds instance %s" % e)
         inf_fd.close()
         os.remove(paths.DIRSRV_BOOT_LDIF)
 
+    def __update_dse_ldif(self):
+        """
+        This method updates dse.ldif right after instance creation. This is
+        supposed to allow admin modify configuration of the DS which has to be
+        done before IPA is fully installed (for example: settings for
+        replication on replicas)
+        DS must be turned off.
+        """
+        self.stop()
+
+        dse_filename = os.path.join(
+            paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % self.serverid,
+            'dse.ldif'
+        )
+
+        with tempfile.NamedTemporaryFile(delete=False) as new_dse_ldif:
+            temp_filename = new_dse_ldif.name
+            with open(dse_filename, "r") as input_file:
+                parser = installutils.ModifyLDIF(input_file, new_dse_ldif)
+                # parse modification from config ldif
+                with open(self.config_ldif, "r") as config_ldif:
+                    parser.modifications_from_ldif(config_ldif)
+                parser.parse()
+            new_dse_ldif.flush()
+        shutil.copy2(temp_filename, dse_filename)
+        try:
+            os.remove(temp_filename)
+        except OSError as e:
+            root_logger.debug("Failed to clean temporary file: %s" % e)
+
     def __add_default_schemas(self):
         pent = pwd.getpwnam(DS_USER)
         for schema_fname in IPA_SCHEMA_FILES:
diff --git a/ipaserver/install/server/common.py b/ipaserver/install/server/common.py
index 3eb7279d200ffd6ab33d8d914c8d4f13e567a171..a09f394d613429818d9a09c14d1cfa6b7fe67186 100644
--- a/ipaserver/install/server/common.py
+++ b/ipaserver/install/server/common.py
@@ -343,6 +343,20 @@ class BaseServer(common.Installable, common.Interactive, core.Composite):
         description="Do not automatically create DNS SSHFP records",
     )
 
+    dirsrv_config_mods = Knob(
+        str, None,
+        description="The path to LDIF file that will be used to modify "
+                    "configuration of dse.ldif during installation of the "
+                    "directory server instance",
+        cli_metavar='FILE',
+    )
+
+    @dirsrv_config_mods.validator
+    def dirsrv_config_mods(self, value):
+        if not os.path.exists(value):
+            raise ValueError("File %s does not exist." % value)
+
+
     def __init__(self, **kwargs):
         super(BaseServer, self).__init__(**kwargs)
 
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 72f6e4d8d7476d73b2d92919ae929501f05f8ece..37669d7eee7ec6d93de922998e472ad683d15f16 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -734,7 +734,8 @@ def install(installer):
 
         if options.dirsrv_cert_files:
             ds = dsinstance.DsInstance(fstore=fstore,
-                                       domainlevel=options.domainlevel)
+                                       domainlevel=options.domainlevel,
+                                       config_ldif=options.dirsrv_config_mods)
             installer._ds = ds
             ds.create_instance(realm_name, host_name, domain_name,
                                dm_password, dirsrv_pkcs12_info,
@@ -743,7 +744,8 @@ def install(installer):
                                hbac_allow=not options.no_hbac_allow)
         else:
             ds = dsinstance.DsInstance(fstore=fstore,
-                                       domainlevel=options.domainlevel)
+                                       domainlevel=options.domainlevel,
+                                       config_ldif=options.dirsrv_config_mods)
             installer._ds = ds
             ds.create_instance(realm_name, host_name, domain_name,
                                dm_password,
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index 3087091e4bebdee6f3aec560acb7916c40615d1e..240973bad4e18ef98a456a727ee48aa1319aa38e 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -60,7 +60,7 @@ def make_pkcs12_info(directory, cert_name, password_name):
         return None
 
 
-def install_replica_ds(config):
+def install_replica_ds(config, options):
     dsinstance.check_ports()
 
     # if we have a pkcs12 file, create the cert db from
@@ -68,7 +68,8 @@ def install_replica_ds(config):
     # cert
     pkcs12_info = make_pkcs12_info(config.dir, "dscert.p12", "dirsrv_pin.txt")
 
-    ds = dsinstance.DsInstance()
+    ds = dsinstance.DsInstance(
+        config_ldif=options.dirsrv_config_mods)
     ds.create_replica(
         realm_name=config.realm_name,
         master_fqdn=config.master_host_name,
@@ -562,7 +563,7 @@ def install(installer):
             ntp.create_instance()
 
         # Configure dirsrv
-        ds = install_replica_ds(config)
+        ds = install_replica_ds(config, options)
 
         # Always try to install DNS records
         install_dns_records(config, options, remote_api)
-- 
2.4.3

From 53bd115a1734befdf20ec96ab1c6b4701b008faa Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Tue, 13 Oct 2015 13:38:29 +0200
Subject: [PATCH] CI: installation with customized DS config

Test covers:

https://fedorahosted.org/freeipa/ticket/4949
https://fedorahosted.org/freeipa/ticket/4048
https://fedorahosted.org/freeipa/ticket/1930
---
 ipatests/test_integration/tasks.py                 | 16 ++--
 .../test_customized_ds_config_install.py           | 94 ++++++++++++++++++++++
 2 files changed, 105 insertions(+), 5 deletions(-)
 create mode 100644 ipatests/test_integration/test_customized_ds_config_install.py

diff --git a/ipatests/test_integration/tasks.py b/ipatests/test_integration/tasks.py
index 918f78cde23d65a20fdab1a9484ea29ecceb4d10..e241454a984aac97eb2d0955f55bb83d85bf9d4c 100644
--- a/ipatests/test_integration/tasks.py
+++ b/ipatests/test_integration/tasks.py
@@ -288,11 +288,7 @@ def get_replica_filename(replica):
     return os.path.join(replica.config.test_dir, 'replica-info.gpg')
 
 
-def install_replica(master, replica, setup_ca=True, setup_dns=False,
-                    setup_kra=False):
-    replica.collect_log(paths.IPAREPLICA_INSTALL_LOG)
-    replica.collect_log(paths.IPAREPLICA_CONNCHECK_LOG)
-
+def replica_prepare(master, replica):
     apply_common_fixes(replica)
     fix_apache_semaphores(replica)
     prepare_reverse_zone(master, replica.ip)
@@ -304,6 +300,16 @@ def install_replica(master, replica, setup_ca=True, setup_dns=False,
         paths.REPLICA_INFO_GPG_TEMPLATE % replica.hostname)
     replica_filename = get_replica_filename(replica)
     replica.put_file_contents(replica_filename, replica_bundle)
+
+
+def install_replica(master, replica, setup_ca=True, setup_dns=False,
+                    setup_kra=False):
+    replica.collect_log(paths.IPAREPLICA_INSTALL_LOG)
+    replica.collect_log(paths.IPAREPLICA_CONNCHECK_LOG)
+
+    replica_prepare(master, replica)
+
+    replica_filename = get_replica_filename(replica)
     args = ['ipa-replica-install', '-U',
             '-p', replica.config.dirman_password,
             '-w', replica.config.admin_password,
diff --git a/ipatests/test_integration/test_customized_ds_config_install.py b/ipatests/test_integration/test_customized_ds_config_install.py
new file mode 100644
index 0000000000000000000000000000000000000000..d55f6b9c8a6bc415b770de101334d2738242d7cc
--- /dev/null
+++ b/ipatests/test_integration/test_customized_ds_config_install.py
@@ -0,0 +1,94 @@
+
+from ipatests.test_integration.base import IntegrationTest
+from ipatests.test_integration import tasks
+
+DIRSRV_CONFIG_MODS = """
+# https://fedorahosted.org/freeipa/ticket/4949
+dn: cn=config,cn=ldbm database,cn=plugins,cn=config
+changetype: modify
+replace: nsslapd-db-locks
+nsslapd-db-locks: 100000
+
+# https://fedorahosted.org/freeipa/ticket/1930
+dn: cn=config
+changetype: modify
+replace: nsslapd-allow-unauthenticated-binds
+nsslapd-allow-unauthenticated-binds: off
+-
+replace: nsslapd-require-secure-binds
+nsslapd-require-secure-binds: off
+-
+replace: nsslapd-allow-anonymous-access
+nsslapd-allow-anonymous-access: off
+-
+replace: nsslapd-minssf
+nsslapd-minssf: 0
+
+# https://fedorahosted.org/freeipa/ticket/4048
+dn: cn=config
+changetype: modify
+replace: nssslapd-maxbersize
+nssslapd-maxbersize: 209715201
+
+dn: cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: modify
+replace: nsslapd-cachememsize
+nsslapd-cachememsize: 10485761
+
+dn: cn=config,cn=ldbm database,cn=plugins,cn=config
+changetype: modify
+replace: nsslapd-import_cachesize
+nsslapd-import_cachesize: 20000001
+-
+replace: nsslapd-dbcachesize
+nsslapd-dbcachesize: 10000001
+"""
+
+CONFIG_LDIF_PATH = "/root/dirsrv-config-mod.ldif"
+
+
+class TestCustomInstallMaster(IntegrationTest):
+    """
+    Install master with customized DS config
+    """
+    topology = 'star'
+
+    @classmethod
+    def install(cls, mh):
+        # just prepare LDIF file on both master and replica
+        cls.master.put_file_contents(CONFIG_LDIF_PATH, DIRSRV_CONFIG_MODS)
+
+    def test_customized_ds_install_master(self):
+        args = [
+            'ipa-server-install', '-U',
+            '-r', self.master.domain.name,
+            '-p', self.master.config.dirman_password,
+            '-a', self.master.config.admin_password,
+            '--dirsrv-config-mods', CONFIG_LDIF_PATH,
+        ]
+        self.master.run_command(args)
+
+
+class TestCustomInstallReplica(IntegrationTest):
+    """
+    Install replica with customized DS config
+    """
+    topology = 'star'
+    num_replicas = 1
+
+    @classmethod
+    def install(cls, mh):
+        # just prepare LDIF file on both master and replica
+        cls.replicas[0].put_file_contents(CONFIG_LDIF_PATH, DIRSRV_CONFIG_MODS)
+        tasks.install_master(cls.master)
+
+    def test_customized_ds_install_replica(self):
+        tasks.replica_prepare(self.master, self.replicas[0])
+        replica_filename = tasks.get_replica_filename(self.replicas[0])
+        args = ['ipa-replica-install', '-U',
+                '-p', self.replicas[0].config.dirman_password,
+                '-w', self.replicas[0].config.admin_password,
+                '--ip-address', self.replicas[0].ip,
+                '--dirsrv-config-mods', CONFIG_LDIF_PATH,
+                replica_filename]
+        self.replicas[0].run_command(args)
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to