mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2026-07-29 15:55:47 -05:00
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>
This commit is contained in:
committed by
Martin Basti
parent
45bb2ad045
commit
d8ae2b4055
@@ -45,7 +45,6 @@ from ipapython.ssh import SSHPublicKey
|
|||||||
from ipapython.dn import DN, RDN
|
from ipapython.dn import DN, RDN
|
||||||
from ipapython.dnsutil import DNSName
|
from ipapython.dnsutil import DNSName
|
||||||
from ipapython.dnsutil import resolve_ip_addresses
|
from ipapython.dnsutil import resolve_ip_addresses
|
||||||
from ipapython.graph import Graph
|
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
unicode = str
|
unicode = str
|
||||||
@@ -765,55 +764,6 @@ def validate_idna_domain(value):
|
|||||||
raise ValueError(error)
|
raise ValueError(error)
|
||||||
|
|
||||||
|
|
||||||
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 detect_dns_zone_realm_type(api, domain):
|
def detect_dns_zone_realm_type(api, domain):
|
||||||
"""
|
"""
|
||||||
Detects the type of the realm that the given DNS zone belongs to.
|
Detects the type of the realm that the given DNS zone belongs to.
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ import ldap
|
|||||||
|
|
||||||
from ipalib import api, errors
|
from ipalib import api, errors
|
||||||
from ipalib.constants import CACERT
|
from ipalib.constants import CACERT
|
||||||
from ipalib.util import create_topology_graph, get_topology_connection_errors
|
from ipaserver.topology import (
|
||||||
|
create_topology_graph, get_topology_connection_errors)
|
||||||
from ipapython.ipa_log_manager import root_logger
|
from ipapython.ipa_log_manager import root_logger
|
||||||
from ipapython import ipautil, ipaldap
|
from ipapython import ipautil, ipaldap
|
||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ from .baseldap import (
|
|||||||
from ipalib import _, ngettext
|
from ipalib import _, ngettext
|
||||||
from ipalib import output
|
from ipalib import output
|
||||||
from ipalib.constants import DOMAIN_LEVEL_1
|
from ipalib.constants import DOMAIN_LEVEL_1
|
||||||
from ipalib.util import create_topology_graph, get_topology_connection_errors
|
from ipaserver.topology import (
|
||||||
|
create_topology_graph, get_topology_connection_errors)
|
||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
|
|||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user