diff --git a/apt-deps.txt b/apt-deps.txt index b8df747b..733336d5 100644 --- a/apt-deps.txt +++ b/apt-deps.txt @@ -3,6 +3,7 @@ cloud-init curl fuseiso gettext +gir1.2-umockdev-1.0 git isolinux jq @@ -25,6 +26,7 @@ python3-coverage python3-dev python3-distutils-extra python3-flake8 +python3-gi python3-jsonschema python3-more-itertools python3-mypy @@ -45,4 +47,5 @@ python3-wheel python3-yaml ssh-import-id ubuntu-drivers-common +umockdev xorriso diff --git a/examples/umockdev/dell-certified+nvidia.yaml b/examples/umockdev/dell-certified+nvidia.yaml new file mode 100644 index 00000000..c22e0a3d --- /dev/null +++ b/examples/umockdev/dell-certified+nvidia.yaml @@ -0,0 +1,11 @@ +devices: + # This is the SMBus from the XPS 9230 + # 0000:00:1f.4 SMBus [0c05]: Intel Corporation Alder Lake PCH-P SMBus Host Controller [8086:51a3] (rev 01) + - modalias: 'pci:v00008086d000051A3sv00001028sd00000AF3bc0Csc05i00' + vendor: '0x8086' + device: '0x51A3' + # This is a fake NVIDIA GPU + - modalias: 'pci:v000010DEd00001E36sv0000deadbeefsd0000deadbeefbc03scdeadbeefideadbeef' + vendor: '0x10DE' + device: '0x1E36' + diff --git a/examples/umockdev/xps-9320.yaml b/examples/umockdev/xps-9320.yaml new file mode 100644 index 00000000..0cf21a7e --- /dev/null +++ b/examples/umockdev/xps-9320.yaml @@ -0,0 +1,32 @@ +devices: + # List was extracted on focal: + # == /sys/devices/pci0000:00/0000:00:14.0/usb3/3-9/3-9:1.0 == + # modalias : usb:v27C6p63BCd0100dcEFdsc00dp00icFFisc00ip00in00 + # vendor : Shenzhen Goodix Technology Co.,Ltd. + # driver : oem-fix-fpr-goodix-deletestoredfp - third-party free + # + # == /sys/devices/pci0000:00/0000:00:1f.4 == + # modalias : pci:v00008086d000051A3sv00001028sd00000AF3bc0Csc05i00 + # vendor : Intel Corporation + # driver : oem-somerville-tentacool-meta - third-party free + # + # == /sys/devices/pci0000:00/0000:00:05.0 == + # modalias : pci:v00008086d0000465Dsv00001028sd00000AF3bc04sc80i00 + # vendor : Intel Corporation + # driver : libcamhal-ipu6ep0 - third-party free + # driver : oem-fix-cam-intel-mipi-ipu6ep - third-party free + # + # == /sys/devices/virtual/dmi/id == + # modalias : dmi:bvnDellInc.:bvr2.2.1:bd04/17/2023:br2.2:svnDellInc.:pnXPS9320:pvr:rvnDellInc.:rn:rvr:cvnDellInc.:ct10:cvr:sku0AF3: + # driver : oem-release - third-party free + # driver : oem-somerville-meta - third-party free + - modalias: 'usb:v27C6p63BCd0100dcEFdsc00dp00icFFisc00ip00in00' + vendor: '0x27C6' + device: '0x63BC' + - modalias: 'pci:v00008086d000051A3sv00001028sd00000AF3bc0Csc05i00' + vendor: '0x8086' + device: '0x51A3' + - modalias: 'pci:v00008086d0000465Dsv00001028sd00000AF3bc04sc80i00' + vendor: '0x8086' + device: '0x465D' + - modalias: 'dmi:bvnDellInc.:bvr2.2.1:bd04/17/2023:br2.2:svnDellInc.:pnXPS9320:pvr:rvnDellInc.:rn:rvr:cvnDellInc.:ct10:cvr:sku0AF3:' diff --git a/scripts/umockdev-wrapper.py b/scripts/umockdev-wrapper.py new file mode 100755 index 00000000..c67a32a8 --- /dev/null +++ b/scripts/umockdev-wrapper.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +''' Read a YAML file passed as the --config argument and execute the command +supplied in a testbed configured with UMockdev. ''' + +import argparse +import os +from typing import Dict, TextIO + +import gi +import yaml + +try: + gi.require_version('UMockdev', '1.0') +except ValueError as exc: + raise RuntimeError('Package gir1.2-umockdev-1.0 is required') from exc + +from gi.repository import UMockdev + + +def main() -> None: + parser = argparse.ArgumentParser() + + parser.add_argument('--config', type=argparse.FileType(), required=True) + parser.add_argument('command', help='Command to execute') + parser.add_argument('args', nargs='*', + help='Command arguments') + + args = parser.parse_args() + + data = yaml.safe_load(args.config) + + testbed = UMockdev.Testbed.new() + + for idx, dev in enumerate(data['devices']): + subsystem = dev['modalias'].split(':', maxsplit=1)[0] + name = f'dev{idx}' + parent = None + attrs: List[str] = [] + properties: List[str] = [] + + # dev is a dict, but add_device expects a list [key1, value1, key2, + # value2, ...], a bit like a Perl's hash in LIST context. + for key, value in dev.items(): + attrs.extend([key, value]) + + testbed.add_device(subsystem, name, parent, attrs, properties) + + os.execvp('umockdev-wrapper', + ['umockdev-wrapper', args.command] + args.args) + + +if __name__ == '__main__': + main() diff --git a/subiquity/server/dryrun.py b/subiquity/server/dryrun.py index 4f86937f..20b3f485 100644 --- a/subiquity/server/dryrun.py +++ b/subiquity/server/dryrun.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from typing import List, TypedDict +from typing import List, Optional, TypedDict import yaml import attr @@ -77,6 +77,11 @@ class DRConfig: {"username": "sisyphus", "strategy": "failure"}, ] + # If running ubuntu-drivers on the host, supply a file to + # umockdev-wrapper.py + ubuntu_drivers_run_on_host_umockdev: Optional[str] = \ + "examples/umockdev/dell-certified+nvidia.yaml" + @classmethod def load(cls, stream): data = yaml.safe_load(stream) diff --git a/subiquity/server/tests/test_ubuntu_drivers.py b/subiquity/server/tests/test_ubuntu_drivers.py index a444d94f..19ffd687 100644 --- a/subiquity/server/tests/test_ubuntu_drivers.py +++ b/subiquity/server/tests/test_ubuntu_drivers.py @@ -19,6 +19,7 @@ from unittest.mock import patch, AsyncMock, Mock from subiquitycore.tests.mocks import make_app +from subiquity.server.dryrun import DRConfig from subiquity.server.ubuntu_drivers import ( UbuntuDriversInterface, UbuntuDriversClientInterface, @@ -158,9 +159,39 @@ oem-somerville-tentacool-meta class TestUbuntuDriversRunDriversInterface(unittest.IsolatedAsyncioTestCase): def setUp(self): self.app = make_app() + self.app.dr_cfg = DRConfig() + self.app.dr_cfg.ubuntu_drivers_run_on_host_umockdev = None self.ubuntu_drivers = UbuntuDriversRunDriversInterface( self.app, gpgpu=False) + def test_init_no_umockdev(self): + self.app.dr_cfg.ubuntu_drivers_run_on_host_umockdev = None + ubuntu_drivers = UbuntuDriversRunDriversInterface( + self.app, gpgpu=False) + self.assertEqual(ubuntu_drivers.list_oem_cmd, + ["ubuntu-drivers", "list-oem"]) + self.assertEqual(ubuntu_drivers.list_drivers_cmd[0:2], + ["ubuntu-drivers", "list"]) + self.assertEqual(ubuntu_drivers.install_drivers_cmd[0:2], + ["ubuntu-drivers", "install"]) + + def test_init_with_umockdev(self): + self.app.dr_cfg.ubuntu_drivers_run_on_host_umockdev = "/xps.yaml" + ubuntu_drivers = UbuntuDriversRunDriversInterface( + self.app, gpgpu=False) + self.assertEqual(ubuntu_drivers.list_oem_cmd, + ["scripts/umockdev-wrapper.py", + "--config", "/xps.yaml", "--", + "ubuntu-drivers", "list-oem"]) + self.assertEqual(ubuntu_drivers.list_drivers_cmd[0:6], + ["scripts/umockdev-wrapper.py", + "--config", "/xps.yaml", "--", + "ubuntu-drivers", "list"]) + self.assertEqual(ubuntu_drivers.install_drivers_cmd[0:6], + ["scripts/umockdev-wrapper.py", + "--config", "/xps.yaml", "--", + "ubuntu-drivers", "install"]) + @patch("subiquity.server.ubuntu_drivers.arun_command") async def test_ensure_cmd_exists(self, mock_arun_command): await self.ubuntu_drivers.ensure_cmd_exists("/target") diff --git a/subiquity/server/ubuntu_drivers.py b/subiquity/server/ubuntu_drivers.py index ed29c80c..a5ebda50 100644 --- a/subiquity/server/ubuntu_drivers.py +++ b/subiquity/server/ubuntu_drivers.py @@ -169,6 +169,28 @@ class UbuntuDriversNoDriversInterface(UbuntuDriversHasDriversInterface): class UbuntuDriversRunDriversInterface(UbuntuDriversInterface): """ A dry-run implementation of ubuntu-drivers that actually runs the ubuntu-drivers command but locally. """ + + def __init__(self, app, gpgpu: bool) -> None: + super().__init__(app, gpgpu) + + if app.dr_cfg.ubuntu_drivers_run_on_host_umockdev is None: + return + + self.list_oem_cmd = [ + "scripts/umockdev-wrapper.py", + "--config", app.dr_cfg.ubuntu_drivers_run_on_host_umockdev, + "--"] + self.list_oem_cmd + + self.list_drivers_cmd = [ + "scripts/umockdev-wrapper.py", + "--config", app.dr_cfg.ubuntu_drivers_run_on_host_umockdev, + "--"] + self.list_drivers_cmd + + self.install_drivers_cmd = [ + "scripts/umockdev-wrapper.py", + "--config", app.dr_cfg.ubuntu_drivers_run_on_host_umockdev, + "--"] + self.install_drivers_cmd + async def ensure_cmd_exists(self, root_dir: str) -> None: # TODO This does not tell us if the "--recommended" option is # available.