Best Practices for actor developmemt¶
Follow the contribution guidelines¶
See the contribution guidelines.
Avoid running code on a module level¶
Despite the actors being written in Python, they are loaded beforehand to get all the meta-information from them. To avoid slow downs during this load phase we ask you to not execute any code that is not absolutely necessary on a module level. Simple, fast code with no side-effects is acceptable, for example constant definitions.
Avoid certain global imports¶
On a module level, try to import only the Python Standard Library modules or modules provided by the Leapp framework. Import any other module within your function or method. This has to do with possible slow down when loading repositories by the leapp tool, and also to avoid unnecessary complications for our tests automation which needs to inspect the actors beforehand.
Use the snactor tool for development¶
The snactor tool helps you with creating the base layout of a new Leapp repository, and with creating boilerplates for the repository artifacts like actors, models, tags, topics, and workflows. It also helps you with debugging as it is able to execute individual actors.
See the tutorial on basic usage of snactor.
Move generic functionality to libraries¶
Part of your actor‘s functionality might end up being rather generic or abstract. In that case, consider converting it to a shared library function. You can introduce it in one of these two places:
-
The most generic functionality which actors of any workflow can use, e.g. function for exectuting a shell command, should go to the Leapp Standard Library. For that, please submit proposals through issues or pull requests under the leapp GitHub repository.
Leapp repository-level shared library
The functionality that may be useful for other actor developers but its scope is limited to the workflow you are writing your actor for, e.g. for an OS in-place upgrade workflow, should go to the
<Leapp repository>/libraries
folder. In this case, we welcome your proposals under the leapp-repository on GitHub.Please note that the name of the library module in this case should be unique and contain the actor name. For example:
repos/system_upgrade/el7toel8/actors/vimmigrate/libraries/vimmigrate.py
Discover standard library functions¶
Before implementing functionality for your actor, browse through the available functionality provided by:
- the Leapp Standard Library,
- the shared library of your Leapp repository (
<Leapp repository>/libraries
folder).
These libraries contain functions that may satisfy your needs. Using them can save you time, lower code complexity and help avoiding duplicate code. Improvement proposals for the library functions are welcome.
Prefer using stdlib functions over shell commands¶
Sources of external functionality to be used in your actor in order of preference:
- the Leapp Standard Library
- the Python Standard Library
- shell commands
Examples:
- Prefer
os.symlink
overls -s
- Prefer
os.remove
overrm
There might be a valid reason for calling a shell command instead of a standard library function, e.g. the Python‘s
shutil.copyfile
is not able to retain all of the file attributes, as opposed to shell‘s cp --preserve
.
Utilize messages produced by existing actors¶
As with the Leapp Standard Library mentioned above, it may be beneficial for you to skim through the actors already in place in the leapp-repository. You might be interested in the messages they produce, for example:
- SystemFactsActor - information about kernel modules, yum repos, sysctl variables, users, firewall, SELinux, etc.
- OSReleaseCollector - system release information
- RpmScanner - list of installed packages
- StorageScanner - storage information
In case you find any message of the existing actors to be incorrect, incomplete or misleading, we encourage you to improve the respective actors.
Write unit testable code¶
Write all the actor’s logic in the actor’s private library in order to be able to write unit tests for each of the
function. It is not currently possible to unit test any method or function in the actor.py. Then, ideally, the
actor.process()
should contain only calling an entry point to the actor‘s library. To create an actor’s library,
create libraries folder in the actor’s folder and in there create an arbitrarily named python file, e.g. {actor_name}.py.
myactor/actor.py:
from leapp.libraries.actor.library import do_the_actor_thingy
class MyActor(Actor):
# <snip>
def process(self):
do_the_actor_thingy(self)
myactor/libraries/myactor.py:
def do_the_actor_thingy(actor):
actor.log.debug("All the actor’s logic shall be outside actor.py")
For more about unit testing, see the tutorial.
Do not introduce new dependencies¶
Ideally, actors shouldn‘t require any additional dependency on top of the dependencies already in the leapp and leapp-repository spec files, which are, as of December 2018, just these:
- dnf
- python-six
- python-setuptools
- findutils
Rather than adding a new dependency to the spec file, detect in the actor if the package is installed and if not, have a fallback option or skip the action you wanted to perform and report to the user that they should install the package if they want to experience as smooth upgrade as possible.
If you‘re writing an actor for the RHEL 7 to RHEL 8 in-place upgrade workflow and you really need to have some package installed, then the only acceptable packages to depend on are the the ones available in the minimal installation of RHEL 7 (packages from the @core group + their dependencies).
For more details about dependencies and how to modify them, see the How to deal with dependencies.
Use convenience exceptions to stop the actor‘s execution¶
If you encounter unexpected input or any other error during execution of an actor and want to stop it, use these exceptions:
- StopActorExecution - raising this exception is a convenient to stop actor‘s execution with no side effect - it has the same effect as returning
None
from the mainprocess()
method in theactor.py
- StopActorExecutionError - raising this exception will stop actor‘s execution and notify the framework that an error has occurred and can influence the result of the workflow execution.
In case of StopActorExecutionError the execution of the workflow will stop or not according to the policy defined in the workflow, there are three possibilities:
- FailImmediately - end the workflow execution right away
- FailPhase - end the workflow execution after finishing the current phase
- ReportOnly - do not end the workflow execution at all and continue with logging the issue only
You can also use the StopActorExecution and StopActorExecutionError exceptions inside a private or shared library.
Use the LEAPP and LEAPP_DEVEL prefixes for new envars¶
In case you need to change a behaviour of actor(s) for testing or development purposes - e.g. be able to skip a functionality in your actor - use environment variables. Such environment variables should start with prefix LEAPP_DEVEL. Such variables are not possible to use on production systems without special LEAPP_UNSUPPORTED variable. This prevents users to break their systems by a mistake.
If you want to enable user to modify behaviour on production systems as well (e.g. enable user increase size of something the actor is producing), use just LEAPP prefix and ensure you inform user about the variable if needed.
In both cases, such environment variables should be mentioned in related commit messages.