On 06/12/2015 04:18 PM, Petr Vobornik wrote:
Some notes:

1. As mentioned in the WIP patch thread: original 'del' worked also with
winsync agreements. I'm not sure why is that. Shouldn't 'disconnect' be
used for winsync agreements? At least man page says that. This patch
doesn't support it if domain level > 0. Is it a blocker?

Following should be addressed in beta:

2. If `ipa-replica-manage del` is run before `ipa-csreplica-manage del`
then the `ipa-csreplica-manage del` will fail unless run with --force
options.

3. Check for orphaned server is missing. I want to use proper graph
traversing algorithm for that given that we have the whole topology.

4. Probably a work for topology plugin: I've seen that the removed
master doesn't remove its segments and agreements even though that it
knows about its removal (doesn't have its own entry in cn=masters). It
leads to failed replication connection attempts. Not a big issue, but
also not wanted.



Martin3 found that there is wrong hostname in one error message. Fixed. Patch 873 rebased.
--
Petr Vobornik
From 4456f0a8b515a2b1db1b0e1a0725394150a1dce4 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 11 Jun 2015 15:38:32 +0200
Subject: [PATCH] server: add "del" command

this command is internal and is supposed to be used by ipa-replica-managed to
delete replica.
---
 API.txt                  | 8 ++++++++
 VERSION                  | 4 ++--
 ipalib/plugins/server.py | 7 +++++++
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/API.txt b/API.txt
index 853d26a59bb5bb1ebff698924a36a30b7757c398..ff53e9457ebaa36004556feebd88515aea2a7a8d 100644
--- a/API.txt
+++ b/API.txt
@@ -3799,6 +3799,14 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: server_del
+args: 1,2,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=True, primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
 command: server_find
 args: 1,10,4
 arg: Str('criteria?', noextrawhitespace=False)
diff --git a/VERSION b/VERSION
index 741d50f2d9b7e564b6a480c73a378500e8d1aca1..2a835122143aa3a2e7c02a888f638ce5e5fcdf83 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=132
-# Last change: dkupka: User life cycle permissions naming and split
+IPA_API_VERSION_MINOR=133
+# Last change: pvoborni - add server-del internal command
diff --git a/ipalib/plugins/server.py b/ipalib/plugins/server.py
index d22f1ea368ad09ab2cff00429f509c99d92f0f60..7fc44197343dbb651782fbf79993cbbe8818efed 100644
--- a/ipalib/plugins/server.py
+++ b/ipalib/plugins/server.py
@@ -87,3 +87,10 @@ class server_find(LDAPSearch):
 @register()
 class server_show(LDAPRetrieve):
     __doc__ = _('Show IPA server.')
+
+
+@register()
+class server_del(LDAPDelete):
+    __doc__ = _('Delete IPA server.')
+    NO_CLI = True
+    msg_summary = _('Deleted IPA server "%(value)s"')
-- 
2.1.0

From 402bd94281fd3d654d019f18536e09cdbb8e9781 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 12 Jun 2015 15:56:30 +0200
Subject: [PATCH] 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
---
 install/tools/ipa-replica-manage | 228 ++++++++++++++++++++++++++++-----------
 1 file changed, 165 insertions(+), 63 deletions(-)

diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index a2b2c820d8e25a2587358e00dc4afc54b309d77b..1c82a6aa6f2c014f2238626435c5002c6b823bab 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -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
@@ -560,6 +561,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:
@@ -567,8 +575,160 @@ 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:
+        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 == hostname:
+            this_services = services_cns
+        else:
+            other_services.append(services_cns)
+            if ca_hostname is None and 'CA' in services_cns:
+                ca_hostname = master
+
+    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
 
@@ -647,10 +807,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" % (
@@ -668,53 +826,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."
 
@@ -749,19 +863,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):
 
-- 
2.1.0

-- 
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