Skip to content

Commit

Permalink
filesystem: permit match spec to be a list
Browse files Browse the repository at this point in the history
(cherry picked from commit 70970b3)
  • Loading branch information
dbungert committed Jul 5, 2024
1 parent b12d1ed commit cf50828
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 9 deletions.
12 changes: 12 additions & 0 deletions doc/reference/autoinstall-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,18 @@ To match a Seagate drive:
match:
model: Seagate
As of Subiquity 24.08.1, match specs may optionally be specified in an ordered
list, and will use the first match spec that matches one or more unused disks:

.. code-block:: yaml
# attempt first to match by serial, then by path
- type: disk
id: data-disk
match:
- serial: Foodisk_1TB_ABC123_1
- path: /dev/nvme0n1
Partition/logical volume extensions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
22 changes: 15 additions & 7 deletions subiquity/models/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1660,14 +1660,22 @@ def _filtered_matches(self, disks: Sequence[_Device], match: dict):
matchers = self._make_matchers(match)
return [disk for disk in disks if all(match_fn(disk) for match_fn in matchers)]

def disk_for_match(self, disks, match):
def disk_for_match(
self, disks: Sequence[_Device], match: dict | Sequence[dict]
) -> _Device:
# a match directive is a dict, or a list of dicts, that specify
# * zero or more keys to filter on
# * an optional sort on size
log.info(f"considering {disks} for {match}")
candidates = self._filtered_matches(disks, match)
candidates = self._sorted_matches(candidates, match)
if candidates:
log.info(f"For match {match}, using the first candidate from {candidates}")
return candidates[0]
log.info(f"For match {match}, no devices match")
if isinstance(match, dict):
match = [match]
for m in match:
candidates = self._filtered_matches(disks, m)
candidates = self._sorted_matches(candidates, m)
if candidates:
log.info(f"For match {m}, using the first candidate from {candidates}")
return candidates[0]
log.info(f"No devices satisfy criteria {match}")
return None

def assign_omitted_offsets(self):
Expand Down
22 changes: 22 additions & 0 deletions subiquity/models/tests/test_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1834,3 +1834,25 @@ def test_matcher_install_media(self):
fake_up_blockdata(m)
actual = m.disk_for_match([iso, disk], {"install-media": True})
self.assertEqual(iso, actual)

def test_match_from_list_first(self):
m = make_model()
vda = make_disk(m, path="/dev/vda", serial="s1")
vdb = make_disk(m, path="/dev/vdb", serial="s2")
fake_up_blockdata(m)
match = [
{"serial": "s1"},
{"path": "/dev/vdb"},
]
self.assertEqual(vda, m.disk_for_match([vda, vdb], match))

def test_match_from_list_second(self):
m = make_model()
vda = make_disk(m, path="/dev/vda", serial="s1")
vdb = make_disk(m, path="/dev/vdb", serial="s2")
fake_up_blockdata(m)
match = [
{"serial": "not-found"},
{"path": "/dev/vdb"},
]
self.assertEqual(vdb, m.disk_for_match([vda, vdb], match))
6 changes: 4 additions & 2 deletions subiquity/server/controllers/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import pathlib
import subprocess
import time
from typing import Any, Callable, Dict, List, Optional, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Union

import attr
import pyudev
Expand Down Expand Up @@ -1341,7 +1341,9 @@ async def _probe(self, *, context=None):
self.start_monitor()
break

def get_bootable_matching_disk(self, match: dict[str, str]):
def get_bootable_matching_disk(
self, match: dict[str, str] | Sequence[dict[str, str]]
):
"""given a match directive, find disks or disk-like devices for which
we have a plan to boot, and return the best matching one of those.
As match directives are autoinstall-supplied, raise AutoinstallError if
Expand Down

0 comments on commit cf50828

Please sign in to comment.