Skip to content

Commit 3a8f0c5

Browse files
committed
Refactor LiveOS installation to reuse existing mount point
Eliminate duplicate mounting by reusing /run/rootfsbase instead of creating /run/install/sources/mount-0000-live_os_image. This simplifies the code and removes redundant filesystem operations during installation. The original implementation created a separate mount point to follow the same pattern as other sources (CDROM, NFS, HDD). However, LiveOS is a special case where /run/rootfsbase is already mounted by the live system and contains the same content. Creating a duplicate mount is redundant. Relevant: rhbz#2413622
1 parent 33f7cb2 commit 3a8f0c5

File tree

4 files changed

+65
-180
lines changed

4 files changed

+65
-180
lines changed

pyanaconda/modules/payloads/source/live_os/initialization.py

Lines changed: 9 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,18 @@
1919
import stat
2020
from collections import namedtuple
2121

22-
import blivet.util
2322
from blivet.size import Size
2423

2524
from pyanaconda.anaconda_loggers import get_module_logger
2625
from pyanaconda.core.util import execWithCapture
27-
from pyanaconda.modules.common.constants.objects import DEVICE_TREE
28-
from pyanaconda.modules.common.constants.services import STORAGE
2926
from pyanaconda.modules.common.errors.payload import SourceSetupError
30-
from pyanaconda.modules.common.structures.storage import DeviceData
3127
from pyanaconda.modules.common.task import Task
32-
from pyanaconda.modules.payloads.source.mount_tasks import SetUpMountTask
3328

3429
log = get_module_logger(__name__)
3530

31+
# Mount point for the Live OS rootfsbase (mounted by the live system)
32+
LIVE_OS_ROOTFSBASE_MOUNT = "/run/rootfsbase"
33+
3634
SetupLiveOSResult = namedtuple("SetupLiveOSResult", ["required_space"])
3735

3836

@@ -53,7 +51,7 @@ def run(self):
5351
block_device = \
5452
self._check_block_device("/dev/mapper/live-base") or \
5553
self._check_block_device("/dev/mapper/live-osimg-min") or \
56-
self._check_mount_point("/run/rootfsbase")
54+
self._check_mount_point(LIVE_OS_ROOTFSBASE_MOUNT)
5755

5856
if not block_device:
5957
raise SourceSetupError("No Live OS image found!")
@@ -89,22 +87,19 @@ def _check_mount_point(self, mount_point):
8987
return None
9088

9189

92-
class SetUpLiveOSSourceTask(SetUpMountTask):
90+
class SetUpLiveOSSourceTask(Task):
9391
"""Task to set up a Live OS image."""
9492

95-
def __init__(self, image_path, target_mount):
93+
def __init__(self, target_mount):
9694
"""Create a new task.
9795
98-
:param image_path: a path to a Live OS image
9996
:param target_mount: a path to a mount point
10097
"""
101-
super().__init__(target_mount)
102-
self._image_path = image_path
98+
super().__init__()
99+
self._target_mount = target_mount
103100

104101
def run(self):
105102
"""Run the task."""
106-
super().run()
107-
108103
required_space = self._calculate_required_space()
109104
return SetupLiveOSResult(required_space=required_space)
110105

@@ -132,7 +127,7 @@ def _calculate_required_space(self):
132127
du_cmd_args.extend(["--exclude", f"{self._target_mount}{pattern}"])
133128

134129
try:
135-
# Execute the `du` command
130+
# Execute the `du` command on the existing mount point
136131
result = execWithCapture("du", du_cmd_args)
137132
# Parse the output for the total size
138133
required_space = result.split()[0] # First column is the total
@@ -144,50 +139,3 @@ def _calculate_required_space(self):
144139
@property
145140
def name(self):
146141
return "Set up a Live OS image"
147-
148-
def _do_mount(self):
149-
"""Run live installation source setup.
150-
151-
Mount the live device and copy from it instead of the overlay at /.
152-
"""
153-
device_path = self._get_device_path()
154-
self._mount_device(device_path)
155-
156-
def _get_device_path(self):
157-
"""Get a device path of the block device."""
158-
log.debug("Resolving %s.", self._image_path)
159-
device_tree = STORAGE.get_proxy(DEVICE_TREE)
160-
161-
# Get the device name.
162-
device_id = device_tree.ResolveDevice(self._image_path)
163-
164-
if not device_id:
165-
raise SourceSetupError("Failed to resolve the Live OS image.")
166-
167-
# Get the device path.
168-
device_data = DeviceData.from_structure(
169-
device_tree.GetDeviceData(device_id)
170-
)
171-
device_path = device_data.path
172-
173-
if not stat.S_ISBLK(os.stat(device_path)[stat.ST_MODE]):
174-
raise SourceSetupError("{} is not a valid block device.".format(device_path))
175-
176-
return device_path
177-
178-
def _mount_device(self, device_path):
179-
"""Mount the specified device."""
180-
log.debug("Mounting %s at %s.", device_path, self._target_mount)
181-
182-
try:
183-
rc = blivet.util.mount(
184-
device_path,
185-
self._target_mount,
186-
fstype="auto",
187-
options="ro"
188-
)
189-
except OSError as e:
190-
raise SourceSetupError(str(e)) from e
191-
192-
if rc != 0:
193-
raise SourceSetupError("Failed to mount the Live OS image.")

pyanaconda/modules/payloads/source/live_os/live_os.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,21 @@
2323
from pyanaconda.modules.payloads.constants import SourceState, SourceType
2424
from pyanaconda.modules.payloads.source.live_os.initialization import (
2525
DetectLiveOSImageTask,
26+
LIVE_OS_ROOTFSBASE_MOUNT,
2627
SetupLiveOSResult,
2728
SetUpLiveOSSourceTask,
2829
)
2930
from pyanaconda.modules.payloads.source.live_os.live_os_interface import (
3031
LiveOSSourceInterface,
3132
)
32-
from pyanaconda.modules.payloads.source.mount_tasks import TearDownMountTask
3333
from pyanaconda.modules.payloads.source.source_base import (
34-
MountingSourceMixin,
3534
PayloadSourceBase,
3635
)
3736

3837
log = get_module_logger(__name__)
3938

4039

41-
class LiveOSSourceModule(PayloadSourceBase, MountingSourceMixin):
40+
class LiveOSSourceModule(PayloadSourceBase):
4241
"""The Live OS source payload module."""
4342

4443
def __init__(self):
@@ -78,6 +77,18 @@ def required_space(self):
7877
"""
7978
return self._required_space
8079

80+
@property
81+
def mount_point(self):
82+
"""Where the Live OS source is mounted.
83+
84+
The Live OS source reuses the existing /run/rootfsbase mount point
85+
that is created by the live system.
86+
87+
:return: path to the mount point
88+
:rtype: str
89+
"""
90+
return LIVE_OS_ROOTFSBASE_MOUNT
91+
8192
@property
8293
def image_path(self):
8394
"""Path to the Live OS image.
@@ -113,7 +124,9 @@ def detect_image_with_task(self):
113124

114125
def get_state(self):
115126
"""Get state of this source."""
116-
return SourceState.from_bool(self.get_mount_state())
127+
# Since we're using an existing mount and don't have a traditional mount state,
128+
# return NOT_APPLICABLE for consistency with other sources like live_image
129+
return SourceState.NOT_APPLICABLE
117130

118131
def set_up_with_tasks(self):
119132
"""Set up the installation source for installation.
@@ -122,7 +135,6 @@ def set_up_with_tasks(self):
122135
:rtype: [Task]
123136
"""
124137
task = SetUpLiveOSSourceTask(
125-
image_path=self.image_path,
126138
target_mount=self.mount_point
127139
)
128140

@@ -136,14 +148,13 @@ def _handle_live_os_task_result(self, result: SetupLiveOSResult):
136148
def tear_down_with_tasks(self):
137149
"""Tear down the installation source.
138150
139-
:return: list of tasks required for the source clean-up
140-
:rtype: [TearDownMountTask]
151+
Note: We don't tear down /run/rootfsbase since it's mounted by the live system
152+
and unmounting it would crash the system.
153+
154+
:return: empty list (no tear-down tasks needed)
155+
:rtype: []
141156
"""
142-
return [
143-
TearDownMountTask(
144-
target_mount=self.mount_point
145-
)
146-
]
157+
return [] # No tear-down tasks needed
147158

148159
def __repr__(self):
149160
"""Return a string representation of the source."""

tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_module_payload_live_os.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
import os
2121
import tempfile
2222
import unittest
23+
from unittest.mock import patch
2324

2425
from pyanaconda.core.constants import PAYLOAD_TYPE_LIVE_OS, SOURCE_TYPE_LIVE_OS_IMAGE
25-
from pyanaconda.core.path import join_paths, make_directories, touch
26+
from pyanaconda.core.path import join_paths, make_directories
2627
from pyanaconda.modules.common.errors.payload import IncompatibleSourceError
2728
from pyanaconda.modules.payloads.constants import SourceState, SourceType
2829
from pyanaconda.modules.payloads.payload.live_image.installation import (
@@ -91,21 +92,19 @@ def _create_source(self, state=SourceState.READY):
9192
"""Create a new source with a mocked state."""
9293
return PayloadSharedTest.prepare_source(SourceType.LIVE_OS_IMAGE, state)
9394

94-
def test_get_kernel_version_list(self):
95+
@patch("pyanaconda.modules.payloads.payload.live_os.live_os.get_kernel_version_list")
96+
def test_get_kernel_version_list(self, get_kernel_mock):
9597
"""Test the get_kernel_version_list method."""
96-
with tempfile.TemporaryDirectory() as tmp:
97-
# Create the image source.
98-
image_source = self._create_source()
99-
image_source._mount_point = tmp
98+
get_kernel_mock.return_value = ["1.2-3.x86_64"]
10099

101-
# Create a fake kernel file.
102-
os.makedirs(join_paths(tmp, "boot"))
103-
kernel_file = join_paths(tmp, "boot", "vmlinuz-1.2-3.x86_64")
104-
touch(kernel_file)
100+
# Create the image source.
101+
image_source = self._create_source()
105102

106-
self.module._update_kernel_version_list(image_source)
103+
self.module._update_kernel_version_list(image_source)
107104

108105
assert self.module.get_kernel_version_list() == ["1.2-3.x86_64"]
106+
# Verify it was called with the mount_point from the source
107+
get_kernel_mock.assert_called_once_with(image_source.mount_point)
109108

110109
def test_install_with_task(self):
111110
"""Test the install_with_tasks method."""

0 commit comments

Comments
 (0)