Merge ~oddbloke/cloud-init/+git/cloud-init:feature/driver-enablement into cloud-init:master
- Git
- lp:~oddbloke/cloud-init/+git/cloud-init
- feature/driver-enablement
- Merge into master
Status: | Merged |
---|---|
Approved by: | Dan Watkins |
Approved revision: | 0bdf1979bc30e45ddca71b8638e6b94e785bc302 |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~oddbloke/cloud-init/+git/cloud-init:feature/driver-enablement |
Merge into: | cloud-init:master |
Diff against target: |
362 lines (+306/-0) 6 files modified
cloudinit/config/cc_ubuntu_drivers.py (+112/-0) cloudinit/config/tests/test_ubuntu_drivers.py (+174/-0) cloudinit/util.py (+15/-0) config/cloud.cfg.tmpl (+3/-0) doc/rtd/topics/modules.rst (+1/-0) tests/unittests/test_handler/test_schema.py (+1/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Ryan Harper | Approve | ||
Scott Moser | Approve | ||
Review via email: mp+363992@code.launchpad.net |
Commit message
Add ubuntu_drivers config module
The ubuntu_drivers config module enables usage of the 'ubuntu-drivers'
command. At this point it only serves as a way of installing NVIDIA
drivers for general purpose graphics processing unit (GPGPU)
functionality.
Also, a small usability improvement to get_cfg_by_path to allow it to
take a string for the key path
"toplevel/
in addition to the original:
("toplevel", "second", "mykey")
Description of the change
Dan Watkins (oddbloke) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:b2515d0c1e9
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 : | # |
PASSED: Continuous integration, rev:b3f9b49af07
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 : | # |
PASSED: Continuous integration, rev:9646774cc3c
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:/
Ryan Harper (raharper) : | # |
Dan Watkins (oddbloke) : | # |
Scott Moser (smoser) : | # |
Scott Moser (smoser) wrote : | # |
i approve with your judgement on the log level applied.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:c9f9075bc85
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:/
- af13b69... by Dan Watkins
-
Modify log level per smoser's suggestion
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:af13b69f8d0
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:/
Ryan Harper (raharper) : | # |
- e8ec33e... by Dan Watkins
-
Add explanatory comment
Dan Watkins (oddbloke) wrote : | # |
On Mon, Mar 18, 2019 at 03:13:42PM -0000, Ryan Harper wrote:
> > + nv_acc = util.translate_
>
> Wasn't this going to be get_cfg_
get_cfg_option_bool doesn't support the path syntax we're using here.
(translate_bool is what get_cfg_option_bool uses itself.)
I've pushed up a comment explaning the use, but would be happy to make
more of a change if you would prefer.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:e8ec33e7d07
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:/
Ryan Harper (raharper) wrote : | # |
I missed that; I just saw the config/by/path. A couple in-line nits.
- 0bdf197... by Dan Watkins
-
Update/add test name and docstrings
Dan Watkins (oddbloke) wrote : | # |
On Mon, Mar 18, 2019 at 04:02:37PM -0000, Ryan Harper wrote:
> I missed that; I just saw the config/by/path. A couple in-line nits.
Nits addressed, I believe.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:0bdf1979bc3
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:/
Preview Diff
1 | diff --git a/cloudinit/config/cc_ubuntu_drivers.py b/cloudinit/config/cc_ubuntu_drivers.py |
2 | new file mode 100644 |
3 | index 0000000..91feb60 |
4 | --- /dev/null |
5 | +++ b/cloudinit/config/cc_ubuntu_drivers.py |
6 | @@ -0,0 +1,112 @@ |
7 | +# This file is part of cloud-init. See LICENSE file for license information. |
8 | + |
9 | +"""Ubuntu Drivers: Interact with third party drivers in Ubuntu.""" |
10 | + |
11 | +from textwrap import dedent |
12 | + |
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 type_utils |
18 | +from cloudinit import util |
19 | + |
20 | +LOG = logging.getLogger(__name__) |
21 | + |
22 | +frequency = PER_INSTANCE |
23 | +distros = ['ubuntu'] |
24 | +schema = { |
25 | + 'id': 'cc_ubuntu_drivers', |
26 | + 'name': 'Ubuntu Drivers', |
27 | + 'title': 'Interact with third party drivers in Ubuntu.', |
28 | + 'description': dedent("""\ |
29 | + This module interacts with the 'ubuntu-drivers' command to install |
30 | + third party driver packages."""), |
31 | + 'distros': distros, |
32 | + 'examples': [dedent("""\ |
33 | + drivers: |
34 | + nvidia: |
35 | + license-accepted: true |
36 | + """)], |
37 | + 'frequency': frequency, |
38 | + 'type': 'object', |
39 | + 'properties': { |
40 | + 'drivers': { |
41 | + 'type': 'object', |
42 | + 'additionalProperties': False, |
43 | + 'properties': { |
44 | + 'nvidia': { |
45 | + 'type': 'object', |
46 | + 'additionalProperties': False, |
47 | + 'required': ['license-accepted'], |
48 | + 'properties': { |
49 | + 'license-accepted': { |
50 | + 'type': 'boolean', |
51 | + 'description': ("Do you accept the NVIDIA driver" |
52 | + " license?"), |
53 | + }, |
54 | + 'version': { |
55 | + 'type': 'string', |
56 | + 'description': ( |
57 | + 'The version of the driver to install (e.g.' |
58 | + ' "390", "410"). Defaults to the latest' |
59 | + ' version.'), |
60 | + }, |
61 | + }, |
62 | + }, |
63 | + }, |
64 | + }, |
65 | + }, |
66 | +} |
67 | +OLD_UBUNTU_DRIVERS_STDERR_NEEDLE = ( |
68 | + "ubuntu-drivers: error: argument <command>: invalid choice: 'install'") |
69 | + |
70 | +__doc__ = get_schema_doc(schema) # Supplement python help() |
71 | + |
72 | + |
73 | +def install_drivers(cfg, pkg_install_func): |
74 | + if not isinstance(cfg, dict): |
75 | + raise TypeError( |
76 | + "'drivers' config expected dict, found '%s': %s" % |
77 | + (type_utils.obj_name(cfg), cfg)) |
78 | + |
79 | + cfgpath = 'nvidia/license-accepted' |
80 | + # Call translate_bool to ensure that we treat string values like "yes" as |
81 | + # acceptance and _don't_ treat string values like "nah" as acceptance |
82 | + # because they're True-ish |
83 | + nv_acc = util.translate_bool(util.get_cfg_by_path(cfg, cfgpath)) |
84 | + if not nv_acc: |
85 | + LOG.debug("Not installing NVIDIA drivers. %s=%s", cfgpath, nv_acc) |
86 | + return |
87 | + |
88 | + if not util.which('ubuntu-drivers'): |
89 | + LOG.debug("'ubuntu-drivers' command not available. " |
90 | + "Installing ubuntu-drivers-common") |
91 | + pkg_install_func(['ubuntu-drivers-common']) |
92 | + |
93 | + driver_arg = 'nvidia' |
94 | + version_cfg = util.get_cfg_by_path(cfg, 'nvidia/version') |
95 | + if version_cfg: |
96 | + driver_arg += ':{}'.format(version_cfg) |
97 | + |
98 | + LOG.debug("Installing NVIDIA drivers (%s=%s, version=%s)", |
99 | + cfgpath, nv_acc, version_cfg if version_cfg else 'latest') |
100 | + |
101 | + try: |
102 | + util.subp(['ubuntu-drivers', 'install', '--gpgpu', driver_arg]) |
103 | + except util.ProcessExecutionError as exc: |
104 | + if OLD_UBUNTU_DRIVERS_STDERR_NEEDLE in exc.stderr: |
105 | + LOG.warning('the available version of ubuntu-drivers is' |
106 | + ' too old to perform requested driver installation') |
107 | + elif 'No drivers found for installation.' in exc.stdout: |
108 | + LOG.warning('ubuntu-drivers found no drivers for installation') |
109 | + raise |
110 | + |
111 | + |
112 | +def handle(name, cfg, cloud, log, _args): |
113 | + if "drivers" not in cfg: |
114 | + log.debug("Skipping module named %s, no 'drivers' key in config", name) |
115 | + return |
116 | + |
117 | + validate_cloudconfig_schema(cfg, schema) |
118 | + install_drivers(cfg['drivers'], cloud.distro.install_packages) |
119 | diff --git a/cloudinit/config/tests/test_ubuntu_drivers.py b/cloudinit/config/tests/test_ubuntu_drivers.py |
120 | new file mode 100644 |
121 | index 0000000..efba4ce |
122 | --- /dev/null |
123 | +++ b/cloudinit/config/tests/test_ubuntu_drivers.py |
124 | @@ -0,0 +1,174 @@ |
125 | +# This file is part of cloud-init. See LICENSE file for license information. |
126 | + |
127 | +import copy |
128 | + |
129 | +from cloudinit.tests.helpers import CiTestCase, skipUnlessJsonSchema, mock |
130 | +from cloudinit.config.schema import ( |
131 | + SchemaValidationError, validate_cloudconfig_schema) |
132 | +from cloudinit.config import cc_ubuntu_drivers as drivers |
133 | +from cloudinit.util import ProcessExecutionError |
134 | + |
135 | +MPATH = "cloudinit.config.cc_ubuntu_drivers." |
136 | +OLD_UBUNTU_DRIVERS_ERROR_STDERR = ( |
137 | + "ubuntu-drivers: error: argument <command>: invalid choice: 'install' " |
138 | + "(choose from 'list', 'autoinstall', 'devices', 'debug')\n") |
139 | + |
140 | + |
141 | +class TestUbuntuDrivers(CiTestCase): |
142 | + cfg_accepted = {'drivers': {'nvidia': {'license-accepted': True}}} |
143 | + install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'] |
144 | + |
145 | + with_logs = True |
146 | + |
147 | + @skipUnlessJsonSchema() |
148 | + def test_schema_requires_boolean_for_license_accepted(self): |
149 | + with self.assertRaisesRegex( |
150 | + SchemaValidationError, ".*license-accepted.*TRUE.*boolean"): |
151 | + validate_cloudconfig_schema( |
152 | + {'drivers': {'nvidia': {'license-accepted': "TRUE"}}}, |
153 | + schema=drivers.schema, strict=True) |
154 | + |
155 | + @mock.patch(MPATH + "util.subp", return_value=('', '')) |
156 | + @mock.patch(MPATH + "util.which", return_value=False) |
157 | + def _assert_happy_path_taken(self, config, m_which, m_subp): |
158 | + """Positive path test through handle. Package should be installed.""" |
159 | + myCloud = mock.MagicMock() |
160 | + drivers.handle('ubuntu_drivers', config, myCloud, None, None) |
161 | + self.assertEqual([mock.call(['ubuntu-drivers-common'])], |
162 | + myCloud.distro.install_packages.call_args_list) |
163 | + self.assertEqual([mock.call(self.install_gpgpu)], |
164 | + m_subp.call_args_list) |
165 | + |
166 | + def test_handle_does_package_install(self): |
167 | + self._assert_happy_path_taken(self.cfg_accepted) |
168 | + |
169 | + def test_trueish_strings_are_considered_approval(self): |
170 | + for true_value in ['yes', 'true', 'on', '1']: |
171 | + new_config = copy.deepcopy(self.cfg_accepted) |
172 | + new_config['drivers']['nvidia']['license-accepted'] = true_value |
173 | + self._assert_happy_path_taken(new_config) |
174 | + |
175 | + @mock.patch(MPATH + "util.subp", side_effect=ProcessExecutionError( |
176 | + stdout='No drivers found for installation.\n', exit_code=1)) |
177 | + @mock.patch(MPATH + "util.which", return_value=False) |
178 | + def test_handle_raises_error_if_no_drivers_found(self, m_which, m_subp): |
179 | + """If ubuntu-drivers doesn't install any drivers, raise an error.""" |
180 | + myCloud = mock.MagicMock() |
181 | + with self.assertRaises(Exception): |
182 | + drivers.handle( |
183 | + 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None) |
184 | + self.assertEqual([mock.call(['ubuntu-drivers-common'])], |
185 | + myCloud.distro.install_packages.call_args_list) |
186 | + self.assertEqual([mock.call(self.install_gpgpu)], |
187 | + m_subp.call_args_list) |
188 | + self.assertIn('ubuntu-drivers found no drivers for installation', |
189 | + self.logs.getvalue()) |
190 | + |
191 | + @mock.patch(MPATH + "util.subp", return_value=('', '')) |
192 | + @mock.patch(MPATH + "util.which", return_value=False) |
193 | + def _assert_inert_with_config(self, config, m_which, m_subp): |
194 | + """Helper to reduce repetition when testing negative cases""" |
195 | + myCloud = mock.MagicMock() |
196 | + drivers.handle('ubuntu_drivers', config, myCloud, None, None) |
197 | + self.assertEqual(0, myCloud.distro.install_packages.call_count) |
198 | + self.assertEqual(0, m_subp.call_count) |
199 | + |
200 | + def test_handle_inert_if_license_not_accepted(self): |
201 | + """Ensure we don't do anything if the license is rejected.""" |
202 | + self._assert_inert_with_config( |
203 | + {'drivers': {'nvidia': {'license-accepted': False}}}) |
204 | + |
205 | + def test_handle_inert_if_garbage_in_license_field(self): |
206 | + """Ensure we don't do anything if unknown text is in license field.""" |
207 | + self._assert_inert_with_config( |
208 | + {'drivers': {'nvidia': {'license-accepted': 'garbage'}}}) |
209 | + |
210 | + def test_handle_inert_if_no_license_key(self): |
211 | + """Ensure we don't do anything if no license key.""" |
212 | + self._assert_inert_with_config({'drivers': {'nvidia': {}}}) |
213 | + |
214 | + def test_handle_inert_if_no_nvidia_key(self): |
215 | + """Ensure we don't do anything if other license accepted.""" |
216 | + self._assert_inert_with_config( |
217 | + {'drivers': {'acme': {'license-accepted': True}}}) |
218 | + |
219 | + def test_handle_inert_if_string_given(self): |
220 | + """Ensure we don't do anything if string refusal given.""" |
221 | + for false_value in ['no', 'false', 'off', '0']: |
222 | + self._assert_inert_with_config( |
223 | + {'drivers': {'nvidia': {'license-accepted': false_value}}}) |
224 | + |
225 | + @mock.patch(MPATH + "install_drivers") |
226 | + def test_handle_no_drivers_does_nothing(self, m_install_drivers): |
227 | + """If no 'drivers' key in the config, nothing should be done.""" |
228 | + myCloud = mock.MagicMock() |
229 | + myLog = mock.MagicMock() |
230 | + drivers.handle('ubuntu_drivers', {'foo': 'bzr'}, myCloud, myLog, None) |
231 | + self.assertIn('Skipping module named', |
232 | + myLog.debug.call_args_list[0][0][0]) |
233 | + self.assertEqual(0, m_install_drivers.call_count) |
234 | + |
235 | + @mock.patch(MPATH + "util.subp", return_value=('', '')) |
236 | + @mock.patch(MPATH + "util.which", return_value=True) |
237 | + def test_install_drivers_no_install_if_present(self, m_which, m_subp): |
238 | + """If 'ubuntu-drivers' is present, no package install should occur.""" |
239 | + pkg_install = mock.MagicMock() |
240 | + drivers.install_drivers(self.cfg_accepted['drivers'], |
241 | + pkg_install_func=pkg_install) |
242 | + self.assertEqual(0, pkg_install.call_count) |
243 | + self.assertEqual([mock.call('ubuntu-drivers')], |
244 | + m_which.call_args_list) |
245 | + self.assertEqual([mock.call(self.install_gpgpu)], |
246 | + m_subp.call_args_list) |
247 | + |
248 | + def test_install_drivers_rejects_invalid_config(self): |
249 | + """install_drivers should raise TypeError if not given a config dict""" |
250 | + pkg_install = mock.MagicMock() |
251 | + with self.assertRaisesRegex(TypeError, ".*expected dict.*"): |
252 | + drivers.install_drivers("mystring", pkg_install_func=pkg_install) |
253 | + self.assertEqual(0, pkg_install.call_count) |
254 | + |
255 | + @mock.patch(MPATH + "util.subp", side_effect=ProcessExecutionError( |
256 | + stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2)) |
257 | + @mock.patch(MPATH + "util.which", return_value=False) |
258 | + def test_install_drivers_handles_old_ubuntu_drivers_gracefully( |
259 | + self, m_which, m_subp): |
260 | + """Older ubuntu-drivers versions should emit message and raise error""" |
261 | + myCloud = mock.MagicMock() |
262 | + with self.assertRaises(Exception): |
263 | + drivers.handle( |
264 | + 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None) |
265 | + self.assertEqual([mock.call(['ubuntu-drivers-common'])], |
266 | + myCloud.distro.install_packages.call_args_list) |
267 | + self.assertEqual([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 | + |
273 | + |
274 | +# Sub-class TestUbuntuDrivers to run the same test cases, but with a version |
275 | +class TestUbuntuDriversWithVersion(TestUbuntuDrivers): |
276 | + cfg_accepted = { |
277 | + 'drivers': {'nvidia': {'license-accepted': True, 'version': '123'}}} |
278 | + install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia:123'] |
279 | + |
280 | + @mock.patch(MPATH + "util.subp", return_value=('', '')) |
281 | + @mock.patch(MPATH + "util.which", return_value=False) |
282 | + def test_version_none_uses_latest(self, m_which, m_subp): |
283 | + myCloud = mock.MagicMock() |
284 | + version_none_cfg = { |
285 | + 'drivers': {'nvidia': {'license-accepted': True, 'version': None}}} |
286 | + drivers.handle( |
287 | + 'ubuntu_drivers', version_none_cfg, myCloud, None, None) |
288 | + self.assertEqual( |
289 | + [mock.call(['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'])], |
290 | + m_subp.call_args_list) |
291 | + |
292 | + def test_specifying_a_version_doesnt_override_license_acceptance(self): |
293 | + self._assert_inert_with_config({ |
294 | + 'drivers': {'nvidia': {'license-accepted': False, |
295 | + 'version': '123'}} |
296 | + }) |
297 | + |
298 | +# vi: ts=4 expandtab |
299 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
300 | index a192091..385f231 100644 |
301 | --- a/cloudinit/util.py |
302 | +++ b/cloudinit/util.py |
303 | @@ -703,6 +703,21 @@ def get_cfg_option_list(yobj, key, default=None): |
304 | # get a cfg entry by its path array |
305 | # for f['a']['b']: get_cfg_by_path(mycfg,('a','b')) |
306 | def get_cfg_by_path(yobj, keyp, default=None): |
307 | + """Return the value of the item at path C{keyp} in C{yobj}. |
308 | + |
309 | + example: |
310 | + get_cfg_by_path({'a': {'b': {'num': 4}}}, 'a/b/num') == 4 |
311 | + get_cfg_by_path({'a': {'b': {'num': 4}}}, 'c/d') == None |
312 | + |
313 | + @param yobj: A dictionary. |
314 | + @param keyp: A path inside yobj. it can be a '/' delimited string, |
315 | + or an iterable. |
316 | + @param default: The default to return if the path does not exist. |
317 | + @return: The value of the item at keyp." |
318 | + is not found.""" |
319 | + |
320 | + if isinstance(keyp, six.string_types): |
321 | + keyp = keyp.split("/") |
322 | cur = yobj |
323 | for tok in keyp: |
324 | if tok not in cur: |
325 | diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl |
326 | index 7513176..25db43e 100644 |
327 | --- a/config/cloud.cfg.tmpl |
328 | +++ b/config/cloud.cfg.tmpl |
329 | @@ -112,6 +112,9 @@ cloud_final_modules: |
330 | - landscape |
331 | - lxd |
332 | {% endif %} |
333 | +{% if variant in ["ubuntu", "unknown"] %} |
334 | + - ubuntu-drivers |
335 | +{% endif %} |
336 | {% if variant not in ["freebsd"] %} |
337 | - puppet |
338 | - chef |
339 | diff --git a/doc/rtd/topics/modules.rst b/doc/rtd/topics/modules.rst |
340 | index d9720f6..3dcdd3b 100644 |
341 | --- a/doc/rtd/topics/modules.rst |
342 | +++ b/doc/rtd/topics/modules.rst |
343 | @@ -54,6 +54,7 @@ Modules |
344 | .. automodule:: cloudinit.config.cc_ssh_import_id |
345 | .. automodule:: cloudinit.config.cc_timezone |
346 | .. automodule:: cloudinit.config.cc_ubuntu_advantage |
347 | +.. automodule:: cloudinit.config.cc_ubuntu_drivers |
348 | .. automodule:: cloudinit.config.cc_update_etc_hosts |
349 | .. automodule:: cloudinit.config.cc_update_hostname |
350 | .. automodule:: cloudinit.config.cc_users_groups |
351 | diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py |
352 | index 1bad07f..e69a47a 100644 |
353 | --- a/tests/unittests/test_handler/test_schema.py |
354 | +++ b/tests/unittests/test_handler/test_schema.py |
355 | @@ -28,6 +28,7 @@ class GetSchemaTest(CiTestCase): |
356 | 'cc_runcmd', |
357 | 'cc_snap', |
358 | 'cc_ubuntu_advantage', |
359 | + 'cc_ubuntu_drivers', |
360 | 'cc_zypper_add_repo' |
361 | ], |
362 | [subschema['id'] for subschema in schema['allOf']]) |
I have tested this in a lxd container (without NVIDIA hardware available) and in a GCE instance with an NVIDIA GPGPU attached, and it behaves as expected.
(This doesn't include support for selecting versions, but I think it adds enough value without that to be worth landing by itself.)
One open question: this relies on ubuntu- drivers- common behaviour that is only present in disco currently (and is only intended to be SRU'd back as far as bionic); what should we do to avoid this running on Ubuntu systems where it can't possibly work?