Merge lp:~allenap/maas/ntp-prefer-nearby-servers--bug-1675095 into lp:~maas-committers/maas/trunk
- ntp-prefer-nearby-servers--bug-1675095
- Merge into trunk
Proposed by
Gavin Panella
Status: | Superseded |
---|---|
Proposed branch: | lp:~allenap/maas/ntp-prefer-nearby-servers--bug-1675095 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
549 lines (+311/-61) 6 files modified
src/maasserver/dbviews.py (+19/-2) src/maasserver/ntp.py (+5/-4) src/maasserver/routablepairs.py (+12/-0) src/maasserver/tests/test_dbviews.py (+91/-22) src/maasserver/tests/test_ntp.py (+54/-32) src/maasserver/tests/test_routablepairs.py (+130/-1) |
To merge this branch: | bzr merge lp:~allenap/maas/ntp-prefer-nearby-servers--bug-1675095 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andres Rodriguez (community) | Needs Fixing | ||
Review via email: mp+321267@code.launchpad.net |
Commit message
Calculated routes between nodes are now prioritised.
Description of the change
This is necessarily hand-wavy because it's nearly impossible to anticipate the cost exactly. Instead a cost is calculated based on a few expectations:
- Sending traffic within a single host is fastest.
- Sending traffic within the same subnet is next.
- Then, sending traffic within the same VLAN.
- Then, sending traffic within the same space.
- Then, sending traffic within the null space.
These costs are relative to one another only, and they do not convey relative _magnitudes_ of cost. They are useful for ordering only.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/dbviews.py' |
2 | --- src/maasserver/dbviews.py 2017-03-07 16:26:47 +0000 |
3 | +++ src/maasserver/dbviews.py 2017-03-29 15:49:17 +0000 |
4 | @@ -36,6 +36,7 @@ |
5 | with closing(connection.cursor()) as cursor: |
6 | cursor.execute(view_sql) |
7 | |
8 | + |
9 | # Note that the `Discovery` model object is backed by this view. Any |
10 | # changes made to this view should be reflected there. |
11 | maasserver_discovery = dedent("""\ |
12 | @@ -102,17 +103,33 @@ |
13 | # should not be used without constraining, say, the sets of nodes, to find |
14 | # addresses that are mutually routable between region controllers for example. |
15 | maasserver_routable_pairs = dedent("""\ |
16 | - SELECT if_left.node_id AS left_node_id, |
17 | + SELECT |
18 | + -- "Left" node. |
19 | + if_left.node_id AS left_node_id, |
20 | if_left.id AS left_interface_id, |
21 | subnet_left.id AS left_subnet_id, |
22 | vlan_left.id AS left_vlan_id, |
23 | sip_left.ip AS left_ip, |
24 | + |
25 | + -- "Right" node. |
26 | if_right.node_id AS right_node_id, |
27 | if_right.id AS right_interface_id, |
28 | subnet_right.id AS right_subnet_id, |
29 | vlan_right.id AS right_vlan_id, |
30 | sip_right.ip AS right_ip, |
31 | - vlan_left.space_id AS space_id |
32 | + |
33 | + -- Space that left and right have in commmon. Can be NULL. |
34 | + vlan_left.space_id AS space_id, |
35 | + |
36 | + -- Relative metric; lower is better. |
37 | + CASE |
38 | + WHEN if_left.node_id = if_right.node_id THEN 0 |
39 | + WHEN subnet_left.id = subnet_right.id THEN 1 |
40 | + WHEN vlan_left.id = vlan_right.id THEN 2 |
41 | + WHEN vlan_left.space_id IS NOT NULL THEN 3 |
42 | + ELSE 4 -- The NULL space. |
43 | + END AS metric |
44 | + |
45 | FROM maasserver_interface AS if_left |
46 | JOIN maasserver_interface_ip_addresses AS ifia_left |
47 | ON if_left.id = ifia_left.interface_id |
48 | |
49 | === modified file 'src/maasserver/ntp.py' |
50 | --- src/maasserver/ntp.py 2016-10-03 20:10:53 +0000 |
51 | +++ src/maasserver/ntp.py 2017-03-29 15:49:17 +0000 |
52 | @@ -124,8 +124,9 @@ |
53 | routable_addrs_map: AddressMap) -> Iterable[IPAddress]: |
54 | """Choose one routable address per destination node. |
55 | |
56 | - The result is stable, preferring IPv6 over IPv4, and then the highest |
57 | - address numberically. (In fact it relies on the ordering provided by |
58 | - `netaddr.IPAddress`, which is basically that.) |
59 | + The addresses are in preference order (see `find_addresses_between_nodes` |
60 | + for information on how that's derived) so this simply chooses the first. |
61 | """ |
62 | - return map(max, routable_addrs_map.values()) |
63 | + for addresses in routable_addrs_map.values(): |
64 | + assert len(addresses) >= 1 |
65 | + yield addresses[0] |
66 | |
67 | === modified file 'src/maasserver/routablepairs.py' |
68 | --- src/maasserver/routablepairs.py 2016-09-19 14:50:27 +0000 |
69 | +++ src/maasserver/routablepairs.py 2017-03-29 15:49:17 +0000 |
70 | @@ -24,6 +24,17 @@ |
71 | ``node-left`` and ``node-right`` are one of the nodes passed in and |
72 | ``addr-left`` and ``addr-right`` are :class:`netaddr.IPAddress` instances |
73 | corresponding to an active address on their respective nodes. |
74 | + |
75 | + The results are sorted, lowest metric first, meaning that earlier |
76 | + addresses are likely to have a lower overall communications cost, lower |
77 | + latency, and so on. The order of preference is: |
78 | + |
79 | + - Same node |
80 | + - Same subnet |
81 | + - Same VLAN |
82 | + - Same space |
83 | + |
84 | + An explicitly defined space is preferred to the default / null space. |
85 | """ |
86 | nodes_left = {node.id: node for node in nodes_left} |
87 | nodes_right = {node.id: node for node in nodes_right} |
88 | @@ -48,6 +59,7 @@ |
89 | FROM maasserver_routable_pairs |
90 | WHERE left_node_id IN (%s) |
91 | AND right_node_id IN (%s) |
92 | + ORDER BY metric ASC |
93 | """) |
94 | |
95 | |
96 | |
97 | === modified file 'src/maasserver/tests/test_dbviews.py' |
98 | --- src/maasserver/tests/test_dbviews.py 2017-01-28 00:51:47 +0000 |
99 | +++ src/maasserver/tests/test_dbviews.py 2017-03-29 15:49:17 +0000 |
100 | @@ -10,6 +10,7 @@ |
101 | _ALL_VIEWS, |
102 | register_all_views, |
103 | ) |
104 | +from maasserver.models.subnet import Subnet |
105 | from maasserver.testing.factory import factory |
106 | from maasserver.testing.testcase import MAASServerTestCase |
107 | from testtools.matchers import HasLength |
108 | @@ -39,29 +40,97 @@ |
109 | cursor.execute("SELECT * from maasserver_routable_pairs") |
110 | self.assertThat(cursor.fetchall(), HasLength(0)) |
111 | |
112 | - def make_node_with_address(self, space, cidr): |
113 | + def make_node_with_address(self, cidr, space=None, vlan=None): |
114 | node = factory.make_Node() |
115 | iface = factory.make_Interface(node=node) |
116 | - subnet = factory.make_Subnet(space=space, cidr=cidr) |
117 | + try: |
118 | + subnet = Subnet.objects.get(cidr=cidr, vlan__space=space) |
119 | + except Subnet.DoesNotExist: |
120 | + subnet = factory.make_Subnet(cidr=cidr, space=space, vlan=vlan) |
121 | sip = factory.make_StaticIPAddress(interface=iface, subnet=subnet) |
122 | return node, iface, subnet, sip |
123 | |
124 | + def test__contains_routes_between_nodes_on_same_subnet(self): |
125 | + network = factory.make_ip4_or_6_network() |
126 | + node1, if1, sn1, sip1 = self.make_node_with_address(network) |
127 | + node2, if2, sn2, sip2 = self.make_node_with_address(network) |
128 | + |
129 | + # Routes between all addresses are found, even back to themselves. |
130 | + left = node1.id, if1.id, sn1.id, sn1.vlan.id, sip1.ip |
131 | + right = node2.id, if2.id, sn2.id, sn2.vlan.id, sip2.ip |
132 | + row = lambda ent1, ent2, metric: (*ent1, *ent2, None, metric) |
133 | + expected = [ |
134 | + row(left, left, 0), |
135 | + row(left, right, 1), # Same space, hence metric of 3. |
136 | + row(right, right, 0), |
137 | + row(right, left, 1), # Same space, hence metric of 3. |
138 | + ] |
139 | + |
140 | + with connection.cursor() as cursor: |
141 | + cursor.execute("SELECT * from maasserver_routable_pairs") |
142 | + self.assertItemsEqual(expected, cursor.fetchall()) |
143 | + |
144 | + def test__contains_routes_between_nodes_on_same_vlan(self): |
145 | + vlan = factory.make_VLAN() |
146 | + network1 = factory.make_ip4_or_6_network() |
147 | + network2 = factory.make_ip4_or_6_network(version=network1.version) |
148 | + node1, if1, sn1, sip1 = self.make_node_with_address( |
149 | + network1, vlan=vlan) |
150 | + node2, if2, sn2, sip2 = self.make_node_with_address( |
151 | + network2, vlan=vlan) |
152 | + |
153 | + # Routes between all addresses are found, even back to themselves. |
154 | + left = node1.id, if1.id, sn1.id, sn1.vlan.id, sip1.ip |
155 | + right = node2.id, if2.id, sn2.id, sn2.vlan.id, sip2.ip |
156 | + row = lambda ent1, ent2, metric: (*ent1, *ent2, None, metric) |
157 | + expected = [ |
158 | + row(left, left, 0), |
159 | + row(left, right, 2), # Same VLAN, hence metric of 2. |
160 | + row(right, right, 0), |
161 | + row(right, left, 2), # Same VLAN, hence metric of 2. |
162 | + ] |
163 | + |
164 | + with connection.cursor() as cursor: |
165 | + cursor.execute("SELECT * from maasserver_routable_pairs") |
166 | + self.assertItemsEqual(expected, cursor.fetchall()) |
167 | + |
168 | def test__contains_routes_between_nodes_on_same_space(self): |
169 | space = factory.make_Space() |
170 | network1 = factory.make_ip4_or_6_network() |
171 | network2 = factory.make_ip4_or_6_network(version=network1.version) |
172 | - node1, if1, sn1, sip1 = self.make_node_with_address(space, network1) |
173 | - node2, if2, sn2, sip2 = self.make_node_with_address(space, network2) |
174 | - |
175 | - # Routes between all addresses are found, even back to themselves. |
176 | - left = node1.id, if1.id, sn1.id, sn1.vlan.id, sip1.ip |
177 | - right = node2.id, if2.id, sn2.id, sn2.vlan.id, sip2.ip |
178 | - row = lambda ent1, ent2: ent1 + ent2 + (space.id, ) |
179 | - expected = [ |
180 | - row(left, left), |
181 | - row(left, right), |
182 | - row(right, right), |
183 | - row(right, left), |
184 | + node1, if1, sn1, sip1 = self.make_node_with_address(network1, space) |
185 | + node2, if2, sn2, sip2 = self.make_node_with_address(network2, space) |
186 | + |
187 | + # Routes between all addresses are found, even back to themselves. |
188 | + left = node1.id, if1.id, sn1.id, sn1.vlan.id, sip1.ip |
189 | + right = node2.id, if2.id, sn2.id, sn2.vlan.id, sip2.ip |
190 | + row = lambda ent1, ent2, metric: (*ent1, *ent2, space.id, metric) |
191 | + expected = [ |
192 | + row(left, left, 0), |
193 | + row(left, right, 3), # Same space, hence metric of 3. |
194 | + row(right, right, 0), |
195 | + row(right, left, 3), # Same space, hence metric of 3. |
196 | + ] |
197 | + |
198 | + with connection.cursor() as cursor: |
199 | + cursor.execute("SELECT * from maasserver_routable_pairs") |
200 | + self.assertItemsEqual(expected, cursor.fetchall()) |
201 | + |
202 | + def test__contains_routes_between_nodes_via_null_space(self): |
203 | + network1 = factory.make_ip4_or_6_network() |
204 | + network2 = factory.make_ip4_or_6_network(version=network1.version) |
205 | + node1, if1, sn1, sip1 = self.make_node_with_address(network1) |
206 | + node2, if2, sn2, sip2 = self.make_node_with_address(network2) |
207 | + |
208 | + # Routes between all addresses are found, even back to themselves. |
209 | + left = node1.id, if1.id, sn1.id, sn1.vlan.id, sip1.ip |
210 | + right = node2.id, if2.id, sn2.id, sn2.vlan.id, sip2.ip |
211 | + row = lambda ent1, ent2, metric: (*ent1, *ent2, None, metric) |
212 | + expected = [ |
213 | + row(left, left, 0), |
214 | + row(left, right, 4), # The NULL space, hence metric of 4. |
215 | + row(right, right, 0), |
216 | + row(right, left, 4), # The NULL space, hence metric of 4. |
217 | ] |
218 | |
219 | with connection.cursor() as cursor: |
220 | @@ -73,8 +142,8 @@ |
221 | space2 = factory.make_Space() |
222 | network1 = factory.make_ip4_or_6_network() |
223 | network2 = factory.make_ip4_or_6_network(version=network1.version) |
224 | - node1, if1, sn1, sip1 = self.make_node_with_address(space1, network1) |
225 | - node2, if2, sn2, sip2 = self.make_node_with_address(space2, network2) |
226 | + node1, if1, sn1, sip1 = self.make_node_with_address(network1, space1) |
227 | + node2, if2, sn2, sip2 = self.make_node_with_address(network2, space2) |
228 | |
229 | # Only routes from left to left and right to right are found: right is |
230 | # not routable from left, and left is not routable from right because |
231 | @@ -82,8 +151,8 @@ |
232 | left = node1.id, if1.id, sn1.id, sn1.vlan.id, sip1.ip |
233 | right = node2.id, if2.id, sn2.id, sn2.vlan.id, sip2.ip |
234 | expected = [ |
235 | - left + left + (space1.id,), |
236 | - right + right + (space2.id,), |
237 | + (*left, *left, space1.id, 0), |
238 | + (*right, *right, space2.id, 0), |
239 | ] |
240 | |
241 | with connection.cursor() as cursor: |
242 | @@ -95,8 +164,8 @@ |
243 | network1 = factory.make_ip4_or_6_network() |
244 | network2 = factory.make_ip4_or_6_network( |
245 | version=(4 if network1.version == 6 else 6)) |
246 | - node1, if1, sn1, sip1 = self.make_node_with_address(space, network1) |
247 | - node2, if2, sn2, sip2 = self.make_node_with_address(space, network2) |
248 | + node1, if1, sn1, sip1 = self.make_node_with_address(network1, space) |
249 | + node2, if2, sn2, sip2 = self.make_node_with_address(network2, space) |
250 | |
251 | # Only routes from left to left and right to right are found: right is |
252 | # not routable from left, and left is not routable from right because |
253 | @@ -104,8 +173,8 @@ |
254 | left = node1.id, if1.id, sn1.id, sn1.vlan.id, sip1.ip |
255 | right = node2.id, if2.id, sn2.id, sn2.vlan.id, sip2.ip |
256 | expected = [ |
257 | - left + left + (space.id,), |
258 | - right + right + (space.id,), |
259 | + (*left, *left, space.id, 0), |
260 | + (*right, *right, space.id, 0), |
261 | ] |
262 | |
263 | with connection.cursor() as cursor: |
264 | |
265 | === modified file 'src/maasserver/tests/test_ntp.py' |
266 | --- src/maasserver/tests/test_ntp.py 2017-01-28 00:51:47 +0000 |
267 | +++ src/maasserver/tests/test_ntp.py 2017-03-29 15:49:17 +0000 |
268 | @@ -5,8 +5,6 @@ |
269 | |
270 | __all__ = [] |
271 | |
272 | -from operator import methodcaller |
273 | - |
274 | from maasserver.models.config import Config |
275 | from maasserver.ntp import ( |
276 | get_peers_for, |
277 | @@ -14,10 +12,12 @@ |
278 | ) |
279 | from maasserver.testing.factory import factory |
280 | from maasserver.testing.testcase import MAASServerTestCase |
281 | -from netaddr import IPAddress |
282 | +from netaddr import ( |
283 | + IPAddress, |
284 | + IPSet, |
285 | +) |
286 | from testtools.matchers import ( |
287 | AfterPreprocessing, |
288 | - AllMatch, |
289 | ContainsAll, |
290 | Equals, |
291 | HasLength, |
292 | @@ -231,7 +231,7 @@ |
293 | |
294 | For racks, machines, and devices, a selection process takes place to |
295 | determine which of several candidate addresses per server to choose. This |
296 | - result is stable, i.e. it will choose the same address each time. |
297 | + result is semi-stable, i.e. it will always prefer "closer" addresses. |
298 | """ |
299 | |
300 | scenarios = ( |
301 | @@ -253,23 +253,31 @@ |
302 | super(TestGetServersFor_Selection, self).setUp() |
303 | Config.objects.set_config("ntp_external_only", False) |
304 | |
305 | - def test_prefers_ipv6_to_ipv4_peers_then_highest_numerically(self): |
306 | + def test_prefers_closest_addresses(self): |
307 | subnet4 = factory.make_Subnet(version=4) |
308 | - subnet6a = factory.make_Subnet(version=6) |
309 | - subnet6b = factory.make_Subnet(version=6) |
310 | - # Ensure that addresses in subnet6a < those in subnet6b. |
311 | - subnet6a, subnet6b = sorted( |
312 | - {subnet6a, subnet6b}, key=methodcaller("get_ipnetwork")) |
313 | - # Create a node and server with an address in each subnet. |
314 | - node, server = self.make_node(), self.make_server() |
315 | - subnets = {subnet4, subnet6a, subnet6b} |
316 | - populate_node_with_addresses(node, subnets) |
317 | - populate_node_with_addresses(server, subnets) |
318 | + subnet6 = factory.make_Subnet(version=6) |
319 | + # Separate subnets but sharing the VLAN, hence routable. |
320 | + subnet4v = factory.make_Subnet(version=4, vlan=subnet4.vlan) |
321 | + subnet6v = factory.make_Subnet(version=6, vlan=subnet6.vlan) |
322 | + |
323 | + # Create a node with an address in the first two subnets... |
324 | + node = self.make_node() |
325 | + populate_node_with_addresses(node, {subnet4, subnet6}) |
326 | + # ... and a server with an address in every subnet. |
327 | + server = self.make_server() |
328 | + populate_node_with_addresses( |
329 | + server, {subnet4, subnet6, subnet4v, subnet6v}) |
330 | + |
331 | + # The NTP server addresses chosen will be those that are "closest" to |
332 | + # the node, and same-subnet wins in this over same-VLAN. No additional |
333 | + # preference is made between IPv4 or IPv6, hence we allow for either. |
334 | + preferred_subnets = subnet4, subnet6 |
335 | + preferred_networks = IPSet( |
336 | + subnet.get_ipnetwork() for subnet in preferred_subnets) |
337 | |
338 | servers = get_servers_for(node) |
339 | self.assertThat(servers, Not(HasLength(0))) |
340 | - self.assertThat(servers, AllMatch(IsIPv6Address)) |
341 | - self.assertThat(subnet6b.get_ipnetwork(), ContainsAll(servers)) |
342 | + self.assertThat(preferred_networks, ContainsAll(servers)) |
343 | |
344 | |
345 | class TestGetPeersFor_Region_RegionRack(MAASServerTestCase): |
346 | @@ -296,23 +304,37 @@ |
347 | get_peers_for(node2), |
348 | IsSetOfServers({node1_address.ip})) |
349 | |
350 | - def test_prefers_ipv6_to_ipv4_peers_then_highest_numerically(self): |
351 | + def test_prefers_closest_addresses(self): |
352 | subnet4 = factory.make_Subnet(version=4) |
353 | - subnet6a = factory.make_Subnet(version=6) |
354 | - subnet6b = factory.make_Subnet(version=6) |
355 | - # Ensure that addresses in subnet6a < those in subnet6b. |
356 | - subnet6a, subnet6b = sorted( |
357 | - {subnet6a, subnet6b}, key=methodcaller("get_ipnetwork")) |
358 | - # Create some peers, each with an address in each subnet. |
359 | - nodes = self.make_node(), self.make_node() |
360 | - subnets = {subnet4, subnet6a, subnet6b} |
361 | - for node in nodes: |
362 | - populate_node_with_addresses(node, subnets) |
363 | - for node in nodes: |
364 | + subnet6 = factory.make_Subnet(version=6) |
365 | + # Separate subnets but sharing the VLAN, hence routable. |
366 | + subnet4v1 = factory.make_Subnet(version=4, vlan=subnet4.vlan) |
367 | + subnet6v1 = factory.make_Subnet(version=6, vlan=subnet6.vlan) |
368 | + subnet4v2 = factory.make_Subnet(version=4, vlan=subnet4.vlan) |
369 | + subnet6v2 = factory.make_Subnet(version=6, vlan=subnet6.vlan) |
370 | + |
371 | + # Create a node with an address in the first two subnets and the first |
372 | + # two same-VLAN subnets. |
373 | + node1 = self.make_node() |
374 | + populate_node_with_addresses( |
375 | + node1, {subnet4, subnet6, subnet4v1, subnet6v1}) |
376 | + # Create a node with an address in the first two subnets and the |
377 | + # second two same-VLAN subnets. |
378 | + node2 = self.make_node() |
379 | + populate_node_with_addresses( |
380 | + node2, {subnet4, subnet6, subnet4v2, subnet6v2}) |
381 | + |
382 | + # The NTP server addresses chosen will be those that are "closest" to |
383 | + # the node, and same-subnet wins in this over same-VLAN. No additional |
384 | + # preference is made between IPv4 or IPv6, hence we allow for either. |
385 | + preferred_subnets = subnet4, subnet6 |
386 | + preferred_networks = IPSet( |
387 | + subnet.get_ipnetwork() for subnet in preferred_subnets) |
388 | + |
389 | + for node in (node1, node2): |
390 | peers = get_peers_for(node) |
391 | self.assertThat(peers, Not(HasLength(0))) |
392 | - self.assertThat(peers, AllMatch(IsIPv6Address)) |
393 | - self.assertThat(subnet6b.get_ipnetwork(), ContainsAll(peers)) |
394 | + self.assertThat(preferred_networks, ContainsAll(peers)) |
395 | |
396 | |
397 | class TestGetPeersFor_Other(MAASServerTestCase): |
398 | |
399 | === modified file 'src/maasserver/tests/test_routablepairs.py' |
400 | --- src/maasserver/tests/test_routablepairs.py 2017-01-28 00:51:47 +0000 |
401 | +++ src/maasserver/tests/test_routablepairs.py 2017-03-29 15:49:17 +0000 |
402 | @@ -5,13 +5,21 @@ |
403 | |
404 | __all__ = [] |
405 | |
406 | -from itertools import product |
407 | +from itertools import ( |
408 | + product, |
409 | + takewhile, |
410 | +) |
411 | +import random |
412 | |
413 | from maasserver.models.node import Node |
414 | from maasserver.routablepairs import find_addresses_between_nodes |
415 | from maasserver.testing.factory import factory |
416 | from maasserver.testing.testcase import MAASServerTestCase |
417 | from testtools import ExpectedException |
418 | +from testtools.matchers import ( |
419 | + AfterPreprocessing, |
420 | + Equals, |
421 | +) |
422 | |
423 | |
424 | class TestFindAddressesBetweenNodes(MAASServerTestCase): |
425 | @@ -102,3 +110,124 @@ |
426 | # No routable addresses are found. |
427 | self.assertItemsEqual( |
428 | expected, find_addresses_between_nodes([node1], [node2])) |
429 | + |
430 | + def gen_disjoint_networks(self): |
431 | + """Generate disjoint networks. |
432 | + |
433 | + Can be IPv4 or IPv6, but once generation has begun they'll all be the |
434 | + same family. |
435 | + """ |
436 | + make_network = random.choice([ |
437 | + factory.make_ipv4_network, |
438 | + factory.make_ipv6_network] |
439 | + ) |
440 | + networks = [] |
441 | + while True: |
442 | + network = make_network(disjoint_from=networks) |
443 | + networks.append(network) |
444 | + yield network |
445 | + |
446 | + def test__yields_routes_with_lowest_metrics_first(self): |
447 | + space = factory.make_Space() |
448 | + # Ensure networks are disjoint but of the same family. |
449 | + networks = self.gen_disjoint_networks() |
450 | + |
451 | + # Create the node for the "left" that has two IP addresses, one in the |
452 | + # null space, one in a non-null space. |
453 | + origin = factory.make_Node(hostname="origin") |
454 | + origin_iface = factory.make_Interface( |
455 | + node=origin, disconnected=True) |
456 | + origin_subnet = factory.make_Subnet( |
457 | + space=space, cidr=next(networks)) |
458 | + origin_subnet_null_space = factory.make_Subnet( |
459 | + space=None, cidr=next(networks)) |
460 | + origin_sip = factory.make_StaticIPAddress( |
461 | + interface=origin_iface, subnet=origin_subnet) |
462 | + origin_sip_null_space = factory.make_StaticIPAddress( |
463 | + interface=origin_iface, subnet=origin_subnet_null_space) |
464 | + |
465 | + # Same subnet, different node. |
466 | + node_same_subnet = factory.make_Node(hostname="same-subnet") |
467 | + sip_same_subnet = factory.make_StaticIPAddress( |
468 | + interface=factory.make_Interface( |
469 | + node=node_same_subnet, disconnected=True), |
470 | + subnet=origin_subnet) |
471 | + |
472 | + # Same VLAN, different subnet, different node. |
473 | + node_same_vlan = factory.make_Node(hostname="same-vlan") |
474 | + sip_same_vlan = factory.make_StaticIPAddress( |
475 | + interface=factory.make_Interface( |
476 | + node=node_same_vlan, disconnected=True), |
477 | + subnet=factory.make_Subnet( |
478 | + space=space, vlan=origin_subnet.vlan, |
479 | + cidr=next(networks))) |
480 | + |
481 | + # Same space, different VLAN, subnet, and node. |
482 | + node_same_space = factory.make_Node(hostname="same-space") |
483 | + sip_same_space = factory.make_StaticIPAddress( |
484 | + interface=factory.make_Interface( |
485 | + node=node_same_space, disconnected=True), |
486 | + subnet=factory.make_Subnet( |
487 | + space=space, cidr=next(networks))) |
488 | + |
489 | + # Null space, different VLAN, subnet, and node. |
490 | + node_null_space = factory.make_Node(hostname="null-space") |
491 | + sip_null_space = factory.make_StaticIPAddress( |
492 | + interface=factory.make_Interface( |
493 | + node=node_null_space, disconnected=True), |
494 | + subnet=factory.make_Subnet( |
495 | + space=None, cidr=next(networks))) |
496 | + |
497 | + # We'll search for routes between `lefts` and `rights`. |
498 | + lefts = [ |
499 | + origin, |
500 | + ] |
501 | + rights = [ |
502 | + node_same_subnet, |
503 | + node_same_vlan, |
504 | + node_same_space, |
505 | + node_null_space, |
506 | + ] |
507 | + |
508 | + # This is in order, lowest "metric" first. |
509 | + expected = [ |
510 | + (origin, origin_sip.get_ipaddress(), |
511 | + node_same_subnet, sip_same_subnet.get_ipaddress()), |
512 | + (origin, origin_sip.get_ipaddress(), |
513 | + node_same_vlan, sip_same_vlan.get_ipaddress()), |
514 | + (origin, origin_sip.get_ipaddress(), |
515 | + node_same_space, sip_same_space.get_ipaddress()), |
516 | + (origin, origin_sip_null_space.get_ipaddress(), |
517 | + node_null_space, sip_null_space.get_ipaddress()), |
518 | + ] |
519 | + self.assertThat( |
520 | + find_addresses_between_nodes(lefts, rights), |
521 | + AfterPreprocessing(list, Equals(expected))) |
522 | + |
523 | + # Same node, same space, different VLAN and subnet. We did not add |
524 | + # this earlier because its existence allows for a large number of |
525 | + # additional routes between the origin and the other nodes, which |
526 | + # would have obscured the test. |
527 | + origin_sip_2 = factory.make_StaticIPAddress( |
528 | + interface=factory.make_Interface( |
529 | + node=origin, disconnected=True), |
530 | + subnet=factory.make_Subnet( |
531 | + space=space, cidr=next(networks))) |
532 | + |
533 | + # Now the first addresses returned are between those addresses we |
534 | + # created on the same node, in no particular order. |
535 | + origin_ips = origin_sip.get_ipaddress(), origin_sip_2.get_ipaddress() |
536 | + expected_mutual = { |
537 | + (origin, ip1, origin, ip2) |
538 | + for ip1, ip2 in product(origin_ips, origin_ips) |
539 | + } |
540 | + # There's a mutual route for the null-space IP address too. |
541 | + expected_mutual.add( |
542 | + (origin, origin_sip_null_space.get_ipaddress(), |
543 | + origin, origin_sip_null_space.get_ipaddress())) |
544 | + observed_mutual = takewhile( |
545 | + (lambda route: route[0] == route[2]), # Route is mutual. |
546 | + find_addresses_between_nodes(lefts, [origin, *rights]), |
547 | + ) |
548 | + self.assertItemsEqual( |
549 | + expected_mutual, observed_mutual) |
http:// pastebin. ubuntu. com/24274775/