Skip to content

Commit

Permalink
Add the ScanFilesForTargetUserspace actor.
Browse files Browse the repository at this point in the history
This actor allows us to specify what files should be copied to the target
userspace container upon its creation. Produces a `TargetUserSpacePreupgradeTasks`
containing the files to be copied.

The functionality provided by this actor is already employed to copy the
`/etc/hosts/` file into the target userspace, as the original implementation
relying on the usage of bind mounting would crash if the file did not exist.
The copy of this file is performed conditionally, that means it is copyied only
when the file exists. The original functionality that enables us to always bind
mount a certain file has been kept and it is still available for use.
  • Loading branch information
MichalHe authored and pirat89 committed Aug 16, 2021
1 parent 74dd759 commit 271ad6f
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from leapp.actors import Actor
from leapp.libraries.actor.scanfilesfortargetuserspace import scan_files_to_copy
from leapp.models import TargetUserSpacePreupgradeTasks
from leapp.tags import FactsPhaseTag, IPUWorkflowTag


class ScanFilesForTargetUserspace(Actor):
"""
Scan the source system and identify files that will be copied into the target userspace when it is created.
"""

name = 'scan_files_for_target_userspace'
consumes = ()
produces = (TargetUserSpacePreupgradeTasks,)
tags = (FactsPhaseTag, IPUWorkflowTag)

def process(self):
scan_files_to_copy()
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os

from leapp.libraries.stdlib import api
from leapp.models import CopyFile, TargetUserSpacePreupgradeTasks

# Maps src location in the source system to the destination within the target system
FILES_TO_COPY_IF_PRESENT = {
'/etc/hosts': '/etc/hosts'
}


def scan_files_to_copy():
"""
Scans the source system and identifies files that should be copied into target userspace.
When an item to be copied is identified a message :class:`CopyFile` is produced.
"""
files_to_copy = []
for src_path in FILES_TO_COPY_IF_PRESENT:
if os.path.isfile(src_path):
dst_path = FILES_TO_COPY_IF_PRESENT[src_path]
files_to_copy.append(CopyFile(src=src_path, dst=dst_path))

preupgrade_task = TargetUserSpacePreupgradeTasks(copy_files=files_to_copy)

api.produce(preupgrade_task)
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import os

import pytest

from leapp.libraries.actor.scanfilesfortargetuserspace import scan_files_to_copy
from leapp.libraries.common.testutils import produce_mocked
from leapp.libraries.stdlib import api


@pytest.fixture
def isfile_default_config():
config = {
'/etc/hosts': True
}
return config


def do_files_to_copy_contain_entry_with_src(files_to_copy, src):
"""Searches the files to be copied for an entry with src field that matches the given src."""
is_present = False
for file_to_copy in files_to_copy:
if file_to_copy.src == src:
is_present = True
break
return is_present


def make_mocked_isfile(configuration):
"""
Creates mocked isfile function that returns values according the given configuration.
The created function raises :class:`ValueError` should the unit under test try to "isfile"
a path that is not present in the configuration.
One global mocked function with configuration is error prone as individual test would
have to return the configuration into the original state after execution.
"""

def mocked_isfile(path):
if path in configuration:
return configuration[path]
error_msg = 'The actor tried to isfile a path that it should not. (path `{0}`)'
raise ValueError(error_msg.format(path))
return mocked_isfile


def test_etc_hosts_present(monkeypatch, isfile_default_config):
"""Tests whether /etc/hosts is identified as "to be copied" into target userspace when it is present."""
mocked_isfile = make_mocked_isfile(isfile_default_config)
actor_produces = produce_mocked()
monkeypatch.setattr(os.path, 'isfile', mocked_isfile)
monkeypatch.setattr(api, 'produce', actor_produces)

scan_files_to_copy()

fail_msg = 'Produced unexpected number of messages.'
assert len(actor_produces.model_instances) == 1, fail_msg

preupgrade_task_msg = actor_produces.model_instances[0]

fail_msg = 'Didn\'t indentify any files to copy into target userspace (at least /etc/hosts shoud be).'
assert preupgrade_task_msg.copy_files, fail_msg

should_copy_hostsfile = do_files_to_copy_contain_entry_with_src(preupgrade_task_msg.copy_files, '/etc/hosts')
assert should_copy_hostsfile, 'Failed to identify /etc/hosts as a file to be copied into target userspace.'

fail_msg = 'Produced message contains rpms to be installed, however only copy files field should be populated.'
assert not preupgrade_task_msg.install_rpms, fail_msg


def test_etc_hosts_missing(monkeypatch, isfile_default_config):
"""Tests whether /etc/hosts is not identified as "to be copied" into target userspace when it is missing."""
isfile_default_config['/etc/hosts'] = False # The file is not present or is a directory (-> shoud not be copied)
mocked_isfile = make_mocked_isfile(isfile_default_config)
actor_produces = produce_mocked()

monkeypatch.setattr(os.path, 'isfile', mocked_isfile)
monkeypatch.setattr(api, 'produce', actor_produces)

scan_files_to_copy()

assert len(actor_produces.model_instances) == 1, 'Produced unexpected number of messages.'

preupgrade_task_msg = actor_produces.model_instances[0]
should_copy_hostsfile = do_files_to_copy_contain_entry_with_src(preupgrade_task_msg.copy_files, '/etc/hosts')
assert not should_copy_hostsfile, 'Identified /etc/hosts as a file to be copied even if it doesn\'t exists'

fail_msg = 'Produced message contains rpms to be installed, however only copy files field should be populated.'
assert not preupgrade_task_msg.install_rpms, fail_msg
5 changes: 4 additions & 1 deletion repos/system_upgrade/common/libraries/mounting.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
from leapp.libraries.common.config.version import get_source_major_version


ALWAYS_BIND = ['/etc/hosts:/etc/hosts']
# Using ALWAYS_BIND will crash the upgrade process if the file does not exist.
# Consider instead adding an entry to the ScanFilesToCopyIntoTargetSystem actor that
# conditionaly (only if it exists) creates CopyFile message to the TargetUserspaceCreator.
ALWAYS_BIND = []

ErrorData = namedtuple('ErrorData', ['summary', 'details'])

Expand Down

0 comments on commit 271ad6f

Please sign in to comment.