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
|
build-essential
|
||||||
cloud-init
|
cloud-init
|
||||||
curl
|
curl
|
||||||
|
dctrl-tools
|
||||||
fuseiso
|
fuseiso
|
||||||
gettext
|
gettext
|
||||||
gir1.2-umockdev-1.0
|
gir1.2-umockdev-1.0
|
||||||
|
|
|
@ -110,6 +110,7 @@ parts:
|
||||||
# This list includes the dependencies for curtin and probert as well,
|
# This list includes the dependencies for curtin and probert as well,
|
||||||
# there doesn't seem to be any real benefit to listing them separately.
|
# there doesn't seem to be any real benefit to listing them separately.
|
||||||
- cloud-init
|
- cloud-init
|
||||||
|
- dctrl-tools
|
||||||
- iso-codes
|
- iso-codes
|
||||||
- libpython3-stdlib
|
- libpython3-stdlib
|
||||||
- libpython3.10-minimal
|
- libpython3.10-minimal
|
||||||
|
|
|
@ -29,13 +29,23 @@ class KernelModel:
|
||||||
# should be True.
|
# should be True.
|
||||||
explicitly_requested: bool = False
|
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:
|
if self.metapkg_name_override is not None:
|
||||||
metapkg = self.metapkg_name_override
|
return self.metapkg_name_override
|
||||||
else:
|
return self.metapkg_name
|
||||||
metapkg = self.metapkg_name
|
|
||||||
|
def render(self):
|
||||||
|
if self.curthooks_no_install:
|
||||||
|
return {'kernel': None}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'kernel': {
|
'kernel': {
|
||||||
'package': metapkg,
|
'package': self.needed_kernel,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ from subiquitycore.async_helpers import (
|
||||||
)
|
)
|
||||||
from subiquitycore.context import with_context
|
from subiquitycore.context import with_context
|
||||||
from subiquitycore.file_util import write_file, generate_config_yaml
|
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.errorreport import ErrorReportKind
|
||||||
from subiquity.common.types import (
|
from subiquity.common.types import (
|
||||||
|
@ -51,6 +51,9 @@ from subiquity.server.curtin import (
|
||||||
run_curtin_command,
|
run_curtin_command,
|
||||||
start_curtin_command,
|
start_curtin_command,
|
||||||
)
|
)
|
||||||
|
from subiquity.server.kernel import (
|
||||||
|
list_installed_kernels,
|
||||||
|
)
|
||||||
from subiquity.server.mounter import (
|
from subiquity.server.mounter import (
|
||||||
Mounter,
|
Mounter,
|
||||||
)
|
)
|
||||||
|
@ -312,6 +315,17 @@ class InstallController(SubiquityController):
|
||||||
step_config=self.generic_config(),
|
step_config=self.generic_config(),
|
||||||
source=source,
|
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)
|
await self.setup_target(context=context)
|
||||||
|
|
||||||
# For OEM, we basically mimic what ubuntu-drivers does:
|
# For OEM, we basically mimic what ubuntu-drivers does:
|
||||||
|
@ -343,6 +357,12 @@ class InstallController(SubiquityController):
|
||||||
for pkg in self.model.oem.metapkgs:
|
for pkg in self.model.oem.metapkgs:
|
||||||
await self.install_package(package=pkg.name)
|
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(
|
await run_curtin_step(
|
||||||
name="curthooks", stages=["curthooks"],
|
name="curthooks", stages=["curthooks"],
|
||||||
step_config=self.generic_config(),
|
step_config=self.generic_config(),
|
||||||
|
|
|
@ -13,7 +13,12 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# 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.lsb_release import lsb_release
|
||||||
|
from subiquitycore.utils import arun_command
|
||||||
|
|
||||||
|
|
||||||
def flavor_to_pkgname(flavor: str, *, dry_run: bool) -> str:
|
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
|
# that's a bit tricky until we get cleverer about
|
||||||
# the apt config in general.
|
# the apt config in general.
|
||||||
return f'linux-{flavor}-{release}'
|
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
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import subprocess
|
||||||
import unittest
|
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):
|
class TestFlavorToPkgname(unittest.TestCase):
|
||||||
|
@ -33,3 +38,36 @@ class TestFlavorToPkgname(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual('linux-generic-hwe-20.04',
|
self.assertEqual('linux-generic-hwe-20.04',
|
||||||
flavor_to_pkgname('generic-hwe', dry_run=True))
|
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