1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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)
|