Attaching a wip patch for `ipa-replica-manage del` to work with managed topology.

There are two prerequisite patches, they add following commands. All commands has NO_CLI flag which means they are hidden in CLI.
- server-del
- serverservice-add, mod, del, show, find

serverservice is object name for server "services" in cn=masters. I don't like the "service" name much but it's already been used in general discussions.

The main patch introduces two distinct methods for deleting servers, one for managed topology another for the old method. They share some code.

There are some differences in behavior.

1. the 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.

2. options --clean and --force aren't used in the new method. I don't think that they are required. They serve for deleting the server entry in cn=masters. The new method is build around this deletion so that it's always done which also means the cleanup is done.

3. Clean RUV task is run after deleting server entry and related cleanup. I don't think it works well. From observing the changes, it looks like it's executed before topology plugin manages to delete the agreements. This task then doesn't want to end and it reports that it has not finished somewhere. It finishes successfully if dirsrv is restarted. Agreements are then removed as well and all is fine.

Ludwig, should the clean RUV step be done differently? E.g. somewhere else or after something finishes?
--
Petr Vobornik
From b3229d3feab3584e7e6e948ae6ea8e11a0a91244 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 11 Jun 2015 16:12:37 +0200
Subject: [PATCH 748/748] ipa-replica-manage: adjust del to work with managed
 topology

---
 install/tools/ipa-replica-manage | 205 +++++++++++++++++++++++++++------------
 1 file changed, 142 insertions(+), 63 deletions(-)

diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index a2b2c820d8e25a2587358e00dc4afc54b309d77b..865c74b4c9a94732a00c5046c26c6a688196dcc1 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -567,8 +567,144 @@ def enforce_host_existence(host, message=None):
 
         sys.exit(message)
 
+def ensure_last_services(api, 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:
+        services = api.Command['serverservice_find'](master, sizelimit=0).get('result')
+        names = [s['cn'][0] for s in services]
+        if master == hostname:
+            this_services = names
+        else:
+            other_services.append(names)
+            if ca_hostname is None and 'CA' in names:
+                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(api.Backend.ldap2)
+        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
+    """
+
+    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']
+    masters = [m['cn'][0].lower() for m in masters]
+
+    # 3. Check topology
+    # TODO: Check for orphans by using graph from topology segments
+    #segments = api.Command.topologysegment_find(u'realm', sizelimit=0).get('result')
+
+    # 4. Check that we are not leaving the installation without CA and/or DNS
+    #    And pick new CA master.
+    ensure_last_services(api, 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(unicode(hostname))
+    except errors.NotFound:
+        print "Server entry already deleted: %s" % (options.host)
+
+    # 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
+    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 +783,9 @@ 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']
+            masters = [m['cn'][0].lower() for m in masters]
         except Exception, e:
             masters = []
             print "Failed to read masters data from '%s': %s" % (
@@ -668,53 +803,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(api, hostname, masters, options)
     else:
         print "Skipping calculation to determine if one or more masters would be orphaned."
 
@@ -749,19 +840,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

From 66a244d3ba31d12dce6843a6bca13806ed036aec Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 11 Jun 2015 15:40:38 +0200
Subject: [PATCH 747/748] add serverservice internal commands

---
 API.txt                  | 71 ++++++++++++++++++++++++++++++++++++++++++++++++
 ipalib/plugins/server.py | 62 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 133 insertions(+)

diff --git a/API.txt b/API.txt
index ff53e9457ebaa36004556feebd88515aea2a7a8d..9fcc02e8c8608e1e8062a99e7413e112eb7dd342 100644
--- a/API.txt
+++ b/API.txt
@@ -3834,6 +3834,77 @@ 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: serverservice_add
+args: 2,6,3
+arg: Str('servercn', cli_name='server', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='cn', multivalue=False, primary_key=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('ipaconfigstring', attribute=True, cli_name='ipaconfigstring', multivalue=True, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+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: serverservice_del
+args: 2,8,4
+arg: Str('servercn', cli_name='server', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='cn', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('ipaconfigstring', attribute=True, autofill=False, cli_name='ipaconfigstring', multivalue=True, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: serverservice_find
+args: 2,8,4
+arg: Str('servercn', cli_name='server', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='cn', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('ipaconfigstring', attribute=True, autofill=False, cli_name='ipaconfigstring', multivalue=True, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: serverservice_mod
+args: 2,8,3
+arg: Str('servercn', cli_name='server', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='cn', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('ipaconfigstring', attribute=True, autofill=False, cli_name='ipaconfigstring', multivalue=True, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+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: serverservice_show
+args: 2,4,3
+arg: Str('servercn', cli_name='server', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='cn', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+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: service_add
 args: 1,11,3
 arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=False, primary_key=True, required=True)
diff --git a/ipalib/plugins/server.py b/ipalib/plugins/server.py
index 7fc44197343dbb651782fbf79993cbbe8818efed..4ac006955740808b5f3eab6d7b6a44f7bb9c9055 100644
--- a/ipalib/plugins/server.py
+++ b/ipalib/plugins/server.py
@@ -94,3 +94,65 @@ class server_del(LDAPDelete):
     __doc__ = _('Delete IPA server.')
     NO_CLI = True
     msg_summary = _('Deleted IPA server "%(value)s"')
+
+
+@register()
+class serverservice(LDAPObject):
+    """
+    IPA server service
+    """
+    NO_CLI = True
+    parent_object = 'server'
+    container_dn = api.env.container_masters
+    object_name = _('serverservice')
+    object_name_plural = _('servers')
+    object_class = ['top', 'ipaconfigobject']
+    default_attributes = [
+        'cn', 'ipaconfigstring',
+    ]
+    takes_params = (
+        Str(
+            'cn',
+            primary_key=True,
+        ),
+        Str(
+            'ipaconfigstring*',
+        ),
+    )
+
+
+@register()
+class serverservice_find(LDAPSearch):
+    __doc__ = _('Search for IPA servers services.')
+    NO_CLI = True
+    msg_summary = ngettext(
+        '%(count)d IPA server service matched',
+        '%(count)d IPA server services matched', 0
+    )
+
+
+@register()
+class serverservice_show(LDAPRetrieve):
+    __doc__ = _('Show IPA server service.')
+    NO_CLI = True
+
+
+@register()
+class serverservice_add(LDAPCreate):
+    __doc__ = _('Add an IPA server service to be managed.')
+    NO_CLI = True
+    msg_summary = _('Added new server service "%(value)s"')
+
+
+@register()
+class serverservice_mod(LDAPUpdate):
+    __doc__ = _('Modify an IPA server service.')
+    NO_CLI = True
+    msg_summary = _('Modified IPA server service "%(value)s"')
+
+
+@register()
+class serverservice_del(LDAPSearch):
+    __doc__ = _('Delete IPA server service.')
+    NO_CLI = True
+    msg_summary = _('Deleted IPA server service "%(value)s"')
-- 
2.1.0

From c4d21821bcac6e1e1a54d4ca16870ddae8a45bfc Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 11 Jun 2015 15:38:32 +0200
Subject: [PATCH 746/748] server: add "del" command

---
 API.txt                  | 8 ++++++++
 ipalib/plugins/server.py | 7 +++++++
 2 files changed, 15 insertions(+)

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

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