Merge lp:~blake-rouse/maas/ip-addr-with-dhclient into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
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
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.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

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.

Revision history for this message
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.

http://imgur.com/wMcNewW

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Nice work. Ship it!

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1.0 MiB)

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://security.ubuntu.com/ubuntu xenial-security InRelease
Get:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease [95.8 kB]
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Fetched 95.8 kB in 0s (231 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
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.3.dfsg.P2-4).
bind9utils is already the newest version (1:9.10.3.dfsg.P2-4).
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.20160115ubuntu2).
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.3.dfsg.P2-4).
firefox is already the newest version (44.0.2+build1-0ubuntu1).
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-hotkeys is already the newest version (0~20130707+...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1014.7 KiB)

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://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease [95.8 kB]
Hit:2 http://security.ubuntu.com/ubuntu xenial-security InRelease
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Fetched 95.8 kB in 0s (230 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
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.3.dfsg.P2-4).
bind9utils is already the newest version (1:9.10.3.dfsg.P2-4).
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.20160115ubuntu2).
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.3.dfsg.P2-4).
firefox is already the newest version (44.0.2+build1-0ubuntu1).
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-hotkeys is already the newest version (0~20130707+...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1.1 MiB)

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://security.ubuntu.com/ubuntu xenial-security InRelease
Hit:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
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.3.dfsg.P2-4).
bind9utils is already the newest version (1:9.10.3.dfsg.P2-4).
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.20160115ubuntu2).
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.3.dfsg.P2-4).
firefox is already the newest version (44.0.2+build1-0ubuntu1).
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-hotkeys is already the newest version (0~20130707+git2d51e3a9+dfsg-2ubuntu1).
libjs-yui3-full...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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))