Merge lp:~jtv/maas/test-multi-zonegenerator into lp:~maas-committers/maas/trunk

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 1856
Proposed branch: lp:~jtv/maas/test-multi-zonegenerator
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 284 lines (+189/-22)
2 files modified
src/maasserver/dns.py (+15/-17)
src/maasserver/tests/test_dns.py (+174/-5)
To merge this branch: bzr merge lp:~jtv/maas/test-multi-zonegenerator
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+203506@code.launchpad.net

Commit message

More testing for ZoneGenerator.

Description of the change

Turns out much of ZoneGenerator, a nontrivial class, was untested — along with some of its helpers. All we had was three top-level tests. Here I add some.

Also, _get_forward_zone_configs was confusing because of some forced symmetry with _get_reverse_zones. Basically the nodegroups it received as an argument were ignored; only the domain names were used. Passing it just the domain names makes that much clearer, even if it means a bit less symmetry.

Jeroen

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

[1]

=== modified file 'src/maasserver/dns.py'
[...]

Everything in here is change for the sake of change. If you must, land
it, but I'm not sure there was any lack of clarity before, and there
certainly isn't any more now.

[2]

+class TestLazyDict(TestCase):

This is cool. Thanks for adding it; I was the one who didn't test it the
first time around. Same goes for ZoneGenerator, except for the top-level
tests.

[3]

+    def test_holds_one_value_per_key(self):

Not sure what this demonstrates. lazydict inherits from dict, so this
isn't surprising behaviour.

review: Approve
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thanks. I promise that the changes in dns.py were not just change for the sake of change. They were all things that made me go "huh?" and, in the case of the parameter to _get_forward_zones, saved me a lot of testing because the problem became so much simpler.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Tuesday 28 Jan 2014 10:47:24 you wrote:
> +class TestLazyDict(TestCase):
> + """Tests for `lazydict`."""

Just a small comment - it would be super useful in these test case docstrings
to describe the overall desired behaviour of the object under test and what
the test cases seek to achieve.

I have no idea* what a lazydict is and the test doesn't help me understand
that.

*Actually I do, but I'm playing Devil's Advocate :)

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Not a bad idea, though we'd have to explore the boundaries between what should go in the class docstring and what goes in the test docstring... As it is, the test docstring really only serves as a place to put the proper spelling of the thing that's being tested, without the CamelCase form that the class format imposes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/dns.py'
2--- src/maasserver/dns.py 2014-01-27 11:35:09 +0000
3+++ src/maasserver/dns.py 2014-01-28 10:46:14 +0000
4@@ -169,29 +169,26 @@
5 self.serial = serial
6
7 @staticmethod
8- def _get_forward_nodegroups(nodegroups):
9- """Return a set of all forward nodegroups.
10-
11- This is the set of all managed nodegroups with the same domain as the
12- domain of any of the given nodegroups.
13+ def _filter_dns_managed(nodegroups):
14+ """Return the subset of `nodegroups` for which we manage DNS."""
15+ return set(filter(is_dns_managed, nodegroups))
16+
17+ @staticmethod
18+ def _get_forward_nodegroups(domains):
19+ """Return the set of forward nodegroups for the given `domains`.
20+
21+ These are all nodegroups with any of the given domains.
22 """
23- forward_domains = {nodegroup.name for nodegroup in nodegroups}
24- forward_nodegroups = NodeGroup.objects.filter(name__in=forward_domains)
25- return {
26- nodegroup for nodegroup in forward_nodegroups
27- if is_dns_managed(nodegroup)
28- }
29+ return ZoneGenerator._filter_dns_managed(
30+ NodeGroup.objects.filter(name__in=domains))
31
32 @staticmethod
33 def _get_reverse_nodegroups(nodegroups):
34- """Return a set of all reverse nodegroups.
35+ """Return the set of reverse nodegroups among `nodegroups`.
36
37 This is the subset of the given nodegroups that are managed.
38 """
39- return {
40- nodegroup for nodegroup in nodegroups
41- if is_dns_managed(nodegroup)
42- }
43+ return ZoneGenerator._filter_dns_managed(nodegroups)
44
45 @staticmethod
46 def _get_mappings():
47@@ -235,7 +232,8 @@
48 get_domain(nodegroup), serial=serial, network=network)
49
50 def __iter__(self):
51- forward_nodegroups = self._get_forward_nodegroups(self.nodegroups)
52+ forward_nodegroups = self._get_forward_nodegroups(
53+ {nodegroup.name for nodegroup in self.nodegroups})
54 reverse_nodegroups = self._get_reverse_nodegroups(self.nodegroups)
55 mappings = self._get_mappings()
56 networks = self._get_networks()
57
58=== modified file 'src/maasserver/tests/test_dns.py'
59--- src/maasserver/tests/test_dns.py 2014-01-27 12:42:51 +0000
60+++ src/maasserver/tests/test_dns.py 2014-01-28 10:46:14 +0000
61@@ -15,7 +15,6 @@
62 __all__ = []
63
64
65-from functools import partial
66 from itertools import islice
67 import socket
68
69@@ -33,9 +32,11 @@
70 from maasserver.models import (
71 Config,
72 node as node_module,
73+ NodeGroupInterface,
74 )
75 from maasserver.testing.factory import factory
76 from maasserver.testing.testcase import MAASServerTestCase
77+from maasserver.utils import map_enum
78 from maastesting.celery import CeleryFixture
79 from maastesting.fakemethod import FakeMethod
80 from mock import (
81@@ -59,6 +60,7 @@
82 from provisioningserver.testing.tests.test_bindfixture import dig_call
83 from rabbitfixture.server import allocate_ports
84 from testresources import FixtureResource
85+from testtools import TestCase
86 from testtools.matchers import (
87 IsInstance,
88 MatchesAll,
89@@ -155,6 +157,37 @@
90 self.assertEqual(nodegroups_with_expected_results, results)
91
92
93+class TestLazyDict(TestCase):
94+ """Tests for `lazydict`."""
95+
96+ def test_empty_initially(self):
97+ self.assertEqual({}, dns.lazydict(Mock()))
98+
99+ def test_populates_on_demand(self):
100+ value = factory.make_name('value')
101+ value_dict = dns.lazydict(lambda key: value)
102+ key = factory.make_name('key')
103+ retrieved_value = value_dict[key]
104+ self.assertEqual(value, retrieved_value)
105+ self.assertEqual({key: value}, value_dict)
106+
107+ def test_remembers_elements(self):
108+ value_dict = dns.lazydict(lambda key: factory.make_name('value'))
109+ key = factory.make_name('key')
110+ self.assertEqual(value_dict[key], value_dict[key])
111+
112+ def test_holds_one_value_per_key(self):
113+ value_dict = dns.lazydict(lambda key: key)
114+ key1 = factory.make_name('key')
115+ key2 = factory.make_name('key')
116+
117+ value1 = value_dict[key1]
118+ value2 = value_dict[key2]
119+
120+ self.assertEqual((key1, key2), (value1, value2))
121+ self.assertEqual({key1: key1, key2: key2}, value_dict)
122+
123+
124 class TestDNSConfigModifications(MAASServerTestCase):
125
126 resources = (
127@@ -434,10 +467,126 @@
128 class TestZoneGenerator(MAASServerTestCase):
129 """Tests for :class:x`dns.ZoneGenerator`."""
130
131- # Factory to return an accepted nodegroup with a managed interface.
132- make_node_group = partial(
133- factory.make_node_group, status=NODEGROUP_STATUS.ACCEPTED,
134- management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
135+ def make_node_group(self, **kwargs):
136+ """Create an accepted nodegroup with a managed interface."""
137+ return factory.make_node_group(
138+ status=NODEGROUP_STATUS.ACCEPTED,
139+ management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS, **kwargs)
140+
141+ def test_get_forward_nodegroups_returns_empty_for_unknown_domain(self):
142+ self.assertEqual(
143+ set(),
144+ dns.ZoneGenerator._get_forward_nodegroups(
145+ factory.make_name('domain')))
146+
147+ def test_get_forward_nodegroups_returns_empty_for_no_domains(self):
148+ self.assertEqual(set(), dns.ZoneGenerator._get_forward_nodegroups([]))
149+
150+ def test_get_forward_nodegroups_returns_dns_managed_nodegroups(self):
151+ domain = factory.make_name('domain')
152+ nodegroup = self.make_node_group(name=domain)
153+ self.assertEqual(
154+ {nodegroup},
155+ dns.ZoneGenerator._get_forward_nodegroups([domain]))
156+
157+ def test_get_forward_nodegroups_includes_multiple_domains(self):
158+ nodegroups = [self.make_node_group() for _ in range(3)]
159+ self.assertEqual(
160+ set(nodegroups),
161+ dns.ZoneGenerator._get_forward_nodegroups(
162+ [nodegroup.name for nodegroup in nodegroups]))
163+
164+ def test_get_forward_nodegroups_ignores_non_dns_nodegroups(self):
165+ domain = factory.make_name('domain')
166+ managed_nodegroup = self.make_node_group(name=domain)
167+ factory.make_node_group(
168+ name=domain, status=NODEGROUP_STATUS.ACCEPTED,
169+ management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
170+ factory.make_node_group(
171+ name=domain, status=NODEGROUP_STATUS.ACCEPTED,
172+ management=NODEGROUPINTERFACE_MANAGEMENT.DHCP)
173+ self.assertEqual(
174+ {managed_nodegroup},
175+ dns.ZoneGenerator._get_forward_nodegroups([domain]))
176+
177+ def test_get_forward_nodegroups_ignores_other_domains(self):
178+ nodegroups = [self.make_node_group() for _ in range(2)]
179+ self.assertEqual(
180+ {nodegroups[0]},
181+ dns.ZoneGenerator._get_forward_nodegroups([nodegroups[0].name]))
182+
183+ def test_get_forward_nodegroups_ignores_unaccepted_nodegroups(self):
184+ domain = factory.make_name('domain')
185+ nodegroups = {
186+ status: factory.make_node_group(
187+ status=status, name=domain,
188+ management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
189+ for status in map_enum(NODEGROUP_STATUS).values()
190+ }
191+ self.assertEqual(
192+ {nodegroups[NODEGROUP_STATUS.ACCEPTED]},
193+ dns.ZoneGenerator._get_forward_nodegroups([domain]))
194+
195+ def test_get_reverse_nodegroups_returns_only_dns_managed_nodegroups(self):
196+ nodegroups = {
197+ management: factory.make_node_group(
198+ status=NODEGROUP_STATUS.ACCEPTED, management=management)
199+ for management in map_enum(NODEGROUPINTERFACE_MANAGEMENT).values()
200+ }
201+ self.assertEqual(
202+ {nodegroups[NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS]},
203+ dns.ZoneGenerator._get_reverse_nodegroups(nodegroups.values()))
204+
205+ def test_get_reverse_nodegroups_ignores_other_nodegroups(self):
206+ nodegroups = [self.make_node_group() for _ in range(3)]
207+ self.assertEqual(
208+ {nodegroups[0]},
209+ dns.ZoneGenerator._get_reverse_nodegroups(nodegroups[:1]))
210+
211+ def test_get_reverse_nodegroups_ignores_unaccepted_nodegroups(self):
212+ nodegroups = {
213+ status: factory.make_node_group(
214+ status=status,
215+ management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
216+ for status in map_enum(NODEGROUP_STATUS).values()
217+ }
218+ self.assertEqual(
219+ {nodegroups[NODEGROUP_STATUS.ACCEPTED]},
220+ dns.ZoneGenerator._get_reverse_nodegroups(nodegroups.values()))
221+
222+ def test_get_networks_returns_network(self):
223+ nodegroup = self.make_node_group()
224+ [interface] = nodegroup.get_managed_interfaces()
225+ networks_dict = dns.ZoneGenerator._get_networks()
226+ retrieved_interface = networks_dict[nodegroup]
227+ self.assertEqual([interface.network], retrieved_interface)
228+
229+ def test_get_networks_returns_multiple_networks(self):
230+ nodegroups = [self.make_node_group() for _ in range(3)]
231+ networks_dict = dns.ZoneGenerator._get_networks()
232+ for nodegroup in nodegroups:
233+ [interface] = nodegroup.get_managed_interfaces()
234+ self.assertEqual([interface.network], networks_dict[nodegroup])
235+
236+ def test_get_networks_returns_managed_networks(self):
237+ nodegroups = [
238+ factory.make_node_group(
239+ status=NODEGROUP_STATUS.ACCEPTED, management=management)
240+ for management in map_enum(NODEGROUPINTERFACE_MANAGEMENT).values()
241+ ]
242+ networks_dict = dns.ZoneGenerator._get_networks()
243+ # Force lazydict to evaluate for all these nodegroups.
244+ for nodegroup in nodegroups:
245+ networks_dict[nodegroup]
246+ self.assertEqual(
247+ {
248+ nodegroup: [
249+ interface.network
250+ for interface in nodegroup.get_managed_interfaces()
251+ ]
252+ for nodegroup in nodegroups
253+ },
254+ networks_dict)
255
256 def test_with_no_nodegroups_yields_nothing(self):
257 self.assertEqual([], dns.ZoneGenerator(()).as_list())
258@@ -451,6 +600,26 @@
259 (forward_zone("henry", "10/32"),
260 reverse_zone("henry", "10/32"))))
261
262+ def test_two_managed_interfaces_yields_one_forward_two_reverse_zones(self):
263+ # XXX JeroenVermeulen 2014-01-28 bug=1052339: This patch becomes
264+ # unnecessary once we allow multiple managed interfaces per nodegroup.
265+ self.patch(NodeGroupInterface, 'clean_management')
266+ nodegroup = self.make_node_group()
267+ factory.make_node_group_interface(
268+ nodegroup=nodegroup,
269+ management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
270+ [interface1, interface2] = nodegroup.get_managed_interfaces()
271+
272+ expected_zones = [
273+ forward_zone(
274+ nodegroup.name, interface1.network, interface2.network),
275+ reverse_zone(nodegroup.name, interface1.network),
276+ reverse_zone(nodegroup.name, interface2.network),
277+ ]
278+ self.assertThat(
279+ dns.ZoneGenerator([nodegroup]).as_list(),
280+ MatchesListwise(expected_zones))
281+
282 def test_with_many_nodegroups_yields_many_zones(self):
283 # This demonstrates ZoneGenerator in all-singing all-dancing mode.
284 nodegroups = [