Pure python tests for systemvm

This approach is instead of serverspec, but filling the same purpose. It's
main advantage is that it uses nose and python, just like the existing
marvin-based integration test suite.
This commit is contained in:
Leo Simons 2014-08-06 13:18:50 +02:00 committed by wilderrodrigues
parent 666dc16e58
commit 2188d8d633
4 changed files with 264 additions and 9 deletions

56
test/systemvm/README.md Normal file
View File

@ -0,0 +1,56 @@
Requirements
============
To run these tests, first get the vagrant setup for the systemvm working,
see ../../tools/vagrant/systemvm.
Then, install dependencies
pip install nose paramiko python-vagrant envassert cuisine
Running tests
=============
Then run the tests using your favorite python unittest runner
nosetests-2.7
If you have already started the systemvm with 'vagrant up', that VM will get
used for all the tests.
If you have not started the systemvm yet, it will be started and stopped for
every test case. That's nice for test isolation, but it's very slow, so it is
not recommended.
You can also run these tests out of the box with PyDev or PyCharm or whatever.
Adding tests
============
Simply create new test_xxx.py files with test cases that extend from
SystemVMTestCase.
Use [envassert](https://pypi.python.org/pypi/envassert) checks to define
your test assertions.
Use [cuisine](https://pypi.python.org/pypi/cuisine),
[fab](https://pypi.python.org/pypi/Fabric), or
[paramiko](https://pypi.python.org/pypi/paramiko) to otherwise interact with
the systemvm. When you do, please consider creating your own little wrappers
around fab run. I.e. the pattern is
```
from __future__ import with_statement
from fabric.api import run, hide
def something_to_do(argument):
with hide("everything"):
result = run("do something %s" % argument).wrangle()
return "expected" in result
```
for a new kind of check and then in your test
```
class HelloSystemVMTestCase(SystemVMTestCase):
@attr(tags=["systemvm"], required_hardware="true")
def test_something(self):
assert something_to_do('foo')
```

149
test/systemvm/__init__.py Normal file
View File

@ -0,0 +1,149 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from vagrant import Vagrant
from unittest import TestCase
from paramiko.config import SSHConfig
from paramiko.client import SSHClient, AutoAddPolicy
from fabric.api import env
from envassert import file, detect
from StringIO import StringIO
from nose.plugins.attrib import attr
import os.path
_defaultVagrantDir = os.path.abspath(os.path.join(
os.path.basename(__file__), '..', '..', '..', 'tools', 'vagrant', 'systemvm'))
class SystemVM(object):
def __init__(self,
host='default',
vagrantDir=None,
controlVagrant=True):
global _defaultVagrantDir
self.host = host
self._controlVagrant = controlVagrant
if vagrantDir is None:
vagrantDir = _defaultVagrantDir
self._vagrant = Vagrant(root=vagrantDir)
self._startedVagrant = False
self._sshClient = None
self._sshConfigStr = None
self._sshConfig = None
self._sshHostConfig = None
def maybeUp(self):
if not self._controlVagrant:
return
state = self._vagrant.status(vm_name=self.host)[0].state
if state == Vagrant.NOT_CREATED:
self._vagrant.up(vm_name=self.host)
self._startedVagrant = True
elif state in [Vagrant.POWEROFF, Vagrant.SAVED, Vagrant.ABORTED]:
raise Exception(
"SystemVM testing does not support resume(), do not use vagrant suspend/halt")
elif state == Vagrant.RUNNING:
self._startedVagrant = False
else:
raise Exception("Unrecognized vagrant state %s" % state)
def maybeDestroy(self):
if not self._controlVagrant or not self._startedVagrant:
return
self._vagrant.destroy(vm_name=self.host)
if self._sshClient is not None:
self._sshClient.close()
def loadSshConfig(self):
if self._sshConfig is None:
self._sshConfigStr = self._vagrant.ssh_config(vm_name=self.host)
configObj = StringIO(self._sshConfigStr)
self._sshConfig = SSHConfig()
# noinspection PyTypeChecker
self._sshConfig.parse(configObj)
self._sshHostConfig = self._sshConfig.lookup(self.host)
@property
def sshConfig(self):
if self._sshConfig is None:
self.loadSshConfig()
return self._sshConfig
@property
def sshConfigStr(self):
if self._sshConfigStr is None:
self.loadSshConfig()
return self._sshConfigStr
@property
def sshClient(self):
if self._sshClient is None:
self.loadSshConfig()
self._sshClient = SSHClient()
self._sshClient.set_missing_host_key_policy(AutoAddPolicy())
self._sshClient.connect(self.hostname, self.sshPort, self.sshUser,
key_filename=self.sshKey, timeout=10)
return self._sshClient
@property
def hostname(self):
return self._sshHostConfig.get('hostname', self.host)
@property
def sshPort(self):
return int(self._sshHostConfig.get('port', 22))
@property
def sshUser(self):
return self._sshHostConfig.get('user', 'root')
@property
def sshKey(self):
return self._sshHostConfig.get('identityfile', '~/.ssh/id_rsa')
class SystemVMTestCase(TestCase):
@classmethod
def setUpClass(cls):
cls.systemvm = SystemVM()
cls.systemvm.maybeUp()
@classmethod
def tearDownClass(cls):
# noinspection PyUnresolvedReferences
cls.systemvm.maybeDestroy()
def setUp(self):
self.sshClient = self.systemvm.sshClient
# self._env_host_string_orig = env.host_string
env.host_string = "%s:%s" % (self.systemvm.hostname, self.systemvm.sshPort)
env.user = self.systemvm.sshUser
env.port = self.systemvm.sshPort
env.key_filename = self.systemvm.sshKey
env.use_ssh_config = True
env.abort_on_prompts = True
env.command_timeout = 10
env.timeout = 5
env.platform_family = detect.detect()
# this could break down when executing multiple test cases in parallel in the same python process
# def tearDown(self):
# env.host_string = self._env_host_string_orig

View File

@ -0,0 +1,51 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Example of using paramiko and envassert for systemvm tests."""
from nose.plugins.attrib import attr
from envassert import file, package, user
from cuisine import file_write
try:
from . import SystemVMTestCase
except (ImportError, ValueError):
from systemvm import SystemVMTestCase
class HelloSystemVMTestCase(SystemVMTestCase):
@attr(tags=["systemvm"], required_hardware="true")
def test_hello_systemvm_paramiko(self):
"""Test we can connect to the systemvm over ssh, low-level with paramiko"""
stdin, stdout, stderr = self.sshClient.exec_command('echo hello')
result = stdout.read().strip()
self.assertEqual('hello', result)
@attr(tags=["systemvm"], required_hardware="true")
def test_hello_systemvm_envassert(self):
"""Test we can run envassert assertions on the systemvm"""
assert file.exists('/etc/hosts')
for packageName in ['dnsmasq', 'haproxy', 'keepalived', 'curl']:
assert package.installed(packageName)
assert user.exists('cloud')
@attr(tags=["systemvm"], required_hardware="true")
def test_hello_systemvm_cuisine(self):
"""Test we can run cuisine on the systemvm"""
file_write('/tmp/run_cuisine', 'success!\n')
assert file.has_line('/tmp/run_cuisine', 'success!')

View File

@ -22,13 +22,12 @@ include RbConfig
VAGRANTFILE_API_VERSION = '2'
unless ENV['VPC_IP']
puts 'Please specify the VPC IP by settings the VPC_IP environment variable'
puts 'Example: export VPC_IP=192.168.56.30'
puts ''
exit 1
if ENV['VPC_IP']
puts 'You did not specify the VPC IP by settings the VPC_IP environment variable'
puts 'Using the default VPC_IP=192.168.56.30'
end
VPC_NAME='r-' + ENV['VPC_IP'].split('.').last + '-VM'
VPC_IP = ENV['VPC_IP'] || '192.168.56.30'
VPC_NAME='r-' + VPC_IP.split('.').last + '-VM'
if ARGV[0] == 'up'
iso_util=''
@ -69,12 +68,12 @@ end
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = 'cloudstack/systemvm'
config.vm.network 'private_network', ip: ENV['VPC_IP'], auto_config: false
config.vm.network 'private_network', ip: VPC_IP, auto_config: false
config.vm.synced_folder 'vagrant', '/vagrant', disabled: true
config.ssh.forward_agent = true
config.ssh.username = 'root'
config.ssh.host = ENV['VPC_IP']
config.ssh.host = VPC_IP
config.ssh.port = 3922
config.ssh.guest_port = 3922
@ -87,7 +86,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
'--medium', './systemvm.iso']
vb.customize('pre-boot', ['modifyvm', :id, '--nic1', 'none'])
extra_data='cmdline:console=hvc0 vpccidr=172.16.0.0/16 domain=devcloud.local dns1=8.8.8.8 dns2=8.8.8.4' +
" template=domP name=#{VPC_NAME} eth0ip=#{ENV['VPC_IP']}" +
" template=domP name=#{VPC_NAME} eth0ip=#{VPC_IP}" +
' eth0mask=255.255.255.0 type=vpcrouter disable_rp_filter=true'
vb.customize('pre-boot', ['setextradata', :id, 'VBoxInternal/Devices/pcbios/0/Config/DmiOEMVBoxRev', extra_data])
end