mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-03 04:12:31 +01:00
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:
parent
666dc16e58
commit
2188d8d633
56
test/systemvm/README.md
Normal file
56
test/systemvm/README.md
Normal 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
149
test/systemvm/__init__.py
Normal 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
|
||||
51
test/systemvm/test_hello_systemvm.py
Normal file
51
test/systemvm/test_hello_systemvm.py
Normal 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!')
|
||||
17
tools/vagrant/systemvm/Vagrantfile
vendored
17
tools/vagrant/systemvm/Vagrantfile
vendored
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user