Merge ~cgrabowski/maas:backport_fix_node_dns_generation_to_3.3 into maas:3.3
- Git
- lp:~cgrabowski/maas
- backport_fix_node_dns_generation_to_3.3
- Merge into 3.3
Proposed by
Christian Grabowski
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Christian Grabowski | ||||
Approved revision: | 741b75b12b146c0a61910a822a3c35c6fc52936e | ||||
Merge reported by: | MAAS Lander | ||||
Merged at revision: | not available | ||||
Proposed branch: | ~cgrabowski/maas:backport_fix_node_dns_generation_to_3.3 | ||||
Merge into: | maas:3.3 | ||||
Diff against target: |
638 lines (+498/-10) 6 files modified
src/maasserver/dns/config.py (+29/-9) src/maasserver/dns/tests/test_config.py (+38/-1) src/maasserver/triggers/system.py (+152/-0) src/maasserver/triggers/tests/test_init.py (+5/-0) src/maasserver/triggers/tests/test_system.py (+271/-0) src/provisioningserver/dns/zoneconfig.py (+3/-0) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Christian Grabowski | Approve | ||
Review via email: mp+434580@code.launchpad.net |
Commit message
add trigger when existing static ip updates
add DELETE-IFACE-IP
add triggers for interface to ip dynamic DNS updates
(cherry picked from commit 147cc75221abc14
Description of the change
To post a comment you must log in.
Revision history for this message
Christian Grabowski (cgrabowski) wrote : | # |
Revision history for this message
Christian Grabowski (cgrabowski) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/dns/config.py b/src/maasserver/dns/config.py |
2 | index c484570..a0390c0 100644 |
3 | --- a/src/maasserver/dns/config.py |
4 | +++ b/src/maasserver/dns/config.py |
5 | @@ -21,6 +21,7 @@ from maasserver.models.dnsdata import DNSData |
6 | from maasserver.models.dnspublication import DNSPublication |
7 | from maasserver.models.dnsresource import DNSResource |
8 | from maasserver.models.domain import Domain |
9 | +from maasserver.models.interface import Interface |
10 | from maasserver.models.node import RackController |
11 | from maasserver.models.subnet import Subnet |
12 | from provisioningserver.dns.actions import ( |
13 | @@ -348,7 +349,7 @@ def process_dns_update_notify(message): |
14 | case _: |
15 | # special case where we know an IP has been deleted but, we can't fetch the value |
16 | # and the rrecord may still have other answers |
17 | - if op == "DELETE-IP": |
18 | + if op == "DELETE-IP" or op == "DELETE-IFACE-IP": |
19 | updates.append( |
20 | DynamicDNSUpdate.create_from_trigger( |
21 | operation="DELETE", |
22 | @@ -366,23 +367,42 @@ def process_dns_update_notify(message): |
23 | rectype="AAAA", |
24 | ) |
25 | ) |
26 | - resource = DNSResource.objects.get( |
27 | - name=update_list[2], domain__name=zone |
28 | - ) |
29 | + |
30 | + ttl = None |
31 | + ip_addresses = [] |
32 | + if op == "DELETE-IP": |
33 | + resource = DNSResource.objects.get( |
34 | + name=update_list[2], domain__name=zone |
35 | + ) |
36 | + ttl = ( |
37 | + int(resource.address_ttl) |
38 | + if resource.address_ttl |
39 | + else None |
40 | + ) |
41 | + ip_addresses = list( |
42 | + resource.ip_addresses.exclude(ip__isnull=True) |
43 | + ) |
44 | + else: |
45 | + iface_id = int(update_list[-1]) |
46 | + iface = Interface.objects.get(id=iface_id) |
47 | + default_domain = Domain.objects.get_default_domain() |
48 | + ttl = ( |
49 | + int(default_domain.ttl) if default_domain.ttl else None |
50 | + ) |
51 | + ip_addresses = list( |
52 | + iface.ip_addresses.exclude(ip__isnull=True) |
53 | + ) |
54 | updates += [ |
55 | DynamicDNSUpdate.create_from_trigger( |
56 | operation="INSERT", |
57 | zone=zone, |
58 | name=name, |
59 | rectype=rectype, |
60 | - ttl=int(resource.address_ttl) |
61 | - if resource.address_ttl |
62 | - else None, |
63 | + ttl=ttl, |
64 | answer=ip.ip, |
65 | ) |
66 | - for ip in resource.ip_addresses.all() |
67 | + for ip in ip_addresses |
68 | ] |
69 | - |
70 | elif len(update_list) > 4: # has an answer |
71 | updates.append( |
72 | DynamicDNSUpdate.create_from_trigger( |
73 | diff --git a/src/maasserver/dns/tests/test_config.py b/src/maasserver/dns/tests/test_config.py |
74 | index 9a2835d..0a96814 100644 |
75 | --- a/src/maasserver/dns/tests/test_config.py |
76 | +++ b/src/maasserver/dns/tests/test_config.py |
77 | @@ -873,7 +873,10 @@ class TestProcessDNSUpdateNotify(MAASServerTestCase): |
78 | domain = factory.make_Domain() |
79 | resource = factory.make_DNSResource(domain=domain) |
80 | ip = resource.ip_addresses.first().ip |
81 | - ip2 = factory.make_StaticIPAddress() |
82 | + subnet = factory.make_Subnet() |
83 | + ip2 = factory.make_StaticIPAddress( |
84 | + subnet=subnet, ip=subnet.get_next_ip_for_allocation()[0] |
85 | + ) |
86 | resource.ip_addresses.add(ip2) |
87 | message = f"DELETE-IP {domain.name} {resource.name} A {resource.address_ttl if resource.address_ttl else 60} {ip}" |
88 | resource.ip_addresses.first().delete() |
89 | @@ -902,3 +905,37 @@ class TestProcessDNSUpdateNotify(MAASServerTestCase): |
90 | ], |
91 | result, |
92 | ) |
93 | + |
94 | + def test_delete_iface_ip(self): |
95 | + domain = factory.make_Domain() |
96 | + node = factory.make_Node_with_Interface_on_Subnet() |
97 | + iface = node.current_config.interface_set.first() |
98 | + ip1 = iface.ip_addresses.first() |
99 | + ip2 = factory.make_StaticIPAddress(interface=iface) |
100 | + ip1.delete() |
101 | + message = f"DELETE-IFACE-IP {domain.name} {node.hostname} A {domain.ttl if domain.ttl else 60} {iface.id}" |
102 | + result, _ = process_dns_update_notify(message) |
103 | + self.assertCountEqual( |
104 | + [ |
105 | + DynamicDNSUpdate( |
106 | + operation="DELETE", |
107 | + zone=domain.name, |
108 | + name=f"{node.hostname}.{domain.name}", |
109 | + rectype="A", |
110 | + ), |
111 | + DynamicDNSUpdate( |
112 | + operation="DELETE", |
113 | + zone=domain.name, |
114 | + name=f"{node.hostname}.{domain.name}", |
115 | + rectype="AAAA", |
116 | + ), |
117 | + DynamicDNSUpdate( |
118 | + operation="INSERT", |
119 | + zone=domain.name, |
120 | + name=f"{node.hostname}.{domain.name}", |
121 | + rectype="A" if IPAddress(ip2.ip).version == 4 else "AAAA", |
122 | + answer=ip2.ip, |
123 | + ), |
124 | + ], |
125 | + result, |
126 | + ) |
127 | diff --git a/src/maasserver/triggers/system.py b/src/maasserver/triggers/system.py |
128 | index a18fe8a..3f7f328 100644 |
129 | --- a/src/maasserver/triggers/system.py |
130 | +++ b/src/maasserver/triggers/system.py |
131 | @@ -2054,6 +2054,124 @@ def render_dns_dynamic_update_subnet_procedure(op): |
132 | ) |
133 | |
134 | |
135 | +def render_dns_dynamic_update_interface_static_ip_address(op): |
136 | + return dedent( |
137 | + f"""\ |
138 | + CREATE OR REPLACE FUNCTION sys_dns_updates_interface_ip_{op}() |
139 | + RETURNS trigger as $$ |
140 | + DECLARE |
141 | + current_hostname text; |
142 | + domain text; |
143 | + iface_name text; |
144 | + ip_addr text; |
145 | + address_ttl int; |
146 | + BEGIN |
147 | + ASSERT TG_WHEN = 'AFTER', 'May only run as an AFTER trigger'; |
148 | + ASSERT TG_LEVEL <> 'STATEMENT', 'Should not be used as a STATEMENT level trigger', TG_NAME; |
149 | + IF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN |
150 | + SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl |
151 | + FROM maasserver_interface AS iface |
152 | + JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id |
153 | + JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=NEW.interface_id; |
154 | + SELECT host(ip) INTO ip_addr FROM maasserver_staticipaddress WHERE id=NEW.staticipaddress_id; |
155 | + PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || current_hostname || ' A ' || address_ttl || ' ' || ip_addr); |
156 | + PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || address_ttl || ' ' || ip_addr); |
157 | + ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN |
158 | + IF EXISTS(SELECT id FROM maasserver_interface WHERE id=OLD.interface_id) THEN |
159 | + SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl |
160 | + FROM maasserver_interface AS iface |
161 | + JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id |
162 | + JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=OLD.interface_id; |
163 | + IF EXISTS(SELECT id FROM maasserver_staticipaddress WHERE id=OLD.staticipaddress_id) THEN |
164 | + SELECT host(ip) INTO ip_addr FROM maasserver_staticipaddress WHERE id=OLD.staticipaddress_id; |
165 | + PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || current_hostname || ' A ' || ip_addr); |
166 | + PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || ip_addr); |
167 | + ELSE |
168 | + PERFORM pg_notify('sys_dns_updates', 'DELETE-IFACE-IP ' || domain || ' ' || current_hostname || ' A ' || OLD.interface_id); |
169 | + PERFORM pg_notify('sys_dns_updates', 'DELETE-IFACE-IP ' || domain || ' ' || current_hostname || ' AAAA ' || OLD.interface_id); |
170 | + END IF; |
171 | + END IF; |
172 | + END IF; |
173 | + RETURN NULL; |
174 | + END; |
175 | + $$ LANGUAGE plpgsql; |
176 | + """ |
177 | + ) |
178 | + |
179 | + |
180 | +dns_dynamic_update_static_ip_address_update = dedent( |
181 | + """\ |
182 | + CREATE OR REPLACE FUNCTION sys_dns_updates_ip_update() |
183 | + RETURNS trigger as $$ |
184 | + DECLARE |
185 | + current_hostname text; |
186 | + domain text; |
187 | + iface_name text; |
188 | + address_ttl int; |
189 | + current_interface_id bigint; |
190 | + BEGIN |
191 | + IF NEW IS DISTINCT FROM OLD THEN |
192 | + IF EXISTS(SELECT id FROM maasserver_interface_ip_addresses WHERE staticipaddress_id=NEW.id) THEN |
193 | + SELECT interface_id INTO current_interface_id FROM maasserver_interface_ip_addresses WHERE staticipaddress_id=NEW.id; |
194 | + SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl |
195 | + FROM maasserver_interface AS iface |
196 | + JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id |
197 | + JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=current_interface_id; |
198 | + IF OLD.ip IS NOT NULL THEN |
199 | + PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || current_hostname || ' A ' || host(OLD.ip)); |
200 | + PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || host(OLD.ip)); |
201 | + END IF; |
202 | + PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || current_hostname || ' A ' || address_ttl || ' ' || host(NEW.ip)); |
203 | + PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || address_ttl || ' ' || host(NEW.ip)); |
204 | + END IF; |
205 | + END IF; |
206 | + RETURN NULL; |
207 | + END; |
208 | + $$ LANGUAGE plpgsql; |
209 | + """ |
210 | +) |
211 | + |
212 | +dns_dynamic_update_node_delete = dedent( |
213 | + """\ |
214 | + CREATE OR REPLACE FUNCTION sys_dns_updates_maasserver_node_delete() |
215 | + RETURNS trigger as $$ |
216 | + DECLARE |
217 | + hostname text; |
218 | + domain text; |
219 | + address_ttl int; |
220 | + BEGIN |
221 | + SELECT name, COALESCE(ttl, 0) INTO domain, address_ttl FROM maasserver_domain WHERE id=OLD.domain_id; |
222 | + PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || OLD.hostname || ' A'); |
223 | + RETURN NULL; |
224 | + END; |
225 | + $$ LANGUAGE plpgsql; |
226 | + """ |
227 | +) |
228 | + |
229 | + |
230 | +dns_dynamic_update_interface_delete = dedent( |
231 | + """\ |
232 | + CREATE OR REPLACE FUNCTION sys_dns_updates_maasserver_interface_delete() |
233 | + RETURNS trigger as $$ |
234 | + DECLARE |
235 | + current_hostname text; |
236 | + current_domain_id bigint; |
237 | + domain text; |
238 | + current_node_id bigint; |
239 | + BEGIN |
240 | + IF EXISTS(SELECT id FROM maasserver_nodeconfig WHERE id=OLD.node_config_id) THEN |
241 | + SELECT node_id INTO current_node_id FROM maasserver_nodeconfig WHERE id=OLD.node_config_id; |
242 | + SELECT hostname, domain_id INTO current_hostname, current_domain_id FROM maasserver_node WHERE id=current_node_id; |
243 | + SELECT name INTO domain FROM maasserver_domain WHERE id=current_domain_id; |
244 | + PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || OLD.name || '.' || current_hostname || ' A'); |
245 | + END IF; |
246 | + RETURN NULL; |
247 | + END; |
248 | + $$ LANGUAGE plpgsql; |
249 | + """ |
250 | +) |
251 | + |
252 | + |
253 | def render_sys_proxy_procedure(proc_name, on_delete=False): |
254 | """Render a database procedure with name `proc_name` that notifies that a |
255 | proxy update is needed. |
256 | @@ -2403,3 +2521,37 @@ def register_system_triggers(): |
257 | "sys_dns_updates_maasserver_subnet_delete", |
258 | "delete", |
259 | ) |
260 | + register_procedure( |
261 | + render_dns_dynamic_update_interface_static_ip_address("insert") |
262 | + ) |
263 | + register_trigger( |
264 | + "maasserver_interface_ip_addresses", |
265 | + "sys_dns_updates_interface_ip_insert", |
266 | + "insert", |
267 | + ) |
268 | + register_procedure( |
269 | + render_dns_dynamic_update_interface_static_ip_address("delete") |
270 | + ) |
271 | + register_trigger( |
272 | + "maasserver_interface_ip_addresses", |
273 | + "sys_dns_updates_interface_ip_delete", |
274 | + "delete", |
275 | + ) |
276 | + register_procedure(dns_dynamic_update_static_ip_address_update) |
277 | + register_trigger( |
278 | + "maasserver_staticipaddress", |
279 | + "sys_dns_updates_ip_update", |
280 | + "update", |
281 | + ) |
282 | + register_procedure(dns_dynamic_update_node_delete) |
283 | + register_trigger( |
284 | + "maasserver_node", |
285 | + "sys_dns_updates_maasserver_node_delete", |
286 | + "delete", |
287 | + ) |
288 | + register_procedure(dns_dynamic_update_interface_delete) |
289 | + register_trigger( |
290 | + "maasserver_interface", |
291 | + "sys_dns_updates_maasserver_interface_delete", |
292 | + "delete", |
293 | + ) |
294 | diff --git a/src/maasserver/triggers/tests/test_init.py b/src/maasserver/triggers/tests/test_init.py |
295 | index e4fb3ed..34395f1 100644 |
296 | --- a/src/maasserver/triggers/tests/test_init.py |
297 | +++ b/src/maasserver/triggers/tests/test_init.py |
298 | @@ -91,14 +91,18 @@ class TestTriggersUsed(MAASServerTestCase): |
299 | "domain_sys_dns_updates_maasserver_domain_update", |
300 | "interface_ip_addresses_sys_dns_nic_ip_link", |
301 | "interface_ip_addresses_sys_dns_nic_ip_unlink", |
302 | + "interface_ip_addresses_sys_dns_updates_interface_ip_insert", |
303 | + "interface_ip_addresses_sys_dns_updates_interface_ip_delete", |
304 | "interface_sys_dhcp_interface_update", |
305 | "interface_sys_dns_interface_update", |
306 | + "interface_sys_dns_updates_maasserver_interface_delete", |
307 | "iprange_sys_dhcp_iprange_delete", |
308 | "iprange_sys_dhcp_iprange_insert", |
309 | "iprange_sys_dhcp_iprange_update", |
310 | "node_sys_dhcp_node_update", |
311 | "node_sys_dns_node_delete", |
312 | "node_sys_dns_node_update", |
313 | + "node_sys_dns_updates_maasserver_node_delete", |
314 | "rbacsync_sys_rbac_sync", |
315 | "regionrackrpcconnection_sys_core_rpc_delete", |
316 | "regionrackrpcconnection_sys_core_rpc_insert", |
317 | @@ -109,6 +113,7 @@ class TestTriggersUsed(MAASServerTestCase): |
318 | "staticipaddress_sys_dhcp_staticipaddress_insert", |
319 | "staticipaddress_sys_dhcp_staticipaddress_update", |
320 | "staticipaddress_sys_dns_staticipaddress_update", |
321 | + "staticipaddress_sys_dns_updates_ip_update", |
322 | "subnet_sys_dns_updates_maasserver_subnet_delete", |
323 | "subnet_sys_dns_updates_maasserver_subnet_insert", |
324 | "subnet_sys_dns_updates_maasserver_subnet_update", |
325 | diff --git a/src/maasserver/triggers/tests/test_system.py b/src/maasserver/triggers/tests/test_system.py |
326 | index d5222b4..4337984 100644 |
327 | --- a/src/maasserver/triggers/tests/test_system.py |
328 | +++ b/src/maasserver/triggers/tests/test_system.py |
329 | @@ -7,6 +7,8 @@ from contextlib import closing |
330 | from django.db import connection |
331 | from twisted.internet.defer import inlineCallbacks |
332 | |
333 | +from maasserver.enum import NODE_STATUS |
334 | +from maasserver.models import Domain |
335 | from maasserver.models.dnspublication import zone_serial |
336 | from maasserver.testing.factory import factory |
337 | from maasserver.testing.testcase import ( |
338 | @@ -367,3 +369,272 @@ class TestSysDNSUpdates( |
339 | finally: |
340 | self.stop_reading() |
341 | yield self.postgres_listener_service.stopService() |
342 | + |
343 | + @wait_for_reactor |
344 | + @inlineCallbacks |
345 | + def test_dns_dynamic_update_interface_static_ip_address_insert(self): |
346 | + listener = self.make_listener_without_delay() |
347 | + yield self.set_service(listener) |
348 | + yield deferToDatabase( |
349 | + self.register_trigger, |
350 | + "maasserver_interface_ip_addresses", |
351 | + "sys_dns_updates", |
352 | + ops=("insert",), |
353 | + trigger="sys_dns_updates_interface_ip_insert", |
354 | + ) |
355 | + vlan = yield deferToDatabase(self.create_vlan) |
356 | + subnet = yield deferToDatabase( |
357 | + self.create_subnet, params={"vlan": vlan} |
358 | + ) |
359 | + self.start_reading() |
360 | + try: |
361 | + node = yield deferToDatabase( |
362 | + self.create_node_with_interface, |
363 | + params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED}, |
364 | + ) |
365 | + domain = yield deferToDatabase(Domain.objects.get_default_domain) |
366 | + expected_iface = yield deferToDatabase( |
367 | + lambda: node.current_config.interface_set.first() |
368 | + ) |
369 | + expected_ip = yield deferToDatabase( |
370 | + lambda: self.create_staticipaddress( |
371 | + params={ |
372 | + "ip": subnet.get_next_ip_for_allocation()[0], |
373 | + "interface": expected_iface, |
374 | + "subnet": subnet, |
375 | + } |
376 | + ) |
377 | + ) |
378 | + msg1 = yield self.get_notify("sys_dns_updates") |
379 | + self.assertEqual( |
380 | + msg1, |
381 | + f"INSERT {domain.name} {node.hostname} A 0 {expected_ip.ip}", |
382 | + ) |
383 | + msg2 = yield self.get_notify("sys_dns_updates") |
384 | + self.assertEqual( |
385 | + msg2, |
386 | + f"INSERT {domain.name} {expected_iface.name}.{node.hostname} A 0 {expected_ip.ip}", |
387 | + ) |
388 | + finally: |
389 | + self.stop_reading() |
390 | + yield self.postgres_listener_service.stopService() |
391 | + |
392 | + @wait_for_reactor |
393 | + @inlineCallbacks |
394 | + def test_dns_dynamic_update_interface_static_ip_address_insert_with_non_default_domain( |
395 | + self, |
396 | + ): |
397 | + listener = self.make_listener_without_delay() |
398 | + yield self.set_service(listener) |
399 | + yield deferToDatabase( |
400 | + self.register_trigger, |
401 | + "maasserver_interface_ip_addresses", |
402 | + "sys_dns_updates", |
403 | + ops=("insert",), |
404 | + trigger="sys_dns_updates_interface_ip_insert", |
405 | + ) |
406 | + vlan = yield deferToDatabase(self.create_vlan) |
407 | + subnet = yield deferToDatabase( |
408 | + self.create_subnet, params={"vlan": vlan} |
409 | + ) |
410 | + domain = yield deferToDatabase(self.create_domain) |
411 | + self.start_reading() |
412 | + try: |
413 | + node = yield deferToDatabase( |
414 | + self.create_node_with_interface, |
415 | + params={ |
416 | + "subnet": subnet, |
417 | + "status": NODE_STATUS.DEPLOYED, |
418 | + "domain": domain, |
419 | + }, |
420 | + ) |
421 | + expected_iface = yield deferToDatabase( |
422 | + lambda: node.current_config.interface_set.first() |
423 | + ) |
424 | + expected_ip = yield deferToDatabase( |
425 | + lambda: self.create_staticipaddress( |
426 | + params={ |
427 | + "ip": subnet.get_next_ip_for_allocation()[0], |
428 | + "interface": expected_iface, |
429 | + "subnet": subnet, |
430 | + } |
431 | + ) |
432 | + ) |
433 | + msg1 = yield self.get_notify("sys_dns_updates") |
434 | + self.assertEqual( |
435 | + msg1, |
436 | + f"INSERT {domain.name} {node.hostname} A 0 {expected_ip.ip}", |
437 | + ) |
438 | + msg2 = yield self.get_notify("sys_dns_updates") |
439 | + self.assertEqual( |
440 | + msg2, |
441 | + f"INSERT {domain.name} {expected_iface.name}.{node.hostname} A 0 {expected_ip.ip}", |
442 | + ) |
443 | + finally: |
444 | + self.stop_reading() |
445 | + yield self.postgres_listener_service.stopService() |
446 | + |
447 | + @wait_for_reactor |
448 | + @inlineCallbacks |
449 | + def test_dns_dynamic_update_interface_static_ip_address_delete(self): |
450 | + listener = self.make_listener_without_delay() |
451 | + yield self.set_service(listener) |
452 | + yield deferToDatabase( |
453 | + self.register_trigger, |
454 | + "maasserver_interface_ip_addresses", |
455 | + "sys_dns_updates", |
456 | + ops=("delete",), |
457 | + trigger="sys_dns_updates_interface_ip_delete", |
458 | + ) |
459 | + vlan = yield deferToDatabase(self.create_vlan) |
460 | + subnet = yield deferToDatabase( |
461 | + self.create_subnet, params={"vlan": vlan} |
462 | + ) |
463 | + node = yield deferToDatabase( |
464 | + self.create_node_with_interface, |
465 | + params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED}, |
466 | + ) |
467 | + domain = yield deferToDatabase(Domain.objects.get_default_domain) |
468 | + iface = yield deferToDatabase( |
469 | + lambda: node.current_config.interface_set.first() |
470 | + ) |
471 | + ip1 = yield deferToDatabase( |
472 | + lambda: self.create_staticipaddress( |
473 | + params={ |
474 | + "ip": subnet.get_next_ip_for_allocation()[0], |
475 | + "interface": iface, |
476 | + "subnet": subnet, |
477 | + } |
478 | + ) |
479 | + ) |
480 | + self.start_reading() |
481 | + try: |
482 | + yield deferToDatabase(iface.unlink_ip_address, ip1) |
483 | + msg1 = yield self.get_notify("sys_dns_updates") |
484 | + self.assertEqual( |
485 | + msg1, f"DELETE {domain.name} {node.hostname} A {ip1.ip}" |
486 | + ) |
487 | + msg2 = yield self.get_notify("sys_dns_updates") |
488 | + self.assertEqual( |
489 | + msg2, |
490 | + f"DELETE {domain.name} {iface.name}.{node.hostname} A {ip1.ip}", |
491 | + ) |
492 | + finally: |
493 | + self.stop_reading() |
494 | + yield self.postgres_listener_service.stopService() |
495 | + |
496 | + @wait_for_reactor |
497 | + @inlineCallbacks |
498 | + def test_dns_dynamc_update_ip_update(self): |
499 | + listener = self.make_listener_without_delay() |
500 | + yield self.set_service(listener) |
501 | + yield deferToDatabase( |
502 | + self.register_trigger, |
503 | + "maasserver_staticipaddress", |
504 | + "sys_dns_updates", |
505 | + ops=("update",), |
506 | + trigger="sys_dns_updates_ip_update", |
507 | + ) |
508 | + vlan = yield deferToDatabase(self.create_vlan) |
509 | + subnet = yield deferToDatabase( |
510 | + self.create_subnet, params={"vlan": vlan} |
511 | + ) |
512 | + node = yield deferToDatabase( |
513 | + self.create_node_with_interface, |
514 | + params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED}, |
515 | + ) |
516 | + domain = yield deferToDatabase(Domain.objects.get_default_domain) |
517 | + iface = yield deferToDatabase( |
518 | + lambda: node.current_config.interface_set.first() |
519 | + ) |
520 | + ip = yield deferToDatabase( |
521 | + lambda: self.create_staticipaddress( |
522 | + params={ |
523 | + "ip": subnet.get_next_ip_for_allocation()[0], |
524 | + "interface": iface, |
525 | + "subnet": subnet, |
526 | + } |
527 | + ) |
528 | + ) |
529 | + old_ip = ip.ip |
530 | + |
531 | + def _set_new_ip(): |
532 | + ip.ip = subnet.get_next_ip_for_allocation()[0] |
533 | + ip.save() |
534 | + |
535 | + self.start_reading() |
536 | + try: |
537 | + yield deferToDatabase(_set_new_ip) |
538 | + msg1 = yield self.get_notify("sys_dns_updates") |
539 | + self.assertEqual( |
540 | + msg1, f"DELETE {domain.name} {node.hostname} A {old_ip}" |
541 | + ) |
542 | + msg2 = yield self.get_notify("sys_dns_updates") |
543 | + self.assertEqual( |
544 | + msg2, |
545 | + f"DELETE {domain.name} {iface.name}.{node.hostname} A {old_ip}", |
546 | + ) |
547 | + msg3 = yield self.get_notify("sys_dns_updates") |
548 | + self.assertEqual( |
549 | + msg3, f"INSERT {domain.name} {node.hostname} A 0 {ip.ip}" |
550 | + ) |
551 | + msg4 = yield self.get_notify("sys_dns_updates") |
552 | + self.assertEqual( |
553 | + msg4, |
554 | + f"INSERT {domain.name} {iface.name}.{node.hostname} A 0 {ip.ip}", |
555 | + ) |
556 | + finally: |
557 | + self.stop_reading() |
558 | + yield self.postgres_listener_service.stopService() |
559 | + |
560 | + @wait_for_reactor |
561 | + @inlineCallbacks |
562 | + def test_dns_dynamic_update_node_delete(self): |
563 | + listener = self.make_listener_without_delay() |
564 | + yield self.set_service(listener) |
565 | + yield deferToDatabase( |
566 | + self.register_trigger, |
567 | + "maasserver_node", |
568 | + "sys_dns_updates", |
569 | + ops=("delete",), |
570 | + ) |
571 | + node = yield deferToDatabase(self.create_node) |
572 | + domain = yield deferToDatabase(Domain.objects.get_default_domain) |
573 | + self.start_reading() |
574 | + try: |
575 | + yield deferToDatabase(node.delete) |
576 | + msg = yield self.get_notify("sys_dns_updates") |
577 | + self.assertEqual(msg, f"DELETE {domain.name} {node.hostname} A") |
578 | + finally: |
579 | + self.stop_reading() |
580 | + yield self.postgres_listener_service.stopService() |
581 | + |
582 | + @wait_for_reactor |
583 | + @inlineCallbacks |
584 | + def test_dns_dynamic_update_interface_delete(self): |
585 | + listener = self.make_listener_without_delay() |
586 | + yield self.set_service(listener) |
587 | + yield deferToDatabase( |
588 | + self.register_trigger, |
589 | + "maasserver_node", |
590 | + "sys_dns_updates", |
591 | + ops=("delete",), |
592 | + ) |
593 | + subnet = yield deferToDatabase(self.create_subnet) |
594 | + node = yield deferToDatabase( |
595 | + self.create_node_with_interface, params={"subnet": subnet} |
596 | + ) |
597 | + domain = yield deferToDatabase(Domain.objects.get_default_domain) |
598 | + iface = yield deferToDatabase( |
599 | + lambda: node.current_config.interface_set.first() |
600 | + ) |
601 | + self.start_reading() |
602 | + try: |
603 | + yield deferToDatabase(iface.delete) |
604 | + msg = yield self.get_notify("sys_dns_updates") |
605 | + self.assertEqual( |
606 | + msg, f"DELETE {domain.name} {iface.name}.{node.hostname} A" |
607 | + ) |
608 | + finally: |
609 | + self.stop_reading() |
610 | + yield self.postgres_listener_service.stopService() |
611 | diff --git a/src/provisioningserver/dns/zoneconfig.py b/src/provisioningserver/dns/zoneconfig.py |
612 | index 13dca69..eed539a 100644 |
613 | --- a/src/provisioningserver/dns/zoneconfig.py |
614 | +++ b/src/provisioningserver/dns/zoneconfig.py |
615 | @@ -7,6 +7,7 @@ |
616 | from datetime import datetime |
617 | from itertools import chain |
618 | import os |
619 | +from pathlib import Path |
620 | |
621 | from netaddr import IPAddress, IPNetwork, spanning_cidr |
622 | from netaddr.core import AddrFormatError |
623 | @@ -323,6 +324,7 @@ class DNSForwardZoneConfig(DomainConfigBase): |
624 | if not self.force_config_write and self.zone_file_exists(zi): |
625 | self.dynamic_update(zi) |
626 | else: |
627 | + Path(f"{zi.target_path}.jnl").unlink(missing_ok=True) |
628 | self.requires_reload = True |
629 | self.write_zone_file( |
630 | zi.target_path, |
631 | @@ -612,6 +614,7 @@ class DNSReverseZoneConfig(DomainConfigBase): |
632 | if not self.force_config_write and self.zone_file_exists(zi): |
633 | self.dynamic_update(zi) |
634 | else: |
635 | + Path(f"{zi.target_path}.jnl").unlink(missing_ok=True) |
636 | self.requires_reload = True |
637 | self.write_zone_file( |
638 | zi.target_path, |
top-approving backport