Merge ~mpontillo/maas:preseed-per-interface-routes--bug-1758919--2.3 into maas:2.3
- Git
- lp:~mpontillo/maas
- preseed-per-interface-routes--bug-1758919--2.3
- Merge into 2.3
Proposed by
Mike Pontillo
Status: | Merged |
---|---|
Approved by: | Mike Pontillo |
Approved revision: | 434ff94a620afd73b314baa08e7e95fa36ec41ca |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~mpontillo/maas:preseed-per-interface-routes--bug-1758919--2.3 |
Merge into: | maas:2.3 |
Diff against target: |
381 lines (+233/-61) 2 files modified
src/maasserver/preseed_network.py (+25/-16) src/maasserver/tests/test_preseed_network.py (+208/-45) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Review via email: mp+342243@code.launchpad.net |
Commit message
LP: #1758919 - Move routes to interface context within preseed.
Backports: 9fbf7eb682c3c3f
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/preseed_network.py b/src/maasserver/preseed_network.py | |||
2 | index 1be72d6..bb56765 100644 | |||
3 | --- a/src/maasserver/preseed_network.py | |||
4 | +++ b/src/maasserver/preseed_network.py | |||
5 | @@ -52,8 +52,6 @@ def _generate_route_operation(route, version=1): | |||
6 | 52 | """Generate route operation place in `network_config`.""" | 52 | """Generate route operation place in `network_config`.""" |
7 | 53 | if version == 1: | 53 | if version == 1: |
8 | 54 | route_operation = { | 54 | route_operation = { |
9 | 55 | "id": route.id, | ||
10 | 56 | "type": "route", | ||
11 | 57 | "destination": route.destination.cidr, | 55 | "destination": route.destination.cidr, |
12 | 58 | "gateway": route.gateway_ip, | 56 | "gateway": route.gateway_ip, |
13 | 59 | "metric": route.metric, | 57 | "metric": route.metric, |
14 | @@ -99,6 +97,19 @@ class InterfaceConfiguration: | |||
15 | 99 | else: | 97 | else: |
16 | 100 | raise ValueError("Unknown interface type: %s" % self.type) | 98 | raise ValueError("Unknown interface type: %s" % self.type) |
17 | 101 | 99 | ||
18 | 100 | if version == 2: | ||
19 | 101 | routes = self._generate_route_operations( | ||
20 | 102 | self.matching_routes, version=version) | ||
21 | 103 | if len(routes) > 0: | ||
22 | 104 | self.config['routes'] = routes | ||
23 | 105 | |||
24 | 106 | def _generate_route_operations(self, matching_routes, version=1): | ||
25 | 107 | """Generate all route operations.""" | ||
26 | 108 | routes = [] | ||
27 | 109 | for route in sorted(matching_routes, key=attrgetter("id")): | ||
28 | 110 | routes.append(_generate_route_operation(route, version=version)) | ||
29 | 111 | return routes | ||
30 | 112 | |||
31 | 102 | def _generate_physical_operation(self, version=1): | 113 | def _generate_physical_operation(self, version=1): |
32 | 103 | """Generate physical interface operation for `interface` and place in | 114 | """Generate physical interface operation for `interface` and place in |
33 | 104 | `network_config`.""" | 115 | `network_config`.""" |
34 | @@ -241,7 +252,8 @@ class InterfaceConfiguration: | |||
35 | 241 | self._set_default_gateway( | 252 | self._set_default_gateway( |
36 | 242 | subnet, | 253 | subnet, |
37 | 243 | v1_subnet_operation if version == 1 else v2_config) | 254 | v1_subnet_operation if version == 1 else v2_config) |
39 | 244 | if subnet.dns_servers is not None: | 255 | if (subnet.dns_servers is not None and |
40 | 256 | len(subnet.dns_servers) > 0): | ||
41 | 245 | v1_subnet_operation["dns_nameservers"] = ( | 257 | v1_subnet_operation["dns_nameservers"] = ( |
42 | 246 | subnet.dns_servers) | 258 | subnet.dns_servers) |
43 | 247 | if "nameservers" not in v2_config: | 259 | if "nameservers" not in v2_config: |
44 | @@ -251,8 +263,16 @@ class InterfaceConfiguration: | |||
45 | 251 | v2_nameservers["addresses"] = [] | 263 | v2_nameservers["addresses"] = [] |
46 | 252 | v2_nameservers["addresses"].extend( | 264 | v2_nameservers["addresses"].extend( |
47 | 253 | [server for server in subnet.dns_servers]) | 265 | [server for server in subnet.dns_servers]) |
50 | 254 | self.matching_routes.update( | 266 | matching_subnet_routes = self._get_matching_routes(subnet) |
51 | 255 | self._get_matching_routes(subnet)) | 267 | if len(matching_subnet_routes) > 0 and version == 1: |
52 | 268 | # For the v1 YAML, the list of routes is rendered | ||
53 | 269 | # within the context of each subnet. | ||
54 | 270 | routes = self._generate_route_operations( | ||
55 | 271 | matching_subnet_routes, version=version) | ||
56 | 272 | v1_subnet_operation['routes'] = routes | ||
57 | 273 | # Keep track of routes which apply to the context of this | ||
58 | 274 | # interface for rendering the v2 YAML. | ||
59 | 275 | self.matching_routes.update(matching_subnet_routes) | ||
60 | 256 | if dhcp_type: | 276 | if dhcp_type: |
61 | 257 | v1_config.append( | 277 | v1_config.append( |
62 | 258 | {"type": dhcp_type} | 278 | {"type": dhcp_type} |
63 | @@ -479,7 +499,6 @@ class NodeNetworkConfiguration: | |||
64 | 479 | for name in sorted(get_dns_search_paths()) | 499 | for name in sorted(get_dns_search_paths()) |
65 | 480 | if name != self.node.domain.name | 500 | if name != self.node.domain.name |
66 | 481 | ] | 501 | ] |
67 | 482 | self._generate_route_operations(version=version) | ||
68 | 483 | self.v1_config.append({ | 502 | self.v1_config.append({ |
69 | 484 | "type": "nameserver", | 503 | "type": "nameserver", |
70 | 485 | "address": default_dns_servers, | 504 | "address": default_dns_servers, |
71 | @@ -517,16 +536,6 @@ class NodeNetworkConfiguration: | |||
72 | 517 | # v2_config.update({"nameservers": nameservers}) | 536 | # v2_config.update({"nameservers": nameservers}) |
73 | 518 | self.config = network_config | 537 | self.config = network_config |
74 | 519 | 538 | ||
75 | 520 | def _generate_route_operations(self, version=1): | ||
76 | 521 | """Generate all route operations.""" | ||
77 | 522 | routes = [] | ||
78 | 523 | for route in sorted(self.matching_routes, key=attrgetter("id")): | ||
79 | 524 | routes.append(_generate_route_operation(route, version=version)) | ||
80 | 525 | if version == 1: | ||
81 | 526 | self.v1_config.extend(routes) | ||
82 | 527 | elif version == 2 and len(routes) > 0: | ||
83 | 528 | self.v2_config["routes"] = routes | ||
84 | 529 | |||
85 | 530 | 539 | ||
86 | 531 | def compose_curtin_network_config(node, version=1): | 540 | def compose_curtin_network_config(node, version=1): |
87 | 532 | """Compose the network configuration for curtin.""" | 541 | """Compose the network configuration for curtin.""" |
88 | diff --git a/src/maasserver/tests/test_preseed_network.py b/src/maasserver/tests/test_preseed_network.py | |||
89 | index 8a509dc..cc66f9f 100644 | |||
90 | --- a/src/maasserver/tests/test_preseed_network.py | |||
91 | +++ b/src/maasserver/tests/test_preseed_network.py | |||
92 | @@ -5,7 +5,6 @@ | |||
93 | 5 | 5 | ||
94 | 6 | __all__ = [] | 6 | __all__ = [] |
95 | 7 | 7 | ||
96 | 8 | from operator import attrgetter | ||
97 | 9 | import random | 8 | import random |
98 | 10 | from textwrap import dedent | 9 | from textwrap import dedent |
99 | 11 | 10 | ||
100 | @@ -15,7 +14,6 @@ from maasserver.enum import ( | |||
101 | 15 | IPADDRESS_FAMILY, | 14 | IPADDRESS_FAMILY, |
102 | 16 | IPADDRESS_TYPE, | 15 | IPADDRESS_TYPE, |
103 | 17 | ) | 16 | ) |
104 | 18 | from maasserver.models.staticroute import StaticRoute | ||
105 | 19 | from maasserver.preseed_network import ( | 17 | from maasserver.preseed_network import ( |
106 | 20 | compose_curtin_network_config, | 18 | compose_curtin_network_config, |
107 | 21 | NodeNetworkConfiguration, | 19 | NodeNetworkConfiguration, |
108 | @@ -228,31 +226,6 @@ class AssertNetworkConfigMixin: | |||
109 | 228 | ret += " - type: dhcp6\n" | 226 | ret += " - type: dhcp6\n" |
110 | 229 | return ret | 227 | return ret |
111 | 230 | 228 | ||
112 | 231 | def collectRoutesConfig(self, node): | ||
113 | 232 | routes = set() | ||
114 | 233 | interfaces = node.interface_set.filter(enabled=True).order_by('name') | ||
115 | 234 | for iface in interfaces: | ||
116 | 235 | addresses = iface.ip_addresses.exclude( | ||
117 | 236 | alloc_type__in=[ | ||
118 | 237 | IPADDRESS_TYPE.DISCOVERED, | ||
119 | 238 | IPADDRESS_TYPE.DHCP, | ||
120 | 239 | ]).order_by('id') | ||
121 | 240 | for address in addresses: | ||
122 | 241 | subnet = address.subnet | ||
123 | 242 | if subnet is not None: | ||
124 | 243 | routes.update( | ||
125 | 244 | StaticRoute.objects.filter( | ||
126 | 245 | source=subnet).order_by('id')) | ||
127 | 246 | config = "" | ||
128 | 247 | for route in sorted(routes, key=attrgetter('id')): | ||
129 | 248 | config += self.ROUTE_CONFIG % { | ||
130 | 249 | 'id': route.id, | ||
131 | 250 | 'destination': route.destination.cidr, | ||
132 | 251 | 'gateway': route.gateway_ip, | ||
133 | 252 | 'metric': route.metric, | ||
134 | 253 | } | ||
135 | 254 | return config | ||
136 | 255 | |||
137 | 256 | def collectDNSConfig(self, node, ipv4=True, ipv6=True): | 229 | def collectDNSConfig(self, node, ipv4=True, ipv6=True): |
138 | 257 | config = "- type: nameserver\n address: %s\n search:\n" % ( | 230 | config = "- type: nameserver\n address: %s\n search:\n" % ( |
139 | 258 | repr(node.get_default_dns_servers(ipv4=ipv4, ipv6=ipv6))) | 231 | repr(node.get_default_dns_servers(ipv4=ipv4, ipv6=ipv6))) |
140 | @@ -466,29 +439,14 @@ class TestBridgeNetworkLayout(MAASServerTestCase, AssertNetworkConfigMixin): | |||
141 | 466 | self.assertNetworkConfig(net_config, config) | 439 | self.assertNetworkConfig(net_config, config) |
142 | 467 | 440 | ||
143 | 468 | 441 | ||
144 | 469 | class TestNetworkLayoutWithRoutes( | ||
145 | 470 | MAASServerTestCase, AssertNetworkConfigMixin): | ||
146 | 471 | |||
147 | 472 | def test__renders_expected_output(self): | ||
148 | 473 | node = factory.make_Node_with_Interface_on_Subnet( | ||
149 | 474 | interface_count=2) | ||
150 | 475 | for iface in node.interface_set.filter(enabled=True): | ||
151 | 476 | subnet = iface.vlan.subnet_set.first() | ||
152 | 477 | factory.make_StaticRoute(source=subnet) | ||
153 | 478 | factory.make_StaticIPAddress( | ||
154 | 479 | interface=iface, subnet=subnet) | ||
155 | 480 | net_config = self.collect_interface_config(node) | ||
156 | 481 | net_config += self.collectRoutesConfig(node) | ||
157 | 482 | net_config += self.collectDNSConfig(node) | ||
158 | 483 | config = compose_curtin_network_config(node) | ||
159 | 484 | self.assertNetworkConfig(net_config, config) | ||
160 | 485 | |||
161 | 486 | |||
162 | 487 | class TestNetplan(MAASServerTestCase): | 442 | class TestNetplan(MAASServerTestCase): |
163 | 488 | 443 | ||
164 | 489 | def _render_netplan_dict(self, node): | 444 | def _render_netplan_dict(self, node): |
165 | 490 | return NodeNetworkConfiguration(node, version=2).config | 445 | return NodeNetworkConfiguration(node, version=2).config |
166 | 491 | 446 | ||
167 | 447 | def _render_v1_dict(self, node): | ||
168 | 448 | return NodeNetworkConfiguration(node, version=1).config | ||
169 | 449 | |||
170 | 492 | def test__single_ethernet_interface(self): | 450 | def test__single_ethernet_interface(self): |
171 | 493 | node = factory.make_Node() | 451 | node = factory.make_Node() |
172 | 494 | factory.make_Interface( | 452 | factory.make_Interface( |
173 | @@ -824,3 +782,208 @@ class TestNetplan(MAASServerTestCase): | |||
174 | 824 | } | 782 | } |
175 | 825 | } | 783 | } |
176 | 826 | self.expectThat(netplan, Equals(expected_netplan)) | 784 | self.expectThat(netplan, Equals(expected_netplan)) |
177 | 785 | |||
178 | 786 | def test__multiple_ethernet_interfaces_with_routes(self): | ||
179 | 787 | node = factory.make_Node() | ||
180 | 788 | vlan = factory.make_VLAN() | ||
181 | 789 | subnet = factory.make_Subnet( | ||
182 | 790 | cidr='10.0.0.0/24', gateway_ip='10.0.0.1', | ||
183 | 791 | dns_servers=[]) | ||
184 | 792 | subnet2 = factory.make_Subnet( | ||
185 | 793 | cidr='10.0.1.0/24', gateway_ip='10.0.1.1', | ||
186 | 794 | dns_servers=[]) | ||
187 | 795 | dest_subnet = factory.make_Subnet(cidr='192.168.0.0/24') | ||
188 | 796 | eth0 = factory.make_Interface( | ||
189 | 797 | node=node, name='eth0', mac_address="00:01:02:03:04:05", vlan=vlan) | ||
190 | 798 | eth1 = factory.make_Interface( | ||
191 | 799 | node=node, name='eth1', mac_address="02:01:02:03:04:05") | ||
192 | 800 | node.boot_interface = eth0 | ||
193 | 801 | node.save() | ||
194 | 802 | factory.make_StaticIPAddress( | ||
195 | 803 | interface=eth0, subnet=subnet, ip='10.0.0.4', | ||
196 | 804 | alloc_type=IPADDRESS_TYPE.STICKY) | ||
197 | 805 | factory.make_StaticIPAddress( | ||
198 | 806 | interface=eth1, subnet=subnet2, ip='10.0.1.4', | ||
199 | 807 | alloc_type=IPADDRESS_TYPE.STICKY) | ||
200 | 808 | factory.make_StaticRoute( | ||
201 | 809 | source=subnet, gateway_ip='10.0.0.3', destination=dest_subnet, | ||
202 | 810 | metric=42) | ||
203 | 811 | factory.make_StaticRoute( | ||
204 | 812 | source=subnet2, gateway_ip='10.0.1.3', destination=dest_subnet, | ||
205 | 813 | metric=43) | ||
206 | 814 | # Make sure we know when and where the default DNS server will be used. | ||
207 | 815 | get_default_dns_servers_mock = self.patch( | ||
208 | 816 | node, 'get_default_dns_servers') | ||
209 | 817 | get_default_dns_servers_mock.return_value = ['127.0.0.2'] | ||
210 | 818 | netplan = self._render_netplan_dict(node) | ||
211 | 819 | expected_netplan = { | ||
212 | 820 | 'network': { | ||
213 | 821 | 'version': 2, | ||
214 | 822 | 'ethernets': { | ||
215 | 823 | 'eth0': { | ||
216 | 824 | 'gateway': '10.0.0.1', | ||
217 | 825 | 'match': {'macaddress': '00:01:02:03:04:05'}, | ||
218 | 826 | 'mtu': 1500, | ||
219 | 827 | 'set-name': 'eth0', | ||
220 | 828 | 'addresses': ['10.0.0.4/24'], | ||
221 | 829 | 'routes': [{ | ||
222 | 830 | 'to': '192.168.0.0/24', | ||
223 | 831 | 'via': '10.0.0.3', | ||
224 | 832 | 'metric': 42, | ||
225 | 833 | }], | ||
226 | 834 | }, | ||
227 | 835 | 'eth1': { | ||
228 | 836 | 'match': {'macaddress': '02:01:02:03:04:05'}, | ||
229 | 837 | 'mtu': 1500, | ||
230 | 838 | 'set-name': 'eth1', | ||
231 | 839 | 'addresses': ['10.0.1.4/24'], | ||
232 | 840 | 'routes': [{ | ||
233 | 841 | 'to': '192.168.0.0/24', | ||
234 | 842 | 'via': '10.0.1.3', | ||
235 | 843 | 'metric': 43, | ||
236 | 844 | }], | ||
237 | 845 | }, | ||
238 | 846 | }, | ||
239 | 847 | }, | ||
240 | 848 | } | ||
241 | 849 | self.expectThat(netplan, Equals(expected_netplan)) | ||
242 | 850 | v1 = self._render_v1_dict(node) | ||
243 | 851 | expected_v1 = { | ||
244 | 852 | 'network': { | ||
245 | 853 | 'version': 1, | ||
246 | 854 | 'config': [ | ||
247 | 855 | { | ||
248 | 856 | 'id': 'eth0', | ||
249 | 857 | 'mac_address': '00:01:02:03:04:05', | ||
250 | 858 | 'mtu': 1500, | ||
251 | 859 | 'name': 'eth0', | ||
252 | 860 | 'subnets': [{ | ||
253 | 861 | 'address': '10.0.0.4/24', | ||
254 | 862 | 'gateway': '10.0.0.1', | ||
255 | 863 | 'type': 'static', | ||
256 | 864 | 'routes': [{ | ||
257 | 865 | 'destination': '192.168.0.0/24', | ||
258 | 866 | 'gateway': '10.0.0.3', | ||
259 | 867 | 'metric': 42 | ||
260 | 868 | }], | ||
261 | 869 | }], | ||
262 | 870 | 'type': 'physical' | ||
263 | 871 | }, | ||
264 | 872 | { | ||
265 | 873 | 'id': 'eth1', | ||
266 | 874 | 'mac_address': '02:01:02:03:04:05', | ||
267 | 875 | 'mtu': 1500, | ||
268 | 876 | 'name': 'eth1', | ||
269 | 877 | 'subnets': [{ | ||
270 | 878 | 'address': '10.0.1.4/24', | ||
271 | 879 | 'type': 'static', | ||
272 | 880 | 'routes': [{ | ||
273 | 881 | 'destination': '192.168.0.0/24', | ||
274 | 882 | 'gateway': '10.0.1.3', | ||
275 | 883 | 'metric': 43, | ||
276 | 884 | }], | ||
277 | 885 | }], | ||
278 | 886 | 'type': 'physical' | ||
279 | 887 | }, | ||
280 | 888 | { | ||
281 | 889 | 'address': ['127.0.0.2'], | ||
282 | 890 | 'search': ['maas'], | ||
283 | 891 | 'type': 'nameserver' | ||
284 | 892 | } | ||
285 | 893 | ], | ||
286 | 894 | } | ||
287 | 895 | } | ||
288 | 896 | self.expectThat(v1, Equals(expected_v1)) | ||
289 | 897 | |||
290 | 898 | def test__multiple_ethernet_interfaces_with_dns(self): | ||
291 | 899 | node = factory.make_Node() | ||
292 | 900 | vlan = factory.make_VLAN() | ||
293 | 901 | subnet = factory.make_Subnet( | ||
294 | 902 | cidr='10.0.0.0/24', gateway_ip='10.0.0.1', | ||
295 | 903 | dns_servers=['10.0.0.2']) | ||
296 | 904 | subnet2 = factory.make_Subnet( | ||
297 | 905 | cidr='10.0.1.0/24', gateway_ip='10.0.1.1', | ||
298 | 906 | dns_servers=['10.0.1.2']) | ||
299 | 907 | eth0 = factory.make_Interface( | ||
300 | 908 | node=node, name='eth0', mac_address="00:01:02:03:04:05", vlan=vlan) | ||
301 | 909 | eth1 = factory.make_Interface( | ||
302 | 910 | node=node, name='eth1', mac_address="02:01:02:03:04:05") | ||
303 | 911 | node.boot_interface = eth0 | ||
304 | 912 | node.save() | ||
305 | 913 | factory.make_StaticIPAddress( | ||
306 | 914 | interface=eth0, subnet=subnet, ip='10.0.0.4', | ||
307 | 915 | alloc_type=IPADDRESS_TYPE.STICKY) | ||
308 | 916 | factory.make_StaticIPAddress( | ||
309 | 917 | interface=eth1, subnet=subnet2, ip='10.0.1.4', | ||
310 | 918 | alloc_type=IPADDRESS_TYPE.STICKY) | ||
311 | 919 | # Make sure we know when and where the default DNS server will be used. | ||
312 | 920 | get_default_dns_servers_mock = self.patch( | ||
313 | 921 | node, 'get_default_dns_servers') | ||
314 | 922 | get_default_dns_servers_mock.return_value = ['127.0.0.2'] | ||
315 | 923 | netplan = self._render_netplan_dict(node) | ||
316 | 924 | expected_netplan = { | ||
317 | 925 | 'network': { | ||
318 | 926 | 'version': 2, | ||
319 | 927 | 'ethernets': { | ||
320 | 928 | 'eth0': { | ||
321 | 929 | 'gateway': '10.0.0.1', | ||
322 | 930 | 'nameservers': { | ||
323 | 931 | 'addresses': ['10.0.0.2'] | ||
324 | 932 | }, | ||
325 | 933 | 'match': {'macaddress': '00:01:02:03:04:05'}, | ||
326 | 934 | 'mtu': 1500, | ||
327 | 935 | 'set-name': 'eth0', | ||
328 | 936 | 'addresses': ['10.0.0.4/24'], | ||
329 | 937 | }, | ||
330 | 938 | 'eth1': { | ||
331 | 939 | 'match': {'macaddress': '02:01:02:03:04:05'}, | ||
332 | 940 | 'nameservers': { | ||
333 | 941 | 'addresses': ['10.0.1.2'] | ||
334 | 942 | }, | ||
335 | 943 | 'mtu': 1500, | ||
336 | 944 | 'set-name': 'eth1', | ||
337 | 945 | 'addresses': ['10.0.1.4/24'], | ||
338 | 946 | }, | ||
339 | 947 | }, | ||
340 | 948 | }, | ||
341 | 949 | } | ||
342 | 950 | self.expectThat(netplan, Equals(expected_netplan)) | ||
343 | 951 | v1 = self._render_v1_dict(node) | ||
344 | 952 | expected_v1 = { | ||
345 | 953 | 'network': { | ||
346 | 954 | 'version': 1, | ||
347 | 955 | 'config': [ | ||
348 | 956 | { | ||
349 | 957 | 'id': 'eth0', | ||
350 | 958 | 'mac_address': '00:01:02:03:04:05', | ||
351 | 959 | 'mtu': 1500, | ||
352 | 960 | 'name': 'eth0', | ||
353 | 961 | 'subnets': [{ | ||
354 | 962 | 'address': '10.0.0.4/24', | ||
355 | 963 | 'dns_nameservers': ['10.0.0.2'], | ||
356 | 964 | 'gateway': '10.0.0.1', | ||
357 | 965 | 'type': 'static', | ||
358 | 966 | }], | ||
359 | 967 | 'type': 'physical' | ||
360 | 968 | }, | ||
361 | 969 | { | ||
362 | 970 | 'id': 'eth1', | ||
363 | 971 | 'mac_address': '02:01:02:03:04:05', | ||
364 | 972 | 'mtu': 1500, | ||
365 | 973 | 'name': 'eth1', | ||
366 | 974 | 'subnets': [{ | ||
367 | 975 | 'address': '10.0.1.4/24', | ||
368 | 976 | 'dns_nameservers': ['10.0.1.2'], | ||
369 | 977 | 'type': 'static', | ||
370 | 978 | }], | ||
371 | 979 | 'type': 'physical' | ||
372 | 980 | }, | ||
373 | 981 | { | ||
374 | 982 | 'address': ['127.0.0.2'], | ||
375 | 983 | 'search': ['maas'], | ||
376 | 984 | 'type': 'nameserver' | ||
377 | 985 | } | ||
378 | 986 | ], | ||
379 | 987 | } | ||
380 | 988 | } | ||
381 | 989 | self.expectThat(v1, Equals(expected_v1)) |
Self-approve backport.