Skip to content

Commit

Permalink
Add 'sti' execute plugin
Browse files Browse the repository at this point in the history
Support running sti tests via 'tmt'. Add an template for easily
create the test plan.

Example of the workflow

$ fedpkg clone python3
$ cd python3
$ tmt init --template sti
$ tmt run -a provision -h virtual.testcloud

Signed-off-by: Miroslav Vadkerti <mvadkert@redhat.com>
  • Loading branch information
thrix committed Apr 30, 2020
1 parent 806df5f commit 7bb7d1c
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 10 deletions.
33 changes: 33 additions & 0 deletions spec/steps/execute.fmf
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,36 @@ example: |
been executed successfully and the journal for test
results.
implemented: /tmt/steps/execute/beakerlib.py

/sti:
story: As a user I want to easily run `STI`_ tests.
summary: Execute STI tests
description: |
Run tests in `STI`_ format via Ansible and `standard-test-roles`_.

.. _STI: https://docs.fedoraproject.org/en-US/ci/standard-test-interface/
.. _standard-test-roles: https://docs.fedoraproject.org/en-US/ci/standard-test-roles/

There are two deviations from the standard::

1. By default only playbook `tests/tests.yml` is executed. If you need
to run multiple playbooks, define separate plans for it, because each
playbook has to be executed in a separate environment.

2. It is possible to run playbook with any name really. The standard
defines that only playbooks `tests/tests*.yml` are executed.

/default:
summary: Execute default playbook
example: |
execute:
how: sti

/custom:
summary: Execute custom playbook
example: |
execute:
how: sti
playbook: tests/full-test-suite.yml

implemented: /tmt/steps/execute/sti.py
12 changes: 9 additions & 3 deletions tmt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -888,14 +888,20 @@ def _ansible_playbook_path(self, playbook):
self.debug(f"Playbook full path: '{playbook}'", level=2)
return playbook

def ansible(self, playbook):
""" Prepare guest using ansible playbook """
def ansible(self, playbook, inventory=None):
"""
Run given playbook against the guest.
If the inventory is not specified, by default the guest user and host
is used.
"""
playbook = self._ansible_playbook_path(playbook)
inventory = inventory or f'{self._ssh_guest()},'
stdout, stderr = self.run(
f'stty cols {tmt.utils.OUTPUT_WIDTH}; ansible-playbook '
f'--ssh-common-args="{self._ssh_options(join=True)}" '
f'-e ansible_python_interpreter=auto'
f'{self._ansible_verbosity()} -i {self._ssh_guest()}, {playbook}')
f'{self._ansible_verbosity()} -i {inventory} {playbook}')
self._ansible_summary(stdout)

def execute(self, command, **kwargs):
Expand Down
5 changes: 4 additions & 1 deletion tmt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ def export(
# Init
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

_init_template_choices = ['empty', 'mini', 'base', 'full']
_init_template_choices = ['empty', 'mini', 'base', 'full', 'sti']
_init_templates = listed(_init_template_choices, join='or')
@main.command()
@click.pass_context
Expand All @@ -745,6 +745,7 @@ def init(context, path, template, force, **kwargs):
* 'mini' template contains a minimal plan and no tests,
* 'base' template contains a plan and a beakerlib test,
* 'full' template contains a 'full' story, an 'full' plan and a shell test.
* 'sti' template contains a plan for executing STI test tests/tests.yml
"""

# Check for existing tree
Expand Down Expand Up @@ -785,3 +786,5 @@ def init(context, path, template, force, **kwargs):
tmt.Test.create('/tests/example', 'shell', tree, force)
tmt.Plan.create('/plans/example', 'full', tree, force)
tmt.Story.create('/stories/example', 'full', tree, force)
elif template == 'sti':
tmt.Plan.create('/plans/sti', 'sti', tree, force)
12 changes: 10 additions & 2 deletions tmt/steps/execute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import shutil

from tmt.steps.execute import shell, beakerlib
from tmt.steps.execute import shell, beakerlib, sti
from tmt.utils import RUNNER
from fmf.utils import listed
from tmt.utils import GeneralError
Expand All @@ -18,6 +18,7 @@ class Execute(tmt.steps.Step):
# supported executors are not loaded automatically, import them and map them in how_map
how_map = {'shell': shell.ExecutorShell,
'beakerlib': beakerlib.ExecutorBeakerlib,
'sti': sti.ExecutorSTI
}

def __init__(self, data, plan):
Expand Down Expand Up @@ -55,9 +56,16 @@ def go(self):
""" Execute the test step """
super(Execute, self).go()

if not self.plan.provision.guests():
guests = self.plan.provision.guests()

if not guests:
raise tmt.utils.ExecuteError("No guests available for execution.")

# STI execution is special, delegate the whole `go` method to it.
if self.data['how'] == 'sti':
self.executor.go(guests, self.data, self.workdir)
return

lognames = ('stdout.log', 'stderr.log', 'nohup.out')

# Remove logs prior to write
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/execute/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def go(self, plan_workdir):
# API
def requires(self):
""" Returns packages required to run tests"""
pass
return ()

def results(self):
""" Returns results from executed tests """
Expand Down
59 changes: 59 additions & 0 deletions tmt/steps/execute/sti.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import os

from tmt.utils import GeneralError
from tmt.steps.execute.base import ExecutorBase


DEFAULT_PLAYBOOK = 'tests/tests.yml'
ANSIBLE_INVENTORY = """
[localhost]
sut ansible_host={hostname} ansible_user=root ansible_remote_tmp={remote_tmp}
"""


class ExecutorSTI(ExecutorBase):
""" Run tests using how: sti """
type = 'sti'

def _playbook(self, data):
if 'playbook' in data and data['playbook']:
playbook = data['playbook']
else:
playbook = DEFAULT_PLAYBOOK

if not os.path.exists(playbook):
raise GeneralError(f"Playbook '{playbook}' not found.")

return playbook

def _inventory(self, guest, workdir):
""" Provides Ansible inventory compared to STI """
hostname = guest.guest or guest.container

inventory_file = os.path.join(workdir, f'inventory-{hostname}')
# Note that we have to force remote_tmp because in rootless container
# it would default to '/root/.ansible/tmp' which is
# not accessible by ordinary users
inventory_content = ANSIBLE_INVENTORY.format(
hostname=hostname,
remote_tmp=os.path.expanduser('~/.ansible/tmp')
)

with open(inventory_file, 'w') as inventory:
inventory.write(inventory_content)

return inventory_file

def go(self, guests, data, workdir):
""" Run tests """
super(ExecutorSTI, self).go(workdir)

for guest in guests:
guest.ansible(
self._playbook(data),
inventory=self._inventory(guest, workdir)
)

def results(self):
""" Returns results from executed tests """
super(ExecutorSTI, self).results()
11 changes: 8 additions & 3 deletions tmt/steps/provision/podman.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

import tmt
import click

Expand Down Expand Up @@ -138,13 +140,16 @@ def start(self):
'-v', f'{tmt_workdir}:{tmt_workdir}:Z', '-itd', self.image],
message=f"Start container '{self.image}'.")[0].strip()

def ansible(self, playbook):
def ansible(self, playbook, inventory=None):
""" Prepare container using ansible playbook """
playbook = self._ansible_playbook_path(playbook)
inventory = inventory or f'{self.container},'
local_temp = os.path.expanduser('~/.ansible/tmp')
stdout, stderr = self.run(
f'stty cols {tmt.utils.OUTPUT_WIDTH}; '
f'{self.shell_env} podman unshare ansible-playbook'
f'{self._ansible_verbosity()} -c podman -i {self.container}, '
f'{self.shell_env} podman unshare '
f'ansible-playbook '
f'{self._ansible_verbosity()} -c podman -i {inventory} '
f'{playbook}')
self._ansible_summary(stdout)

Expand Down
7 changes: 7 additions & 0 deletions tmt/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@
how: beakerlib
""".lstrip()

PLAN['sti'] = """
summary:
Run STI test tests/tests.yml
execute:
how: sti
""".lstrip()

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Story Templates
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down

0 comments on commit 7bb7d1c

Please sign in to comment.