ubuntu-drivers: add support for umockdev in dry-run

When running subiquity in dry-run mode with SUBIQUITY_DEBUG=run-drivers,
we now support using a YAML file describing the hardware in a
umockdev-compatible way. This allows to give some control on what
ubuntu-drivers list will reply.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
Olivier Gayot 2023-06-12 17:09:16 +02:00
parent 364e718c60
commit e56053f2bb
7 changed files with 159 additions and 1 deletions

View File

@ -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

View File

@ -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'

View File

@ -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:'

54
scripts/umockdev-wrapper.py Executable file
View File

@ -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()

View File

@ -13,7 +13,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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)

View File

@ -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")

View File

@ -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.