Merge ~harlowja/cloud-init:sys-io-errors into cloud-init:master

Proposed by Joshua Harlow
Status: Merged
Merged at revision: 3ac34f5325edf7f1212cd0b6a3d1cfe2ed45a63b
Proposed branch: ~harlowja/cloud-init:sys-io-errors
Merge into: cloud-init:master
Diff against target: 333 lines (+102/-95)
3 files modified
cloudinit/net/__init__.py (+84/-82)
cloudinit/net/cmdline.py (+6/-3)
tests/unittests/test_net.py (+12/-10)
Reviewer Review Type Date Requested Status
Lars Kellogg-Stedman (community) Approve
Scott Moser Needs Fixing
Review via email: mp+305882@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

Josh,

I had sort of done this as:
http://paste.ubuntu.com/23207571/

The person who had reproduced this on arch said that the patch above it fixes it.

Another option...
it looks like callers of sys_netdev_info could use read_sys_net.
And read_sys_net actually handles the IOError already.

I think thats the option I'd prefer.

Revision history for this message
Joshua Harlow (harlowja) wrote :

Ya, that makes sense to me, I can adjust this one.

Revision history for this message
Scott Moser (smoser) wrote :

hm.. i had just inteded to say lets drop sys_netdev_info and instead make the callers use read_sys_net

Revision history for this message
Joshua Harlow (harlowja) wrote :

Oh, lol.

Revision history for this message
Mike Dorman (mdorman-m) wrote :

Data point: We tried this patch out locally under RHEL7 and it's working like a champ.

Revision history for this message
Scott Moser (smoser) wrote :

it looks like your test case is busted, flake8 fails which makes me wonder if its working correctly.
  tests/unittests/test_net.py:478:36: F821 undefined name 'name'
  tests/unittests/test_net.py:478:42: F821 undefined name 'field'

that and a few other changes:
  http://paste.ubuntu.com/23354704/

we should not any longer need to catch the IOError and OSError from users of read_sys_net as it should handle them if enoent is provided.

Revision history for this message
Scott Moser (smoser) wrote :

Josh,
can you take another look here?

Revision history for this message
Joshua Harlow (harlowja) wrote :

Will double check, though I fixed that recently.

Revision history for this message
Joshua Harlow (harlowja) wrote :

*thought

Revision history for this message
Scott Moser (smoser) wrote :

it looks like you fixed the test cases, but one error still.

Also, can you adjust summary line of your commit to be
   Replace usage of sys_netdev_info with read_sys_net.

review: Needs Fixing
Revision history for this message
Lars Kellogg-Stedman (larsks) wrote :

The updated patch looks good to me. One inline comment, but it's not operationally important.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
2old mode 100644
3new mode 100755
4index 7e58bfe..465c2a0
5--- a/cloudinit/net/__init__.py
6+++ b/cloudinit/net/__init__.py
7@@ -32,25 +32,53 @@ def sys_dev_path(devname, path=""):
8 return SYS_CLASS_NET + devname + "/" + path
9
10
11-def read_sys_net(devname, path, translate=None, enoent=None, keyerror=None):
12+def read_sys_net(devname, path, translate=None,
13+ on_enoent=None, on_keyerror=None,
14+ on_einval=None):
15+ dev_path = sys_dev_path(devname, path)
16 try:
17- contents = util.load_file(sys_dev_path(devname, path))
18+ contents = util.load_file(dev_path)
19 except (OSError, IOError) as e:
20- if getattr(e, 'errno', None) in (errno.ENOENT, errno.ENOTDIR):
21- if enoent is not None:
22- return enoent
23+ e_errno = getattr(e, 'errno', None)
24+ if e_errno in (errno.ENOENT, errno.ENOTDIR):
25+ if on_enoent is not None:
26+ return on_enoent(e)
27+ if e_errno in (errno.EINVAL,):
28+ if on_einval is not None:
29+ return on_einval(e)
30 raise
31 contents = contents.strip()
32 if translate is None:
33 return contents
34 try:
35- return translate.get(contents)
36- except KeyError:
37- LOG.debug("found unexpected value '%s' in '%s/%s'", contents,
38- devname, path)
39- if keyerror is not None:
40- return keyerror
41- raise
42+ return translate[contents]
43+ except KeyError as e:
44+ if on_keyerror is not None:
45+ return on_keyerror(e)
46+ else:
47+ LOG.debug("Found unexpected (not translatable) value"
48+ " '%s' in '%s", contents, dev_path)
49+ raise
50+
51+
52+def read_sys_net_safe(iface, field, translate=None):
53+ def on_excp_false(e):
54+ return False
55+ return read_sys_net(iface, field,
56+ on_keyerror=on_excp_false,
57+ on_enoent=on_excp_false,
58+ on_einval=on_excp_false,
59+ translate=translate)
60+
61+
62+def read_sys_net_int(iface, field):
63+ val = read_sys_net_safe(iface, field)
64+ if val is False:
65+ return None
66+ try:
67+ return int(val)
68+ except TypeError:
69+ return None
70
71
72 def is_up(devname):
73@@ -58,8 +86,7 @@ def is_up(devname):
74 # operstate as up for the purposes of network configuration. See
75 # Documentation/networking/operstates.txt in the kernel source.
76 translate = {'up': True, 'unknown': True, 'down': False}
77- return read_sys_net(devname, "operstate", enoent=False, keyerror=False,
78- translate=translate)
79+ return read_sys_net_safe(devname, "operstate", translate=translate)
80
81
82 def is_wireless(devname):
83@@ -70,21 +97,14 @@ def is_connected(devname):
84 # is_connected isn't really as simple as that. 2 is
85 # 'physically connected'. 3 is 'not connected'. but a wlan interface will
86 # always show 3.
87- try:
88- iflink = read_sys_net(devname, "iflink", enoent=False)
89- if iflink == "2":
90- return True
91- if not is_wireless(devname):
92- return False
93- LOG.debug("'%s' is wireless, basing 'connected' on carrier", devname)
94-
95- return read_sys_net(devname, "carrier", enoent=False, keyerror=False,
96- translate={'0': False, '1': True})
97-
98- except IOError as e:
99- if e.errno == errno.EINVAL:
100- return False
101- raise
102+ iflink = read_sys_net_safe(devname, "iflink")
103+ if iflink == "2":
104+ return True
105+ if not is_wireless(devname):
106+ return False
107+ LOG.debug("'%s' is wireless, basing 'connected' on carrier", devname)
108+ return read_sys_net_safe(devname, "carrier",
109+ translate={'0': False, '1': True})
110
111
112 def is_physical(devname):
113@@ -109,25 +129,9 @@ def is_disabled_cfg(cfg):
114 return cfg.get('config') == "disabled"
115
116
117-def sys_netdev_info(name, field):
118- if not os.path.exists(os.path.join(SYS_CLASS_NET, name)):
119- raise OSError("%s: interface does not exist in %s" %
120- (name, SYS_CLASS_NET))
121- fname = os.path.join(SYS_CLASS_NET, name, field)
122- if not os.path.exists(fname):
123- raise OSError("%s: could not find sysfs entry: %s" % (name, fname))
124- data = util.load_file(fname)
125- if data[-1] == '\n':
126- data = data[:-1]
127- return data
128-
129-
130 def generate_fallback_config():
131 """Determine which attached net dev is most likely to have a connection and
132 generate network state to run dhcp on that interface"""
133- # by default use eth0 as primary interface
134- nconf = {'config': [], 'version': 1}
135-
136 # get list of interfaces that could have connections
137 invalid_interfaces = set(['lo'])
138 potential_interfaces = set(get_devicelist())
139@@ -142,30 +146,21 @@ def generate_fallback_config():
140 if os.path.exists(sys_dev_path(interface, "bridge")):
141 # skip any bridges
142 continue
143- try:
144- carrier = int(sys_netdev_info(interface, 'carrier'))
145- if carrier:
146- connected.append(interface)
147- continue
148- except OSError:
149- pass
150+ carrier = read_sys_net_int(interface, 'carrier')
151+ if carrier:
152+ connected.append(interface)
153+ continue
154 # check if nic is dormant or down, as this may make a nick appear to
155 # not have a carrier even though it could acquire one when brought
156 # online by dhclient
157- try:
158- dormant = int(sys_netdev_info(interface, 'dormant'))
159- if dormant:
160- possibly_connected.append(interface)
161- continue
162- except OSError:
163- pass
164- try:
165- operstate = sys_netdev_info(interface, 'operstate')
166- if operstate in ['dormant', 'down', 'lowerlayerdown', 'unknown']:
167- possibly_connected.append(interface)
168- continue
169- except OSError:
170- pass
171+ dormant = read_sys_net_int(interface, 'dormant')
172+ if dormant:
173+ possibly_connected.append(interface)
174+ continue
175+ operstate = read_sys_net_safe(interface, 'operstate')
176+ if operstate in ['dormant', 'down', 'lowerlayerdown', 'unknown']:
177+ possibly_connected.append(interface)
178+ continue
179
180 # don't bother with interfaces that might not be connected if there are
181 # some that definitely are
182@@ -173,23 +168,30 @@ def generate_fallback_config():
183 potential_interfaces = connected
184 else:
185 potential_interfaces = possibly_connected
186- # if there are no interfaces, give up
187- if not potential_interfaces:
188- return
189+
190 # if eth0 exists use it above anything else, otherwise get the interface
191- # that looks 'first'
192- if DEFAULT_PRIMARY_INTERFACE in potential_interfaces:
193- name = DEFAULT_PRIMARY_INTERFACE
194+ # that we can read 'first' (using the sorted defintion of first).
195+ names = list(sorted(potential_interfaces))
196+ if DEFAULT_PRIMARY_INTERFACE in names:
197+ names.remove(DEFAULT_PRIMARY_INTERFACE)
198+ names.insert(0, DEFAULT_PRIMARY_INTERFACE)
199+ target_name = None
200+ target_mac = None
201+ for name in names:
202+ mac = read_sys_net_safe(name, 'address')
203+ if mac:
204+ target_name = name
205+ target_mac = mac
206+ break
207+ if target_mac and target_name:
208+ nconf = {'config': [], 'version': 1}
209+ nconf['config'].append(
210+ {'type': 'physical', 'name': target_name,
211+ 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]})
212+ return nconf
213 else:
214- name = sorted(potential_interfaces)[0]
215-
216- mac = sys_netdev_info(name, 'address')
217- target_name = name
218-
219- nconf['config'].append(
220- {'type': 'physical', 'name': target_name,
221- 'mac_address': mac, 'subnets': [{'type': 'dhcp'}]})
222- return nconf
223+ # can't read any interfaces addresses (or there are none); give up
224+ return None
225
226
227 def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
228@@ -352,7 +354,7 @@ def get_interface_mac(ifname):
229 # for a bond slave, get the nic's hwaddress, not the address it
230 # is using because its part of a bond.
231 path = "bonding_slave/perm_hwaddr"
232- return read_sys_net(ifname, path, enoent=False)
233+ return read_sys_net_safe(ifname, path)
234
235
236 def get_interfaces_by_mac(devs=None):
237diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py
238old mode 100644
239new mode 100755
240index cbed908..a8bf73f
241--- a/cloudinit/net/cmdline.py
242+++ b/cloudinit/net/cmdline.py
243@@ -26,7 +26,7 @@ import sys
244 import six
245
246 from . import get_devicelist
247-from . import sys_netdev_info
248+from . import read_sys_net_safe
249
250 from cloudinit import util
251
252@@ -210,7 +210,10 @@ def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None):
253 return None
254
255 if mac_addrs is None:
256- mac_addrs = dict((k, sys_netdev_info(k, 'address'))
257- for k in get_devicelist())
258+ mac_addrs = {}
259+ for k in get_devicelist():
260+ mac_addr = read_sys_net_safe(k, 'address')
261+ if mac_addr:
262+ mac_addrs[k] = mac_addr
263
264 return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs)
265diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
266old mode 100644
267new mode 100755
268index 780e4ad..789c78b
269--- a/tests/unittests/test_net.py
270+++ b/tests/unittests/test_net.py
271@@ -445,7 +445,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
272 }
273
274
275-def _setup_test(tmp_dir, mock_get_devicelist, mock_sys_netdev_info,
276+def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net,
277 mock_sys_dev_path):
278 mock_get_devicelist.return_value = ['eth1000']
279 dev_characteristics = {
280@@ -458,10 +458,12 @@ def _setup_test(tmp_dir, mock_get_devicelist, mock_sys_netdev_info,
281 }
282 }
283
284- def netdev_info(name, field):
285- return dev_characteristics[name][field]
286+ def fake_read(devname, path, translate=None,
287+ on_enoent=None, on_keyerror=None,
288+ on_einval=None):
289+ return dev_characteristics[devname][path]
290
291- mock_sys_netdev_info.side_effect = netdev_info
292+ mock_read_sys_net.side_effect = fake_read
293
294 def sys_dev_path(devname, path=""):
295 return tmp_dir + devname + "/" + path
296@@ -477,15 +479,15 @@ def _setup_test(tmp_dir, mock_get_devicelist, mock_sys_netdev_info,
297 class TestSysConfigRendering(TestCase):
298
299 @mock.patch("cloudinit.net.sys_dev_path")
300- @mock.patch("cloudinit.net.sys_netdev_info")
301+ @mock.patch("cloudinit.net.read_sys_net")
302 @mock.patch("cloudinit.net.get_devicelist")
303 def test_default_generation(self, mock_get_devicelist,
304- mock_sys_netdev_info,
305+ mock_read_sys_net,
306 mock_sys_dev_path):
307 tmp_dir = tempfile.mkdtemp()
308 self.addCleanup(shutil.rmtree, tmp_dir)
309 _setup_test(tmp_dir, mock_get_devicelist,
310- mock_sys_netdev_info, mock_sys_dev_path)
311+ mock_read_sys_net, mock_sys_dev_path)
312
313 network_cfg = net.generate_fallback_config()
314 ns = network_state.parse_net_config_data(network_cfg,
315@@ -534,15 +536,15 @@ USERCTL=no
316 class TestEniNetRendering(TestCase):
317
318 @mock.patch("cloudinit.net.sys_dev_path")
319- @mock.patch("cloudinit.net.sys_netdev_info")
320+ @mock.patch("cloudinit.net.read_sys_net")
321 @mock.patch("cloudinit.net.get_devicelist")
322 def test_default_generation(self, mock_get_devicelist,
323- mock_sys_netdev_info,
324+ mock_read_sys_net,
325 mock_sys_dev_path):
326 tmp_dir = tempfile.mkdtemp()
327 self.addCleanup(shutil.rmtree, tmp_dir)
328 _setup_test(tmp_dir, mock_get_devicelist,
329- mock_sys_netdev_info, mock_sys_dev_path)
330+ mock_read_sys_net, mock_sys_dev_path)
331
332 network_cfg = net.generate_fallback_config()
333 ns = network_state.parse_net_config_data(network_cfg,

Subscribers

People subscribed via source and target branches