With patch "878 topology: check topology in ipa-replica-manage del" we can use the same logic for POC of
  ipa topologysuffix-verify
command.

Checks done:
  1. check if the topology is not disconnected. In other words if
     there are replication paths between all servers.
  2. check if servers don't have more than a recommended number of
     replication agreements (which was set to 4)

I'm not sure what else we want to test but these two seemed as low hanging fruit. Additional checks can be also added later.

https://fedorahosted.org/freeipa/ticket/4302
--
Petr Vobornik
From 60360ec4c1c836859ff9b79ecc5670f5ea47fc5a Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Wed, 17 Jun 2015 13:50:32 +0200
Subject: [PATCH] Verify replication topology for a suffix

Checks done:
  1. check if the topology is not disconnected. In other words if
     there are replication paths between all servers.
  2. check if servers don't have more than a recommended number of
     replication agreements(4)

https://fedorahosted.org/freeipa/ticket/4302
---
 API.txt                    |  5 +++
 VERSION                    |  4 +--
 ipalib/constants.py        |  4 +++
 ipalib/plugins/topology.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 94 insertions(+), 2 deletions(-)

diff --git a/API.txt b/API.txt
index c7f02b9e6d120348ee90a3e6ff34646728e2b332..f072210230ffc30c0995945a423565ab06b87675 100644
--- a/API.txt
+++ b/API.txt
@@ -4911,6 +4911,11 @@ 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: topologysuffix_verify
+args: 1,1,1
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
 command: trust_add
 args: 1,13,3
 arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, required=True)
diff --git a/VERSION b/VERSION
index f38b4946553aecea1a458f9cf57387e93929fef5..fd3c76446ec5b180356da03ea916df09abb7b028 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=134
-# Last change: jcholast - User life cycle: provide preserved user virtual attribute
+IPA_API_VERSION_MINOR=135
+# Last change: pvoborni: add topologysuffix-verify command
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 330f9df74e604d98759999a7a9624312ea8944d5..a062505c349436332d430af4fd29c76d20c85343 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -170,6 +170,10 @@ DEFAULT_CONFIG = (
     # KRA plugin
     ('kra_host', FQDN),  # Set in Env._finalize_core()
 
+    # Topology plugin
+    ('recommended_max_agmts', 4),  # Recommended maximum number of replication
+                                   # agreements
+
     # Special CLI:
     ('prompt_all', False),
     ('interactive', True),
diff --git a/ipalib/plugins/topology.py b/ipalib/plugins/topology.py
index 494d3bb0a564e5c8ef3d7c2af50dbf1e83a36e1f..49060d672b6522277014b0b9c1e0ecb92e091077 100644
--- a/ipalib/plugins/topology.py
+++ b/ipalib/plugins/topology.py
@@ -10,6 +10,7 @@ from ipalib.plugins.baseldap import (
     LDAPRetrieve)
 from ipalib import _, ngettext
 from ipalib import output
+from ipalib.util import create_topology_graph, get_topology_connection_errors
 from ipapython.dn import DN
 
 
@@ -401,3 +402,85 @@ class topologysuffix_mod(LDAPUpdate):
 @register()
 class topologysuffix_show(LDAPRetrieve):
     __doc__ = _('Show managed suffix.')
+
+
+@register()
+class topologysuffix_verify(LDAPQuery):
+    __doc__ = _('''
+Verify replication topology for suffix.
+
+Checks done:
+  1. check if a topology is not disconnected. In other words if there are
+     replication paths between all servers.
+  2. check if servers don't have more than the recommended number of
+     replication agreements
+''')
+
+    def execute(self, *keys, **options):
+
+        validate_domain_level(self.api)
+
+        masters = self.api.Command.server_find('', sizelimit=0)['result']
+        segments = self.api.Command.topologysegment_find(
+            keys[0], sizelimit=0)['result']
+        graph = create_topology_graph(masters, segments)
+        master_cns = [m['cn'][0] for m in masters]
+        master_cns.sort()
+
+        # check if each master can contact others
+        connect_errors = get_topology_connection_errors(graph)
+
+        # check if suggested maximum number of agreements per replica
+        max_agmts_errors = []
+        for m in master_cns:
+            # chosen direction doesn't matter much given that 'both' is the
+            # only allowed direction
+            suppliers = graph.get_tails(m)
+            if len(suppliers) > self.api.env.recommended_max_agmts:
+                max_agmts_errors.append((m, suppliers))
+
+        return dict(
+            result={
+                'in_order': not connect_errors and not max_agmts_errors,
+                'connect_errors': connect_errors,
+                'max_agmts_errors': max_agmts_errors,
+                'max_agmts': self.api.env.recommended_max_agmts
+            },
+        )
+
+    def output_for_cli(self, textui, output, *args, **options):
+
+        in_order = output['result']['in_order']
+        connect_errors = output['result']['connect_errors']
+        max_agmts_errors = output['result']['max_agmts_errors']
+
+        if in_order:
+            header = _('Replication topology of suffix "%(suffix)s" '
+                       'is in order.')
+        else:
+            header = _('Replication topology of suffix "%(suffix)s" contains '
+                       'errors.')
+        textui.print_h1(header % {'suffix': args[0]})
+
+        if connect_errors:
+            textui.print_dashed(unicode(_('Topology is disconnected')))
+            for err in connect_errors:
+                msg = _("Server %(srv)s can't contact servers: %(replicas)s")
+                msg = msg % {'srv': err[0], 'replicas': ', '.join(err[2])}
+                textui.print_indented(msg)
+
+        if max_agmts_errors:
+            textui.print_dashed(unicode(_('Recommended maximum number of '
+                                          'agreements per replica exceeded')))
+            textui.print_attribute(
+                unicode(_("Maximum number of agreements per replica")),
+                [output['result']['max_agmts']]
+            )
+            for err in max_agmts_errors:
+                msg = _('Server "%(srv)s" has %(n)d agreements with servers:')
+                msg = msg % {'srv': err[0], 'n': len(err[1])}
+                textui.print_indented(msg)
+                for replica in err[1]:
+                    textui.print_indented(replica, 2)
+
+        return 0
-- 
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