Skip to content

Commit

Permalink
Add submodule to run tests on remote machines.
Browse files Browse the repository at this point in the history
Includes a session-level fixture to turn on machines before tests run
and turn them off at end of session.

The scan test is parameterized on the profiles listed in config file,
and runs once per profile. In each profile, all hosts have known facts
compared to collected facts.

If no config file is found, the fixture quits and config file dependent
tests are skipped.

Closes #24
Closes #25
  • Loading branch information
kdelee committed Sep 15, 2017
1 parent e89a72d commit ba0d487
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 0 deletions.
9 changes: 9 additions & 0 deletions camayoc/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@

VAULT_PASSWORD_INPUT = 'Please enter your rho vault password:'
"""Vault password input prompt."""

VCENTER_DATA_CENTER = 0
"""The index of the VCenter data center in the MOB"""

VCENTER_CLUSTER = 1
"""The index of the cluster in the data center in the VCenter MOB"""

VCENTER_HOST = 0
"""The index of the host in the cluster in the VCenter MOB"""
2 changes: 2 additions & 0 deletions camayoc/tests/remote/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# coding=utf-8
"""Module containing tools for controlling and testing remote systems."""
134 changes: 134 additions & 0 deletions camayoc/tests/remote/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# coding=utf-8
"""Pytest customizations and fixtures for tests to execute on remote hosts."""
import os
import pytest
from camayoc import config
from camayoc.exceptions import ConfigFileNotFoundError
from time import sleep
from camayoc.constants import (
VCENTER_DATA_CENTER,
VCENTER_CLUSTER,
VCENTER_HOST,
)


@pytest.fixture(scope='session', autouse=True)
def manage_systems(request):
"""Fixture that manages remote systems.
At the beginning of a session, hosts will be turned on. We must wait for
the machines to complete booting so there is about 30 seconds of overhead
at the beginning before test begin executing.
At the end of the session, they are turned off.
Hosts must have a "provider" specified in the config file, otherwise they
will be skipped and assumed to be accessible by rho.
Hosts that are powered on at the beginning of the session are then powered
down at the end of the session.
Example::
#/home/user/.config/camayoc/config.yaml
rho:
auths:
- username: root
name: sonar
sshkeyfile: /home/elijah/.ssh/id_sonar_jenkins_rsa
hosts:
- hostname: string-name-of-vm-on-vcenter
ip: XX.XX.XXX.XX
provider: vcenter
facts:
connection.port: 22
cpu.count: 1
profiles:
- name: profile1
auths:
- auth1
hosts:
- XX.XX.XXX.XX
vcenter:
hostname: **Required** -- url to vcenter instance
username: **optional** -- will first try environment variable $VCUSER
password: **optional** -- will first try environment variable $VCPASS
.. warning::
It is a bad idea to have multiple sessions of pytest running
concurrently against the same hosts/config file.
If one session ends before another, it will power down the
machines leaving the other one to fail.
"""
from pyVim.connect import SmartConnect, Disconnect
import ssl

try:
cfg = config.get_config()
except ConfigFileNotFoundError:
# if we don't have a config file, do nothing
return

s = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
s.verify_mode = ssl.CERT_NONE
# try and make a secure connection first
if 'vcenter' in cfg.keys():
if os.getenv('VCUSER', False):
vcuser = os.environ['VCUSER']
else:
vcuser = cfg['vcenter']['username']
if os.getenv('VCPASS', False):
vcpassword = os.environ['VCPASS']
else:
vcpassword = cfg['vcenter']['password']
try:
c = SmartConnect(
host=cfg['vcenter']['hostname'],
user=vcuser,
pwd=vcpassword
)
except ssl.SSLError:
c = SmartConnect(
host=cfg['vcenter']['hostname'],
user=vcuser,
pwd=vcpassword,
sslContext=s
)
# these index choices are particular to our VCenter setup
fldr = c.content.rootFolder.childEntity[VCENTER_DATA_CENTER].hostFolder
vm_host = fldr.childEntity[VCENTER_CLUSTER].host[VCENTER_HOST]
# the host has a property "vm" which is a list of VMs
vms = vm_host.vm
for host in cfg['rho']['hosts']:
if ('provider' in host.keys()) and (
host['provider'] == 'vcenter'):
for vm in vms:
if vm.name == host['hostname']:
if vm.runtime.powerState == 'poweredOff':
vm.PowerOnVM_Task()
# need to wait for machines to boot and start sshd
sleep(30)

def shutdown_systems():
"""Turn off hosts at the end of a session.
This runs once at the end of each session.
"""
try:
cfg = config.get_config()
except ConfigFileNotFoundError:
# if we don't have a config file, do nothing
return

for host in cfg['rho']['hosts']:
if ('provider' in host.keys()) and (host['provider'] == 'vcenter'):
for vm in vms:
if vm.name == host['hostname']:
if vm.runtime.powerState == 'poweredOn':
vm.PowerOffVM_Task()
Disconnect(c)

# this line makes this part of the fixture only run at end of session
# pytest provides us this request object
request.addfinalizer(shutdown_systems) # noqa: F821
2 changes: 2 additions & 0 deletions camayoc/tests/remote/rho/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# coding=utf-8
"""Module of tests for running rho against remote systems."""
117 changes: 117 additions & 0 deletions camayoc/tests/remote/rho/test_scan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# coding=utf-8
"""Tests for ``rho scan`` commands.
These tests are parametrized on the profiles listed in the config file. If scan
is successful, all facts will be validated before test fails, and then all
failed facts will be reported with associated host.
:caseautomation: automated
:casecomponent: scan
:caseimportance: high
:requirement: RHO
:testtype: functional
:upstream: yes
"""

import csv
import os
import pexpect
import pytest
from io import BytesIO

from camayoc import config
from camayoc.tests.rho.utils import auth_add, input_vault_password
from camayoc.exceptions import ConfigFileNotFoundError


def profiles():
"""Gather profiles from config file."""
try:
profs = config.get_config()['rho']['profiles']
except ConfigFileNotFoundError:
profs = []
return profs


# The test will execute once per profile.
@pytest.mark.parametrize('profile', profiles())
def test_scan(isolated_filesystem, profile):
"""Scan the machines listed in profile.
Then test facts for each host in profile.
:id: 6ee18084-86db-45ea-8fdd-59fed5639170
:description: Scan a machine and test collected facts.
:steps:
1) Run ``rho scan --profile <profile> --reportfile <reportfile>``
2) Validate collected facts against known facts in config file
:expectedresults:
A scan is performed and a report with valid facts are
generated.
"""
cfg = config.get_config()

for auth in cfg['rho']['auths']:
auth_add({
'name': auth['name'],
'username': auth['username'],
'sshkeyfile': auth['sshkeyfile'],
})

auths = ' '.join(item for item in profile['auths'])
hosts = ' '.join(item for item in profile['hosts'])
rho_profile_add = pexpect.spawn(
'rho profile add --name {} --auth {} --hosts {}'
.format(profile['name'], auths, hosts)
)
input_vault_password(rho_profile_add)
assert rho_profile_add.expect(
'Profile "{}" was added'.format(profile['name'])) == 0
assert rho_profile_add.expect(pexpect.EOF) == 0
rho_profile_add.close()
assert rho_profile_add.exitstatus == 0

reportfile = '{}-report.csv'.format(profile['name'])
rho_scan = pexpect.spawn(
'rho scan --profile {} --reportfile {}'
.format(profile['name'], reportfile),
timeout=300,
)
input_vault_password(rho_scan)
rho_scan.logfile = BytesIO()
assert rho_scan.expect(pexpect.EOF) == 0
logfile = rho_scan.logfile.getvalue().decode('utf-8')
rho_scan.logfile.close()
rho_scan.close()
assert rho_scan.exitstatus == 0, logfile
assert os.path.isfile(reportfile)

# we will collect errors in scanned facts and report at end of test on the
# results if collected facts do not match expected values.
scan_errors = []
with open(reportfile) as csvfile:
scan_results = csv.DictReader(csvfile)
# each row corresponds to a scanned host
for row in scan_results:
known_facts = {}
for host in cfg['rho']['hosts']:
# find the facts for the scanned host we are inspecting
if host['ip'] == row['connection.host']:
known_facts = host['facts']
break
for fact in known_facts.keys():
try:
assert (str(known_facts[fact]) in str(row[fact]))
except AssertionError:
msg = 'Test failed on host {} in profile {}. \
Scan found {} = {} instead of {}.'.format(
host['ip'],
profile['name'],
fact,
row[fact],
known_facts[fact],
)
scan_errors.append(msg)
if len(scan_errors) != 0:
msg = '\n'.join(e for e in scan_errors)
raise AssertionError(msg)
3 changes: 3 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ reference for developers, not a gospel.
api/tests.rst
api/tests.test_cli.rst
api/tests.test_config.rst
api/camayoc.tests.remote.rst
api/camayoc.tests.remote.conftest.rst
api/camayoc.tests.remote.rho.test_scan.rst
7 changes: 7 additions & 0 deletions docs/api/camayoc.tests.remote.conftest.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
camayoc\.tests\.remote\.conftest module
=======================================

.. automodule:: camayoc.tests.remote.conftest
:members:
:undoc-members:
:show-inheritance:
15 changes: 15 additions & 0 deletions docs/api/camayoc.tests.remote.rho.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
camayoc\.tests\.remote\.rho package
===================================

.. automodule:: camayoc.tests.remote.rho
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

.. toctree::

camayoc.tests.remote.rho.test_scan

7 changes: 7 additions & 0 deletions docs/api/camayoc.tests.remote.rho.test_scan.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
camayoc\.tests\.remote\.rho\.test\_scan module
==============================================

.. automodule:: camayoc.tests.remote.rho.test_scan
:members:
:undoc-members:
:show-inheritance:
22 changes: 22 additions & 0 deletions docs/api/camayoc.tests.remote.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
camayoc\.tests\.remote package
==============================

.. automodule:: camayoc.tests.remote
:members:
:undoc-members:
:show-inheritance:

Subpackages
-----------

.. toctree::

camayoc.tests.remote.rho

Submodules
----------

.. toctree::

camayoc.tests.remote.conftest

1 change: 1 addition & 0 deletions docs/api/camayoc.tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Subpackages

.. toctree::

camayoc.tests.remote
camayoc.tests.rho

Submodules
Expand Down

0 comments on commit ba0d487

Please sign in to comment.