Skip to content

Commit

Permalink
Add --actor-config to snactor run
Browse files Browse the repository at this point in the history
This patch introduces support for specifying a model which the actor
should consume for configuration data.
Using this feature requires to run an actor before which produces this
model and saves the output.

Additionally to facilitate the testing needs for this PR `snactor
messages add` has been added, which allows injecting messages from the
commandline.

Jira-Task: OAMG-1092

Signed-off-by: Vinzenz Feenstra <vfeenstr@redhat.com>
  • Loading branch information
vinzenz authored and Rezney committed Jun 5, 2020
1 parent 6f0610c commit c464a59
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ container-test:
docker run --rm -ti -v ${PWD}:/payload leapp-tests
test: lint
pytest --cov-report term-missing --cov=leapp tests/scripts
pytest -vv --cov-report term-missing --cov=leapp tests/scripts
lint:
pytest --cache-clear --pylint -m pylint leapp tests/scripts/*.py
Expand Down
19 changes: 17 additions & 2 deletions docs/source/el7toel8/actor-rhel7-to-rhel8.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ reporting.RelatedResource('pam', 'pam_securetty')

The related resources are especially useful when you have a lot of accompanied objects like files or directories by your report and you would like to present it to the user in a specific way.


## Testing your new actor

During development of your new actor, it is expected that you will test your work to verify that results match your expectations. You can do that by manually executing your actor, or writing tests on various levels (i.e unit tests, component tests, E2E tests).
Expand Down Expand Up @@ -313,6 +312,22 @@ As you can see the actor is executed without errors. But, by default, snactor do

Now we can see that the _OSReleaseCollector_ actor produced a message of the _OSReleaseFacts_ model, containing data like OS Release name and version.

### Executing a single actor that uses the workflow config

If you need to execute an actor on its own that requires the `IPUConfig` model you can execute the actor with the
following command:

```shell
snactor run --actor-config IPUConfig ActorName
```

In order for this to work you have to run the `IPUWorkflowConfig` actor before and save its output, so that the config
data is stored in the database for the current session:

```shell
snactor run --save-output IPUWorkflowConfig
```

### Executing the whole upgrade workflow with the new actor

Finally, you can make your actor part of the “leapp upgrade” process and check how it behaves when executed together with all the other actors in the workflow. Assuming that your new actor is tagged properly, being part of _IPUWorkflow_, and part of an existing phase, you can place it inside an existing leapp repository on a testing RHEL 7 system. All Leapp components (i.e actors, models, tags) placed inside **/etc/leapp/repos.d/system_upgrade/el7toel8/** will be used by the “leapp upgrade” command during upgrade process.
Expand Down Expand Up @@ -435,4 +450,4 @@ When filing an issue, include:

## Where can I seek help?

We’ll gladly answer your questions and lead you to through any troubles with the actor development. You can reach us, the OS and Application Modernization Group, at freenode IRC server in channel __#leapp__.
We’ll gladly answer your questions and lead you to through any troubles with the actor development. You can reach us, the OS and Application Modernization Group, at freenode IRC server in channel **#leapp**.
4 changes: 2 additions & 2 deletions leapp/messaging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

from leapp.dialogs import RawMessageDialog
from leapp.dialogs.renderer import CommandlineRenderer
from leapp.messaging.answerstore import AnswerStore
from leapp.exceptions import CannotConsumeErrorMessages
from leapp.models import DialogModel, ErrorModel
from leapp.messaging.answerstore import AnswerStore
from leapp.messaging.commands import WorkflowCommand
from leapp.models import DialogModel, ErrorModel
from leapp.utils import get_api_models


Expand Down
54 changes: 50 additions & 4 deletions leapp/snactor/commands/messages/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import json
import os
import sys
from importlib import import_module

from leapp.utils.clicmd import command
from leapp.utils.repository import requires_repository
from leapp.exceptions import CommandError, LeappError, ModelDefinitionError
from leapp.logger import configure_logger
from leapp.messaging.inprocess import InProcessMessaging
from leapp.models.fields import ModelViolationError
from leapp.repository.scan import find_and_scan_repositories
from leapp.snactor.context import with_snactor_context
from leapp.utils.audit import get_connection, get_config
from leapp.utils.audit import get_config, get_connection
from leapp.utils.clicmd import command, command_arg, command_opt
from leapp.utils.repository import find_repository_basedir, requires_repository

_MAIN_LONG_DESCRIPTION = '''
This group of commands are around managing messages stored in the
Expand All @@ -29,8 +37,18 @@ def messages(args): # noqa; pylint: disable=unused-argument
'''


_ADD_LONG_DESCRIPTION = '''
With this command messages in the current repository scope can be added.
This gives the developer control over the input data available to actors
run and developed in this repository.
For more information please consider reading the documentation at:
https://red.ht/leapp-docs
'''


@messages.command('clear', help='Deletes all messages from the current repository scope',
description=_CLEAR_LONG_DESCRIPTION)
description=_ADD_LONG_DESCRIPTION)
@requires_repository
@with_snactor_context
def clear(args): # noqa; pylint: disable=unused-argument
Expand All @@ -40,3 +58,31 @@ def clear(args): # noqa; pylint: disable=unused-argument
))
with get_connection(None) as con:
con.execute("DELETE FROM message WHERE context = ?", (os.environ["LEAPP_EXECUTION_ID"],))


class Injector(object):
name = 'injector'


@messages.command('add', help='Adds a message to the current repository scope',
description=_CLEAR_LONG_DESCRIPTION)
@command_opt('model', short_name='m', help='Model to add')
@command_arg('data', help='Data to add in JSON format')
@requires_repository
@with_snactor_context
def add(args): # noqa; pylint: disable=unused-argument
basedir = find_repository_basedir('.')
repository = find_and_scan_repositories(basedir, include_locals=True)
try:
repository.load()
except LeappError as exc:
sys.stderr.write(exc.message)
sys.stderr.write('\n')
sys.exit(1)
try:
model = getattr(import_module('leapp.models'), args.model, None)
InProcessMessaging().produce(model.create(json.loads(args.data)), Injector())
except ModelDefinitionError:
raise CommandError('No such model: {}'.format(args.model))
except ModelViolationError as e:
raise CommandError('Invalid data format for model: {}\n => {}'.format(args.model, e.message))
20 changes: 11 additions & 9 deletions leapp/snactor/commands/run.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from importlib import import_module
import json
import sys

from leapp.exceptions import LeappError, CommandError
from leapp.utils.clicmd import command, command_opt, command_arg
from leapp.utils.repository import requires_repository, find_repository_basedir
from leapp.exceptions import CommandError, LeappError
from leapp.logger import configure_logger
from leapp.messaging.inprocess import InProcessMessaging
from leapp.utils.output import report_errors, beautify_actor_exception
from leapp.repository.scan import find_and_scan_repositories
from leapp.snactor.context import with_snactor_context

from leapp.utils.clicmd import command, command_arg, command_opt
from leapp.utils.output import beautify_actor_exception, report_errors
from leapp.utils.repository import find_repository_basedir, requires_repository

_LONG_DESCRIPTION = '''
Runs the given actor as specified as `actor_name` in a testing environment.
Expand All @@ -21,8 +21,9 @@

@command('run', help='Execute the given actor', description=_LONG_DESCRIPTION)
@command_arg('actor-name')
@command_opt('--save-output', is_flag=True)
@command_opt('--print-output', is_flag=True)
@command_opt('--actor-config', help='Name of the workflow config model to use')
@command_opt('--save-output', is_flag=True, help='Save the produced messages by this actor.')
@command_opt('--print-output', is_flag=True, help='Print the produced messages by this actor.')
@requires_repository
@with_snactor_context
def cli(args):
Expand All @@ -39,13 +40,14 @@ def cli(args):
actor = repository.lookup_actor(args.actor_name)
if not actor:
raise CommandError('Actor "{}" not found!'.format(args.actor_name))
messaging = InProcessMessaging(stored=args.save_output)
config_model = getattr(import_module('leapp.models'), args.actor_config) if args.actor_config else None
messaging = InProcessMessaging(stored=args.save_output, config_model=config_model)
messaging.load(actor.consumes)

failure = False
with beautify_actor_exception():
try:
actor(messaging=messaging, logger=actor_logger).run()
actor(messaging=messaging, logger=actor_logger, config_model=config_model).run()
except BaseException:
failure = True
raise
Expand Down
2 changes: 1 addition & 1 deletion leapp/utils/workarounds/mp.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, *args, **kwargs):
super(FixedFinalize, self).__init__(*args, **kwargs)
self._pid = os.getpid()

def __call__(self, *args, **kwargs): # pylint: disable=signature-differs
def __call__(self, *args, **kwargs): # pylint: disable=signature-differs
if self._pid != os.getpid():
return None
return super(FixedFinalize, self).__call__(*args, **kwargs)
Expand Down
40 changes: 40 additions & 0 deletions tests/scripts/test_snactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def test_new_actor(repository_dir):
with pytest.raises(CalledProcessError):
check_call(['snactor', 'discover'])
repository_dir.join('actors/test/actor.py').write('''
from __future__ import print_function
from leapp.actors import Actor
from leapp.models import TestModel
from leapp.tags import TestTag
Expand All @@ -144,6 +145,8 @@ class Test(Actor):
def process(self):
self.produce(TestModel(value='Some testing value'))
if self._configuration:
print(self.configuration.value)
''')
check_call(['snactor', 'discover'])

Expand Down Expand Up @@ -211,6 +214,43 @@ def test_run_actor(repository_dir):
check_call(['snactor', '--debug', 'run', '--print-output', '--save-output', 'Test'])


def test_actor_config(repository_dir):
with repository_dir.as_cwd():
repository_dir.join('models/testconfig.py').write('''
from leapp.models import Model, fields
from leapp.topics import TestTopic
class TestConfig(Model):
topic = TestTopic
value = fields.String(default='Test config value')
''')
check_call(['snactor', 'new-actor', 'ConfigTest'])
repository_dir.join('actors/configtest/actor.py').write('''
from __future__ import print_function
from leapp.actors import Actor
from leapp.models import TestConfig
from leapp.tags import TestTag
class ConfigTest(Actor):
name = 'test'
description = 'No description has been provided for the test actor.'
consumes = (TestConfig,)
produces = ()
tags = (TestTag,)
def process(self):
print(self.configuration.value)
''')
with pytest.raises(CalledProcessError):
# Raises if actor-config is not specified
check_call(['snactor', 'run', 'ConfigTest'])
# Inject message
check_call(['snactor', 'messages', 'add', '-m', 'TestConfig', '{"value": "ConfigTest calls"}'])
# Ensure the injected data is now available to the actor
check_call(['snactor', 'run', '--actor-config', 'TestConfig', 'Test'])


def test_clear_messages(repository_dir):
with repository_dir.as_cwd():
check_call(['snactor', 'messages', 'clear'])

0 comments on commit c464a59

Please sign in to comment.