Merge ~harlowja/cloud-init:sys-io-errors into cloud-init:master
- Git
- lp:~harlowja/cloud-init
- sys-io-errors
- Merge into master
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) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Lars Kellogg-Stedman (community) | Approve | ||
Scott Moser | Needs Fixing | ||
Review via email: mp+305882@code.launchpad.net |
Commit message
Description of the change
Scott Moser (smoser) wrote : | # |
Joshua Harlow (harlowja) wrote : | # |
Ya, that makes sense to me, I can adjust this one.
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
Joshua Harlow (harlowja) wrote : | # |
Oh, lol.
Mike Dorman (mdorman-m) wrote : | # |
Data point: We tried this patch out locally under RHEL7 and it's working like a champ.
Scott Moser (smoser) wrote : | # |
it looks like your test case is busted, flake8 fails which makes me wonder if its working correctly.
tests/
tests/
that and a few other changes:
http://
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.
Scott Moser (smoser) wrote : | # |
Josh,
can you take another look here?
Joshua Harlow (harlowja) wrote : | # |
Will double check, though I fixed that recently.
Joshua Harlow (harlowja) wrote : | # |
*thought
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.
Lars Kellogg-Stedman (larsks) wrote : | # |
The updated patch looks good to me. One inline comment, but it's not operationally important.
Preview Diff
1 | diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py |
2 | old mode 100644 |
3 | new mode 100755 |
4 | index 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): |
237 | diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py |
238 | old mode 100644 |
239 | new mode 100755 |
240 | index 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) |
265 | diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py |
266 | old mode 100644 |
267 | new mode 100755 |
268 | index 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, |
Josh,
I had sort of done this as: paste.ubuntu. com/23207571/
http://
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.