Merge lp:~julian-edwards/maas/upstream-dns into lp:~maas-committers/maas/trunk
- upstream-dns
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Julian Edwards |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1729 |
Proposed branch: | lp:~julian-edwards/maas/upstream-dns |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
440 lines (+153/-5) 14 files modified
etc/maas/templates/dns/named.conf.options.inside.maas.template (+6/-0) src/maasserver/dns.py (+4/-1) src/maasserver/dns_connect.py (+9/-0) src/maasserver/forms.py (+1/-0) src/maasserver/forms_settings.py (+14/-0) src/maasserver/management/commands/set_up_dns.py (+5/-0) src/maasserver/models/config.py (+1/-0) src/maasserver/tests/test_dns.py (+16/-2) src/maastesting/bindfixture.py (+8/-1) src/maastesting/tests/test_bindfixture.py (+19/-0) src/provisioningserver/dns/config.py (+32/-0) src/provisioningserver/dns/tests/test_config.py (+29/-0) src/provisioningserver/tasks.py (+3/-0) src/provisioningserver/tests/test_tasks.py (+6/-1) |
To merge this branch: | bzr merge lp:~julian-edwards/maas/upstream-dns |
Related bugs: | |
Related blueprints: |
Usability points from the training debrief
(Essential)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+193893@code.launchpad.net |
Commit message
Have a DNS forwarders setting ("upstream DNS" in the settings UI) which is written to a special file that can be included in the DNS options {} block.
Description of the change
Add a new Config setting for upstream DNS ("forwarders" in the DNS config) which is written out to a file called named.conf.
This branch turned out to be much bigger than expected because you can't have more than one options block (which would have been easy to just throw in our existing template for DNS). I got this technique from LaMont who set it up this way for his Puppet stuff.
I've also amended the BindFixture so it can take this option although it doesn't use it anywhere when you "make run", it's only set up in tests.
We will also need to make a change in the packaging so that the new file gets included in the named.conf.options file.
Julian Edwards (julian-edwards) wrote : | # |
Thanks for the review Mr V.
On 08/11/13 15:21, Jeroen T. Vermeulen wrote:
> Instead of "non-MAAS domains" you could say something like "domains not managed by this MAAS," to avoid confusion? For example I imagine a beginning user might mistake "non-MAAS domains" for a way of saying "DNS domains, not domains as in MAAS domain controller."
Ok changed, thanks.
> In src/maastesting
>
> forwarders = "forwarders { 1.2.3.4; };"
> ….
> expected_in_file = (
> resources.homedir + '/' + forwarders.
>
> It looks as if you're doing effectively an os.path.join() on a directory and the actual DNS options text. Didn't you mean for “forwarders” to look like a filename?
It's checking text exactly as it should appear in the file -
os.path.join would be inappropriate here.
> Also, I don't know about resources.homedir, but '/' is unicode and forwarders.
Grar, that's a leftover from fiddling, I fixed it. Thanks for spotting.
> In src/provisionin
>
> """Write out the named.conf.
>
> This file should be included by the top-level named.conf.options
> inside its 'options' block. MAAS cannot write the options file itself,
> so relies on either the DNSFixture in the test suite, or the packaging.
> Both should set that file up appropriately to include our file.
> """
>
> Only a small nitpick, but I'd lead with the production situation ("MAAS cannot write the options file itself, so relies on the packaging") and make the equivalent test rigging parenthetical.
Mind if I ignore that? Mostly because I'm not sure what you want and it
makes perfect sense already :)
> And to make up for that nitpick, here are a few tricks to offload some work from this function:
>
> template_path = os.path.join(
> locate_
> "named.
>
> ...can be simplified to:
>
> template_path = locate_config(
> TEMPLATES_DIR, "named.
You sure about that?
>
> Next there's...
>
> with open(template_path, "r") as f:
> template = tempita.
>
> Tempita has a Template factory called from_filename which does the reading for you, and also passes the filename as the template name.
>
> So try simply:
>
> template = tempita.
Right! Fixed.
>
> .
>
> In src/provisionin
>
> fake_dns = "1.2.3.4"
>
> We have a factory method for that! Use ge...
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~julian-edwards/maas/upstream-dns into lp:maas failed. Below is the output from the failed tests.
Ign http://
Get:1 http://
Get:2 http://
Ign http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Get:6 http://
Hit http://
Hit http://
Get:7 http://
Hit http://
Hit http://
Get:8 http://
Hit http://
Hit http://
Hit http://
Hit http://
Ign http://
Get:9 http://
Ign http://
Get:10 http://
Get:11 http://
Get:12 http://
Hit http://
Hit http://
Ign http://
Ign http://
Ign http://
Ign http://
Fetched 290 kB in 0s (1,411 kB/s)
Reading package lists...
sudo DEBIAN_
--
Julian Edwards (julian-edwards) wrote : | # |
Idiot linter.
Jeroen T. Vermeulen (jtv) wrote : | # |
Julian wrote:
> > In src/maastesting
> >
> > forwarders = "forwarders { 1.2.3.4; };"
> > ….
> > expected_in_file = (
> > resources.homedir + '/' + forwarders.
> >
> > It looks as if you're doing effectively an os.path.join() on a directory and the actual DNS options text. Didn't you mean for “forwarders” to look like a filename?
>
> It's checking text exactly as it should appear in the file -
> os.path.join would be inappropriate here.
Not my point. My point is that you seem to be conflating the text of the snippet that you want to include, with the path to the file that contains the snippet. You're constructing something like
/tmp/
Not what you meant, right? AFAICS you meant "forwarders" to look like a filename, not a piece of configuration.
.
> > template_path = os.path.join(
> > locate_
> > "named.
> >
> > ...can be simplified to:
> >
> > template_path = locate_config(
> > TEMPLATES_DIR, "named.
>
> You sure about that?
Quite sure, thank you.
.
> > And finally,
> >
> > 436 FileExists(),
> > 437 FileExists(),
> > 438 FileExists(),
> > 439 + FileExists(),
> >
> > WTF? :-)
>
> It's one of those crazy mass-checks that Raphers is so fond of. I just
> made it a bit bigger :)
And therefore, better.
Jeroen
Julian Edwards (julian-edwards) wrote : | # |
On 08/11/13 17:41, Jeroen T. Vermeulen wrote:
> Julian wrote:
>
>>> In src/maastesting
>>>
>>> forwarders = "forwarders { 1.2.3.4; };"
>>> ….
>>> expected_in_file = (
>>> resources.homedir + '/' + forwarders.
>>>
>>> It looks as if you're doing effectively an os.path.join() on a directory and the actual DNS options text. Didn't you mean for “forwarders” to look like a filename?
>>
>> It's checking text exactly as it should appear in the file -
>> os.path.join would be inappropriate here.
>
> Not my point. My point is that you seem to be conflating the text of the snippet that you want to include, with the path to the file that contains the snippet. You're constructing something like
>
> /tmp/tmp4SANQ84
>
> Not what you meant, right? AFAICS you meant "forwarders" to look like a filename, not a piece of configuration.
No - I want it to look like configuration. You're the one who's
conflating two things here:
1. The include file path
2. The contents of the included file
The snippet you have above from test_bindfixture.py is very misleading,
remember that there's TWO files I am dealing with; the top-level options
that *includes* the other file which contains the "forwarders" option.
>
> .
>
>>> template_path = os.path.join(
>>> locate_
>>> "named.
>>>
>>> ...can be simplified to:
>>>
>>> template_path = locate_config(
>>> TEMPLATES_DIR, "named.
>>
>> You sure about that?
>
> Quite sure, thank you.
Why does the rest of the code need to use it then? It takes into
account the environment to override the template location.
> And therefore, better.
\m/
Or something…
:)
Julian Edwards (julian-edwards) wrote : | # |
On 11/11/13 09:42, Julian Edwards wrote:
> No - I want it to look like configuration. You're the one who's
> conflating two things here:
>
> 1. The include file path
> 2. The contents of the included file
>
> The snippet you have above from test_bindfixture.py is very misleading,
> remember that there's TWO files I am dealing with; the top-level options
> that *includes* the other file which contains the "forwarders" option.
Ok as soon as I write this I see why I sent you up the garden path....
forwarders = "forwarders { 1.2.3.4; };"
should read:
forwarders = "named.
I will fix this in a subsequent branch. Sorry…
Preview Diff
1 | === added file 'etc/maas/templates/dns/named.conf.options.inside.maas.template' |
2 | --- etc/maas/templates/dns/named.conf.options.inside.maas.template 1970-01-01 00:00:00 +0000 |
3 | +++ etc/maas/templates/dns/named.conf.options.inside.maas.template 2013-11-08 06:27:21 +0000 |
4 | @@ -0,0 +1,6 @@ |
5 | +{{if upstream_dns}} |
6 | + forwarders { |
7 | + {{upstream_dns}}; |
8 | + }; |
9 | +{{endif}} |
10 | + |
11 | |
12 | === modified file 'src/maasserver/dns.py' |
13 | --- src/maasserver/dns.py 2013-10-18 16:57:37 +0000 |
14 | +++ src/maasserver/dns.py 2013-11-08 06:27:21 +0000 |
15 | @@ -37,6 +37,7 @@ |
16 | ) |
17 | from maasserver.exceptions import MAASException |
18 | from maasserver.models import ( |
19 | + Config, |
20 | DHCPLease, |
21 | NodeGroup, |
22 | NodeGroupInterface, |
23 | @@ -312,7 +313,9 @@ |
24 | if not write_conf: |
25 | return |
26 | zones = ZoneGenerator(NodeGroup.objects.all()).as_list() |
27 | + upstream_dns = Config.objects.get_config("upstream_dns") |
28 | tasks.write_full_dns_config.delay( |
29 | zones=zones, |
30 | callback=tasks.rndc_command.subtask( |
31 | - args=[['reload'], reload_retry])) |
32 | + args=[['reload'], reload_retry]), |
33 | + upstream_dns=upstream_dns) |
34 | |
35 | === modified file 'src/maasserver/dns_connect.py' |
36 | --- src/maasserver/dns_connect.py 2013-10-07 09:12:40 +0000 |
37 | +++ src/maasserver/dns_connect.py 2013-11-08 06:27:21 +0000 |
38 | @@ -23,6 +23,7 @@ |
39 | from django.dispatch import receiver |
40 | from maasserver.enum import NODEGROUPINTERFACE_MANAGEMENT |
41 | from maasserver.models import ( |
42 | + Config, |
43 | Node, |
44 | NodeGroup, |
45 | NodeGroupInterface, |
46 | @@ -88,3 +89,11 @@ |
47 | |
48 | |
49 | connect_to_field_change(dns_post_edit_hostname_Node, Node, 'hostname') |
50 | + |
51 | + |
52 | +def upstream_dns_changed(sender, instance, created, **kwargs): |
53 | + from maasserver.dns import write_full_dns_config |
54 | + write_full_dns_config() |
55 | + |
56 | + |
57 | +Config.objects.config_changed_connect("upstream_dns", upstream_dns_changed) |
58 | |
59 | === modified file 'src/maasserver/forms.py' |
60 | --- src/maasserver/forms.py 2013-10-15 11:51:48 +0000 |
61 | +++ src/maasserver/forms.py 2013-11-08 06:27:21 +0000 |
62 | @@ -626,6 +626,7 @@ |
63 | maas_name = get_config_field('maas_name') |
64 | enlistment_domain = get_config_field('enlistment_domain') |
65 | http_proxy = get_config_field('http_proxy') |
66 | + upstream_dns = get_config_field('upstream_dns') |
67 | |
68 | |
69 | class CommissioningForm(ConfigForm): |
70 | |
71 | === modified file 'src/maasserver/forms_settings.py' |
72 | --- src/maasserver/forms_settings.py 2013-10-07 09:12:40 +0000 |
73 | +++ src/maasserver/forms_settings.py 2013-11-08 06:27:21 +0000 |
74 | @@ -170,6 +170,20 @@ |
75 | "proxy).") |
76 | } |
77 | }, |
78 | + 'upstream_dns': { |
79 | + 'default': None, |
80 | + 'form': forms.GenericIPAddressField, |
81 | + 'form_kwargs': { |
82 | + 'label': ( |
83 | + "Upstream DNS used to resolve domains not managed by this " |
84 | + "MAAS"), |
85 | + 'required': False, |
86 | + 'help_text': ( |
87 | + "Only used when MAAS is running its own DNS server. This " |
88 | + "value is used as the value of 'forwarders' in the DNS " |
89 | + "server config.") |
90 | + } |
91 | + }, |
92 | 'default_distro_series': { |
93 | 'default': DISTRO_SERIES.precise, |
94 | 'form': forms.ChoiceField, |
95 | |
96 | === modified file 'src/maasserver/management/commands/set_up_dns.py' |
97 | --- src/maasserver/management/commands/set_up_dns.py 2013-10-18 16:57:37 +0000 |
98 | +++ src/maasserver/management/commands/set_up_dns.py 2013-11-08 06:27:21 +0000 |
99 | @@ -26,8 +26,10 @@ |
100 | from optparse import make_option |
101 | |
102 | from django.core.management.base import BaseCommand |
103 | +from maasserver.models import Config |
104 | from provisioningserver.dns.config import ( |
105 | DNSConfig, |
106 | + set_up_options_conf, |
107 | setup_rndc, |
108 | ) |
109 | |
110 | @@ -49,6 +51,9 @@ |
111 | def handle(self, *args, **options): |
112 | no_clobber = options.get('no_clobber') |
113 | setup_rndc() |
114 | + upstream_dns = Config.objects.get_config("upstream_dns") |
115 | + set_up_options_conf( |
116 | + overwrite=not no_clobber, upstream_dns=upstream_dns) |
117 | config = DNSConfig() |
118 | config.write_config( |
119 | overwrite=not no_clobber, zone_names=(), reverse_zone_names=()) |
120 | |
121 | === modified file 'src/maasserver/models/config.py' |
122 | --- src/maasserver/models/config.py 2013-10-18 17:02:17 +0000 |
123 | +++ src/maasserver/models/config.py 2013-11-08 06:27:21 +0000 |
124 | @@ -56,6 +56,7 @@ |
125 | 'default_distro_series': DISTRO_SERIES.precise, |
126 | 'commissioning_distro_series': DISTRO_SERIES.precise, |
127 | 'http_proxy': None, |
128 | + 'upstream_dns': None, |
129 | ## /settings |
130 | } |
131 | |
132 | |
133 | === modified file 'src/maasserver/tests/test_dns.py' |
134 | --- src/maasserver/tests/test_dns.py 2013-10-18 16:35:07 +0000 |
135 | +++ src/maasserver/tests/test_dns.py 2013-11-08 06:27:21 +0000 |
136 | @@ -30,7 +30,10 @@ |
137 | NODEGROUP_STATUS, |
138 | NODEGROUPINTERFACE_MANAGEMENT, |
139 | ) |
140 | -from maasserver.models import node as node_module |
141 | +from maasserver.models import ( |
142 | + Config, |
143 | + node as node_module, |
144 | + ) |
145 | from maasserver.testing.factory import factory |
146 | from maasserver.testing.testcase import MAASServerTestCase |
147 | from maastesting.bindfixture import BINDServer |
148 | @@ -38,6 +41,7 @@ |
149 | from maastesting.fakemethod import FakeMethod |
150 | from maastesting.tests.test_bindfixture import dig_call |
151 | from mock import ( |
152 | + ANY, |
153 | call, |
154 | Mock, |
155 | ) |
156 | @@ -285,9 +289,19 @@ |
157 | self.assertEqual( |
158 | ([(['reload'], True)]), recorder.extract_args()) |
159 | |
160 | + def test_write_full_dns_passes_upstream_dns_parameter(self): |
161 | + self.patch(settings, 'DNS_CONNECT', True) |
162 | + self.create_managed_nodegroup() |
163 | + random_ip = factory.getRandomIPAddress() |
164 | + Config.objects.set_config("upstream_dns", random_ip) |
165 | + patched_task = self.patch(dns.tasks.write_full_dns_config, "delay") |
166 | + dns.write_full_dns_config() |
167 | + patched_task.assert_called_once_with( |
168 | + zones=ANY, callback=ANY, upstream_dns=random_ip) |
169 | + |
170 | def test_write_full_dns_doesnt_call_task_it_no_interface_configured(self): |
171 | self.patch(settings, 'DNS_CONNECT', True) |
172 | - patched_task = self.patch(tasks, 'write_full_dns_config') |
173 | + patched_task = self.patch(dns.tasks.write_full_dns_config, "delay") |
174 | dns.write_full_dns_config() |
175 | self.assertEqual(0, patched_task.call_count) |
176 | |
177 | |
178 | === modified file 'src/maastesting/bindfixture.py' |
179 | --- src/maastesting/bindfixture.py 2013-10-18 16:57:37 +0000 |
180 | +++ src/maastesting/bindfixture.py 2013-11-08 06:27:21 +0000 |
181 | @@ -69,6 +69,8 @@ |
182 | :ivar homedir: A directory where to put all the files the |
183 | BIND server needs (configuration files and executable). |
184 | :ivar log_file: The log file allocated for the server. |
185 | + :ivar include_in_options: Name of a file under homedir to include inside |
186 | + the options block. |
187 | """ |
188 | |
189 | # The full path where the 'named' executable can be |
190 | @@ -88,6 +90,9 @@ |
191 | listen-on port {{port}} {127.0.0.1;}; |
192 | pid-file "{{homedir}}/named.pid"; |
193 | session-keyfile "{{homedir}}/session.key"; |
194 | + {{if include_in_options}} |
195 | + include "{{homedir}}/{{include_in_options}}"; |
196 | + {{endif}} |
197 | }; |
198 | |
199 | logging{ |
200 | @@ -105,13 +110,14 @@ |
201 | """)) |
202 | |
203 | def __init__(self, port=None, rndc_port=None, homedir=None, |
204 | - log_file=None): |
205 | + log_file=None, include_in_options=None): |
206 | super(BINDServerResources, self).__init__() |
207 | self._defaults = dict( |
208 | port=port, |
209 | rndc_port=rndc_port, |
210 | homedir=homedir, |
211 | log_file=log_file, |
212 | + include_in_options=include_in_options, |
213 | ) |
214 | |
215 | def setUp(self, overwrite_config=False): |
216 | @@ -139,6 +145,7 @@ |
217 | self.NAMED_CONF_TEMPLATE.substitute( |
218 | homedir=self.homedir, port=self.port, |
219 | log_file=self.log_file, |
220 | + include_in_options=self.include_in_options, |
221 | extra=namedrndcconf)) |
222 | atomic_write( |
223 | GENERATED_HEADER + named_conf, self.conf_file) |
224 | |
225 | === modified file 'src/maastesting/tests/test_bindfixture.py' |
226 | --- src/maastesting/tests/test_bindfixture.py 2013-10-07 09:12:40 +0000 |
227 | +++ src/maastesting/tests/test_bindfixture.py 2013-11-08 06:27:21 +0000 |
228 | @@ -27,6 +27,7 @@ |
229 | Contains, |
230 | FileContains, |
231 | FileExists, |
232 | + Not, |
233 | ) |
234 | from testtools.testcase import gather_details |
235 | |
236 | @@ -90,6 +91,7 @@ |
237 | self.assertIsInstance(resources.rndc_port, int) |
238 | self.assertIsInstance(resources.homedir, unicode) |
239 | self.assertIsInstance(resources.log_file, unicode) |
240 | + self.assertIs(resources.include_in_options, None) |
241 | self.assertIsInstance(resources.named_file, unicode) |
242 | self.assertIsInstance(resources.conf_file, unicode) |
243 | self.assertIsInstance( |
244 | @@ -110,6 +112,23 @@ |
245 | FileContains(matcher=Contains( |
246 | b'default-port %s' % ( |
247 | resources.rndc_port)))) |
248 | + # This should ideally be in its own test but it's here to cut |
249 | + # test run time. See test_setUp_honours_include_in_options() |
250 | + # as its counterpart. |
251 | + self.assertThat( |
252 | + resources.conf_file, |
253 | + Not(FileContains(matcher=Contains( |
254 | + "forwarders")))) |
255 | + |
256 | + def test_setUp_honours_include_in_options(self): |
257 | + forwarders = "forwarders { 1.2.3.4; };" |
258 | + with BINDServerResources(include_in_options=forwarders) as resources: |
259 | + expected_in_file = ( |
260 | + resources.homedir + '/' + forwarders).encode("ascii") |
261 | + self.assertThat( |
262 | + resources.conf_file, |
263 | + FileContains(matcher=Contains( |
264 | + expected_in_file))) |
265 | |
266 | def test_defaults_reallocated_after_teardown(self): |
267 | seen_homedirs = set() |
268 | |
269 | === modified file 'src/provisioningserver/dns/config.py' |
270 | --- src/provisioningserver/dns/config.py 2013-10-15 11:35:10 +0000 |
271 | +++ src/provisioningserver/dns/config.py 2013-11-08 06:27:21 +0000 |
272 | @@ -17,6 +17,7 @@ |
273 | 'DNSForwardZoneConfig', |
274 | 'DNSReverseZoneConfig', |
275 | 'setup_rndc', |
276 | + 'set_up_options_conf', |
277 | ] |
278 | |
279 | |
280 | @@ -47,6 +48,7 @@ |
281 | |
282 | |
283 | MAAS_NAMED_CONF_NAME = 'named.conf.maas' |
284 | +MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME = 'named.conf.options.inside.maas' |
285 | MAAS_NAMED_RNDC_CONF_NAME = 'named.conf.rndc.maas' |
286 | MAAS_RNDC_CONF_NAME = 'rndc.conf.maas' |
287 | |
288 | @@ -154,6 +156,35 @@ |
289 | TEMPLATES_DIR = 'templates/dns' |
290 | |
291 | |
292 | +def set_up_options_conf(overwrite=True, **kwargs): |
293 | + """Write out the named.conf.options.inside.maas file. |
294 | + |
295 | + This file should be included by the top-level named.conf.options |
296 | + inside its 'options' block. MAAS cannot write the options file itself, |
297 | + so relies on either the DNSFixture in the test suite, or the packaging. |
298 | + Both should set that file up appropriately to include our file. |
299 | + """ |
300 | + template_path = os.path.join( |
301 | + locate_config(TEMPLATES_DIR), |
302 | + "named.conf.options.inside.maas.template") |
303 | + template = tempita.Template.from_filename(template_path) |
304 | + |
305 | + # Make sure "upstream_dns" is set at least to None. It's a |
306 | + # special piece of config that can't be obtained in celery |
307 | + # task code and we don't want to require that every call site |
308 | + # has to specify it. If it's not set, the substitution will |
309 | + # fail with the default template that uses this value. |
310 | + kwargs.setdefault("upstream_dns") |
311 | + try: |
312 | + rendered = template.substitute(kwargs) |
313 | + except NameError as error: |
314 | + raise DNSConfigFail(*error.args) |
315 | + |
316 | + target_path = os.path.join( |
317 | + conf.DNS_CONFIG_DIR, MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME) |
318 | + atomic_write(rendered, target_path, overwrite=overwrite, mode=0644) |
319 | + |
320 | + |
321 | class DNSConfigBase: |
322 | __metaclass__ = ABCMeta |
323 | |
324 | @@ -183,6 +214,7 @@ |
325 | return tempita.Template(f.read(), name=self.template_path) |
326 | |
327 | def render_template(self, template, **kwargs): |
328 | + """Substitute supplied kwargs into the supplied Tempita template.""" |
329 | try: |
330 | return template.substitute(kwargs) |
331 | except NameError as error: |
332 | |
333 | === modified file 'src/provisioningserver/dns/tests/test_config.py' |
334 | --- src/provisioningserver/dns/tests/test_config.py 2013-10-15 11:51:48 +0000 |
335 | +++ src/provisioningserver/dns/tests/test_config.py 2013-11-08 06:27:21 +0000 |
336 | @@ -48,8 +48,10 @@ |
337 | extract_suggested_named_conf, |
338 | generate_rndc, |
339 | MAAS_NAMED_CONF_NAME, |
340 | + MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME, |
341 | MAAS_NAMED_RNDC_CONF_NAME, |
342 | MAAS_RNDC_CONF_NAME, |
343 | + set_up_options_conf, |
344 | setup_rndc, |
345 | shortened_reversed_ip, |
346 | TEMPLATES_DIR, |
347 | @@ -93,6 +95,33 @@ |
348 | conf_content = stream.read() |
349 | self.assertIn(content, conf_content) |
350 | |
351 | + def test_set_up_options_conf_writes_configuration(self): |
352 | + dns_conf_dir = self.make_dir() |
353 | + self.patch(conf, 'DNS_CONFIG_DIR', dns_conf_dir) |
354 | + fake_dns = factory.getRandomIPAddress() |
355 | + set_up_options_conf(upstream_dns=fake_dns) |
356 | + target_file = os.path.join( |
357 | + dns_conf_dir, MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME) |
358 | + self.assertThat( |
359 | + target_file, |
360 | + FileContains(matcher=Contains(fake_dns))) |
361 | + |
362 | + def test_set_up_options_conf_handles_no_upstream_dns(self): |
363 | + dns_conf_dir = self.make_dir() |
364 | + self.patch(conf, 'DNS_CONFIG_DIR', dns_conf_dir) |
365 | + set_up_options_conf() |
366 | + target_file = os.path.join( |
367 | + dns_conf_dir, MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME) |
368 | + self.assertThat(target_file, FileExists()) |
369 | + |
370 | + def test_set_up_options_conf_raises_on_bad_template(self): |
371 | + template = self.make_file( |
372 | + name="named.conf.options.inside.maas.template", |
373 | + contents=b"{{nonexistent}}") |
374 | + self.patch(config, "TEMPLATES_DIR", os.path.dirname(template)) |
375 | + exception = self.assertRaises(DNSConfigFail, set_up_options_conf) |
376 | + self.assertIn("name 'nonexistent' is not defined", repr(exception)) |
377 | + |
378 | def test_rndc_config_includes_default_controls(self): |
379 | dns_conf_dir = self.make_dir() |
380 | self.patch(conf, 'DNS_CONFIG_DIR', dns_conf_dir) |
381 | |
382 | === modified file 'src/provisioningserver/tasks.py' |
383 | --- src/provisioningserver/tasks.py 2013-10-15 11:51:48 +0000 |
384 | +++ src/provisioningserver/tasks.py 2013-11-08 06:27:21 +0000 |
385 | @@ -43,6 +43,7 @@ |
386 | from provisioningserver.dns.config import ( |
387 | DNSConfig, |
388 | execute_rndc_command, |
389 | + set_up_options_conf, |
390 | setup_rndc, |
391 | ) |
392 | from provisioningserver.omshell import Omshell |
393 | @@ -197,6 +198,8 @@ |
394 | # Write main config file. |
395 | dns_config = DNSConfig(zones=zones) |
396 | dns_config.write_config(**kwargs) |
397 | + # Write the included options file. |
398 | + set_up_options_conf(**kwargs) |
399 | if callback is not None: |
400 | callback.delay() |
401 | |
402 | |
403 | === modified file 'src/provisioningserver/tests/test_tasks.py' |
404 | --- src/provisioningserver/tests/test_tasks.py 2013-10-15 11:51:48 +0000 |
405 | +++ src/provisioningserver/tests/test_tasks.py 2013-11-08 06:27:21 +0000 |
406 | @@ -57,6 +57,7 @@ |
407 | DNSForwardZoneConfig, |
408 | DNSReverseZoneConfig, |
409 | MAAS_NAMED_CONF_NAME, |
410 | + MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME, |
411 | MAAS_NAMED_RNDC_CONF_NAME, |
412 | MAAS_RNDC_CONF_NAME, |
413 | ) |
414 | @@ -450,7 +451,8 @@ |
415 | command = factory.getRandomString() |
416 | result = write_full_dns_config.delay( |
417 | zones=zones, |
418 | - callback=rndc_command.subtask(args=[command])) |
419 | + callback=rndc_command.subtask(args=[command]), |
420 | + upstream_dns=factory.getRandomIPAddress()) |
421 | |
422 | forward_file_name = 'zone.%s' % domain |
423 | reverse_file_name = 'zone.0.168.192.in-addr.arpa' |
424 | @@ -461,6 +463,8 @@ |
425 | os.path.join(self.dns_conf_dir, forward_file_name), |
426 | os.path.join(self.dns_conf_dir, reverse_file_name), |
427 | os.path.join(self.dns_conf_dir, MAAS_NAMED_CONF_NAME), |
428 | + os.path.join( |
429 | + self.dns_conf_dir, MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME), |
430 | ), |
431 | MatchesListwise( |
432 | ( |
433 | @@ -469,6 +473,7 @@ |
434 | FileExists(), |
435 | FileExists(), |
436 | FileExists(), |
437 | + FileExists(), |
438 | ))) |
439 | |
440 | def test_write_full_dns_attached_to_dns_worker_queue(self): |
The branch as a whole looks great, with lots of helpful explanation. It brings out the simplicity in what, in the wrong hands, could have been short but incomprehensible logic. As always though, I have remarks!
First off, I have a small suggestion for the UI label in src/maasserver/ forms_settings. py (form_kwargs, ll. 81—88 of the diff):
Instead of "non-MAAS domains" you could say something like "domains not managed by this MAAS," to avoid confusion? For example I imagine a beginning user might mistake "non-MAAS domains" for a way of saying "DNS domains, not domains as in MAAS domain controller."
.
In src/maastesting /tests/ test_bindfixtur e.py, I don't get what test_setUp_ honours_ include_ in_options does with “forwarders” and “expected_in_file” (ll. 255—258 of the diff):
forwarders = "forwarders { 1.2.3.4; };"
expected_ in_file = (
resources. homedir + '/' + forwarders. encode( "ascii" ))
….
It looks as if you're doing effectively an os.path.join() on a directory and the actual DNS options text. Didn't you mean for “forwarders” to look like a filename?
Also, I don't know about resources.homedir, but '/' is unicode and forwarders. encode( "ascii" ) is bytes, right? What's the point of the encoding? If you mean to produce the whole thing as bytes, why not encode the whole thing at once with os.path. join(.. .).encode( 'ascii' )? If resources.homedir is still raw bytes in python3, we can probably fix it in the BIND fixture.
.
In src/provisionin gserver/ dns/config. py, set_up_options_conf (ll. 290—317 of the diff):
"""Write out the named.conf. options. inside. maas file.
This file should be included by the top-level named.conf.options
inside its 'options' block. MAAS cannot write the options file itself,
so relies on either the DNSFixture in the test suite, or the packaging.
Both should set that file up appropriately to include our file.
"""
Only a small nitpick, but I'd lead with the production situation ("MAAS cannot write the options file itself, so relies on the packaging") and make the equivalent test rigging parenthetical.
And to make up for that nitpick, here are a few tricks to offload some work from this function:
template_path = os.path.join(
locate_ config( TEMPLATES_ DIR),
"named. conf.options. inside. maas.template" )
...can be simplified to:
template_path = locate_config(
TEMPLATES_ DIR, "named. conf.options. inside. maas.template" )
Next there's...
with open(template_path, "r") as f: Template( f.read( ), name=template_path)
template = tempita.
Tempita has a Template factory called from_filename which does the reading for you, and also passes the filename as the template name.
So try simply:
template = tempita. Template. from_filename( template_ path)
.
In src/provisionin gserver/ dns/tests/ test_config. py, test_set_ up_options_ conf_writes_ configuration hard-codes an IP address (l. 353 of the diff):
fake_dns = "1.2.3.4"
We have a factory method for that! Use getRandomIPAddr ess().
.
In that same file, test_set_ up_options_ conf_raises_ on_bad_ template does thi...