Merge lp:~lamont/maas/bug-1599223-2.0 into lp:maas/2.0
- bug-1599223-2.0
- Merge into 2.0
Proposed by
LaMont Jones
Status: | Merged |
---|---|
Approved by: | LaMont Jones |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5150 |
Proposed branch: | lp:~lamont/maas/bug-1599223-2.0 |
Merge into: | lp:maas/2.0 |
Diff against target: |
696 lines (+237/-135) 6 files modified
src/maasserver/dns/tests/test_zonegenerator.py (+11/-22) src/maasserver/dns/zonegenerator.py (+12/-38) src/maasserver/models/dnsresource.py (+6/-3) src/maasserver/models/staticipaddress.py (+102/-36) src/maasserver/models/tests/test_dnsresource.py (+2/-2) src/maasserver/models/tests/test_staticipaddress.py (+104/-34) |
To merge this branch: | bzr merge lp:~lamont/maas/bug-1599223-2.0 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
LaMont Jones (community) | Approve | ||
Review via email:
|
Commit message
Refactor get_hostname_
When generating reverse DNS, we have to consider all of the forward zones, not just our subnet.
Description of the change
Refactor get_hostname_
When generating reverse DNS, we have to consider all of the forward zones, not just our subnet.
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/dns/tests/test_zonegenerator.py' |
2 | --- src/maasserver/dns/tests/test_zonegenerator.py 2016-05-12 19:07:37 +0000 |
3 | +++ src/maasserver/dns/tests/test_zonegenerator.py 2016-07-08 20:46:18 +0000 |
4 | @@ -371,10 +371,15 @@ |
5 | forward_zone("henry"), |
6 | reverse_zone(default_domain, "10/29"), |
7 | reverse_zone(default_domain, "10/24"))) |
8 | - self.assertEqual( |
9 | - {node.hostname: HostnameIPMapping( |
10 | + self.assertEqual({ |
11 | + node.hostname: HostnameIPMapping( |
12 | node.system_id, default_ttl, |
13 | - {'%s' % boot_ip.ip}, node.node_type)}, zones[0]._mapping) |
14 | + {'%s' % boot_ip.ip}, node.node_type), |
15 | + "%s.%s" % (interfaces[0].name, node.hostname): |
16 | + HostnameIPMapping( |
17 | + node.system_id, default_ttl, |
18 | + {'%s' % sip.ip}, node.node_type)}, |
19 | + zones[0]._mapping) |
20 | self.assertEqual( |
21 | {dnsdata.dnsresource.name: HostnameRRsetMapping( |
22 | None, |
23 | @@ -384,9 +389,7 @@ |
24 | node.fqdn: HostnameIPMapping( |
25 | node.system_id, 30, {'%s' % boot_ip.ip}, node.node_type), |
26 | '%s.%s' % (interfaces[0].name, node.fqdn): HostnameIPMapping( |
27 | - None, default_ttl, {'%s' % sip.ip}, None), |
28 | - '%s.%s' % (boot_iface.name, node.fqdn): HostnameIPMapping( |
29 | - None, default_ttl, {'%s' % boot_ip.ip}, None)}, |
30 | + None, default_ttl, {'%s' % sip.ip}, None)}, |
31 | zones[1]._mapping) |
32 | |
33 | def rfc2317_network(self, network): |
34 | @@ -517,8 +520,6 @@ |
35 | node.system_id, domain.ttl, {boot_ip.ip}, node.node_type)} |
36 | expected_reverse = { |
37 | node.fqdn: HostnameIPMapping( |
38 | - node.system_id, domain.ttl, {boot_ip.ip}, node.node_type), |
39 | - "%s.%s" % (boot_iface.name, node.fqdn): HostnameIPMapping( |
40 | node.system_id, domain.ttl, {boot_ip.ip}, node.node_type)} |
41 | zones = ZoneGenerator( |
42 | domain, subnet, default_ttl=global_ttl, |
43 | @@ -545,10 +546,6 @@ |
44 | expected_reverse = { |
45 | node.fqdn: HostnameIPMapping( |
46 | node.system_id, node.address_ttl, {boot_ip.ip}, |
47 | - node.node_type), |
48 | - "%s.%s" % (boot_iface.name, node.fqdn): |
49 | - HostnameIPMapping( |
50 | - node.system_id, node.address_ttl, {boot_ip.ip}, |
51 | node.node_type)} |
52 | zones = ZoneGenerator( |
53 | domain, subnet, default_ttl=global_ttl, |
54 | @@ -581,11 +578,7 @@ |
55 | node.system_id, node.address_ttl, ips, node.node_type)} |
56 | expected_reverse = { |
57 | node.fqdn: HostnameIPMapping( |
58 | - node.system_id, node.address_ttl, ips, node.node_type), |
59 | - "%s.%s" % (boot_iface.name, node.fqdn): |
60 | - HostnameIPMapping( |
61 | - node.system_id, node.address_ttl, {boot_ip.ip}, |
62 | - node.node_type)} |
63 | + node.system_id, node.address_ttl, ips, node.node_type)} |
64 | zones = ZoneGenerator( |
65 | domain, subnet, default_ttl=global_ttl, |
66 | serial=random.randint(0, 65535)).as_list() |
67 | @@ -621,11 +614,7 @@ |
68 | node.fqdn: HostnameIPMapping( |
69 | node.system_id, node.address_ttl, node_ips, node.node_type), |
70 | dnsrr.fqdn: HostnameIPMapping( |
71 | - None, dnsrr.address_ttl, dnsrr_ips, None), |
72 | - "%s.%s" % (boot_iface.name, node.fqdn): |
73 | - HostnameIPMapping( |
74 | - node.system_id, node.address_ttl, {boot_ip.ip}, |
75 | - node.node_type)} |
76 | + None, dnsrr.address_ttl, dnsrr_ips, None)} |
77 | zones = ZoneGenerator( |
78 | domain, subnet, default_ttl=global_ttl, |
79 | serial=random.randint(0, 65535)).as_list() |
80 | |
81 | === modified file 'src/maasserver/dns/zonegenerator.py' |
82 | --- src/maasserver/dns/zonegenerator.py 2016-04-21 19:13:47 +0000 |
83 | +++ src/maasserver/dns/zonegenerator.py 2016-07-08 20:46:18 +0000 |
84 | @@ -22,11 +22,7 @@ |
85 | from maasserver.models.dnsdata import DNSData |
86 | from maasserver.models.dnsresource import separate_fqdn |
87 | from maasserver.models.domain import Domain |
88 | -from maasserver.models.node import Node |
89 | -from maasserver.models.staticipaddress import ( |
90 | - HostnameIPMapping, |
91 | - StaticIPAddress, |
92 | -) |
93 | +from maasserver.models.staticipaddress import StaticIPAddress |
94 | from maasserver.models.subnet import Subnet |
95 | from maasserver.server_address import get_maas_facing_server_address |
96 | from netaddr import ( |
97 | @@ -255,12 +251,16 @@ |
98 | IPNetwork("%s/124" % network.network).network) |
99 | rfc2317_glue.setdefault(basenet, set()).add(network) |
100 | |
101 | + # Since get_hostname_ip_mapping(Subnet) ignores Subnet.id, so we can |
102 | + # just do it once and be happy. LP#1600259 |
103 | + if len(subnets): |
104 | + mappings['reverse'] = mappings[Subnet.objects.first()] |
105 | + |
106 | # For each of the zones that we are generating (one or more per |
107 | # subnet), compile the zone from: |
108 | # 1. Dynamic ranges on this subnet. |
109 | # 2. Node: ip mapping(subnet), including DNSResource records for |
110 | # StaticIPAddresses in this subnet. |
111 | - # 3. Interfaces on any node that have IP addresses in this subnet. |
112 | # All of this needs to be done smallest to largest so that we can |
113 | # correctly gather the rfc2317 glue that we need. Failure to sort |
114 | # means that we wind up grabbing (and deleting) the rfc2317 glue info |
115 | @@ -283,38 +283,12 @@ |
116 | for ip_range in subnet.get_dynamic_ranges() |
117 | ] |
118 | |
119 | - # 2. Start with the map of all of the nodes on this subnet, |
120 | - # including all DNSResource-associated addresses. |
121 | - mapping = mappings[subnet] |
122 | - |
123 | - # 3. Add all of the interface named records. |
124 | - # 2015-12-18 lamont N.B., these are not found in the forward zone, |
125 | - # on purpose. If someone eventually calls this a bug, we can |
126 | - # revisit the size increase this would create in the forward zone. |
127 | - # This will also include any discovered addresses on such |
128 | - # interfaces. |
129 | - nodes = Node.objects.filter( |
130 | - interface__ip_addresses__subnet_id=subnet.id, |
131 | - interface__ip_addresses__ip__isnull=False).prefetch_related( |
132 | - "interface_set__ip_addresses") |
133 | - for node in nodes: |
134 | - if node.address_ttl is not None: |
135 | - ttl = node.address_ttl |
136 | - elif node.domain.ttl is not None: |
137 | - ttl = node.domain.ttl |
138 | - else: |
139 | - ttl = default_ttl |
140 | - for iface in node.interface_set.all(): |
141 | - ips_in_subnet = { |
142 | - ip.ip |
143 | - for ip in iface.ip_addresses.all() |
144 | - if (ip.ip is not None and ip.subnet_id == subnet.id)} |
145 | - if len(ips_in_subnet) > 0: |
146 | - iface_map = HostnameIPMapping( |
147 | - node.system_id, ttl, ips_in_subnet, node.node_type) |
148 | - mapping.update({ |
149 | - "%s.%s" % (iface.name, iface.node.fqdn): iface_map |
150 | - }) |
151 | + # 2. Start with the map of all of the nodes, including all |
152 | + # DNSResource-associated addresses. We will prune this to just |
153 | + # entries for the subnet when we actually generate the zonefile. |
154 | + # If we get here, then we have subnets, so we noticed that above |
155 | + # and created mappings['reverse']. LP#1600259 |
156 | + mapping = mappings['reverse'] |
157 | |
158 | # Use the default_domain as the name for the NS host in the reverse |
159 | # zones. If this network is actually a parent rfc2317 glue |
160 | |
161 | === modified file 'src/maasserver/models/dnsresource.py' |
162 | --- src/maasserver/models/dnsresource.py 2016-05-11 19:01:48 +0000 |
163 | +++ src/maasserver/models/dnsresource.py 2016-07-08 20:46:18 +0000 |
164 | @@ -75,14 +75,17 @@ |
165 | """ |
166 | if fqdn is None or fqdn == '' or fqdn == '@': |
167 | return (fqdn, None) |
168 | - elif Domain.objects.filter(name=fqdn).exists(): |
169 | - if domainname == fqdn or domainname is None: |
170 | + if domainname is not None: |
171 | + if domainname == fqdn: |
172 | return ('@', fqdn) |
173 | else: |
174 | # strip off the passed in ".$domainname" from the fqdn. |
175 | name = fqdn[:-len(domainname) - 1] |
176 | return (name, domainname) |
177 | - elif rrtype == 'SRV': |
178 | + else: |
179 | + if Domain.objects.filter(name=fqdn).exists(): |
180 | + return ('@', fqdn) |
181 | + if rrtype == 'SRV': |
182 | spec = SRV_LHS |
183 | else: |
184 | spec = LABEL |
185 | |
186 | === modified file 'src/maasserver/models/staticipaddress.py' |
187 | --- src/maasserver/models/staticipaddress.py 2016-05-26 17:50:49 +0000 |
188 | +++ src/maasserver/models/staticipaddress.py 2016-07-08 20:46:18 +0000 |
189 | @@ -51,7 +51,6 @@ |
190 | from maasserver.models.domain import Domain |
191 | from maasserver.models.subnet import Subnet |
192 | from maasserver.models.timestampedmodel import TimestampedModel |
193 | -from maasserver.utils import strip_domain |
194 | from maasserver.utils.dns import get_ip_based_hostname |
195 | from maasserver.utils.orm import ( |
196 | make_serialization_failure, |
197 | @@ -226,8 +225,10 @@ |
198 | qs = self.filter( |
199 | Q(alloc_type=IPADDRESS_TYPE.USER_RESERVED) | |
200 | Q(dnsresource__isnull=False)) |
201 | + # If this is a subnet, we need to ignore subnet.id, as per |
202 | + # get_hostname_ip_mapping(). LP#1600259 |
203 | if isinstance(domain_or_subnet, Subnet): |
204 | - qs = qs.filter(subnet_id=domain_or_subnet.id) |
205 | + pass |
206 | elif isinstance(domain_or_subnet, Domain): |
207 | qs = qs.filter(dnsresource__domain_id=domain_or_subnet.id) |
208 | qs = qs.prefetch_related("dnsresource_set") |
209 | @@ -307,20 +308,6 @@ |
210 | # return the IPs for the oldest Interface address. |
211 | # |
212 | # For nodes that have disable_ipv4 set, leave out any IPv4 address. |
213 | - if isinstance(domain_or_subnet, Subnet): |
214 | - search_term = "staticip.subnet_id" |
215 | - elif isinstance(domain_or_subnet, Domain): |
216 | - # The model has nodes in the parent domain, but they actually live |
217 | - # in the child domain. And the parent needs the glue. So we |
218 | - # return such nodes addresses in _BOTH_ the parent and the child |
219 | - # domains. domain2.name will be non-null if this host's fqdn is the |
220 | - # name of a domain in MAAS. |
221 | - # n.b. The SQL says: WHERE ... $search_term = %s ... |
222 | - # we overload that to accomplish our objectives. |
223 | - search_term = """domain2.name IS NOT NULL OR node.domain_id""" |
224 | - else: |
225 | - raise ValueError('bad object passed to get_hostname_ip_mapping') |
226 | - |
227 | default_ttl = "%d" % Config.objects.get_config('default_dns_ttl') |
228 | if raw_ttl: |
229 | ttl_clause = """node.address_ttl""" |
230 | @@ -332,11 +319,11 @@ |
231 | %s)""" % default_ttl |
232 | sql_query = """ |
233 | SELECT DISTINCT ON (node.hostname, is_boot, family(staticip.ip)) |
234 | - node.hostname, |
235 | + CONCAT(node.hostname, '.', domain.name) AS fqdn, |
236 | node.system_id, |
237 | node.node_type, |
238 | """ + ttl_clause + """ AS ttl, |
239 | - domain.name, staticip.ip, |
240 | + staticip.ip, |
241 | COALESCE( |
242 | node.boot_interface_id IS NOT NULL AND |
243 | ( |
244 | @@ -359,22 +346,42 @@ |
245 | link.interface_id = interface.id |
246 | JOIN maasserver_staticipaddress AS staticip ON |
247 | staticip.id = link.staticipaddress_id |
248 | + """ |
249 | + if isinstance(domain_or_subnet, Domain): |
250 | + # The model has nodes in the parent domain, but they actually live |
251 | + # in the child domain. And the parent needs the glue. So we |
252 | + # return such nodes addresses in _BOTH_ the parent and the child |
253 | + # domains. domain2.name will be non-null if this host's fqdn is the |
254 | + # name of a domain in MAAS. |
255 | + sql_query += """ |
256 | LEFT JOIN maasserver_domain as domain2 ON |
257 | /* Pick up another copy of domain looking for instances of |
258 | * nodes a the top of a domain. |
259 | */ |
260 | domain2.name = CONCAT(node.hostname, '.', domain.name) |
261 | WHERE |
262 | + (domain2.name IS NOT NULL OR node.domain_id = %s) AND |
263 | + """ |
264 | + query_parms = [domain_or_subnet.id, ] |
265 | + else: |
266 | + # For subnets, we need ALL the names, so that we can correctly |
267 | + # identify which ones should have the FQDN. dns/zonegenerator.py |
268 | + # optimizes based on this, and only calls once with a subnet, |
269 | + # expecting to get all the subnets back in one table. |
270 | + sql_query += """ |
271 | + WHERE |
272 | + """ |
273 | + query_parms = [] |
274 | + sql_query += """ |
275 | staticip.ip IS NOT NULL AND |
276 | host(staticip.ip) != '' AND |
277 | - (""" + search_term + """ = %s) AND |
278 | ( |
279 | node.disable_ipv4 IS FALSE OR |
280 | family(staticip.ip) <> 4 |
281 | ) |
282 | ORDER BY |
283 | node.hostname, |
284 | - is_boot, |
285 | + is_boot DESC, |
286 | family(staticip.ip), |
287 | CASE |
288 | WHEN interface.type = 'bond' AND |
289 | @@ -407,33 +414,92 @@ |
290 | END, |
291 | interface.id |
292 | """ |
293 | + iface_sql_query = """ |
294 | + SELECT |
295 | + CONCAT(node.hostname, '.', domain.name) AS fqdn, |
296 | + node.system_id, |
297 | + node.node_type, |
298 | + """ + ttl_clause + """ AS ttl, |
299 | + staticip.ip, |
300 | + interface.name |
301 | + FROM |
302 | + maasserver_interface AS interface |
303 | + JOIN maasserver_node AS node ON |
304 | + node.id = interface.node_id |
305 | + JOIN maasserver_domain as domain ON |
306 | + domain.id = node.domain_id |
307 | + JOIN maasserver_interface_ip_addresses AS link ON |
308 | + link.interface_id = interface.id |
309 | + JOIN maasserver_staticipaddress AS staticip ON |
310 | + staticip.id = link.staticipaddress_id |
311 | + """ |
312 | + if isinstance(domain_or_subnet, Domain): |
313 | + # This logic is similar to the logic in sql_query above. |
314 | + iface_sql_query += """ |
315 | + LEFT JOIN maasserver_domain as domain2 ON |
316 | + /* Pick up another copy of domain looking for instances of |
317 | + * the name as the top of a domain. |
318 | + */ |
319 | + domain2.name = CONCAT( |
320 | + interface.name, '.', node.hostname, '.', domain.name) |
321 | + WHERE |
322 | + (domain2.name IS NOT NULL OR node.domain_id = %s) AND |
323 | + """ |
324 | + else: |
325 | + # For subnets, we need ALL the names, so that we can correctly |
326 | + # identify which ones should have the FQDN. dns/zonegenerator.py |
327 | + # optimizes based on this, and only calls once with a subnet, |
328 | + # expecting to get all the subnets back in one table. |
329 | + iface_sql_query += """ |
330 | + WHERE |
331 | + """ |
332 | + iface_sql_query += """ |
333 | + staticip.ip IS NOT NULL AND |
334 | + host(staticip.ip) != '' AND |
335 | + ( |
336 | + node.disable_ipv4 IS FALSE OR |
337 | + family(staticip.ip) <> 4 |
338 | + ) |
339 | + ORDER BY |
340 | + node.hostname, |
341 | + interface.id |
342 | + """ |
343 | # We get user reserved et al mappings first, so that we can overwrite |
344 | # TTL as we process the return from the SQL horror above. |
345 | mapping = self._get_user_reserved_mappings(domain_or_subnet) |
346 | + # All of the mappings that we got mean that we will only want to add |
347 | + # addresses for the boot interface (is_boot == True). |
348 | iface_is_boot = defaultdict(bool, { |
349 | hostname: True for hostname in mapping.keys() |
350 | }) |
351 | - cursor.execute(sql_query, (domain_or_subnet.id,)) |
352 | + cursor.execute(sql_query, query_parms) |
353 | # The records from the query provide, for each hostname (after |
354 | # stripping domain), the boot and non-boot interface ip address in ipv4 |
355 | # and ipv6. Our task: if there are boot interace IPs, they win. If |
356 | # there are none, then whatever we got wins. The ORDER BY means that |
357 | - # we will see all of the non-boot interfaces before we see any boot |
358 | + # we will see all of the boot interfaces before we see any non-boot |
359 | # interface IPs. See Bug#1584850 |
360 | - for (node_name, system_id, node_type, |
361 | - ttl, domain_name, ip, is_boot) in cursor.fetchall(): |
362 | - hostname = "%s.%s" % (strip_domain(node_name), domain_name) |
363 | - mapping[hostname].node_type = node_type |
364 | - mapping[hostname].system_id = system_id |
365 | - mapping[hostname].ttl = ttl |
366 | - # If this is a boot interface, and we have previously only found |
367 | - # non-boot interface IPs, discard them. See also Bug#1584850. |
368 | - if is_boot and not iface_is_boot[hostname]: |
369 | - mapping[hostname].ips = set() |
370 | - iface_is_boot[hostname] = True |
371 | - # At this point, if the interface type is correct, keep the IP. |
372 | - if is_boot == iface_is_boot[hostname]: |
373 | - mapping[hostname].ips.add(ip) |
374 | + for (fqdn, system_id, node_type, ttl, |
375 | + ip, is_boot) in cursor.fetchall(): |
376 | + mapping[fqdn].node_type = node_type |
377 | + mapping[fqdn].system_id = system_id |
378 | + mapping[fqdn].ttl = ttl |
379 | + if is_boot: |
380 | + iface_is_boot[fqdn] = True |
381 | + # If we have an IP on the right interface type, save it. |
382 | + if is_boot == iface_is_boot[fqdn]: |
383 | + mapping[fqdn].ips.add(ip) |
384 | + # Next, get all the addresses, on all the interfaces, and add the ones |
385 | + # that are not already present on the FQDN as $IFACE.$FQDN. |
386 | + cursor.execute(iface_sql_query, (domain_or_subnet.id,)) |
387 | + for (fqdn, system_id, node_type, ttl, |
388 | + ip, iface_name) in cursor.fetchall(): |
389 | + if ip not in mapping[fqdn].ips: |
390 | + name = "%s.%s" % (iface_name, fqdn) |
391 | + mapping[name].node_type = node_type |
392 | + mapping[name].system_id = system_id |
393 | + mapping[name].ttl = ttl |
394 | + mapping[name].ips.add(ip) |
395 | return mapping |
396 | |
397 | def filter_by_ip_family(self, family): |
398 | |
399 | === modified file 'src/maasserver/models/tests/test_dnsresource.py' |
400 | --- src/maasserver/models/tests/test_dnsresource.py 2016-04-22 01:56:28 +0000 |
401 | +++ src/maasserver/models/tests/test_dnsresource.py 2016-07-08 20:46:18 +0000 |
402 | @@ -131,9 +131,9 @@ |
403 | parent = "%s.%s" % ( |
404 | factory.make_name("b"), |
405 | factory.make_name("c")) |
406 | - label = factory.make_name("a") |
407 | + label = "%s.%s" % (factory.make_name("a"), factory.make_name("d")) |
408 | name = "%s.%s" % (label, parent) |
409 | - factory.make_Domain(name=name) |
410 | + factory.make_Domain(name=parent) |
411 | self.assertEqual( |
412 | (label, parent), separate_fqdn(name, domainname=parent)) |
413 | |
414 | |
415 | === modified file 'src/maasserver/models/tests/test_staticipaddress.py' |
416 | --- src/maasserver/models/tests/test_staticipaddress.py 2016-05-26 17:50:49 +0000 |
417 | +++ src/maasserver/models/tests/test_staticipaddress.py 2016-07-08 20:46:18 +0000 |
418 | @@ -33,6 +33,7 @@ |
419 | HostnameIPMapping, |
420 | StaticIPAddress, |
421 | ) |
422 | +from maasserver.models.subnet import Subnet |
423 | from maasserver.testing.factory import factory |
424 | from maasserver.testing.testcase import ( |
425 | MAASServerTestCase, |
426 | @@ -357,23 +358,54 @@ |
427 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
428 | self.assertEqual(expected_mapping, mapping) |
429 | |
430 | - def test_get_hostname_ip_mapping_returns_fqdn(self): |
431 | + def test_get_hostname_ip_mapping_returns_all_mappings_for_subnet(self): |
432 | + domain = Domain.objects.get_default_domain() |
433 | + expected_mapping = {} |
434 | + for _ in range(3): |
435 | + node = factory.make_Node(interface=True, disable_ipv4=False) |
436 | + boot_interface = node.get_boot_interface() |
437 | + subnet = factory.make_Subnet() |
438 | + staticip = factory.make_StaticIPAddress( |
439 | + alloc_type=IPADDRESS_TYPE.STICKY, |
440 | + ip=factory.pick_ip_in_Subnet(subnet), |
441 | + subnet=subnet, interface=boot_interface) |
442 | + full_hostname = "%s.%s" % (node.hostname, domain.name) |
443 | + expected_mapping[full_hostname] = HostnameIPMapping( |
444 | + node.system_id, 30, {staticip.ip}, node.node_type) |
445 | + # See also LP#1600259. It doesn't matter what subnet is passed in, you |
446 | + # get all of them. |
447 | + mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
448 | + Subnet.objects.first()) |
449 | + self.assertEqual(expected_mapping, mapping) |
450 | + |
451 | + def test_get_hostname_ip_mapping_returns_fqdn_and_other(self): |
452 | hostname = factory.make_name('hostname') |
453 | domainname = factory.make_name('domain') |
454 | factory.make_Domain(name=domainname) |
455 | full_hostname = "%s.%s" % (hostname, domainname) |
456 | subnet = factory.make_Subnet() |
457 | node = factory.make_Node_with_Interface_on_Subnet( |
458 | - interface=True, hostname=full_hostname, |
459 | + interface=True, hostname=full_hostname, interface_count=3, |
460 | subnet=subnet, disable_ipv4=False) |
461 | boot_interface = node.get_boot_interface() |
462 | staticip = factory.make_StaticIPAddress( |
463 | alloc_type=IPADDRESS_TYPE.STICKY, |
464 | ip=factory.pick_ip_in_Subnet(subnet), |
465 | subnet=subnet, interface=boot_interface) |
466 | + ifaces = node.interface_set.exclude(interface=boot_interface) |
467 | + sip2 = factory.make_StaticIPAddress( |
468 | + alloc_type=IPADDRESS_TYPE.STICKY, |
469 | + ip=factory.pick_ip_in_Subnet(subnet), |
470 | + subnet=subnet, interface=ifaces[0]) |
471 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(subnet) |
472 | - self.assertEqual({full_hostname: HostnameIPMapping( |
473 | - node.system_id, 30, {staticip.ip}, node.node_type)}, mapping) |
474 | + self.assertEqual({ |
475 | + full_hostname: |
476 | + HostnameIPMapping( |
477 | + node.system_id, 30, {staticip.ip}, node.node_type), |
478 | + "%s.%s" % (ifaces[0].name, full_hostname): |
479 | + HostnameIPMapping( |
480 | + node.system_id, 30, {sip2.ip}, node.node_type), |
481 | + }, mapping) |
482 | |
483 | def make_mapping(self, node, raw_ttl=False): |
484 | if raw_ttl or node.address_ttl is not None: |
485 | @@ -481,12 +513,16 @@ |
486 | ip=factory.pick_ip_in_Subnet(subnet), |
487 | subnet=subnet, interface=boot_interface) |
488 | newer_nic = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
489 | - factory.make_StaticIPAddress( |
490 | + newer_ip = factory.make_StaticIPAddress( |
491 | alloc_type=IPADDRESS_TYPE.STICKY, interface=newer_nic) |
492 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
493 | node.domain) |
494 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
495 | - node.system_id, 30, {staticip.ip}, node.node_type)}, mapping) |
496 | + expected_mapping = { |
497 | + node.fqdn: HostnameIPMapping( |
498 | + node.system_id, 30, {staticip.ip}, node.node_type), |
499 | + "%s.%s" % (newer_nic.name, node.fqdn): HostnameIPMapping( |
500 | + node.system_id, 30, {newer_ip.ip}, node.node_type)} |
501 | + self.assertEqual(expected_mapping, mapping) |
502 | |
503 | def test_get_hostname_ip_mapping_picks_sticky_over_auto(self): |
504 | subnet = factory.make_Subnet( |
505 | @@ -500,12 +536,16 @@ |
506 | ip=factory.pick_ip_in_Subnet(subnet), |
507 | subnet=subnet, interface=boot_interface) |
508 | nic = node.get_boot_interface() |
509 | - factory.make_StaticIPAddress( |
510 | + auto_ip = factory.make_StaticIPAddress( |
511 | alloc_type=IPADDRESS_TYPE.AUTO, interface=nic) |
512 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
513 | node.domain) |
514 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
515 | - node.system_id, 30, {staticip.ip}, node.node_type)}, mapping) |
516 | + expected_mapping = { |
517 | + node.fqdn: HostnameIPMapping( |
518 | + node.system_id, 30, {staticip.ip}, node.node_type), |
519 | + "%s.%s" % (nic.name, node.fqdn): HostnameIPMapping( |
520 | + node.system_id, 30, {auto_ip.ip}, node.node_type)} |
521 | + self.assertEqual(expected_mapping, mapping) |
522 | |
523 | def test_get_hostname_ip_mapping_combines_IPv4_and_IPv6_addresses(self): |
524 | node = factory.make_Node(interface=True, disable_ipv4=False) |
525 | @@ -584,13 +624,17 @@ |
526 | staticip = factory.make_StaticIPAddress( |
527 | alloc_type=IPADDRESS_TYPE.AUTO, interface=iface, |
528 | subnet=subnet) |
529 | - factory.make_StaticIPAddress( |
530 | + discovered = factory.make_StaticIPAddress( |
531 | alloc_type=IPADDRESS_TYPE.DISCOVERED, interface=iface, |
532 | subnet=subnet) |
533 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
534 | node.domain) |
535 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
536 | - node.system_id, 30, {staticip.ip}, node.node_type)}, mapping) |
537 | + expected_mapping = { |
538 | + node.fqdn: HostnameIPMapping( |
539 | + node.system_id, 30, {staticip.ip}, node.node_type), |
540 | + "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping( |
541 | + node.system_id, 30, {discovered.ip}, node.node_type)} |
542 | + self.assertEqual(expected_mapping, mapping) |
543 | |
544 | def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self): |
545 | subnet = factory.make_Subnet( |
546 | @@ -605,7 +649,7 @@ |
547 | iface3 = factory.make_Interface(node=node) |
548 | bondif = factory.make_Interface( |
549 | INTERFACE_TYPE.BOND, node=node, parents=[iface2, iface3]) |
550 | - factory.make_StaticIPAddress( |
551 | + iface_ip = factory.make_StaticIPAddress( |
552 | alloc_type=IPADDRESS_TYPE.STICKY, interface=iface, |
553 | subnet=subnet) |
554 | factory.make_StaticIPAddress( |
555 | @@ -619,8 +663,12 @@ |
556 | subnet=subnet) |
557 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
558 | node.domain) |
559 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
560 | - node.system_id, 30, {bond_staticip.ip}, node.node_type)}, mapping) |
561 | + expected_mapping = { |
562 | + node.fqdn: HostnameIPMapping( |
563 | + node.system_id, 30, {bond_staticip.ip}, node.node_type), |
564 | + "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping( |
565 | + node.system_id, 30, {iface_ip.ip}, node.node_type)} |
566 | + self.assertEqual(expected_mapping, mapping) |
567 | |
568 | def test_get_hostname_ip_mapping_prefers_bond_with_boot_interface(self): |
569 | subnet = factory.make_Subnet( |
570 | @@ -643,8 +691,11 @@ |
571 | subnet=subnet) |
572 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
573 | node.domain) |
574 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
575 | - node.system_id, 30, {bond_staticip.ip}, node.node_type)}, mapping) |
576 | + expected_mapping = { |
577 | + node.fqdn: HostnameIPMapping( |
578 | + node.system_id, 30, {bond_staticip.ip}, node.node_type), |
579 | + } |
580 | + self.assertEqual(expected_mapping, mapping) |
581 | |
582 | def test_get_hostname_ip_mapping_ignores_bond_without_boot_interface(self): |
583 | subnet = factory.make_Subnet( |
584 | @@ -666,13 +717,17 @@ |
585 | factory.make_StaticIPAddress( |
586 | alloc_type=IPADDRESS_TYPE.STICKY, interface=iface3, |
587 | subnet=subnet) |
588 | - factory.make_StaticIPAddress( |
589 | + bondip = factory.make_StaticIPAddress( |
590 | alloc_type=IPADDRESS_TYPE.STICKY, interface=bondif, |
591 | subnet=subnet) |
592 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
593 | node.domain) |
594 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
595 | - node.system_id, 30, {boot_staticip.ip}, node.node_type)}, mapping) |
596 | + expected_mapping = { |
597 | + node.fqdn: HostnameIPMapping( |
598 | + node.system_id, 30, {boot_staticip.ip}, node.node_type), |
599 | + "%s.%s" % (bondif.name, node.fqdn): HostnameIPMapping( |
600 | + node.system_id, 30, {bondip.ip}, node.node_type)} |
601 | + self.assertEqual(expected_mapping, mapping) |
602 | |
603 | def test_get_hostname_ip_mapping_prefers_boot_interface(self): |
604 | subnet = factory.make_Subnet( |
605 | @@ -681,7 +736,7 @@ |
606 | hostname=factory.make_name('host'), subnet=subnet, |
607 | disable_ipv4=False) |
608 | iface = node.get_boot_interface() |
609 | - factory.make_StaticIPAddress( |
610 | + iface_ip = factory.make_StaticIPAddress( |
611 | alloc_type=IPADDRESS_TYPE.STICKY, interface=iface, |
612 | subnet=subnet) |
613 | new_boot_interface = factory.make_Interface( |
614 | @@ -694,8 +749,12 @@ |
615 | subnet=subnet) |
616 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
617 | node.domain) |
618 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
619 | - node.system_id, 30, {boot_sip.ip}, node.node_type)}, mapping) |
620 | + expected_mapping = { |
621 | + node.fqdn: HostnameIPMapping( |
622 | + node.system_id, 30, {boot_sip.ip}, node.node_type), |
623 | + "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping( |
624 | + node.system_id, 30, {iface_ip.ip}, node.node_type)} |
625 | + self.assertEqual(expected_mapping, mapping) |
626 | |
627 | def test_get_hostname_ip_mapping_prefers_boot_interface_to_alias(self): |
628 | subnet = factory.make_Subnet( |
629 | @@ -704,7 +763,7 @@ |
630 | hostname=factory.make_name('host'), subnet=subnet, |
631 | disable_ipv4=False) |
632 | iface = node.get_boot_interface() |
633 | - factory.make_StaticIPAddress( |
634 | + iface_ip = factory.make_StaticIPAddress( |
635 | alloc_type=IPADDRESS_TYPE.STICKY, interface=iface, |
636 | subnet=subnet) |
637 | new_boot_interface = factory.make_Interface( |
638 | @@ -717,8 +776,12 @@ |
639 | subnet=subnet) |
640 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
641 | node.domain) |
642 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
643 | - node.system_id, 30, {boot_sip.ip}, node.node_type)}, mapping) |
644 | + expected_mapping = { |
645 | + node.fqdn: HostnameIPMapping( |
646 | + node.system_id, 30, {boot_sip.ip}, node.node_type), |
647 | + "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping( |
648 | + node.system_id, 30, {iface_ip.ip}, node.node_type)} |
649 | + self.assertEqual(expected_mapping, mapping) |
650 | |
651 | def test_get_hostname_ip_mapping_prefers_physical_interfaces_to_vlan(self): |
652 | subnet = factory.make_Subnet( |
653 | @@ -732,13 +795,17 @@ |
654 | phy_staticip = factory.make_StaticIPAddress( |
655 | alloc_type=IPADDRESS_TYPE.STICKY, interface=iface, |
656 | subnet=subnet) |
657 | - factory.make_StaticIPAddress( |
658 | + vlanip = factory.make_StaticIPAddress( |
659 | alloc_type=IPADDRESS_TYPE.STICKY, interface=vlanif, |
660 | subnet=subnet) |
661 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
662 | node.domain) |
663 | - self.assertEqual({node.fqdn: HostnameIPMapping( |
664 | - node.system_id, 30, {phy_staticip.ip}, node.node_type)}, mapping) |
665 | + expected_mapping = { |
666 | + node.fqdn: HostnameIPMapping( |
667 | + node.system_id, 30, {phy_staticip.ip}, node.node_type), |
668 | + "%s.%s" % (vlanif.name, node.fqdn): HostnameIPMapping( |
669 | + node.system_id, 30, {vlanip.ip}, node.node_type)} |
670 | + self.assertEqual(expected_mapping, mapping) |
671 | |
672 | @skip("XXX: GavinPanella 2016-04-27 bug=1556188: Fails spuriously.") |
673 | def test_get_hostname_ip_mapping_returns_domain_head_ips(self): |
674 | @@ -791,8 +858,9 @@ |
675 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
676 | expected_mapping = { |
677 | node.fqdn: HostnameIPMapping( |
678 | - node.system_id, 30, {sip0.ip}, node.node_type |
679 | - )} |
680 | + node.system_id, 30, {sip0.ip}, node.node_type), |
681 | + "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping( |
682 | + node.system_id, 30, {sip1.ip}, node.node_type)} |
683 | self.assertEqual(expected_mapping, mapping) |
684 | |
685 | def test_get_hostname_ip_mapping_returns_correct_bond_ip(self): |
686 | @@ -838,8 +906,10 @@ |
687 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
688 | expected_mapping = { |
689 | node.fqdn: HostnameIPMapping( |
690 | - node.system_id, 30, {bond_sip.ip}, node.node_type |
691 | - )} |
692 | + node.system_id, 30, {bond_sip.ip}, node.node_type), |
693 | + "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping( |
694 | + node.system_id, 30, {sip1.ip}, node.node_type), |
695 | + } |
696 | self.assertEqual(expected_mapping, mapping) |
697 | |
698 |
Simple backport from trunk to 2.0.