Merge ~brtknr/cloud-init:infiniband-patch into cloud-init:master

Proposed by Bharat Kunwar
Status: Merged
Approved by: Scott Moser
Approved revision: 8814039d483603ac62421b339e9e522fe0724c23
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~brtknr/cloud-init:infiniband-patch
Merge into: cloud-init:master
Diff against target: 249 lines (+147/-1)
5 files modified
cloudinit/net/__init__.py (+38/-0)
cloudinit/net/network_state.py (+4/-0)
cloudinit/net/sysconfig.py (+14/-0)
cloudinit/sources/helpers/openstack.py (+11/-0)
tests/unittests/test_net.py (+80/-1)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Scott Moser Approve
Mark Goddard (community) Approve
Review via email: mp+353182@code.launchpad.net

Commit message

Add support for Infiniband network interfaces (IPoIB).

OpenStack ironic references Infiniband interfaces via a 6 byte 'MAC
address' formed from bytes 13-15 and 18-20 of interface's hardware
address. This address is used as the ethernet_mac_address of Infiniband
links in network_data.json in configdrives generated by OpenStack nova.
We can use this address to map links in network_data.json to their
corresponding interface names.

When generating interface configuration files, we need to use the
interface's full hardware address as the HWADDR, rather than the 6 byte
MAC address provided by network_data.json.

This change allows IB interfaces to be referenced in this dual mode - by
MAC address and hardware address, depending on the context.

Support TYPE=InfiniBand for sysconfig configuration of IB interfaces.

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

Hi,
Thank you for contributing to cloud-init.

To contribute, you must sign the Canonical Contributor License Agreement (CLA) [1].

If you have already signed it as an individual, your Launchpad user will be listed in the contributor-agreement-canonical launchpad group [2]. Unfortunately there is no easy way to check if an organization or company you are doing work for has signed. If you are unsure or have questions, email <email address hidden> or ping smoser in #cloud-init channel via freenode.

For information on how to sign, please see the HACKING document [3].

Thanks again, and please feel free to reach out with any questions.

Revision history for this message
Mark Goddard (mgoddard) wrote :

LGTM.

review: Approve
Revision history for this message
Bharat Kunwar (brtknr) wrote :

I’ve just signed it. Although, I put in <email address hidden> <mailto:<email address hidden>> instead of Scott Moser as Canonical Project Manager. Sorry.

> On 17 Aug 2018, at 15:24, Scott Moser <email address hidden> wrote:
>
> Hi,
> Thank you for contributing to cloud-init.
>
> To contribute, you must sign the Canonical Contributor License Agreement (CLA) [1].
>
> If you have already signed it as an individual, your Launchpad user will be listed in the contributor-agreement-canonical launchpad group [2]. Unfortunately there is no easy way to check if an organization or company you are doing work for has signed. If you are unsure or have questions, email <email address hidden> or ping smoser in #cloud-init channel via freenode.
>
> For information on how to sign, please see the HACKING document [3].
>
> Thanks again, and please feel free to reach out with any questions.
> --
> https://code.launchpad.net/~brtknr/cloud-init/+git/cloud-init/+merge/353182
> You are the owner of ~brtknr/cloud-init:infiniband-patch.

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

good enough on the CLA. thanks.

On Fri, Aug 17, 2018 at 10:36 AM Bharat Kunwar <email address hidden> wrote:

> I’ve just signed it. Although, I put in <email address hidden> <mailto:
> <email address hidden>> instead of Scott Moser as Canonical Project
> Manager. Sorry.
>
> > On 17 Aug 2018, at 15:24, Scott Moser <email address hidden> wrote:
> >
> > Hi,
> > Thank you for contributing to cloud-init.
> >
> > To contribute, you must sign the Canonical Contributor License Agreement
> (CLA) [1].
> >
> > If you have already signed it as an individual, your Launchpad user will
> be listed in the contributor-agreement-canonical launchpad group [2].
> Unfortunately there is no easy way to check if an organization or company
> you are doing work for has signed. If you are unsure or have questions,
> email <email address hidden> or ping smoser in #cloud-init channel via
> freenode.
> >
> > For information on how to sign, please see the HACKING document [3].
> >
> > Thanks again, and please feel free to reach out with any questions.
> > --
> >
> https://code.launchpad.net/~brtknr/cloud-init/+git/cloud-init/+merge/353182
> > You are the owner of ~brtknr/cloud-init:infiniband-patch.
>
>
> --
> https://code.launchpad.net/~brtknr/cloud-init/+git/cloud-init/+merge/353182
> You are subscribed to branch cloud-init:master.
>

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:6a3880f2b1922f04d8c3bc7a589ee0a9810a3f76
https://jenkins.ubuntu.com/server/job/cloud-init-ci/245/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/245/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

This only adds rendering for sysconfig, right?
We'd definitely like to have support for ENI rendering.
We'd need upstream netplan feature for netplan rendering, but I'd like to see a warning emitted should that path get hit.

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

@Mark,

Could you respond to the above?

Revision history for this message
Mark Goddard (mgoddard) wrote :

Hi Scott, sorry for not replying sooner. I spoke to Bharat about this yesterday, and adding ENI support is on his todo list. It seems that ENI config should be the same as for ethernet, but we'll need to make a small additional change to ensure that we handle the new infiniband interface type. I've not used netplan before, but we can look at adding a warning.

Revision history for this message
Bharat Kunwar (brtknr) wrote :

@smoser, any progress on this patch? To summarise our discussion from IRC, I tested this on Ubuntu to see if ENI would be rendered and it appears that it does since openstack helper obtains the fixed ipv4 address from ConfigDrive which ENI already knows how to handle.

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

2018-09-26 11:40:18 @smoser it is IPoIB right?
2018-09-26 11:40:31 @smoser sorry for being infiniband unaware.
2018-09-26 11:47:08 brtknr smoser: hmm i dont see any response. where exactly?
2018-09-26 11:48:10 @smoser brtknr: bah. sorry
2018-09-26 11:48:19 @smoser i didn't hit 'save'
2018-09-26 11:48:26 @smoser it configuers IPoIB right ?
2018-09-26 11:48:36 brtknr smoser: yes
2018-09-26 11:48:38 @smoser and in some cases... that could be problematic ?
2018-09-26 11:48:47 brtknr smoser: which cases?
2018-09-26 11:49:06 brtknr smoser: it only configures IPoIB if ConfigDrive is enabled
2018-09-26 11:49:19 brtknr smoser: as a data source
2018-09-26 11:49:25 @smoser i was under theimpression that enabling IPoIB might make be incompatible with other infiniband "modes"
2018-09-26 11:50:05 @smoser (for your info, it is config drive or openstack metadata i'm pretty sure at this point, both would work with your change)
2018-09-26 11:50:33 @smoser so what i'm thinking is possibly problematic
2018-09-26 11:51:04 @smoser a.) user had infiniband hardware on openstack cloud. they manually configured some infiniband function after deplyoing an instance.
2018-09-26 11:51:29 @smoser b.) cloud-init upgraade, new instances with new cloud-init would then cause their existing process to fail
2018-09-26 11:53:33 @smoser is that possible ?
2018-09-26 11:54:09 @smoser I'm *not* suggesting that makes the MP unacceptable. I'm just wanting to clarify if that is possible.
2018-09-26 11:54:33 brtknr smoser: users need to enable these kernel modules: ib_ipoib
2018-09-26 11:54:35 brtknr mlx5_ib
2018-09-26 11:54:42 brtknr in order for this to work
2018-09-26 11:55:06 brtknr therefore, it wouldnt automatically interfere with existing setup
2018-09-26 11:55:49 brtknr e.g. openstack cloud with infiniband hardware would need to have these modules enabled by default in the respective cloud images
2018-09-26 11:56:26 brtknr mgoddard also says it should only have an entry in the metadata/configdrive if there is a port for the IB interface, and the port has an IP address
2018-09-26 11:57:38 brtknr as far as im aware, ib_ipoib and mlx5_ib are not enabled by default on neither Centos nor Ubuntu

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

FAILED: Autolanding.
More details in the following jenkins job:
https://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/71/
Executed test runs:
    FAILED: Checkout

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

FAILED: Autolanding.
Unapproved changes made after approval.
https://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/72/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

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

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
2index 5e87bca..f83d368 100644
3--- a/cloudinit/net/__init__.py
4+++ b/cloudinit/net/__init__.py
5@@ -569,6 +569,20 @@ def get_interface_mac(ifname):
6 return read_sys_net_safe(ifname, path)
7
8
9+def get_ib_interface_hwaddr(ifname, ethernet_format):
10+ """Returns the string value of an Infiniband interface's hardware
11+ address. If ethernet_format is True, an Ethernet MAC-style 6 byte
12+ representation of the address will be returned.
13+ """
14+ # Type 32 is Infiniband.
15+ if read_sys_net_safe(ifname, 'type') == '32':
16+ mac = get_interface_mac(ifname)
17+ if mac and ethernet_format:
18+ # Use bytes 13-15 and 18-20 of the hardware address.
19+ mac = mac[36:-14] + mac[51:]
20+ return mac
21+
22+
23 def get_interfaces_by_mac():
24 """Build a dictionary of tuples {mac: name}.
25
26@@ -580,6 +594,15 @@ def get_interfaces_by_mac():
27 "duplicate mac found! both '%s' and '%s' have mac '%s'" %
28 (name, ret[mac], mac))
29 ret[mac] = name
30+ # Try to get an Infiniband hardware address (in 6 byte Ethernet format)
31+ # for the interface.
32+ ib_mac = get_ib_interface_hwaddr(name, True)
33+ if ib_mac:
34+ if ib_mac in ret:
35+ raise RuntimeError(
36+ "duplicate mac found! both '%s' and '%s' have mac '%s'" %
37+ (name, ret[ib_mac], ib_mac))
38+ ret[ib_mac] = name
39 return ret
40
41
42@@ -607,6 +630,21 @@ def get_interfaces():
43 return ret
44
45
46+def get_ib_hwaddrs_by_interface():
47+ """Build a dictionary mapping Infiniband interface names to their hardware
48+ address."""
49+ ret = {}
50+ for name, _, _, _ in get_interfaces():
51+ ib_mac = get_ib_interface_hwaddr(name, False)
52+ if ib_mac:
53+ if ib_mac in ret:
54+ raise RuntimeError(
55+ "duplicate mac found! both '%s' and '%s' have mac '%s'" %
56+ (name, ret[ib_mac], ib_mac))
57+ ret[name] = ib_mac
58+ return ret
59+
60+
61 class EphemeralIPv4Network(object):
62 """Context manager which sets up temporary static network configuration.
63
64diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
65index 72c803e..f76e508 100644
66--- a/cloudinit/net/network_state.py
67+++ b/cloudinit/net/network_state.py
68@@ -483,6 +483,10 @@ class NetworkStateInterpreter(object):
69
70 interfaces.update({iface['name']: iface})
71
72+ @ensure_command_keys(['name'])
73+ def handle_infiniband(self, command):
74+ self.handle_physical(command)
75+
76 @ensure_command_keys(['address'])
77 def handle_nameserver(self, command):
78 dns = self._network_state.get('dns')
79diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
80index 66e970e..9c16d3a 100644
81--- a/cloudinit/net/sysconfig.py
82+++ b/cloudinit/net/sysconfig.py
83@@ -174,6 +174,7 @@ class NetInterface(ConfigMap):
84 'ethernet': 'Ethernet',
85 'bond': 'Bond',
86 'bridge': 'Bridge',
87+ 'infiniband': 'InfiniBand',
88 }
89
90 def __init__(self, iface_name, base_sysconf_dir, templates,
91@@ -569,6 +570,18 @@ class Renderer(renderer.Renderer):
92 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
93
94 @classmethod
95+ def _render_ib_interfaces(cls, network_state, iface_contents):
96+ ib_filter = renderer.filter_by_type('infiniband')
97+ for iface in network_state.iter_interfaces(ib_filter):
98+ iface_name = iface['name']
99+ iface_cfg = iface_contents[iface_name]
100+ iface_cfg.kind = 'infiniband'
101+ iface_subnets = iface.get("subnets", [])
102+ route_cfg = iface_cfg.routes
103+ cls._render_subnets(iface_cfg, iface_subnets)
104+ cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
105+
106+ @classmethod
107 def _render_sysconfig(cls, base_sysconf_dir, network_state,
108 templates=None):
109 '''Given state, return /etc/sysconfig files + contents'''
110@@ -586,6 +599,7 @@ class Renderer(renderer.Renderer):
111 cls._render_bond_interfaces(network_state, iface_contents)
112 cls._render_vlan_interfaces(network_state, iface_contents)
113 cls._render_bridge_interfaces(network_state, iface_contents)
114+ cls._render_ib_interfaces(network_state, iface_contents)
115 contents = {}
116 for iface_name, iface_cfg in iface_contents.items():
117 if iface_cfg or iface_cfg.children:
118diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
119index 76a6e31..9c29cea 100644
120--- a/cloudinit/sources/helpers/openstack.py
121+++ b/cloudinit/sources/helpers/openstack.py
122@@ -675,6 +675,17 @@ def convert_net_json(network_json=None, known_macs=None):
123 else:
124 cfg[key] = fmt % link_id_info[target]['name']
125
126+ # Infiniband interfaces may be referenced in network_data.json by a 6 byte
127+ # Ethernet MAC-style address, and we use that address to look up the
128+ # interface name above. Now ensure that the hardware address is set to the
129+ # full 20 byte address.
130+ ib_known_hwaddrs = net.get_ib_hwaddrs_by_interface()
131+ if ib_known_hwaddrs:
132+ for cfg in config:
133+ if cfg['name'] in ib_known_hwaddrs:
134+ cfg['mac_address'] = ib_known_hwaddrs[cfg['name']]
135+ cfg['type'] = 'infiniband'
136+
137 for service in services:
138 cfg = service
139 cfg.update({'type': 'nameserver'})
140diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
141index f3165da..5d9c7d9 100644
142--- a/tests/unittests/test_net.py
143+++ b/tests/unittests/test_net.py
144@@ -3260,11 +3260,15 @@ class TestGetInterfacesByMac(CiTestCase):
145 def _se_interface_has_own_mac(self, name):
146 return name in self.data['own_macs']
147
148+ def _se_get_ib_interface_hwaddr(self, name, ethernet_format):
149+ ib_hwaddr = self.data.get('ib_hwaddr', {})
150+ return ib_hwaddr.get(name, {}).get(ethernet_format)
151+
152 def _mock_setup(self):
153 self.data = copy.deepcopy(self._data)
154 self.data['devices'] = set(list(self.data['macs'].keys()))
155 mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge',
156- 'interface_has_own_mac', 'is_vlan')
157+ 'interface_has_own_mac', 'is_vlan', 'get_ib_interface_hwaddr')
158 self.mocks = {}
159 for n in mocks:
160 m = mock.patch('cloudinit.net.' + n,
161@@ -3338,6 +3342,20 @@ class TestGetInterfacesByMac(CiTestCase):
162 ret = net.get_interfaces_by_mac()
163 self.assertEqual('lo', ret[empty_mac])
164
165+ def test_ib(self):
166+ ib_addr = '80:00:00:28:fe:80:00:00:00:00:00:00:00:11:22:03:00:33:44:56'
167+ ib_addr_eth_format = '00:11:22:33:44:56'
168+ self._mock_setup()
169+ self.data['devices'] = ['enp0s1', 'ib0']
170+ self.data['own_macs'].append('ib0')
171+ self.data['macs']['ib0'] = ib_addr
172+ self.data['ib_hwaddr'] = {'ib0': {True: ib_addr_eth_format,
173+ False: ib_addr}}
174+ result = net.get_interfaces_by_mac()
175+ expected = {'aa:aa:aa:aa:aa:01': 'enp0s1',
176+ ib_addr_eth_format: 'ib0', ib_addr: 'ib0'}
177+ self.assertEqual(expected, result)
178+
179
180 class TestInterfacesSorting(CiTestCase):
181
182@@ -3352,6 +3370,67 @@ class TestInterfacesSorting(CiTestCase):
183 ['enp0s3', 'enp0s8', 'enp0s13', 'enp1s2', 'enp2s0', 'enp2s3'])
184
185
186+class TestGetIBHwaddrsByInterface(CiTestCase):
187+
188+ _ib_addr = '80:00:00:28:fe:80:00:00:00:00:00:00:00:11:22:03:00:33:44:56'
189+ _ib_addr_eth_format = '00:11:22:33:44:56'
190+ _data = {'devices': ['enp0s1', 'enp0s2', 'bond1', 'bridge1',
191+ 'bridge1-nic', 'tun0', 'ib0'],
192+ 'bonds': ['bond1'],
193+ 'bridges': ['bridge1'],
194+ 'own_macs': ['enp0s1', 'enp0s2', 'bridge1-nic', 'bridge1', 'ib0'],
195+ 'macs': {'enp0s1': 'aa:aa:aa:aa:aa:01',
196+ 'enp0s2': 'aa:aa:aa:aa:aa:02',
197+ 'bond1': 'aa:aa:aa:aa:aa:01',
198+ 'bridge1': 'aa:aa:aa:aa:aa:03',
199+ 'bridge1-nic': 'aa:aa:aa:aa:aa:03',
200+ 'tun0': None,
201+ 'ib0': _ib_addr},
202+ 'ib_hwaddr': {'ib0': {True: _ib_addr_eth_format,
203+ False: _ib_addr}}}
204+ data = {}
205+
206+ def _mock_setup(self):
207+ self.data = copy.deepcopy(self._data)
208+ mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge',
209+ 'interface_has_own_mac', 'get_ib_interface_hwaddr')
210+ self.mocks = {}
211+ for n in mocks:
212+ m = mock.patch('cloudinit.net.' + n,
213+ side_effect=getattr(self, '_se_' + n))
214+ self.addCleanup(m.stop)
215+ self.mocks[n] = m.start()
216+
217+ def _se_get_devicelist(self):
218+ return self.data['devices']
219+
220+ def _se_get_interface_mac(self, name):
221+ return self.data['macs'][name]
222+
223+ def _se_is_bridge(self, name):
224+ return name in self.data['bridges']
225+
226+ def _se_interface_has_own_mac(self, name):
227+ return name in self.data['own_macs']
228+
229+ def _se_get_ib_interface_hwaddr(self, name, ethernet_format):
230+ ib_hwaddr = self.data.get('ib_hwaddr', {})
231+ return ib_hwaddr.get(name, {}).get(ethernet_format)
232+
233+ def test_ethernet(self):
234+ self._mock_setup()
235+ self.data['devices'].remove('ib0')
236+ result = net.get_ib_hwaddrs_by_interface()
237+ expected = {}
238+ self.assertEqual(expected, result)
239+
240+ def test_ib(self):
241+ self._mock_setup()
242+ result = net.get_ib_hwaddrs_by_interface()
243+ expected = {'ib0': self._ib_addr}
244+ self.assertEqual(expected, result)
245+
246+
247 def _gzip_data(data):
248 with io.BytesIO() as iobuf:
249 gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf)

Subscribers

People subscribed via source and target branches