Merge lp:~blake-rouse/maas/ip-addr-with-dhclient into lp:~maas-committers/maas/trunk
- ip-addr-with-dhclient
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Andres Rodriguez |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4711 |
Proposed branch: | lp:~blake-rouse/maas/ip-addr-with-dhclient |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
2330 lines (+953/-970) 16 files modified
src/maasserver/models/node.py (+46/-0) src/maasserver/models/tests/test_node.py (+24/-3) src/provisioningserver/networks.py (+15/-15) src/provisioningserver/plugin.py (+7/-7) src/provisioningserver/pserv_services/networks_monitoring_service.py (+9/-9) src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py (+17/-16) src/provisioningserver/rpc/clusterservice.py (+1/-1) src/provisioningserver/rpc/tests/test_clusterservice.py (+3/-3) src/provisioningserver/tests/test_networks.py (+17/-17) src/provisioningserver/tests/test_plugin.py (+7/-7) src/provisioningserver/utils/dhclient.py (+67/-0) src/provisioningserver/utils/network.py (+104/-195) src/provisioningserver/utils/ps.py (+67/-0) src/provisioningserver/utils/tests/test_dhclient.py (+113/-0) src/provisioningserver/utils/tests/test_network.py (+265/-697) src/provisioningserver/utils/tests/test_ps.py (+191/-0) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/ip-addr-with-dhclient |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Review via email: mp+287689@code.launchpad.net |
Commit message
Exclusivily use ip addr to get networks from the rack controller. Parse the running dhclient's to get the DHCP addresses assigned to interfaces. Update the discovered IP address for the rack controller interfaces that DHCP.
Description of the change
Blake Rouse (blake-rouse) wrote : | # |
Blake Rouse (blake-rouse) wrote : | # |
Here is a screenshot showing what my interfaces look like on my desktop. This is every working with network manager running, which is cool.
Mike Pontillo (mpontillo) wrote : | # |
Nice work. I looked through but didn't do a full review yet. A couple comments:
- Isn't showing virbr0 somewhat of a regression? That is, in 1.9 I think we skip any bridge interface that isn't bridged to at least one physical interface. (Would we create a fabric for lxcbr0 as well, or is there still code to prevent this somewhere?)
- It seems like a future branch should validate that rack controllers must have a static address on every managed subnet with a dynamic range, when the user goes to provide DHCP on a subnet.
Blake Rouse (blake-rouse) wrote : | # |
> Nice work. I looked through but didn't do a full review yet. A couple
> comments:
>
> - Isn't showing virbr0 somewhat of a regression? That is, in 1.9 I think we
> skip any bridge interface that isn't bridged to at least one physical
> interface. (Would we create a fabric for lxcbr0 as well, or is there still
> code to prevent this somewhere?)
No its not a regression its actually an improvement. The bridge are now shown correctly and each are added to its own fabric.
>
> - It seems like a future branch should validate that rack controllers must
> have a static address on every managed subnet with a dynamic range, when the
> user goes to provide DHCP on a subnet.
Actually the rack controller only needs to at least have one IP address in one of the subnets in the VLAN. Even if that subnet does not have a dynamic range that is fine, as DHCPD will do the correct thing with the pool defined in the other subnet on the VLAN. I would hope there is validation already in place to say a rack controller must at least have an IP address on a subnet for that VLAN.
Mike Pontillo (mpontillo) wrote : | # |
I agree it could be seen as an improvement, but in Seattle I thought we decided that we should not create a new fabric for every LXC bridge; since these can have random subnet assignments, this may lead to creating at least one useless fabric by default for every rack. So we came up with the compromise of only adding bridges that are bridged to the outside world. (This has the drawback that a fully-virtualized MAAS install must be manually configured, though.)
I think this will be less of an issue in MAAS 2.0 since we will have the ability to combine fabrics, but it's still not perfect because MAAS should not auto-create top-level objects like a Fabric without user confirmation.
Mike Pontillo (mpontillo) wrote : | # |
Nice work. Ship it!
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/ip-addr-with-dhclient into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Get:2 http://
Hit:3 http://
Hit:4 http://
Fetched 95.8 kB in 0s (231 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-1ubuntu1).
archdetect-deb is already the newest version (1.114ubuntu3).
authbind is already the newest version (2.1.1+nmu1).
bind9 is already the newest version (1:9.10.
bind9utils is already the newest version (1:9.10.
build-essential is already the newest version (12.1ubuntu2).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubun
dh-apport is already the newest version (2.20-0ubuntu3).
dh-systemd is already the newest version (1.28ubuntu2).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.
firefox is already the newest version (44.0.2+
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.0-1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu9).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-jquery-
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/ip-addr-with-dhclient into lp:maas failed. Below is the output from the failed tests.
Get:1 http://
Hit:2 http://
Hit:3 http://
Hit:4 http://
Fetched 95.8 kB in 0s (230 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-1ubuntu1).
archdetect-deb is already the newest version (1.114ubuntu3).
authbind is already the newest version (2.1.1+nmu1).
bind9 is already the newest version (1:9.10.
bind9utils is already the newest version (1:9.10.
build-essential is already the newest version (12.1ubuntu2).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubun
dh-apport is already the newest version (2.20-0ubuntu3).
dh-systemd is already the newest version (1.28ubuntu2).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.
firefox is already the newest version (44.0.2+
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.0-1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu9).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-jquery-
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/ip-addr-with-dhclient into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Hit:2 http://
Hit:3 http://
Hit:4 http://
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-1ubuntu1).
archdetect-deb is already the newest version (1.114ubuntu3).
authbind is already the newest version (2.1.1+nmu1).
bind9 is already the newest version (1:9.10.
bind9utils is already the newest version (1:9.10.
build-essential is already the newest version (12.1ubuntu2).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubun
dh-apport is already the newest version (2.20-0ubuntu3).
dh-systemd is already the newest version (1.28ubuntu2).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.
firefox is already the newest version (44.0.2+
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.0-1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu9).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-jquery-
libjs-yui3-full...
Preview Diff
1 | === modified file 'src/maasserver/models/node.py' |
2 | --- src/maasserver/models/node.py 2016-02-28 20:47:48 +0000 |
3 | +++ src/maasserver/models/node.py 2016-03-01 19:26:45 +0000 |
4 | @@ -3199,6 +3199,8 @@ |
5 | |
6 | def _update_links(self, interface, links, force_vlan=False): |
7 | """Update the links on `interface`.""" |
8 | + interface.ip_addresses.filter( |
9 | + alloc_type=IPADDRESS_TYPE.DISCOVERED).delete() |
10 | current_ip_addresses = list( |
11 | interface.ip_addresses.exclude( |
12 | alloc_type=IPADDRESS_TYPE.DISCOVERED)) |
13 | @@ -3214,6 +3216,46 @@ |
14 | interface.ip_addresses.add(dhcp_address) |
15 | else: |
16 | current_ip_addresses.remove(dhcp_address) |
17 | + if "address" in link: |
18 | + # DHCP IP address was discovered. Add it as a discovered |
19 | + # IP address. |
20 | + ip_network = IPNetwork(link["address"]) |
21 | + ip_addr = str(ip_network.ip) |
22 | + |
23 | + # Get or create the subnet for this link. If created if |
24 | + # will be added to the VLAN on the interface. |
25 | + subnet, _ = Subnet.objects.get_or_create( |
26 | + cidr=str(ip_network.cidr), defaults={ |
27 | + "name": str(ip_network.cidr), |
28 | + "vlan": interface.vlan, |
29 | + "space": Space.objects.get_default_space(), |
30 | + }) |
31 | + |
32 | + # Make sure that the subnet is on the same VLAN as the |
33 | + # interface. |
34 | + if force_vlan and subnet.vlan_id != interface.vlan_id: |
35 | + maaslog.error( |
36 | + "Unable to update IP address '%s' assigned to " |
37 | + "interface '%s' on rack controller '%s'. " |
38 | + "Subnet '%s' for IP address is not on " |
39 | + "VLAN '%s.%d'." % ( |
40 | + ip_addr, interface.name, self.hostname, |
41 | + subnet.name, subnet.vlan.fabric.name, |
42 | + subnet.vlan.vid)) |
43 | + continue |
44 | + |
45 | + # Create the DISCOVERED IP address. |
46 | + ip_address, created = ( |
47 | + StaticIPAddress.objects.get_or_create( |
48 | + ip=ip_addr, defaults={ |
49 | + "alloc_type": IPADDRESS_TYPE.DISCOVERED, |
50 | + "subnet": subnet, |
51 | + })) |
52 | + if not created: |
53 | + ip_address.alloc_type = IPADDRESS_TYPE.DISCOVERED |
54 | + ip_address.subnet = subnet |
55 | + ip_address.save() |
56 | + interface.ip_addresses.add(ip_address) |
57 | updated_ip_addresses.add(dhcp_address) |
58 | elif link["mode"] == "static": |
59 | ip_network = IPNetwork(link["address"]) |
60 | @@ -3259,6 +3301,10 @@ |
61 | "alloc_type": IPADDRESS_TYPE.STICKY, |
62 | "subnet": subnet, |
63 | })) |
64 | + if not created: |
65 | + ip_address.alloc_type = IPADDRESS_TYPE.STICKY |
66 | + ip_address.subnet = subnet |
67 | + ip_address.save() |
68 | else: |
69 | current_ip_addresses.remove(ip_address) |
70 | |
71 | |
72 | === modified file 'src/maasserver/models/tests/test_node.py' |
73 | --- src/maasserver/models/tests/test_node.py 2016-02-27 02:31:24 +0000 |
74 | +++ src/maasserver/models/tests/test_node.py 2016-03-01 19:26:45 +0000 |
75 | @@ -4394,6 +4394,8 @@ |
76 | |
77 | def test__new_physical_with_dhcp_link(self): |
78 | rack = self.create_empty_rack_controller() |
79 | + network = factory.make_ip4_or_6_network() |
80 | + ip = factory.pick_ip_in_network(network) |
81 | interfaces = { |
82 | "eth0": { |
83 | "type": "physical", |
84 | @@ -4401,6 +4403,7 @@ |
85 | "parents": [], |
86 | "links": [{ |
87 | "mode": "dhcp", |
88 | + "address": "%s/%d" % (str(ip), network.prefixlen), |
89 | }], |
90 | "enabled": True, |
91 | }, |
92 | @@ -4416,13 +4419,31 @@ |
93 | enabled=True, |
94 | vlan=default_vlan, |
95 | )) |
96 | - eth0_addresses = list(eth0.ip_addresses.all()) |
97 | - self.assertThat(eth0_addresses, HasLength(1)) |
98 | + dhcp_addresses = list(eth0.ip_addresses.filter( |
99 | + alloc_type=IPADDRESS_TYPE.DHCP)) |
100 | + self.assertThat(dhcp_addresses, HasLength(1)) |
101 | self.assertThat( |
102 | - eth0_addresses[0], MatchesStructure.byEquality( |
103 | + dhcp_addresses[0], MatchesStructure.byEquality( |
104 | alloc_type=IPADDRESS_TYPE.DHCP, |
105 | ip=None, |
106 | )) |
107 | + subnet = Subnet.objects.get(cidr=str(network.cidr)) |
108 | + self.assertThat( |
109 | + subnet, MatchesStructure.byEquality( |
110 | + name=str(network.cidr), |
111 | + cidr=str(network.cidr), |
112 | + vlan=default_vlan, |
113 | + space=Space.objects.get_default_space(), |
114 | + )) |
115 | + discovered_addresses = list(eth0.ip_addresses.filter( |
116 | + alloc_type=IPADDRESS_TYPE.DISCOVERED)) |
117 | + self.assertThat(discovered_addresses, HasLength(1)) |
118 | + self.assertThat( |
119 | + discovered_addresses[0], MatchesStructure.byEquality( |
120 | + alloc_type=IPADDRESS_TYPE.DISCOVERED, |
121 | + ip=ip, |
122 | + subnet=subnet, |
123 | + )) |
124 | |
125 | def test__new_physical_with_multiple_dhcp_link(self): |
126 | rack = self.create_empty_rack_controller() |
127 | |
128 | === renamed file 'src/provisioningserver/eni.py' => 'src/provisioningserver/networks.py' |
129 | --- src/provisioningserver/eni.py 2016-02-26 21:09:21 +0000 |
130 | +++ src/provisioningserver/networks.py 2016-03-01 19:26:45 +0000 |
131 | @@ -8,11 +8,11 @@ |
132 | "clear_current_interfaces_definition", |
133 | ] |
134 | |
135 | -from provisioningserver.utils.network import get_eni_interfaces_definition |
136 | +from provisioningserver.utils.network import get_all_interfaces_definition |
137 | |
138 | -# Holds the current /etc/network/interfaces definition that the rack |
139 | -# controller has processed. |
140 | -_current_eni_definition = None |
141 | +# Holds the current interfaces definition that the rack controller has |
142 | +# processed. |
143 | +_current_definition = None |
144 | |
145 | |
146 | def get_interfaces_definition(): |
147 | @@ -20,20 +20,20 @@ |
148 | boolean for it the definition has changed since the last time this method |
149 | was called. |
150 | """ |
151 | - global _current_eni_definition |
152 | - if _current_eni_definition is None: |
153 | - _current_eni_definition = get_eni_interfaces_definition() |
154 | - return _current_eni_definition, True |
155 | + global _current_definition |
156 | + if _current_definition is None: |
157 | + _current_definition = get_all_interfaces_definition() |
158 | + return _current_definition, True |
159 | else: |
160 | - new_definition = get_eni_interfaces_definition() |
161 | - if _current_eni_definition != new_definition: |
162 | - _current_eni_definition = new_definition |
163 | - return _current_eni_definition, True |
164 | + new_definition = get_all_interfaces_definition() |
165 | + if _current_definition != new_definition: |
166 | + _current_definition = new_definition |
167 | + return _current_definition, True |
168 | else: |
169 | - return _current_eni_definition, False |
170 | + return _current_definition, False |
171 | |
172 | |
173 | def clear_current_interfaces_definition(): |
174 | """Clear the current cached interfaces definition.""" |
175 | - global _current_eni_definition |
176 | - _current_eni_definition = None |
177 | + global _current_definition |
178 | + _current_definition = None |
179 | |
180 | === modified file 'src/provisioningserver/plugin.py' |
181 | --- src/provisioningserver/plugin.py 2016-02-26 16:19:41 +0000 |
182 | +++ src/provisioningserver/plugin.py 2016-03-01 19:26:45 +0000 |
183 | @@ -124,12 +124,12 @@ |
184 | rpc_service.setName("rpc") |
185 | return rpc_service |
186 | |
187 | - def _makeENIMonitoringService(self, rpc_service): |
188 | - from provisioningserver.pserv_services.eni_monitoring_service \ |
189 | - import ENIMonitoringService |
190 | - eni_monitor = ENIMonitoringService(rpc_service, reactor) |
191 | - eni_monitor.setName("eni_monitor") |
192 | - return eni_monitor |
193 | + def _makeNetworksMonitoringService(self, rpc_service): |
194 | + from provisioningserver.pserv_services.networks_monitoring_service \ |
195 | + import NetworksMonitoringService |
196 | + networks_monitor = NetworksMonitoringService(rpc_service, reactor) |
197 | + networks_monitor.setName("networks_monitor") |
198 | + return networks_monitor |
199 | |
200 | def _makeDHCPProbeService(self, rpc_service): |
201 | from provisioningserver.pserv_services.dhcp_probe_service \ |
202 | @@ -151,7 +151,7 @@ |
203 | rpc_service = self._makeRPCService() |
204 | yield rpc_service |
205 | # Other services that make up the MAAS Region Controller. |
206 | - yield self._makeENIMonitoringService(rpc_service) |
207 | + yield self._makeNetworksMonitoringService(rpc_service) |
208 | yield self._makeDHCPProbeService(rpc_service) |
209 | yield self._makeLeaseSocketService(rpc_service) |
210 | yield self._makeNodePowerMonitorService() |
211 | |
212 | === renamed file 'src/provisioningserver/pserv_services/eni_monitoring_service.py' => 'src/provisioningserver/pserv_services/networks_monitoring_service.py' |
213 | --- src/provisioningserver/pserv_services/eni_monitoring_service.py 2016-02-26 20:37:13 +0000 |
214 | +++ src/provisioningserver/pserv_services/networks_monitoring_service.py 2016-03-01 19:26:45 +0000 |
215 | @@ -1,20 +1,20 @@ |
216 | # Copyright 2016 Canonical Ltd. This software is licensed under the |
217 | # GNU Affero General Public License version 3 (see the file LICENSE). |
218 | |
219 | -"""/etc/network/interfaces monitoring service.""" |
220 | +"""Networks monitoring service.""" |
221 | |
222 | __all__ = [ |
223 | - "ENIMonitoringService", |
224 | + "NetworksMonitoringService", |
225 | ] |
226 | |
227 | |
228 | from datetime import timedelta |
229 | |
230 | -from provisioningserver.eni import ( |
231 | +from provisioningserver.logger.log import get_maas_logger |
232 | +from provisioningserver.networks import ( |
233 | clear_current_interfaces_definition, |
234 | get_interfaces_definition, |
235 | ) |
236 | -from provisioningserver.logger.log import get_maas_logger |
237 | from provisioningserver.rpc.exceptions import NoConnectionsAvailable |
238 | from provisioningserver.rpc.region import UpdateInterfaces |
239 | from provisioningserver.utils.twisted import ( |
240 | @@ -26,10 +26,10 @@ |
241 | from twisted.internet.threads import deferToThread |
242 | |
243 | |
244 | -maaslog = get_maas_logger("eni.monitor") |
245 | - |
246 | - |
247 | -class ENIMonitoringService(TimerService, object): |
248 | +maaslog = get_maas_logger("networks.monitor") |
249 | + |
250 | + |
251 | +class NetworksMonitoringService(TimerService, object): |
252 | """Service to monitor the interfaces definition on the rack controller. |
253 | |
254 | Parsed the "/etc/network/interfaces" and "ip addr show" to update the |
255 | @@ -42,7 +42,7 @@ |
256 | |
257 | def __init__(self, client_service, reactor): |
258 | # Call self.try_update_interfaces() every self.check_interval. |
259 | - super(ENIMonitoringService, self).__init__( |
260 | + super(NetworksMonitoringService, self).__init__( |
261 | self.check_interval, self.tryUpdateInterfaces) |
262 | self.clock = reactor |
263 | self.client_service = client_service |
264 | |
265 | === renamed file 'src/provisioningserver/pserv_services/tests/test_eni_monitoring_service.py' => 'src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py' |
266 | --- src/provisioningserver/pserv_services/tests/test_eni_monitoring_service.py 2016-02-26 20:37:13 +0000 |
267 | +++ src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py 2016-03-01 19:26:45 +0000 |
268 | @@ -1,7 +1,7 @@ |
269 | # Copyright 2016 Canonical Ltd. This software is licensed under the |
270 | # GNU Affero General Public License version 3 (see the file LICENSE). |
271 | |
272 | -"""Tests for /etc/network/interfaces monitor.""" |
273 | +"""Tests for networks monitor.""" |
274 | |
275 | __all__ = [] |
276 | |
277 | @@ -18,9 +18,9 @@ |
278 | Mock, |
279 | sentinel, |
280 | ) |
281 | -from provisioningserver.pserv_services import eni_monitoring_service |
282 | -from provisioningserver.pserv_services.eni_monitoring_service import ( |
283 | - ENIMonitoringService, |
284 | +from provisioningserver.pserv_services import networks_monitoring_service |
285 | +from provisioningserver.pserv_services.networks_monitoring_service import ( |
286 | + NetworksMonitoringService, |
287 | ) |
288 | from provisioningserver.rpc import ( |
289 | getRegionClient, |
290 | @@ -32,7 +32,7 @@ |
291 | from twisted.internet.task import Clock |
292 | |
293 | |
294 | -class TestENIMonitorService(PservTestCase): |
295 | +class TestNetworksMonitorService(PservTestCase): |
296 | |
297 | run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) |
298 | |
299 | @@ -43,7 +43,7 @@ |
300 | |
301 | def test_is_called_every_interval(self): |
302 | clock = Clock() |
303 | - service = ENIMonitoringService( |
304 | + service = NetworksMonitoringService( |
305 | sentinel.service, clock) |
306 | |
307 | # Avoid actually updating. |
308 | @@ -74,18 +74,19 @@ |
309 | def test_get_interfaces_definition_is_initiated_in_new_thread(self): |
310 | clock = Clock() |
311 | rpc_service = Mock() |
312 | - deferToThread = self.patch(eni_monitoring_service, 'deferToThread') |
313 | + deferToThread = self.patch( |
314 | + networks_monitoring_service, 'deferToThread') |
315 | deferToThread.return_value = defer.succeed(({}, False)) |
316 | - service = ENIMonitoringService(rpc_service, clock) |
317 | + service = NetworksMonitoringService(rpc_service, clock) |
318 | service.startService() |
319 | self.assertThat( |
320 | deferToThread, MockCalledOnceWith( |
321 | - eni_monitoring_service.get_interfaces_definition)) |
322 | + networks_monitoring_service.get_interfaces_definition)) |
323 | |
324 | def test_logs_errors(self): |
325 | clock = Clock() |
326 | - maaslog = self.patch(eni_monitoring_service, 'maaslog') |
327 | - service = ENIMonitoringService( |
328 | + maaslog = self.patch(networks_monitoring_service, 'maaslog') |
329 | + service = NetworksMonitoringService( |
330 | sentinel.service, clock) |
331 | error_message = factory.make_string() |
332 | self.patch(service, 'updateInterfaces').side_effect = Exception( |
333 | @@ -101,9 +102,9 @@ |
334 | def test_calls_clear_current_interfaces_when_fails_to_send_to_region(self): |
335 | clock = Clock() |
336 | clear_current_interfaces_definition = self.patch( |
337 | - eni_monitoring_service, 'clear_current_interfaces_definition') |
338 | + networks_monitoring_service, 'clear_current_interfaces_definition') |
339 | get_interfaces_definition = self.patch( |
340 | - eni_monitoring_service, 'get_interfaces_definition') |
341 | + networks_monitoring_service, 'get_interfaces_definition') |
342 | get_interfaces_definition.return_value = ({}, True) |
343 | |
344 | protocol, connecting = self.patch_rpc_methods() |
345 | @@ -113,7 +114,7 @@ |
346 | region.UpdateInterfaces.commandName] |
347 | rpc_service = Mock() |
348 | rpc_service.getClient.return_value = getRegionClient() |
349 | - service = ENIMonitoringService(rpc_service, clock) |
350 | + service = NetworksMonitoringService(rpc_service, clock) |
351 | yield service.startService() |
352 | yield service.stopService() |
353 | self.assertThat( |
354 | @@ -126,7 +127,7 @@ |
355 | self.addCleanup((yield connecting)) |
356 | |
357 | deferToThread = self.patch( |
358 | - eni_monitoring_service, 'deferToThread') |
359 | + networks_monitoring_service, 'deferToThread') |
360 | interfaces = { |
361 | "eth0": { |
362 | "type": "physical", |
363 | @@ -142,7 +143,7 @@ |
364 | rpc_service = Mock() |
365 | rpc_service.getClient.return_value = client |
366 | |
367 | - service = ENIMonitoringService( |
368 | + service = NetworksMonitoringService( |
369 | rpc_service, clock) |
370 | yield service.startService() |
371 | yield service.stopService() |
372 | |
373 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
374 | --- src/provisioningserver/rpc/clusterservice.py 2016-02-26 16:19:41 +0000 |
375 | +++ src/provisioningserver/rpc/clusterservice.py 2016-03-01 19:26:45 +0000 |
376 | @@ -34,8 +34,8 @@ |
377 | from provisioningserver.drivers.hardware.vmware import probe_vmware_and_enlist |
378 | from provisioningserver.drivers.power import power_drivers_by_name |
379 | from provisioningserver.drivers.power.mscm import probe_and_enlist_mscm |
380 | -from provisioningserver.eni import get_interfaces_definition |
381 | from provisioningserver.logger.log import get_maas_logger |
382 | +from provisioningserver.networks import get_interfaces_definition |
383 | from provisioningserver.power.change import maybe_change_power_state |
384 | from provisioningserver.power.poweraction import UnknownPowerType |
385 | from provisioningserver.power.query import get_power_state |
386 | |
387 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' |
388 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2016-02-26 02:46:38 +0000 |
389 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2016-03-01 19:26:45 +0000 |
390 | @@ -93,7 +93,7 @@ |
391 | from provisioningserver.security import set_shared_secret_on_filesystem |
392 | from provisioningserver.testing.config import ClusterConfigurationFixture |
393 | from provisioningserver.twisted.protocols import amp |
394 | -from provisioningserver.utils.network import get_eni_interfaces_definition |
395 | +from provisioningserver.utils.network import get_all_interfaces_definition |
396 | from testtools import ExpectedException |
397 | from testtools.deferredruntest import extract_result |
398 | from testtools.matchers import ( |
399 | @@ -1255,7 +1255,7 @@ |
400 | def test_registerRackWithRegion_end_to_end(self): |
401 | maas_url = factory.make_simple_http_url() |
402 | hostname = platform.node().split('.')[0] |
403 | - interfaces = get_eni_interfaces_definition() |
404 | + interfaces = get_all_interfaces_definition() |
405 | self.useFixture(ClusterConfigurationFixture( |
406 | maas_url=maas_url)) |
407 | fixture = self.useFixture(MockLiveClusterToRegionRPCFixture()) |
408 | @@ -1866,7 +1866,7 @@ |
409 | 'distro_series': distro_series, |
410 | 'swap_size': swap_size, |
411 | 'architecture': architecture, |
412 | - 'interfaces': get_eni_interfaces_definition(), |
413 | + 'interfaces': get_all_interfaces_definition(), |
414 | }, response) |
415 | |
416 | |
417 | |
418 | === renamed file 'src/provisioningserver/tests/test_eni.py' => 'src/provisioningserver/tests/test_networks.py' |
419 | --- src/provisioningserver/tests/test_eni.py 2016-02-26 20:37:13 +0000 |
420 | +++ src/provisioningserver/tests/test_networks.py 2016-03-01 19:26:45 +0000 |
421 | @@ -1,44 +1,44 @@ |
422 | # Copyright 2016 Canonical Ltd. This software is licensed under the |
423 | # GNU Affero General Public License version 3 (see the file LICENSE). |
424 | |
425 | -"""Test eni module.""" |
426 | +"""Test networks module.""" |
427 | |
428 | __all__ = [ |
429 | ] |
430 | |
431 | from maastesting.testcase import MAASTestCase |
432 | from mock import sentinel |
433 | -from provisioningserver import eni |
434 | +from provisioningserver import networks |
435 | |
436 | |
437 | class TestGetInterfacesDefinition(MAASTestCase): |
438 | """Tests for `get_interfaces_definition`.""" |
439 | |
440 | def test__sets_global_when_None(self): |
441 | - eni._current_eni_definition = None |
442 | - patched_getter = self.patch(eni, "get_eni_interfaces_definition") |
443 | + networks._current_definition = None |
444 | + patched_getter = self.patch(networks, "get_all_interfaces_definition") |
445 | patched_getter.return_value = sentinel.definition |
446 | - expected_value, updated = eni.get_interfaces_definition() |
447 | + expected_value, updated = networks.get_interfaces_definition() |
448 | self.assertEquals(sentinel.definition, expected_value) |
449 | - self.assertEquals(sentinel.definition, eni._current_eni_definition) |
450 | + self.assertEquals(sentinel.definition, networks._current_definition) |
451 | self.assertTrue(updated) |
452 | |
453 | def test__sets_global_when_different(self): |
454 | - eni._current_eni_definition = sentinel.old_definition |
455 | - patched_getter = self.patch(eni, "get_eni_interfaces_definition") |
456 | + networks._current_definition = sentinel.old_definition |
457 | + patched_getter = self.patch(networks, "get_all_interfaces_definition") |
458 | patched_getter.return_value = sentinel.definition |
459 | - expected_value, updated = eni.get_interfaces_definition() |
460 | + expected_value, updated = networks.get_interfaces_definition() |
461 | self.assertEquals(sentinel.definition, expected_value) |
462 | - self.assertEquals(sentinel.definition, eni._current_eni_definition) |
463 | + self.assertEquals(sentinel.definition, networks._current_definition) |
464 | self.assertTrue(updated) |
465 | |
466 | def test__returns_not_changed_if_same(self): |
467 | - eni._current_eni_definition = sentinel.definition |
468 | - patched_getter = self.patch(eni, "get_eni_interfaces_definition") |
469 | + networks._current_definition = sentinel.definition |
470 | + patched_getter = self.patch(networks, "get_all_interfaces_definition") |
471 | patched_getter.return_value = sentinel.definition |
472 | - expected_value, updated = eni.get_interfaces_definition() |
473 | + expected_value, updated = networks.get_interfaces_definition() |
474 | self.assertEquals(sentinel.definition, expected_value) |
475 | - self.assertEquals(sentinel.definition, eni._current_eni_definition) |
476 | + self.assertEquals(sentinel.definition, networks._current_definition) |
477 | self.assertFalse(updated) |
478 | |
479 | |
480 | @@ -46,6 +46,6 @@ |
481 | """Tests for `clear_current_interfaces_definition`.""" |
482 | |
483 | def test__sets_global_to_None(self): |
484 | - eni._current_eni_definition = sentinel.old_definition |
485 | - eni.clear_current_interfaces_definition() |
486 | - self.assertIsNone(eni._current_eni_definition) |
487 | + networks._current_definition = sentinel.old_definition |
488 | + networks.clear_current_interfaces_definition() |
489 | + self.assertIsNone(networks._current_definition) |
490 | |
491 | === modified file 'src/provisioningserver/tests/test_plugin.py' |
492 | --- src/provisioningserver/tests/test_plugin.py 2016-02-26 16:19:41 +0000 |
493 | +++ src/provisioningserver/tests/test_plugin.py 2016-03-01 19:26:45 +0000 |
494 | @@ -21,9 +21,6 @@ |
495 | from provisioningserver.pserv_services.dhcp_probe_service import ( |
496 | DHCPProbeService, |
497 | ) |
498 | -from provisioningserver.pserv_services.eni_monitoring_service import ( |
499 | - ENIMonitoringService, |
500 | -) |
501 | from provisioningserver.pserv_services.image import BootImageEndpointService |
502 | from provisioningserver.pserv_services.image_download_service import ( |
503 | ImageDownloadService, |
504 | @@ -31,6 +28,9 @@ |
505 | from provisioningserver.pserv_services.lease_socket_service import ( |
506 | LeaseSocketService, |
507 | ) |
508 | +from provisioningserver.pserv_services.networks_monitoring_service import ( |
509 | + NetworksMonitoringService, |
510 | +) |
511 | from provisioningserver.pserv_services.node_power_monitor_service import ( |
512 | NodePowerMonitorService, |
513 | ) |
514 | @@ -94,7 +94,7 @@ |
515 | service = service_maker.makeService(options) |
516 | self.assertIsInstance(service, MultiService) |
517 | expected_services = [ |
518 | - "dhcp_probe", "eni_monitor", "image_download", |
519 | + "dhcp_probe", "networks_monitor", "image_download", |
520 | "lease_socket_service", "node_monitor", "rpc", "tftp", |
521 | "image_service", "service_monitor", |
522 | ] |
523 | @@ -127,12 +127,12 @@ |
524 | node_monitor = service.getServiceNamed("node_monitor") |
525 | self.assertIsInstance(node_monitor, NodePowerMonitorService) |
526 | |
527 | - def test_eni_monitor_service(self): |
528 | + def test_networks_monitor_service(self): |
529 | options = Options() |
530 | service_maker = ProvisioningServiceMaker("Spike", "Milligan") |
531 | service = service_maker.makeService(options) |
532 | - eni_monitor = service.getServiceNamed("eni_monitor") |
533 | - self.assertIsInstance(eni_monitor, ENIMonitoringService) |
534 | + networks_monitor = service.getServiceNamed("networks_monitor") |
535 | + self.assertIsInstance(networks_monitor, NetworksMonitoringService) |
536 | |
537 | def test_dhcp_probe_service(self): |
538 | options = Options() |
539 | |
540 | === added file 'src/provisioningserver/utils/dhclient.py' |
541 | --- src/provisioningserver/utils/dhclient.py 1970-01-01 00:00:00 +0000 |
542 | +++ src/provisioningserver/utils/dhclient.py 2016-03-01 19:26:45 +0000 |
543 | @@ -0,0 +1,67 @@ |
544 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
545 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
546 | + |
547 | +"""Helpers for inspecting dhclient.""" |
548 | + |
549 | +__all__ = [ |
550 | + 'get_dhclient_info', |
551 | + ] |
552 | + |
553 | +import os |
554 | +import re |
555 | + |
556 | +from provisioningserver.utils.fs import read_text_file |
557 | +from provisioningserver.utils.ps import get_running_pids_with_command |
558 | + |
559 | + |
560 | +re_entry = re.compile( |
561 | + r''' |
562 | + ^\s* # Ignore leading whitespace on each line. |
563 | + lease # Look only lease stanzas. |
564 | + \s+{ # Open bracket. |
565 | + ([^}]+) # Capture the contents of lease. |
566 | + } # Close bracket. |
567 | + ''', |
568 | + re.MULTILINE | re.DOTALL | re.VERBOSE) |
569 | + |
570 | + |
571 | +def get_lastest_fixed_address(lease_path): |
572 | + """Return the lastest fixed address assigned in `lease_path`.""" |
573 | + try: |
574 | + lease_contents = read_text_file(lease_path) |
575 | + except FileNotFoundError: |
576 | + return None |
577 | + |
578 | + matches = re_entry.findall(lease_contents) |
579 | + if len(matches) > 0: |
580 | + # Get the IP address assigned to the interface. |
581 | + last_lease = matches[-1] |
582 | + for line in last_lease.splitlines(): |
583 | + line = line.strip() |
584 | + if len(line) > 0: |
585 | + statement, value = line.split(" ", 1) |
586 | + if statement in ["fixed-address", "fixed-address6"]: |
587 | + return value.split(";", 1)[0].strip() |
588 | + # No matches or unable to identify fixed-address{6} in the last match. |
589 | + return None |
590 | + |
591 | + |
592 | +def get_dhclient_info(proc_path="/proc"): |
593 | + """Return dictionary mapping interfaces to assigned address from |
594 | + dhclient. |
595 | + """ |
596 | + dhclient_pids = get_running_pids_with_command( |
597 | + "dhclient", proc_path=proc_path) |
598 | + dhclient_info = {} |
599 | + for pid in dhclient_pids: |
600 | + cmdline = read_text_file( |
601 | + os.path.join(proc_path, str(pid), "cmdline")).split("\x00") |
602 | + if "-lf" in cmdline: |
603 | + idx_lf = cmdline.index("-lf") |
604 | + lease_path = cmdline[idx_lf + 1] # After '-lf' option. |
605 | + interface_name = cmdline[-2] # Last argument. |
606 | + ip_address = get_lastest_fixed_address(lease_path) |
607 | + if (ip_address is not None and |
608 | + len(ip_address) > 0 and not ip_address.isspace()): |
609 | + dhclient_info[interface_name] = ip_address |
610 | + return dhclient_info |
611 | |
612 | === modified file 'src/provisioningserver/utils/network.py' |
613 | --- src/provisioningserver/utils/network.py 2016-02-25 22:05:39 +0000 |
614 | +++ src/provisioningserver/utils/network.py 2016-03-01 19:26:45 +0000 |
615 | @@ -16,6 +16,7 @@ |
616 | ] |
617 | |
618 | |
619 | +from operator import attrgetter |
620 | from socket import ( |
621 | AF_INET, |
622 | AF_INET6, |
623 | @@ -25,17 +26,14 @@ |
624 | getaddrinfo, |
625 | ) |
626 | |
627 | -from curtin.net import parse_deb_config |
628 | from netaddr import ( |
629 | IPAddress, |
630 | IPNetwork, |
631 | IPRange, |
632 | ) |
633 | import netifaces |
634 | -from provisioningserver.utils.ipaddr import ( |
635 | - get_interface_type, |
636 | - parse_ip_addr, |
637 | -) |
638 | +from provisioningserver.utils.dhclient import get_dhclient_info |
639 | +from provisioningserver.utils.ipaddr import get_ip_addr |
640 | from provisioningserver.utils.shell import call_and_check |
641 | |
642 | # Address families in /etc/network/interfaces that MAAS chooses to parse. All |
643 | @@ -541,205 +539,116 @@ |
644 | return int(value_string, base) |
645 | |
646 | |
647 | -def extract_network_from_config(config): |
648 | - """Extract `IPNetwork` from `config`. |
649 | - |
650 | - :param config: Configuration entry from an interface definition that was |
651 | - parsed from /etc/network/interfaces. |
652 | - """ |
653 | - ip_network = IPNetwork(config["address"]) |
654 | - if ip_network.version == 4 and ip_network.prefixlen == 32: |
655 | - # Missing the prefixlen in the address. It is not required for aliases |
656 | - # but it normally will be set. |
657 | - netmask = config.get("netmask", None) |
658 | - if netmask is not None: |
659 | - # The netmask can either be a dotted quad or a prefix length. |
660 | - if "." in netmask: |
661 | - ip_network.prefixlen = IPAddress(netmask).netmask_bits() |
662 | - else: |
663 | - ip_network.prefixlen = int(netmask) |
664 | - elif ip_network.version == 6 and ip_network.prefixlen == 128: |
665 | - # Missing the prefixlen in the address. It is not required for aliases |
666 | - # but it normally will be set. |
667 | - netmask = config.get("netmask", None) |
668 | - if netmask is not None: |
669 | - # In IPv6 the netmask is only the mask. |
670 | - ip_network.prefixlen = int(netmask) |
671 | - return ip_network |
672 | - |
673 | - |
674 | -def get_link_from_config(config): |
675 | - """Return the link definition from `config`. |
676 | - |
677 | - :param config: Configuration entry from an interface definition that was |
678 | - parsed from /etc/network/interfaces. |
679 | - """ |
680 | - if config["method"] == "dhcp": |
681 | - return { |
682 | - "mode": "dhcp", |
683 | - } |
684 | - elif config["method"] == "static": |
685 | - link = { |
686 | - "mode": "static", |
687 | - "address": str(extract_network_from_config(config)), |
688 | - } |
689 | - if "gateway" in config: |
690 | - link["gateway"] = str(IPAddress(config["gateway"])) |
691 | - return link |
692 | - else: |
693 | - return None |
694 | - |
695 | - |
696 | -def get_primary_link_from_links(links): |
697 | - """Return the primary link out of `links`.""" |
698 | +def fix_link_addresses(links): |
699 | + """Fix the addresses defined in `links`. |
700 | + |
701 | + Some address will have a prefixlen of 32 or 128 depending if IPv4 or IPv6. |
702 | + Fix those address to fall within a subnet that is already defined in |
703 | + another link. The addresses that get fixed will be placed into the smallest |
704 | + subnet defined in `links`. |
705 | + """ |
706 | + subnets_v4 = [] |
707 | + links_v4 = [] |
708 | + subnets_v6 = [] |
709 | + links_v6 = [] |
710 | + |
711 | + # Loop through and build a list of subnets where the prefixlen is not |
712 | + # 32 or 128 for IPv4 and IPv6 respectively. |
713 | for link in links: |
714 | - if link["mode"] == "static": |
715 | - ip_addr = IPNetwork(link["address"]) |
716 | - if ip_addr.version == 4 and ip_addr.prefixlen != 32: |
717 | - return link |
718 | - elif ip_addr.version == 6 and ip_addr.prefixlen != 128: |
719 | - return link |
720 | - return None |
721 | - |
722 | - |
723 | -def get_bond_master(config): |
724 | - """Return the name of the bond master for this interface from `config`.""" |
725 | - if "bond" in config and "master" in config["bond"]: |
726 | - return config["bond"]["master"] |
727 | - else: |
728 | - return None |
729 | - |
730 | - |
731 | -def get_type_and_parent_from_name_and_config(name, config): |
732 | - """Return the type of interface and parent interface from `config`. |
733 | - |
734 | - :param name: Name of the entry in the configuration from |
735 | - /etc/network/interfaces. |
736 | - :param config: Configuration entry from an interface definition that was |
737 | - parsed from /etc/network/interfaces. |
738 | - """ |
739 | - if ":" in name: |
740 | - return "alias", name.split(":", 1)[0], None |
741 | - elif "." in name: |
742 | - parent = name.split(".", 1)[0] |
743 | - if "vlan-raw-device" in config: |
744 | - parent = config["vlan-raw-device"] |
745 | - return "vlan", parent, get_bond_master(config) |
746 | - else: |
747 | - if "vlan-raw-device" in config: |
748 | - return "vlan", config["vlan-raw-device"], get_bond_master(config) |
749 | - elif ("bond" in config and |
750 | - "mode" in config["bond"] and |
751 | - "master" not in config["bond"]): |
752 | - # Since bond-mode is set and bond-master is missing then this |
753 | - # is the bond master. |
754 | - return "bond", None, None |
755 | - else: |
756 | - return "physical", None, get_bond_master(config) |
757 | - |
758 | - |
759 | -def get_eni_interfaces_definition( |
760 | - eni_path="/etc/network/interfaces", include_other_interfaces=True): |
761 | - """Return interfaces definition from `eni_path`. |
762 | + ip_addr = IPNetwork(link["address"]) |
763 | + if ip_addr.version == 4: |
764 | + if ip_addr.prefixlen == 32: |
765 | + links_v4.append(link) |
766 | + else: |
767 | + subnets_v4.append(ip_addr.cidr) |
768 | + elif ip_addr.version == 6: |
769 | + if ip_addr.prefixlen == 128: |
770 | + links_v6.append(link) |
771 | + else: |
772 | + subnets_v6.append(ip_addr.cidr) |
773 | + |
774 | + # Sort the subnets so the smallest prefixlen is first. |
775 | + subnets_v4 = sorted(subnets_v4, key=attrgetter("prefixlen"), reverse=True) |
776 | + subnets_v6 = sorted(subnets_v6, key=attrgetter("prefixlen"), reverse=True) |
777 | + |
778 | + # Fix all addresses that have prefixlen of 32 or 128 that fit in inside |
779 | + # one of the already defined subnets. |
780 | + for link in links_v4: |
781 | + ip_addr = IPNetwork(link["address"]) |
782 | + for subnet in subnets_v4: |
783 | + if ip_addr.ip in subnet: |
784 | + ip_addr.prefixlen = subnet.prefixlen |
785 | + link["address"] = str(ip_addr) |
786 | + break |
787 | + for link in links_v6: |
788 | + ip_addr = IPNetwork(link["address"]) |
789 | + for subnet in subnets_v6: |
790 | + if ip_addr.ip in subnet: |
791 | + ip_addr.prefixlen = subnet.prefixlen |
792 | + link["address"] = str(ip_addr) |
793 | + break |
794 | + |
795 | + |
796 | +def get_all_interfaces_definition(): |
797 | + """Return interfaces definition by parsing "ip addr" and the running |
798 | + "dhclient" processes on the machine. |
799 | |
800 | The interfaces definition is defined as a contract between the region and |
801 | the rack controller. The region controller processes this resulting |
802 | dictionary to update the interfaces model for the rack controller. |
803 | - `eni_path` should be `/etc/network/interfaces`. |
804 | """ |
805 | - parsed_config = parse_deb_config(eni_path) |
806 | - ipaddr_info = call_and_check(["/sbin/ip", "addr", "show"]) |
807 | - ipaddr_info = parse_ip_addr(ipaddr_info) |
808 | - |
809 | - # Filter dictionary for address families and interface methods that |
810 | - # MAAS only cares about and interfaces that show up in "ip addr show". |
811 | - parsed_config = { |
812 | - name: config |
813 | - for name, config in parsed_config.items() |
814 | - if ":" in name or ( |
815 | - name in ipaddr_info and |
816 | - config["family"] in ENI_PARSED_ADDRESS_FAMILIES and |
817 | - config["method"] in ENI_PARSED_METHODS) |
818 | + interfaces = {} |
819 | + dhclient_info = get_dhclient_info() |
820 | + ipaddr_info = { |
821 | + name: ipaddr |
822 | + for name, ipaddr in get_ip_addr().items() |
823 | + if (ipaddr["type"] not in ["ethernet", "loopback", "ipip"] and |
824 | + not ipaddr["type"].startswith("unknown-")) |
825 | } |
826 | + for name, ipaddr in ipaddr_info.items(): |
827 | + iface_type = "physical" |
828 | + parents = [] |
829 | + mac_address = None |
830 | + vid = None |
831 | + if ipaddr["type"] == "ethernet.bond": |
832 | + iface_type = "bond" |
833 | + mac_address = ipaddr["mac"] |
834 | + for bond_nic in ipaddr["bonded_interfaces"]: |
835 | + if bond_nic in interfaces or bond_nic in ipaddr_info: |
836 | + parents.append(bond_nic) |
837 | + elif ipaddr["type"] == "ethernet.vlan": |
838 | + iface_type = "vlan" |
839 | + parents.append(name.split(".", 1)[0]) |
840 | + vid = ipaddr["vid"] |
841 | + else: |
842 | + mac_address = ipaddr["mac"] |
843 | |
844 | - # Create all empty interface definitions from /etc/network/interfaces. |
845 | - interfaces = {} |
846 | - for name, _ in parsed_config.items(): |
847 | - # Aliases are skipped and performed in the following loop. |
848 | - if ":" in name: |
849 | - continue |
850 | - interfaces[name] = { |
851 | + # Create the interface definition will links for both IPv4 and IPv6. |
852 | + interface = { |
853 | + "type": iface_type, |
854 | "links": [], |
855 | - "enabled": True, |
856 | - "parents": [], |
857 | + "enabled": True if 'UP' in ipaddr['flags'] else False, |
858 | + "parents": parents, |
859 | + "source": "ipaddr", |
860 | } |
861 | - |
862 | - # Update the values in each interface dictionary. |
863 | - for name, config in parsed_config.items(): |
864 | - iface_type, iface_parent, bond_master = ( |
865 | - get_type_and_parent_from_name_and_config(name, config)) |
866 | - if iface_type != "alias": |
867 | - # Update the interface definition. |
868 | - interfaces[name]["type"] = iface_type |
869 | - if iface_type == "vlan": |
870 | - interfaces[name]["parents"].append(iface_parent) |
871 | - interfaces[name]["vid"] = int(name.split(".", 1)[1]) |
872 | - if iface_type in ["bond", "physical"]: |
873 | - # Only set the MAC address for physical and bond interfaces. |
874 | - # Other interface types it is infered from its relations. |
875 | - interfaces[name]["mac_address"] = ipaddr_info[name]["mac"] |
876 | - if "mtu" in config: |
877 | - interfaces[name]["mtu"] = int(config["mtu"]) |
878 | - |
879 | - # Set interface as the parent of the bond. |
880 | - if bond_master is not None and bond_master in interfaces: |
881 | - interfaces[bond_master]["parents"].append(name) |
882 | - |
883 | - # Add the link for this interface. |
884 | - link = get_link_from_config(config) |
885 | - if link is not None: |
886 | - interfaces[name]["links"].append(link) |
887 | - else: |
888 | - # Add the link to the parent interface as this is an alias. |
889 | - link = get_link_from_config(config) |
890 | - if link is not None: |
891 | - interfaces[iface_parent]["links"].append(link) |
892 | - |
893 | - # Fix static mode links on interfaces so the prefixlen is matching. This |
894 | - # occurs because on aliases the prefixlen can be a /32 when IPv4 or |
895 | - # /128 when IPv6. |
896 | - for config in interfaces.values(): |
897 | - if len(config["links"]) > 1: |
898 | - # More than one link, fix prefixlen. |
899 | - primary_link = get_primary_link_from_links(config["links"]) |
900 | - if primary_link is not None: |
901 | - ip_addr = IPNetwork(primary_link["address"]) |
902 | - for link in config["links"]: |
903 | - if link != primary_link and link["mode"] == "static": |
904 | - link_addr = IPNetwork(link["address"]) |
905 | - link_addr.prefixlen = ip_addr.prefixlen |
906 | - link["address"] = str(link_addr) |
907 | - |
908 | - # Add the interfaces that are wireless or physical interfaces that are |
909 | - # not represented in the interfaces dictionary. These interfaces are not |
910 | - # enabled since they do not appear in /etc/network/interfaces. |
911 | - if include_other_interfaces: |
912 | - for name, ipaddr in ipaddr_info.items(): |
913 | - # Skip interfaces we already know about or those that are aliases. |
914 | - if name in interfaces or ":" in name: |
915 | - continue |
916 | - |
917 | - # Only process physical or wireless interfaces. |
918 | - iface_type = get_interface_type(name) |
919 | - if iface_type not in ["ethernet.physical", "ethernet.wireless"]: |
920 | - continue |
921 | - |
922 | - interfaces[name] = { |
923 | - "type": "physical", |
924 | - "links": [], |
925 | - "enabled": False, |
926 | - "mac_address": ipaddr["mac"], |
927 | - "parents": [], |
928 | - } |
929 | + if mac_address is not None: |
930 | + interface["mac_address"] = mac_address |
931 | + if vid is not None: |
932 | + interface["vid"] = vid |
933 | + # Add the static and dynamic IP addresses assigned to the interface. |
934 | + dhcp_address = dhclient_info.get(name, None) |
935 | + for address in ipaddr.get("inet", []) + ipaddr.get("inet6", []): |
936 | + if str(IPNetwork(address).ip) == dhcp_address: |
937 | + interface["links"].append({ |
938 | + "mode": "dhcp", |
939 | + "address": address, |
940 | + }) |
941 | + else: |
942 | + interface["links"].append({ |
943 | + "mode": "static", |
944 | + "address": address, |
945 | + }) |
946 | + fix_link_addresses(interface["links"]) |
947 | + interfaces[name] = interface |
948 | |
949 | return interfaces |
950 | |
951 | === added file 'src/provisioningserver/utils/ps.py' |
952 | --- src/provisioningserver/utils/ps.py 1970-01-01 00:00:00 +0000 |
953 | +++ src/provisioningserver/utils/ps.py 2016-03-01 19:26:45 +0000 |
954 | @@ -0,0 +1,67 @@ |
955 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
956 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
957 | + |
958 | +"""Helpers for inspecting processes.""" |
959 | + |
960 | +__all__ = [ |
961 | + 'running_in_container', |
962 | + ] |
963 | + |
964 | +import os |
965 | + |
966 | +from provisioningserver.utils.fs import read_text_file |
967 | + |
968 | + |
969 | +def is_pid_in_container(pid, proc_path="/proc"): |
970 | + """Return True if the `pid` is running in a container. |
971 | + |
972 | + This should only be called when not running in a container itself, because |
973 | + if this process is running in a container than the `pid` process is |
974 | + sure to be running in the container as well. |
975 | + """ |
976 | + cgroup_path = os.path.join(proc_path, str(pid), "cgroup") |
977 | + cgroup_info = read_text_file(cgroup_path) |
978 | + for line in cgroup_info.splitlines(): |
979 | + id_num, subsytem, hierarchy = line.split(":", 2) |
980 | + if int(id_num) == 1: |
981 | + if "lxc" in hierarchy or "docker" in hierarchy: |
982 | + return True |
983 | + return False |
984 | + |
985 | + |
986 | +def running_in_container(proc_path="/proc"): |
987 | + """Return True if running in an LXC or Docker container.""" |
988 | + return is_pid_in_container(1, proc_path=proc_path) |
989 | + |
990 | + |
991 | +def get_running_pids_with_command( |
992 | + command, exclude_container_processes=True, proc_path="/proc"): |
993 | + """Return list of pids that are running the following command. |
994 | + |
995 | + :param command: The command to search for. This is only the command as |
996 | + `cat` not the full command line. |
997 | + :param exclude_container_processes: Excludes processes that are running |
998 | + in an LXC container on the host. |
999 | + """ |
1000 | + running_pids = [pid for pid in os.listdir(proc_path) if pid.isdigit()] |
1001 | + pids = [] |
1002 | + for pid in running_pids: |
1003 | + try: |
1004 | + pid_command = read_text_file( |
1005 | + os.path.join(proc_path, pid, "comm")).strip() |
1006 | + except FileNotFoundError: |
1007 | + # Process was closed while running. |
1008 | + pass |
1009 | + else: |
1010 | + if pid_command == command: |
1011 | + pids.append(int(pid)) |
1012 | + |
1013 | + if (exclude_container_processes and |
1014 | + not running_in_container(proc_path=proc_path)): |
1015 | + return [ |
1016 | + pid |
1017 | + for pid in pids |
1018 | + if not is_pid_in_container(pid, proc_path=proc_path) |
1019 | + ] |
1020 | + else: |
1021 | + return pids |
1022 | |
1023 | === added file 'src/provisioningserver/utils/tests/test_dhclient.py' |
1024 | --- src/provisioningserver/utils/tests/test_dhclient.py 1970-01-01 00:00:00 +0000 |
1025 | +++ src/provisioningserver/utils/tests/test_dhclient.py 2016-03-01 19:26:45 +0000 |
1026 | @@ -0,0 +1,113 @@ |
1027 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
1028 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1029 | + |
1030 | +"""Tests for dhclient helpers.""" |
1031 | + |
1032 | +__all__ = [] |
1033 | + |
1034 | +import os |
1035 | +import random |
1036 | +from textwrap import dedent |
1037 | + |
1038 | +from maastesting.factory import factory |
1039 | +from maastesting.testcase import MAASTestCase |
1040 | +from provisioningserver.utils import dhclient as dhclient_module |
1041 | +from provisioningserver.utils.dhclient import ( |
1042 | + get_dhclient_info, |
1043 | + get_lastest_fixed_address, |
1044 | +) |
1045 | +from provisioningserver.utils.fs import atomic_write |
1046 | + |
1047 | + |
1048 | +class TestGetLatestFixedAddress(MAASTestCase): |
1049 | + |
1050 | + IPV4_LEASE_FILE = dedent("""\ |
1051 | + lease { |
1052 | + interface "eno1"; |
1053 | + fixed-address 192.168.1.111; |
1054 | + } |
1055 | + lease { |
1056 | + interface "eno1"; |
1057 | + fixed-address 192.168.1.112; |
1058 | + } |
1059 | + lease { |
1060 | + interface "eno1"; |
1061 | + fixed-address 192.168.1.113; |
1062 | + } |
1063 | + """) |
1064 | + |
1065 | + IPV6_LEASE_FILE = dedent("""\ |
1066 | + lease { |
1067 | + interface "eno1"; |
1068 | + fixed-address6 2001:db8:a0b:12f0::1; |
1069 | + } |
1070 | + lease { |
1071 | + interface "eno1"; |
1072 | + fixed-address6 2001:db8:a0b:12f0::2; |
1073 | + } |
1074 | + lease { |
1075 | + interface "eno1"; |
1076 | + fixed-address6 2001:db8:a0b:12f0::3; |
1077 | + } |
1078 | + """) |
1079 | + |
1080 | + def test__missing(self): |
1081 | + self.assertIsNone( |
1082 | + get_lastest_fixed_address(factory.make_name("lease"))) |
1083 | + |
1084 | + def test__empty(self): |
1085 | + path = self.make_file(contents="") |
1086 | + self.assertIsNone(get_lastest_fixed_address(path)) |
1087 | + |
1088 | + def test__random(self): |
1089 | + path = self.make_file() |
1090 | + self.assertIsNone(get_lastest_fixed_address(path)) |
1091 | + |
1092 | + def test__ipv4(self): |
1093 | + path = self.make_file(contents=self.IPV4_LEASE_FILE) |
1094 | + self.assertEquals( |
1095 | + "192.168.1.113", get_lastest_fixed_address(path)) |
1096 | + |
1097 | + def test__ipv6(self): |
1098 | + path = self.make_file(contents=self.IPV6_LEASE_FILE) |
1099 | + self.assertEquals( |
1100 | + "2001:db8:a0b:12f0::3", get_lastest_fixed_address(path)) |
1101 | + |
1102 | + |
1103 | +class TestGetDhclientInfo(MAASTestCase): |
1104 | + |
1105 | + def test__returns_interface_name_with_address(self): |
1106 | + proc_path = self.make_dir() |
1107 | + leases_path = self.make_dir() |
1108 | + running_pids = set( |
1109 | + random.randint(2, 999) |
1110 | + for _ in range(3) |
1111 | + ) |
1112 | + self.patch( |
1113 | + dhclient_module, |
1114 | + "get_running_pids_with_command").return_value = running_pids |
1115 | + interfaces = {} |
1116 | + for pid in running_pids: |
1117 | + interface_name = factory.make_name("eth") |
1118 | + address = factory.make_ipv4_address() |
1119 | + interfaces[interface_name] = address |
1120 | + lease_path = os.path.join(leases_path, "%s.lease" % interface_name) |
1121 | + lease_data = dedent("""\ |
1122 | + lease { |
1123 | + interface "%s"; |
1124 | + fixed-address %s; |
1125 | + } |
1126 | + """) % (interface_name, address) |
1127 | + atomic_write(lease_data.encode("ascii"), lease_path) |
1128 | + cmdline_path = os.path.join(proc_path, str(pid), "cmdline") |
1129 | + cmdline = [ |
1130 | + "/sbin/dhclient", "-d", "-q", |
1131 | + "-pf", "/run/dhclient-%s.pid" % interface_name, |
1132 | + "-lf", lease_path, |
1133 | + "-cf", "/var/lib/dhclient/dhclient-%s.conf" % interface_name, |
1134 | + interface_name, |
1135 | + ] |
1136 | + cmdline = "\x00".join(cmdline) + "\x00" |
1137 | + os.mkdir(os.path.join(proc_path, str(pid))) |
1138 | + atomic_write(cmdline.encode("ascii"), cmdline_path) |
1139 | + self.assertEquals(interfaces, get_dhclient_info(proc_path=proc_path)) |
1140 | |
1141 | === modified file 'src/provisioningserver/utils/tests/test_network.py' |
1142 | --- src/provisioningserver/utils/tests/test_network.py 2016-02-25 22:05:39 +0000 |
1143 | +++ src/provisioningserver/utils/tests/test_network.py 2016-03-01 19:26:45 +0000 |
1144 | @@ -11,8 +11,6 @@ |
1145 | EAI_NONAME, |
1146 | gaierror, |
1147 | ) |
1148 | -from textwrap import dedent |
1149 | -from unittest import skipIf |
1150 | |
1151 | from maastesting.factory import factory |
1152 | from maastesting.matchers import MockCalledOnceWith |
1153 | @@ -37,7 +35,7 @@ |
1154 | find_mac_via_arp, |
1155 | get_all_addresses_for_interface, |
1156 | get_all_interface_addresses, |
1157 | - get_eni_interfaces_definition, |
1158 | + get_all_interfaces_definition, |
1159 | inet_ntop, |
1160 | intersect_iprange, |
1161 | ip_range_within_network, |
1162 | @@ -763,708 +761,278 @@ |
1163 | self.assertThat(parse_integer("0b10000000"), Equals(0b10000000)) |
1164 | |
1165 | |
1166 | -class TestGetENIInterfacesDefinition(MAASTestCase): |
1167 | - """Tests for `get_eni_interfaces_definition` and all helper methods.""" |
1168 | - |
1169 | - def setUp(self): |
1170 | - super(TestGetENIInterfacesDefinition, self).setUp() |
1171 | - self.patch(network_module, "call_and_check") |
1172 | - |
1173 | - def patch_parse_ip_addr(self, nic_mapping): |
1174 | - # Extract the interface name to MAC address mapping. |
1175 | - nic_mapping = { |
1176 | - name: { |
1177 | - "mac": mac |
1178 | - } |
1179 | - for name, mac in nic_mapping.items() |
1180 | - } |
1181 | - self.patch(network_module, "parse_ip_addr").return_value = nic_mapping |
1182 | - |
1183 | - def assertInterfacesResult( |
1184 | - self, eni_config, nic_mapping, expected_results, |
1185 | - include_other_interfaces=True): |
1186 | - file_name = self.make_file(contents=eni_config) |
1187 | - self.patch_parse_ip_addr(nic_mapping) |
1188 | - observed_result = get_eni_interfaces_definition( |
1189 | - eni_path=file_name, |
1190 | - include_other_interfaces=include_other_interfaces) |
1191 | +class TestGetAllInterfacesDefinition(MAASTestCase): |
1192 | + """Tests for `get_all_interfaces_definition` and all helper methods.""" |
1193 | + |
1194 | + def assertInterfacesResult(self, ip_addr, dhclient_info, expected_results): |
1195 | + self.patch(network_module, "get_ip_addr").return_value = ip_addr |
1196 | + self.patch( |
1197 | + network_module, "get_dhclient_info").return_value = dhclient_info |
1198 | + observed_result = get_all_interfaces_definition() |
1199 | self.assertThat(observed_result, expected_results) |
1200 | |
1201 | - def test__includes_interfaces_no_in_eni_as_disabled(self): |
1202 | - nic_mapping = { |
1203 | - "eth0": factory.make_mac_address(), |
1204 | - "eth1": factory.make_mac_address(), |
1205 | - "wlan0": factory.make_mac_address(), |
1206 | - "br0": factory.make_mac_address(), |
1207 | - } |
1208 | - interface_types = { |
1209 | - "eth0": "ethernet.physical", |
1210 | - "eth1": "ethernet.physical", |
1211 | - "wlan0": "ethernet.wireless", |
1212 | - "br0": "ethernet.bridge", |
1213 | - } |
1214 | - self.patch(network_module, "get_interface_type").side_effect = ( |
1215 | - lambda name: interface_types[name]) |
1216 | - expected_result = MatchesDict({ |
1217 | - "eth0": MatchesDict({ |
1218 | - "type": Equals("physical"), |
1219 | - "mac_address": Equals(nic_mapping["eth0"]), |
1220 | + def test__ignores_loopback(self): |
1221 | + ip_addr = { |
1222 | + "vnet": { |
1223 | + "type": "loopback", |
1224 | + "flags": ["UP"], |
1225 | + "inet": ["127.0.0.1/32"], |
1226 | + "inet6": ["::1"], |
1227 | + }, |
1228 | + } |
1229 | + self.assertInterfacesResult(ip_addr, {}, MatchesDict({})) |
1230 | + |
1231 | + def test__ignores_ethernet(self): |
1232 | + ip_addr = { |
1233 | + "vnet": { |
1234 | + "type": "ethernet", |
1235 | + "mac": factory.make_mac_address(), |
1236 | + "flags": ["UP"], |
1237 | + "inet": ["192.168.122.2/24"], |
1238 | + }, |
1239 | + } |
1240 | + self.assertInterfacesResult(ip_addr, {}, MatchesDict({})) |
1241 | + |
1242 | + def test__ignores_ipip(self): |
1243 | + ip_addr = { |
1244 | + "vnet": { |
1245 | + "type": "ipip", |
1246 | + "flags": ["UP"], |
1247 | + }, |
1248 | + } |
1249 | + self.assertInterfacesResult(ip_addr, {}, MatchesDict({})) |
1250 | + |
1251 | + def test__simple(self): |
1252 | + ip_addr = { |
1253 | + "eth0": { |
1254 | + "type": "ethernet.physical", |
1255 | + "mac": factory.make_mac_address(), |
1256 | + "flags": ["UP"], |
1257 | + "inet": ["192.168.122.2/24"], |
1258 | + }, |
1259 | + } |
1260 | + expected_result = MatchesDict({ |
1261 | + "eth0": MatchesDict({ |
1262 | + "type": Equals("physical"), |
1263 | + "mac_address": Equals(ip_addr["eth0"]["mac"]), |
1264 | + "enabled": Is(True), |
1265 | + "parents": Equals([]), |
1266 | + "links": Equals([{ |
1267 | + "mode": "static", |
1268 | + "address": "192.168.122.2/24", |
1269 | + }]), |
1270 | + "source": Equals("ipaddr"), |
1271 | + }), |
1272 | + }) |
1273 | + self.assertInterfacesResult(ip_addr, {}, expected_result) |
1274 | + |
1275 | + def test__simple_with_dhcp(self): |
1276 | + ip_addr = { |
1277 | + "eth0": { |
1278 | + "type": "ethernet.physical", |
1279 | + "mac": factory.make_mac_address(), |
1280 | + "flags": ["UP"], |
1281 | + "inet": ["192.168.122.2/24", "192.168.122.200/32"], |
1282 | + }, |
1283 | + } |
1284 | + dhclient_info = { |
1285 | + "eth0": "192.168.122.2", |
1286 | + } |
1287 | + expected_result = MatchesDict({ |
1288 | + "eth0": MatchesDict({ |
1289 | + "type": Equals("physical"), |
1290 | + "mac_address": Equals(ip_addr["eth0"]["mac"]), |
1291 | + "enabled": Is(True), |
1292 | + "parents": Equals([]), |
1293 | + "links": MatchesSetwise( |
1294 | + MatchesDict({ |
1295 | + "mode": Equals("dhcp"), |
1296 | + "address": Equals("192.168.122.2/24"), |
1297 | + }), |
1298 | + MatchesDict({ |
1299 | + "mode": Equals("static"), |
1300 | + "address": Equals("192.168.122.200/24"), |
1301 | + }), |
1302 | + ), |
1303 | + "source": Equals("ipaddr"), |
1304 | + }), |
1305 | + }) |
1306 | + self.assertInterfacesResult(ip_addr, dhclient_info, expected_result) |
1307 | + |
1308 | + def test__fixing_links(self): |
1309 | + ip_addr = { |
1310 | + "eth0": { |
1311 | + "type": "ethernet.physical", |
1312 | + "mac": factory.make_mac_address(), |
1313 | + "flags": ["UP"], |
1314 | + "inet": [ |
1315 | + "192.168.122.2/24", |
1316 | + "192.168.122.3/32", |
1317 | + "192.168.123.3/32", |
1318 | + ], |
1319 | + "inet6": [ |
1320 | + "2001:db8:a0b:12f0::1/96", |
1321 | + "2001:db8:a0b:12f0::2/128", |
1322 | + ] |
1323 | + }, |
1324 | + } |
1325 | + expected_result = MatchesDict({ |
1326 | + "eth0": MatchesDict({ |
1327 | + "type": Equals("physical"), |
1328 | + "mac_address": Equals(ip_addr["eth0"]["mac"]), |
1329 | + "enabled": Is(True), |
1330 | + "parents": Equals([]), |
1331 | + "links": MatchesSetwise( |
1332 | + MatchesDict({ |
1333 | + "mode": Equals("static"), |
1334 | + "address": Equals("192.168.122.2/24"), |
1335 | + }), |
1336 | + MatchesDict({ |
1337 | + "mode": Equals("static"), |
1338 | + "address": Equals("192.168.122.3/24"), |
1339 | + }), |
1340 | + MatchesDict({ |
1341 | + "mode": Equals("static"), |
1342 | + "address": Equals("192.168.123.3/32"), |
1343 | + }), |
1344 | + MatchesDict({ |
1345 | + "mode": Equals("static"), |
1346 | + "address": Equals("2001:db8:a0b:12f0::1/96"), |
1347 | + }), |
1348 | + MatchesDict({ |
1349 | + "mode": Equals("static"), |
1350 | + "address": Equals("2001:db8:a0b:12f0::2/96"), |
1351 | + }), |
1352 | + ), |
1353 | + "source": Equals("ipaddr"), |
1354 | + }), |
1355 | + }) |
1356 | + self.assertInterfacesResult(ip_addr, {}, expected_result) |
1357 | + |
1358 | + def test__complex(self): |
1359 | + ip_addr = { |
1360 | + "eth0": { |
1361 | + "type": "ethernet.physical", |
1362 | + "mac": factory.make_mac_address(), |
1363 | + "flags": [], |
1364 | + }, |
1365 | + "eth1": { |
1366 | + "type": "ethernet.physical", |
1367 | + "mac": factory.make_mac_address(), |
1368 | + "flags": ["UP"], |
1369 | + }, |
1370 | + "eth2": { |
1371 | + "type": "ethernet.physical", |
1372 | + "mac": factory.make_mac_address(), |
1373 | + "flags": ["UP"], |
1374 | + }, |
1375 | + "bond0": { |
1376 | + "type": "ethernet.bond", |
1377 | + "mac": factory.make_mac_address(), |
1378 | + "flags": ["UP"], |
1379 | + "bonded_interfaces": ["eth1", "eth2"], |
1380 | + "inet": ["192.168.122.2/24", "192.168.122.3/32"], |
1381 | + "inet6": ["2001:db8::3:2:2/96"], |
1382 | + }, |
1383 | + "bond0.10": { |
1384 | + "type": "ethernet.vlan", |
1385 | + "flags": ["UP"], |
1386 | + "vid": 10, |
1387 | + "inet": ["192.168.123.2/24", "192.168.123.3/32"], |
1388 | + }, |
1389 | + "wlan0": { |
1390 | + "type": "ethernet.wireless", |
1391 | + "mac": factory.make_mac_address(), |
1392 | + "flags": ["UP"], |
1393 | + }, |
1394 | + "br0": { |
1395 | + "type": "ethernet.bridge", |
1396 | + "mac": factory.make_mac_address(), |
1397 | + "flags": ["UP"], |
1398 | + "inet": ["192.168.124.2/24"], |
1399 | + }, |
1400 | + } |
1401 | + expected_result = MatchesDict({ |
1402 | + "eth0": MatchesDict({ |
1403 | + "type": Equals("physical"), |
1404 | + "mac_address": Equals(ip_addr["eth0"]["mac"]), |
1405 | "enabled": Is(False), |
1406 | "parents": Equals([]), |
1407 | "links": Equals([]), |
1408 | + "source": Equals("ipaddr"), |
1409 | }), |
1410 | "eth1": MatchesDict({ |
1411 | "type": Equals("physical"), |
1412 | - "mac_address": Equals(nic_mapping["eth1"]), |
1413 | - "enabled": Is(False), |
1414 | - "parents": Equals([]), |
1415 | - "links": Equals([]), |
1416 | + "mac_address": Equals(ip_addr["eth1"]["mac"]), |
1417 | + "enabled": Is(True), |
1418 | + "parents": Equals([]), |
1419 | + "links": Equals([]), |
1420 | + "source": Equals("ipaddr"), |
1421 | + }), |
1422 | + "eth2": MatchesDict({ |
1423 | + "type": Equals("physical"), |
1424 | + "mac_address": Equals(ip_addr["eth2"]["mac"]), |
1425 | + "enabled": Is(True), |
1426 | + "parents": Equals([]), |
1427 | + "links": Equals([]), |
1428 | + "source": Equals("ipaddr"), |
1429 | + }), |
1430 | + "bond0": MatchesDict({ |
1431 | + "type": Equals("bond"), |
1432 | + "mac_address": Equals(ip_addr["bond0"]["mac"]), |
1433 | + "enabled": Is(True), |
1434 | + "parents": Equals(["eth1", "eth2"]), |
1435 | + "links": MatchesSetwise( |
1436 | + MatchesDict({ |
1437 | + "mode": Equals("static"), |
1438 | + "address": Equals("192.168.122.2/24"), |
1439 | + }), |
1440 | + MatchesDict({ |
1441 | + "mode": Equals("static"), |
1442 | + "address": Equals("192.168.122.3/24"), |
1443 | + }), |
1444 | + MatchesDict({ |
1445 | + "mode": Equals("static"), |
1446 | + "address": Equals("2001:db8::3:2:2/96"), |
1447 | + }), |
1448 | + ), |
1449 | + "source": Equals("ipaddr"), |
1450 | + }), |
1451 | + "bond0.10": MatchesDict({ |
1452 | + "type": Equals("vlan"), |
1453 | + "enabled": Is(True), |
1454 | + "parents": Equals(["bond0"]), |
1455 | + "vid": Equals(10), |
1456 | + "links": MatchesSetwise( |
1457 | + MatchesDict({ |
1458 | + "mode": Equals("static"), |
1459 | + "address": Equals("192.168.123.2/24"), |
1460 | + }), |
1461 | + MatchesDict({ |
1462 | + "mode": Equals("static"), |
1463 | + "address": Equals("192.168.123.3/24"), |
1464 | + }), |
1465 | + ), |
1466 | + "source": Equals("ipaddr"), |
1467 | }), |
1468 | "wlan0": MatchesDict({ |
1469 | "type": Equals("physical"), |
1470 | - "mac_address": Equals(nic_mapping["wlan0"]), |
1471 | - "enabled": Is(False), |
1472 | - "parents": Equals([]), |
1473 | - "links": Equals([]), |
1474 | - }), |
1475 | - }) |
1476 | - self.assertInterfacesResult("", nic_mapping, expected_result) |
1477 | - |
1478 | - def test__ignores_inet_loopbacks(self): |
1479 | - eni = dedent("""\ |
1480 | - auto lo |
1481 | - iface lo inet loopback |
1482 | - |
1483 | - auto lo2 |
1484 | - iface lo2 inet loopback |
1485 | - """) |
1486 | - self.assertInterfacesResult( |
1487 | - eni, {}, MatchesDict({}), include_other_interfaces=False) |
1488 | - |
1489 | - def test__ignores_inet_bootp(self): |
1490 | - eni = dedent("""\ |
1491 | - auto eth0 |
1492 | - iface eth0 inet bootp |
1493 | - """) |
1494 | - nic_mapping = { |
1495 | - "eth0": factory.make_mac_address(), |
1496 | - } |
1497 | - self.assertInterfacesResult( |
1498 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1499 | - |
1500 | - def test__ignores_inet_tunnel(self): |
1501 | - eni = dedent("""\ |
1502 | - auto eth0 |
1503 | - iface eth0 inet tunnel |
1504 | - """) |
1505 | - nic_mapping = { |
1506 | - "eth0": factory.make_mac_address(), |
1507 | - } |
1508 | - self.assertInterfacesResult( |
1509 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1510 | - |
1511 | - def test__ignores_inet_ppp(self): |
1512 | - eni = dedent("""\ |
1513 | - auto eth0 |
1514 | - iface eth0 inet ppp |
1515 | - """) |
1516 | - nic_mapping = { |
1517 | - "eth0": factory.make_mac_address(), |
1518 | - } |
1519 | - self.assertInterfacesResult( |
1520 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1521 | - |
1522 | - def test__ignores_inet_wvdial(self): |
1523 | - eni = dedent("""\ |
1524 | - auto eth0 |
1525 | - iface eth0 inet wvdial |
1526 | - """) |
1527 | - nic_mapping = { |
1528 | - "eth0": factory.make_mac_address(), |
1529 | - } |
1530 | - self.assertInterfacesResult( |
1531 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1532 | - |
1533 | - def test__ignores_inet_ipv4ll(self): |
1534 | - eni = dedent("""\ |
1535 | - auto eth0 |
1536 | - iface eth0 inet ipv4ll |
1537 | - """) |
1538 | - nic_mapping = { |
1539 | - "eth0": factory.make_mac_address(), |
1540 | - } |
1541 | - self.assertInterfacesResult( |
1542 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1543 | - |
1544 | - def test__ignores_ipx_static(self): |
1545 | - eni = dedent("""\ |
1546 | - auto eth0 |
1547 | - iface eth0 ipx static |
1548 | - """) |
1549 | - nic_mapping = { |
1550 | - "eth0": factory.make_mac_address(), |
1551 | - } |
1552 | - self.assertInterfacesResult( |
1553 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1554 | - |
1555 | - def test__ignores_ipx_dynamic(self): |
1556 | - eni = dedent("""\ |
1557 | - auto eth0 |
1558 | - iface eth0 ipx dynamic |
1559 | - """) |
1560 | - nic_mapping = { |
1561 | - "eth0": factory.make_mac_address(), |
1562 | - } |
1563 | - self.assertInterfacesResult( |
1564 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1565 | - |
1566 | - def test__ignores_inet6_auto(self): |
1567 | - eni = dedent("""\ |
1568 | - auto eth0 |
1569 | - iface eth0 inet6 auto |
1570 | - """) |
1571 | - nic_mapping = { |
1572 | - "eth0": factory.make_mac_address(), |
1573 | - } |
1574 | - self.assertInterfacesResult( |
1575 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1576 | - |
1577 | - def test__ignores_inet6_loopback(self): |
1578 | - eni = dedent("""\ |
1579 | - auto lo |
1580 | - iface lo inet6 loopback |
1581 | - |
1582 | - auto lo2 |
1583 | - iface lo2 inet6 loopback |
1584 | - """) |
1585 | - self.assertInterfacesResult( |
1586 | - eni, {}, MatchesDict({}), include_other_interfaces=False) |
1587 | - |
1588 | - def test__ignores_inet6_v4tunnel(self): |
1589 | - eni = dedent("""\ |
1590 | - auto eth0 |
1591 | - iface eth0 inet6 v4tunnel |
1592 | - """) |
1593 | - nic_mapping = { |
1594 | - "eth0": factory.make_mac_address(), |
1595 | - } |
1596 | - self.assertInterfacesResult( |
1597 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1598 | - |
1599 | - def test__ignores_inet6_6to4(self): |
1600 | - eni = dedent("""\ |
1601 | - auto eth0 |
1602 | - iface eth0 inet6 6to4 |
1603 | - """) |
1604 | - nic_mapping = { |
1605 | - "eth0": factory.make_mac_address(), |
1606 | - } |
1607 | - self.assertInterfacesResult( |
1608 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1609 | - |
1610 | - def test__ignores_can_static(self): |
1611 | - eni = dedent("""\ |
1612 | - auto eth0 |
1613 | - iface eth0 can static |
1614 | - """) |
1615 | - nic_mapping = { |
1616 | - "eth0": factory.make_mac_address(), |
1617 | - } |
1618 | - self.assertInterfacesResult( |
1619 | - eni, nic_mapping, MatchesDict({}), include_other_interfaces=False) |
1620 | - |
1621 | - def test__grabs_mtu_on_interface(self): |
1622 | - eni = dedent("""\ |
1623 | - auto eth0 |
1624 | - iface eth0 inet static |
1625 | - address 192.168.122.2 |
1626 | - netmask 24 |
1627 | - gateway 192.168.122.1 |
1628 | - dns-nameservers 192.168.1.1 |
1629 | - mtu 1500 |
1630 | - """) |
1631 | - nic_mapping = { |
1632 | - "eth0": factory.make_mac_address(), |
1633 | - } |
1634 | - expected_result = MatchesDict({ |
1635 | - "eth0": MatchesDict({ |
1636 | - "type": Equals("physical"), |
1637 | - "parents": Equals([]), |
1638 | - "mac_address": Equals(nic_mapping["eth0"]), |
1639 | - "enabled": Is(True), |
1640 | - "mtu": Equals(1500), |
1641 | - "links": MatchesSetwise( |
1642 | - MatchesDict({ |
1643 | - "mode": Equals("static"), |
1644 | - "address": Equals("192.168.122.2/24"), |
1645 | - "gateway": Equals("192.168.122.1"), |
1646 | - }), |
1647 | - ), |
1648 | - }), |
1649 | - }) |
1650 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
1651 | - |
1652 | - def test__inet_with_netmask_as_prefix_len(self): |
1653 | - eni = dedent("""\ |
1654 | - auto eth0 |
1655 | - iface eth0 inet static |
1656 | - address 192.168.122.2 |
1657 | - netmask 24 |
1658 | - gateway 192.168.122.1 |
1659 | - dns-nameservers 192.168.1.1 |
1660 | - """) |
1661 | - nic_mapping = { |
1662 | - "eth0": factory.make_mac_address(), |
1663 | - } |
1664 | - expected_result = MatchesDict({ |
1665 | - "eth0": MatchesDict({ |
1666 | - "type": Equals("physical"), |
1667 | - "parents": Equals([]), |
1668 | - "mac_address": Equals(nic_mapping["eth0"]), |
1669 | - "enabled": Is(True), |
1670 | - "links": MatchesSetwise( |
1671 | - MatchesDict({ |
1672 | - "mode": Equals("static"), |
1673 | - "address": Equals("192.168.122.2/24"), |
1674 | - "gateway": Equals("192.168.122.1"), |
1675 | - }), |
1676 | - ), |
1677 | - }), |
1678 | - }) |
1679 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
1680 | - |
1681 | - def test__inet_simple_with_comments(self): |
1682 | - eni = dedent("""\ |
1683 | - # This file describes the network interfaces available on your |
1684 | - # system and how to activate them. For more information, |
1685 | - # see interfaces(5). |
1686 | - |
1687 | - # The loopback network interface |
1688 | - auto lo |
1689 | - iface lo inet loopback |
1690 | - |
1691 | - auto eth0 |
1692 | - iface eth0 inet static |
1693 | - address 192.168.122.2 |
1694 | - netmask 255.255.255.0 |
1695 | - gateway 192.168.122.1 |
1696 | - dns-nameservers 192.168.1.1 |
1697 | - """) |
1698 | - nic_mapping = { |
1699 | - "eth0": factory.make_mac_address(), |
1700 | - } |
1701 | - expected_result = MatchesDict({ |
1702 | - "eth0": MatchesDict({ |
1703 | - "type": Equals("physical"), |
1704 | - "parents": Equals([]), |
1705 | - "mac_address": Equals(nic_mapping["eth0"]), |
1706 | - "enabled": Is(True), |
1707 | - "links": MatchesSetwise( |
1708 | - MatchesDict({ |
1709 | - "mode": Equals("static"), |
1710 | - "address": Equals("192.168.122.2/24"), |
1711 | - "gateway": Equals("192.168.122.1"), |
1712 | - }), |
1713 | - ), |
1714 | - }), |
1715 | - }) |
1716 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
1717 | - |
1718 | - def test__inet_simple_with_aliases(self): |
1719 | - eni = dedent("""\ |
1720 | - auto lo |
1721 | - iface lo inet loopback |
1722 | - |
1723 | - auto eth0 |
1724 | - iface eth0 inet static |
1725 | - address 192.168.122.2 |
1726 | - netmask 255.255.255.0 |
1727 | - gateway 192.168.122.1 |
1728 | - dns-nameservers 192.168.1.1 |
1729 | - |
1730 | - auto eth0:1 |
1731 | - iface eth0:1 inet static |
1732 | - address 192.168.122.3/32 |
1733 | - |
1734 | - auto eth0:2 |
1735 | - iface eth0:2 inet static |
1736 | - address 192.168.122.4 |
1737 | - netmask 255.255.255.255 |
1738 | - |
1739 | - auto eth0:3 |
1740 | - iface eth0:3 inet dhcp |
1741 | - """) |
1742 | - nic_mapping = { |
1743 | - "eth0": factory.make_mac_address(), |
1744 | - } |
1745 | - expected_result = MatchesDict({ |
1746 | - "eth0": MatchesDict({ |
1747 | - "type": Equals("physical"), |
1748 | - "parents": Equals([]), |
1749 | - "mac_address": Equals(nic_mapping["eth0"]), |
1750 | - "enabled": Is(True), |
1751 | - "links": MatchesSetwise( |
1752 | - MatchesDict({ |
1753 | - "mode": Equals("static"), |
1754 | - "address": Equals("192.168.122.2/24"), |
1755 | - "gateway": Equals("192.168.122.1"), |
1756 | - }), |
1757 | - MatchesDict({ |
1758 | - "mode": Equals("static"), |
1759 | - "address": Equals("192.168.122.3/24"), |
1760 | - }), |
1761 | - MatchesDict({ |
1762 | - "mode": Equals("static"), |
1763 | - "address": Equals("192.168.122.4/24"), |
1764 | - }), |
1765 | - MatchesDict({ |
1766 | - "mode": Equals("dhcp"), |
1767 | - }), |
1768 | - ), |
1769 | - }), |
1770 | - }) |
1771 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
1772 | - |
1773 | - def test__inet_vlan(self): |
1774 | - eni = dedent("""\ |
1775 | - auto lo |
1776 | - iface lo inet loopback |
1777 | - |
1778 | - auto eth0 |
1779 | - iface eth0 inet static |
1780 | - address 192.168.122.2 |
1781 | - netmask 255.255.255.0 |
1782 | - gateway 192.168.122.1 |
1783 | - dns-nameservers 192.168.1.1 |
1784 | - |
1785 | - auto eth0.10 |
1786 | - iface eth0.10 inet static |
1787 | - address 192.168.123.2 |
1788 | - netmask 255.255.255.0 |
1789 | - |
1790 | - auto eth0.20 |
1791 | - iface eth0.20 inet static |
1792 | - address 192.168.124.2 |
1793 | - netmask 255.255.255.0 |
1794 | - |
1795 | - auto eth0.30 |
1796 | - iface eth0.30 inet dhcp |
1797 | - """) |
1798 | - eth0_mac = factory.make_mac_address() |
1799 | - nic_mapping = { |
1800 | - "eth0": eth0_mac, |
1801 | - "eth0.10": eth0_mac, |
1802 | - "eth0.20": eth0_mac, |
1803 | - "eth0.30": eth0_mac, |
1804 | - } |
1805 | - expected_result = MatchesDict({ |
1806 | - "eth0": MatchesDict({ |
1807 | - "type": Equals("physical"), |
1808 | - "parents": Equals([]), |
1809 | - "mac_address": Equals(nic_mapping["eth0"]), |
1810 | - "enabled": Is(True), |
1811 | - "links": MatchesSetwise( |
1812 | - MatchesDict({ |
1813 | - "mode": Equals("static"), |
1814 | - "address": Equals("192.168.122.2/24"), |
1815 | - "gateway": Equals("192.168.122.1"), |
1816 | - }), |
1817 | - ), |
1818 | - }), |
1819 | - "eth0.10": MatchesDict({ |
1820 | - "type": Equals("vlan"), |
1821 | - "parents": Equals(["eth0"]), |
1822 | - "vid": Equals(10), |
1823 | - "enabled": Is(True), |
1824 | - "links": MatchesSetwise( |
1825 | - MatchesDict({ |
1826 | - "mode": Equals("static"), |
1827 | - "address": Equals("192.168.123.2/24"), |
1828 | - }), |
1829 | - ), |
1830 | - }), |
1831 | - "eth0.20": MatchesDict({ |
1832 | - "type": Equals("vlan"), |
1833 | - "parents": Equals(["eth0"]), |
1834 | - "vid": Equals(20), |
1835 | - "enabled": Is(True), |
1836 | - "links": MatchesSetwise( |
1837 | - MatchesDict({ |
1838 | - "mode": Equals("static"), |
1839 | - "address": Equals("192.168.124.2/24"), |
1840 | - }), |
1841 | - ), |
1842 | - }), |
1843 | - "eth0.30": MatchesDict({ |
1844 | - "type": Equals("vlan"), |
1845 | - "parents": Equals(["eth0"]), |
1846 | - "vid": Equals(30), |
1847 | - "enabled": Is(True), |
1848 | - "links": MatchesSetwise( |
1849 | - MatchesDict({ |
1850 | - "mode": Equals("dhcp"), |
1851 | - }), |
1852 | - ), |
1853 | - }), |
1854 | - }) |
1855 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
1856 | - |
1857 | - def test__inet_vlan_with_aliases(self): |
1858 | - eni = dedent("""\ |
1859 | - auto lo |
1860 | - iface lo inet loopback |
1861 | - |
1862 | - auto eth0 |
1863 | - iface eth0 inet static |
1864 | - address 192.168.122.2 |
1865 | - netmask 255.255.255.0 |
1866 | - gateway 192.168.122.1 |
1867 | - dns-nameservers 192.168.1.1 |
1868 | - |
1869 | - auto eth0.10 |
1870 | - iface eth0.10 inet static |
1871 | - address 192.168.123.2 |
1872 | - netmask 255.255.255.0 |
1873 | - |
1874 | - auto eth0.10:1 |
1875 | - iface eth0.10:1 inet static |
1876 | - address 192.168.123.3/32 |
1877 | - |
1878 | - auto eth0.10:2 |
1879 | - iface eth0.10:2 inet static |
1880 | - address 192.168.123.4/32 |
1881 | - """) |
1882 | - eth0_mac = factory.make_mac_address() |
1883 | - nic_mapping = { |
1884 | - "eth0": eth0_mac, |
1885 | - "eth0.10": eth0_mac, |
1886 | - } |
1887 | - expected_result = MatchesDict({ |
1888 | - "eth0": MatchesDict({ |
1889 | - "type": Equals("physical"), |
1890 | - "parents": Equals([]), |
1891 | - "mac_address": Equals(nic_mapping["eth0"]), |
1892 | - "enabled": Is(True), |
1893 | - "links": MatchesSetwise( |
1894 | - MatchesDict({ |
1895 | - "mode": Equals("static"), |
1896 | - "address": Equals("192.168.122.2/24"), |
1897 | - "gateway": Equals("192.168.122.1"), |
1898 | - }), |
1899 | - ), |
1900 | - }), |
1901 | - "eth0.10": MatchesDict({ |
1902 | - "type": Equals("vlan"), |
1903 | - "parents": Equals(["eth0"]), |
1904 | - "vid": Equals(10), |
1905 | - "enabled": Is(True), |
1906 | - "links": MatchesSetwise( |
1907 | - MatchesDict({ |
1908 | - "mode": Equals("static"), |
1909 | - "address": Equals("192.168.123.2/24"), |
1910 | - }), |
1911 | - MatchesDict({ |
1912 | - "mode": Equals("static"), |
1913 | - "address": Equals("192.168.123.3/24"), |
1914 | - }), |
1915 | - MatchesDict({ |
1916 | - "mode": Equals("static"), |
1917 | - "address": Equals("192.168.123.4/24"), |
1918 | - }), |
1919 | - ), |
1920 | - }), |
1921 | - }) |
1922 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
1923 | - |
1924 | - @skipIf( |
1925 | - installed_curtin_version < 358, |
1926 | - "Update curtin to be newer than bzr358 to support parsing bonds " |
1927 | - "in /etc/network/interfaces.") |
1928 | - def test__inet_bond_with_vlan_and_aliases(self): |
1929 | - eni = dedent("""\ |
1930 | - auto lo |
1931 | - iface lo inet loopback |
1932 | - |
1933 | - auto eth0 |
1934 | - iface eth0 inet manual |
1935 | - bond-master bond0 |
1936 | - bond-primary eth0 |
1937 | - bond-mode active-backup |
1938 | - |
1939 | - auto eth1 |
1940 | - iface eth1 inet manual |
1941 | - bond-master bond0 |
1942 | - bond-primary eth0 |
1943 | - bond-mode active-backup |
1944 | - |
1945 | - auto bond0 |
1946 | - iface bond0 inet static |
1947 | - address 192.168.122.2 |
1948 | - netmask 255.255.255.0 |
1949 | - gateway 192.168.122.1 |
1950 | - dns-nameservers 192.168.1.1 |
1951 | - bond-slaves none |
1952 | - bond-primary eth0 |
1953 | - bond-mode active-backup |
1954 | - bond-miimon 100 |
1955 | - |
1956 | - auto bond0.10 |
1957 | - iface bond0.10 inet static |
1958 | - address 192.168.123.2 |
1959 | - netmask 255.255.255.0 |
1960 | - |
1961 | - auto bond0.10:1 |
1962 | - iface bond0.10:1 inet static |
1963 | - address 192.168.123.3 |
1964 | - netmask 255.255.255.255 |
1965 | - """) |
1966 | - eth0_mac = factory.make_mac_address() |
1967 | - eth0_mac = factory.make_mac_address() |
1968 | - nic_mapping = { |
1969 | - "eth0": eth0_mac, |
1970 | - "eth1": factory.make_mac_address(), |
1971 | - "bond0": eth0_mac, |
1972 | - "bond0.10": eth0_mac, |
1973 | - } |
1974 | - expected_result = MatchesDict({ |
1975 | - "eth0": MatchesDict({ |
1976 | - "type": Equals("physical"), |
1977 | - "mac_address": Equals(nic_mapping["eth0"]), |
1978 | - "enabled": Is(True), |
1979 | - "links": Equals([]), |
1980 | - "parents": Equals([]), |
1981 | - }), |
1982 | - "eth1": MatchesDict({ |
1983 | - "type": Equals("physical"), |
1984 | - "mac_address": Equals(nic_mapping["eth1"]), |
1985 | - "enabled": Is(True), |
1986 | - "links": Equals([]), |
1987 | - "parents": Equals([]), |
1988 | - }), |
1989 | - "bond0": MatchesDict({ |
1990 | - "type": Equals("bond"), |
1991 | - "parents": MatchesSetwise(Equals("eth0"), Equals("eth1")), |
1992 | - "mac_address": Equals(nic_mapping["eth0"]), |
1993 | - "enabled": Is(True), |
1994 | - "links": MatchesSetwise( |
1995 | - MatchesDict({ |
1996 | - "mode": Equals("static"), |
1997 | - "address": Equals("192.168.122.2/24"), |
1998 | - "gateway": Equals("192.168.122.1"), |
1999 | - }), |
2000 | - ), |
2001 | - }), |
2002 | - "bond0.10": MatchesDict({ |
2003 | - "type": Equals("vlan"), |
2004 | - "parents": Equals(["bond0"]), |
2005 | - "vid": Equals(10), |
2006 | - "enabled": Is(True), |
2007 | - "links": MatchesSetwise( |
2008 | - MatchesDict({ |
2009 | - "mode": Equals("static"), |
2010 | - "address": Equals("192.168.123.2/24"), |
2011 | - }), |
2012 | - MatchesDict({ |
2013 | - "mode": Equals("static"), |
2014 | - "address": Equals("192.168.123.3/24"), |
2015 | - }), |
2016 | - ), |
2017 | - }), |
2018 | - }) |
2019 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
2020 | - |
2021 | - @skipIf( |
2022 | - installed_curtin_version < 358, |
2023 | - "Update curtin to be newer than bzr358 to support parsing bonds " |
2024 | - "in /etc/network/interfaces.") |
2025 | - def test__inet6_bond_with_vlan_and_aliases(self): |
2026 | - eni = dedent("""\ |
2027 | - auto lo |
2028 | - iface lo inet6 loopback |
2029 | - |
2030 | - auto eth0 |
2031 | - iface eth0 inet6 manual |
2032 | - bond-master bond0 |
2033 | - bond-primary eth0 |
2034 | - bond-mode active-backup |
2035 | - |
2036 | - auto eth1 |
2037 | - iface eth1 inet6 manual |
2038 | - bond-master bond0 |
2039 | - bond-primary eth0 |
2040 | - bond-mode active-backup |
2041 | - |
2042 | - auto bond0 |
2043 | - iface bond0 inet6 static |
2044 | - address 2001:db8::3:2:2 |
2045 | - netmask 96 |
2046 | - gateway 2001:db8::3:2:1 |
2047 | - bond-slaves none |
2048 | - bond-primary eth0 |
2049 | - bond-mode active-backup |
2050 | - bond-miimon 100 |
2051 | - |
2052 | - auto bond0.10 |
2053 | - iface bond0.10 inet6 static |
2054 | - address 2001:db8::4:2:3 |
2055 | - netmask 96 |
2056 | - |
2057 | - auto bond0.10:1 |
2058 | - iface bond0.10:1 inet6 static |
2059 | - address 2001:db8::4:2:4 |
2060 | - netmask 128 |
2061 | - """) |
2062 | - eth0_mac = factory.make_mac_address() |
2063 | - eth0_mac = factory.make_mac_address() |
2064 | - nic_mapping = { |
2065 | - "eth0": eth0_mac, |
2066 | - "eth1": factory.make_mac_address(), |
2067 | - "bond0": eth0_mac, |
2068 | - "bond0.10": eth0_mac, |
2069 | - } |
2070 | - expected_result = MatchesDict({ |
2071 | - "eth0": MatchesDict({ |
2072 | - "type": Equals("physical"), |
2073 | - "mac_address": Equals(nic_mapping["eth0"]), |
2074 | - "enabled": Is(True), |
2075 | - "links": Equals([]), |
2076 | - "parents": Equals([]), |
2077 | - }), |
2078 | - "eth1": MatchesDict({ |
2079 | - "type": Equals("physical"), |
2080 | - "mac_address": Equals(nic_mapping["eth1"]), |
2081 | - "enabled": Is(True), |
2082 | - "links": Equals([]), |
2083 | - "parents": Equals([]), |
2084 | - }), |
2085 | - "bond0": MatchesDict({ |
2086 | - "type": Equals("bond"), |
2087 | - "parents": MatchesSetwise(Equals("eth0"), Equals("eth1")), |
2088 | - "mac_address": Equals(nic_mapping["eth0"]), |
2089 | - "enabled": Is(True), |
2090 | - "links": MatchesSetwise( |
2091 | - MatchesDict({ |
2092 | - "mode": Equals("static"), |
2093 | - "address": Equals("2001:db8::3:2:2/96"), |
2094 | - "gateway": Equals("2001:db8::3:2:1"), |
2095 | - }), |
2096 | - ), |
2097 | - }), |
2098 | - "bond0.10": MatchesDict({ |
2099 | - "type": Equals("vlan"), |
2100 | - "parents": Equals(["bond0"]), |
2101 | - "vid": Equals(10), |
2102 | - "enabled": Is(True), |
2103 | - "links": MatchesSetwise( |
2104 | - MatchesDict({ |
2105 | - "mode": Equals("static"), |
2106 | - "address": Equals("2001:db8::4:2:3/96"), |
2107 | - }), |
2108 | - MatchesDict({ |
2109 | - "mode": Equals("static"), |
2110 | - "address": Equals("2001:db8::4:2:4/96"), |
2111 | - }), |
2112 | - ), |
2113 | - }), |
2114 | - }) |
2115 | - self.assertInterfacesResult(eni, nic_mapping, expected_result) |
2116 | + "mac_address": Equals(ip_addr["wlan0"]["mac"]), |
2117 | + "enabled": Is(True), |
2118 | + "parents": Equals([]), |
2119 | + "links": Equals([]), |
2120 | + "source": Equals("ipaddr"), |
2121 | + }), |
2122 | + "br0": MatchesDict({ |
2123 | + "type": Equals("physical"), |
2124 | + "mac_address": Equals(ip_addr["br0"]["mac"]), |
2125 | + "enabled": Is(True), |
2126 | + "parents": Equals([]), |
2127 | + "links": Equals([{ |
2128 | + "mode": "static", |
2129 | + "address": "192.168.124.2/24", |
2130 | + }]), |
2131 | + "source": Equals("ipaddr"), |
2132 | + }), |
2133 | + }) |
2134 | + self.assertInterfacesResult(ip_addr, {}, expected_result) |
2135 | |
2136 | === added file 'src/provisioningserver/utils/tests/test_ps.py' |
2137 | --- src/provisioningserver/utils/tests/test_ps.py 1970-01-01 00:00:00 +0000 |
2138 | +++ src/provisioningserver/utils/tests/test_ps.py 2016-03-01 19:26:45 +0000 |
2139 | @@ -0,0 +1,191 @@ |
2140 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
2141 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
2142 | + |
2143 | +"""Tests for process helpers.""" |
2144 | + |
2145 | +__all__ = [] |
2146 | + |
2147 | +import os |
2148 | +import random |
2149 | +from textwrap import dedent |
2150 | + |
2151 | +from maastesting.factory import factory |
2152 | +from maastesting.testcase import MAASTestCase |
2153 | +from provisioningserver.utils.fs import atomic_write |
2154 | +from provisioningserver.utils.ps import ( |
2155 | + get_running_pids_with_command, |
2156 | + is_pid_in_container, |
2157 | + running_in_container, |
2158 | +) |
2159 | + |
2160 | + |
2161 | +NOT_IN_CONTAINER = dedent("""\ |
2162 | + 11:freezer:/ |
2163 | + 10:perf_event:/ |
2164 | + 9:cpuset:/ |
2165 | + 8:net_cls,net_prio:/init.scope |
2166 | + 7:devices:/init.scope |
2167 | + 6:blkio:/init.scope |
2168 | + 5:memory:/init.scope |
2169 | + 4:cpu,cpuacct:/init.scope |
2170 | + 3:pids:/init.scope |
2171 | + 2:hugetlb:/ |
2172 | + 1:name=systemd:/init.scope |
2173 | + """) |
2174 | + |
2175 | +IN_DOCKER_CONTAINER = dedent("""\ |
2176 | + 11:freezer:/system.slice/docker-8467.scope |
2177 | + 10:perf_event:/ |
2178 | + 9:cpuset:/system.slice/docker-8467.scope |
2179 | + 8:net_cls,net_prio:/init.scope |
2180 | + 7:devices:/init.scope/system.slice/docker-8467.scope |
2181 | + 6:blkio:/system.slice/docker-8467.scope |
2182 | + 5:memory:/system.slice/docker-8467.scope |
2183 | + 4:cpu,cpuacct:/system.slice/docker-8467.scope |
2184 | + 3:pids:/system.slice/docker-8467.scopeatomic_write |
2185 | + 2:hugetlb:/ |
2186 | + 1:name=systemd:/system.slice/docker-8467.scope |
2187 | + """) |
2188 | + |
2189 | +IN_LXC_CONTAINER = dedent("""\ |
2190 | + 11:freezer:/lxc/maas |
2191 | + 10:perf_event:/lxc/maas |
2192 | + 9:cpuset:/lxc/maas |
2193 | + 8:net_cls,net_prio:/lxc/maas |
2194 | + 7:devices:/lxc/maas/init.scope |
2195 | + 6:blkio:/lxc/maas |
2196 | + 5:memory:/lxc/maas |
2197 | + 4:cpu,cpuacct:/lxc/maas |
2198 | + 3:pids:/lxc/maas/init.scope |
2199 | + 2:hugetlb:/lxc/maas |
2200 | + 1:name=systemd:/lxc/maas/init.scope |
2201 | + """) |
2202 | + |
2203 | + |
2204 | +class TestIsPIDInContainer(MAASTestCase): |
2205 | + |
2206 | + scenarios = ( |
2207 | + ("not_in_container", { |
2208 | + "result": False, |
2209 | + "cgroup": NOT_IN_CONTAINER, |
2210 | + }), |
2211 | + ("in_docker_container", { |
2212 | + "result": True, |
2213 | + "cgroup": IN_DOCKER_CONTAINER, |
2214 | + }), |
2215 | + ("in_lxc_container", { |
2216 | + "result": True, |
2217 | + "cgroup": IN_LXC_CONTAINER, |
2218 | + }), |
2219 | + ) |
2220 | + |
2221 | + def test__result(self): |
2222 | + proc_path = self.make_dir() |
2223 | + pid = random.randint(1, 1000) |
2224 | + pid_path = os.path.join(proc_path, str(pid)) |
2225 | + os.mkdir(pid_path) |
2226 | + atomic_write( |
2227 | + self.cgroup.encode("ascii"), |
2228 | + os.path.join(pid_path, "cgroup")) |
2229 | + self.assertEqual( |
2230 | + self.result, is_pid_in_container(pid, proc_path=proc_path)) |
2231 | + |
2232 | + |
2233 | +class TestRunningInContainer(MAASTestCase): |
2234 | + |
2235 | + scenarios = ( |
2236 | + ("not_in_container", { |
2237 | + "result": False, |
2238 | + "cgroup": NOT_IN_CONTAINER, |
2239 | + }), |
2240 | + ("in_docker_container", { |
2241 | + "result": True, |
2242 | + "cgroup": IN_DOCKER_CONTAINER, |
2243 | + }), |
2244 | + ("in_lxc_container", { |
2245 | + "result": True, |
2246 | + "cgroup": IN_LXC_CONTAINER, |
2247 | + }), |
2248 | + ) |
2249 | + |
2250 | + def test__result(self): |
2251 | + proc_path = self.make_dir() |
2252 | + pid_path = os.path.join(proc_path, "1") |
2253 | + os.mkdir(pid_path) |
2254 | + atomic_write( |
2255 | + self.cgroup.encode("ascii"), |
2256 | + os.path.join(pid_path, "cgroup")) |
2257 | + self.assertEqual( |
2258 | + self.result, running_in_container(proc_path=proc_path)) |
2259 | + |
2260 | + |
2261 | +class TestGetRunningPIDsWithCommand(MAASTestCase): |
2262 | + |
2263 | + def make_process(self, proc_path, pid, in_container=False, command=None): |
2264 | + cgroup = NOT_IN_CONTAINER |
2265 | + if in_container: |
2266 | + cgroup = random.choice([IN_DOCKER_CONTAINER, IN_LXC_CONTAINER]) |
2267 | + pid_path = os.path.join(proc_path, str(pid)) |
2268 | + os.mkdir(pid_path) |
2269 | + atomic_write( |
2270 | + cgroup.encode("ascii"), |
2271 | + os.path.join(pid_path, "cgroup")) |
2272 | + if command is not None: |
2273 | + atomic_write( |
2274 | + command.encode("ascii"), |
2275 | + os.path.join(pid_path, "comm")) |
2276 | + |
2277 | + def make_init_process(self, proc_path, in_container=False): |
2278 | + self.make_process(proc_path, 1, in_container=in_container) |
2279 | + |
2280 | + def test_returns_processes_running_on_host_not_container(self): |
2281 | + proc_path = self.make_dir() |
2282 | + self.make_init_process(proc_path) |
2283 | + command = factory.make_name("command") |
2284 | + pids_running_command = set( |
2285 | + random.randint(2, 999) |
2286 | + for _ in range(3) |
2287 | + ) |
2288 | + for pid in pids_running_command: |
2289 | + self.make_process(proc_path, pid, command=command) |
2290 | + pids_not_running_command = set( |
2291 | + random.randint(1000, 1999) |
2292 | + for _ in range(3) |
2293 | + ) |
2294 | + for pid in pids_not_running_command: |
2295 | + self.make_process( |
2296 | + proc_path, pid, command=factory.make_name("command")) |
2297 | + pids_running_command_in_container = set( |
2298 | + random.randint(2000, 2999) |
2299 | + for _ in range(3) |
2300 | + ) |
2301 | + for pid in pids_running_command_in_container: |
2302 | + self.make_process( |
2303 | + proc_path, pid, |
2304 | + in_container=True, command=command) |
2305 | + self.assertItemsEqual( |
2306 | + pids_running_command, |
2307 | + get_running_pids_with_command(command, proc_path=proc_path)) |
2308 | + |
2309 | + def test_returns_processes_when_running_in_container(self): |
2310 | + proc_path = self.make_dir() |
2311 | + self.make_init_process(proc_path, in_container=True) |
2312 | + command = factory.make_name("command") |
2313 | + pids_running_command = set( |
2314 | + random.randint(2, 999) |
2315 | + for _ in range(3) |
2316 | + ) |
2317 | + for pid in pids_running_command: |
2318 | + self.make_process( |
2319 | + proc_path, pid, in_container=True, command=command) |
2320 | + pids_not_running_command = set( |
2321 | + random.randint(1000, 1999) |
2322 | + for _ in range(3) |
2323 | + ) |
2324 | + for pid in pids_not_running_command: |
2325 | + self.make_process( |
2326 | + proc_path, pid, |
2327 | + in_container=True, command=factory.make_name("command")) |
2328 | + self.assertItemsEqual( |
2329 | + pids_running_command, |
2330 | + get_running_pids_with_command(command, proc_path=proc_path)) |
This is a very large branch because it completely replaced what we where doing with just using all of "ip addr" output. That output was extended as well to get the DHCP IP addresses so the model in the region can be fully complete.
I have QA'd this branch and it is working much better now. All interfaces on machines will appear except for those that are just plain ethernet devices (aka. tun-tap devices). So bridges and bonds all show up. This branch does not create bridged correctly in the MAAS model it just creates them as physical interfaces.