Merge lp:~mpontillo/maas/dns-api-ui-consistency--bug-1686864 into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Rejected
Rejected by: MAAS Lander
Proposed branch: lp:~mpontillo/maas/dns-api-ui-consistency--bug-1686864
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~mpontillo/maas/dns-dhcp-fixes--bug-1686234
Diff against target: 256 lines (+117/-16)
4 files modified
src/maasserver/api/dnsresources.py (+64/-1)
src/maasserver/models/domain.py (+29/-13)
src/maasserver/models/tests/test_domain.py (+23/-1)
src/maasserver/testing/factory.py (+1/-1)
To merge this branch: bzr merge lp:~mpontillo/maas/dns-api-ui-consistency--bug-1686864
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+323361@code.launchpad.net

Commit message

Optionally allow implicit DNS records to be shown using the API.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

Transitioned to Git.

lp:maas has now moved from Bzr to Git.
Please propose your branches with Launchpad using Git.

git clone https://git.launchpad.net/maas

Unmerged revisions

6026. By Mike Pontillo

Don't include redundant DNS data in the extra records shown in the API.

6025. By Mike Pontillo

Add a way to get implicit DNS records via the API.

6024. By Mike Pontillo

Add a way to see implicit DNS records.

6023. By Mike Pontillo

Merge trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api/dnsresources.py'
--- src/maasserver/api/dnsresources.py 2017-03-29 22:30:31 +0000
+++ src/maasserver/api/dnsresources.py 2017-04-28 00:50:14 +0000
@@ -2,11 +2,15 @@
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""API handlers: `DNSResource`."""4"""API handlers: `DNSResource`."""
5from collections import OrderedDict
56
7from django.db.models.query import QuerySet
8from formencode.validators import StringBool
6from maasserver.api.support import (9from maasserver.api.support import (
7 admin_method,10 admin_method,
8 OperationsHandler,11 OperationsHandler,
9)12)
13from maasserver.api.utils import get_optional_param
10from maasserver.enum import NODE_PERMISSION14from maasserver.enum import NODE_PERMISSION
11from maasserver.exceptions import MAASAPIValidationError15from maasserver.exceptions import MAASAPIValidationError
12from maasserver.forms.dnsresource import DNSResourceForm16from maasserver.forms.dnsresource import DNSResourceForm
@@ -27,6 +31,40 @@
27)31)
2832
2933
34class DNSResourcesQuerySet(QuerySet):
35
36 def __iter__(self):
37 """Custom iterator which also includes implicit DNS records."""
38 previous_domain = None
39 for record in super().__iter__():
40 # This works because the query always either contains a single
41 # domain, or is sorted by domain.
42 if record.domain != previous_domain:
43 previous_domain = record.domain
44 rrdata = record.domain.render_json_for_related_rrdata(
45 include_dnsdata=False, as_dict=True)
46 for name, value in rrdata.items():
47 name = name.split('.')[0]
48 # Remove redundant info (this is provided in the top
49 # level 'fqdn' field).
50 if (self._name_filter is not None and
51 name != self._name_filter):
52 continue
53 items = []
54 for rr in value:
55 if (self._rrtype_filter is not None and
56 rr['rrtype'] != self._rrtype_filter):
57 continue
58 del rr['name']
59 items.append(rr)
60 if len(items) > 0:
61 resource = DNSResource(
62 id=-1, name=name, domain=record.domain)
63 resource._rrdata = items
64 yield resource
65 yield record
66
67
30class DNSResourcesHandler(OperationsHandler):68class DNSResourcesHandler(OperationsHandler):
31 """Manage dnsresources."""69 """Manage dnsresources."""
32 api_doc_section_name = "DNSResources"70 api_doc_section_name = "DNSResources"
@@ -44,12 +82,18 @@
44 :param name: restrict the listing to entries of the given name.82 :param name: restrict the listing to entries of the given name.
45 :param rrtype: restrict the listing to entries which have83 :param rrtype: restrict the listing to entries which have
46 records of the given rrtype.84 records of the given rrtype.
85 :param all: if True, also include implicit DNS records created for
86 nodes registered in MAAS.
47 """87 """
48 data = request.GET88 data = request.GET
49 fqdn = data.get('fqdn', None)89 fqdn = data.get('fqdn', None)
50 name = data.get('name', None)90 name = data.get('name', None)
51 domainname = data.get('domain', None)91 domainname = data.get('domain', None)
52 rrtype = data.get('rrtype', None)92 rrtype = data.get('rrtype', None)
93 if rrtype is not None:
94 rrtype = rrtype.upper()
95 _all = get_optional_param(
96 request.GET, 'all', default=False, validator=StringBool)
53 if domainname is None and name is None and fqdn is not None:97 if domainname is None and name is None and fqdn is not None:
54 # We need a type for resource separation. If the user didn't give98 # We need a type for resource separation. If the user didn't give
55 # us a rrtype, then assume it's an address of some sort.99 # us a rrtype, then assume it's an address of some sort.
@@ -66,10 +110,22 @@
66 query = domain.dnsresource_set.all().order_by('name')110 query = domain.dnsresource_set.all().order_by('name')
67 else:111 else:
68 query = DNSResource.objects.all().order_by('domain_id', 'name')112 query = DNSResource.objects.all().order_by('domain_id', 'name')
113 rrtype_filter = None
114 name_filter = None
69 if name is not None:115 if name is not None:
70 query = query.filter(name=name)116 query = query.filter(name=name)
117 name_filter = name
71 if rrtype is not None:118 if rrtype is not None:
72 query = query.filter(dnsdata__rrtype=rrtype)119 query = query.filter(dnsdata__rrtype=rrtype)
120 rrtype_filter = rrtype
121 query = query.prefetch_related('ip_addresses', 'dnsdata_set')
122 query = query.select_related('domain')
123 # Note: This must be done last, otherwise our hacks to show additional
124 # records won't work.
125 if _all is True:
126 query.__class__ = DNSResourcesQuerySet
127 query._name_filter = name_filter
128 query._rrtype_filter = rrtype_filter
73 return query129 return query
74130
75 @admin_method131 @admin_method
@@ -137,12 +193,19 @@
137 @classmethod193 @classmethod
138 def ip_addresses(cls, dnsresource):194 def ip_addresses(cls, dnsresource):
139 """Return IPAddresses within the specified dnsresource."""195 """Return IPAddresses within the specified dnsresource."""
196 rrdata = getattr(dnsresource, '_rrdata', None)
197 if rrdata is not None:
198 return None
140 return dnsresource.ip_addresses.all()199 return dnsresource.ip_addresses.all()
141200
142 @classmethod201 @classmethod
143 def resource_records(cls, dnsresource):202 def resource_records(cls, dnsresource):
144 """Other data for this dnsresource."""203 """Other data for this dnsresource."""
145 return dnsresource.dnsdata_set.all().order_by('rrtype')204 rrdata = getattr(dnsresource, '_rrdata', None)
205 if rrdata is not None:
206 return rrdata
207 else:
208 return dnsresource.dnsdata_set.all().order_by('rrtype')
146209
147 def read(self, request, id):210 def read(self, request, id):
148 """Read dnsresource.211 """Read dnsresource.
149212
=== modified file 'src/maasserver/models/domain.py'
--- src/maasserver/models/domain.py 2017-04-17 14:11:28 +0000
+++ src/maasserver/models/domain.py 2017-04-28 00:50:14 +0000
@@ -12,6 +12,7 @@
12 "validate_domain_name",12 "validate_domain_name",
13 ]13 ]
1414
15from collections import OrderedDict, defaultdict
15import datetime16import datetime
16import re17import re
1718
@@ -351,7 +352,8 @@
351 super(Domain, self).clean(*args, **kwargs)352 super(Domain, self).clean(*args, **kwargs)
352 self.clean_name()353 self.clean_name()
353354
354 def render_json_for_related_rrdata(self, for_list=False):355 def render_json_for_related_rrdata(
356 self, for_list=False, include_dnsdata=True, as_dict=False):
355 """Render a representation of this domain's related non-IP data,357 """Render a representation of this domain's related non-IP data,
356 suitable for converting to JSON.358 suitable for converting to JSON.
357359
@@ -360,8 +362,13 @@
360 DNSData,362 DNSData,
361 StaticIPAddress,363 StaticIPAddress,
362 )364 )
363 rr_mapping = DNSData.objects.get_hostname_dnsdata_mapping(365 if include_dnsdata is True:
364 self, raw_ttl=True)366 rr_mapping = DNSData.objects.get_hostname_dnsdata_mapping(
367 self, raw_ttl=True)
368 else:
369 # Circular imports.
370 from maasserver.models.dnsdata import HostnameRRsetMapping
371 rr_mapping = defaultdict(HostnameRRsetMapping)
365 # Smash the IP Addresses in the rrset mapping, so that the far end372 # Smash the IP Addresses in the rrset mapping, so that the far end
366 # only needs to worry about one thing.373 # only needs to worry about one thing.
367 ip_mapping = StaticIPAddress.objects.get_hostname_ip_mapping(374 ip_mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
@@ -376,16 +383,25 @@
376 rr_mapping[hostname].rrset.add((info.ttl, 'AAAA', ip))383 rr_mapping[hostname].rrset.add((info.ttl, 'AAAA', ip))
377 else:384 else:
378 rr_mapping[hostname].rrset.add((info.ttl, 'A', ip))385 rr_mapping[hostname].rrset.add((info.ttl, 'A', ip))
379 data = []386 if as_dict is True:
387 result = OrderedDict()
388 else:
389 result = []
380 for hostname, info in rr_mapping.items():390 for hostname, info in rr_mapping.items():
381 data += [{391 data = [{
382 'name': hostname,392 'name': hostname,
383 'system_id': info.system_id,393 'system_id': info.system_id,
384 'node_type': info.node_type,394 'node_type': info.node_type,
385 'ttl': ttl,395 'ttl': ttl,
386 'rrtype': rrtype,396 'rrtype': rrtype,
387 'rrdata': rrdata397 'rrdata': rrdata
388 }398 }
389 for ttl, rrtype, rrdata in info.rrset399 for ttl, rrtype, rrdata in info.rrset
390 ]400 ]
391 return data401 if as_dict is True:
402 existing = result.get(hostname, [])
403 existing.extend(data)
404 result[hostname] = existing
405 else:
406 result.extend(data)
407 return result
392408
=== modified file 'src/maasserver/models/tests/test_domain.py'
--- src/maasserver/models/tests/test_domain.py 2017-04-05 19:57:09 +0000
+++ src/maasserver/models/tests/test_domain.py 2017-04-28 00:50:14 +0000
@@ -32,7 +32,11 @@
32from maasserver.testing.factory import factory32from maasserver.testing.factory import factory
33from maasserver.testing.testcase import MAASServerTestCase33from maasserver.testing.testcase import MAASServerTestCase
34from netaddr import IPAddress34from netaddr import IPAddress
35from testtools.matchers import MatchesStructure35from testtools.matchers import (
36 Equals,
37 HasLength,
38 MatchesStructure,
39)
36from testtools.testcase import ExpectedException40from testtools.testcase import ExpectedException
3741
3842
@@ -372,6 +376,9 @@
372 dnsresource__domain_id=domain.id)376 dnsresource__domain_id=domain.id)
373 self.assertEqual("0 0 1688 %s." % target, srvrr.rrdata)377 self.assertEqual("0 0 1688 %s." % target, srvrr.rrdata)
374378
379
380class TestRenderRRData(MAASServerTestCase):
381
375 def render_rrdata(self, domain, for_list=False):382 def render_rrdata(self, domain, for_list=False):
376 rr_map = DNSData.objects.get_hostname_dnsdata_mapping(383 rr_map = DNSData.objects.get_hostname_dnsdata_mapping(
377 domain, raw_ttl=True)384 domain, raw_ttl=True)
@@ -414,3 +421,18 @@
414 expected = self.render_rrdata(domain, for_list=False)421 expected = self.render_rrdata(domain, for_list=False)
415 actual = domain.render_json_for_related_rrdata(for_list=False)422 actual = domain.render_json_for_related_rrdata(for_list=False)
416 self.assertItemsEqual(expected, actual)423 self.assertItemsEqual(expected, actual)
424
425 def test_renders_as_dictionary(self):
426 domain = factory.make_Domain()
427 name1 = factory.make_name(prefix='a')
428 name2 = factory.make_name(prefix='b')
429 factory.make_DNSData(name=name1, domain=domain, rrtype='MX')
430 rrdata_list = domain.render_json_for_related_rrdata(as_dict=False)
431 rrdata_dict = domain.render_json_for_related_rrdata(as_dict=True)
432 self.assertThat(rrdata_dict[name1], Equals([rrdata_list[0]]))
433 self.assertThat(rrdata_dict[name1], HasLength(1))
434 factory.make_DNSData(name=name1, domain=domain, rrtype='MX')
435 factory.make_DNSData(name=name2, domain=domain, rrtype='NS')
436 rrdata_dict = domain.render_json_for_related_rrdata(as_dict=True)
437 self.assertThat(rrdata_dict[name1], HasLength(2))
438 self.assertThat(rrdata_dict[name2], HasLength(1))
417439
=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py 2017-04-28 00:50:13 +0000
+++ src/maasserver/testing/factory.py 2017-04-28 00:50:14 +0000
@@ -655,7 +655,7 @@
655 else ip655 else ip
656 for ip in ip_addresses656 for ip in ip_addresses
657 ]657 ]
658 dnsrr = DNSResource(658 dnsrr, _ = DNSResource.objects.get_or_create(
659 name=name, address_ttl=address_ttl,659 name=name, address_ttl=address_ttl,
660 domain=domain)660 domain=domain)
661 dnsrr.save()661 dnsrr.save()