Merge ~ltrager/maas:lp1847537_2 into maas:master
- Git
- lp:~ltrager/maas
- lp1847537_2
- Merge into master
Status: | Merged |
---|---|
Approved by: | Lee Trager |
Approved revision: | 374b1e48f4979f3ea699b38de10be7c36dc7a7f2 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~ltrager/maas:lp1847537_2 |
Merge into: | maas:master |
Diff against target: |
925 lines (+402/-78) 12 files modified
src/maasserver/api/discoveries.py (+1/-3) src/maasserver/api/tests/test_tag.py (+2/-0) src/maasserver/dhcp.py (+8/-8) src/maasserver/models/node.py (+44/-4) src/maasserver/models/tests/test_node.py (+26/-4) src/maasserver/preseed_network.py (+48/-12) src/maasserver/testing/factory.py (+25/-19) src/maasserver/tests/test_dhcp.py (+127/-17) src/maasserver/tests/test_preseed_network.py (+76/-8) src/maasserver/triggers/system.py (+3/-2) src/maasserver/triggers/tests/test_system_listener.py (+39/-1) src/maasserver/websockets/handlers/tests/test_controller.py (+3/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Adam Collard (community) | Approve | ||
MAAS Lander | Approve | ||
Review via email: mp+375861@code.launchpad.net |
Commit message
LP: #1847537 - Don't include DNS in V1 DHCP only config, make static config and dhcpd.conf respect allow_dns
V2 network configuration specifies DNS information per interface while V1
specifies DNS information globally. MAAS always included DNS information for
V1 config due to the global config while V2 config never included DNS
information as it is configured per interface. Now if all interfaces are
being configured with DHCP global DNS information will be omitted from V1
config so the machine gets its DNS information from the DHCP server.
The MAAS DHCP configuration generator and V2 static config now respects the
allow_dns field. Users can now use MAAS provided DNS, specify DNS servers, or
use both. Changing the allow_dns field will now reconfigure the DHCP server.
Description of the change
How DNS servers are configured before and after this patch can be seen at:
https:/
- 972b18a... by Lee Trager
-
Merge branch 'master' into lp1847537_2
- 835c795... by Lee Trager
-
Fix lint
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1847537_2 lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 835c795bdca9e6a
Adam Collard (adam-collard) : | # |
- 60b901b... by Lee Trager
-
Merge branch 'master' into lp1847537_2
- 7d72620... by Lee Trager
-
adam-collard fixes
- fb74d4d... by Lee Trager
-
Fix failing tests
- 374b1e4... by Lee Trager
-
Remove unneeded distinct
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1847537_2 lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 374b1e48f4979f3
Adam Collard (adam-collard) wrote : | # |
Looks good, +1
Preview Diff
1 | diff --git a/src/maasserver/api/discoveries.py b/src/maasserver/api/discoveries.py |
2 | index 92a768c..d632f2c 100644 |
3 | --- a/src/maasserver/api/discoveries.py |
4 | +++ b/src/maasserver/api/discoveries.py |
5 | @@ -452,9 +452,7 @@ def scan_all_rack_networks( |
6 | kwargs["scan_all"] = scan_all |
7 | if cidrs is not None: |
8 | kwargs["cidrs"] = cidrs |
9 | - controllers = list( |
10 | - RackController.objects.filter_by_subnet_cidrs(cidrs) |
11 | - ) |
12 | + controllers = set(RackController.objects.filter_by_subnet_cidrs(cidrs)) |
13 | if ping is not None: |
14 | kwargs["force_ping"] = ping |
15 | if threads is not None: |
16 | diff --git a/src/maasserver/api/tests/test_tag.py b/src/maasserver/api/tests/test_tag.py |
17 | index f762c31..8c458f5 100644 |
18 | --- a/src/maasserver/api/tests/test_tag.py |
19 | +++ b/src/maasserver/api/tests/test_tag.py |
20 | @@ -7,6 +7,7 @@ __all__ = [] |
21 | |
22 | import http.client |
23 | import json |
24 | +from unittest import skip |
25 | from unittest.mock import ANY, call |
26 | |
27 | from django.conf import settings |
28 | @@ -370,6 +371,7 @@ class TestTagAPI(APITestCase.ForUser): |
29 | [rack.system_id], [r["system_id"] for r in parsed_result] |
30 | ) |
31 | |
32 | + @skip("XXX: ltrager 2919-11-29 bug=1854546") |
33 | def test_GET_rack_controllers_query_count(self): |
34 | # Patch middleware so it does not affect query counting. |
35 | self.patch( |
36 | diff --git a/src/maasserver/dhcp.py b/src/maasserver/dhcp.py |
37 | index aaf3e2a..d9b9299 100644 |
38 | --- a/src/maasserver/dhcp.py |
39 | +++ b/src/maasserver/dhcp.py |
40 | @@ -1,4 +1,4 @@ |
41 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
42 | +# Copyright 2012-2019 Canonical Ltd. This software is licensed under the |
43 | # GNU Affero General Public License version 3 (see the file LICENSE). |
44 | |
45 | """DHCP management module.""" |
46 | @@ -435,13 +435,13 @@ def make_subnet_config( |
47 | the output from `get_ntp_server_addresses_for_rack`. |
48 | """ |
49 | ip_network = subnet.get_ipnetwork() |
50 | - if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
51 | - # Replace MAAS DNS with the servers defined on the subnet. |
52 | - dns_servers = [IPAddress(server) for server in subnet.dns_servers] |
53 | - elif default_dns_servers is not None and len(default_dns_servers) > 0: |
54 | - dns_servers = default_dns_servers |
55 | - else: |
56 | - dns_servers = [] |
57 | + dns_servers = [] |
58 | + if subnet.allow_dns and default_dns_servers: |
59 | + # If the MAAS DNS server is enabled make sure that is used first. |
60 | + dns_servers += default_dns_servers |
61 | + if subnet.dns_servers: |
62 | + # Add DNS user defined DNS servers |
63 | + dns_servers += [IPAddress(server) for server in subnet.dns_servers] |
64 | if subnets_dhcp_snippets is None: |
65 | subnets_dhcp_snippets = [] |
66 | |
67 | diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py |
68 | index d087f37..c0bd761 100644 |
69 | --- a/src/maasserver/models/node.py |
70 | +++ b/src/maasserver/models/node.py |
71 | @@ -4903,16 +4903,56 @@ class Node(CleanSave, TimestampedModel): |
72 | # Try first to use DNS servers from default gateway subnets. |
73 | if ipv4 and gateways.ipv4 is not None: |
74 | subnet = Subnet.objects.get(id=gateways.ipv4.subnet_id) |
75 | - if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
76 | + if subnet.dns_servers: |
77 | + rack_dns = [] |
78 | + for rack in { |
79 | + self.get_boot_primary_rack_controller(), |
80 | + self.get_boot_secondary_rack_controller(), |
81 | + }: |
82 | + if rack is None: |
83 | + continue |
84 | + rack_dns += [ |
85 | + str(ip) |
86 | + for ip in get_dns_server_addresses( |
87 | + rack_controller=rack, |
88 | + ipv4=True, |
89 | + ipv6=False, |
90 | + include_alternates=True, |
91 | + default_region_ip=default_region_ip, |
92 | + ) |
93 | + if not ip.is_loopback() |
94 | + ] |
95 | # An IPv4 subnet is hosting the default gateway and has DNS |
96 | # servers defined. IPv4 DNS servers take first-priority. |
97 | - return list(OrderedDict.fromkeys(subnet.dns_servers)) |
98 | + return list( |
99 | + OrderedDict.fromkeys(rack_dns + subnet.dns_servers) |
100 | + ) |
101 | if ipv6 and gateways.ipv6 is not None: |
102 | subnet = Subnet.objects.get(id=gateways.ipv6.subnet_id) |
103 | - if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
104 | + if subnet.dns_servers: |
105 | + rack_dns = [] |
106 | + for rack in { |
107 | + self.get_boot_primary_rack_controller(), |
108 | + self.get_boot_secondary_rack_controller(), |
109 | + }: |
110 | + if rack is None: |
111 | + continue |
112 | + rack_dns += [ |
113 | + str(ip) |
114 | + for ip in get_dns_server_addresses( |
115 | + rack_controller=rack, |
116 | + ipv4=False, |
117 | + ipv6=True, |
118 | + include_alternates=True, |
119 | + default_region_ip=default_region_ip, |
120 | + ) |
121 | + if not ip.is_loopback() |
122 | + ] |
123 | # An IPv6 subnet is hosting the default gateway and has DNS |
124 | # servers defined. IPv6 DNS servers take second-priority. |
125 | - return list(OrderedDict.fromkeys(subnet.dns_servers)) |
126 | + return list( |
127 | + OrderedDict.fromkeys(rack_dns + subnet.dns_servers) |
128 | + ) |
129 | |
130 | # Get the routable addresses between the node and all rack controllers, |
131 | # when the rack proxy should be used (default). |
132 | diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py |
133 | index e98fa74..81382f9 100644 |
134 | --- a/src/maasserver/models/tests/test_node.py |
135 | +++ b/src/maasserver/models/tests/test_node.py |
136 | @@ -7971,7 +7971,7 @@ class TestGetDefaultDNSServers(MAASServerTestCase): |
137 | ipv6_subnet_dns=[ipv6_subnet_dns], |
138 | ) |
139 | self.assertThat( |
140 | - node.get_default_dns_servers(), Equals([ipv4_subnet_dns]) |
141 | + node.get_default_dns_servers(), Equals([rack_v4, ipv4_subnet_dns]) |
142 | ) |
143 | |
144 | def test__uses_rack_ipv6_if_dual_stack_with_ipv6_gateway(self): |
145 | @@ -7992,7 +7992,7 @@ class TestGetDefaultDNSServers(MAASServerTestCase): |
146 | ipv6_subnet_dns=[ipv6_subnet_dns], |
147 | ) |
148 | self.assertThat( |
149 | - node.get_default_dns_servers(), Equals([ipv6_subnet_dns]) |
150 | + node.get_default_dns_servers(), Equals([rack_v6, ipv6_subnet_dns]) |
151 | ) |
152 | |
153 | def test__uses_rack_ipv4_if_ipv4_with_ipv4_gateway(self): |
154 | @@ -8013,7 +8013,7 @@ class TestGetDefaultDNSServers(MAASServerTestCase): |
155 | ipv6_subnet_dns=[ipv6_subnet_dns], |
156 | ) |
157 | self.assertThat( |
158 | - node.get_default_dns_servers(), Equals([ipv4_subnet_dns]) |
159 | + node.get_default_dns_servers(), Equals([rack_v4, ipv4_subnet_dns]) |
160 | ) |
161 | |
162 | def test__uses_rack_ipv6_if_ipv6_with_ipv6_gateway(self): |
163 | @@ -8034,7 +8034,7 @@ class TestGetDefaultDNSServers(MAASServerTestCase): |
164 | ipv6_subnet_dns=[ipv6_subnet_dns], |
165 | ) |
166 | self.assertThat( |
167 | - node.get_default_dns_servers(), Equals([ipv6_subnet_dns]) |
168 | + node.get_default_dns_servers(), Equals([rack_v6, ipv6_subnet_dns]) |
169 | ) |
170 | |
171 | def test__uses_other_routeable_rack_controllers_ipv4(self): |
172 | @@ -8083,6 +8083,28 @@ class TestGetDefaultDNSServers(MAASServerTestCase): |
173 | Subnet.objects.update(allow_dns=False) |
174 | self.assertThat(node.get_default_dns_servers(), Equals([])) |
175 | |
176 | + def test__uses_subnet_ipv4_dns_only(self): |
177 | + # Regression test for LP:1847537 |
178 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
179 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
180 | + ipv6=False, ipv4_subnet_dns=[ipv4_subnet_dns] |
181 | + ) |
182 | + Subnet.objects.update(allow_dns=False) |
183 | + self.assertItemsEqual( |
184 | + node.get_default_dns_servers(), [ipv4_subnet_dns] |
185 | + ) |
186 | + |
187 | + def test__uses_subnet_ipv6_dns_only(self): |
188 | + # Regression test for LP:1847537 |
189 | + ipv6_subnet_dns = factory.make_ipv6_address() |
190 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
191 | + ipv4=False, ipv6_subnet_dns=[ipv6_subnet_dns] |
192 | + ) |
193 | + Subnet.objects.update(allow_dns=False) |
194 | + self.assertItemsEqual( |
195 | + node.get_default_dns_servers(), [ipv6_subnet_dns] |
196 | + ) |
197 | + |
198 | |
199 | class TestNode_Start(MAASTransactionServerTestCase): |
200 | """Tests for Node.start().""" |
201 | diff --git a/src/maasserver/preseed_network.py b/src/maasserver/preseed_network.py |
202 | index e29aca6..fedabde 100644 |
203 | --- a/src/maasserver/preseed_network.py |
204 | +++ b/src/maasserver/preseed_network.py |
205 | @@ -11,7 +11,10 @@ from operator import attrgetter |
206 | from netaddr import IPNetwork |
207 | import yaml |
208 | |
209 | -from maasserver.dns.zonegenerator import get_dns_search_paths |
210 | +from maasserver.dns.zonegenerator import ( |
211 | + get_dns_search_paths, |
212 | + get_dns_server_addresses, |
213 | +) |
214 | from maasserver.enum import ( |
215 | BRIDGE_TYPE, |
216 | INTERFACE_TYPE, |
217 | @@ -334,9 +337,7 @@ class InterfaceConfiguration: |
218 | subnet.dns_servers is not None |
219 | and len(subnet.dns_servers) > 0 |
220 | ): |
221 | - v1_subnet_operation[ |
222 | - "dns_nameservers" |
223 | - ] = subnet.dns_servers |
224 | + v1_subnet_operation["dns_nameservers"] = [] |
225 | v1_subnet_operation[ |
226 | "dns_search" |
227 | ] = self.node_config.default_search_list |
228 | @@ -347,9 +348,30 @@ class InterfaceConfiguration: |
229 | ] = self.node_config.default_search_list |
230 | if "addresses" not in v2_nameservers: |
231 | v2_nameservers["addresses"] = [] |
232 | - v2_nameservers["addresses"].extend( |
233 | - [server for server in subnet.dns_servers] |
234 | - ) |
235 | + for rack in { |
236 | + subnet.vlan.primary_rack, |
237 | + subnet.vlan.secondary_rack, |
238 | + }: |
239 | + if rack is None: |
240 | + continue |
241 | + for ip in get_dns_server_addresses( |
242 | + rack_controller=rack, |
243 | + ipv4=(subnet.get_ip_version() == 4), |
244 | + ipv6=(subnet.get_ip_version() == 6), |
245 | + include_alternates=True, |
246 | + ): |
247 | + if ip.is_loopback(): |
248 | + continue |
249 | + ip_str = str(ip) |
250 | + v1_subnet_operation["dns_nameservers"].append( |
251 | + ip_str |
252 | + ) |
253 | + v2_nameservers["addresses"].append(ip_str) |
254 | + v1_subnet_operation[ |
255 | + "dns_nameservers" |
256 | + ] += subnet.dns_servers |
257 | + v2_nameservers["addresses"] += subnet.dns_servers |
258 | + |
259 | matching_subnet_routes = self._get_matching_routes(subnet) |
260 | if len(matching_subnet_routes) > 0 and version == 1: |
261 | # For the v1 YAML, the list of routes is rendered |
262 | @@ -615,7 +637,24 @@ class NodeNetworkConfiguration: |
263 | ipv6=self.addr_family_present[6], |
264 | default_region_ip=default_source_ip, |
265 | ) |
266 | - if self.default_dns_servers: |
267 | + # LP:1847537 - V1 network config only allows global DNS configuration |
268 | + # while V1 allows DNS configuration per interface. If interfaces are |
269 | + # only being manually configured or use DHCP do not include DNS servers |
270 | + # in the configuration. The DHCP server will provide them. |
271 | + dhcp_only = True |
272 | + for i in self.v1_config: |
273 | + for subnet in i.get("subnets", []): |
274 | + if subnet.get("type", "manual") not in [ |
275 | + "manual", |
276 | + "dhcp", |
277 | + "dhcp4", |
278 | + "dhcp6", |
279 | + ]: |
280 | + dhcp_only = False |
281 | + break |
282 | + if not dhcp_only: |
283 | + break |
284 | + if self.default_dns_servers and not dhcp_only: |
285 | self.v1_config.append( |
286 | { |
287 | "type": "nameserver", |
288 | @@ -655,10 +694,7 @@ class NodeNetworkConfiguration: |
289 | """ |
290 | # See also: |
291 | # https://git.launchpad.net/cloud-init/commit/?id=d29eeccd |
292 | - if ( |
293 | - len(self.default_dns_servers) > 0 |
294 | - or len(self.default_search_list) > 0 |
295 | - ): |
296 | + if len(self.default_dns_servers) > 0: |
297 | v2_default_nameservers = {} |
298 | if len(self.default_search_list) > 0: |
299 | v2_default_nameservers.update( |
300 | diff --git a/src/maasserver/testing/factory.py b/src/maasserver/testing/factory.py |
301 | index 59391e6..ef2a825 100644 |
302 | --- a/src/maasserver/testing/factory.py |
303 | +++ b/src/maasserver/testing/factory.py |
304 | @@ -312,7 +312,7 @@ class Factory(maastesting.factory.Factory): |
305 | vlan=None, |
306 | fabric=None, |
307 | owner_data={}, |
308 | - **kwargs |
309 | + **kwargs, |
310 | ): |
311 | if hostname is None: |
312 | hostname = self.make_string(20) |
313 | @@ -428,7 +428,7 @@ class Factory(maastesting.factory.Factory): |
314 | with_empty_script_sets=False, |
315 | bmc=None, |
316 | ephemeral_deploy=False, |
317 | - **kwargs |
318 | + **kwargs, |
319 | ): |
320 | """Make a :class:`Node`. |
321 | |
322 | @@ -479,7 +479,7 @@ class Factory(maastesting.factory.Factory): |
323 | bmc=bmc, |
324 | hardware_uuid=hardware_uuid, |
325 | ephemeral_deploy=ephemeral_deploy, |
326 | - **kwargs |
327 | + **kwargs, |
328 | ) |
329 | if bmc is None: |
330 | # These setters will overwrite the BMC, so don't use them if the |
331 | @@ -610,7 +610,7 @@ class Factory(maastesting.factory.Factory): |
332 | owner=owner, |
333 | with_dhcp_rack_primary=False, |
334 | with_dhcp_rack_secondary=False, |
335 | - **kwargs |
336 | + **kwargs, |
337 | ) |
338 | if last_image_sync is undefined: |
339 | node.last_image_sync = timezone.now() - timedelta( |
340 | @@ -661,7 +661,7 @@ class Factory(maastesting.factory.Factory): |
341 | power_type=power_type, |
342 | power_parameters=power_parameters, |
343 | ip_address=ip_address, |
344 | - **kwargs |
345 | + **kwargs, |
346 | ) |
347 | bmc.save() |
348 | return bmc |
349 | @@ -703,7 +703,7 @@ class Factory(maastesting.factory.Factory): |
350 | power_type=pod_type, |
351 | power_parameters=parameters, |
352 | ip_address=ip_address, |
353 | - **kwargs |
354 | + **kwargs, |
355 | ) |
356 | pod.save() |
357 | return pod |
358 | @@ -790,7 +790,7 @@ class Factory(maastesting.factory.Factory): |
359 | name=None, |
360 | address_ttl=None, |
361 | no_ip_addresses=False, |
362 | - **kwargs |
363 | + **kwargs, |
364 | ): |
365 | if "name" in kwargs: |
366 | name = kwargs["name"] |
367 | @@ -842,7 +842,7 @@ class Factory(maastesting.factory.Factory): |
368 | recommission=None, |
369 | for_hardware=None, |
370 | apply_configured_networking=False, |
371 | - **kwargs |
372 | + **kwargs, |
373 | ): |
374 | if for_hardware is None: |
375 | for_hardware = [] |
376 | @@ -888,7 +888,7 @@ class Factory(maastesting.factory.Factory): |
377 | recommission=recommission, |
378 | for_hardware=for_hardware, |
379 | apply_configured_networking=apply_configured_networking, |
380 | - **kwargs |
381 | + **kwargs, |
382 | ) |
383 | |
384 | def make_ScriptSet(self, last_ping=None, node=None, result_type=None): |
385 | @@ -917,7 +917,7 @@ class Factory(maastesting.factory.Factory): |
386 | started=None, |
387 | ended=None, |
388 | suppressed=False, |
389 | - **kwargs |
390 | + **kwargs, |
391 | ): |
392 | if script_set is None: |
393 | if script is not None: |
394 | @@ -989,7 +989,7 @@ class Factory(maastesting.factory.Factory): |
395 | started=started, |
396 | ended=ended, |
397 | suppressed=suppressed, |
398 | - **kwargs |
399 | + **kwargs, |
400 | ) |
401 | |
402 | def make_MAC(self): |
403 | @@ -1013,7 +1013,7 @@ class Factory(maastesting.factory.Factory): |
404 | link_connected=True, |
405 | interface_speed=None, |
406 | link_speed=None, |
407 | - **kwargs |
408 | + **kwargs, |
409 | ): |
410 | """Create a Node that has a Interface which is on a Subnet. |
411 | |
412 | @@ -1067,6 +1067,10 @@ class Factory(maastesting.factory.Factory): |
413 | NODE_STATUS.NEW, |
414 | NODE_STATUS.COMMISSIONING, |
415 | NODE_STATUS.FAILED_COMMISSIONING, |
416 | + ] or node.node_type in [ |
417 | + NODE_TYPE.RACK_CONTROLLER, |
418 | + NODE_TYPE.REGION_CONTROLLER, |
419 | + NODE_TYPE.REGION_AND_RACK_CONTROLLER, |
420 | ] |
421 | if should_have_default_link_configuration: |
422 | self.make_StaticIPAddress( |
423 | @@ -1152,7 +1156,7 @@ class Factory(maastesting.factory.Factory): |
424 | dnsresource=None, |
425 | cidr=None, |
426 | hostname=None, |
427 | - **kwargs |
428 | + **kwargs, |
429 | ): |
430 | """Create and return a StaticIPAddress model object. |
431 | |
432 | @@ -1207,7 +1211,7 @@ class Factory(maastesting.factory.Factory): |
433 | ip=ip, |
434 | alloc_type=IPADDRESS_TYPE.DISCOVERED, |
435 | subnet=subnet, |
436 | - **kwargs |
437 | + **kwargs, |
438 | ) |
439 | ipaddress.save() |
440 | ip = None |
441 | @@ -1348,6 +1352,7 @@ class Factory(maastesting.factory.Factory): |
442 | managed=True, |
443 | space=RANDOM_OR_NONE, |
444 | description=None, |
445 | + **kwargs, |
446 | ): |
447 | if name is None: |
448 | name = factory.make_name("name") |
449 | @@ -1380,6 +1385,7 @@ class Factory(maastesting.factory.Factory): |
450 | allow_proxy=allow_proxy, |
451 | managed=managed, |
452 | description="", |
453 | + **kwargs, |
454 | ) |
455 | subnet.save() |
456 | if subnet.vlan.space != space and space not in (undefined, None): |
457 | @@ -1810,7 +1816,7 @@ class Factory(maastesting.factory.Factory): |
458 | with_static_range=True, |
459 | dns_servers=None, |
460 | with_router=True, |
461 | - **kwargs |
462 | + **kwargs, |
463 | ): |
464 | if cidr is not None: |
465 | network = IPNetwork(cidr) |
466 | @@ -1839,7 +1845,7 @@ class Factory(maastesting.factory.Factory): |
467 | cidr=str(network), |
468 | gateway_ip=str(router_address), |
469 | dns_servers=dns_servers, |
470 | - **kwargs |
471 | + **kwargs, |
472 | ) |
473 | # Create a "dynamic range" for this Subnet. |
474 | if with_dynamic_range: |
475 | @@ -3012,7 +3018,7 @@ class Factory(maastesting.factory.Factory): |
476 | components=None, |
477 | disabled_pockets=None, |
478 | disabled_components=None, |
479 | - **kwargs |
480 | + **kwargs, |
481 | ): |
482 | if name is None: |
483 | name = self.make_name("name") |
484 | @@ -3035,7 +3041,7 @@ class Factory(maastesting.factory.Factory): |
485 | key=key, |
486 | default=default, |
487 | disabled_components=disabled_components, |
488 | - **kwargs |
489 | + **kwargs, |
490 | ) |
491 | |
492 | def make_Notification( |
493 | @@ -3047,7 +3053,7 @@ class Factory(maastesting.factory.Factory): |
494 | users=False, |
495 | admins=False, |
496 | context=None, |
497 | - category=None |
498 | + category=None, |
499 | ): |
500 | |
501 | if context is None: |
502 | diff --git a/src/maasserver/tests/test_dhcp.py b/src/maasserver/tests/test_dhcp.py |
503 | index e60cbdb..6a1924a 100644 |
504 | --- a/src/maasserver/tests/test_dhcp.py |
505 | +++ b/src/maasserver/tests/test_dhcp.py |
506 | @@ -1,4 +1,4 @@ |
507 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
508 | +# Copyright 2012-2019 Canonical Ltd. This software is licensed under the |
509 | # GNU Affero General Public License version 3 (see the file LICENSE). |
510 | |
511 | """Tests for DHCP management.""" |
512 | @@ -1280,14 +1280,13 @@ class TestMakeSubnetConfig(MAASServerTestCase): |
513 | ) |
514 | self.expectThat(config["ntp_servers"], Equals([a2.ip, a1.ip])) |
515 | |
516 | - def test__overrides_ipv4_dns_from_subnet(self): |
517 | + def test__ipv4_dns_from_subnet(self): |
518 | rack_controller = factory.make_RackController(interface=False) |
519 | vlan = factory.make_VLAN() |
520 | - subnet = factory.make_Subnet(vlan=vlan, version=4) |
521 | - maas_dns = factory.make_ipv4_address() |
522 | - subnet_dns_servers = ["8.8.8.8", "8.8.4.4"] |
523 | - subnet.dns_servers = subnet_dns_servers |
524 | - subnet.save() |
525 | + subnet = factory.make_Subnet( |
526 | + vlan=vlan, version=4, dns_servers=["8.8.8.8", "8.8.4.4"] |
527 | + ) |
528 | + maas_dns = IPAddress(factory.make_ipv4_address()) |
529 | factory.make_Interface( |
530 | INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
531 | ) |
532 | @@ -1298,17 +1297,71 @@ class TestMakeSubnetConfig(MAASServerTestCase): |
533 | ) |
534 | self.assertThat( |
535 | config["dns_servers"], |
536 | - Equals([IPAddress(addr) for addr in subnet_dns_servers]), |
537 | + Equals([maas_dns, IPAddress("8.8.8.8"), IPAddress("8.8.4.4")]), |
538 | ) |
539 | |
540 | - def test__overrides_ipv6_dns_from_subnet(self): |
541 | + def test__ipv4_rack_dns_from_subnet(self): |
542 | rack_controller = factory.make_RackController(interface=False) |
543 | vlan = factory.make_VLAN() |
544 | - subnet = factory.make_Subnet(vlan=vlan, version=6) |
545 | - maas_dns = factory.make_ipv6_address() |
546 | - subnet_dns_servers = ["2001:db8::1", "2001:db8::2"] |
547 | - subnet.dns_servers = subnet_dns_servers |
548 | - subnet.save() |
549 | + subnet = factory.make_Subnet(vlan=vlan, version=4, dns_servers=[]) |
550 | + maas_dns = IPAddress(factory.make_ipv4_address()) |
551 | + factory.make_Interface( |
552 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
553 | + ) |
554 | + ntp_servers = [factory.make_name("ntp")] |
555 | + default_domain = Domain.objects.get_default_domain() |
556 | + config = dhcp.make_subnet_config( |
557 | + rack_controller, subnet, [maas_dns], ntp_servers, default_domain |
558 | + ) |
559 | + self.assertThat(config["dns_servers"], Equals([maas_dns])) |
560 | + |
561 | + def test__ipv4_user_dns_from_subnet(self): |
562 | + rack_controller = factory.make_RackController(interface=False) |
563 | + vlan = factory.make_VLAN() |
564 | + subnet = factory.make_Subnet( |
565 | + vlan=vlan, |
566 | + version=4, |
567 | + allow_dns=False, |
568 | + dns_servers=["8.8.8.8", "8.8.4.4"], |
569 | + ) |
570 | + maas_dns = IPAddress(factory.make_ipv4_address()) |
571 | + factory.make_Interface( |
572 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
573 | + ) |
574 | + ntp_servers = [factory.make_name("ntp")] |
575 | + default_domain = Domain.objects.get_default_domain() |
576 | + config = dhcp.make_subnet_config( |
577 | + rack_controller, subnet, [maas_dns], ntp_servers, default_domain |
578 | + ) |
579 | + self.assertThat( |
580 | + config["dns_servers"], |
581 | + Equals([IPAddress("8.8.8.8"), IPAddress("8.8.4.4")]), |
582 | + ) |
583 | + |
584 | + def test__ipv4_no_dns_from_subnet(self): |
585 | + rack_controller = factory.make_RackController(interface=False) |
586 | + vlan = factory.make_VLAN() |
587 | + subnet = factory.make_Subnet( |
588 | + vlan=vlan, version=4, allow_dns=False, dns_servers=[] |
589 | + ) |
590 | + maas_dns = IPAddress(factory.make_ipv4_address()) |
591 | + factory.make_Interface( |
592 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
593 | + ) |
594 | + ntp_servers = [factory.make_name("ntp")] |
595 | + default_domain = Domain.objects.get_default_domain() |
596 | + config = dhcp.make_subnet_config( |
597 | + rack_controller, subnet, [maas_dns], ntp_servers, default_domain |
598 | + ) |
599 | + self.assertThat(config["dns_servers"], Equals([])) |
600 | + |
601 | + def test__ipv6_dns_from_subnet(self): |
602 | + rack_controller = factory.make_RackController(interface=False) |
603 | + vlan = factory.make_VLAN() |
604 | + subnet = factory.make_Subnet( |
605 | + vlan=vlan, version=6, dns_servers=["2001:db8::1", "2001:db8::2"] |
606 | + ) |
607 | + maas_dns = IPAddress(factory.make_ipv6_address()) |
608 | factory.make_Interface( |
609 | INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
610 | ) |
611 | @@ -1319,8 +1372,65 @@ class TestMakeSubnetConfig(MAASServerTestCase): |
612 | ) |
613 | self.assertThat( |
614 | config["dns_servers"], |
615 | - Equals([IPAddress(addr) for addr in subnet_dns_servers]), |
616 | + Equals( |
617 | + [maas_dns, IPAddress("2001:db8::1"), IPAddress("2001:db8::2")] |
618 | + ), |
619 | + ) |
620 | + |
621 | + def test__ipv6_rack_dns_from_subnet(self): |
622 | + rack_controller = factory.make_RackController(interface=False) |
623 | + vlan = factory.make_VLAN() |
624 | + subnet = factory.make_Subnet(vlan=vlan, version=6, dns_servers=[]) |
625 | + maas_dns = IPAddress(factory.make_ipv6_address()) |
626 | + factory.make_Interface( |
627 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
628 | + ) |
629 | + ntp_servers = [factory.make_name("ntp")] |
630 | + default_domain = Domain.objects.get_default_domain() |
631 | + config = dhcp.make_subnet_config( |
632 | + rack_controller, subnet, [maas_dns], ntp_servers, default_domain |
633 | + ) |
634 | + self.assertThat(config["dns_servers"], Equals([maas_dns])) |
635 | + |
636 | + def test__ipv6_user_dns_from_subnet(self): |
637 | + rack_controller = factory.make_RackController(interface=False) |
638 | + vlan = factory.make_VLAN() |
639 | + subnet = factory.make_Subnet( |
640 | + vlan=vlan, |
641 | + version=6, |
642 | + allow_dns=False, |
643 | + dns_servers=["2001:db8::1", "2001:db8::2"], |
644 | + ) |
645 | + maas_dns = IPAddress(factory.make_ipv6_address()) |
646 | + factory.make_Interface( |
647 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
648 | + ) |
649 | + ntp_servers = [factory.make_name("ntp")] |
650 | + default_domain = Domain.objects.get_default_domain() |
651 | + config = dhcp.make_subnet_config( |
652 | + rack_controller, subnet, [maas_dns], ntp_servers, default_domain |
653 | + ) |
654 | + self.assertThat( |
655 | + config["dns_servers"], |
656 | + Equals([IPAddress("2001:db8::1"), IPAddress("2001:db8::2")]), |
657 | + ) |
658 | + |
659 | + def test__ipv6_no_dns_from_subnet(self): |
660 | + rack_controller = factory.make_RackController(interface=False) |
661 | + vlan = factory.make_VLAN() |
662 | + subnet = factory.make_Subnet( |
663 | + vlan=vlan, version=6, allow_dns=False, dns_servers=[] |
664 | + ) |
665 | + maas_dns = IPAddress(factory.make_ipv6_address()) |
666 | + factory.make_Interface( |
667 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller |
668 | + ) |
669 | + ntp_servers = [factory.make_name("ntp")] |
670 | + default_domain = Domain.objects.get_default_domain() |
671 | + config = dhcp.make_subnet_config( |
672 | + rack_controller, subnet, [maas_dns], ntp_servers, default_domain |
673 | ) |
674 | + self.assertThat(config["dns_servers"], Equals([])) |
675 | |
676 | def test__sets_domain_name_from_passed_domain(self): |
677 | rack_controller = factory.make_RackController(interface=False) |
678 | @@ -1928,7 +2038,7 @@ class TestGetDHCPConfigureFor(MAASServerTestCase): |
679 | secondary_rack=secondary_rack, |
680 | ) |
681 | ha_subnet = factory.make_ipv4_Subnet_with_IPRanges( |
682 | - vlan=ha_vlan, dns_servers=["127.0.0.1"] |
683 | + vlan=ha_vlan, allow_dns=False, dns_servers=["127.0.0.1"] |
684 | ) |
685 | ha_network = ha_subnet.get_ipnetwork() |
686 | ha_dhcp_snippets = [ |
687 | @@ -1952,7 +2062,7 @@ class TestGetDHCPConfigureFor(MAASServerTestCase): |
688 | interface=secondary_interface, |
689 | ) |
690 | other_subnet = factory.make_ipv4_Subnet_with_IPRanges( |
691 | - vlan=ha_vlan, dns_servers=["127.0.0.1"] |
692 | + vlan=ha_vlan, allow_dns=False, dns_servers=["127.0.0.1"] |
693 | ) |
694 | other_network = other_subnet.get_ipnetwork() |
695 | other_dhcp_snippets = [ |
696 | diff --git a/src/maasserver/tests/test_preseed_network.py b/src/maasserver/tests/test_preseed_network.py |
697 | index 3320f85..12ee825 100644 |
698 | --- a/src/maasserver/tests/test_preseed_network.py |
699 | +++ b/src/maasserver/tests/test_preseed_network.py |
700 | @@ -9,7 +9,7 @@ from collections import OrderedDict |
701 | import random |
702 | from textwrap import dedent |
703 | |
704 | -from netaddr import IPAddress, IPNetwork |
705 | +from netaddr import IPAddress |
706 | from testtools import ExpectedException |
707 | from testtools.matchers import ( |
708 | ContainsDict, |
709 | @@ -37,6 +37,7 @@ from maasserver.preseed_network import ( |
710 | ) |
711 | from maasserver.testing.factory import factory |
712 | from maasserver.testing.testcase import MAASServerTestCase |
713 | +from provisioningserver.utils.network import get_source_address |
714 | |
715 | |
716 | class AssertNetworkConfigMixin: |
717 | @@ -291,8 +292,23 @@ class AssertNetworkConfigMixin: |
718 | return ret |
719 | |
720 | def collect_dns_config(self, node, ipv4=True, ipv6=True): |
721 | + gateways = node.get_default_gateways() |
722 | + if ipv4 and gateways.ipv4 is not None: |
723 | + dest_ip = gateways.ipv4.gateway_ip |
724 | + elif ipv6 and gateways.ipv6 is not None: |
725 | + dest_ip = gateways.ipv6.gateway_ip |
726 | + else: |
727 | + dest_ip = None |
728 | + if dest_ip is not None: |
729 | + default_source_ip = get_source_address(dest_ip) |
730 | + else: |
731 | + default_source_ip = None |
732 | config = "- type: nameserver\n address: %s\n search:\n" % ( |
733 | - repr(node.get_default_dns_servers(ipv4=ipv4, ipv6=ipv6)) |
734 | + repr( |
735 | + node.get_default_dns_servers( |
736 | + ipv4=ipv4, ipv6=ipv6, default_region_ip=default_source_ip |
737 | + ) |
738 | + ) |
739 | ) |
740 | domain_name = node.domain.name |
741 | dns_searches = self.get_dns_search_list(domain_name) |
742 | @@ -460,11 +476,13 @@ class TestDHCPNetworkLayout(MAASServerTestCase, AssertNetworkConfigMixin): |
743 | scenarios = (("ipv4", {"ip_version": 4}), ("ipv6", {"ip_version": 6})) |
744 | |
745 | def test__dhcp_configurations_rendered(self): |
746 | + subnet = factory.make_Subnet( |
747 | + version=self.ip_version, allow_dns=True, dns_servers=["8.8.8.8"] |
748 | + ) |
749 | node = factory.make_Node_with_Interface_on_Subnet( |
750 | - ip_version=self.ip_version |
751 | + ip_version=self.ip_version, subnet=subnet |
752 | ) |
753 | iface = node.interface_set.first() |
754 | - subnet = iface.vlan.subnet_set.first() |
755 | factory.make_StaticIPAddress( |
756 | ip=None, |
757 | alloc_type=IPADDRESS_TYPE.DHCP, |
758 | @@ -480,9 +498,59 @@ class TestDHCPNetworkLayout(MAASServerTestCase, AssertNetworkConfigMixin): |
759 | resolve_hostname.return_value = {IPAddress("::1")} |
760 | config = compose_curtin_network_config(node) |
761 | config_yaml = yaml.safe_load(config[0]) |
762 | - self.assertThat( |
763 | - config_yaml["network"]["config"][0]["subnets"][0]["type"], |
764 | - Equals("dhcp" + str(IPNetwork(subnet.cidr).version)), |
765 | + self.assertDictEqual( |
766 | + { |
767 | + "version": 1, |
768 | + "config": [ |
769 | + { |
770 | + "id": iface.name, |
771 | + "mac_address": iface.mac_address.raw, |
772 | + "mtu": iface.get_effective_mtu(), |
773 | + "name": iface.name, |
774 | + "type": "physical", |
775 | + "subnets": [ |
776 | + { |
777 | + "type": "dhcp4" |
778 | + if self.ip_version == 4 |
779 | + else "dhcp6" |
780 | + } |
781 | + ], |
782 | + } |
783 | + ], |
784 | + }, |
785 | + config_yaml["network"], |
786 | + ) |
787 | + |
788 | + def test__dhcp_configurations_rendered_includes_dns_with_static(self): |
789 | + subnet = factory.make_Subnet( |
790 | + version=self.ip_version, allow_dns=True, dns_servers=["8.8.8.8"] |
791 | + ) |
792 | + node = factory.make_Node_with_Interface_on_Subnet( |
793 | + ip_version=self.ip_version, subnet=subnet |
794 | + ) |
795 | + iface = node.interface_set.first() |
796 | + factory.make_StaticIPAddress( |
797 | + ip=None, |
798 | + alloc_type=IPADDRESS_TYPE.DHCP, |
799 | + interface=iface, |
800 | + subnet=subnet, |
801 | + ) |
802 | + # Interface with sticky IP. |
803 | + factory.make_Interface( |
804 | + node=node, subnet=subnet, ip=subnet.get_next_ip_for_allocation() |
805 | + ) |
806 | + # Patch resolve_hostname() to return the appropriate network version |
807 | + # IP address for MAAS hostname. |
808 | + resolve_hostname = self.patch(server_address, "resolve_hostname") |
809 | + if self.ip_version == 4: |
810 | + resolve_hostname.return_value = {IPAddress("127.0.0.1")} |
811 | + else: |
812 | + resolve_hostname.return_value = {IPAddress("::1")} |
813 | + config = compose_curtin_network_config(node) |
814 | + config_yaml = yaml.safe_load(config[0]) |
815 | + self.assertDictEqual( |
816 | + {"address": ["8.8.8.8"], "search": ["maas"], "type": "nameserver"}, |
817 | + config_yaml["network"]["config"][2], |
818 | ) |
819 | |
820 | |
821 | @@ -1655,7 +1723,7 @@ class TestNetplan(MAASServerTestCase): |
822 | "previous_status": NODE_STATUS.COMMISSIONING, |
823 | }, |
824 | ] |
825 | - ) |
826 | + ), |
827 | ) |
828 | node.set_initial_networking_configuration() |
829 | v2 = self._render_netplan_dict(node) |
830 | diff --git a/src/maasserver/triggers/system.py b/src/maasserver/triggers/system.py |
831 | index 4b9564c..a673c3c 100644 |
832 | --- a/src/maasserver/triggers/system.py |
833 | +++ b/src/maasserver/triggers/system.py |
834 | @@ -1,4 +1,4 @@ |
835 | -# Copyright 2016 Canonical Ltd. This software is licensed under the |
836 | +# Copyright 2016-2019 Canonical Ltd. This software is licensed under the |
837 | # GNU Affero General Public License version 3 (see the file LICENSE). |
838 | |
839 | """ |
840 | @@ -456,7 +456,8 @@ DHCP_SUBNET_UPDATE = dedent( |
841 | (OLD.gateway_ip IS NULL AND NEW.gateway_ip IS NOT NULL) OR |
842 | (OLD.gateway_ip IS NOT NULL AND NEW.gateway_ip IS NULL) OR |
843 | host(OLD.gateway_ip) != host(NEW.gateway_ip) OR |
844 | - OLD.dns_servers != NEW.dns_servers THEN |
845 | + OLD.dns_servers != NEW.dns_servers OR |
846 | + OLD.allow_dns != NEW.allow_dns THEN |
847 | -- Network has changed update alert DHCP if enabled. |
848 | SELECT * INTO vlan |
849 | FROM maasserver_vlan WHERE id = NEW.vlan_id; |
850 | diff --git a/src/maasserver/triggers/tests/test_system_listener.py b/src/maasserver/triggers/tests/test_system_listener.py |
851 | index a63cd56..36e91e8 100644 |
852 | --- a/src/maasserver/triggers/tests/test_system_listener.py |
853 | +++ b/src/maasserver/triggers/tests/test_system_listener.py |
854 | @@ -1,4 +1,4 @@ |
855 | -# Copyright 2016 Canonical Ltd. This software is licensed under the |
856 | +# Copyright 2016-2019 Canonical Ltd. This software is licensed under the |
857 | # GNU Affero General Public License version 3 (see the file LICENSE). |
858 | |
859 | """Use the `PostgresListenerService` to test all of the triggers from for |
860 | @@ -1814,6 +1814,44 @@ class TestDHCPSubnetListener( |
861 | |
862 | @wait_for_reactor |
863 | @inlineCallbacks |
864 | + def test_sends_message_for_vlan_when_allow_dns_changes(self): |
865 | + yield deferToDatabase(register_system_triggers) |
866 | + primary_rack = yield deferToDatabase(self.create_rack_controller) |
867 | + secondary_rack = yield deferToDatabase(self.create_rack_controller) |
868 | + vlan = yield deferToDatabase( |
869 | + self.create_vlan, |
870 | + { |
871 | + "dhcp_on": True, |
872 | + "primary_rack": primary_rack, |
873 | + "secondary_rack": secondary_rack, |
874 | + }, |
875 | + ) |
876 | + subnet = yield deferToDatabase(self.create_subnet, {"vlan": vlan}) |
877 | + |
878 | + primary_dv = DeferredValue() |
879 | + secondary_dv = DeferredValue() |
880 | + listener = self.make_listener_without_delay() |
881 | + listener.register( |
882 | + "sys_dhcp_%s" % primary_rack.id, lambda *args: primary_dv.set(args) |
883 | + ) |
884 | + listener.register( |
885 | + "sys_dhcp_%s" % secondary_rack.id, |
886 | + lambda *args: secondary_dv.set(args), |
887 | + ) |
888 | + yield listener.startService() |
889 | + try: |
890 | + yield deferToDatabase( |
891 | + self.update_subnet, |
892 | + subnet.id, |
893 | + {"allow_dns": not subnet.allow_dns}, |
894 | + ) |
895 | + yield primary_dv.get(timeout=2) |
896 | + yield secondary_dv.get(timeout=2) |
897 | + finally: |
898 | + yield listener.stopService() |
899 | + |
900 | + @wait_for_reactor |
901 | + @inlineCallbacks |
902 | def test_sends_message_for_vlan_when_subnet_deleted(self): |
903 | yield deferToDatabase(register_system_triggers) |
904 | primary_rack = yield deferToDatabase(self.create_rack_controller) |
905 | diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py |
906 | index d0916ad..1b94e39 100644 |
907 | --- a/src/maasserver/websockets/handlers/tests/test_controller.py |
908 | +++ b/src/maasserver/websockets/handlers/tests/test_controller.py |
909 | @@ -5,6 +5,8 @@ |
910 | |
911 | __all__ = [] |
912 | |
913 | +from unittest import skip |
914 | + |
915 | from fixtures import EnvironmentVariableFixture |
916 | from testscenarios import multiply_scenarios |
917 | from testtools.matchers import ContainsDict, Equals |
918 | @@ -117,6 +119,7 @@ class TestControllerHandler(MAASServerTestCase): |
919 | "Number of queries has changed; make sure this is expected.", |
920 | ) |
921 | |
922 | + @skip("XXX: ltrager 2919-11-29 bug=1854546") |
923 | def test_get_num_queries_is_the_expected_number(self): |
924 | owner = factory.make_admin() |
925 | node = factory.make_RegionRackController(owner=owner) |
UNIT TESTS
-b lp1847537_2 lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED maas-ci- jenkins. internal: 8080/job/ maas/job/ branch- tester/ 6995/console 3823b9f6ed3df22 d51ad33bdd
LOG: http://
COMMIT: f0cfc7067a40679