tmt.steps.context package
Submodules
tmt.steps.context.abort module
- class tmt.steps.context.abort.AbortContext(path: Path, logger: Logger)
Bases:
HasEnvironmentProvides API for handling a phase-requested abort of a step.
- property environment: Environment
Environment variables this object wants to expose to user commands.
- 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:
GeneralErrorRaised 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|& catis 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.
- 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_ROOTvariable is set, it is used. Otherwise,TEST_PIDFILE_ROOTis 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:
HasEnvironmentTracks information about guest reboots.
- property environment: Environment
Environment variables this object wants to expose to user commands.
- 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:
Truewhen the reboot has taken place,Falseotherwise.
- hard_requested: bool = False
If set, an asynchronous observer requested a hard reboot.
- 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:
MetadataContainerData 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:
HasEnvironmentTracks information about restarts of an action, e.g. a test script.
- property environment: Environment
Environment variables this object wants to expose to user commands.
- 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_rebootis set. Setting the flag without providing the reboot context will raise an exception.- Returns:
Truewhen the restart is to take place,Falseotherwise.
- is_requested_test: Callable[[], bool]
A callback indicating whether a restart has been requested. It is called by
requestedproperty. Accepts no arguments, and returns a single boolean.
- 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:
HasEnvironmentProvides restraint-related context for execution.
- enabled: bool
- property environment: Environment
Environment variables this object wants to expose to user commands.
- taskname: str | None = None