Skip to content

Commit

Permalink
Add 'sti' execute plugin
Browse files Browse the repository at this point in the history
Support running Standard Test Interface tests via 'tmt'.

Example of the workflow

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

Signed-off-by: Miroslav Vadkerti <mvadkert@redhat.com>
  • Loading branch information
thrix committed May 28, 2020
1 parent 04b6173 commit 510b3ff
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 9 deletions.
33 changes: 33 additions & 0 deletions spec/steps/execute.fmf
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,36 @@ description: |
example: |
execute:
how: beakerlib

/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
15 changes: 11 additions & 4 deletions tmt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,14 +936,21 @@ 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, options=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()},'
options = f'{options} ' or ''
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'-e ansible_python_interpreter=auto {options}'
f'{self._ansible_verbosity()} -i {inventory} {playbook}')
self._ansible_summary(stdout)

def execute(self, command, **kwargs):
Expand Down
108 changes: 108 additions & 0 deletions tmt/steps/execute/sti.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import os
import click
import tmt

from tmt.utils import GeneralError

# The default playbook used to run the tests
DEFAULT_PLAYBOOK = 'tests/tests.yml'

# Ansible inventory file required to run the tests
ANSIBLE_INVENTORY = """
[localhost]
sut ansible_host={hostname} ansible_user=root ansible_remote_tmp={remote_tmp}
"""


class ExecutorSTI(tmt.steps.execute.ExecutePlugin):
""" Run tests using ansible according to Standard Test Interface spec """
_doc = """
Execute STI tests
An inventory file is prepared according to the STI specification
and ansible-playbook is used to run the given playbook.
"""

# Supported methods
_methods = [
tmt.steps.Method(
name='sti', doc=_doc, order=50),
]

@classmethod
def options(cls, how=None):
""" Prepare command line options for given method """
options = []
if how == 'sti':
options.append(click.option(
'-p', '--playbook', metavar='PLAYBOOK',
help=f"""
Ansible playbook to be run as a test.
By default '{DEFAULT_PLAYBOOK}'
"""))
return options + super().options(how)

def show(self):
""" Show discover details """
super().show(['playbook'])

def _playbook(self):
if 'playbook' in self.data and self.data['playbook']:
playbook = self.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):
""" Provides Ansible inventory compared to STI """
hostname = guest.guest or guest.container

inventory_file = os.path.join(
self.step.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 wake(self):
""" Wake up the plugin (override data with command line) """

value = self.opt('playbook')
if value:
self.data['playbook'] = value

def go(self):
""" Execute available tests """
super().go()

# Nothing to do in dry mode
if self.opt('dry'):
self._results = []
return

for guest in self.step.plan.provision.guests():
guest.ansible(
self._playbook(),
inventory=self._inventory(guest),
options=f'-e artifacts={self.step.workdir}'
)

def results(self):
""" Returns results from executed tests """
return []

def requires(self):
""" No packages are required """
return []
6 changes: 4 additions & 2 deletions tmt/steps/provision/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ def guest(self):
class GuestLocal(tmt.Guest):
""" Local Host """

def ansible(self, playbook):
def ansible(self, playbook, inventory=None, options=None):
""" Prepare localhost using ansible playbook """
playbook = self._ansible_playbook_path(playbook)
options = f'{options} ' or ''
stdout, stderr = self.run(
f'sudo sh -c "stty cols {tmt.utils.OUTPUT_WIDTH}; ansible-playbook'
f'{self._ansible_verbosity()} -c local -i localhost, {playbook}"')
f'{self._ansible_verbosity()} {options}'
f'-c local -i localhost, {playbook}"')
self._ansible_summary(stdout)

def execute(self, command, **kwargs):
Expand Down
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, options=None):
""" Prepare container using ansible playbook """
playbook = self._ansible_playbook_path(playbook)
inventory = inventory or f'{self.container},'
options = f'{options} ' or ''
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 ansible-playbook '
f'{self._ansible_verbosity()} -c podman -i {inventory} {options}'
f'{playbook}')
self._ansible_summary(stdout)

Expand Down

0 comments on commit 510b3ff

Please sign in to comment.