Merge ~ma-brothier/cloud-init:interfaces-order into cloud-init:master

Proposed by Marc-Aurele Brothier
Status: Merged
Approved by: Scott Moser
Approved revision: 0f705cce22f6028431b8593171369f98a98497c5
Merged at revision: 543e25cda6235d18adb6485e4266944d59e1979d
Proposed branch: ~ma-brothier/cloud-init:interfaces-order
Merge into: cloud-init:master
Diff against target: 63 lines (+26/-1)
2 files modified
cloudinit/net/__init__.py (+12/-1)
tests/unittests/test_net.py (+14/-0)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+324535@code.launchpad.net

Description of the change

Natural sort order of interfaces

The code deciding which interface to choose as the default to request the IP address through DHCP does not sort the interfaces correctly. On Ubuntu Xenial images for example, the interfaces are named ens1, ens2, ens3..., ens11, ... depending on the pci bus address. The python sorting will list 'ens11' before 'ens3' for example despite the fact that 'ens3' should be before 'ens11'.

This patch address this issue and sort the interface names according to a human sorting.

To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

Marc,

This looks good. Some comments... they're all minor, and should be explained more in HACKING.rst
http://cloudinit.readthedocs.io/en/latest/topics/hacking.html

a.) change description to be
    Summary
    <blank line>
    Description

b.) lets go ahead and just keep the function in a net/__init__.py rather than putting it in util. And name it _natural_sort_key(). At some point if someone else needs such a function we can move it to util.

c.) a unit test would be good. Just add something to tests/unittests/test_net.py to test your _natural_key_sort. Also realize you can (and do) get interfaces named 'enp0s25' (as I have on this system).

d.) You'll need to sign the contributors agreement (see HACKING.rst). Please feel free to ping me if you have any questions.

e.) Thanks!

d.) Thanks!

Revision history for this message
Scott Moser (smoser) :
review: Needs Fixing
Revision history for this message
Marc-Aurele Brothier (ma-brothier) wrote :

Thanks Scott for the comments.

a) done

b) change done

c) the tests don't run on my OSX, they got stuck in test_datasource/test_azure.py", line 333, in test_password_given. Can you give me a hint how to run only those unittests?

d) done

Dunno what's e) and f) but glad for the "thanks".
I must say the user experience on launchpad is quite an adventure to figure out things.

About the test: I created a virtualenv with py2.7 and installed the requirements. So far if I run this command I got this error:
python -m nose test_net.py
ERROR: Failure: ImportError (cannot import name renderers)

0f705cc... by Marc-Aurele Brothier

Add simple test for natural sorting

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

Marc,
to run the unit tests, you need to be in the top level directory (so that it can "import cloudinit").

so:
python -m nose tests/unittests/test_net.py

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

thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
index 8c6cd05..65accbb 100644
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -17,6 +17,17 @@ SYS_CLASS_NET = "/sys/class/net/"
17DEFAULT_PRIMARY_INTERFACE = 'eth0'17DEFAULT_PRIMARY_INTERFACE = 'eth0'
1818
1919
20def _natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
21 """Sorting for Humans: natural sort order. Can be use as the key to sort
22 functions.
23 This will sort ['eth0', 'ens3', 'ens10', 'ens12', 'ens8', 'ens0'] as
24 ['ens0', 'ens3', 'ens8', 'ens10', 'ens12', 'eth0'] instead of the simple
25 python way which will produce ['ens0', 'ens10', 'ens12', 'ens3', 'ens8',
26 'eth0']."""
27 return [int(text) if text.isdigit() else text.lower()
28 for text in re.split(_nsre, s)]
29
30
20def sys_dev_path(devname, path=""):31def sys_dev_path(devname, path=""):
21 return SYS_CLASS_NET + devname + "/" + path32 return SYS_CLASS_NET + devname + "/" + path
2233
@@ -169,7 +180,7 @@ def generate_fallback_config():
169180
170 # if eth0 exists use it above anything else, otherwise get the interface181 # if eth0 exists use it above anything else, otherwise get the interface
171 # that we can read 'first' (using the sorted defintion of first).182 # that we can read 'first' (using the sorted defintion of first).
172 names = list(sorted(potential_interfaces))183 names = list(sorted(potential_interfaces, key=_natural_sort_key))
173 if DEFAULT_PRIMARY_INTERFACE in names:184 if DEFAULT_PRIMARY_INTERFACE in names:
174 names.remove(DEFAULT_PRIMARY_INTERFACE)185 names.remove(DEFAULT_PRIMARY_INTERFACE)
175 names.insert(0, DEFAULT_PRIMARY_INTERFACE)186 names.insert(0, DEFAULT_PRIMARY_INTERFACE)
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 5169821..9993241 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1,6 +1,7 @@
1# This file is part of cloud-init. See LICENSE file for license information.1# This file is part of cloud-init. See LICENSE file for license information.
22
3from cloudinit import net3from cloudinit import net
4from cloudinit.net import _natural_sort_key
4from cloudinit.net import cmdline5from cloudinit.net import cmdline
5from cloudinit.net import eni6from cloudinit.net import eni
6from cloudinit.net import netplan7from cloudinit.net import netplan
@@ -1627,6 +1628,19 @@ class TestGetInterfacesByMac(CiTestCase):
1627 self.assertEqual('lo', ret[empty_mac])1628 self.assertEqual('lo', ret[empty_mac])
16281629
16291630
1631class TestInterfacesSorting(CiTestCase):
1632
1633 def test_natural_order(self):
1634 data = ['ens5', 'ens6', 'ens3', 'ens20', 'ens13', 'ens2']
1635 self.assertEqual(
1636 sorted(data, key=_natural_sort_key),
1637 ['ens2', 'ens3', 'ens5', 'ens6', 'ens13', 'ens20'])
1638 data2 = ['enp2s0', 'enp2s3', 'enp0s3', 'enp0s13', 'enp0s8', 'enp1s2']
1639 self.assertEqual(
1640 sorted(data2, key=_natural_sort_key),
1641 ['enp0s3', 'enp0s8', 'enp0s13', 'enp1s2', 'enp2s0', 'enp2s3'])
1642
1643
1630def _gzip_data(data):1644def _gzip_data(data):
1631 with io.BytesIO() as iobuf:1645 with io.BytesIO() as iobuf:
1632 gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf)1646 gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf)

Subscribers

People subscribed via source and target branches