summaryrefslogtreecommitdiffstats
path: root/ipaserver/topology.py
diff options
context:
space:
mode:
authorMartin Babinsky <mbabinsk@redhat.com>2016-06-08 18:16:24 +0200
committerMartin Basti <mbasti@redhat.com>2016-06-17 18:55:19 +0200
commitd8ae2b4055284de8c1baf76819d6611978f83cc6 (patch)
tree58d295c0fd771af79b06160fb845de92d33d3562 /ipaserver/topology.py
parent45bb2ad045654c020fe6ac4e77ed2741cd35d717 (diff)
downloadfreeipa-d8ae2b4055284de8c1baf76819d6611978f83cc6.tar.gz
freeipa-d8ae2b4055284de8c1baf76819d6611978f83cc6.tar.xz
freeipa-d8ae2b4055284de8c1baf76819d6611978f83cc6.zip
ipaserver module for working with managed topology
This module should aggregate common functionality utilized in the commands managing domain-level 1 topology. https://fedorahosted.org/freeipa/ticket/5588 Reviewed-By: Martin Basti <mbasti@redhat.com>
Diffstat (limited to 'ipaserver/topology.py')
-rw-r--r--ipaserver/topology.py195
1 files changed, 195 insertions, 0 deletions
diff --git a/ipaserver/topology.py b/ipaserver/topology.py
new file mode 100644
index 000000000..27c3b29a4
--- /dev/null
+++ b/ipaserver/topology.py
@@ -0,0 +1,195 @@
+#
+# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
+#
+
+"""
+set of functions and classes useful for management of domain level 1 topology
+"""
+
+from copy import deepcopy
+
+from ipalib import _
+from ipapython.graph import Graph
+
+CURR_TOPOLOGY_DISCONNECTED = _("""
+Replication topology in suffix '%(suffix)s' is disconnected:
+%(errors)s""")
+
+REMOVAL_DISCONNECTS_TOPOLOGY = _("""
+Removal of '%(hostname)s' leads to disconnected topology in suffix '%(suffix)s':
+%(errors)s""")
+
+
+def create_topology_graph(masters, segments):
+ """
+ Create an oriented graph from topology defined by masters and segments.
+
+ :param masters
+ :param segments
+ :returns: Graph
+ """
+ graph = Graph()
+
+ for m in masters:
+ graph.add_vertex(m['cn'][0])
+
+ for s in segments:
+ direction = s['iparepltoposegmentdirection'][0]
+ left = s['iparepltoposegmentleftnode'][0]
+ right = s['iparepltoposegmentrightnode'][0]
+ try:
+ if direction == u'both':
+ graph.add_edge(left, right)
+ graph.add_edge(right, left)
+ elif direction == u'left-right':
+ graph.add_edge(left, right)
+ elif direction == u'right-left':
+ graph.add_edge(right, left)
+ except ValueError: # ignore segments with deleted master
+ pass
+
+ return graph
+
+
+def get_topology_connection_errors(graph):
+ """
+ Traverse graph from each master and find out which masters are not
+ reachable.
+
+ :param graph: topology graph where vertices are masters
+ :returns: list of errors, error is: (master, visited, not_visited)
+ """
+ connect_errors = []
+ master_cns = list(graph.vertices)
+ master_cns.sort()
+ for m in master_cns:
+ visited = graph.bfs(m)
+ not_visited = graph.vertices - visited
+ if not_visited:
+ connect_errors.append((m, list(visited), list(not_visited)))
+ return connect_errors
+
+
+def _map_masters_to_suffixes(masters):
+ masters_to_suffix = {}
+
+ for master in masters:
+ try:
+ managed_suffixes = master.get(
+ 'iparepltopomanagedsuffix_topologysuffix')
+ except KeyError:
+ continue
+
+ for suffix_name in managed_suffixes:
+ try:
+ masters_to_suffix[suffix_name].append(master)
+ except KeyError:
+ masters_to_suffix[suffix_name] = [master]
+
+ return masters_to_suffix
+
+
+def _create_topology_graphs(api_instance):
+ """
+ Construct a topology graph for each topology suffix
+ :param api_instance: instance of IPA API
+ """
+ masters = api_instance.Command.server_find(
+ u'', sizelimit=0, no_members=False)['result']
+
+ suffix_to_masters = _map_masters_to_suffixes(masters)
+
+ topology_graphs = {}
+
+ for suffix_name in suffix_to_masters:
+ segments = api_instance.Command.topologysegment_find(
+ suffix_name, sizelimit=0).get('result')
+
+ topology_graphs[suffix_name] = create_topology_graph(
+ suffix_to_masters[suffix_name], segments)
+
+ return topology_graphs
+
+
+def _format_topology_errors(topo_errors):
+ msg_lines = []
+ for error in topo_errors:
+ msg_lines.append(
+ _("Topology does not allow server %(server)s to replicate with "
+ "servers:")
+ % {'server': error[0]}
+ )
+ for srv in error[2]:
+ msg_lines.append(" %s" % srv)
+
+ return "\n".join(msg_lines)
+
+
+class TopologyConnectivity(object):
+ """
+ a simple class abstracting the replication connectivity in managed topology
+ """
+
+ def __init__(self, api_instance):
+ self.api = api_instance
+
+ self.graphs = _create_topology_graphs(self.api)
+
+ @property
+ def errors(self):
+ errors_by_suffix = {}
+ for suffix in self.graphs:
+ errors_by_suffix[suffix] = get_topology_connection_errors(
+ self.graphs[suffix]
+ )
+
+ return errors_by_suffix
+
+ def errors_after_master_removal(self, master_cn):
+ graphs_before = deepcopy(self.graphs)
+
+ for s in self.graphs:
+ try:
+ self.graphs[s].remove_vertex(master_cn)
+ except ValueError:
+ pass
+
+ errors_after_removal = self.errors
+
+ self.graphs = graphs_before
+
+ return errors_after_removal
+
+ def check_current_state(self):
+ err_msg = ""
+ for suffix in self.errors:
+ errors = self.errors[suffix]
+ if errors:
+ err_msg = "\n".join([
+ err_msg,
+ CURR_TOPOLOGY_DISCONNECTED % dict(
+ suffix=suffix,
+ errors=_format_topology_errors(errors)
+ )])
+
+ if err_msg:
+ raise ValueError(err_msg)
+
+ def check_state_after_removal(self, master_cn):
+ err_msg = ""
+ errors_after_removal = self.errors_after_master_removal(master_cn)
+
+ for suffix in errors_after_removal:
+ errors = errors_after_removal[suffix]
+ if errors:
+ err_msg = "\n".join([
+ err_msg,
+ REMOVAL_DISCONNECTS_TOPOLOGY % dict(
+ hostname=master_cn,
+ suffix=suffix,
+ errors=_format_topology_errors(errors)
+ )
+ ])
+
+ if err_msg:
+ raise ValueError(err_msg)