86baa9
From 04f68b7354cf1268019b08885eeb3a6915f314ef Mon Sep 17 00:00:00 2001
86baa9
From: Christian Heimes <cheimes@redhat.com>
86baa9
Date: Thu, 12 Jul 2018 14:37:18 +0200
86baa9
Subject: [PATCH] Unify and simplify LDAP service discovery
86baa9
86baa9
Move LDAP service discovery and service definitions from
86baa9
ipaserver.install to ipaserver. Simplify and unify different
86baa9
implementations in favor of a single implementation.
86baa9
86baa9
Signed-off-by: Christian Heimes <cheimes@redhat.com>
86baa9
Reviewed-By: Thomas Woerner <twoerner@redhat.com>
86baa9
---
86baa9
 install/tools/ipa-ca-install                |   9 +-
86baa9
 install/tools/ipactl                        |   4 +-
86baa9
 ipaserver/install/cainstance.py             |   3 +-
86baa9
 ipaserver/install/ipa_kra_install.py        |  11 +-
86baa9
 ipaserver/install/opendnssecinstance.py     |   3 +-
86baa9
 ipaserver/install/server/replicainstall.py  |  18 +--
86baa9
 ipaserver/install/service.py                |  65 +----------
86baa9
 ipaserver/masters.py                        | 123 ++++++++++++++++++++
86baa9
 ipaserver/plugins/cert.py                   |  10 +-
86baa9
 ipaserver/plugins/dogtag.py                 |  99 ++++------------
86baa9
 ipaserver/servroles.py                      |   9 +-
86baa9
 ipatests/test_ipaserver/test_serverroles.py |   3 +-
86baa9
 12 files changed, 192 insertions(+), 165 deletions(-)
86baa9
 create mode 100644 ipaserver/masters.py
86baa9
86baa9
diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
86baa9
index f78f43d94981d29939a247f3c492c5e7340298ea..dcdbe884f15b13b92ec68a11d9f00e3e28771b42 100755
86baa9
--- a/install/tools/ipa-ca-install
86baa9
+++ b/install/tools/ipa-ca-install
86baa9
@@ -34,6 +34,7 @@ from ipaserver.install.installutils import check_creds, ReplicaConfig
86baa9
 from ipaserver.install import dsinstance, ca
86baa9
 from ipaserver.install import cainstance, service
86baa9
 from ipaserver.install import custodiainstance
86baa9
+from ipaserver.masters import find_providing_server
86baa9
 from ipapython import version
86baa9
 from ipalib import api
86baa9
 from ipalib.constants import DOMAIN_LEVEL_0
86baa9
@@ -211,8 +212,9 @@ def install_replica(safe_options, options, filename):
86baa9
         config.subject_base = attrs.get('ipacertificatesubjectbase')[0]
86baa9
 
86baa9
     if config.ca_host_name is None:
86baa9
-        config.ca_host_name = \
86baa9
-            service.find_providing_server('CA', api.Backend.ldap2, api.env.ca_host)
86baa9
+        config.ca_host_name = find_providing_server(
86baa9
+            'CA', api.Backend.ldap2, [api.env.ca_host]
86baa9
+        )
86baa9
 
86baa9
     options.realm_name = config.realm_name
86baa9
     options.domain_name = config.domain_name
86baa9
@@ -299,7 +301,8 @@ def promote(safe_options, options, filename):
86baa9
             paths.KRB5_KEYTAB,
86baa9
             ccache)
86baa9
 
86baa9
-        ca_host = service.find_providing_server('CA', api.Backend.ldap2)
86baa9
+        ca_host = find_providing_server('CA', api.Backend.ldap2)
86baa9
+
86baa9
         if ca_host is None:
86baa9
             install_master(safe_options, options)
86baa9
         else:
86baa9
diff --git a/install/tools/ipactl b/install/tools/ipactl
86baa9
index ade91f7f75dab4fdf3b7fa73d2624a7395bc2a53..2767a26d1b70337d37dbcd87c707919579fe7e29 100755
86baa9
--- a/install/tools/ipactl
86baa9
+++ b/install/tools/ipactl
86baa9
@@ -224,9 +224,9 @@ def get_config(dirsrv):
86baa9
         svc_list.append([order, name])
86baa9
 
86baa9
     ordered_list = []
86baa9
-    for (order, svc) in sorted(svc_list):
86baa9
+    for order, svc in sorted(svc_list):
86baa9
         if svc in service.SERVICE_LIST:
86baa9
-            ordered_list.append(service.SERVICE_LIST[svc][0])
86baa9
+            ordered_list.append(service.SERVICE_LIST[svc].systemd_name)
86baa9
     return ordered_list
86baa9
 
86baa9
 def get_config_from_file():
86baa9
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
86baa9
index e101087ac2d738eb95cc643bfb12faaf5e65f0be..f424e7cd76d24a5a633a4f4babf3e112537be92c 100644
86baa9
--- a/ipaserver/install/cainstance.py
86baa9
+++ b/ipaserver/install/cainstance.py
86baa9
@@ -71,6 +71,7 @@ from ipaserver.install import replication
86baa9
 from ipaserver.install import sysupgrade
86baa9
 from ipaserver.install.dogtaginstance import DogtagInstance
86baa9
 from ipaserver.plugins import ldap2
86baa9
+from ipaserver.masters import ENABLED_SERVICE
86baa9
 
86baa9
 logger = logging.getLogger(__name__)
86baa9
 
86baa9
@@ -1304,7 +1305,7 @@ class CAInstance(DogtagInstance):
86baa9
             config = ['caRenewalMaster']
86baa9
         else:
86baa9
             config = []
86baa9
-        self._ldap_enable(u'enabledService', "CA", self.fqdn, basedn, config)
86baa9
+        self._ldap_enable(ENABLED_SERVICE, "CA", self.fqdn, basedn, config)
86baa9
 
86baa9
     def setup_lightweight_ca_key_retrieval(self):
86baa9
         if sysupgrade.get_upgrade_state('dogtag', 'setup_lwca_key_retrieval'):
86baa9
diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py
86baa9
index b536685f5f1f3fccab07fd37aa001958e2d38420..19260ac7f23a7c6f3a6328d4f146510a186b706e 100644
86baa9
--- a/ipaserver/install/ipa_kra_install.py
86baa9
+++ b/ipaserver/install/ipa_kra_install.py
86baa9
@@ -41,6 +41,7 @@ from ipaserver.install.installutils import create_replica_config
86baa9
 from ipaserver.install import dogtaginstance
86baa9
 from ipaserver.install import kra
86baa9
 from ipaserver.install.installutils import ReplicaConfig
86baa9
+from ipaserver.masters import find_providing_server
86baa9
 
86baa9
 logger = logging.getLogger(__name__)
86baa9
 
86baa9
@@ -206,8 +207,14 @@ class KRAInstaller(KRAInstall):
86baa9
                 config.subject_base = attrs.get('ipacertificatesubjectbase')[0]
86baa9
 
86baa9
             if config.kra_host_name is None:
86baa9
-                config.kra_host_name = service.find_providing_server(
86baa9
-                    'KRA', api.Backend.ldap2, api.env.ca_host)
86baa9
+                config.kra_host_name = find_providing_server(
86baa9
+                    'KRA', api.Backend.ldap2, [api.env.ca_host]
86baa9
+                )
86baa9
+                if config.kra_host_name is None:
86baa9
+                    # all CA/KRA servers are down or unreachable.
86baa9
+                    raise admintool.ScriptError(
86baa9
+                        "Failed to find an active KRA server!"
86baa9
+                    )
86baa9
             custodia = custodiainstance.get_custodia_instance(
86baa9
                 config, custodiainstance.CustodiaModes.KRA_PEER)
86baa9
         else:
86baa9
diff --git a/ipaserver/install/opendnssecinstance.py b/ipaserver/install/opendnssecinstance.py
86baa9
index 0337bb22fea44f95ee9077423136353a991325db..d6725dff11599a8755a29b6707dc7b451258629f 100644
86baa9
--- a/ipaserver/install/opendnssecinstance.py
86baa9
+++ b/ipaserver/install/opendnssecinstance.py
86baa9
@@ -15,6 +15,7 @@ from subprocess import CalledProcessError
86baa9
 from ipalib.install import sysrestore
86baa9
 from ipaserver.install import service
86baa9
 from ipaserver.install import installutils
86baa9
+from ipaserver.masters import ENABLED_SERVICE
86baa9
 from ipapython.dn import DN
86baa9
 from ipapython import ipautil
86baa9
 from ipaplatform import services
86baa9
@@ -45,7 +46,7 @@ def get_dnssec_key_masters(conn):
86baa9
     filter_attrs = {
86baa9
         u'cn': u'DNSSEC',
86baa9
         u'objectclass': u'ipaConfigObject',
86baa9
-        u'ipaConfigString': [KEYMASTER, u'enabledService'],
86baa9
+        u'ipaConfigString': [KEYMASTER, ENABLED_SERVICE],
86baa9
     }
86baa9
     only_masters_f = conn.make_filter(filter_attrs, rules=conn.MATCH_ALL)
86baa9
 
86baa9
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
86baa9
index b221e1291f973e7255263a39cfd680af1321598d..37ecbe4146fa908c30fb708037fcaa47af1a258b 100644
86baa9
--- a/ipaserver/install/server/replicainstall.py
86baa9
+++ b/ipaserver/install/server/replicainstall.py
86baa9
@@ -46,6 +46,7 @@ from ipaserver.install.installutils import (
86baa9
     validate_mask)
86baa9
 from ipaserver.install.replication import (
86baa9
     ReplicationManager, replica_conn_check)
86baa9
+from ipaserver.masters import find_providing_servers, find_providing_server
86baa9
 import SSSDConfig
86baa9
 from subprocess import CalledProcessError
86baa9
 
86baa9
@@ -1257,9 +1258,10 @@ def promote_check(installer):
86baa9
         if subject_base is not None:
86baa9
             config.subject_base = DN(subject_base)
86baa9
 
86baa9
-        # Find if any server has a CA
86baa9
-        ca_host = service.find_providing_server(
86baa9
-                'CA', conn, config.ca_host_name)
86baa9
+        # Find any server with a CA
86baa9
+        ca_host = find_providing_server(
86baa9
+            'CA', conn, [config.ca_host_name]
86baa9
+        )
86baa9
         if ca_host is not None:
86baa9
             config.ca_host_name = ca_host
86baa9
             ca_enabled = True
86baa9
@@ -1280,14 +1282,16 @@ def promote_check(installer):
86baa9
                              "custom certificates.")
86baa9
                 raise ScriptError(rval=3)
86baa9
 
86baa9
-        kra_host = service.find_providing_server(
86baa9
-                'KRA', conn, config.kra_host_name)
86baa9
+        # Find any server with a KRA
86baa9
+        kra_host = find_providing_server(
86baa9
+            'KRA', conn, [config.kra_host_name]
86baa9
+        )
86baa9
         if kra_host is not None:
86baa9
             config.kra_host_name = kra_host
86baa9
             kra_enabled = True
86baa9
         else:
86baa9
             if options.setup_kra:
86baa9
-                logger.error("There is no KRA server in the domain, "
86baa9
+                logger.error("There is no active KRA server in the domain, "
86baa9
                              "can't setup a KRA clone")
86baa9
                 raise ScriptError(rval=3)
86baa9
             kra_enabled = False
86baa9
@@ -1577,7 +1581,7 @@ def install(installer):
86baa9
     # Enable configured services and update DNS SRV records
86baa9
     service.enable_services(config.host_name)
86baa9
     api.Command.dns_update_system_records()
86baa9
-    ca_servers = service.find_providing_servers('CA', api.Backend.ldap2, api)
86baa9
+    ca_servers = find_providing_servers('CA', api.Backend.ldap2, api=api)
86baa9
     api.Backend.ldap2.disconnect()
86baa9
 
86baa9
     # Everything installed properly, activate ipa service.
86baa9
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
86baa9
index baefc0076990e67e56dfa68da48b56f4cd55849f..a030801175491f65dc83aa9d42afdb1dfdb65b0f 100644
86baa9
--- a/ipaserver/install/service.py
86baa9
+++ b/ipaserver/install/service.py
86baa9
@@ -38,34 +38,15 @@ from ipapython import kerberos
86baa9
 from ipalib import api, errors
86baa9
 from ipaplatform import services
86baa9
 from ipaplatform.paths import paths
86baa9
+from ipaserver.masters import (
86baa9
+    CONFIGURED_SERVICE, ENABLED_SERVICE, SERVICE_LIST
86baa9
+)
86baa9
 
86baa9
 logger = logging.getLogger(__name__)
86baa9
 
86baa9
 if six.PY3:
86baa9
     unicode = str
86baa9
 
86baa9
-# The service name as stored in cn=masters,cn=ipa,cn=etc. In the tuple
86baa9
-# the first value is the *nix service name, the second the start order.
86baa9
-SERVICE_LIST = {
86baa9
-    'KDC': ('krb5kdc', 10),
86baa9
-    'KPASSWD': ('kadmin', 20),
86baa9
-    'DNS': ('named', 30),
86baa9
-    'HTTP': ('httpd', 40),
86baa9
-    'KEYS': ('ipa-custodia', 41),
86baa9
-    'NTP': ('ntpd', 45),
86baa9
-    'CA': ('pki-tomcatd', 50),
86baa9
-    'KRA': ('pki-tomcatd', 51),
86baa9
-    'ADTRUST': ('smb', 60),
86baa9
-    'EXTID': ('winbind', 70),
86baa9
-    'OTPD': ('ipa-otpd', 80),
86baa9
-    'DNSKeyExporter': ('ipa-ods-exporter', 90),
86baa9
-    'DNSSEC': ('ods-enforcerd', 100),
86baa9
-    'DNSKeySync': ('ipa-dnskeysyncd', 110),
86baa9
-}
86baa9
-
86baa9
-CONFIGURED_SERVICE = u'configuredService'
86baa9
-ENABLED_SERVICE = u'enabledService'
86baa9
-
86baa9
 
86baa9
 def print_msg(message, output_fd=sys.stdout):
86baa9
     logger.debug("%s", message)
86baa9
@@ -117,44 +98,6 @@ def add_principals_to_group(admin_conn, group, member_attr, principals):
86baa9
         pass
86baa9
 
86baa9
 
86baa9
-def find_providing_servers(svcname, conn, api):
86baa9
-    """
86baa9
-    Find servers that provide the given service.
86baa9
-
86baa9
-    :param svcname: The service to find
86baa9
-    :param conn: a connection to the LDAP server
86baa9
-    :return: list of host names (possibly empty)
86baa9
-
86baa9
-    """
86baa9
-    dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
86baa9
-    query_filter = conn.make_filter({'objectClass': 'ipaConfigObject',
86baa9
-                                     'ipaConfigString': ENABLED_SERVICE,
86baa9
-                                     'cn': svcname}, rules='&')
86baa9
-    try:
86baa9
-        entries, _trunc = conn.find_entries(filter=query_filter, base_dn=dn)
86baa9
-    except errors.NotFound:
86baa9
-        return []
86baa9
-    else:
86baa9
-        return [entry.dn[1].value for entry in entries]
86baa9
-
86baa9
-
86baa9
-def find_providing_server(svcname, conn, host_name=None, api=api):
86baa9
-    """
86baa9
-    Find a server that provides the given service.
86baa9
-
86baa9
-    :param svcname: The service to find
86baa9
-    :param conn: a connection to the LDAP server
86baa9
-    :param host_name: the preferred server
86baa9
-    :return: the selected host name
86baa9
-
86baa9
-    """
86baa9
-    servers = find_providing_servers(svcname, conn, api)
86baa9
-    if len(servers) == 0:
86baa9
-        return None
86baa9
-    if host_name in servers:
86baa9
-        return host_name
86baa9
-    return servers[0]
86baa9
-
86baa9
 
86baa9
 def case_insensitive_attr_has_value(attr, value):
86baa9
     """
86baa9
@@ -618,7 +561,7 @@ class Service(object):
86baa9
 
86baa9
     def _ldap_enable(self, value, name, fqdn, ldap_suffix, config):
86baa9
         extra_config_opts = [
86baa9
-            ' '.join([u'startOrder', unicode(SERVICE_LIST[name][1])])
86baa9
+            u'startOrder {}'.format(SERVICE_LIST[name].startorder),
86baa9
         ]
86baa9
         extra_config_opts.extend(config)
86baa9
 
86baa9
diff --git a/ipaserver/masters.py b/ipaserver/masters.py
86baa9
new file mode 100644
86baa9
index 0000000000000000000000000000000000000000..171c3abe0d6eea5aa6bcc642815eceae3ae885e7
86baa9
--- /dev/null
86baa9
+++ b/ipaserver/masters.py
86baa9
@@ -0,0 +1,123 @@
86baa9
+#
86baa9
+# Copyright (C) 2018  FreeIPA Contributors see COPYING for license
86baa9
+#
86baa9
+"""Helpers services in for cn=masters,cn=ipa,cn=etc
86baa9
+"""
86baa9
+
86baa9
+from __future__ import absolute_import
86baa9
+
86baa9
+import collections
86baa9
+import logging
86baa9
+import random
86baa9
+
86baa9
+from ipapython.dn import DN
86baa9
+from ipalib import api
86baa9
+from ipalib import errors
86baa9
+
86baa9
+logger = logging.getLogger(__name__)
86baa9
+
86baa9
+# constants for ipaConfigString
86baa9
+CONFIGURED_SERVICE = u'configuredService'
86baa9
+ENABLED_SERVICE = u'enabledService'
86baa9
+
86baa9
+# The service name as stored in cn=masters,cn=ipa,cn=etc. The values are:
86baa9
+# 0: systemd service name
86baa9
+# 1: start order for system service
86baa9
+# 2: LDAP server entry CN, also used as SERVICE_LIST key
86baa9
+service_definition = collections.namedtuple(
86baa9
+    "service_definition",
86baa9
+    "systemd_name startorder service_entry"
86baa9
+)
86baa9
+
86baa9
+SERVICES = [
86baa9
+    service_definition('krb5kdc', 10, 'KDC'),
86baa9
+    service_definition('kadmin', 20, 'KPASSWD'),
86baa9
+    service_definition('named', 30, 'DNS'),
86baa9
+    service_definition('httpd', 40, 'HTTP'),
86baa9
+    service_definition('ipa-custodia', 41, 'KEYS'),
86baa9
+    service_definition('ntpd', 45, 'NTP'),
86baa9
+    service_definition('pki-tomcatd', 50, 'CA'),
86baa9
+    service_definition('pki-tomcatd', 51, 'KRA'),
86baa9
+    service_definition('smb', 60, 'ADTRUST'),
86baa9
+    service_definition('winbind', 70, 'EXTID'),
86baa9
+    service_definition('ipa-otpd', 80, 'OTPD'),
86baa9
+    service_definition('ipa-ods-exporter', 90, 'DNSKeyExporter'),
86baa9
+    service_definition('ods-enforcerd', 100, 'DNSSEC'),
86baa9
+    service_definition('ipa-dnskeysyncd', 110, 'DNSKeySync'),
86baa9
+]
86baa9
+
86baa9
+SERVICE_LIST = {s.service_entry: s for s in SERVICES}
86baa9
+
86baa9
+
86baa9
+def find_providing_servers(svcname, conn=None, preferred_hosts=(), api=api):
86baa9
+    """Find servers that provide the given service.
86baa9
+
86baa9
+    :param svcname: The service to find
86baa9
+    :param preferred_hosts: preferred servers
86baa9
+    :param conn: a connection to the LDAP server
86baa9
+    :param api: ipalib.API instance
86baa9
+    :return: list of host names in randomized order (possibly empty)
86baa9
+
86baa9
+    Preferred servers are moved to the front of the list if and only if they
86baa9
+    are found as providing servers.
86baa9
+    """
86baa9
+    assert isinstance(preferred_hosts, (tuple, list))
86baa9
+    if svcname not in SERVICE_LIST:
86baa9
+        raise ValueError("Unknown service '{}'.".format(svcname))
86baa9
+    if conn is None:
86baa9
+        conn = api.Backend.ldap2
86baa9
+
86baa9
+    dn = DN(api.env.container_masters, api.env.basedn)
86baa9
+    query_filter = conn.make_filter(
86baa9
+        {
86baa9
+            'objectClass': 'ipaConfigObject',
86baa9
+            'ipaConfigString': ENABLED_SERVICE,
86baa9
+            'cn': svcname
86baa9
+        },
86baa9
+        rules='&'
86baa9
+    )
86baa9
+    try:
86baa9
+        entries, _trunc = conn.find_entries(
86baa9
+            filter=query_filter,
86baa9
+            attrs_list=[],
86baa9
+            base_dn=dn
86baa9
+        )
86baa9
+    except errors.NotFound:
86baa9
+        return []
86baa9
+
86baa9
+    # unique list of host names, DNS is case insensitive
86baa9
+    servers = list(set(entry.dn[1].value.lower() for entry in entries))
86baa9
+    # shuffle the list like DNS SRV would randomize it
86baa9
+    random.shuffle(servers)
86baa9
+    # Move preferred hosts to front
86baa9
+    for host_name in reversed(preferred_hosts):
86baa9
+        host_name = host_name.lower()
86baa9
+        try:
86baa9
+            servers.remove(host_name)
86baa9
+        except ValueError:
86baa9
+            # preferred server not found, log and ignore
86baa9
+            logger.warning(
86baa9
+                "Lookup failed: Preferred host %s does not provide %s.",
86baa9
+                host_name, svcname
86baa9
+            )
86baa9
+        else:
86baa9
+            servers.insert(0, host_name)
86baa9
+    return servers
86baa9
+
86baa9
+
86baa9
+def find_providing_server(svcname, conn=None, preferred_hosts=(), api=api):
86baa9
+    """Find a server that provides the given service.
86baa9
+
86baa9
+    :param svcname: The service to find
86baa9
+    :param conn: a connection to the LDAP server
86baa9
+    :param host_name: the preferred server
86baa9
+    :param api: ipalib.API instance
86baa9
+    :return: the selected host name or None
86baa9
+    """
86baa9
+    servers = find_providing_servers(
86baa9
+        svcname, conn=conn, preferred_hosts=preferred_hosts, api=api
86baa9
+    )
86baa9
+    if not servers:
86baa9
+        return None
86baa9
+    else:
86baa9
+        return servers[0]
86baa9
diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
86baa9
index da77f507cb2307eeec63acd0ab9d58c985ea8fe2..ff750e9d38ff98e0e1fa1c2eee5a3d0719da94bf 100644
86baa9
--- a/ipaserver/plugins/cert.py
86baa9
+++ b/ipaserver/plugins/cert.py
86baa9
@@ -55,6 +55,7 @@ from ipalib import output
86baa9
 from ipapython import dnsutil, kerberos
86baa9
 from ipapython.dn import DN
86baa9
 from ipaserver.plugins.service import normalize_principal, validate_realm
86baa9
+from ipaserver.masters import ENABLED_SERVICE, CONFIGURED_SERVICE
86baa9
 
86baa9
 try:
86baa9
     import pyhbac
86baa9
@@ -297,19 +298,14 @@ def caacl_check(principal, ca, profile_id):
86baa9
 def ca_kdc_check(api_instance, hostname):
86baa9
     master_dn = api_instance.Object.server.get_dn(unicode(hostname))
86baa9
     kdc_dn = DN(('cn', 'KDC'), master_dn)
86baa9
-
86baa9
+    wanted = {ENABLED_SERVICE, CONFIGURED_SERVICE}
86baa9
     try:
86baa9
         kdc_entry = api_instance.Backend.ldap2.get_entry(
86baa9
             kdc_dn, ['ipaConfigString'])
86baa9
-
86baa9
-        ipaconfigstring = {val.lower() for val in kdc_entry['ipaConfigString']}
86baa9
-
86baa9
-        if 'enabledservice' not in ipaconfigstring \
86baa9
-                and 'configuredservice' not in ipaconfigstring:
86baa9
+        if not wanted.intersection(kdc_entry['ipaConfigString']):
86baa9
             raise errors.NotFound(
86baa9
                 reason=_("enabledService/configuredService not in "
86baa9
                          "ipaConfigString kdc entry"))
86baa9
-
86baa9
     except errors.NotFound:
86baa9
         raise errors.ACIError(
86baa9
             info=_("Host '%(hostname)s' is not an active KDC")
86baa9
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
86baa9
index 5a44f00c583cd4bae8211a511f907c4e179bb3f6..17e2225688c0c3878ef22e5979c65e30f971b5b3 100644
86baa9
--- a/ipaserver/plugins/dogtag.py
86baa9
+++ b/ipaserver/plugins/dogtag.py
86baa9
@@ -255,6 +255,7 @@ from ipalib import Backend, api
86baa9
 from ipapython.dn import DN
86baa9
 import ipapython.cookie
86baa9
 from ipapython import dogtag, ipautil, certdb
86baa9
+from ipaserver.masters import find_providing_server
86baa9
 
86baa9
 if api.env.in_server:
86baa9
     import pki
86baa9
@@ -1208,56 +1209,6 @@ def parse_updateCRL_xml(doc):
86baa9
     return response
86baa9
 
86baa9
 
86baa9
-def host_has_service(host, ldap2, service='CA'):
86baa9
-    """
86baa9
-    :param host: A host which might be a master for a service.
86baa9
-    :param ldap2: connection to the local database
86baa9
-    :param service: The service for which the host might be a master.
86baa9
-    :return:   (true, false)
86baa9
-
86baa9
-    Check if a specified host is a master for a specified service.
86baa9
-    """
86baa9
-    base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
86baa9
-                 ('cn', 'etc'), api.env.basedn)
86baa9
-    filter_attrs = {
86baa9
-        'objectClass': 'ipaConfigObject',
86baa9
-        'cn': service,
86baa9
-        'ipaConfigString': 'enabledService',
86baa9
-        }
86baa9
-    query_filter = ldap2.make_filter(filter_attrs, rules='&')
86baa9
-    try:
86baa9
-        ent, _trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
86baa9
-        if len(ent):
86baa9
-            return True
86baa9
-    except Exception:
86baa9
-        pass
86baa9
-    return False
86baa9
-
86baa9
-
86baa9
-def select_any_master(ldap2, service='CA'):
86baa9
-    """
86baa9
-    :param ldap2: connection to the local database
86baa9
-    :param service: The service for which we're looking for a master.
86baa9
-    :return:   host as str
86baa9
-
86baa9
-    Select any host which is a master for a specified service.
86baa9
-    """
86baa9
-    base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
86baa9
-                  api.env.basedn)
86baa9
-    filter_attrs = {
86baa9
-         'objectClass': 'ipaConfigObject',
86baa9
-         'cn': service,
86baa9
-         'ipaConfigString': 'enabledService',}
86baa9
-    query_filter = ldap2.make_filter(filter_attrs, rules='&')
86baa9
-    try:
86baa9
-        ent, _trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
86baa9
-        if len(ent):
86baa9
-            entry = random.choice(ent)
86baa9
-            return entry.dn[1].value
86baa9
-    except Exception:
86baa9
-        pass
86baa9
-    return None
86baa9
-
86baa9
 #-------------------------------------------------------------------------------
86baa9
 
86baa9
 from ipalib import Registry, errors, SkipPluginModule
86baa9
@@ -1265,7 +1216,6 @@ if api.env.ra_plugin != 'dogtag':
86baa9
     # In this case, abort loading this plugin module...
86baa9
     raise SkipPluginModule(reason='dogtag not selected as RA plugin')
86baa9
 import os
86baa9
-import random
86baa9
 from ipaserver.plugins import rabase
86baa9
 from ipalib.constants import TYPE_ERROR
86baa9
 from ipalib import _
86baa9
@@ -1330,17 +1280,19 @@ class RestClient(Backend):
86baa9
         if self._ca_host is not None:
86baa9
             return self._ca_host
86baa9
 
86baa9
-        ldap2 = self.api.Backend.ldap2
86baa9
-        if host_has_service(api.env.ca_host, ldap2, "CA"):
86baa9
-            object.__setattr__(self, '_ca_host', api.env.ca_host)
86baa9
-        elif api.env.host != api.env.ca_host:
86baa9
-            if host_has_service(api.env.host, ldap2, "CA"):
86baa9
-                object.__setattr__(self, '_ca_host', api.env.host)
86baa9
-        else:
86baa9
-            object.__setattr__(self, '_ca_host', select_any_master(ldap2))
86baa9
-        if self._ca_host is None:
86baa9
-            object.__setattr__(self, '_ca_host', api.env.ca_host)
86baa9
-        return self._ca_host
86baa9
+        preferred = [api.env.ca_host]
86baa9
+        if api.env.host != api.env.ca_host:
86baa9
+            preferred.append(api.env.host)
86baa9
+        ca_host = find_providing_server(
86baa9
+            'CA', conn=self.api.Backend.ldap2, preferred_hosts=preferred,
86baa9
+            api=self.api
86baa9
+        )
86baa9
+        if ca_host is None:
86baa9
+            # TODO: need during installation, CA is not yet set as enabled
86baa9
+            ca_host = api.env.ca_host
86baa9
+        # object is locked, need to use __setattr__()
86baa9
+        object.__setattr__(self, '_ca_host', ca_host)
86baa9
+        return ca_host
86baa9
 
86baa9
     def __enter__(self):
86baa9
         """Log into the REST API"""
86baa9
@@ -2082,9 +2034,7 @@ class kra(Backend):
86baa9
     """
86baa9
 
86baa9
     def __init__(self, api, kra_port=443):
86baa9
-
86baa9
         self.kra_port = kra_port
86baa9
-
86baa9
         super(kra, self).__init__(api)
86baa9
 
86baa9
     @property
86baa9
@@ -2095,17 +2045,18 @@ class kra(Backend):
86baa9
 
86baa9
         Select our KRA host.
86baa9
         """
86baa9
-        ldap2 = self.api.Backend.ldap2
86baa9
-        if host_has_service(api.env.ca_host, ldap2, "KRA"):
86baa9
-            return api.env.ca_host
86baa9
+        preferred = [api.env.ca_host]
86baa9
         if api.env.host != api.env.ca_host:
86baa9
-            if host_has_service(api.env.host, ldap2, "KRA"):
86baa9
-                return api.env.host
86baa9
-        host = select_any_master(ldap2, "KRA")
86baa9
-        if host:
86baa9
-            return host
86baa9
-        else:
86baa9
-            return api.env.ca_host
86baa9
+            preferred.append(api.env.host)
86baa9
+
86baa9
+        kra_host = find_providing_server(
86baa9
+            'KRA', self.api.Backend.ldap2, preferred_hosts=preferred,
86baa9
+            api=self.api
86baa9
+        )
86baa9
+        if kra_host is None:
86baa9
+            # TODO: need during installation, KRA is not yet set as enabled
86baa9
+            kra_host = api.env.ca_host
86baa9
+        return kra_host
86baa9
 
86baa9
     @contextlib.contextmanager
86baa9
     def get_client(self):
86baa9
diff --git a/ipaserver/servroles.py b/ipaserver/servroles.py
86baa9
index 994a59e35bb5d3a007e3d63bb273ba9ea552407d..af4e63710136a15e1673210c3e2207658698fbb5 100644
86baa9
--- a/ipaserver/servroles.py
86baa9
+++ b/ipaserver/servroles.py
86baa9
@@ -79,7 +79,7 @@ import six
86baa9
 
86baa9
 from ipalib import _, errors
86baa9
 from ipapython.dn import DN
86baa9
-
86baa9
+from ipaserver.masters import ENABLED_SERVICE
86baa9
 
86baa9
 if six.PY3:
86baa9
     unicode = str
86baa9
@@ -483,11 +483,8 @@ class ServiceBasedRole(BaseServerRole):
86baa9
         :param entry: LDAPEntry of the service
86baa9
         :returns: True if the service entry is enabled, False otherwise
86baa9
         """
86baa9
-        enabled_value = 'enabledservice'
86baa9
-        ipaconfigstring_values = set(
86baa9
-            e.lower() for e in entry.get('ipaConfigString', []))
86baa9
-
86baa9
-        return enabled_value in ipaconfigstring_values
86baa9
+        ipaconfigstring_values = set(entry.get('ipaConfigString', []))
86baa9
+        return ENABLED_SERVICE in ipaconfigstring_values
86baa9
 
86baa9
     def _get_services_by_masters(self, entries):
86baa9
         """
86baa9
diff --git a/ipatests/test_ipaserver/test_serverroles.py b/ipatests/test_ipaserver/test_serverroles.py
86baa9
index 76f1378ed5a94069cdef918d577e3658b78ecc43..e1bf0254dddfe03ade4fe72419b609de8e95b79a 100644
86baa9
--- a/ipatests/test_ipaserver/test_serverroles.py
86baa9
+++ b/ipatests/test_ipaserver/test_serverroles.py
86baa9
@@ -16,6 +16,7 @@ import pytest
86baa9
 from ipaplatform.paths import paths
86baa9
 from ipalib import api, create_api, errors
86baa9
 from ipapython.dn import DN
86baa9
+from ipaserver.masters import ENABLED_SERVICE
86baa9
 
86baa9
 pytestmark = pytest.mark.needs_ipaapi
86baa9
 
86baa9
@@ -25,7 +26,7 @@ def _make_service_entry(ldap_backend, dn, enabled=True, other_config=None):
86baa9
         'objectClass': ['top', 'nsContainer', 'ipaConfigObject'],
86baa9
     }
86baa9
     if enabled:
86baa9
-        mods.update({'ipaConfigString': ['enabledService']})
86baa9
+        mods.update({'ipaConfigString': [ENABLED_SERVICE]})
86baa9
 
86baa9
     if other_config is not None:
86baa9
         mods.setdefault('ipaConfigString', [])
86baa9
-- 
86baa9
2.20.1
86baa9