freeipa/ipaserver/topology.py
Martin Babinsky 8ef4888af7 fix incorrect suffix handling in topology checks
When trying to delete a partially removed master entry lacking
'iparepltopomanagedsuffix' attribute, the code that tries to retrieve
tha value for further computations passes None and causes unhandled
internal errors.

If the attribute is empty or not present, we should return empty list
instead as to not break calling cod attribute, the code that tries to
retrieve tha value for further computations passes None and causes
unhandled internal errors. We should return empty list instead.

https://pagure.io/freeipa/issue/6965

Reviewed-By: Felipe Volpone <felipevolpone@gmail.com>
2017-06-05 18:37:37 +02:00

199 lines
5.4 KiB
Python

#
# 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 = {}
managed_suffix_attr = 'iparepltopomanagedsuffix_topologysuffix'
for master in masters:
if managed_suffix_attr not in master:
continue
managed_suffixes = master[managed_suffix_attr]
if managed_suffixes is None:
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)