oem: ensure a single kernel gets installed
Before running curthooks, we now look in the target if there is an installed kernel. If there is, we instruct curtin _not_ to install another one. Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
parent
b844119b79
commit
6424c3c52b
|
@ -1,6 +1,7 @@
|
|||
build-essential
|
||||
cloud-init
|
||||
curl
|
||||
dctrl-tools
|
||||
fuseiso
|
||||
gettext
|
||||
gir1.2-umockdev-1.0
|
||||
|
|
|
@ -110,6 +110,7 @@ parts:
|
|||
# This list includes the dependencies for curtin and probert as well,
|
||||
# there doesn't seem to be any real benefit to listing them separately.
|
||||
- cloud-init
|
||||
- dctrl-tools
|
||||
- iso-codes
|
||||
- libpython3-stdlib
|
||||
- libpython3.10-minimal
|
||||
|
|
|
@ -29,13 +29,23 @@ class KernelModel:
|
|||
# should be True.
|
||||
explicitly_requested: bool = False
|
||||
|
||||
def render(self):
|
||||
# If set to True, we won't request curthooks to install the kernel.
|
||||
# We can use this option if the kernel is already part of the source image
|
||||
# of if a kernel got installed using ubuntu-drivers.
|
||||
curthooks_no_install: bool = False
|
||||
|
||||
@property
|
||||
def needed_kernel(self) -> Optional[str]:
|
||||
if self.metapkg_name_override is not None:
|
||||
metapkg = self.metapkg_name_override
|
||||
else:
|
||||
metapkg = self.metapkg_name
|
||||
return self.metapkg_name_override
|
||||
return self.metapkg_name
|
||||
|
||||
def render(self):
|
||||
if self.curthooks_no_install:
|
||||
return {'kernel': None}
|
||||
|
||||
return {
|
||||
'kernel': {
|
||||
'package': metapkg,
|
||||
'package': self.needed_kernel,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ from subiquitycore.async_helpers import (
|
|||
)
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.file_util import write_file, generate_config_yaml
|
||||
from subiquitycore.utils import log_process_streams
|
||||
from subiquitycore.utils import arun_command, log_process_streams
|
||||
|
||||
from subiquity.common.errorreport import ErrorReportKind
|
||||
from subiquity.common.types import (
|
||||
|
@ -51,6 +51,9 @@ from subiquity.server.curtin import (
|
|||
run_curtin_command,
|
||||
start_curtin_command,
|
||||
)
|
||||
from subiquity.server.kernel import (
|
||||
list_installed_kernels,
|
||||
)
|
||||
from subiquity.server.mounter import (
|
||||
Mounter,
|
||||
)
|
||||
|
@ -312,6 +315,17 @@ class InstallController(SubiquityController):
|
|||
step_config=self.generic_config(),
|
||||
source=source,
|
||||
)
|
||||
if self.app.opts.dry_run:
|
||||
# In dry-run, extract does not do anything. Let's create what's
|
||||
# needed manually. Ideally, we would not hardcode
|
||||
# var/lib/dpkg/status because it is an implementation detail.
|
||||
status = "var/lib/dpkg/status"
|
||||
(root / status).parent.mkdir(parents=True, exist_ok=True)
|
||||
await arun_command([
|
||||
"cp", "-aT", "--",
|
||||
str(Path("/") / status),
|
||||
str(root / status),
|
||||
])
|
||||
await self.setup_target(context=context)
|
||||
|
||||
# For OEM, we basically mimic what ubuntu-drivers does:
|
||||
|
@ -343,6 +357,12 @@ class InstallController(SubiquityController):
|
|||
for pkg in self.model.oem.metapkgs:
|
||||
await self.install_package(package=pkg.name)
|
||||
|
||||
# If we already have a kernel installed, don't bother requesting
|
||||
# curthooks to install it again or we might end up with two
|
||||
# kernels.
|
||||
if await list_installed_kernels(Path(self.tpath())):
|
||||
self.model.kernel.curthooks_no_install = True
|
||||
|
||||
await run_curtin_step(
|
||||
name="curthooks", stages=["curthooks"],
|
||||
step_config=self.generic_config(),
|
||||
|
|
|
@ -13,7 +13,12 @@
|
|||
# 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/>.
|
||||
|
||||
import pathlib
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from subiquitycore.lsb_release import lsb_release
|
||||
from subiquitycore.utils import arun_command
|
||||
|
||||
|
||||
def flavor_to_pkgname(flavor: str, *, dry_run: bool) -> str:
|
||||
|
@ -27,3 +32,28 @@ def flavor_to_pkgname(flavor: str, *, dry_run: bool) -> str:
|
|||
# that's a bit tricky until we get cleverer about
|
||||
# the apt config in general.
|
||||
return f'linux-{flavor}-{release}'
|
||||
|
||||
|
||||
async def list_installed_kernels(rootfs: pathlib.Path) -> List[str]:
|
||||
''' Return the list of linux-image packages installed in rootfs. '''
|
||||
# TODO use python-apt instead coupled with rootdir.
|
||||
# Ideally, we should not hardcode var/lib/dpkg/status which is an
|
||||
# implementation detail.
|
||||
try:
|
||||
cp = await arun_command([
|
||||
'grep-status',
|
||||
'--whole-pkg',
|
||||
'-FProvides', 'linux-image', '--and', '-FStatus', 'installed',
|
||||
'--show-field=Package',
|
||||
'--no-field-names',
|
||||
str(rootfs / pathlib.Path('var/lib/dpkg/status')),
|
||||
], check=True)
|
||||
except subprocess.CalledProcessError as cpe:
|
||||
# grep-status exits with status 1 when there is no match.
|
||||
if cpe.returncode != 1:
|
||||
raise
|
||||
stdout = cpe.stdout
|
||||
else:
|
||||
stdout = cp.stdout
|
||||
|
||||
return [line for line in stdout.splitlines() if line]
|
||||
|
|
|
@ -13,9 +13,14 @@
|
|||
# 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/>.
|
||||
|
||||
import subprocess
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from subiquity.server.kernel import flavor_to_pkgname
|
||||
from subiquity.server.kernel import (
|
||||
flavor_to_pkgname,
|
||||
list_installed_kernels,
|
||||
)
|
||||
|
||||
|
||||
class TestFlavorToPkgname(unittest.TestCase):
|
||||
|
@ -33,3 +38,36 @@ class TestFlavorToPkgname(unittest.TestCase):
|
|||
|
||||
self.assertEqual('linux-generic-hwe-20.04',
|
||||
flavor_to_pkgname('generic-hwe', dry_run=True))
|
||||
|
||||
|
||||
class TestListInstalledKernels(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_one_kernel(self):
|
||||
rv = subprocess.CompletedProcess([], 0)
|
||||
rv.stdout = 'linux-image-6.1.0-16-generic'
|
||||
with mock.patch('subiquity.server.kernel.arun_command',
|
||||
return_value=rv) as mock_arun:
|
||||
ret = await list_installed_kernels('/')
|
||||
self.assertEqual(['linux-image-6.1.0-16-generic'], ret)
|
||||
mock_arun.assert_called_once()
|
||||
|
||||
async def test_two_kernels(self):
|
||||
rv = subprocess.CompletedProcess([], 0)
|
||||
rv.stdout = '''\
|
||||
linux-image-6.1.0-16-generic
|
||||
linux-image-6.2.0-24-generic
|
||||
'''
|
||||
with mock.patch('subiquity.server.kernel.arun_command',
|
||||
return_value=rv) as mock_arun:
|
||||
ret = await list_installed_kernels('/')
|
||||
self.assertEqual(['linux-image-6.1.0-16-generic',
|
||||
'linux-image-6.2.0-24-generic'], ret)
|
||||
mock_arun.assert_called_once()
|
||||
|
||||
async def test_no_kernel(self):
|
||||
rv = subprocess.CompletedProcess([], 0)
|
||||
rv.stdout = '\n'
|
||||
with mock.patch('subiquity.server.kernel.arun_command',
|
||||
return_value=rv) as mock_arun:
|
||||
ret = await list_installed_kernels('/')
|
||||
self.assertEqual([], ret)
|
||||
mock_arun.assert_called_once()
|
||||
|
|
Loading…
Reference in New Issue