Merge lp:~allenap/maas/celery-in-dns-removal into lp:~maas-committers/maas/trunk
- celery-in-dns-removal
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 3454 |
Proposed branch: | lp:~allenap/maas/celery-in-dns-removal |
Merge into: | lp:~maas-committers/maas/trunk |
Prerequisite: | lp:~allenap/maas/celery-in-dns-remove-setup-rndc-configuration |
Diff against target: |
2017 lines (+659/-823) 27 files modified
docs/development/lease-scanning-and-dns.rst (+8/-12) docs/development/tagging.rst (+5/-0) etc/maas/templates/dns/named.conf.options.inside.maas.template (+5/-4) etc/maas/templates/dns/named.conf.template (+3/-1) required-packages/base (+0/-1) src/maas/celeryconfig.py (+0/-34) src/maas/settings.py (+0/-3) src/maasserver/dns/config.py (+101/-74) src/maasserver/dns/connect.py (+20/-20) src/maasserver/dns/tests/test_config.py (+76/-65) src/maasserver/management/commands/write_dns_config.py (+2/-2) src/maasserver/models/node.py (+4/-4) src/maasserver/models/tests/test_dhcplease.py (+2/-2) src/maasserver/models/tests/test_node.py (+4/-4) src/maasserver/start_up.py (+2/-2) src/maasserver/tests/test_commands_write_dns_config.py (+3/-3) src/maasserver/tests/test_forms_network.py (+6/-6) src/maasserver/tests/test_start_up.py (+1/-1) src/maastesting/celery.py (+0/-84) src/maastesting/tests/test_celery.py (+0/-67) src/provisioningserver/dns/actions.py (+142/-0) src/provisioningserver/dns/tests/test_actions.py (+271/-0) src/provisioningserver/dns/tests/test_config.py (+4/-3) src/provisioningserver/logger/utils.py (+0/-47) src/provisioningserver/rpc/clusterservice.py (+0/-4) src/provisioningserver/tasks.py (+0/-153) src/provisioningserver/tests/test_tasks.py (+0/-227) |
To merge this branch: | bzr merge lp:~allenap/maas/celery-in-dns-removal |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+241852@code.launchpad.net |
Commit message
Remove all remaining Celery dependencies.
Description of the change
Much of this is mechanical, including name changes for several
functions, and fair amount of code has been removed.
I changed names because my brain was aching trying to follow all the
calls. The API had grown ad-hoc and there was no consistency. There are
still some warts (there are code comments to explain). Anyway, the main
things I changed are:
- In the region the functions to update DNS settings/options and zones
are all prefixed with "dns_".
- In the cluster the functions to update BIND configurations and write
zones files are all prefixed with "bind_". These are lower-level than
the dns_* functions.
There's no RPC between region and cluster, so the division is really
just a conceptual one, for now. As before, we only ever update a single
DNS server which runs on the same machine as the region controller.
To be clear: MAAS does not support DNS on multiple region controllers,
and never has, so don't worry that that's missing here. The division of
responsibility between the dns_* and bind_* functions lends itself to
solving this later.
Gavin Panella (allenap) wrote : | # |
Thank you for taking this on, I really appreciate it.
Gavin Panella (allenap) wrote : | # |
This is now ready to land. After discussion with Andres, I'm holding on this because it doesn't really fit with the goals of 1.7.1. It's up to the folk in Austin this week to decide when to land it :)
Julian Edwards (julian-edwards) wrote : | # |
On Saturday 15 Nov 2014 11:46:41 you wrote:
> To be clear: MAAS does not support DNS on multiple region controllers,
> and never has, so don't worry that that's missing here. The division of
> responsibility between the dns_* and bind_* functions lends itself to
> solving this later.
Just a thought, but if each region controller physical host runs a bind daemon
then a per-host lock will suffice to duplicate the jobs to the right places.
This has the advantage of making our DNS HA.
Gavin Panella (allenap) wrote : | # |
> Just a thought, but if each region controller physical host runs a
> bind daemon then a per-host lock will suffice to duplicate the jobs to
> the right places.
It will, but a web API request to, say, provision a node arrives at only
one host (different each time maybe, but only one host sees each
individual request), and that's when we trigger updates to DNS. The
region controller that services that request needs to be able to tell at
least one region controller on each other host in the region to update
BIND's configuration and zone files.
> This has the advantage of making our DNS HA.
We have a "mesh" so that every region controller can talk to every
cluster controller, and we could extend that so that every region
controller can talk to every other region controller, and thus address
the problem above.
Alternative #1: we could set up BIND to be HA on its own. Its config
would be updated by a periodic job, say, but zone data updates would
come via nsupdate. BIND would then sort out distributing those changes
as required.
Alternative #2: we create an independent BIND management service. The
region calls out to it when a change is needed and it then encapsulates
configuration changes, zone data changes, and HA, and the region doesn't
need to know.
Personally I think I like #2 most. I suspect it might also be the least
work. Meshing together all the region controllers was originally my
favourite, but I see many possibilities for deadlock there.
Julian Edwards (julian-edwards) wrote : | # |
On Tuesday 18 Nov 2014 10:48:21 you wrote:
> > Just a thought, but if each region controller physical host runs a
> > bind daemon then a per-host lock will suffice to duplicate the jobs to
> > the right places.
>
> It will, but a web API request to, say, provision a node arrives at only
> one host (different each time maybe, but only one host sees each
> individual request), and that's when we trigger updates to DNS. The
> region controller that services that request needs to be able to tell at
> least one region controller on each other host in the region to update
> BIND's configuration and zone files.
Right, that's what I mean by a per-host lock. We just create a job on every
host.
>
> > This has the advantage of making our DNS HA.
>
> We have a "mesh" so that every region controller can talk to every
> cluster controller, and we could extend that so that every region
> controller can talk to every other region controller, and thus address
> the problem above.
We could also do PG triggers.
>
> Alternative #1: we could set up BIND to be HA on its own. Its config
> would be updated by a periodic job, say, but zone data updates would
> come via nsupdate. BIND would then sort out distributing those changes
> as required.
We would still have the problem of deciding from which appserver we send
updates / amend the zone file. But it would be nice to use its own
replication!
>
> Alternative #2: we create an independent BIND management service. The
> region calls out to it when a change is needed and it then encapsulates
> configuration changes, zone data changes, and HA, and the region doesn't
> need to know.
>
> Personally I think I like #2 most. I suspect it might also be the least
> work. Meshing together all the region controllers was originally my
> favourite, but I see many possibilities for deadlock there.
It's an idea. We should put something together to discuss all of these
options.
I suspect the trigger thing might be the least work but I've not thought hard
about it yet.
MAAS Lander (maas-lander) wrote : | # |
Voting does not meet specified criteria. Required: Approve >= 1, Disapprove == 0. Got: .
Gavin Panella (allenap) : | # |
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~allenap/maas/celery-in-dns-removal into lp:maas failed. Below is the output from the failed tests.
Ign http://
Ign http://
Get:1 http://
Ign http://
Get:2 http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Hit http://
Get:6 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:7 http://
Get:8 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Ign http://
Ign http://
Fetched 544 kB in 2s (194 kB/s)
Reading package lists...
sudo DEBIAN_
--
MAAS Lander (maas-lander) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file 'docs/development/lease-scanning-and-dns.rst' |
2 | --- docs/development/lease-scanning-and-dns.rst 2014-10-02 11:53:59 +0000 |
3 | +++ docs/development/lease-scanning-and-dns.rst 2015-01-13 18:36:01 +0000 |
4 | @@ -26,15 +26,11 @@ |
5 | stores the active leases in the DHCPLease table. |
6 | |
7 | |
8 | -Updating the DNS zone file |
9 | -========================== |
10 | - |
11 | -If a new lease is found then the dns.change_dns_zones() function gets called |
12 | -which invokes two tasks:: |
13 | - |
14 | - #. ``write_dns_zone_config()`` |
15 | - #. ``rndc_command()`` |
16 | - |
17 | -The first is responsible for writing out a new zone file with the appropriate |
18 | -sequence number and timestamp, and then the second is chained on to that |
19 | -and sends an rndc message to the DNS server to reload the zone. |
20 | +Updating one or more DNS zone files |
21 | +=================================== |
22 | + |
23 | +If a new lease is found then the dns.dns_update_zones() function gets called |
24 | +which takes two steps:: |
25 | + |
26 | + #. Write out updated zone files. |
27 | + #. Ask BIND to reload the zone. |
28 | |
29 | === modified file 'docs/development/tagging.rst' |
30 | --- docs/development/tagging.rst 2014-08-16 10:34:55 +0000 |
31 | +++ docs/development/tagging.rst 2015-01-13 18:36:01 +0000 |
32 | @@ -16,6 +16,11 @@ |
33 | New or updated tag definition |
34 | ----------------------------- |
35 | |
36 | +**Note** that this is somewhat outdated. See `bug 1372544`_ (*Tag changes will |
37 | +never be evaluated on unconnected clusters*) for more information. |
38 | + |
39 | +.. _bug 1372544: https://bugs.launchpad.net/maas/+bug/1372544 |
40 | + |
41 | When a new tag is created or an existing tag is modified, its |
42 | expression must be evaluated for every node known to the region. It's |
43 | a moderately computationally intensive process, so the work is spread |
44 | |
45 | === modified file 'etc/maas/templates/dns/named.conf.options.inside.maas.template' |
46 | --- etc/maas/templates/dns/named.conf.options.inside.maas.template 2014-08-29 04:27:00 +0000 |
47 | +++ etc/maas/templates/dns/named.conf.options.inside.maas.template 2015-01-13 18:36:01 +0000 |
48 | @@ -1,10 +1,11 @@ |
49 | {{if upstream_dns}} |
50 | - forwarders { |
51 | - {{upstream_dns}}; |
52 | - }; |
53 | +forwarders { |
54 | +{{for upstream_dns_address in upstream_dns}} |
55 | + {{upstream_dns_address}}; |
56 | +{{endfor}} |
57 | +}; |
58 | {{endif}} |
59 | |
60 | allow-query { any; }; |
61 | allow-recursion { trusted; }; |
62 | allow-query-cache { trusted; }; |
63 | - |
64 | |
65 | === modified file 'etc/maas/templates/dns/named.conf.template' |
66 | --- etc/maas/templates/dns/named.conf.template 2014-08-29 04:27:00 +0000 |
67 | +++ etc/maas/templates/dns/named.conf.template 2015-01-13 18:36:01 +0000 |
68 | @@ -11,7 +11,9 @@ |
69 | # Access control for recursive queries. See named.conf.options.inside.maas |
70 | # for the directives used on this ACL. |
71 | acl "trusted" { |
72 | - {{trusted_networks}} |
73 | +{{for trusted_network in trusted_networks}} |
74 | + {{trusted_network}}; |
75 | +{{endfor}} |
76 | localnets; |
77 | localhost; |
78 | }; |
79 | |
80 | === modified file 'required-packages/base' |
81 | --- required-packages/base 2014-09-24 13:05:22 +0000 |
82 | +++ required-packages/base 2015-01-13 18:36:01 +0000 |
83 | @@ -13,7 +13,6 @@ |
84 | postgresql |
85 | python-amqplib |
86 | python-bzrlib |
87 | -python-celery |
88 | python-convoy |
89 | python-crochet |
90 | python-curtin |
91 | |
92 | === removed file 'src/maas/celeryconfig.py' |
93 | --- src/maas/celeryconfig.py 2014-09-29 15:51:04 +0000 |
94 | +++ src/maas/celeryconfig.py 1970-01-01 00:00:00 +0000 |
95 | @@ -1,34 +0,0 @@ |
96 | -# Copyright 2012-2014 Canonical Ltd. This software is licensed under the |
97 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
98 | - |
99 | -"""Celery settings for the region controller.""" |
100 | - |
101 | -from __future__ import ( |
102 | - absolute_import, |
103 | - print_function, |
104 | - unicode_literals, |
105 | -) |
106 | - |
107 | -str = None |
108 | - |
109 | -__metaclass__ = type |
110 | - |
111 | - |
112 | -# Each cluster should have its own queue created automatically by Celery. |
113 | -CELERY_CREATE_MISSING_QUEUES = True |
114 | - |
115 | -CELERY_IMPORTS = ( |
116 | - # Tasks. |
117 | - "provisioningserver.tasks", |
118 | - ) |
119 | - |
120 | -CELERY_ACKS_LATE = True |
121 | - |
122 | -# Do not store the tasks' return values (aka tombstones); |
123 | -# This improves performance. |
124 | -CELERY_IGNORE_RESULT = True |
125 | - |
126 | -# Don't queue, always run tasks immediately, and propagate exceptions back to |
127 | -# the caller. This eliminates the need for a queue. |
128 | -CELERY_ALWAYS_EAGER = True |
129 | -CELERY_EAGER_PROPAGATES_EXCEPTIONS = True |
130 | |
131 | === modified file 'src/maas/settings.py' |
132 | --- src/maas/settings.py 2014-11-10 21:21:47 +0000 |
133 | +++ src/maas/settings.py 2015-01-13 18:36:01 +0000 |
134 | @@ -309,9 +309,6 @@ |
135 | 'maasjson': 'maasserver.json', |
136 | } |
137 | |
138 | -# Set up celery to use the production settings. |
139 | -os.environ['CELERY_CONFIG_MODULE'] = 'maas.celeryconfig' |
140 | - |
141 | # Allow the user to override settings in maas_local_settings. |
142 | import_local_settings() |
143 | |
144 | |
145 | === modified file 'src/maasserver/dns/config.py' |
146 | --- src/maasserver/dns/config.py 2014-10-15 10:28:01 +0000 |
147 | +++ src/maasserver/dns/config.py 2015-01-13 18:36:01 +0000 |
148 | @@ -13,15 +13,11 @@ |
149 | |
150 | __metaclass__ = type |
151 | __all__ = [ |
152 | - 'add_zone', |
153 | - 'change_dns_zones', |
154 | - 'get_trusted_networks', |
155 | - 'is_dns_enabled', |
156 | - 'next_zone_serial', |
157 | - 'write_full_dns_config', |
158 | + 'dns_add_zones', |
159 | + 'dns_update_all_zones', |
160 | + 'dns_update_zones', |
161 | ] |
162 | |
163 | - |
164 | from django.conf import settings |
165 | from maasserver import locks |
166 | from maasserver.dns.zonegenerator import ZoneGenerator |
167 | @@ -35,7 +31,15 @@ |
168 | Sequence, |
169 | ) |
170 | from maasserver.utils import synchronised |
171 | -from provisioningserver import tasks |
172 | +from provisioningserver.dns.actions import ( |
173 | + bind_reconfigure, |
174 | + bind_reload, |
175 | + bind_reload_with_retries, |
176 | + bind_reload_zone, |
177 | + bind_write_configuration, |
178 | + bind_write_options, |
179 | + bind_write_zones, |
180 | + ) |
181 | from provisioningserver.logger import get_maas_logger |
182 | |
183 | |
184 | @@ -67,88 +71,111 @@ |
185 | |
186 | |
187 | @synchronised(locks.dns) |
188 | -def change_dns_zones(nodegroups): |
189 | - """Update the zone configuration for the given list of Nodegroups. |
190 | - |
191 | - :param nodegroups: The list of nodegroups (or the nodegroup) for which the |
192 | - zone should be updated. |
193 | - :type nodegroups: list (or :class:`NodeGroup`) |
194 | - """ |
195 | - if not (is_dns_enabled() and is_dns_in_use()): |
196 | - return |
197 | - serial = next_zone_serial() |
198 | - for zone in ZoneGenerator(nodegroups, serial): |
199 | - maaslog.info("Generating new DNS zone file for %s", zone.zone_name) |
200 | - zone_reload_subtask = tasks.rndc_command.subtask( |
201 | - args=[['reload', zone.zone_name]]) |
202 | - tasks.write_dns_zone_config.delay( |
203 | - zones=[zone], callback=zone_reload_subtask) |
204 | - |
205 | - |
206 | -@synchronised(locks.dns) |
207 | -def add_zone(nodegroup): |
208 | - """Add to the DNS server a new zone for the given `nodegroup`. |
209 | - |
210 | - To do this we have to write a new configuration file for the zone |
211 | - and update the master config to include this new configuration. |
212 | - These are done in turn by chaining Celery subtasks. |
213 | - |
214 | - :param nodegroup: The nodegroup for which the zone should be added. |
215 | - :type nodegroup: :class:`NodeGroup` |
216 | - """ |
217 | - if not (is_dns_enabled() and is_dns_in_use()): |
218 | - return |
219 | +def dns_add_zones(clusters): |
220 | + """Add zone files for the given cluster(s), and serve them. |
221 | + |
222 | + Serving these new zone files means updating BIND's configuration to |
223 | + include them, then asking it to load the new configuration. |
224 | + |
225 | + :param clusters: The clusters(s) for which zones should be served. |
226 | + :type clusters: :py:class:`NodeGroup`, or an iterable thereof. |
227 | + """ |
228 | + if not (is_dns_enabled() and is_dns_in_use()): |
229 | + return |
230 | + |
231 | zones_to_write = ZoneGenerator( |
232 | - nodegroup, serial_generator=next_zone_serial |
233 | - ).as_list() |
234 | + clusters, serial_generator=next_zone_serial).as_list() |
235 | if len(zones_to_write) == 0: |
236 | return None |
237 | serial = next_zone_serial() |
238 | # Compute non-None zones. |
239 | zones = ZoneGenerator(NodeGroup.objects.all(), serial).as_list() |
240 | - reconfig_subtask = tasks.rndc_command.subtask(args=[['reconfig']]) |
241 | - write_dns_config_subtask = tasks.write_dns_config.subtask( |
242 | - kwargs={ |
243 | - 'zones': zones, 'callback': reconfig_subtask, |
244 | - 'trusted_networks': get_trusted_networks()}) |
245 | - tasks.write_dns_zone_config.delay( |
246 | - zones=zones_to_write, callback=write_dns_config_subtask) |
247 | - |
248 | - |
249 | -@synchronised(locks.dns) |
250 | -def write_full_dns_config(reload_retry=False, force=False): |
251 | - """Write the DNS configuration. |
252 | - |
253 | - :param reload_retry: Should the reload rndc command be retried in case |
254 | - of failure? Defaults to `False`. |
255 | + bind_write_zones(zones_to_write) |
256 | + bind_write_configuration(zones, trusted_networks=get_trusted_networks()) |
257 | + bind_reconfigure() |
258 | + |
259 | + |
260 | +@synchronised(locks.dns) |
261 | +def dns_update_zones(clusters): |
262 | + """Update the zone files for the given cluster(s). |
263 | + |
264 | + Once the new zone files have been written, BIND is asked to reload them. |
265 | + It's assumed that BIND already serves the zones that are being written. |
266 | + |
267 | + :param clusters: Those cluster(s) for which the zone should be updated. |
268 | + :type clusters: A :py:class:`NodeGroup`, or an iterable thereof. |
269 | + """ |
270 | + if not (is_dns_enabled() and is_dns_in_use()): |
271 | + return |
272 | + |
273 | + serial = next_zone_serial() |
274 | + for zone in ZoneGenerator(clusters, serial): |
275 | + maaslog.info("Generating new DNS zone file for %s", zone.zone_name) |
276 | + bind_write_zones([zone]) |
277 | + bind_reload_zone(zone.zone_name) |
278 | + |
279 | + |
280 | +@synchronised(locks.dns) |
281 | +def dns_update_all_zones(reload_retry=False, force=False): |
282 | + """Update all zone files for the given cluster(s), and serve them. |
283 | + |
284 | + Serving these zone files means updating BIND's configuration to include |
285 | + them, then asking it to load the new configuration. |
286 | + |
287 | + :param reload_retry: Should the DNS server reload be retried in case |
288 | + of failure? Defaults to `False`. |
289 | :type reload_retry: bool |
290 | - :param force: Write the configuration even if no interface is |
291 | - configured to manage DNS. |
292 | + :param force: Update the configuration even if no interface is configured |
293 | + to manage DNS. This makes sense when deconfiguring an interface. |
294 | :type force: bool |
295 | """ |
296 | - write_conf = ( |
297 | - is_dns_enabled() and (force or is_dns_in_use())) |
298 | + write_conf = is_dns_enabled() and (force or is_dns_in_use()) |
299 | if not write_conf: |
300 | return |
301 | + |
302 | + clusters = NodeGroup.objects.all() |
303 | zones = ZoneGenerator( |
304 | - NodeGroup.objects.all(), serial_generator=next_zone_serial |
305 | - ).as_list() |
306 | + clusters, serial_generator=next_zone_serial).as_list() |
307 | + bind_write_zones(zones) |
308 | + |
309 | + # We should not be calling bind_write_options() here; call-sites should be |
310 | + # making a separate call. It's a historical legacy, where many sites now |
311 | + # expect this side-effect from calling dns_update_all_zones(), and some |
312 | + # that call it for this side-effect alone. At present all it does is set |
313 | + # the upstream DNS servers, nothing to do with serving zones at all! |
314 | + bind_write_options(upstream_dns=get_upstream_dns()) |
315 | + |
316 | + # Nor should we be rewriting ACLs that are related only to allowing |
317 | + # recursive queries to the upstream DNS servers. Again, this is legacy, |
318 | + # where the "trusted" ACL ended up in the same configuration file as the |
319 | + # zone stanzas, and so both need to be rewritten at the same time. |
320 | + bind_write_configuration(zones, trusted_networks=get_trusted_networks()) |
321 | + |
322 | + # Reloading with retries may be a legacy from Celery days, or it may be |
323 | + # necessary to recover from races during start-up. We're not sure if it is |
324 | + # actually needed but it seems safer to maintain this behaviour until we |
325 | + # have a better understanding. |
326 | + if reload_retry: |
327 | + bind_reload_with_retries() |
328 | + else: |
329 | + bind_reload() |
330 | + |
331 | + |
332 | +def get_upstream_dns(): |
333 | + """Return the IP addresses of configured upstream DNS servers. |
334 | + |
335 | + :return: A list of IP addresses. |
336 | + """ |
337 | upstream_dns = Config.objects.get_config("upstream_dns") |
338 | - tasks.write_full_dns_config.delay( |
339 | - zones=zones, |
340 | - callback=tasks.rndc_command.subtask( |
341 | - args=[['reload'], reload_retry]), |
342 | - upstream_dns=upstream_dns, |
343 | - trusted_networks=get_trusted_networks()) |
344 | + return [] if upstream_dns is None else upstream_dns.split() |
345 | |
346 | |
347 | def get_trusted_networks(): |
348 | """Return the CIDR representation of all the Networks we know about. |
349 | |
350 | - This must be a whitespace separated list, where each item ends in a |
351 | - semicolon, or blank if there's no networks. |
352 | + :return: A list of CIDR-format network specifications. |
353 | """ |
354 | - networks = " ".join( |
355 | - "%s;" % net.get_network().cidr |
356 | - for net in Network.objects.all()) |
357 | - return networks |
358 | + return [ |
359 | + unicode(net.get_network().cidr) |
360 | + for net in Network.objects.all() |
361 | + ] |
362 | |
363 | === modified file 'src/maasserver/dns/connect.py' |
364 | --- src/maasserver/dns/connect.py 2014-08-29 09:42:38 +0000 |
365 | +++ src/maasserver/dns/connect.py 2015-01-13 18:36:01 +0000 |
366 | @@ -36,13 +36,13 @@ |
367 | def dns_post_save_NodeGroup(sender, instance, created, **kwargs): |
368 | """Create or update DNS zones related to the saved nodegroup.""" |
369 | from maasserver.dns.config import ( |
370 | - write_full_dns_config, |
371 | - add_zone, |
372 | + dns_update_all_zones, |
373 | + dns_add_zones, |
374 | ) |
375 | if created: |
376 | - add_zone(instance) |
377 | + dns_add_zones(instance) |
378 | else: |
379 | - write_full_dns_config() |
380 | + dns_update_all_zones() |
381 | |
382 | |
383 | # XXX rvb 2012-09-12: This is only needed because we use that |
384 | @@ -52,24 +52,24 @@ |
385 | def dns_post_save_NodeGroupInterface(sender, instance, created, **kwargs): |
386 | """Create or update DNS zones related to the saved nodegroupinterface.""" |
387 | from maasserver.dns.config import ( |
388 | - write_full_dns_config, |
389 | - add_zone, |
390 | + dns_update_all_zones, |
391 | + dns_add_zones, |
392 | ) |
393 | if created: |
394 | - add_zone(instance.nodegroup) |
395 | + dns_add_zones(instance.nodegroup) |
396 | else: |
397 | - write_full_dns_config() |
398 | + dns_update_all_zones() |
399 | |
400 | |
401 | def dns_post_edit_management_NodeGroupInterface(instance, old_values, deleted): |
402 | """Delete DNS zones related to the interface.""" |
403 | - from maasserver.dns.config import write_full_dns_config |
404 | + from maasserver.dns.config import dns_update_all_zones |
405 | [old_field] = old_values |
406 | if old_field == NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS: |
407 | # Force the dns config to be written as this might have been |
408 | # triggered by the last DNS-enabled interface being deleted |
409 | # or switched off (i.e. management set to DHCP or UNMANAGED). |
410 | - write_full_dns_config(force=True) |
411 | + dns_update_all_zones(force=True) |
412 | |
413 | |
414 | connect_to_field_change( |
415 | @@ -81,8 +81,8 @@ |
416 | def dns_post_delete_Node(sender, instance, **kwargs): |
417 | """When a Node is deleted, update the Node's zone file.""" |
418 | try: |
419 | - from maasserver.dns.config import change_dns_zones |
420 | - change_dns_zones(instance.nodegroup) |
421 | + from maasserver.dns.config import dns_update_zones |
422 | + dns_update_zones(instance.nodegroup) |
423 | except NodeGroup.DoesNotExist: |
424 | # If this Node is being deleted because the whole NodeGroup |
425 | # has been deleted, no need to update the zone file because |
426 | @@ -92,29 +92,29 @@ |
427 | |
428 | def dns_post_edit_hostname_Node(instance, old_values, **kwargs): |
429 | """When a Node has been flagged, update the related zone.""" |
430 | - from maasserver.dns.config import change_dns_zones |
431 | - change_dns_zones(instance.nodegroup) |
432 | + from maasserver.dns.config import dns_update_zones |
433 | + dns_update_zones(instance.nodegroup) |
434 | |
435 | |
436 | connect_to_field_change(dns_post_edit_hostname_Node, Node, ['hostname']) |
437 | |
438 | |
439 | def dns_setting_changed(sender, instance, created, **kwargs): |
440 | - from maasserver.dns.config import write_full_dns_config |
441 | - write_full_dns_config() |
442 | + from maasserver.dns.config import dns_update_all_zones |
443 | + dns_update_all_zones() |
444 | |
445 | |
446 | @receiver(post_save, sender=Network) |
447 | def dns_post_save_Network(sender, instance, **kwargs): |
448 | """When a network is added/changed, put it in the DNS trusted networks.""" |
449 | - from maasserver.dns.config import write_full_dns_config |
450 | - write_full_dns_config() |
451 | + from maasserver.dns.config import dns_update_all_zones |
452 | + dns_update_all_zones() |
453 | |
454 | |
455 | @receiver(post_delete, sender=Network) |
456 | def dns_post_delete_Network(sender, instance, **kwargs): |
457 | - from maasserver.dns.config import write_full_dns_config |
458 | - write_full_dns_config() |
459 | + from maasserver.dns.config import dns_update_all_zones |
460 | + dns_update_all_zones() |
461 | |
462 | |
463 | Config.objects.config_changed_connect("upstream_dns", dns_setting_changed) |
464 | |
465 | === modified file 'src/maasserver/dns/tests/test_config.py' |
466 | --- src/maasserver/dns/tests/test_config.py 2014-10-29 17:05:36 +0000 |
467 | +++ src/maasserver/dns/tests/test_config.py 2015-01-13 18:36:01 +0000 |
468 | @@ -18,20 +18,19 @@ |
469 | from itertools import islice |
470 | import random |
471 | |
472 | -from celery.task import task |
473 | from django.conf import settings |
474 | from django.core.management import call_command |
475 | from maasserver import locks |
476 | from maasserver.dns import config as dns_config_module |
477 | from maasserver.dns.config import ( |
478 | - add_zone, |
479 | - change_dns_zones, |
480 | + dns_add_zones, |
481 | + dns_update_all_zones, |
482 | + dns_update_zones, |
483 | get_trusted_networks, |
484 | + get_upstream_dns, |
485 | is_dns_enabled, |
486 | is_dns_in_use, |
487 | next_zone_serial, |
488 | - tasks as dns_tasks, |
489 | - write_full_dns_config, |
490 | zone_serial, |
491 | ) |
492 | from maasserver.enum import ( |
493 | @@ -46,17 +45,16 @@ |
494 | from maasserver.testing.factory import factory |
495 | from maasserver.testing.testcase import MAASServerTestCase |
496 | from maastesting.fakemethod import FakeMethod |
497 | -from maastesting.matchers import MockCalledOnceWith |
498 | -from mock import ( |
499 | - ANY, |
500 | - sentinel, |
501 | +from maastesting.matchers import ( |
502 | + MockCalledOnceWith, |
503 | + MockNotCalled, |
504 | ) |
505 | +from mock import sentinel |
506 | from netaddr import ( |
507 | IPAddress, |
508 | IPNetwork, |
509 | IPRange, |
510 | ) |
511 | -from provisioningserver import tasks |
512 | from provisioningserver.dns.config import ( |
513 | compose_config_path, |
514 | DNSConfig, |
515 | @@ -141,7 +139,7 @@ |
516 | interface.static_ip_range_low, interface.static_ip_range_high) |
517 | static_ip = unicode(islice(ips, lease_number, lease_number + 1).next()) |
518 | staticaddress = factory.make_StaticIPAddress(ip=static_ip, mac=mac) |
519 | - change_dns_zones([nodegroup]) |
520 | + dns_update_zones([nodegroup]) |
521 | return nodegroup, node, staticaddress |
522 | |
523 | def dig_resolve(self, fqdn, version=4): |
524 | @@ -184,57 +182,57 @@ |
525 | def check_dns_is_locked(): |
526 | self.assertTrue( |
527 | locks.dns.is_locked(), "locks.dns isn't locked") |
528 | - return False # Prevent change_dns_zones from continuing. |
529 | + return False # Prevent dns_update_zones from continuing. |
530 | |
531 | is_dns_enabled = self.patch_autospec( |
532 | dns_config_module, 'is_dns_enabled') |
533 | is_dns_enabled.side_effect = check_dns_is_locked |
534 | return is_dns_enabled |
535 | |
536 | - def test_change_dns_zone_uses_lock(self): |
537 | - is_dns_enabled = self.patch_is_dns_enabled() |
538 | - dns_config_module.change_dns_zones(sentinel.nodegroup) |
539 | - self.assertThat(is_dns_enabled, MockCalledOnceWith()) |
540 | - |
541 | - def test_add_zone_uses_lock(self): |
542 | - is_dns_enabled = self.patch_is_dns_enabled() |
543 | - dns_config_module.add_zone(sentinel.nodegroup) |
544 | - self.assertThat(is_dns_enabled, MockCalledOnceWith()) |
545 | - |
546 | - def test_write_full_dns_config_uses_lock(self): |
547 | - is_dns_enabled = self.patch_is_dns_enabled() |
548 | - dns_config_module.write_full_dns_config() |
549 | + def test_dns_update_zones_uses_lock(self): |
550 | + is_dns_enabled = self.patch_is_dns_enabled() |
551 | + dns_config_module.dns_update_zones(sentinel.nodegroup) |
552 | + self.assertThat(is_dns_enabled, MockCalledOnceWith()) |
553 | + |
554 | + def test_dns_add_zones_uses_lock(self): |
555 | + is_dns_enabled = self.patch_is_dns_enabled() |
556 | + dns_config_module.dns_add_zones(sentinel.nodegroup) |
557 | + self.assertThat(is_dns_enabled, MockCalledOnceWith()) |
558 | + |
559 | + def test_dns_update_all_zones_config_uses_lock(self): |
560 | + is_dns_enabled = self.patch_is_dns_enabled() |
561 | + dns_config_module.dns_update_all_zones() |
562 | self.assertThat(is_dns_enabled, MockCalledOnceWith()) |
563 | |
564 | |
565 | class TestDNSConfigModifications(TestDNSServer): |
566 | |
567 | - def test_add_zone_loads_dns_zone(self): |
568 | + def test_dns_add_zones_loads_dns_zone(self): |
569 | nodegroup, node, static = self.create_nodegroup_with_static_ip() |
570 | self.patch(settings, 'DNS_CONNECT', True) |
571 | - add_zone(nodegroup) |
572 | + dns_add_zones(nodegroup) |
573 | self.assertDNSMatches(node.hostname, nodegroup.name, static.ip) |
574 | |
575 | - def test_add_zone_preserves_trusted_networks(self): |
576 | + def test_dns_add_zones_preserves_trusted_networks(self): |
577 | nodegroup, node, static = self.create_nodegroup_with_static_ip() |
578 | trusted_network = factory.make_ipv4_address() |
579 | get_trusted_networks_patch = self.patch( |
580 | dns_config_module, 'get_trusted_networks') |
581 | - get_trusted_networks_patch.return_value = trusted_network + ';' |
582 | + get_trusted_networks_patch.return_value = [trusted_network] |
583 | self.patch(settings, 'DNS_CONNECT', True) |
584 | - add_zone(nodegroup) |
585 | + dns_add_zones(nodegroup) |
586 | self.assertThat( |
587 | compose_config_path(DNSConfig.target_file_name), |
588 | FileContains(matcher=Contains(trusted_network))) |
589 | |
590 | - def test_change_dns_zone_changes_dns_zone(self): |
591 | + def test_dns_update_zones_changes_dns_zone(self): |
592 | nodegroup, _, _ = self.create_nodegroup_with_static_ip() |
593 | self.patch(settings, 'DNS_CONNECT', True) |
594 | - write_full_dns_config() |
595 | + dns_update_all_zones() |
596 | nodegroup, new_node, new_static = ( |
597 | self.create_nodegroup_with_static_ip( |
598 | nodegroup=nodegroup, lease_number=2)) |
599 | - change_dns_zones(nodegroup) |
600 | + dns_update_zones(nodegroup) |
601 | self.assertDNSMatches(new_node.hostname, nodegroup.name, new_static.ip) |
602 | |
603 | def test_is_dns_enabled_return_false_if_DNS_CONNECT_False(self): |
604 | @@ -252,60 +250,57 @@ |
605 | self.create_managed_nodegroup() |
606 | self.assertTrue(is_dns_in_use()) |
607 | |
608 | - def test_write_full_dns_loads_full_dns_config(self): |
609 | + def test_dns_update_all_zones_loads_full_dns_config(self): |
610 | nodegroup, node, static = self.create_nodegroup_with_static_ip() |
611 | self.patch(settings, 'DNS_CONNECT', True) |
612 | - write_full_dns_config() |
613 | + dns_update_all_zones() |
614 | self.assertDNSMatches(node.hostname, nodegroup.name, static.ip) |
615 | |
616 | - def test_write_full_dns_passes_reload_retry_parameter(self): |
617 | + def test_dns_update_all_zones_passes_reload_retry_parameter(self): |
618 | self.patch(settings, 'DNS_CONNECT', True) |
619 | - recorder = FakeMethod() |
620 | self.create_managed_nodegroup() |
621 | - |
622 | - @task |
623 | - def recorder_task(*args, **kwargs): |
624 | - return recorder(*args, **kwargs) |
625 | - self.patch(tasks, 'rndc_command', recorder_task) |
626 | - write_full_dns_config(reload_retry=True) |
627 | - self.assertEqual( |
628 | - ([(['reload'], True)]), recorder.extract_args()) |
629 | - |
630 | - def test_write_full_dns_passes_upstream_dns_parameter(self): |
631 | + bind_reload_with_retries = self.patch_autospec( |
632 | + dns_config_module, "bind_reload_with_retries") |
633 | + dns_update_all_zones(reload_retry=True) |
634 | + self.assertThat(bind_reload_with_retries, MockCalledOnceWith()) |
635 | + |
636 | + def test_dns_update_all_zones_passes_upstream_dns_parameter(self): |
637 | self.patch(settings, 'DNS_CONNECT', True) |
638 | self.create_managed_nodegroup() |
639 | random_ip = factory.make_ipv4_address() |
640 | Config.objects.set_config("upstream_dns", random_ip) |
641 | - patched_task = self.patch(dns_tasks.write_full_dns_config, "delay") |
642 | - write_full_dns_config() |
643 | - self.assertThat(patched_task, MockCalledOnceWith( |
644 | - zones=ANY, callback=ANY, trusted_networks=ANY, |
645 | - upstream_dns=random_ip)) |
646 | + bind_write_options = self.patch_autospec( |
647 | + dns_config_module, "bind_write_options") |
648 | + dns_update_all_zones() |
649 | + self.assertThat( |
650 | + bind_write_options, |
651 | + MockCalledOnceWith(upstream_dns=[random_ip])) |
652 | |
653 | - def test_write_full_dns_writes_trusted_networks_parameter(self): |
654 | + def test_dns_update_all_zones_writes_trusted_networks_parameter(self): |
655 | self.patch(settings, 'DNS_CONNECT', True) |
656 | self.create_managed_nodegroup() |
657 | trusted_network = factory.make_ipv4_address() |
658 | get_trusted_networks_patch = self.patch( |
659 | dns_config_module, 'get_trusted_networks') |
660 | - get_trusted_networks_patch.return_value = trusted_network + ';' |
661 | - write_full_dns_config() |
662 | + get_trusted_networks_patch.return_value = [trusted_network] |
663 | + dns_update_all_zones() |
664 | self.assertThat( |
665 | compose_config_path(DNSConfig.target_file_name), |
666 | FileContains(matcher=Contains(trusted_network))) |
667 | |
668 | - def test_write_full_dns_doesnt_call_task_it_no_interface_configured(self): |
669 | + def test_dns_update_all_zones_does_nada_if_no_interface_configured(self): |
670 | self.patch(settings, 'DNS_CONNECT', True) |
671 | - patched_task = self.patch(dns_tasks.write_full_dns_config, "delay") |
672 | - write_full_dns_config() |
673 | - self.assertEqual(0, patched_task.call_count) |
674 | + bind_write_configuration = self.patch_autospec( |
675 | + dns_config_module, "bind_write_configuration") |
676 | + dns_update_all_zones() |
677 | + self.assertThat(bind_write_configuration, MockNotCalled()) |
678 | |
679 | def test_dns_config_has_NS_record(self): |
680 | ip = factory.make_ipv4_address() |
681 | self.patch(settings, 'DEFAULT_MAAS_URL', 'http://%s/' % ip) |
682 | nodegroup, node, static = self.create_nodegroup_with_static_ip() |
683 | self.patch(settings, 'DNS_CONNECT', True) |
684 | - write_full_dns_config() |
685 | + dns_update_all_zones() |
686 | # Get the NS record for the zone 'nodegroup.name'. |
687 | ns_record = dig_call( |
688 | port=self.bind.config.port, |
689 | @@ -419,7 +414,7 @@ |
690 | ip = "%s" % random.choice(ip_range) |
691 | lease = factory.make_DHCPLease( |
692 | nodegroup=nodegroup, mac=mac.mac_address, ip=ip) |
693 | - change_dns_zones([nodegroup]) |
694 | + dns_update_zones([nodegroup]) |
695 | self.assertDNSMatches(node.hostname, nodegroup.name, lease.ip) |
696 | |
697 | |
698 | @@ -435,19 +430,35 @@ |
699 | node.hostname, nodegroup.name, static.ip, version=6) |
700 | |
701 | |
702 | +class TestGetUpstreamDNS(MAASServerTestCase): |
703 | + """Test for maasserver/dns/config.py:get_upstream_dns()""" |
704 | + |
705 | + def test__returns_empty_list_if_not_set(self): |
706 | + self.assertEqual([], get_upstream_dns()) |
707 | + |
708 | + def test__returns_list_of_one_address_if_set(self): |
709 | + address = factory.make_ipv4_address() |
710 | + Config.objects.set_config("upstream_dns", address) |
711 | + self.assertEqual([address], get_upstream_dns()) |
712 | + |
713 | + def test__returns_list_of_many_address_if_set(self): |
714 | + addresses = [factory.make_ipv4_address(), factory.make_ipv4_address()] |
715 | + Config.objects.set_config("upstream_dns", " ".join(addresses)) |
716 | + self.assertEqual(addresses, get_upstream_dns()) |
717 | + |
718 | + |
719 | class TestGetTrustedNetworks(MAASServerTestCase): |
720 | """Test for maasserver/dns/config.py:get_trusted_networks()""" |
721 | |
722 | def test__returns_empty_string_if_no_networks(self): |
723 | - self.assertEqual("", get_trusted_networks()) |
724 | + self.assertEqual([], get_trusted_networks()) |
725 | |
726 | def test__returns_single_network(self): |
727 | net = factory.make_Network() |
728 | - expected = unicode(net.get_network().cidr) + ';' |
729 | + expected = [unicode(net.get_network().cidr)] |
730 | self.assertEqual(expected, get_trusted_networks()) |
731 | |
732 | def test__returns_many_networks(self): |
733 | nets = [factory.make_Network() for _ in xrange(random.randint(1, 5))] |
734 | - expected = "; ".join(unicode(net.get_network().cidr) for net in nets) |
735 | - expected += ';' |
736 | + expected = [unicode(net.get_network().cidr) for net in nets] |
737 | self.assertEqual(expected, get_trusted_networks()) |
738 | |
739 | === modified file 'src/maasserver/management/commands/write_dns_config.py' |
740 | --- src/maasserver/management/commands/write_dns_config.py 2014-08-15 09:44:42 +0000 |
741 | +++ src/maasserver/management/commands/write_dns_config.py 2015-01-13 18:36:01 +0000 |
742 | @@ -24,7 +24,7 @@ |
743 | ] |
744 | |
745 | from django.core.management.base import BaseCommand |
746 | -from maasserver.dns.config import write_full_dns_config |
747 | +from maasserver.dns.config import dns_update_all_zones |
748 | |
749 | |
750 | class Command(BaseCommand): |
751 | @@ -33,4 +33,4 @@ |
752 | "this region has cluster controllers configured to manage DNS.") |
753 | |
754 | def handle(self, *args, **options): |
755 | - write_full_dns_config() |
756 | + dns_update_all_zones() |
757 | |
758 | === modified file 'src/maasserver/models/node.py' |
759 | --- src/maasserver/models/node.py 2015-01-13 17:10:46 +0000 |
760 | +++ src/maasserver/models/node.py 2015-01-13 18:36:01 +0000 |
761 | @@ -1427,8 +1427,8 @@ |
762 | """ |
763 | deallocated_ips = StaticIPAddress.objects.deallocate_by_node(self) |
764 | self.delete_host_maps(deallocated_ips) |
765 | - from maasserver.dns.config import change_dns_zones |
766 | - change_dns_zones([self.nodegroup]) |
767 | + from maasserver.dns.config import dns_update_zones |
768 | + dns_update_zones([self.nodegroup]) |
769 | |
770 | def get_boot_purpose(self): |
771 | """ |
772 | @@ -1518,7 +1518,7 @@ |
773 | """ |
774 | # Avoid circular imports. |
775 | from metadataserver.models import NodeUserData |
776 | - from maasserver.dns.config import change_dns_zones |
777 | + from maasserver.dns.config import dns_update_zones |
778 | |
779 | if not by_user.has_perm(NODE_PERMISSION.EDIT, self): |
780 | # You can't stop a node you don't own unless you're an |
781 | @@ -1560,7 +1560,7 @@ |
782 | transition_timeout = None |
783 | |
784 | # Update the DNS zone with the new static IP info as necessary. |
785 | - change_dns_zones(self.nodegroup) |
786 | + dns_update_zones(self.nodegroup) |
787 | |
788 | power_info = self.get_effective_power_info() |
789 | if not power_info.can_be_started: |
790 | |
791 | === modified file 'src/maasserver/models/tests/test_dhcplease.py' |
792 | --- src/maasserver/models/tests/test_dhcplease.py 2014-11-25 13:06:05 +0000 |
793 | +++ src/maasserver/models/tests/test_dhcplease.py 2015-01-13 18:36:01 +0000 |
794 | @@ -166,10 +166,10 @@ |
795 | map_leases(nodegroup)) |
796 | |
797 | def test_update_leases_does_not_update_dns_zone_if_nothing_added(self): |
798 | - self.patch(dns, 'change_dns_zones') |
799 | + self.patch(dns, 'dns_update_zones') |
800 | nodegroup = factory.make_NodeGroup() |
801 | DHCPLease.objects.update_leases(nodegroup, {}) |
802 | - self.assertFalse(dns.change_dns_zones.called) |
803 | + self.assertFalse(dns.dns_update_zones.called) |
804 | |
805 | def test_get_hostname_ip_mapping_returns_mapping(self): |
806 | nodegroup = factory.make_NodeGroup() |
807 | |
808 | === modified file 'src/maasserver/models/tests/test_node.py' |
809 | --- src/maasserver/models/tests/test_node.py 2015-01-13 17:10:46 +0000 |
810 | +++ src/maasserver/models/tests/test_node.py 2015-01-13 18:36:01 +0000 |
811 | @@ -1228,7 +1228,7 @@ |
812 | {node.nodegroup: expected})) |
813 | |
814 | def test_deallocate_static_ip_updates_dns(self): |
815 | - change_dns_zones = self.patch(dns_config, 'change_dns_zones') |
816 | + dns_update_zones = self.patch(dns_config, 'dns_update_zones') |
817 | nodegroup = factory.make_NodeGroup( |
818 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS, |
819 | status=NODEGROUP_STATUS.ACCEPTED) |
820 | @@ -1236,7 +1236,7 @@ |
821 | nodegroup=nodegroup, status=NODE_STATUS.ALLOCATED, |
822 | owner=factory.make_User(), power_type='ether_wake') |
823 | node.release() |
824 | - self.assertThat(change_dns_zones, MockCalledOnceWith([node.nodegroup])) |
825 | + self.assertThat(dns_update_zones, MockCalledOnceWith([node.nodegroup])) |
826 | |
827 | def test_release_logs_and_raises_errors_in_stopping(self): |
828 | node = factory.make_Node(status=NODE_STATUS.DEPLOYED) |
829 | @@ -2424,12 +2424,12 @@ |
830 | user = factory.make_User() |
831 | node = self.make_acquired_node_with_mac(user) |
832 | |
833 | - change_dns_zones = self.patch(dns_config, "change_dns_zones") |
834 | + dns_update_zones = self.patch(dns_config, "dns_update_zones") |
835 | |
836 | node.start(user) |
837 | |
838 | self.assertThat( |
839 | - change_dns_zones, MockCalledOnceWith(node.nodegroup)) |
840 | + dns_update_zones, MockCalledOnceWith(node.nodegroup)) |
841 | |
842 | def test__starts_nodes(self): |
843 | user = factory.make_User() |
844 | |
845 | === modified file 'src/maasserver/start_up.py' |
846 | --- src/maasserver/start_up.py 2014-10-10 13:35:47 +0000 |
847 | +++ src/maasserver/start_up.py 2015-01-13 18:36:01 +0000 |
848 | @@ -26,7 +26,7 @@ |
849 | security, |
850 | ) |
851 | from maasserver.bootresources import ensure_boot_source_definition |
852 | -from maasserver.dns.config import write_full_dns_config |
853 | +from maasserver.dns.config import dns_update_all_zones |
854 | from maasserver.fields import register_mac_type |
855 | from maasserver.models import NodeGroup |
856 | from provisioningserver.upgrade_cluster import create_gnupg_home |
857 | @@ -77,4 +77,4 @@ |
858 | ensure_boot_source_definition() |
859 | |
860 | # Regenerate MAAS's DNS configuration. This should be reentrant, really. |
861 | - write_full_dns_config(reload_retry=True) |
862 | + dns_update_all_zones(reload_retry=True) |
863 | |
864 | === modified file 'src/maasserver/tests/test_commands_write_dns_config.py' |
865 | --- src/maasserver/tests/test_commands_write_dns_config.py 2014-09-23 21:51:24 +0000 |
866 | +++ src/maasserver/tests/test_commands_write_dns_config.py 2015-01-13 18:36:01 +0000 |
867 | @@ -26,7 +26,7 @@ |
868 | from maasserver.testing.factory import factory |
869 | from maasserver.testing.testcase import MAASServerTestCase |
870 | from netaddr import IPNetwork |
871 | -from provisioningserver import tasks |
872 | +from provisioningserver.dns import actions |
873 | from provisioningserver.dns.testing import patch_dns_config_path |
874 | from testtools.matchers import FileExists |
875 | |
876 | @@ -37,8 +37,8 @@ |
877 | dns_conf_dir = self.make_dir() |
878 | patch_dns_config_path(self, dns_conf_dir) |
879 | self.patch(settings, 'DNS_CONNECT', True) |
880 | - # Prevent rndc task dispatch. |
881 | - self.patch(tasks, "rndc_command") |
882 | + # Prevent rndc execution. |
883 | + self.patch(actions, "execute_rndc_command") |
884 | domain = factory.make_string() |
885 | factory.make_NodeGroup( |
886 | name=domain, |
887 | |
888 | === modified file 'src/maasserver/tests/test_forms_network.py' |
889 | --- src/maasserver/tests/test_forms_network.py 2014-12-11 15:14:32 +0000 |
890 | +++ src/maasserver/tests/test_forms_network.py 2015-01-13 18:36:01 +0000 |
891 | @@ -181,8 +181,8 @@ |
892 | form.errors) |
893 | |
894 | def test_writes_dns_when_network_edited(self): |
895 | - write_full_dns_config = self.patch( |
896 | - dns_config_module, "write_full_dns_config") |
897 | + dns_update_all_zones = self.patch( |
898 | + dns_config_module, "dns_update_all_zones") |
899 | network = factory.make_ipv4_network() |
900 | name = factory.make_name('network') |
901 | definition = { |
902 | @@ -194,14 +194,14 @@ |
903 | } |
904 | form = NetworkForm(data=definition) |
905 | form.save() |
906 | - self.assertThat(write_full_dns_config, MockCalledOnceWith()) |
907 | + self.assertThat(dns_update_all_zones, MockCalledOnceWith()) |
908 | |
909 | def test_writes_dns_when_network_deleted(self): |
910 | network = factory.make_Network() |
911 | - write_full_dns_config = self.patch( |
912 | - dns_config_module, "write_full_dns_config") |
913 | + dns_update_all_zones = self.patch( |
914 | + dns_config_module, "dns_update_all_zones") |
915 | network.delete() |
916 | - self.assertThat(write_full_dns_config, MockCalledOnceWith()) |
917 | + self.assertThat(dns_update_all_zones, MockCalledOnceWith()) |
918 | |
919 | |
920 | class TestCreateNetworkFromNodeGroupInterface(MAASServerTestCase): |
921 | |
922 | === modified file 'src/maasserver/tests/test_start_up.py' |
923 | --- src/maasserver/tests/test_start_up.py 2014-10-20 21:11:45 +0000 |
924 | +++ src/maasserver/tests/test_start_up.py 2015-01-13 18:36:01 +0000 |
925 | @@ -77,7 +77,7 @@ |
926 | |
927 | def test__calls_write_full_dns_config(self): |
928 | recorder = FakeMethod() |
929 | - self.patch(start_up, 'write_full_dns_config', recorder) |
930 | + self.patch(start_up, 'dns_update_all_zones', recorder) |
931 | start_up.inner_start_up() |
932 | self.assertEqual( |
933 | (1, [()]), |
934 | |
935 | === removed file 'src/maastesting/celery.py' |
936 | --- src/maastesting/celery.py 2014-09-29 15:42:42 +0000 |
937 | +++ src/maastesting/celery.py 1970-01-01 00:00:00 +0000 |
938 | @@ -1,84 +0,0 @@ |
939 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
940 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
941 | - |
942 | -"""A fixture to make Celery run tasks in a synchronous fashion.""" |
943 | - |
944 | -from __future__ import ( |
945 | - absolute_import, |
946 | - print_function, |
947 | - unicode_literals, |
948 | - ) |
949 | - |
950 | -str = None |
951 | - |
952 | -__metaclass__ = type |
953 | -__all__ = [ |
954 | - 'CeleryFixture', |
955 | - ] |
956 | - |
957 | -from celery import ( |
958 | - current_app, |
959 | - signals, |
960 | - ) |
961 | -from fixtures import Fixture |
962 | -from testtools.monkey import MonkeyPatcher |
963 | - |
964 | - |
965 | -class CeleryFixture(Fixture): |
966 | - """This fixture will record Celery tasks as they're run. |
967 | - |
968 | - This fixture can be used directly:: |
969 | - |
970 | - >>> from unittest import TestCase |
971 | - |
972 | - >>> class CeleryTest1(TestCase): |
973 | - ... |
974 | - ... def setUp(self): |
975 | - ... super(CeleryTest1, self).setUp() |
976 | - ... self.useFixture(CeleryFixture()) |
977 | - |
978 | - It can also be converted into a FixtureResource:: |
979 | - |
980 | - >>> from testresources import FixtureResource |
981 | - |
982 | - >>> class CeleryTest2(TestCase): |
983 | - ... |
984 | - ... resources = ( |
985 | - ... ("celery", FixtureResource(CeleryFixture())), |
986 | - ... ) |
987 | - ... |
988 | - |
989 | - """ |
990 | - |
991 | - def setUp(self): |
992 | - super(CeleryFixture, self).setUp() |
993 | - self.configure() |
994 | - self.record_tasks() |
995 | - |
996 | - def configure(self): |
997 | - patcher = MonkeyPatcher() |
998 | - patcher.add_patch(current_app.conf, 'CELERY_ALWAYS_EAGER', True) |
999 | - patcher.add_patch( |
1000 | - current_app.conf, 'CELERY_EAGER_PROPAGATES_EXCEPTIONS', True) |
1001 | - self.addCleanup(patcher.restore) |
1002 | - patcher.patch() |
1003 | - |
1004 | - def record_tasks(self): |
1005 | - self.tasks = [] |
1006 | - |
1007 | - def on_task_postrun(**kwargs): |
1008 | - self.tasks.append(kwargs) |
1009 | - signals.task_postrun.connect(on_task_postrun, weak=False) |
1010 | - self.addCleanup(lambda: self.cleanup_tasks()) |
1011 | - |
1012 | - def get_task_routing(self): |
1013 | - """Get a mapping between the name of the tasks and the queue they |
1014 | - were sent to. |
1015 | - """ |
1016 | - return { |
1017 | - task_info['task'].name: task_info['task'].queue |
1018 | - for task_info in self.tasks |
1019 | - } |
1020 | - |
1021 | - def cleanup_tasks(self): |
1022 | - self.tasks = [] |
1023 | |
1024 | === removed file 'src/maastesting/tests/test_celery.py' |
1025 | --- src/maastesting/tests/test_celery.py 2014-09-24 10:12:35 +0000 |
1026 | +++ src/maastesting/tests/test_celery.py 1970-01-01 00:00:00 +0000 |
1027 | @@ -1,67 +0,0 @@ |
1028 | -# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the |
1029 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1030 | - |
1031 | -"""Test matchers.""" |
1032 | - |
1033 | -from __future__ import ( |
1034 | - absolute_import, |
1035 | - print_function, |
1036 | - unicode_literals, |
1037 | - ) |
1038 | - |
1039 | -str = None |
1040 | - |
1041 | -__metaclass__ = type |
1042 | -__all__ = [] |
1043 | - |
1044 | -import random |
1045 | - |
1046 | -from celery.decorators import task |
1047 | -from celery.result import EagerResult |
1048 | -from maastesting.celery import CeleryFixture |
1049 | -from maastesting.testcase import MAASTestCase |
1050 | - |
1051 | - |
1052 | -@task() |
1053 | -def task_add(x, y): |
1054 | - return x + y |
1055 | - |
1056 | - |
1057 | -@task() |
1058 | -def task_exception(x, y): |
1059 | - raise RuntimeError() |
1060 | - |
1061 | - |
1062 | -class TestCeleryFixture(MAASTestCase): |
1063 | - """Tests `CeleryFixture`.""" |
1064 | - |
1065 | - def setUp(self): |
1066 | - super(TestCeleryFixture, self).setUp() |
1067 | - self.celery = self.useFixture(CeleryFixture()) |
1068 | - |
1069 | - def test_celery_eagerresult_contains_result(self): |
1070 | - # The result is an instance of EagerResult and it contains the actual |
1071 | - # result. |
1072 | - x = random.randrange(100) |
1073 | - y = random.randrange(100) |
1074 | - result = task_add.delay(x, y) |
1075 | - self.assertIsInstance(result, EagerResult) |
1076 | - self.assertEqual(x + y, result.result) |
1077 | - |
1078 | - def test_celery_exception_raised(self): |
1079 | - self.assertRaises(RuntimeError, task_exception.delay, 1, 2) |
1080 | - |
1081 | - def test_celery_records_tasks(self): |
1082 | - x = random.randrange(100) |
1083 | - y = random.randrange(100) |
1084 | - task_add.delay(x=x, y=y) |
1085 | - z = random.randrange(100) |
1086 | - t = random.randrange(100) |
1087 | - task_add.delay(x=z, y=t) |
1088 | - tasks = self.celery.tasks |
1089 | - self.assertEqual(2, len(tasks)) |
1090 | - self.assertEqual( |
1091 | - ['maastesting.tests.test_celery.task_add'] * 2, |
1092 | - [task['task'].name for task in tasks]) |
1093 | - self.assertEqual({'x': x, 'y': y}, tasks[0]['kwargs']) |
1094 | - self.assertEqual({'x': z, 'y': t}, tasks[1]['kwargs']) |
1095 | |
1096 | === added file 'src/provisioningserver/dns/actions.py' |
1097 | --- src/provisioningserver/dns/actions.py 1970-01-01 00:00:00 +0000 |
1098 | +++ src/provisioningserver/dns/actions.py 2015-01-13 18:36:01 +0000 |
1099 | @@ -0,0 +1,142 @@ |
1100 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
1101 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1102 | + |
1103 | +"""Low-level actions to manage the DNS service, like reloading zones.""" |
1104 | + |
1105 | +from __future__ import ( |
1106 | + absolute_import, |
1107 | + print_function, |
1108 | + unicode_literals, |
1109 | + ) |
1110 | + |
1111 | +str = None |
1112 | + |
1113 | +__metaclass__ = type |
1114 | +__all__ = [ |
1115 | + "bind_reconfigure", |
1116 | + "bind_reload", |
1117 | + "bind_reload_zone", |
1118 | + "bind_write_configuration", |
1119 | + "bind_write_options", |
1120 | + "bind_write_zones", |
1121 | +] |
1122 | + |
1123 | +import collections |
1124 | +from subprocess import CalledProcessError |
1125 | +from time import sleep |
1126 | + |
1127 | +from provisioningserver.dns.config import ( |
1128 | + DNSConfig, |
1129 | + execute_rndc_command, |
1130 | + set_up_options_conf, |
1131 | + ) |
1132 | +from provisioningserver.logger import get_maas_logger |
1133 | +from provisioningserver.utils.shell import ExternalProcessError |
1134 | + |
1135 | + |
1136 | +maaslog = get_maas_logger("dns") |
1137 | + |
1138 | + |
1139 | +def bind_reconfigure(): |
1140 | + """Ask BIND to reload its configuration and *new* zone files. |
1141 | + |
1142 | + From rndc(8): |
1143 | + |
1144 | + Reload the configuration file and load new zones, but do not reload |
1145 | + existing zone files even if they have changed. This is faster than a |
1146 | + full reload when there is a large number of zones because it avoids the |
1147 | + need to examine the modification times of the zones files. |
1148 | + |
1149 | + """ |
1150 | + try: |
1151 | + execute_rndc_command(("reconfig",)) |
1152 | + except CalledProcessError as exc: |
1153 | + maaslog.error("Reloading BIND configuration failed: %s", exc) |
1154 | + # Log before upgrade so that the output does not go to maaslog. |
1155 | + ExternalProcessError.upgrade(exc) |
1156 | + raise |
1157 | + |
1158 | + |
1159 | +def bind_reload(): |
1160 | + """Ask BIND to reload its configuration and all zone files.""" |
1161 | + try: |
1162 | + execute_rndc_command(("reload",)) |
1163 | + except CalledProcessError as exc: |
1164 | + maaslog.error("Reloading BIND failed: %s", exc) |
1165 | + # Log before upgrade so that the output does not go to maaslog. |
1166 | + ExternalProcessError.upgrade(exc) |
1167 | + raise |
1168 | + |
1169 | + |
1170 | +def bind_reload_with_retries(attempts=10, interval=2): |
1171 | + """Ask BIND to reload its configuration and all zone files. |
1172 | + |
1173 | + :param attempts: The number of attempts. |
1174 | + :param interval: The time in seconds to sleep between each attempt. |
1175 | + """ |
1176 | + for countdown in xrange(attempts - 1, -1, -1): |
1177 | + try: |
1178 | + bind_reload() |
1179 | + except CalledProcessError: |
1180 | + if countdown == 0: |
1181 | + raise |
1182 | + else: |
1183 | + sleep(interval) |
1184 | + else: |
1185 | + break |
1186 | + |
1187 | + |
1188 | +def bind_reload_zone(zone_name): |
1189 | + """Ask BIND to reload the zone file for the given zone. |
1190 | + |
1191 | + :param zone_name: The name of the zone to reload. |
1192 | + """ |
1193 | + try: |
1194 | + execute_rndc_command(("reload", zone_name)) |
1195 | + except CalledProcessError as exc: |
1196 | + maaslog.error("Reloading BIND zone %r failed: %s", zone_name, exc) |
1197 | + # Log before upgrade so that the output does not go to maaslog. |
1198 | + ExternalProcessError.upgrade(exc) |
1199 | + raise |
1200 | + |
1201 | + |
1202 | +def bind_write_configuration(zones, trusted_networks): |
1203 | + """Write BIND's configuration. |
1204 | + |
1205 | + :param zones: Those zones to include in main config. |
1206 | + :type zones: Sequence of :py:class:`DNSZoneData`. |
1207 | + |
1208 | + :param trusted_networks: A sequence of CIDR network specifications that |
1209 | + are permitted to use the DNS server as a forwarder. |
1210 | + """ |
1211 | + # trusted_networks was formerly specified as a single IP address with |
1212 | + # netmask. These assertions are here to prevent code that assumes that |
1213 | + # slipping through. |
1214 | + assert not isinstance(trusted_networks, (bytes, unicode)) |
1215 | + assert isinstance(trusted_networks, collections.Sequence) |
1216 | + |
1217 | + dns_config = DNSConfig(zones=zones) |
1218 | + dns_config.write_config(trusted_networks=trusted_networks) |
1219 | + |
1220 | + |
1221 | +def bind_write_options(upstream_dns): |
1222 | + """Write BIND options. |
1223 | + |
1224 | + :param upstream_dns: A sequence of upstream DNS servers. |
1225 | + """ |
1226 | + # upstream_dns was formerly specified as a single IP address. These |
1227 | + # assertions are here to prevent code that assumes that slipping through. |
1228 | + assert not isinstance(upstream_dns, (bytes, unicode)) |
1229 | + assert isinstance(upstream_dns, collections.Sequence) |
1230 | + |
1231 | + set_up_options_conf(upstream_dns=upstream_dns) |
1232 | + |
1233 | + |
1234 | +def bind_write_zones(zones): |
1235 | + """Write out DNS zones. |
1236 | + |
1237 | + :param zones: Those zones to write. |
1238 | + :type zones: Sequence of :py:class:`DNSZoneData`. |
1239 | + """ |
1240 | + for zone in zones: |
1241 | + zone.write_config() |
1242 | |
1243 | === added file 'src/provisioningserver/dns/tests/test_actions.py' |
1244 | --- src/provisioningserver/dns/tests/test_actions.py 1970-01-01 00:00:00 +0000 |
1245 | +++ src/provisioningserver/dns/tests/test_actions.py 2015-01-13 18:36:01 +0000 |
1246 | @@ -0,0 +1,271 @@ |
1247 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
1248 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1249 | + |
1250 | +"""Tests for :py:module:`provisioningserver.dns.actions`.""" |
1251 | + |
1252 | +from __future__ import ( |
1253 | + absolute_import, |
1254 | + print_function, |
1255 | + unicode_literals, |
1256 | + ) |
1257 | + |
1258 | +str = None |
1259 | + |
1260 | +__metaclass__ = type |
1261 | +__all__ = [] |
1262 | + |
1263 | +import os |
1264 | +from os.path import join |
1265 | +import random |
1266 | +from random import randint |
1267 | +from subprocess import CalledProcessError |
1268 | +from textwrap import dedent |
1269 | + |
1270 | +from fixtures import FakeLogger |
1271 | +from maastesting.factory import factory |
1272 | +from maastesting.matchers import ( |
1273 | + MockCalledOnceWith, |
1274 | + MockCallsMatch, |
1275 | + ) |
1276 | +from maastesting.testcase import MAASTestCase |
1277 | +from mock import ( |
1278 | + call, |
1279 | + sentinel, |
1280 | + ) |
1281 | +from netaddr import IPNetwork |
1282 | +from provisioningserver.dns import actions |
1283 | +from provisioningserver.dns.config import ( |
1284 | + MAAS_NAMED_CONF_NAME, |
1285 | + MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME, |
1286 | + ) |
1287 | +from provisioningserver.dns.testing import patch_dns_config_path |
1288 | +from provisioningserver.dns.zoneconfig import ( |
1289 | + DNSForwardZoneConfig, |
1290 | + DNSReverseZoneConfig, |
1291 | + ) |
1292 | +from provisioningserver.testing.testcase import PservTestCase |
1293 | +from provisioningserver.utils.shell import ExternalProcessError |
1294 | +from testtools.matchers import ( |
1295 | + AllMatch, |
1296 | + Contains, |
1297 | + FileContains, |
1298 | + FileExists, |
1299 | + ) |
1300 | + |
1301 | + |
1302 | +class TestReconfigure(MAASTestCase): |
1303 | + """Tests for :py:func:`actions.bind_reconfigure`.""" |
1304 | + |
1305 | + def test__executes_rndc_command(self): |
1306 | + self.patch_autospec(actions, "execute_rndc_command") |
1307 | + actions.bind_reconfigure() |
1308 | + self.assertThat( |
1309 | + actions.execute_rndc_command, |
1310 | + MockCalledOnceWith(("reconfig",))) |
1311 | + |
1312 | + def test__logs_subprocess_error(self): |
1313 | + erc = self.patch_autospec(actions, "execute_rndc_command") |
1314 | + erc.side_effect = factory.make_CalledProcessError() |
1315 | + with FakeLogger("maas") as logger: |
1316 | + self.assertRaises(CalledProcessError, actions.bind_reconfigure) |
1317 | + self.assertDocTestMatches( |
1318 | + "Reloading BIND configuration failed: " |
1319 | + "Command ... returned non-zero exit status ...", |
1320 | + logger.output) |
1321 | + |
1322 | + def test__upgrades_subprocess_error(self): |
1323 | + erc = self.patch_autospec(actions, "execute_rndc_command") |
1324 | + erc.side_effect = factory.make_CalledProcessError() |
1325 | + self.assertRaises(ExternalProcessError, actions.bind_reconfigure) |
1326 | + |
1327 | + |
1328 | +class TestReload(MAASTestCase): |
1329 | + """Tests for :py:func:`actions.bind_reload`.""" |
1330 | + |
1331 | + def test__executes_rndc_command(self): |
1332 | + self.patch_autospec(actions, "execute_rndc_command") |
1333 | + actions.bind_reload() |
1334 | + self.assertThat( |
1335 | + actions.execute_rndc_command, |
1336 | + MockCalledOnceWith(("reload",))) |
1337 | + |
1338 | + def test__logs_subprocess_error(self): |
1339 | + erc = self.patch_autospec(actions, "execute_rndc_command") |
1340 | + erc.side_effect = factory.make_CalledProcessError() |
1341 | + with FakeLogger("maas") as logger: |
1342 | + self.assertRaises(CalledProcessError, actions.bind_reload) |
1343 | + self.assertDocTestMatches( |
1344 | + "Reloading BIND failed: " |
1345 | + "Command ... returned non-zero exit status ...", |
1346 | + logger.output) |
1347 | + |
1348 | + def test__upgrades_subprocess_error(self): |
1349 | + erc = self.patch_autospec(actions, "execute_rndc_command") |
1350 | + erc.side_effect = factory.make_CalledProcessError() |
1351 | + self.assertRaises(ExternalProcessError, actions.bind_reload) |
1352 | + |
1353 | + |
1354 | +class TestReloadWithRetries(MAASTestCase): |
1355 | + """Tests for :py:func:`actions.bind_reload_with_retries`.""" |
1356 | + |
1357 | + def test__calls_bind_reload_count_times(self): |
1358 | + self.patch_autospec(actions, "sleep") # Disable. |
1359 | + bind_reload = self.patch_autospec(actions, "bind_reload") |
1360 | + bind_reload.side_effect = factory.make_CalledProcessError() |
1361 | + attempts = randint(3, 13) |
1362 | + self.assertRaises( |
1363 | + CalledProcessError, actions.bind_reload_with_retries, |
1364 | + attempts=attempts) |
1365 | + expected_calls = [call()] * attempts |
1366 | + self.assertThat( |
1367 | + actions.bind_reload, |
1368 | + MockCallsMatch(*expected_calls)) |
1369 | + |
1370 | + def test__returns_on_success(self): |
1371 | + self.patch_autospec(actions, "sleep") # Disable. |
1372 | + bind_reload = self.patch(actions, "bind_reload") |
1373 | + bind_reload.side_effect = [ |
1374 | + factory.make_CalledProcessError(), |
1375 | + factory.make_CalledProcessError(), |
1376 | + None, # Success |
1377 | + ] |
1378 | + actions.bind_reload_with_retries(attempts=5) |
1379 | + expected_calls = [call(), call(), call()] |
1380 | + self.assertThat( |
1381 | + actions.bind_reload, |
1382 | + MockCallsMatch(*expected_calls)) |
1383 | + |
1384 | + def test__sleeps_interval_seconds_between_attempts(self): |
1385 | + self.patch_autospec(actions, "sleep") # Disable. |
1386 | + bind_reload = self.patch_autospec(actions, "bind_reload") |
1387 | + bind_reload.side_effect = factory.make_CalledProcessError() |
1388 | + attempts = randint(3, 13) |
1389 | + self.assertRaises( |
1390 | + CalledProcessError, actions.bind_reload_with_retries, |
1391 | + attempts=attempts, interval=sentinel.interval) |
1392 | + expected_sleep_calls = [call(sentinel.interval)] * (attempts - 1) |
1393 | + self.assertThat(actions.sleep, MockCallsMatch(*expected_sleep_calls)) |
1394 | + |
1395 | + |
1396 | +class TestReloadZone(MAASTestCase): |
1397 | + """Tests for :py:func:`actions.bind_reload_zone`.""" |
1398 | + |
1399 | + def test__executes_rndc_command(self): |
1400 | + self.patch_autospec(actions, "execute_rndc_command") |
1401 | + actions.bind_reload_zone(sentinel.zone) |
1402 | + self.assertThat( |
1403 | + actions.execute_rndc_command, |
1404 | + MockCalledOnceWith(("reload", sentinel.zone))) |
1405 | + |
1406 | + def test__logs_subprocess_error(self): |
1407 | + erc = self.patch_autospec(actions, "execute_rndc_command") |
1408 | + erc.side_effect = factory.make_CalledProcessError() |
1409 | + with FakeLogger("maas") as logger: |
1410 | + self.assertRaises( |
1411 | + CalledProcessError, actions.bind_reload_zone, sentinel.zone) |
1412 | + self.assertDocTestMatches( |
1413 | + "Reloading BIND zone ... failed: " |
1414 | + "Command ... returned non-zero exit status ...", |
1415 | + logger.output) |
1416 | + |
1417 | + def test__upgrades_subprocess_error(self): |
1418 | + erc = self.patch_autospec(actions, "execute_rndc_command") |
1419 | + erc.side_effect = factory.make_CalledProcessError() |
1420 | + self.assertRaises( |
1421 | + ExternalProcessError, actions.bind_reload_zone, sentinel.zone) |
1422 | + |
1423 | + |
1424 | +class TestConfiguration(PservTestCase): |
1425 | + """Tests for the `bind_write_*` functions.""" |
1426 | + |
1427 | + def setUp(self): |
1428 | + super(TestConfiguration, self).setUp() |
1429 | + # Ensure that files are written to a temporary directory. |
1430 | + self.dns_conf_dir = self.make_dir() |
1431 | + patch_dns_config_path(self, self.dns_conf_dir) |
1432 | + # Patch out calls to 'execute_rndc_command'. |
1433 | + self.patch_autospec(actions, 'execute_rndc_command') |
1434 | + |
1435 | + def test_bind_write_configuration_writes_file(self): |
1436 | + domain = factory.make_string() |
1437 | + zones = [ |
1438 | + DNSReverseZoneConfig( |
1439 | + domain, serial=random.randint(1, 100), |
1440 | + network=factory.make_ipv4_network()), |
1441 | + DNSReverseZoneConfig( |
1442 | + domain, serial=random.randint(1, 100), |
1443 | + network=factory.make_ipv6_network()), |
1444 | + ] |
1445 | + actions.bind_write_configuration( |
1446 | + zones=zones, trusted_networks=[]) |
1447 | + self.assertThat( |
1448 | + os.path.join(self.dns_conf_dir, MAAS_NAMED_CONF_NAME), |
1449 | + FileExists()) |
1450 | + |
1451 | + def test_bind_write_configuration_writes_file_with_acl(self): |
1452 | + trusted_networks = [ |
1453 | + factory.make_ipv4_network(), |
1454 | + factory.make_ipv6_network(), |
1455 | + ] |
1456 | + actions.bind_write_configuration( |
1457 | + zones=[], trusted_networks=trusted_networks) |
1458 | + expected_file = os.path.join(self.dns_conf_dir, MAAS_NAMED_CONF_NAME) |
1459 | + self.assertThat(expected_file, FileExists()) |
1460 | + expected_content = dedent("""\ |
1461 | + acl "trusted" { |
1462 | + %s; |
1463 | + %s; |
1464 | + localnets; |
1465 | + localhost; |
1466 | + }; |
1467 | + """) |
1468 | + expected_content %= tuple(trusted_networks) |
1469 | + self.assertThat(expected_file, FileContains( |
1470 | + matcher=Contains(expected_content))) |
1471 | + |
1472 | + def test_bind_write_zones_writes_file(self): |
1473 | + domain = factory.make_string() |
1474 | + network = IPNetwork('192.168.0.3/24') |
1475 | + dns_ip = factory.pick_ip_in_network(network) |
1476 | + ip = factory.pick_ip_in_network(network) |
1477 | + forward_zone = DNSForwardZoneConfig( |
1478 | + domain, serial=random.randint(1, 100), |
1479 | + mapping={factory.make_string(): [ip]}, |
1480 | + dns_ip=dns_ip) |
1481 | + reverse_zone = DNSReverseZoneConfig( |
1482 | + domain, serial=random.randint(1, 100), network=network) |
1483 | + actions.bind_write_zones(zones=[forward_zone, reverse_zone]) |
1484 | + |
1485 | + forward_file_name = 'zone.%s' % domain |
1486 | + reverse_file_name = 'zone.0.168.192.in-addr.arpa' |
1487 | + expected_files = [ |
1488 | + join(self.dns_conf_dir, forward_file_name), |
1489 | + join(self.dns_conf_dir, reverse_file_name), |
1490 | + ] |
1491 | + self.assertThat(expected_files, AllMatch(FileExists())) |
1492 | + |
1493 | + def test_bind_write_options_sets_up_config(self): |
1494 | + # bind_write_configuration_and_zones writes the config file, writes |
1495 | + # the zone files, and reloads the dns service. |
1496 | + upstream_dns = [ |
1497 | + factory.make_ipv4_address(), |
1498 | + factory.make_ipv4_address(), |
1499 | + ] |
1500 | + actions.bind_write_options(upstream_dns=upstream_dns) |
1501 | + expected_options_file = join( |
1502 | + self.dns_conf_dir, MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME) |
1503 | + self.assertThat(expected_options_file, FileExists()) |
1504 | + expected_options_content = dedent("""\ |
1505 | + forwarders { |
1506 | + %s; |
1507 | + %s; |
1508 | + }; |
1509 | + |
1510 | + allow-query { any; }; |
1511 | + allow-recursion { trusted; }; |
1512 | + allow-query-cache { trusted; }; |
1513 | + """) |
1514 | + expected_options_content %= tuple(upstream_dns) |
1515 | + self.assertThat( |
1516 | + expected_options_file, |
1517 | + FileContains(expected_options_content)) |
1518 | |
1519 | === modified file 'src/provisioningserver/dns/tests/test_config.py' |
1520 | --- src/provisioningserver/dns/tests/test_config.py 2014-11-17 11:02:09 +0000 |
1521 | +++ src/provisioningserver/dns/tests/test_config.py 2015-01-13 18:36:01 +0000 |
1522 | @@ -133,13 +133,14 @@ |
1523 | |
1524 | def test_set_up_options_conf_writes_configuration(self): |
1525 | dns_conf_dir = patch_dns_config_path(self) |
1526 | - fake_dns = factory.make_ipv4_address() |
1527 | + fake_dns = [factory.make_ipv4_address(), factory.make_ipv4_address()] |
1528 | set_up_options_conf(upstream_dns=fake_dns) |
1529 | target_file = os.path.join( |
1530 | dns_conf_dir, MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME) |
1531 | self.assertThat( |
1532 | - target_file, |
1533 | - FileContains(matcher=Contains(fake_dns))) |
1534 | + target_file, MatchesAll(*( |
1535 | + FileContains(matcher=Contains(address)) |
1536 | + for address in fake_dns))) |
1537 | |
1538 | def test_set_up_options_conf_handles_no_upstream_dns(self): |
1539 | dns_conf_dir = patch_dns_config_path(self) |
1540 | |
1541 | === removed file 'src/provisioningserver/logger/utils.py' |
1542 | --- src/provisioningserver/logger/utils.py 2014-09-17 08:51:31 +0000 |
1543 | +++ src/provisioningserver/logger/utils.py 1970-01-01 00:00:00 +0000 |
1544 | @@ -1,47 +0,0 @@ |
1545 | -# Copyright 2014 Canonical Ltd. This software is licensed under the |
1546 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1547 | - |
1548 | -"""Utilities for logging.""" |
1549 | - |
1550 | -from __future__ import ( |
1551 | - absolute_import, |
1552 | - print_function, |
1553 | - unicode_literals, |
1554 | - ) |
1555 | - |
1556 | -str = None |
1557 | - |
1558 | -__metaclass__ = type |
1559 | -__all__ = [ |
1560 | - "log_call", |
1561 | -] |
1562 | - |
1563 | -from functools import wraps |
1564 | -import logging |
1565 | - |
1566 | -from provisioningserver.logger.log import get_maas_logger |
1567 | - |
1568 | - |
1569 | -maaslog = get_maas_logger("calls") |
1570 | - |
1571 | - |
1572 | -def log_call(level=logging.INFO): |
1573 | - """Log to the maaslog that something happened with a task. |
1574 | - |
1575 | - :param event: The event that we want to log. |
1576 | - :param task_name: The name of the task. |
1577 | - :**kwargs: A dict of args passed to the task. |
1578 | - """ |
1579 | - def _decorator(func): |
1580 | - @wraps(func) |
1581 | - def wrapper(*args, **kwargs): |
1582 | - arg_string = "%s %s" % (args, kwargs) |
1583 | - maaslog.log( |
1584 | - level, "Starting task '%s' with args: %s" % |
1585 | - (func.__name__, arg_string)) |
1586 | - func(*args, **kwargs) |
1587 | - maaslog.log( |
1588 | - level, "Finished task '%s' with args: %s" % |
1589 | - (func.__name__, arg_string)) |
1590 | - return wrapper |
1591 | - return _decorator |
1592 | |
1593 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
1594 | --- src/provisioningserver/rpc/clusterservice.py 2014-12-17 20:55:55 +0000 |
1595 | +++ src/provisioningserver/rpc/clusterservice.py 2015-01-13 18:36:01 +0000 |
1596 | @@ -18,7 +18,6 @@ |
1597 | |
1598 | from functools import partial |
1599 | import json |
1600 | -import logging |
1601 | from os import urandom |
1602 | import random |
1603 | import re |
1604 | @@ -42,7 +41,6 @@ |
1605 | from provisioningserver.drivers.hardware.ucsm import probe_and_enlist_ucsm |
1606 | from provisioningserver.drivers.hardware.virsh import probe_virsh_and_enlist |
1607 | from provisioningserver.logger.log import get_maas_logger |
1608 | -from provisioningserver.logger.utils import log_call |
1609 | from provisioningserver.network import discover_networks |
1610 | from provisioningserver.rpc import ( |
1611 | cluster, |
1612 | @@ -254,7 +252,6 @@ |
1613 | nameservers=nameservers, netmasks=netmasks), |
1614 | } |
1615 | |
1616 | - @log_call(level=logging.DEBUG) |
1617 | @cluster.PowerOn.responder |
1618 | def power_on(self, system_id, hostname, power_type, context): |
1619 | """Turn a node on.""" |
1620 | @@ -264,7 +261,6 @@ |
1621 | d.addCallback(lambda _: {}) |
1622 | return d |
1623 | |
1624 | - @log_call(level=logging.DEBUG) |
1625 | @cluster.PowerOff.responder |
1626 | def power_off(self, system_id, hostname, power_type, context): |
1627 | """Turn a node off.""" |
1628 | |
1629 | === removed file 'src/provisioningserver/tasks.py' |
1630 | --- src/provisioningserver/tasks.py 2014-11-14 17:18:10 +0000 |
1631 | +++ src/provisioningserver/tasks.py 1970-01-01 00:00:00 +0000 |
1632 | @@ -1,153 +0,0 @@ |
1633 | -# Copyright 2012-2014 Canonical Ltd. This software is licensed under the |
1634 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1635 | - |
1636 | -"""Provisioning server tasks that are run in Celery workers.""" |
1637 | - |
1638 | -from __future__ import ( |
1639 | - absolute_import, |
1640 | - print_function, |
1641 | - unicode_literals, |
1642 | - ) |
1643 | - |
1644 | -str = None |
1645 | - |
1646 | -__metaclass__ = type |
1647 | -__all__ = [ |
1648 | - 'rndc_command', |
1649 | - 'write_dns_config', |
1650 | - 'write_dns_zone_config', |
1651 | - 'write_full_dns_config', |
1652 | - ] |
1653 | - |
1654 | -from functools import wraps |
1655 | -from subprocess import CalledProcessError |
1656 | - |
1657 | -from celery.app import app_or_default |
1658 | -from celery.task import task |
1659 | -from provisioningserver.dns.config import ( |
1660 | - DNSConfig, |
1661 | - execute_rndc_command, |
1662 | - set_up_options_conf, |
1663 | - ) |
1664 | -from provisioningserver.logger import get_maas_logger |
1665 | -from provisioningserver.logger.utils import log_call |
1666 | - |
1667 | - |
1668 | -celery_config = app_or_default().conf |
1669 | - |
1670 | -maaslog = get_maas_logger("tasks") |
1671 | - |
1672 | - |
1673 | -# The tasks catch bare exceptions in an attempt to circumvent Celery's |
1674 | -# bizarre exception handling which prints a stack trace but not the |
1675 | -# error message contained in the exception itself! The message is |
1676 | -# printed and then the exception re-raised so that it marks the task as |
1677 | -# failed - in doing so it logs the stack trace, which is why the code |
1678 | -# does not do a simple maaslog.exception(exc). |
1679 | -def log_exception_text(func): |
1680 | - """Wrap a function and log any exception text raised.""" |
1681 | - @wraps(func) |
1682 | - def wrapper(*args, **kwargs): |
1683 | - try: |
1684 | - func(*args, **kwargs) |
1685 | - except Exception as e: |
1686 | - maaslog.error("%s: %s", func.__name__, unicode(e)) |
1687 | - raise |
1688 | - return wrapper |
1689 | - |
1690 | - |
1691 | -# ===================================================================== |
1692 | -# DNS-related tasks |
1693 | -# ===================================================================== |
1694 | - |
1695 | -# How many times should a rndc task be retried? |
1696 | -RNDC_COMMAND_MAX_RETRY = 10 |
1697 | - |
1698 | -# How long to wait between rndc tasks retries (in seconds)? |
1699 | -RNDC_COMMAND_RETRY_DELAY = 2 |
1700 | - |
1701 | - |
1702 | -@task(max_retries=RNDC_COMMAND_MAX_RETRY) |
1703 | -@log_call() |
1704 | -@log_exception_text |
1705 | -def rndc_command(arguments, retry=False, callback=None): |
1706 | - """Use rndc to execute a command. |
1707 | - :param arguments: Argument list passed down to the rndc command. |
1708 | - :type arguments : list |
1709 | - :param retry: Should this task be retried in case of failure? |
1710 | - :type retry: bool |
1711 | - :param callback: Callback subtask. |
1712 | - :type callback: callable |
1713 | - """ |
1714 | - try: |
1715 | - execute_rndc_command(arguments) |
1716 | - except CalledProcessError as exc: |
1717 | - if retry: |
1718 | - return rndc_command.retry( |
1719 | - exc=exc, countdown=RNDC_COMMAND_RETRY_DELAY) |
1720 | - else: |
1721 | - maaslog.error("rndc_command failed: %s", unicode(exc)) |
1722 | - raise |
1723 | - if callback is not None: |
1724 | - callback.delay() |
1725 | - |
1726 | - |
1727 | -@task |
1728 | -@log_call() |
1729 | -@log_exception_text |
1730 | -def write_full_dns_config(zones=None, callback=None, **kwargs): |
1731 | - """Write out the DNS configuration files: the main configuration |
1732 | - file and the zone files. |
1733 | - :param zones: List of zones to write. |
1734 | - :type zones: list of :class:`DNSZoneData` |
1735 | - :param callback: Callback subtask. |
1736 | - :type callback: callable |
1737 | - :param **kwargs: Keyword args passed to DNSConfig.write_config() |
1738 | - """ |
1739 | - if zones is not None: |
1740 | - for zone in zones: |
1741 | - zone.write_config() |
1742 | - # Write main config file. |
1743 | - dns_config = DNSConfig(zones=zones) |
1744 | - dns_config.write_config(**kwargs) |
1745 | - # Write the included options file. |
1746 | - set_up_options_conf(**kwargs) |
1747 | - if callback is not None: |
1748 | - callback.delay() |
1749 | - |
1750 | - |
1751 | -@task |
1752 | -@log_call() |
1753 | -@log_exception_text |
1754 | -def write_dns_config(zones=(), callback=None, **kwargs): |
1755 | - """Write out the DNS configuration file. |
1756 | - |
1757 | - :param zones: List of zones to include as part of the main |
1758 | - config. |
1759 | - :type zones: list of :class:`DNSZoneData` |
1760 | - :param callback: Callback subtask. |
1761 | - :type callback: callable |
1762 | - :param **kwargs: Keyword args passed to DNSConfig.write_config() |
1763 | - """ |
1764 | - dns_config = DNSConfig(zones=zones) |
1765 | - dns_config.write_config(**kwargs) |
1766 | - if callback is not None: |
1767 | - callback.delay() |
1768 | - |
1769 | - |
1770 | -@task |
1771 | -@log_call() |
1772 | -@log_exception_text |
1773 | -def write_dns_zone_config(zones, callback=None, **kwargs): |
1774 | - """Write out DNS zones. |
1775 | - |
1776 | - :param zone: The zone data to write the configuration for. |
1777 | - :type zone: :class:`DNSZoneData` |
1778 | - :param callback: Callback subtask. |
1779 | - :type callback: callable |
1780 | - :param **kwargs: Keyword args passed to DNSZoneConfig.write_config() |
1781 | - """ |
1782 | - for zone in zones: |
1783 | - zone.write_config() |
1784 | - if callback is not None: |
1785 | - callback.delay() |
1786 | |
1787 | === removed file 'src/provisioningserver/tests/test_tasks.py' |
1788 | --- src/provisioningserver/tests/test_tasks.py 2014-11-14 17:18:10 +0000 |
1789 | +++ src/provisioningserver/tests/test_tasks.py 1970-01-01 00:00:00 +0000 |
1790 | @@ -1,227 +0,0 @@ |
1791 | -# Copyright 2012-2014 Canonical Ltd. This software is licensed under the |
1792 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1793 | - |
1794 | -"""Tests for Celery tasks.""" |
1795 | - |
1796 | -from __future__ import ( |
1797 | - absolute_import, |
1798 | - print_function, |
1799 | - unicode_literals, |
1800 | - ) |
1801 | - |
1802 | -str = None |
1803 | - |
1804 | -__metaclass__ = type |
1805 | -__all__ = [] |
1806 | - |
1807 | -import os |
1808 | -import random |
1809 | -from subprocess import CalledProcessError |
1810 | - |
1811 | -import celery |
1812 | -from celery import states |
1813 | -from maastesting.celery import CeleryFixture |
1814 | -from maastesting.factory import factory |
1815 | -from maastesting.fakemethod import ( |
1816 | - FakeMethod, |
1817 | - MultiFakeMethod, |
1818 | - ) |
1819 | -from netaddr import IPNetwork |
1820 | -from provisioningserver import tasks |
1821 | -from provisioningserver.dns.config import ( |
1822 | - MAAS_NAMED_CONF_NAME, |
1823 | - MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME, |
1824 | - ) |
1825 | -from provisioningserver.dns.testing import patch_dns_config_path |
1826 | -from provisioningserver.dns.zoneconfig import ( |
1827 | - DNSForwardZoneConfig, |
1828 | - DNSReverseZoneConfig, |
1829 | - ) |
1830 | -from provisioningserver.tasks import ( |
1831 | - rndc_command, |
1832 | - RNDC_COMMAND_MAX_RETRY, |
1833 | - write_dns_config, |
1834 | - write_dns_zone_config, |
1835 | - write_full_dns_config, |
1836 | - ) |
1837 | -from provisioningserver.testing.testcase import PservTestCase |
1838 | -from provisioningserver.utils.shell import ExternalProcessError |
1839 | -from testtools.matchers import ( |
1840 | - Equals, |
1841 | - FileExists, |
1842 | - MatchesListwise, |
1843 | - ) |
1844 | - |
1845 | - |
1846 | -def assertTaskRetried(runner, result, nb_retries, task_name): |
1847 | - # In celery version 2.5 (in Saucy) a retried tasks that eventually |
1848 | - # succeeds comes out in a 'SUCCESS' state and in 3.1 (in Trusty) is comes |
1849 | - # out with a 'RETRY' state. |
1850 | - # In both cases the task is successfully retried. |
1851 | - if celery.VERSION[0] == 2: |
1852 | - runner.assertTrue(result.successful()) |
1853 | - else: |
1854 | - runner.assertEqual( |
1855 | - len(runner.celery.tasks), nb_retries) |
1856 | - last_task = runner.celery.tasks[0] |
1857 | - # The last task succeeded. |
1858 | - runner.assertEqual( |
1859 | - (last_task['task'].name, last_task['state']), |
1860 | - (task_name, states.SUCCESS)) |
1861 | - |
1862 | - |
1863 | -class TestDNSTasks(PservTestCase): |
1864 | - |
1865 | - def setUp(self): |
1866 | - super(TestDNSTasks, self).setUp() |
1867 | - # Patch DNS_CONFIG_DIR so that the configuration files will be |
1868 | - # written in a temporary directory. |
1869 | - self.dns_conf_dir = self.make_dir() |
1870 | - patch_dns_config_path(self, self.dns_conf_dir) |
1871 | - # Record the calls to 'execute_rndc_command' (instead of |
1872 | - # executing real rndc commands). |
1873 | - self.rndc_recorder = FakeMethod() |
1874 | - self.patch(tasks, 'execute_rndc_command', self.rndc_recorder) |
1875 | - self.celery = self.useFixture(CeleryFixture()) |
1876 | - |
1877 | - def test_write_dns_config_writes_file(self): |
1878 | - zone_names = [random.randint(1, 100), random.randint(1, 100)] |
1879 | - command = factory.make_string() |
1880 | - result = write_dns_config.delay( |
1881 | - zone_names=zone_names, |
1882 | - callback=rndc_command.subtask(args=[command])) |
1883 | - |
1884 | - self.assertThat( |
1885 | - ( |
1886 | - result.successful(), |
1887 | - os.path.join(self.dns_conf_dir, MAAS_NAMED_CONF_NAME), |
1888 | - self.rndc_recorder.calls, |
1889 | - ), |
1890 | - MatchesListwise( |
1891 | - ( |
1892 | - Equals(True), |
1893 | - FileExists(), |
1894 | - Equals([((command,), {})]), |
1895 | - )), |
1896 | - result) |
1897 | - |
1898 | - def test_write_dns_zone_config_writes_file(self): |
1899 | - command = factory.make_string() |
1900 | - domain = factory.make_string() |
1901 | - network = IPNetwork('192.168.0.3/24') |
1902 | - dns_ip = factory.pick_ip_in_network(network) |
1903 | - ip = factory.pick_ip_in_network(network) |
1904 | - forward_zone = DNSForwardZoneConfig( |
1905 | - domain, serial=random.randint(1, 100), |
1906 | - mapping={factory.make_string(): [ip]}, |
1907 | - dns_ip=dns_ip) |
1908 | - reverse_zone = DNSReverseZoneConfig( |
1909 | - domain, serial=random.randint(1, 100), network=network) |
1910 | - result = write_dns_zone_config.delay( |
1911 | - zones=[forward_zone, reverse_zone], |
1912 | - callback=rndc_command.subtask(args=[command])) |
1913 | - |
1914 | - forward_file_name = 'zone.%s' % domain |
1915 | - reverse_file_name = 'zone.0.168.192.in-addr.arpa' |
1916 | - self.assertThat( |
1917 | - ( |
1918 | - result.successful(), |
1919 | - os.path.join(self.dns_conf_dir, forward_file_name), |
1920 | - os.path.join(self.dns_conf_dir, reverse_file_name), |
1921 | - self.rndc_recorder.calls, |
1922 | - ), |
1923 | - MatchesListwise( |
1924 | - ( |
1925 | - Equals(True), |
1926 | - FileExists(), |
1927 | - FileExists(), |
1928 | - Equals([((command, ), {})]), |
1929 | - )), |
1930 | - result) |
1931 | - |
1932 | - def test_rndc_command_execute_command(self): |
1933 | - command = factory.make_string() |
1934 | - result = rndc_command.delay(command) |
1935 | - |
1936 | - self.assertThat( |
1937 | - (result.successful(), self.rndc_recorder.calls), |
1938 | - MatchesListwise( |
1939 | - ( |
1940 | - Equals(True), |
1941 | - Equals([((command,), {})]), |
1942 | - ))) |
1943 | - |
1944 | - def test_rndc_command_can_be_retried(self): |
1945 | - # The rndc_command task can be retried. |
1946 | - # Simulate a temporary failure. |
1947 | - number_of_failures = RNDC_COMMAND_MAX_RETRY |
1948 | - raised_exception = CalledProcessError( |
1949 | - factory.make_name('exception'), random.randint(100, 200)) |
1950 | - simulate_failures = MultiFakeMethod( |
1951 | - [FakeMethod(failure=raised_exception)] * number_of_failures + |
1952 | - [FakeMethod()]) |
1953 | - self.patch(tasks, 'execute_rndc_command', simulate_failures) |
1954 | - command = factory.make_string() |
1955 | - result = rndc_command.delay(command, retry=True) |
1956 | - assertTaskRetried( |
1957 | - self, result, RNDC_COMMAND_MAX_RETRY + 1, |
1958 | - 'provisioningserver.tasks.rndc_command') |
1959 | - |
1960 | - def test_rndc_command_is_retried_a_limited_number_of_times(self): |
1961 | - # If we simulate RNDC_COMMAND_MAX_RETRY + 1 failures, the |
1962 | - # task fails. |
1963 | - number_of_failures = RNDC_COMMAND_MAX_RETRY + 1 |
1964 | - raised_exception = ExternalProcessError( |
1965 | - random.randint(100, 200), factory.make_name('exception')) |
1966 | - simulate_failures = MultiFakeMethod( |
1967 | - [FakeMethod(failure=raised_exception)] * number_of_failures + |
1968 | - [FakeMethod()]) |
1969 | - self.patch(tasks, 'execute_rndc_command', simulate_failures) |
1970 | - command = factory.make_string() |
1971 | - self.assertRaises( |
1972 | - ExternalProcessError, rndc_command.delay, |
1973 | - command, retry=True) |
1974 | - |
1975 | - def test_write_full_dns_config_sets_up_config(self): |
1976 | - # write_full_dns_config writes the config file, writes |
1977 | - # the zone files, and reloads the dns service. |
1978 | - domain = factory.make_string() |
1979 | - network = IPNetwork('192.168.0.3/24') |
1980 | - ip = factory.pick_ip_in_network(network) |
1981 | - dns_ip = factory.pick_ip_in_network(network) |
1982 | - zones = [ |
1983 | - DNSForwardZoneConfig( |
1984 | - domain, serial=random.randint(1, 100), |
1985 | - mapping={factory.make_string(): [ip]}, |
1986 | - dns_ip=dns_ip, |
1987 | - ), |
1988 | - DNSReverseZoneConfig( |
1989 | - domain, serial=random.randint(1, 100), network=network), |
1990 | - ] |
1991 | - command = factory.make_string() |
1992 | - result = write_full_dns_config.delay( |
1993 | - zones=zones, |
1994 | - callback=rndc_command.subtask(args=[command]), |
1995 | - upstream_dns=factory.make_ipv4_address()) |
1996 | - |
1997 | - forward_file_name = 'zone.%s' % domain |
1998 | - reverse_file_name = 'zone.0.168.192.in-addr.arpa' |
1999 | - self.assertThat( |
2000 | - ( |
2001 | - result.successful(), |
2002 | - self.rndc_recorder.calls, |
2003 | - os.path.join(self.dns_conf_dir, forward_file_name), |
2004 | - os.path.join(self.dns_conf_dir, reverse_file_name), |
2005 | - os.path.join(self.dns_conf_dir, MAAS_NAMED_CONF_NAME), |
2006 | - os.path.join( |
2007 | - self.dns_conf_dir, MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME), |
2008 | - ), |
2009 | - MatchesListwise( |
2010 | - ( |
2011 | - Equals(True), |
2012 | - Equals([((command,), {})]), |
2013 | - FileExists(), |
2014 | - FileExists(), |
2015 | - FileExists(), |
2016 | - FileExists(), |
2017 | - ))) |
Brilliant, thanks! I have a few notes inline, but nothing serious. Some things may be worth polishing in later branches.