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

Proposed by LaMont Jones on 2016-07-08
Status: Merged
Approved by: LaMont Jones on 2016-07-11
Approved revision: 5150
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
Reviewer Review Type Date Requested Status
LaMont Jones (community) Approve on 2016-07-11
Review via email: mp+299586@code.launchpad.net

Commit message

Refactor get_hostname_ip_mapping() to also return interface-named rows.
When generating reverse DNS, we have to consider all of the forward zones, not just our subnet.

Description of the change

Refactor get_hostname_ip_mapping() to also return interface-named rows.
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.
lp:~lamont/maas/bug-1599223-2.0 updated on 2016-07-08
5150. By LaMont Jones on 2016-07-08

Clean up the ugly SQL and better optimize it.

LaMont Jones (lamont) wrote :

Simple backport from trunk to 2.0.

review: Approve

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

Subscribers

People subscribed via source and target branches

to all changes: