Merge ~chad.smith/cloud-init:bug/1840080-ubuntu-drivers-emit-latelink-v2 into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- bug/1840080-ubuntu-drivers-emit-latelink-v2
- Merge into master
Status: | Merged |
---|---|
Approved by: | Dan Watkins |
Approved revision: | 2eb8eee7281a84a832f865a7ddd0edabe534e987 |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~chad.smith/cloud-init:bug/1840080-ubuntu-drivers-emit-latelink-v2 |
Merge into: | cloud-init:master |
Diff against target: |
310 lines (+130/-36) 3 files modified
cloudinit/config/cc_ubuntu_drivers.py (+48/-10) cloudinit/config/tests/test_ubuntu_drivers.py (+80/-25) cloudinit/tests/helpers.py (+2/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
cloud-init Commiters | Pending | ||
Review via email: mp+371546@code.launchpad.net |
Commit message
ubuntu-drivers: call db_x_loadtempla
Emit a script allowing cloud-init to set linux/nvidia/
debconf selection to true. This avoids having to call
debconf-
linux-restricte
Cloud-init loads this debconf template and sets the value to true in the
debconf database by sourcing debconf's /usr/share/
uses db_x_loadtempla
linux/nvidia/
LP: #1840080
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
Ryan Harper (raharper) wrote : | # |
some inline comments/
Chad Smith (chad.smith) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:71fcd42be63
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:48733aa42ac
https:/
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:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:cdd846983dc
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
FAILED: Ubuntu LTS: Integration
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Just validated on Eoan that this package properly sets up linux/nvidia/
The ubuntu-
As it is, this behavior paves the way for the ubuntu-
# cloud-init properly registers and sets linux/nvidia/
ubuntu@
cloud-init linux/nvidia/
cloud-init cloud-init/
# ubuntu-
dpkg -l | egrep 'cloud-
ii cloud-init 19.2-21-
ii cloud-initramfs
ii cloud-initramfs
ii libnvidia-
ii libnvidia-
ii nvidia-
ii nvidia-
ii nvidia-
ii ubuntu-
### If we manually install linux-modules-
ubuntu@
cloud-init linux/nvidia/
linux-modules-
# from dpkg -l on eoan-proposed
ii linux-modules-
Ryan Harper (raharper) wrote : | # |
Implementation looks great. One suggestion on the template description.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:cdd846983dc
https:/
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:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:8da37261c56
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:19dbf5e1449
https:/
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:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
FAILED: Checkout
Dan Watkins (oddbloke) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
FAILED: Checkout
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
FAILED: Checkout
Server Team CI bot (server-team-bot) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
Unapproved changes made after approval.
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Server Team CI bot (server-team-bot) : | # |
Preview Diff
1 | diff --git a/cloudinit/config/cc_ubuntu_drivers.py b/cloudinit/config/cc_ubuntu_drivers.py |
2 | index 4da34ee..297451d 100644 |
3 | --- a/cloudinit/config/cc_ubuntu_drivers.py |
4 | +++ b/cloudinit/config/cc_ubuntu_drivers.py |
5 | @@ -2,13 +2,14 @@ |
6 | |
7 | """Ubuntu Drivers: Interact with third party drivers in Ubuntu.""" |
8 | |
9 | +import os |
10 | from textwrap import dedent |
11 | |
12 | -from cloudinit.config import cc_apt_configure |
13 | from cloudinit.config.schema import ( |
14 | get_schema_doc, validate_cloudconfig_schema) |
15 | from cloudinit import log as logging |
16 | from cloudinit.settings import PER_INSTANCE |
17 | +from cloudinit import temp_utils |
18 | from cloudinit import type_utils |
19 | from cloudinit import util |
20 | |
21 | @@ -65,6 +66,33 @@ OLD_UBUNTU_DRIVERS_STDERR_NEEDLE = ( |
22 | __doc__ = get_schema_doc(schema) # Supplement python help() |
23 | |
24 | |
25 | +# Use a debconf template to configure a global debconf variable |
26 | +# (linux/nvidia/latelink) setting this to "true" allows the |
27 | +# 'linux-restricted-modules' deb to accept the NVIDIA EULA and the package |
28 | +# will automatically link the drivers to the running kernel. |
29 | + |
30 | +# EOL_XENIAL: can then drop this script and use python3-debconf which is only |
31 | +# available in Bionic and later. Can't use python3-debconf currently as it |
32 | +# isn't in Xenial and doesn't yet support X_LOADTEMPLATEFILE debconf command. |
33 | + |
34 | +NVIDIA_DEBCONF_CONTENT = """\ |
35 | +Template: linux/nvidia/latelink |
36 | +Type: boolean |
37 | +Default: true |
38 | +Description: Late-link NVIDIA kernel modules? |
39 | + Enable this to link the NVIDIA kernel modules in cloud-init and |
40 | + make them available for use. |
41 | +""" |
42 | + |
43 | +NVIDIA_DRIVER_LATELINK_DEBCONF_SCRIPT = """\ |
44 | +#!/bin/sh |
45 | +# Allow cloud-init to trigger EULA acceptance via registering a debconf |
46 | +# template to set linux/nvidia/latelink true |
47 | +. /usr/share/debconf/confmodule |
48 | +db_x_loadtemplatefile "$1" cloud-init |
49 | +""" |
50 | + |
51 | + |
52 | def install_drivers(cfg, pkg_install_func): |
53 | if not isinstance(cfg, dict): |
54 | raise TypeError( |
55 | @@ -90,17 +118,27 @@ def install_drivers(cfg, pkg_install_func): |
56 | if version_cfg: |
57 | driver_arg += ':{}'.format(version_cfg) |
58 | |
59 | - LOG.debug("Installing NVIDIA drivers (%s=%s, version=%s)", |
60 | + LOG.debug("Installing and activating NVIDIA drivers (%s=%s, version=%s)", |
61 | cfgpath, nv_acc, version_cfg if version_cfg else 'latest') |
62 | |
63 | - # Setting NVIDIA latelink confirms acceptance of EULA for the package |
64 | - # linux-restricted-modules |
65 | - # Reference code defining debconf variable is here |
66 | - # https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/ |
67 | - # linux-restricted-modules/+git/eoan/tree/debian/templates/ |
68 | - # nvidia.templates.in |
69 | - selections = b'linux-restricted-modules linux/nvidia/latelink boolean true' |
70 | - cc_apt_configure.debconf_set_selections(selections) |
71 | + # Register and set debconf selection linux/nvidia/latelink = true |
72 | + tdir = temp_utils.mkdtemp(needs_exe=True) |
73 | + debconf_file = os.path.join(tdir, 'nvidia.template') |
74 | + debconf_script = os.path.join(tdir, 'nvidia-debconf.sh') |
75 | + try: |
76 | + util.write_file(debconf_file, NVIDIA_DEBCONF_CONTENT) |
77 | + util.write_file( |
78 | + debconf_script, |
79 | + util.encode_text(NVIDIA_DRIVER_LATELINK_DEBCONF_SCRIPT), |
80 | + mode=0o755) |
81 | + util.subp([debconf_script, debconf_file]) |
82 | + except Exception as e: |
83 | + util.logexc( |
84 | + LOG, "Failed to register NVIDIA debconf template: %s", str(e)) |
85 | + raise |
86 | + finally: |
87 | + if os.path.isdir(tdir): |
88 | + util.del_dir(tdir) |
89 | |
90 | try: |
91 | util.subp(['ubuntu-drivers', 'install', '--gpgpu', driver_arg]) |
92 | diff --git a/cloudinit/config/tests/test_ubuntu_drivers.py b/cloudinit/config/tests/test_ubuntu_drivers.py |
93 | index 6a763bd..4695269 100644 |
94 | --- a/cloudinit/config/tests/test_ubuntu_drivers.py |
95 | +++ b/cloudinit/config/tests/test_ubuntu_drivers.py |
96 | @@ -1,6 +1,7 @@ |
97 | # This file is part of cloud-init. See LICENSE file for license information. |
98 | |
99 | import copy |
100 | +import os |
101 | |
102 | from cloudinit.tests.helpers import CiTestCase, skipUnlessJsonSchema, mock |
103 | from cloudinit.config.schema import ( |
104 | @@ -9,11 +10,27 @@ from cloudinit.config import cc_ubuntu_drivers as drivers |
105 | from cloudinit.util import ProcessExecutionError |
106 | |
107 | MPATH = "cloudinit.config.cc_ubuntu_drivers." |
108 | +M_TMP_PATH = MPATH + "temp_utils.mkdtemp" |
109 | OLD_UBUNTU_DRIVERS_ERROR_STDERR = ( |
110 | "ubuntu-drivers: error: argument <command>: invalid choice: 'install' " |
111 | "(choose from 'list', 'autoinstall', 'devices', 'debug')\n") |
112 | |
113 | |
114 | +class AnyTempScriptAndDebconfFile(object): |
115 | + |
116 | + def __init__(self, tmp_dir, debconf_file): |
117 | + self.tmp_dir = tmp_dir |
118 | + self.debconf_file = debconf_file |
119 | + |
120 | + def __eq__(self, cmd): |
121 | + if not len(cmd) == 2: |
122 | + return False |
123 | + script, debconf_file = cmd |
124 | + if bool(script.startswith(self.tmp_dir) and script.endswith('.sh')): |
125 | + return debconf_file == self.debconf_file |
126 | + return False |
127 | + |
128 | + |
129 | class TestUbuntuDrivers(CiTestCase): |
130 | cfg_accepted = {'drivers': {'nvidia': {'license-accepted': True}}} |
131 | install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'] |
132 | @@ -28,20 +45,23 @@ class TestUbuntuDrivers(CiTestCase): |
133 | {'drivers': {'nvidia': {'license-accepted': "TRUE"}}}, |
134 | schema=drivers.schema, strict=True) |
135 | |
136 | - @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections") |
137 | + @mock.patch(M_TMP_PATH) |
138 | @mock.patch(MPATH + "util.subp", return_value=('', '')) |
139 | @mock.patch(MPATH + "util.which", return_value=False) |
140 | def _assert_happy_path_taken( |
141 | - self, config, m_which, m_subp, m_debconf_set_selections): |
142 | + self, config, m_which, m_subp, m_tmp): |
143 | """Positive path test through handle. Package should be installed.""" |
144 | + tdir = self.tmp_dir() |
145 | + debconf_file = os.path.join(tdir, 'nvidia.template') |
146 | + m_tmp.return_value = tdir |
147 | myCloud = mock.MagicMock() |
148 | drivers.handle('ubuntu_drivers', config, myCloud, None, None) |
149 | self.assertEqual([mock.call(['ubuntu-drivers-common'])], |
150 | myCloud.distro.install_packages.call_args_list) |
151 | - self.assertEqual([mock.call(self.install_gpgpu)], |
152 | - m_subp.call_args_list) |
153 | - m_debconf_set_selections.assert_called_with( |
154 | - b'linux-restricted-modules linux/nvidia/latelink boolean true') |
155 | + self.assertEqual( |
156 | + [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), |
157 | + mock.call(self.install_gpgpu)], |
158 | + m_subp.call_args_list) |
159 | |
160 | def test_handle_does_package_install(self): |
161 | self._assert_happy_path_taken(self.cfg_accepted) |
162 | @@ -52,20 +72,33 @@ class TestUbuntuDrivers(CiTestCase): |
163 | new_config['drivers']['nvidia']['license-accepted'] = true_value |
164 | self._assert_happy_path_taken(new_config) |
165 | |
166 | - @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections") |
167 | - @mock.patch(MPATH + "util.subp", side_effect=ProcessExecutionError( |
168 | - stdout='No drivers found for installation.\n', exit_code=1)) |
169 | + @mock.patch(M_TMP_PATH) |
170 | + @mock.patch(MPATH + "util.subp") |
171 | @mock.patch(MPATH + "util.which", return_value=False) |
172 | - def test_handle_raises_error_if_no_drivers_found(self, m_which, m_subp, _): |
173 | + def test_handle_raises_error_if_no_drivers_found( |
174 | + self, m_which, m_subp, m_tmp): |
175 | """If ubuntu-drivers doesn't install any drivers, raise an error.""" |
176 | + tdir = self.tmp_dir() |
177 | + debconf_file = os.path.join(tdir, 'nvidia.template') |
178 | + m_tmp.return_value = tdir |
179 | myCloud = mock.MagicMock() |
180 | + |
181 | + def fake_subp(cmd): |
182 | + if cmd[0].startswith(tdir): |
183 | + return |
184 | + raise ProcessExecutionError( |
185 | + stdout='No drivers found for installation.\n', exit_code=1) |
186 | + m_subp.side_effect = fake_subp |
187 | + |
188 | with self.assertRaises(Exception): |
189 | drivers.handle( |
190 | 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None) |
191 | self.assertEqual([mock.call(['ubuntu-drivers-common'])], |
192 | myCloud.distro.install_packages.call_args_list) |
193 | - self.assertEqual([mock.call(self.install_gpgpu)], |
194 | - m_subp.call_args_list) |
195 | + self.assertEqual( |
196 | + [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), |
197 | + mock.call(self.install_gpgpu)], |
198 | + m_subp.call_args_list) |
199 | self.assertIn('ubuntu-drivers found no drivers for installation', |
200 | self.logs.getvalue()) |
201 | |
202 | @@ -113,19 +146,25 @@ class TestUbuntuDrivers(CiTestCase): |
203 | myLog.debug.call_args_list[0][0][0]) |
204 | self.assertEqual(0, m_install_drivers.call_count) |
205 | |
206 | - @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections") |
207 | + @mock.patch(M_TMP_PATH) |
208 | @mock.patch(MPATH + "util.subp", return_value=('', '')) |
209 | @mock.patch(MPATH + "util.which", return_value=True) |
210 | - def test_install_drivers_no_install_if_present(self, m_which, m_subp, _): |
211 | + def test_install_drivers_no_install_if_present( |
212 | + self, m_which, m_subp, m_tmp): |
213 | """If 'ubuntu-drivers' is present, no package install should occur.""" |
214 | + tdir = self.tmp_dir() |
215 | + debconf_file = os.path.join(tdir, 'nvidia.template') |
216 | + m_tmp.return_value = tdir |
217 | pkg_install = mock.MagicMock() |
218 | drivers.install_drivers(self.cfg_accepted['drivers'], |
219 | pkg_install_func=pkg_install) |
220 | self.assertEqual(0, pkg_install.call_count) |
221 | self.assertEqual([mock.call('ubuntu-drivers')], |
222 | m_which.call_args_list) |
223 | - self.assertEqual([mock.call(self.install_gpgpu)], |
224 | - m_subp.call_args_list) |
225 | + self.assertEqual( |
226 | + [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), |
227 | + mock.call(self.install_gpgpu)], |
228 | + m_subp.call_args_list) |
229 | |
230 | def test_install_drivers_rejects_invalid_config(self): |
231 | """install_drivers should raise TypeError if not given a config dict""" |
232 | @@ -134,21 +173,33 @@ class TestUbuntuDrivers(CiTestCase): |
233 | drivers.install_drivers("mystring", pkg_install_func=pkg_install) |
234 | self.assertEqual(0, pkg_install.call_count) |
235 | |
236 | - @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections") |
237 | - @mock.patch(MPATH + "util.subp", side_effect=ProcessExecutionError( |
238 | - stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2)) |
239 | + @mock.patch(M_TMP_PATH) |
240 | + @mock.patch(MPATH + "util.subp") |
241 | @mock.patch(MPATH + "util.which", return_value=False) |
242 | def test_install_drivers_handles_old_ubuntu_drivers_gracefully( |
243 | - self, m_which, m_subp, _): |
244 | + self, m_which, m_subp, m_tmp): |
245 | """Older ubuntu-drivers versions should emit message and raise error""" |
246 | + tdir = self.tmp_dir() |
247 | + debconf_file = os.path.join(tdir, 'nvidia.template') |
248 | + m_tmp.return_value = tdir |
249 | myCloud = mock.MagicMock() |
250 | + |
251 | + def fake_subp(cmd): |
252 | + if cmd[0].startswith(tdir): |
253 | + return |
254 | + raise ProcessExecutionError( |
255 | + stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2) |
256 | + m_subp.side_effect = fake_subp |
257 | + |
258 | with self.assertRaises(Exception): |
259 | drivers.handle( |
260 | 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None) |
261 | self.assertEqual([mock.call(['ubuntu-drivers-common'])], |
262 | myCloud.distro.install_packages.call_args_list) |
263 | - self.assertEqual([mock.call(self.install_gpgpu)], |
264 | - m_subp.call_args_list) |
265 | + self.assertEqual( |
266 | + [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), |
267 | + mock.call(self.install_gpgpu)], |
268 | + m_subp.call_args_list) |
269 | self.assertIn('WARNING: the available version of ubuntu-drivers is' |
270 | ' too old to perform requested driver installation', |
271 | self.logs.getvalue()) |
272 | @@ -160,17 +211,21 @@ class TestUbuntuDriversWithVersion(TestUbuntuDrivers): |
273 | 'drivers': {'nvidia': {'license-accepted': True, 'version': '123'}}} |
274 | install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia:123'] |
275 | |
276 | - @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections") |
277 | + @mock.patch(M_TMP_PATH) |
278 | @mock.patch(MPATH + "util.subp", return_value=('', '')) |
279 | @mock.patch(MPATH + "util.which", return_value=False) |
280 | - def test_version_none_uses_latest(self, m_which, m_subp, _): |
281 | + def test_version_none_uses_latest(self, m_which, m_subp, m_tmp): |
282 | + tdir = self.tmp_dir() |
283 | + debconf_file = os.path.join(tdir, 'nvidia.template') |
284 | + m_tmp.return_value = tdir |
285 | myCloud = mock.MagicMock() |
286 | version_none_cfg = { |
287 | 'drivers': {'nvidia': {'license-accepted': True, 'version': None}}} |
288 | drivers.handle( |
289 | 'ubuntu_drivers', version_none_cfg, myCloud, None, None) |
290 | self.assertEqual( |
291 | - [mock.call(['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'])], |
292 | + [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), |
293 | + mock.call(['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'])], |
294 | m_subp.call_args_list) |
295 | |
296 | def test_specifying_a_version_doesnt_override_license_acceptance(self): |
297 | diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py |
298 | index f41180f..23fddd0 100644 |
299 | --- a/cloudinit/tests/helpers.py |
300 | +++ b/cloudinit/tests/helpers.py |
301 | @@ -198,7 +198,8 @@ class CiTestCase(TestCase): |
302 | prefix="ci-%s." % self.__class__.__name__) |
303 | else: |
304 | tmpd = tempfile.mkdtemp(dir=dir) |
305 | - self.addCleanup(functools.partial(shutil.rmtree, tmpd)) |
306 | + self.addCleanup( |
307 | + functools.partial(shutil.rmtree, tmpd, ignore_errors=True)) |
308 | return tmpd |
309 | |
310 | def tmp_path(self, path, dir=None): |
FAILED: Continuous integration, rev:044f9d692eb ca82186b9159a69 e75816ad99f998 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1060/
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1060//rebuild
https:/