mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-25 16:31:08 -06:00
ipa-replica-manage: adjust del to work with managed topology
Introduces new method for deletion of replica. This method is used if managed topology is enabled. part of https://fedorahosted.org/freeipa/ticket/4302 Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
This commit is contained in:
parent
d58bdf29a5
commit
e9e4509b10
@ -25,6 +25,7 @@ import traceback
|
||||
from urllib2 import urlparse
|
||||
import ldap
|
||||
import socket
|
||||
import time
|
||||
|
||||
from ipapython import ipautil
|
||||
from ipaserver.install import replication, dsinstance, installutils
|
||||
@ -564,6 +565,13 @@ def check_last_link(delrepl, realm, dirman_passwd, force):
|
||||
else:
|
||||
return None
|
||||
|
||||
def check_last_link_managed(api, masters, hostname, force):
|
||||
# segments = api.Command.topologysegment_find(u'realm', sizelimit=0).get('result')
|
||||
# replica_names = [m.single_value('cn') for m in masters]
|
||||
# orphaned = []
|
||||
# TODO add proper graph traversing algorithm here
|
||||
return None
|
||||
|
||||
def enforce_host_existence(host, message=None):
|
||||
if host is not None and not ipautil.host_exists(host):
|
||||
if message is None:
|
||||
@ -571,8 +579,161 @@ def enforce_host_existence(host, message=None):
|
||||
|
||||
sys.exit(message)
|
||||
|
||||
def ensure_last_services(conn, hostname, masters, options):
|
||||
"""
|
||||
1. When deleting master, check if there will be at least one remaining
|
||||
DNS and CA server.
|
||||
2. Pick CA renewal master
|
||||
|
||||
Return this_services, other_services, ca_hostname
|
||||
"""
|
||||
|
||||
this_services = []
|
||||
other_services = []
|
||||
ca_hostname = None
|
||||
|
||||
for master in masters:
|
||||
master_cn = master['cn'][0]
|
||||
try:
|
||||
services = conn.get_entries(master['dn'], conn.SCOPE_ONELEVEL)
|
||||
except errors.NotFound:
|
||||
continue
|
||||
services_cns = [s.single_value['cn'] for s in services]
|
||||
if master_cn == hostname:
|
||||
this_services = services_cns
|
||||
else:
|
||||
other_services.append(services_cns)
|
||||
if ca_hostname is None and 'CA' in services_cns:
|
||||
ca_hostname = master_cn
|
||||
|
||||
if 'CA' in this_services and not any(['CA' in o for o in other_services]):
|
||||
print "Deleting this server is not allowed as it would leave your installation without a CA."
|
||||
sys.exit(1)
|
||||
|
||||
other_dns = True
|
||||
if 'DNS' in this_services and not any(['DNS' in o for o in other_services]):
|
||||
other_dns = False
|
||||
print "Deleting this server will leave your installation without a DNS."
|
||||
if not options.force and not ipautil.user_input("Continue to delete?", False):
|
||||
sys.exit("Deletion aborted")
|
||||
|
||||
# test if replica is not DNSSEC master
|
||||
# allow to delete it if is last DNS server
|
||||
if 'DNS' in this_services and other_dns and not options.force:
|
||||
dnssec_masters = opendnssecinstance.get_dnssec_key_masters(conn)
|
||||
if hostname in dnssec_masters:
|
||||
print "Replica is active DNSSEC key master. Uninstall could break your DNS system."
|
||||
sys.exit("Deletion aborted")
|
||||
|
||||
ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
|
||||
if ca.is_renewal_master(hostname):
|
||||
try:
|
||||
ca.set_renewal_master(options.host)
|
||||
except errors.NotFound:
|
||||
ca.set_renewal_master(ca_hostname)
|
||||
|
||||
return this_services, other_services, ca_hostname
|
||||
|
||||
|
||||
def cleanup_server_dns_entries(realm, hostname, suffix, options):
|
||||
try:
|
||||
if bindinstance.dns_container_exists(options.host, suffix,
|
||||
dm_password=options.dirman_passwd):
|
||||
bind = bindinstance.BindInstance()
|
||||
bind.remove_master_dns_records(hostname, realm, realm.lower())
|
||||
bind.remove_ipa_ca_dns_records(hostname, realm.lower())
|
||||
bind.remove_server_ns_records(hostname)
|
||||
|
||||
keysyncd = dnskeysyncinstance.DNSKeySyncInstance()
|
||||
keysyncd.remove_replica_public_keys(hostname)
|
||||
except Exception, e:
|
||||
print "Failed to cleanup %s DNS entries: %s" % (hostname, e)
|
||||
print "You may need to manually remove them from the tree"
|
||||
|
||||
|
||||
def del_master(realm, hostname, options):
|
||||
|
||||
if has_managed_topology():
|
||||
del_master_managed(realm, hostname, options)
|
||||
else:
|
||||
del_master_direct(realm, hostname, options)
|
||||
|
||||
def del_master_managed(realm, hostname, options):
|
||||
"""
|
||||
Removing of master in managed_topology
|
||||
"""
|
||||
|
||||
hostname_u = unicode(hostname)
|
||||
if hostname == options.host:
|
||||
print "Can't remove itself: %s" % (options.host)
|
||||
sys.exit(1)
|
||||
|
||||
# 1. Connect to the local server
|
||||
try:
|
||||
thisrepl = replication.ReplicationManager(realm, options.host,
|
||||
options.dirman_passwd)
|
||||
except Exception as e:
|
||||
print "Failed to connect to server %s: %s" % (options.host, e)
|
||||
sys.exit(1)
|
||||
|
||||
# 2. Get all masters
|
||||
masters = api.Command.server_find('', sizelimit=0)['result']
|
||||
|
||||
# 3. Check topology
|
||||
orphans = check_last_link_managed(api, masters, hostname, options.force)
|
||||
|
||||
# 4. Check that we are not leaving the installation without CA and/or DNS
|
||||
# And pick new CA master.
|
||||
ensure_last_services(api.Backend.ldap2, hostname, masters, options)
|
||||
|
||||
# Save the RID value before we start deleting
|
||||
rid = get_rid_by_host(realm, options.host, hostname,
|
||||
options.dirman_passwd, options.nolookup)
|
||||
|
||||
# 5. Remove master entry. Topology plugin will remove replication agreements.
|
||||
try:
|
||||
api.Command.server_del(hostname_u)
|
||||
except errors.NotFound:
|
||||
print "Server entry already deleted: %s" % (hostname)
|
||||
|
||||
# 6. Cleanup
|
||||
try:
|
||||
thisrepl.replica_cleanup(hostname, realm, force=True)
|
||||
except Exception, e:
|
||||
print "Failed to cleanup %s entries: %s" % (hostname, e)
|
||||
print "You may need to manually remove them from the tree"
|
||||
|
||||
# 7. Clean RUV for the deleted master
|
||||
# Wait for topology plugin to delete segments
|
||||
i = 0
|
||||
while True:
|
||||
left = api.Command.topologysegment_find(
|
||||
u'realm', iparepltoposegmentleftnode=hostname_u, sizelimit=0)['result']
|
||||
right = api.Command.topologysegment_find(
|
||||
u'realm', iparepltoposegmentrightnode=hostname_u, sizelimit=0)['result']
|
||||
if not left and not right:
|
||||
print "Agreements deleted"
|
||||
break
|
||||
time.sleep(1)
|
||||
if i == 5: # taking too long, something is wrong, report
|
||||
print "Waiting for removal of replication agreements"
|
||||
i += 1
|
||||
|
||||
# Clean RUV
|
||||
if rid is not None:
|
||||
try:
|
||||
thisrepl.cleanallruv(rid)
|
||||
except KeyboardInterrupt:
|
||||
print "Wait for task interrupted. It will continue to run in the background"
|
||||
|
||||
# 8. And clean up the removed replica DNS entries if any.
|
||||
cleanup_server_dns_entries(realm, hostname, thisrepl.suffix, options)
|
||||
|
||||
def del_master_direct(realm, hostname, options):
|
||||
"""
|
||||
Removing of master for realm without managed topology (domain level < 1)
|
||||
"""
|
||||
|
||||
force_del = False
|
||||
delrepl = None
|
||||
|
||||
@ -651,10 +812,8 @@ def del_master(realm, hostname, options):
|
||||
|
||||
# Check for orphans if the remote server is up.
|
||||
if delrepl and not winsync:
|
||||
masters_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm))
|
||||
try:
|
||||
masters = delrepl.conn.get_entries(
|
||||
masters_dn, delrepl.conn.SCOPE_ONELEVEL)
|
||||
masters = api.Command.server_find('', sizelimit=0)['result']
|
||||
except Exception, e:
|
||||
masters = []
|
||||
print "Failed to read masters data from '%s': %s" % (
|
||||
@ -672,53 +831,9 @@ def del_master(realm, hostname, options):
|
||||
print "You will need to reconfigure your replication topology to delete this server."
|
||||
sys.exit(1)
|
||||
|
||||
# Check that we are not leaving the installation without CA and/or DNS
|
||||
this_services = []
|
||||
other_services = []
|
||||
ca_hostname = None
|
||||
|
||||
for master_cn in [m.single_value['cn'] for m in masters]:
|
||||
master_dn = DN(('cn', master_cn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm))
|
||||
try:
|
||||
services = delrepl.conn.get_entries(master_dn,
|
||||
delrepl.conn.SCOPE_ONELEVEL)
|
||||
except errors.NotFound:
|
||||
continue
|
||||
services_cns = [s.single_value['cn'] for s in services]
|
||||
|
||||
if master_cn == hostname:
|
||||
this_services = services_cns
|
||||
else:
|
||||
other_services.append(services_cns)
|
||||
if ca_hostname is None and 'CA' in services_cns:
|
||||
ca_hostname = master_cn
|
||||
|
||||
if 'CA' in this_services and not any(['CA' in o for o in other_services]):
|
||||
print "Deleting this server is not allowed as it would leave your installation without a CA."
|
||||
sys.exit(1)
|
||||
|
||||
other_dns = True
|
||||
if 'DNS' in this_services and not any(['DNS' in o for o in other_services]):
|
||||
other_dns = False
|
||||
print "Deleting this server will leave your installation without a DNS."
|
||||
if not options.force and not ipautil.user_input("Continue to delete?", False):
|
||||
sys.exit("Deletion aborted")
|
||||
|
||||
# test if replica is not DNSSEC master
|
||||
# allow to delete it if is last DNS server
|
||||
if 'DNS' in this_services and other_dns and not options.force:
|
||||
dnssec_masters = opendnssecinstance.get_dnssec_key_masters(delrepl.conn)
|
||||
if hostname in dnssec_masters:
|
||||
print "Replica is active DNSSEC key master. Uninstall could break your DNS system."
|
||||
sys.exit("Deletion aborted")
|
||||
|
||||
# Pick CA renewal master
|
||||
ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
|
||||
if ca.is_renewal_master(hostname):
|
||||
try:
|
||||
ca.set_renewal_master(options.host)
|
||||
except errors.NotFound:
|
||||
ca.set_renewal_master(ca_hostname)
|
||||
# 4. Check that we are not leaving the installation without CA and/or DNS
|
||||
# And pick new CA master.
|
||||
ensure_last_services(thisrepl.conn, hostname, masters, options)
|
||||
else:
|
||||
print "Skipping calculation to determine if one or more masters would be orphaned."
|
||||
|
||||
@ -753,19 +868,7 @@ def del_master(realm, hostname, options):
|
||||
print "You may need to manually remove them from the tree"
|
||||
|
||||
# 7. And clean up the removed replica DNS entries if any.
|
||||
try:
|
||||
if bindinstance.dns_container_exists(options.host, thisrepl.suffix,
|
||||
dm_password=options.dirman_passwd):
|
||||
bind = bindinstance.BindInstance()
|
||||
bind.remove_master_dns_records(hostname, realm, realm.lower())
|
||||
bind.remove_ipa_ca_dns_records(hostname, realm.lower())
|
||||
bind.remove_server_ns_records(hostname)
|
||||
|
||||
keysyncd = dnskeysyncinstance.DNSKeySyncInstance()
|
||||
keysyncd.remove_replica_public_keys(hostname)
|
||||
except Exception, e:
|
||||
print "Failed to cleanup %s DNS entries: %s" % (hostname, e)
|
||||
print "You may need to manually remove them from the tree"
|
||||
cleanup_server_dns_entries(realm, hostname, thisrepl.suffix, options)
|
||||
|
||||
def add_link(realm, replica1, replica2, dirman_passwd, options):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user