Merge lp:~lamont/maas/bug-1599223 into lp:maas/trunk

Proposed by LaMont Jones on 2016-07-08
Status: Merged
Approved by: LaMont Jones on 2016-07-08
Approved revision: 5160
Merged at revision: 5162
Proposed branch: lp:~lamont/maas/bug-1599223
Merge into: lp:maas/trunk
Diff against target: 539 lines (+164/-112)
6 files modified
src/maasserver/dns/tests/test_zonegenerator.py (+11/-22)
src/maasserver/dns/zonegenerator.py (+1/-35)
src/maasserver/models/dnsresource.py (+6/-3)
src/maasserver/models/staticipaddress.py (+61/-16)
src/maasserver/models/tests/test_dnsresource.py (+2/-2)
src/maasserver/models/tests/test_staticipaddress.py (+83/-34)
To merge this branch: bzr merge lp:~lamont/maas/bug-1599223
Reviewer Review Type Date Requested Status
Mike Pontillo 2016-07-08 Approve on 2016-07-08
Review via email: mp+299491@code.launchpad.net

Commit message

Refactor get_hostname_ip_mapping() and stop generating multiple PTR RRs for node FQDNs.

Description of the change

Cleanup how IP Addresses get into the DNS, and reduce the overall number of SQL queries for that process significantly. All IP addresses wind up in the DNS, with the FQDN being what we expect it to be historically, and $IFACE.$FQDN for the other IPs, so that there is only the one PTR RR for the FQDN. ($IFACE.$FQDN may have many PTR RRs in the RRset.)

In the old world, we created forward/reverse DNS for the FQDN, and also created PTR RRs naming $IFACE.$FQDN for each address on each interface on each node that resided in the subnet that we were generating a PTR zone for.

With this change, $IFACE.$FQDN is promoted to first-class citizen, and we create forward and reverse DNS for the names. Any name properly given to the FQDN is omitted from the $IFACE.$FQDN collection, since that would cause us to generate multiple PTR RRs for the primary IPs for the domain. The admin who needs multiple PTR RRs for specific situations can use dnsresource-records to cause that to happen.

To post a comment you must log in.
Mike Pontillo (mpontillo) wrote :

Looks good to me. Was this tested end-to-end on nodes deployed with more than one interface? We need to make sure to do that before backporting to 2.0. Let me know if I can help.

review: Approve
LaMont Jones (lamont) wrote :

Currently, there can be some disparity between forward and reverse zones (which predates this branch) wherein we might choose to return the FQDN for a PTR where there is a IFACE.FQDN A/AAAA RR. I'll file a separate bug report for that.

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 00:18:04 +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 00:18:04 +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@@ -260,7 +256,6 @@
98 # 1. Dynamic ranges on this subnet.
99 # 2. Node: ip mapping(subnet), including DNSResource records for
100 # StaticIPAddresses in this subnet.
101- # 3. Interfaces on any node that have IP addresses in this subnet.
102 # All of this needs to be done smallest to largest so that we can
103 # correctly gather the rfc2317 glue that we need. Failure to sort
104 # means that we wind up grabbing (and deleting) the rfc2317 glue info
105@@ -287,35 +282,6 @@
106 # including all DNSResource-associated addresses.
107 mapping = mappings[subnet]
108
109- # 3. Add all of the interface named records.
110- # 2015-12-18 lamont N.B., these are not found in the forward zone,
111- # on purpose. If someone eventually calls this a bug, we can
112- # revisit the size increase this would create in the forward zone.
113- # This will also include any discovered addresses on such
114- # interfaces.
115- nodes = Node.objects.filter(
116- interface__ip_addresses__subnet_id=subnet.id,
117- interface__ip_addresses__ip__isnull=False).prefetch_related(
118- "interface_set__ip_addresses")
119- for node in nodes:
120- if node.address_ttl is not None:
121- ttl = node.address_ttl
122- elif node.domain.ttl is not None:
123- ttl = node.domain.ttl
124- else:
125- ttl = default_ttl
126- for iface in node.interface_set.all():
127- ips_in_subnet = {
128- ip.ip
129- for ip in iface.ip_addresses.all()
130- if (ip.ip is not None and ip.subnet_id == subnet.id)}
131- if len(ips_in_subnet) > 0:
132- iface_map = HostnameIPMapping(
133- node.system_id, ttl, ips_in_subnet, node.node_type)
134- mapping.update({
135- "%s.%s" % (iface.name, iface.node.fqdn): iface_map
136- })
137-
138 # Use the default_domain as the name for the NS host in the reverse
139 # zones. If this network is actually a parent rfc2317 glue
140 # network, then we need to generate the glue records.
141
142=== modified file 'src/maasserver/models/dnsresource.py'
143--- src/maasserver/models/dnsresource.py 2016-05-11 19:01:48 +0000
144+++ src/maasserver/models/dnsresource.py 2016-07-08 00:18:04 +0000
145@@ -75,14 +75,17 @@
146 """
147 if fqdn is None or fqdn == '' or fqdn == '@':
148 return (fqdn, None)
149- elif Domain.objects.filter(name=fqdn).exists():
150- if domainname == fqdn or domainname is None:
151+ if domainname is not None:
152+ if domainname == fqdn:
153 return ('@', fqdn)
154 else:
155 # strip off the passed in ".$domainname" from the fqdn.
156 name = fqdn[:-len(domainname) - 1]
157 return (name, domainname)
158- elif rrtype == 'SRV':
159+ else:
160+ if Domain.objects.filter(name=fqdn).exists():
161+ return ('@', fqdn)
162+ if rrtype == 'SRV':
163 spec = SRV_LHS
164 else:
165 spec = LABEL
166
167=== modified file 'src/maasserver/models/staticipaddress.py'
168--- src/maasserver/models/staticipaddress.py 2016-07-01 12:23:35 +0000
169+++ src/maasserver/models/staticipaddress.py 2016-07-08 00:18:04 +0000
170@@ -377,7 +377,7 @@
171 )
172 ORDER BY
173 node.hostname,
174- is_boot,
175+ is_boot DESC,
176 family(staticip.ip),
177 CASE
178 WHEN interface.type = 'bond' AND
179@@ -410,6 +410,42 @@
180 END,
181 interface.id
182 """
183+ iface_sql_query = """
184+ SELECT
185+ node.hostname,
186+ node.system_id,
187+ node.node_type,
188+ """ + ttl_clause + """ AS ttl,
189+ domain.name, staticip.ip,
190+ interface.name
191+ FROM
192+ maasserver_interface AS interface
193+ JOIN maasserver_node AS node ON
194+ node.id = interface.node_id
195+ JOIN maasserver_domain as domain ON
196+ domain.id = node.domain_id
197+ JOIN maasserver_interface_ip_addresses AS link ON
198+ link.interface_id = interface.id
199+ JOIN maasserver_staticipaddress AS staticip ON
200+ staticip.id = link.staticipaddress_id
201+ LEFT JOIN maasserver_domain as domain2 ON
202+ /* Pick up another copy of domain looking for instances of
203+ * the name as the top of a domain.
204+ */
205+ domain2.name = CONCAT(
206+ interface.name, '.', node.hostname, '.', domain.name)
207+ WHERE
208+ staticip.ip IS NOT NULL AND
209+ host(staticip.ip) != '' AND
210+ (""" + search_term + """ = %s) AND
211+ (
212+ node.disable_ipv4 IS FALSE OR
213+ family(staticip.ip) <> 4
214+ )
215+ ORDER BY
216+ node.hostname,
217+ interface.id
218+ """
219 # We get user reserved et al mappings first, so that we can overwrite
220 # TTL as we process the return from the SQL horror above.
221 mapping = self._get_user_reserved_mappings(domain_or_subnet)
222@@ -421,22 +457,31 @@
223 # stripping domain), the boot and non-boot interface ip address in ipv4
224 # and ipv6. Our task: if there are boot interace IPs, they win. If
225 # there are none, then whatever we got wins. The ORDER BY means that
226- # we will see all of the non-boot interfaces before we see any boot
227+ # we will see all of the boot interfaces before we see any non-boot
228 # interface IPs. See Bug#1584850
229- for (node_name, system_id, node_type,
230- ttl, domain_name, ip, is_boot) in cursor.fetchall():
231- hostname = "%s.%s" % (strip_domain(node_name), domain_name)
232- mapping[hostname].node_type = node_type
233- mapping[hostname].system_id = system_id
234- mapping[hostname].ttl = ttl
235- # If this is a boot interface, and we have previously only found
236- # non-boot interface IPs, discard them. See also Bug#1584850.
237- if is_boot and not iface_is_boot[hostname]:
238- mapping[hostname].ips = set()
239- iface_is_boot[hostname] = True
240- # At this point, if the interface type is correct, keep the IP.
241- if is_boot == iface_is_boot[hostname]:
242- mapping[hostname].ips.add(ip)
243+ for (node_name, system_id, node_type, ttl, domain_name,
244+ ip, is_boot) in cursor.fetchall():
245+ fqdn = "%s.%s" % (strip_domain(node_name), domain_name)
246+ mapping[fqdn].node_type = node_type
247+ mapping[fqdn].system_id = system_id
248+ mapping[fqdn].ttl = ttl
249+ if is_boot:
250+ iface_is_boot[fqdn] = True
251+ # If we have an IP on the right interface type, save it.
252+ if is_boot == iface_is_boot[fqdn]:
253+ mapping[fqdn].ips.add(ip)
254+ # Next, get all the addresses, on all the interfaces, and add the ones
255+ # that are not already present on the FQDN as $IFACE.$FQDN.
256+ cursor.execute(iface_sql_query, (domain_or_subnet.id,))
257+ for (node_name, system_id, node_type, ttl,
258+ domain_name, ip, iface_name) in cursor.fetchall():
259+ fqdn = "%s.%s" % (strip_domain(node_name), domain_name)
260+ if ip not in mapping[fqdn].ips:
261+ name = "%s.%s" % (iface_name, fqdn)
262+ mapping[name].node_type = node_type
263+ mapping[name].system_id = system_id
264+ mapping[name].ttl = ttl
265+ mapping[name].ips.add(ip)
266 return mapping
267
268 def filter_by_ip_family(self, family):
269
270=== modified file 'src/maasserver/models/tests/test_dnsresource.py'
271--- src/maasserver/models/tests/test_dnsresource.py 2016-04-22 01:56:28 +0000
272+++ src/maasserver/models/tests/test_dnsresource.py 2016-07-08 00:18:04 +0000
273@@ -131,9 +131,9 @@
274 parent = "%s.%s" % (
275 factory.make_name("b"),
276 factory.make_name("c"))
277- label = factory.make_name("a")
278+ label = "%s.%s" % (factory.make_name("a"), factory.make_name("d"))
279 name = "%s.%s" % (label, parent)
280- factory.make_Domain(name=name)
281+ factory.make_Domain(name=parent)
282 self.assertEqual(
283 (label, parent), separate_fqdn(name, domainname=parent))
284
285
286=== modified file 'src/maasserver/models/tests/test_staticipaddress.py'
287--- src/maasserver/models/tests/test_staticipaddress.py 2016-07-01 12:26:51 +0000
288+++ src/maasserver/models/tests/test_staticipaddress.py 2016-07-08 00:18:04 +0000
289@@ -356,23 +356,34 @@
290 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain)
291 self.assertEqual(expected_mapping, mapping)
292
293- def test_get_hostname_ip_mapping_returns_fqdn(self):
294+ def test_get_hostname_ip_mapping_returns_fqdn_and_other(self):
295 hostname = factory.make_name('hostname')
296 domainname = factory.make_name('domain')
297 factory.make_Domain(name=domainname)
298 full_hostname = "%s.%s" % (hostname, domainname)
299 subnet = factory.make_Subnet()
300 node = factory.make_Node_with_Interface_on_Subnet(
301- interface=True, hostname=full_hostname,
302+ interface=True, hostname=full_hostname, interface_count=3,
303 subnet=subnet, disable_ipv4=False)
304 boot_interface = node.get_boot_interface()
305 staticip = factory.make_StaticIPAddress(
306 alloc_type=IPADDRESS_TYPE.STICKY,
307 ip=factory.pick_ip_in_Subnet(subnet),
308 subnet=subnet, interface=boot_interface)
309+ ifaces = node.interface_set.exclude(interface=boot_interface)
310+ sip2 = factory.make_StaticIPAddress(
311+ alloc_type=IPADDRESS_TYPE.STICKY,
312+ ip=factory.pick_ip_in_Subnet(subnet),
313+ subnet=subnet, interface=ifaces[0])
314 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(subnet)
315- self.assertEqual({full_hostname: HostnameIPMapping(
316- node.system_id, 30, {staticip.ip}, node.node_type)}, mapping)
317+ self.assertEqual({
318+ full_hostname:
319+ HostnameIPMapping(
320+ node.system_id, 30, {staticip.ip}, node.node_type),
321+ "%s.%s" % (ifaces[0].name, full_hostname):
322+ HostnameIPMapping(
323+ node.system_id, 30, {sip2.ip}, node.node_type),
324+ }, mapping)
325
326 def make_mapping(self, node, raw_ttl=False):
327 if raw_ttl or node.address_ttl is not None:
328@@ -480,12 +491,16 @@
329 ip=factory.pick_ip_in_Subnet(subnet),
330 subnet=subnet, interface=boot_interface)
331 newer_nic = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
332- factory.make_StaticIPAddress(
333+ newer_ip = factory.make_StaticIPAddress(
334 alloc_type=IPADDRESS_TYPE.STICKY, interface=newer_nic)
335 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
336 node.domain)
337- self.assertEqual({node.fqdn: HostnameIPMapping(
338- node.system_id, 30, {staticip.ip}, node.node_type)}, mapping)
339+ expected_mapping = {
340+ node.fqdn: HostnameIPMapping(
341+ node.system_id, 30, {staticip.ip}, node.node_type),
342+ "%s.%s" % (newer_nic.name, node.fqdn): HostnameIPMapping(
343+ node.system_id, 30, {newer_ip.ip}, node.node_type)}
344+ self.assertEqual(expected_mapping, mapping)
345
346 def test_get_hostname_ip_mapping_picks_sticky_over_auto(self):
347 subnet = factory.make_Subnet(
348@@ -499,12 +514,16 @@
349 ip=factory.pick_ip_in_Subnet(subnet),
350 subnet=subnet, interface=boot_interface)
351 nic = node.get_boot_interface()
352- factory.make_StaticIPAddress(
353+ auto_ip = factory.make_StaticIPAddress(
354 alloc_type=IPADDRESS_TYPE.AUTO, interface=nic)
355 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
356 node.domain)
357- self.assertEqual({node.fqdn: HostnameIPMapping(
358- node.system_id, 30, {staticip.ip}, node.node_type)}, mapping)
359+ expected_mapping = {
360+ node.fqdn: HostnameIPMapping(
361+ node.system_id, 30, {staticip.ip}, node.node_type),
362+ "%s.%s" % (nic.name, node.fqdn): HostnameIPMapping(
363+ node.system_id, 30, {auto_ip.ip}, node.node_type)}
364+ self.assertEqual(expected_mapping, mapping)
365
366 def test_get_hostname_ip_mapping_combines_IPv4_and_IPv6_addresses(self):
367 node = factory.make_Node(interface=True, disable_ipv4=False)
368@@ -583,13 +602,17 @@
369 staticip = factory.make_StaticIPAddress(
370 alloc_type=IPADDRESS_TYPE.AUTO, interface=iface,
371 subnet=subnet)
372- factory.make_StaticIPAddress(
373+ discovered = factory.make_StaticIPAddress(
374 alloc_type=IPADDRESS_TYPE.DISCOVERED, interface=iface,
375 subnet=subnet)
376 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
377 node.domain)
378- self.assertEqual({node.fqdn: HostnameIPMapping(
379- node.system_id, 30, {staticip.ip}, node.node_type)}, mapping)
380+ expected_mapping = {
381+ node.fqdn: HostnameIPMapping(
382+ node.system_id, 30, {staticip.ip}, node.node_type),
383+ "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping(
384+ node.system_id, 30, {discovered.ip}, node.node_type)}
385+ self.assertEqual(expected_mapping, mapping)
386
387 def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self):
388 subnet = factory.make_Subnet(
389@@ -604,7 +627,7 @@
390 iface3 = factory.make_Interface(node=node)
391 bondif = factory.make_Interface(
392 INTERFACE_TYPE.BOND, node=node, parents=[iface2, iface3])
393- factory.make_StaticIPAddress(
394+ iface_ip = factory.make_StaticIPAddress(
395 alloc_type=IPADDRESS_TYPE.STICKY, interface=iface,
396 subnet=subnet)
397 factory.make_StaticIPAddress(
398@@ -618,8 +641,12 @@
399 subnet=subnet)
400 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
401 node.domain)
402- self.assertEqual({node.fqdn: HostnameIPMapping(
403- node.system_id, 30, {bond_staticip.ip}, node.node_type)}, mapping)
404+ expected_mapping = {
405+ node.fqdn: HostnameIPMapping(
406+ node.system_id, 30, {bond_staticip.ip}, node.node_type),
407+ "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping(
408+ node.system_id, 30, {iface_ip.ip}, node.node_type)}
409+ self.assertEqual(expected_mapping, mapping)
410
411 def test_get_hostname_ip_mapping_prefers_bond_with_boot_interface(self):
412 subnet = factory.make_Subnet(
413@@ -642,8 +669,11 @@
414 subnet=subnet)
415 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
416 node.domain)
417- self.assertEqual({node.fqdn: HostnameIPMapping(
418- node.system_id, 30, {bond_staticip.ip}, node.node_type)}, mapping)
419+ expected_mapping = {
420+ node.fqdn: HostnameIPMapping(
421+ node.system_id, 30, {bond_staticip.ip}, node.node_type),
422+ }
423+ self.assertEqual(expected_mapping, mapping)
424
425 def test_get_hostname_ip_mapping_ignores_bond_without_boot_interface(self):
426 subnet = factory.make_Subnet(
427@@ -665,13 +695,17 @@
428 factory.make_StaticIPAddress(
429 alloc_type=IPADDRESS_TYPE.STICKY, interface=iface3,
430 subnet=subnet)
431- factory.make_StaticIPAddress(
432+ bondip = factory.make_StaticIPAddress(
433 alloc_type=IPADDRESS_TYPE.STICKY, interface=bondif,
434 subnet=subnet)
435 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
436 node.domain)
437- self.assertEqual({node.fqdn: HostnameIPMapping(
438- node.system_id, 30, {boot_staticip.ip}, node.node_type)}, mapping)
439+ expected_mapping = {
440+ node.fqdn: HostnameIPMapping(
441+ node.system_id, 30, {boot_staticip.ip}, node.node_type),
442+ "%s.%s" % (bondif.name, node.fqdn): HostnameIPMapping(
443+ node.system_id, 30, {bondip.ip}, node.node_type)}
444+ self.assertEqual(expected_mapping, mapping)
445
446 def test_get_hostname_ip_mapping_prefers_boot_interface(self):
447 subnet = factory.make_Subnet(
448@@ -680,7 +714,7 @@
449 hostname=factory.make_name('host'), subnet=subnet,
450 disable_ipv4=False)
451 iface = node.get_boot_interface()
452- factory.make_StaticIPAddress(
453+ iface_ip = factory.make_StaticIPAddress(
454 alloc_type=IPADDRESS_TYPE.STICKY, interface=iface,
455 subnet=subnet)
456 new_boot_interface = factory.make_Interface(
457@@ -693,8 +727,12 @@
458 subnet=subnet)
459 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
460 node.domain)
461- self.assertEqual({node.fqdn: HostnameIPMapping(
462- node.system_id, 30, {boot_sip.ip}, node.node_type)}, mapping)
463+ expected_mapping = {
464+ node.fqdn: HostnameIPMapping(
465+ node.system_id, 30, {boot_sip.ip}, node.node_type),
466+ "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping(
467+ node.system_id, 30, {iface_ip.ip}, node.node_type)}
468+ self.assertEqual(expected_mapping, mapping)
469
470 def test_get_hostname_ip_mapping_prefers_boot_interface_to_alias(self):
471 subnet = factory.make_Subnet(
472@@ -703,7 +741,7 @@
473 hostname=factory.make_name('host'), subnet=subnet,
474 disable_ipv4=False)
475 iface = node.get_boot_interface()
476- factory.make_StaticIPAddress(
477+ iface_ip = factory.make_StaticIPAddress(
478 alloc_type=IPADDRESS_TYPE.STICKY, interface=iface,
479 subnet=subnet)
480 new_boot_interface = factory.make_Interface(
481@@ -716,8 +754,12 @@
482 subnet=subnet)
483 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
484 node.domain)
485- self.assertEqual({node.fqdn: HostnameIPMapping(
486- node.system_id, 30, {boot_sip.ip}, node.node_type)}, mapping)
487+ expected_mapping = {
488+ node.fqdn: HostnameIPMapping(
489+ node.system_id, 30, {boot_sip.ip}, node.node_type),
490+ "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping(
491+ node.system_id, 30, {iface_ip.ip}, node.node_type)}
492+ self.assertEqual(expected_mapping, mapping)
493
494 def test_get_hostname_ip_mapping_prefers_physical_interfaces_to_vlan(self):
495 subnet = factory.make_Subnet(
496@@ -731,13 +773,17 @@
497 phy_staticip = factory.make_StaticIPAddress(
498 alloc_type=IPADDRESS_TYPE.STICKY, interface=iface,
499 subnet=subnet)
500- factory.make_StaticIPAddress(
501+ vlanip = factory.make_StaticIPAddress(
502 alloc_type=IPADDRESS_TYPE.STICKY, interface=vlanif,
503 subnet=subnet)
504 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
505 node.domain)
506- self.assertEqual({node.fqdn: HostnameIPMapping(
507- node.system_id, 30, {phy_staticip.ip}, node.node_type)}, mapping)
508+ expected_mapping = {
509+ node.fqdn: HostnameIPMapping(
510+ node.system_id, 30, {phy_staticip.ip}, node.node_type),
511+ "%s.%s" % (vlanif.name, node.fqdn): HostnameIPMapping(
512+ node.system_id, 30, {vlanip.ip}, node.node_type)}
513+ self.assertEqual(expected_mapping, mapping)
514
515 @skip("XXX: GavinPanella 2016-04-27 bug=1556188: Fails spuriously.")
516 def test_get_hostname_ip_mapping_returns_domain_head_ips(self):
517@@ -790,8 +836,9 @@
518 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain)
519 expected_mapping = {
520 node.fqdn: HostnameIPMapping(
521- node.system_id, 30, {sip0.ip}, node.node_type
522- )}
523+ node.system_id, 30, {sip0.ip}, node.node_type),
524+ "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping(
525+ node.system_id, 30, {sip1.ip}, node.node_type)}
526 self.assertEqual(expected_mapping, mapping)
527
528 def test_get_hostname_ip_mapping_returns_correct_bond_ip(self):
529@@ -837,8 +884,10 @@
530 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain)
531 expected_mapping = {
532 node.fqdn: HostnameIPMapping(
533- node.system_id, 30, {bond_sip.ip}, node.node_type
534- )}
535+ node.system_id, 30, {bond_sip.ip}, node.node_type),
536+ "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping(
537+ node.system_id, 30, {sip1.ip}, node.node_type),
538+ }
539 self.assertEqual(expected_mapping, mapping)
540
541