tmt.steps.context package

Submodules

tmt.steps.context.abort module

class tmt.steps.context.abort.AbortContext(path: Path, logger: Logger)

Bases: HasEnvironment

Provides API for handling a phase-requested abort of a step.

property environment: Environment

Environment variables this object wants to expose to user commands.

logger: Logger

Used for logging.

path: Path

Path in which the abort request file should be stored.

property request_path: Path

A path to the abort request file.

property requested: bool

Whether a testing abort was requested

exception tmt.steps.context.abort.AbortStep(message: str, causes: list[Exception] | None = None, *args: Any, **kwargs: Any)

Bases: GeneralError

Raised by a plugin phases when the entire step should abort.

General error.

Parameters:
  • message – error message.

  • causes – optional list of exceptions that caused this one. Since raise ... from ... allows only for a single cause, and some of our workflows may raise exceptions triggered by more than one exception, we need a mechanism for storing them. Our reporting will honor this field, and report causes the same way as __cause__.

tmt.steps.context.pidfile module

Pidfile handling.

tmt must make sure running a script must allow for multiple external factors: the test timeout, interactivity, reboots and tmt-reboot invocations. tmt must present consistent info on what is the PID to kill from tmt-reboot, and where to save additional reboot info.

Note

Historically, this is mostly visible with tests, but the concept applies to all restartable actions on the guest: prepare/shell scripts, finish/ansible playbooks, and so on.

To achieve these goals, tmt uses two wrappers, the inner and the outer one. The inner one wraps the actual action: a test script as defined in test metadata, prepare/shell script, or ansible-playbook invocation. The outer one then runs the inner wrapper while performing the necessary setup and accounting. The inner wrapper is driven by user-provided inputs, while the outer contains what tmt itself needs to do to correctly integrate the action with tmt. tmt invokes the outer wrapper which then invokes the inner wrapper which then invokes the action.

The inner wrapper exists to give tmt a single command to run to invoke the action. Test or prepare script may be a single command, but also a multiline, complicated shell script. To avoid issues with quotes and escaping things here and there, tmt saves the action into the inner wrapper, and then the outer wrapper can work with just a single executable shell script.

For the duration of the action, the outer wrapper creates so-called “pidfile”. The pidfile contains outer wrapper PID and path to the reboot-request file corresponding to the action being run. All actions against the pidfile must be taken while holding the pidfile lock, to serialize access between the wrapper and tmt-reboot. The file might be missing, that’s allowed, but if it exists, it must contain correct info.

Before quitting the outer wrapper, the pidfile is removed. There seems to be an apparent race condition: action quits -> tmt-reboot is called from a parallel session, grabs a pidfile lock, inspects pidfile, updates reboot-request, and sends signal to designed PID -> wrapper grabs the lock & removes the pidfile. This leaves us with tmt-reboot sending signal to non-existent PID - which is reported by tmt-reboot, “try again later” - and reboot-request file signaling reboot is needed after the action is done.

This cannot be solved without the action being involved in the reboot, which does not seem like a viable option. The actions must be restartable though, it may get restarted in this “weird” way. On the other hand, this is probably not a problem in real-life scenarios: actions that are to be interrupted by out-of-session reboot are expecting this event, and they do not finish on their own.

The ssh client always allocates a tty, so the timeout handling works (#1387). Because the allocated tty is generally not suitable for the execution of test, or scripts in general, the wrapper uses |& cat to emulate execution without a tty. In certain cases, where the execution of given action with available tty is required (#2381), the tty can be enabled in the outer script.

The outer wrapper handles the following 3 execution modes:

  • In the interactive mode, stdin and stdout are unhandled, it is expected user interacts with the executed command.

  • In the non-interactive mode without a tty, stdin is fed with /dev/null (EOF), and |& cat is used to simulate the “no tty available” for the running action.

  • In the non-interactive mode with a tty, stdin is available to the action, and the simulation of “tty not available” for output is not run.

tmt.steps.context.pidfile.INNER_WRAPPER_TEMPLATE = <Template memory:7b30cedda3c0>

A template for the inner wrapper which invokes the action script.

tmt.steps.context.pidfile.OUTER_WRAPPER_TEMPLATE = <Template memory:7b30cee7f4d0>

A template for the outer wrapper which handles most of the orchestration and invokes the inner wrapper.

class tmt.steps.context.pidfile.PidFileContext(phase: tmt.steps.Phase, guest: tmt.steps.provision.Guest, logger: tmt.log.Logger)

Bases: HasEnvironment

create_wrappers(path: Path, inner_filename_template: str, outer_filename_template: str, before_message_template: str | None = None, after_message_template: str | None = None, **variables: Any) tuple[Path, Path]
property environment: Environment

Environment variables this object wants to expose to user commands.

guest: Guest

Guest on which the action runs.

logger: Logger

Used for logging.

phase: Phase

Phase owning this context.

property pidfile_lock_path: Path

Path to the pidfile lock.

property pidfile_path: Path

Path to the pidfile.

tmt.steps.context.pidfile.TEST_PIDFILE_ROOT = Path('/var/tmp')

The default directory for storing test pid file.

tmt.steps.context.pidfile.effective_pidfile_root() Path

Find out what the actual pidfile directory is.

If TMT_TEST_PIDFILE_ROOT variable is set, it is used. Otherwise, TEST_PIDFILE_ROOT is picked.

tmt.steps.context.reboot module

class tmt.steps.context.reboot.RebootContext(owner_label: str, guest: Guest, path: Path, logger: Logger, reboot_counter: int = 0, hard_requested: bool = False)

Bases: HasEnvironment

Tracks information about guest reboots.

property environment: Environment

Environment variables this object wants to expose to user commands.

guest: Guest

A guest to reboot when requested.

handle_reboot(restart: RestartContext | None = None) bool

Reboot the guest if requested.

Orchestrate the reboot if it was requested. Increment corresponding counters.

Parameters:

restart – if set, it’s a tracker of restart whose accounting should be updated as well.

Returns:

True when the reboot has taken place, False otherwise.

hard_requested: bool = False

If set, an asynchronous observer requested a hard reboot.

logger: Logger

Used for logging.

owner_label: str

A label describing the owner of this context, for the logging purposes.

path: Path

Path in which the reboot request file should be stored.

reboot_counter: int = 0

Number of times the guest has been rebooted.

property request_path: Path

A path to the reboot request file.

property requested: bool

Whether a guest reboot has been requested

property soft_requested: bool

If set, a soft reboot was requested.

class tmt.steps.context.reboot.RebootData(*, command: str | None = None, timeout: int = 600, systemd_soft_reboot: bool = False)

Bases: MetadataContainer

Data structure representing reboot request details.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

command: str | None
model_config: ClassVar[ConfigDict] = {'alias_generator': <function key_to_option>, 'extra': 'forbid', 'validate_assignment': True, 'validate_default': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

systemd_soft_reboot: bool
timeout: int

tmt.steps.context.restart module

class tmt.steps.context.restart.RestartContext(owner_label: str, guest: Guest, is_requested_test: Callable[[], bool], restart_limit: int, restart_with_reboot: bool, logger: Logger, restart_counter: int = 0)

Bases: HasEnvironment

Tracks information about restarts of an action, e.g. a test script.

property environment: Environment

Environment variables this object wants to expose to user commands.

guest: Guest

Guest on which the restartable action runs.

handle_restart(reboot: RebootContext | None = None) bool

“Restart” the action when requested.

Note

The action is not actually restarted, because running the action is managed by the owner of this context. Instead, the method performs all necessary steps before letting plugin know it should run the action once again.

Check whether an action restart was needed and allowed, and update the accounting info before letting the caller know it’s time to run the action once again.

If requested, the guest might be rebooted as well.

Parameters:

reboot – if set, it’s a tracker of guest reboots to be used when restart_with_reboot is set. Setting the flag without providing the reboot context will raise an exception.

Returns:

True when the restart is to take place, False otherwise.

is_requested_test: Callable[[], bool]

A callback indicating whether a restart has been requested. It is called by requested property. Accepts no arguments, and returns a single boolean.

logger: Logger

Used for logging.

owner_label: str

A label describing the owner of this context, for the logging purposes.

property requested: bool

Whether a restart has been requested.

restart_counter: int = 0

Number of times the action has been restarted.

restart_limit: int

A maximum number of restarts allowed. Once reached, an attempt to restart once again will raise RestartMaxAttemptsError.

restart_with_reboot: bool

If set, a hard reboot will be invoked before the restart.

tmt.steps.context.restraint module

class tmt.steps.context.restraint.RestraintContext(enabled: bool, logger: Logger, taskname: str | None = None)

Bases: HasEnvironment

Provides restraint-related context for execution.

enabled: bool
property environment: Environment

Environment variables this object wants to expose to user commands.

logger: Logger

Used for logging.

taskname: str | None = None

Module contents