diff options
author | Martin Babinsky <mbabinsk@redhat.com> | 2016-06-08 18:16:24 +0200 |
---|---|---|
committer | Martin Basti <mbasti@redhat.com> | 2016-06-17 18:55:19 +0200 |
commit | d8ae2b4055284de8c1baf76819d6611978f83cc6 (patch) | |
tree | 58d295c0fd771af79b06160fb845de92d33d3562 /ipaserver/topology.py | |
parent | 45bb2ad045654c020fe6ac4e77ed2741cd35d717 (diff) | |
download | freeipa-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.py | 195 |
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) |