-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
live_mode: add new actors implementing the live mode functionality
Add actors that scan the new configuration file devel-livemode.ini, informing the rest of the actor collective about the configuration. Based on this configuration, additional packages are requested to be installed into the target userspace. The target userspace is also modified to contain services that execute leapp based on kernel cmdline. For a full list of modifications, see models/livemode.py added in a previous commit. The feature can be enabled by setting LEAPP_UNSUPPORTED=1 together with LEAPP_DEVEL_ENABLE_LIVE_MODE=1. Note, that the squashfs-tools package must be installed (otherwise an error will be raised). The live mode feature is currently tested only for x86_64, and, therefore, attempting to use this feature on a different architecture will be prohibited by the implementation. Jira ref: RHEL-45280
- Loading branch information
Michal Hecko
committed
Aug 16, 2024
1 parent
89874fd
commit f6de260
Showing
23 changed files
with
2,186 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Configuration for the *experimental* livemode feature | ||
# It is likely that this entire configuration file will be replaced by some | ||
# other mechanism/file in the future. For the full list of configuration options, | ||
# see models/livemode.py | ||
[livemode] | ||
squashfs_fullpath=/var/lib/leapp/live-upgrade.img | ||
setup_network_manager=no | ||
autostart_upgrade_after_reboot=yes | ||
setup_passwordless_root=no |
20 changes: 20 additions & 0 deletions
20
repos/system_upgrade/common/actors/livemode/emit_livemode_userspace_requirements/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from leapp.actors import Actor | ||
from leapp.libraries.actor import emit_livemode_userspace_requirements as emit_livemode_userspace_requirements_lib | ||
from leapp.models import LiveModeConfig, TargetUserSpaceUpgradeTasks | ||
from leapp.tags import ExperimentalTag, InterimPreparationPhaseTag, IPUWorkflowTag | ||
|
||
|
||
class EmitLiveModeRequirements(Actor): | ||
""" | ||
Request addiontal packages to be installed into target userspace. | ||
Additional packages can be requested using LiveModeConfig.additional_packages | ||
""" | ||
|
||
name = 'emit_livemode_requirements' | ||
consumes = (LiveModeConfig,) | ||
produces = (TargetUserSpaceUpgradeTasks,) | ||
tags = (ExperimentalTag, InterimPreparationPhaseTag, IPUWorkflowTag,) | ||
|
||
def process(self): | ||
emit_livemode_userspace_requirements_lib.emit_livemode_userspace_requirements() |
38 changes: 38 additions & 0 deletions
38
...de/emit_livemode_userspace_requirements/libraries/emit_livemode_userspace_requirements.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import LiveModeConfig, TargetUserSpaceUpgradeTasks | ||
|
||
# NOTE: would also need | ||
# _REQUIRED_PACKAGES from actors/commonleappdracutmodules/libraries/modscan.py | ||
|
||
_REQUIRED_PACKAGES_FOR_LIVE_MODE = [ | ||
'systemd-container', | ||
'dbus-daemon', | ||
'NetworkManager', | ||
'util-linux', | ||
'dracut-live', | ||
'dracut-squash', | ||
'dmidecode', | ||
'pciutils', | ||
'lsscsi', | ||
'passwd', | ||
'kexec-tools', | ||
'vi', | ||
'less', | ||
'openssh-clients', | ||
'strace', | ||
'tcpdump', | ||
] | ||
|
||
|
||
def emit_livemode_userspace_requirements(): | ||
livemode_config = next(api.consume(LiveModeConfig), None) | ||
if not livemode_config or not livemode_config.is_enabled: | ||
return | ||
|
||
packages = _REQUIRED_PACKAGES_FOR_LIVE_MODE + livemode_config.additional_packages | ||
if livemode_config.setup_opensshd_with_auth_keys: | ||
packages += ['openssh-server', 'crypto-policies'] | ||
|
||
packages = sorted(set(packages)) | ||
|
||
api.produce(TargetUserSpaceUpgradeTasks(install_rpms=packages)) |
37 changes: 37 additions & 0 deletions
37
...rs/livemode/emit_livemode_userspace_requirements/tests/test_emit_livemode_requirements.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import pytest | ||
|
||
from leapp.libraries.actor import emit_livemode_userspace_requirements as emit_livemode_userspace_requirements_lib | ||
from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import LiveModeConfig, TargetUserSpaceUpgradeTasks | ||
|
||
|
||
@pytest.mark.parametrize('livemode_config', (None, LiveModeConfig(squashfs_fullpath='<squashfs>', is_enabled=False))) | ||
def test_no_emit_if_livemode_disabled(monkeypatch, livemode_config): | ||
messages = [livemode_config] if livemode_config else [] | ||
actor_mock = CurrentActorMocked(msgs=messages) | ||
monkeypatch.setattr(api, 'current_actor', actor_mock) | ||
monkeypatch.setattr(api, 'produce', produce_mocked()) | ||
|
||
emit_livemode_userspace_requirements_lib.emit_livemode_userspace_requirements() | ||
|
||
assert not api.produce.called | ||
|
||
|
||
def test_emit(monkeypatch): | ||
config = LiveModeConfig(squashfs_fullpath='<squashfs_path>', is_enabled=True, additional_packages=['EXTRA_PKG']) | ||
actor_mock = CurrentActorMocked(msgs=[config]) | ||
monkeypatch.setattr(api, 'current_actor', actor_mock) | ||
monkeypatch.setattr(api, 'produce', produce_mocked()) | ||
|
||
emit_livemode_userspace_requirements_lib.emit_livemode_userspace_requirements() | ||
|
||
assert api.produce.called | ||
assert len(api.produce.model_instances) == 1 | ||
|
||
required_pkgs = api.produce.model_instances[0] | ||
assert isinstance(required_pkgs, TargetUserSpaceUpgradeTasks) | ||
|
||
assert 'dracut-live' in required_pkgs.install_rpms | ||
assert 'dracut-squash' in required_pkgs.install_rpms | ||
assert 'EXTRA_PKG' in required_pkgs.install_rpms |
29 changes: 29 additions & 0 deletions
29
repos/system_upgrade/common/actors/livemode/liveimagegenerator/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from leapp.actors import Actor | ||
from leapp.libraries.actor.liveimagegenerator import generate_live_image_if_enabled | ||
from leapp.models import ( | ||
LiveImagePreparationInfo, | ||
LiveModeArtifacts, | ||
LiveModeConfig, | ||
LiveModeRequirementsTasks, | ||
PrepareLiveImagePostTasks, | ||
TargetUserSpaceInfo | ||
) | ||
from leapp.tags import ExperimentalTag, InterimPreparationPhaseTag, IPUWorkflowTag | ||
|
||
|
||
class LiveImageGenerator(Actor): | ||
""" | ||
Generates the squashfs image for the livemode upgrade | ||
""" | ||
|
||
name = 'live_image_generator' | ||
consumes = (LiveModeConfig, | ||
LiveModeRequirementsTasks, | ||
LiveImagePreparationInfo, | ||
PrepareLiveImagePostTasks, | ||
TargetUserSpaceInfo,) | ||
produces = (LiveModeArtifacts,) | ||
tags = (ExperimentalTag, InterimPreparationPhaseTag, IPUWorkflowTag,) | ||
|
||
def process(self): | ||
generate_live_image_if_enabled() |
72 changes: 72 additions & 0 deletions
72
.../system_upgrade/common/actors/livemode/liveimagegenerator/libraries/liveimagegenerator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import os | ||
import os.path | ||
import shutil | ||
|
||
from leapp.exceptions import StopActorExecutionError | ||
from leapp.libraries.common import mounting | ||
from leapp.libraries.stdlib import api, CalledProcessError, run | ||
from leapp.models import LiveModeArtifacts, LiveModeConfig, TargetUserSpaceInfo | ||
|
||
|
||
def lighten_target_userpace(context): | ||
""" | ||
Remove unneeded files from the target userspace. | ||
""" | ||
|
||
userspace_trees_to_prune = ['artifacts', 'boot'] | ||
|
||
for tree_to_prune in userspace_trees_to_prune: | ||
tree_full_path = os.path.join(context.base_dir, tree_to_prune) | ||
try: | ||
shutil.rmtree(tree_full_path) | ||
except OSError as error: | ||
api.current_logger().warning('Failed to remove /%s directory from the live image. Full error: %s', | ||
tree_to_prune, error) | ||
|
||
|
||
def build_squashfs(livemode_config, userspace_info): | ||
""" | ||
Generate the live rootfs image based on the target userspace | ||
:param livemode LiveModeConfig: Livemode configuration message | ||
:param userspace_info TargetUserspaceInfo: Information about how target userspace is set up | ||
""" | ||
target_userspace_fullpath = userspace_info.path | ||
squashfs_fullpath = livemode_config.squashfs_fullpath | ||
|
||
api.current_logger().info('Building the squashfs image %s from target userspace located at %s', | ||
squashfs_fullpath, target_userspace_fullpath) | ||
|
||
try: | ||
if os.path.exists(squashfs_fullpath): | ||
os.unlink(squashfs_fullpath) | ||
except OSError as error: | ||
api.current_logger().warning('Failed to remove already existing %s. Full error: %s', | ||
squashfs_fullpath, error) | ||
|
||
try: | ||
run(['mksquashfs', target_userspace_fullpath, squashfs_fullpath]) | ||
except CalledProcessError as error: | ||
raise StopActorExecutionError( | ||
'Cannot pack the target userspace into a squash image. ', | ||
details={'details': 'The following error occurred while building the squashfs image: {0}.'.format(error)} | ||
) | ||
|
||
return squashfs_fullpath | ||
|
||
|
||
def generate_live_image_if_enabled(): | ||
""" | ||
Main function to generate the additional artifacts needed to run in live mode. | ||
""" | ||
|
||
livemode_config = next(api.consume(LiveModeConfig), None) | ||
if not livemode_config or not livemode_config.is_enabled: | ||
return | ||
|
||
userspace_info = next(api.consume(TargetUserSpaceInfo), None) | ||
|
||
with mounting.NspawnActions(base_dir=userspace_info.path) as context: | ||
lighten_target_userpace(context) | ||
squashfs_path = build_squashfs(livemode_config, userspace_info) | ||
api.produce(LiveModeArtifacts(squashfs_path=squashfs_path)) |
96 changes: 96 additions & 0 deletions
96
...s/system_upgrade/common/actors/livemode/liveimagegenerator/tests/test_image_generation.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import collections | ||
import os | ||
import shutil | ||
|
||
import pytest | ||
|
||
from leapp.libraries.actor import liveimagegenerator as live_image_generator_lib | ||
from leapp.libraries.common import mounting | ||
from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import LiveModeArtifacts, LiveModeConfig, TargetUserSpaceInfo | ||
|
||
|
||
def test_squafs_creation(monkeypatch): | ||
userspace_info = TargetUserSpaceInfo(path='/USERSPACE', scratch='/SCRATCH', mounts='/MOUNTS') | ||
livemode_config = LiveModeConfig(is_enabled=True, squashfs_fullpath='/var/lib/leapp/squashfs.img') | ||
|
||
def exists_mock(path): | ||
assert path == '/var/lib/leapp/squashfs.img' | ||
return True | ||
|
||
monkeypatch.setattr(os.path, 'exists', exists_mock) | ||
|
||
def unlink_mock(path): | ||
assert path == '/var/lib/leapp/squashfs.img' | ||
|
||
monkeypatch.setattr(os, 'unlink', unlink_mock) | ||
|
||
commands_executed = [] | ||
|
||
def run_mock(command): | ||
commands_executed.append(command[0]) | ||
|
||
monkeypatch.setattr(live_image_generator_lib, 'run', run_mock) | ||
|
||
live_image_generator_lib.build_squashfs(livemode_config, userspace_info) | ||
assert commands_executed == ['mksquashfs'] | ||
|
||
|
||
def test_userspace_lightening(monkeypatch): | ||
|
||
removed_trees = [] | ||
|
||
def rmtree_mock(path): | ||
removed_trees.append(path) | ||
|
||
monkeypatch.setattr(shutil, 'rmtree', rmtree_mock) | ||
|
||
_ContextMock = collections.namedtuple('ContextMock', ('base_dir')) | ||
context_mock = _ContextMock(base_dir='/USERSPACE') | ||
|
||
live_image_generator_lib.lighten_target_userpace(context_mock) | ||
|
||
assert removed_trees == ['/USERSPACE/artifacts', '/USERSPACE/boot'] | ||
|
||
|
||
@pytest.mark.parametrize( | ||
('livemode_config', 'should_produce'), | ||
( | ||
(LiveModeConfig(is_enabled=True, squashfs_fullpath='/squashfs'), True), | ||
(LiveModeConfig(is_enabled=False, squashfs_fullpath='/squashfs'), False), | ||
(None, False), | ||
) | ||
) | ||
def test_generate_live_image_if_enabled(monkeypatch, livemode_config, should_produce): | ||
userspace_info = TargetUserSpaceInfo(path='/USERSPACE', scratch='/SCRATCH', mounts='/MOUNTS') | ||
messages = [livemode_config, userspace_info] if livemode_config else [userspace_info] | ||
actor_mock = CurrentActorMocked(msgs=messages) | ||
monkeypatch.setattr(api, 'current_actor', actor_mock) | ||
|
||
class NspawnMock(object): | ||
def __init__(self, *args, **kwargs): | ||
pass | ||
|
||
def __enter__(self, *args, **kwargs): | ||
pass | ||
|
||
def __exit__(self, *args, **kwargs): | ||
pass | ||
|
||
monkeypatch.setattr(mounting, 'NspawnActions', NspawnMock) | ||
monkeypatch.setattr(live_image_generator_lib, 'lighten_target_userpace', lambda context: None) | ||
monkeypatch.setattr(live_image_generator_lib, 'build_squashfs', | ||
lambda livemode_config, userspace_info: '/squashfs') | ||
monkeypatch.setattr(api, 'produce', produce_mocked()) | ||
|
||
live_image_generator_lib.generate_live_image_if_enabled() | ||
|
||
if should_produce: | ||
assert api.produce.called | ||
assert len(api.produce.model_instances) == 1 | ||
artifacts = api.produce.model_instances[0] | ||
assert isinstance(artifacts, LiveModeArtifacts) | ||
assert artifacts.squashfs_path == '/squashfs' | ||
else: | ||
assert not api.produce.called |
18 changes: 18 additions & 0 deletions
18
repos/system_upgrade/common/actors/livemode/livemode_config_scanner/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from leapp.actors import Actor | ||
from leapp.libraries.actor import scan_livemode_config as scan_livemode_config_lib | ||
from leapp.models import InstalledRPM, LiveModeConfig | ||
from leapp.tags import ExperimentalTag, FactsPhaseTag, IPUWorkflowTag | ||
|
||
|
||
class LiveModeConfigScanner(Actor): | ||
""" | ||
Read livemode configuration located at /etc/leapp/files/devel-livemode.ini | ||
""" | ||
|
||
name = 'live_mode_config_scanner' | ||
consumes = (InstalledRPM,) | ||
produces = (LiveModeConfig,) | ||
tags = (ExperimentalTag, FactsPhaseTag, IPUWorkflowTag,) | ||
|
||
def process(self): | ||
scan_livemode_config_lib.scan_config_and_emit_message() |
Oops, something went wrong.