summaryrefslogtreecommitdiffstats
path: root/ipaserver/install
diff options
context:
space:
mode:
authorMartin Babinsky <mbabinsk@redhat.com>2015-11-19 17:58:44 +0100
committerMartin Basti <mbasti@redhat.com>2015-12-02 14:10:19 +0100
commitb8c619a7139bd7b65caa03b68431e22791ff19bf (patch)
treee80d0e334549c5f08dedfa348cefe12f86c893c9 /ipaserver/install
parent8d4b14e0ce33baed5f237175ef2a853538ead0a8 (diff)
downloadfreeipa-b8c619a7139bd7b65caa03b68431e22791ff19bf.tar.gz
freeipa-b8c619a7139bd7b65caa03b68431e22791ff19bf.tar.xz
freeipa-b8c619a7139bd7b65caa03b68431e22791ff19bf.zip
implement domain level 1 specific topology checks into IPA server uninstaller
When uninstalling domain level 1 master its removal from topology is checked on remote masters. The uninstaller also checks whether the uninstallation disconnects the topology and if yes aborts the procedure. The '--ignore-disconnected-topology' options skips this check. https://fedorahosted.org/freeipa/ticket/5377 https://fedorahosted.org/freeipa/ticket/5409 Reviewed-By: Jan Cholasta <jcholast@redhat.com> Reviewed-By: Martin Basti <mbasti@redhat.com>
Diffstat (limited to 'ipaserver/install')
-rw-r--r--ipaserver/install/server/install.py193
1 files changed, 166 insertions, 27 deletions
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 3c9a527d6..dbc8f5cd3 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -26,7 +26,7 @@ from ipapython.ipautil import (
from ipaplatform import services
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
-from ipalib import api, constants, errors, x509
+from ipalib import api, create_api, constants, errors, x509
from ipalib.constants import CACERT
from ipalib.util import validate_domain_name
import ipaclient.ntpconf
@@ -290,6 +290,107 @@ def common_cleanup(func):
return decorated
+def check_master_deleted(api, masters, interactive):
+ try:
+ host_princ = api.Command.host_show(
+ api.env.host)['result']['krbprincipalname'][0]
+ except Exception as e:
+ root_logger.warning(
+ "Failed to get host principal name: {0}".format(e)
+ )
+ return False
+
+ ccache_path = os.path.join('/', 'tmp', 'krb5cc_host')
+ with ipautil.private_ccache(ccache_path):
+ try:
+ ipautil.kinit_keytab(host_princ, paths.KRB5_KEYTAB, ccache_path)
+ except Exception as e:
+ root_logger.error(
+ "Kerberos authentication as '{0}' failed: {1}".format(
+ host_princ, e
+ )
+ )
+ return False
+
+ last_server = True
+ for master in masters:
+ master_cn = master['cn'][0]
+ if api.env.host == master_cn:
+ continue
+
+ last_server = False
+ master_ldap_uri = u'ldap://{0}'.format(master_cn)
+
+ # initialize remote api
+ remote_api = create_api(mode=None)
+ remote_api.bootstrap(ldap_uri=master_ldap_uri, in_server=True)
+ remote_api.finalize()
+
+ root_logger.debug("Connecting to '{0}'...".format(master_ldap_uri))
+ try:
+ remote_api.Backend.ldap2.connect(ccache=ccache_path)
+ remote_api.Command.server_show(api.env.host)
+ root_logger.debug(
+ "Server entry '{0}' present on '{1}'".format(
+ api.env.host, master_cn
+ )
+ )
+ return False
+ except (errors.NotFound, errors.ACIError):
+ # this may occur because the node was already deleted from the
+ # topology and the host principal doesn't exist
+ root_logger.debug(
+ "'{0}' was removed from topology".format(
+ api.env.host
+ )
+ )
+ return True
+ except errors.NetworkError:
+ # try the next master
+ root_logger.debug(
+ "Connection to remote master '{0}' failed".format(
+ master_cn
+ )
+ )
+ except Exception as e:
+ root_logger.debug(
+ "Unexpected error when connecting to remote master '{0}': "
+ "{1}".format(
+ master_cn, e
+ )
+ )
+ finally:
+ root_logger.debug("Disconnecting from {0}".format(master_cn))
+
+ if remote_api.Backend.ldap2.isconnected():
+ remote_api.Backend.ldap2.disconnect()
+
+ # prompt the user if we are not able to determine whether the IPA master
+ # was removed from topology
+ if not last_server:
+ print("WARNING: Failed to determine whether the IPA master was "
+ "already removed from topology.")
+ if (interactive and not user_input("Proceed with uninstallation?", False)):
+ print("Aborted")
+ sys.exit(1)
+
+ return False
+
+ return True
+
+
+def check_topology_connectivity(api, masters):
+ topo_errors = replication.check_last_link_managed(
+ api, api.env.host, masters)
+ errors_after_uninstall = [topo_errors[i][1] for i in topo_errors]
+
+ if any(errors_after_uninstall):
+ print("Uninstallation leads to disconnected topology")
+ print("Use '--ignore-topology-disconnect' to skip this check")
+ print("Aborting uninstallation")
+ sys.exit(1)
+
+
@common_cleanup
def install_check(installer):
options = installer
@@ -999,35 +1100,62 @@ def uninstall_check(installer):
else:
api.Backend.ldap2.connect(autobind=True)
dns.uninstall_check(options)
+ domain_level = dsinstance.get_domain_level(api)
- rm = replication.ReplicationManager(
- realm=api.env.realm,
- hostname=api.env.host,
- dirman_passwd=None,
- conn=conn
- )
- agreements = rm.find_ipa_replication_agreements()
-
- if agreements:
- other_masters = [a.get('cn')[0][4:] for a in agreements]
- msg = (
- "\nReplication agreements with the following IPA masters "
- "found: %s. Removing any replication agreements before "
- "uninstalling the server is strongly recommended. You can "
- "remove replication agreements by running the following "
- "command on any other IPA master:\n" % ", ".join(
- other_masters)
- )
- cmd = "$ ipa-replica-manage del %s\n" % api.env.host
- print(textwrap.fill(msg, width=80, replace_whitespace=False))
- print(cmd)
- if (installer.interactive and
- not user_input("Are you sure you want to continue with the "
- "uninstall procedure?", False)):
- print("")
- print("Aborting uninstall operation.")
+ if domain_level == constants.DOMAIN_LEVEL_0:
+ if options.ignore_topology_disconnect:
+ print("'--ignore-topology-disconnect' option can not be used "
+ "in domain level {0}".format(constants.DOMAIN_LEVEL_0))
sys.exit(1)
+ rm = replication.ReplicationManager(
+ realm=api.env.realm,
+ hostname=api.env.host,
+ dirman_passwd=None,
+ conn=conn
+ )
+ agreements = rm.find_ipa_replication_agreements()
+
+ if agreements:
+ other_masters = [a.get('cn')[0][4:] for a in agreements]
+ msg = (
+ "\nReplication agreements with the following IPA masters "
+ "found: %s. Removing any replication agreements before "
+ "uninstalling the server is strongly recommended. You can "
+ "remove replication agreements by running the following "
+ "command on any other IPA master:\n" % ", ".join(
+ other_masters)
+ )
+ cmd = "$ ipa-replica-manage del %s\n" % api.env.host
+ print(textwrap.fill(msg, width=80, replace_whitespace=False))
+ print(cmd)
+ if (installer.interactive and
+ not user_input("Are you sure you want to continue with"
+ " the uninstall procedure?", False)):
+ print("")
+ print("Aborting uninstall operation.")
+ sys.exit(1)
+ else:
+ masters = api.Command.server_find(sizelimit=0)['result']
+
+ if not check_master_deleted(api, masters,
+ not options.unattended):
+ print("WARNING: This IPA master is still a part of the "
+ "replication topology.")
+ print("To properly remove the master entry and clean "
+ "up related segments, run:")
+ print(" $ ipa-replica-manage del {0}".format(api.env.host))
+ if (not options.unattended and not user_input(
+ "Do you want to continue uninstallation?", False)):
+ print("Aborted")
+ sys.exit(1)
+
+ if not options.ignore_topology_disconnect:
+ check_topology_connectivity(api, masters)
+ else:
+ print("Ignoring topology errors and forcing uninstall")
+
+
installer._fstore = fstore
installer._sstore = sstore
@@ -1227,6 +1355,12 @@ class Server(BaseServer):
cli_name='no_hbac_allow',
)
+ ignore_topology_disconnect = Knob(
+ bool, False,
+ description="do not check whether server uninstall disconnects the "
+ "topology (domain level 1+)",
+ )
+
# ca
skip_schema_check = None
@@ -1272,6 +1406,11 @@ class Server(BaseServer):
"You must specify at least one of --forwarder, "
"--auto-forwarders, or --no-forwarders options")
+ if self.ignore_topology_disconnect and not self.uninstalling:
+ raise RuntimeError(
+ "'--ignore-topology-disconnect' can be used only during "
+ "uninstallation")
+
if self.idmax < self.idstart:
raise RuntimeError(
"idmax (%s) cannot be smaller than idstart (%s)" %