Initial commit for stress framework from tempest
This commit is contained in:
parent
df94348675
commit
3a3f7d7d9c
17
CONTRIBUTING.rst
Normal file
17
CONTRIBUTING.rst
Normal file
@ -0,0 +1,17 @@
|
||||
If you would like to contribute to the development of OpenStack, you must
|
||||
follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
If you already have a good understanding of how the system works and your
|
||||
OpenStack accounts are set up, you can skip to the development workflow
|
||||
section of this documentation to learn how changes to OpenStack should be
|
||||
submitted for review via the Gerrit tool:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/tempest_stress
|
4
HACKING.rst
Normal file
4
HACKING.rst
Normal file
@ -0,0 +1,4 @@
|
||||
tempest_stress Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
28
LICENSE
28
LICENSE
@ -1,3 +1,4 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
@ -172,30 +173,3 @@
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed 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.
|
||||
|
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@ -0,0 +1,6 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
64
README.rst
Normal file
64
README.rst
Normal file
@ -0,0 +1,64 @@
|
||||
.. _stress_field_guide:
|
||||
|
||||
Tempest Field Guide to Stress Tests
|
||||
===================================
|
||||
|
||||
OpenStack is a distributed, asynchronous system that is prone to race condition
|
||||
bugs. These bugs will not be easily found during
|
||||
functional testing but will be encountered by users in large deployments in a
|
||||
way that is hard to debug. The stress test tries to cause these bugs to happen
|
||||
in a more controlled environment.
|
||||
|
||||
Stress tests are designed to stress an OpenStack environment by running a high
|
||||
workload against it and seeing what breaks. The stress test framework runs
|
||||
several test jobs in parallel and can run any existing test in Tempest as a
|
||||
stress job.
|
||||
|
||||
Environment
|
||||
-----------
|
||||
This particular framework assumes your working Nova cluster understands Nova
|
||||
API 2.0. The stress tests can read the logs from the cluster. To enable this
|
||||
you have to provide the hostname to call 'nova-manage' and
|
||||
the private key and user name for ssh to the cluster in the
|
||||
[stress] section of tempest.conf. You also need to provide the
|
||||
location of the log files:
|
||||
|
||||
target_logfiles = "regexp to all log files to be checked for errors"
|
||||
target_private_key_path = "private ssh key for controller and log file nodes"
|
||||
target_ssh_user = "username for controller and log file nodes"
|
||||
target_controller = "hostname or ip of controller node (for nova-manage)
|
||||
log_check_interval = "time between checking logs for errors (default 60s)"
|
||||
|
||||
To activate logging on your console please make sure that you activate `use_stderr`
|
||||
in tempest.conf or use the default `logging.conf.sample` file.
|
||||
|
||||
Running default stress test set
|
||||
-------------------------------
|
||||
|
||||
The stress test framework can automatically discover test inside the tempest
|
||||
test suite. All test flag with the `@stresstest` decorator will be executed.
|
||||
In order to use this discovery you have to install tempest CLI, be in the
|
||||
tempest root directory and execute the following:
|
||||
|
||||
tempest run-stress -a -d 30
|
||||
|
||||
Running the sample test
|
||||
-----------------------
|
||||
|
||||
To test installation, do the following:
|
||||
|
||||
tempest run-stress -t tempest/stress/etc/server-create-destroy-test.json -d 30
|
||||
|
||||
This sample test tries to create a few VMs and kill a few VMs.
|
||||
|
||||
|
||||
Additional Tools
|
||||
----------------
|
||||
|
||||
Sometimes the tests don't finish, or there are failures. In these
|
||||
cases, you may want to clean out the nova cluster. We have provided
|
||||
some scripts to do this in the ``tools`` subdirectory.
|
||||
You can use the following script to destroy any keypairs,
|
||||
floating ips, and servers:
|
||||
|
||||
tempest/stress/tools/cleanup.py
|
75
doc/source/conf.py
Normal file
75
doc/source/conf.py
Normal file
@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Licensed 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'tempest_stress'
|
||||
copyright = u'2016, OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
4
doc/source/contributing.rst
Normal file
4
doc/source/contributing.rst
Normal file
@ -0,0 +1,4 @@
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
.. include:: ../../CONTRIBUTING.rst
|
24
doc/source/index.rst
Normal file
24
doc/source/index.rst
Normal file
@ -0,0 +1,24 @@
|
||||
.. tempest_stress documentation master file, created by
|
||||
sphinx-quickstart on Tue Jul 9 22:26:36 2013.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to tempest_stress's documentation!
|
||||
========================================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
12
doc/source/installation.rst
Normal file
12
doc/source/installation.rst
Normal file
@ -0,0 +1,12 @@
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install tempest_stress
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv tempest_stress
|
||||
$ pip install tempest_stress
|
1
doc/source/readme.rst
Normal file
1
doc/source/readme.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../README.rst
|
7
doc/source/usage.rst
Normal file
7
doc/source/usage.rst
Normal file
@ -0,0 +1,7 @@
|
||||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
To use tempest_stress in a project::
|
||||
|
||||
import tempest_stress
|
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@ -0,0 +1,8 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
pbr>=1.6 # Apache-2.0
|
||||
Babel>=1.3
|
||||
oslo.log>=1.14.0 # Apache-2.0
|
||||
tempest>=1333.0.0
|
54
setup.cfg
Normal file
54
setup.cfg
Normal file
@ -0,0 +1,54 @@
|
||||
[metadata]
|
||||
name = tempest_stress
|
||||
summary = OpenStack is a distributed, asynchronous system that is prone to race condition
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
tempest_stress
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
run-tempest-stress = tempest_stress.cmd.run_stress:main
|
||||
|
||||
tempest.test_plugins =
|
||||
tempest_stress = tempest_stress.plugin:TempestStressPlugin
|
||||
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[compile_catalog]
|
||||
directory = tempest_stress/locale
|
||||
domain = tempest_stress
|
||||
|
||||
[update_catalog]
|
||||
domain = tempest_stress
|
||||
output_dir = tempest_stress/locale
|
||||
input_file = tempest_stress/locale/tempest_stress.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = tempest_stress/locale/tempest_stress.pot
|
29
setup.py
Normal file
29
setup.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
0
tempest_stress/__init__.py
Normal file
0
tempest_stress/__init__.py
Normal file
0
tempest_stress/actions/__init__.py
Normal file
0
tempest_stress/actions/__init__.py
Normal file
42
tempest_stress/actions/server_create_destroy.py
Normal file
42
tempest_stress/actions/server_create_destroy.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright 2013 Quanta Research Cambridge, Inc.
|
||||
#
|
||||
# Licensed 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 tempest.common.utils import data_utils
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
import tempest.stress.stressaction as stressaction
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class ServerCreateDestroyTest(stressaction.StressAction):
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
self.image = CONF.compute.image_ref
|
||||
self.flavor = CONF.compute.flavor_ref
|
||||
|
||||
def run(self):
|
||||
name = data_utils.rand_name(self.__class__.__name__ + "-instance")
|
||||
self.logger.info("creating %s" % name)
|
||||
server = self.manager.servers_client.create_server(
|
||||
name=name, imageRef=self.image, flavorRef=self.flavor)['server']
|
||||
server_id = server['id']
|
||||
waiters.wait_for_server_status(self.manager.servers_client, server_id,
|
||||
'ACTIVE')
|
||||
self.logger.info("created %s" % server_id)
|
||||
self.logger.info("deleting %s" % name)
|
||||
self.manager.servers_client.delete_server(server_id)
|
||||
waiters.wait_for_server_termination(self.manager.servers_client,
|
||||
server_id)
|
||||
self.logger.info("deleted %s" % server_id)
|
200
tempest_stress/actions/ssh_floating.py
Normal file
200
tempest_stress/actions/ssh_floating.py
Normal file
@ -0,0 +1,200 @@
|
||||
# Licensed 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.
|
||||
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import test_utils
|
||||
import tempest.stress.stressaction as stressaction
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class FloatingStress(stressaction.StressAction):
|
||||
|
||||
# from the scenario manager
|
||||
def ping_ip_address(self, ip_address):
|
||||
cmd = ['ping', '-c1', '-w1', ip_address]
|
||||
|
||||
proc = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
proc.communicate()
|
||||
success = proc.returncode == 0
|
||||
return success
|
||||
|
||||
def tcp_connect_scan(self, addr, port):
|
||||
# like tcp
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.connect((addr, port))
|
||||
except socket.error as exc:
|
||||
self.logger.info("%s(%s): %s", self.server_id, self.floating['ip'],
|
||||
str(exc))
|
||||
return False
|
||||
self.logger.info("%s(%s): Connected :)", self.server_id,
|
||||
self.floating['ip'])
|
||||
s.close()
|
||||
return True
|
||||
|
||||
def check_port_ssh(self):
|
||||
def func():
|
||||
return self.tcp_connect_scan(self.floating['ip'], 22)
|
||||
if not test_utils.call_until_true(func, self.check_timeout,
|
||||
self.check_interval):
|
||||
raise RuntimeError("Cannot connect to the ssh port.")
|
||||
|
||||
def check_icmp_echo(self):
|
||||
self.logger.info("%s(%s): Pinging..",
|
||||
self.server_id, self.floating['ip'])
|
||||
|
||||
def func():
|
||||
return self.ping_ip_address(self.floating['ip'])
|
||||
if not test_utils.call_until_true(func, self.check_timeout,
|
||||
self.check_interval):
|
||||
raise RuntimeError("%s(%s): Cannot ping the machine.",
|
||||
self.server_id, self.floating['ip'])
|
||||
self.logger.info("%s(%s): pong :)",
|
||||
self.server_id, self.floating['ip'])
|
||||
|
||||
def _create_vm(self):
|
||||
self.name = name = data_utils.rand_name(
|
||||
self.__class__.__name__ + "-instance")
|
||||
servers_client = self.manager.servers_client
|
||||
self.logger.info("creating %s" % name)
|
||||
vm_args = self.vm_extra_args.copy()
|
||||
vm_args['security_groups'] = [self.sec_grp]
|
||||
server = servers_client.create_server(name=name, imageRef=self.image,
|
||||
flavorRef=self.flavor,
|
||||
**vm_args)['server']
|
||||
self.server_id = server['id']
|
||||
if self.wait_after_vm_create:
|
||||
waiters.wait_for_server_status(self.manager.servers_client,
|
||||
self.server_id, 'ACTIVE')
|
||||
|
||||
def _destroy_vm(self):
|
||||
self.logger.info("deleting %s" % self.server_id)
|
||||
self.manager.servers_client.delete_server(self.server_id)
|
||||
waiters.wait_for_server_termination(self.manager.servers_client,
|
||||
self.server_id)
|
||||
self.logger.info("deleted %s" % self.server_id)
|
||||
|
||||
def _create_sec_group(self):
|
||||
sec_grp_cli = self.manager.compute_security_groups_client
|
||||
s_name = data_utils.rand_name(self.__class__.__name__ + '-sec_grp')
|
||||
s_description = data_utils.rand_name('desc')
|
||||
self.sec_grp = sec_grp_cli.create_security_group(
|
||||
name=s_name, description=s_description)['security_group']
|
||||
create_rule = sec_grp_cli.create_security_group_rule
|
||||
create_rule(parent_group_id=self.sec_grp['id'], ip_protocol='tcp',
|
||||
from_port=22, to_port=22)
|
||||
create_rule(parent_group_id=self.sec_grp['id'], ip_protocol='icmp',
|
||||
from_port=-1, to_port=-1)
|
||||
|
||||
def _destroy_sec_grp(self):
|
||||
sec_grp_cli = self.manager.compute_security_groups_client
|
||||
sec_grp_cli.delete_security_group(self.sec_grp['id'])
|
||||
|
||||
def _create_floating_ip(self):
|
||||
floating_cli = self.manager.compute_floating_ips_client
|
||||
self.floating = (floating_cli.create_floating_ip(self.floating_pool)
|
||||
['floating_ip'])
|
||||
|
||||
def _destroy_floating_ip(self):
|
||||
cli = self.manager.compute_floating_ips_client
|
||||
cli.delete_floating_ip(self.floating['id'])
|
||||
cli.wait_for_resource_deletion(self.floating['id'])
|
||||
self.logger.info("Deleted Floating IP %s", str(self.floating['ip']))
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
self.image = CONF.compute.image_ref
|
||||
self.flavor = CONF.compute.flavor_ref
|
||||
self.vm_extra_args = kwargs.get('vm_extra_args', {})
|
||||
self.wait_after_vm_create = kwargs.get('wait_after_vm_create',
|
||||
True)
|
||||
self.new_vm = kwargs.get('new_vm', False)
|
||||
self.new_sec_grp = kwargs.get('new_sec_group', False)
|
||||
self.new_floating = kwargs.get('new_floating', False)
|
||||
self.reboot = kwargs.get('reboot', False)
|
||||
self.floating_pool = kwargs.get('floating_pool', None)
|
||||
self.verify = kwargs.get('verify', ('check_port_ssh',
|
||||
'check_icmp_echo'))
|
||||
self.check_timeout = kwargs.get('check_timeout', 120)
|
||||
self.check_interval = kwargs.get('check_interval', 1)
|
||||
self.wait_for_disassociate = kwargs.get('wait_for_disassociate',
|
||||
True)
|
||||
|
||||
# allocate floating
|
||||
if not self.new_floating:
|
||||
self._create_floating_ip()
|
||||
# add security group
|
||||
if not self.new_sec_grp:
|
||||
self._create_sec_group()
|
||||
# create vm
|
||||
if not self.new_vm:
|
||||
self._create_vm()
|
||||
|
||||
def wait_disassociate(self):
|
||||
cli = self.manager.compute_floating_ips_client
|
||||
|
||||
def func():
|
||||
floating = (cli.show_floating_ip(self.floating['id'])
|
||||
['floating_ip'])
|
||||
return floating['instance_id'] is None
|
||||
|
||||
if not test_utils.call_until_true(func, self.check_timeout,
|
||||
self.check_interval):
|
||||
raise RuntimeError("IP disassociate timeout!")
|
||||
|
||||
def run_core(self):
|
||||
cli = self.manager.compute_floating_ips_client
|
||||
cli.associate_floating_ip_to_server(self.floating['ip'],
|
||||
self.server_id)
|
||||
for method in self.verify:
|
||||
m = getattr(self, method)
|
||||
m()
|
||||
cli.disassociate_floating_ip_from_server(self.floating['ip'],
|
||||
self.server_id)
|
||||
if self.wait_for_disassociate:
|
||||
self.wait_disassociate()
|
||||
|
||||
def run(self):
|
||||
if self.new_sec_grp:
|
||||
self._create_sec_group()
|
||||
if self.new_floating:
|
||||
self._create_floating_ip()
|
||||
if self.new_vm:
|
||||
self._create_vm()
|
||||
if self.reboot:
|
||||
self.manager.servers_client.reboot(self.server_id, 'HARD')
|
||||
waiters.wait_for_server_status(self.manager.servers_client,
|
||||
self.server_id, 'ACTIVE')
|
||||
|
||||
self.run_core()
|
||||
|
||||
if self.new_vm:
|
||||
self._destroy_vm()
|
||||
if self.new_floating:
|
||||
self._destroy_floating_ip()
|
||||
if self.new_sec_grp:
|
||||
self._destroy_sec_grp()
|
||||
|
||||
def tearDown(self):
|
||||
if not self.new_vm:
|
||||
self._destroy_vm()
|
||||
if not self.new_floating:
|
||||
self._destroy_floating_ip()
|
||||
if not self.new_sec_grp:
|
||||
self._destroy_sec_grp()
|
92
tempest_stress/actions/unit_test.py
Normal file
92
tempest_stress/actions/unit_test.py
Normal file
@ -0,0 +1,92 @@
|
||||
# Licensed 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 oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
from tempest import config
|
||||
import tempest.stress.stressaction as stressaction
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class SetUpClassRunTime(object):
|
||||
|
||||
process = 'process'
|
||||
action = 'action'
|
||||
application = 'application'
|
||||
|
||||
allowed = set((process, action, application))
|
||||
|
||||
@classmethod
|
||||
def validate(cls, name):
|
||||
if name not in cls.allowed:
|
||||
raise KeyError("\'%s\' not a valid option" % name)
|
||||
|
||||
|
||||
class UnitTest(stressaction.StressAction):
|
||||
"""This is a special action for running existing unittests as stress test.
|
||||
|
||||
You need to pass ``test_method`` and ``class_setup_per``
|
||||
using ``kwargs`` in the JSON descriptor;
|
||||
``test_method`` should be the fully qualified name of a unittest,
|
||||
``class_setup_per`` should be one from:
|
||||
``application``: once in the stress job lifetime
|
||||
``process``: once in the worker process lifetime
|
||||
``action``: on each action
|
||||
Not all combination working in every case.
|
||||
"""
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
method = kwargs['test_method'].split('.')
|
||||
self.test_method = method.pop()
|
||||
self.klass = importutils.import_class('.'.join(method))
|
||||
self.logger = logging.getLogger('.'.join(method))
|
||||
# valid options are 'process', 'application' , 'action'
|
||||
self.class_setup_per = kwargs.get('class_setup_per',
|
||||
SetUpClassRunTime.process)
|
||||
SetUpClassRunTime.validate(self.class_setup_per)
|
||||
|
||||
if self.class_setup_per == SetUpClassRunTime.application:
|
||||
self.klass.setUpClass()
|
||||
self.setupclass_called = False
|
||||
|
||||
@property
|
||||
def action(self):
|
||||
if self.test_method:
|
||||
return self.test_method
|
||||
return super(UnitTest, self).action
|
||||
|
||||
def run_core(self):
|
||||
res = self.klass(self.test_method).run()
|
||||
if res.errors:
|
||||
raise RuntimeError(res.errors)
|
||||
|
||||
def run(self):
|
||||
if self.class_setup_per != SetUpClassRunTime.application:
|
||||
if (self.class_setup_per == SetUpClassRunTime.action
|
||||
or self.setupclass_called is False):
|
||||
self.klass.setUpClass()
|
||||
self.setupclass_called = True
|
||||
|
||||
try:
|
||||
self.run_core()
|
||||
finally:
|
||||
if (CONF.stress.leave_dirty_stack is False
|
||||
and self.class_setup_per == SetUpClassRunTime.action):
|
||||
self.klass.tearDownClass()
|
||||
else:
|
||||
self.run_core()
|
||||
|
||||
def tearDown(self):
|
||||
if self.class_setup_per != SetUpClassRunTime.action:
|
||||
self.klass.tearDownClass()
|
70
tempest_stress/actions/volume_attach_delete.py
Normal file
70
tempest_stress/actions/volume_attach_delete.py
Normal file
@ -0,0 +1,70 @@
|
||||
# (c) 2013 Deutsche Telekom AG
|
||||
# Licensed 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 tempest.common.utils import data_utils
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
import tempest.stress.stressaction as stressaction
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class VolumeAttachDeleteTest(stressaction.StressAction):
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
self.image = CONF.compute.image_ref
|
||||
self.flavor = CONF.compute.flavor_ref
|
||||
|
||||
def run(self):
|
||||
# Step 1: create volume
|
||||
name = data_utils.rand_name(self.__class__.__name__ + "-volume")
|
||||
self.logger.info("creating volume: %s" % name)
|
||||
volume = self.manager.volumes_client.create_volume(
|
||||
display_name=name, size=CONF.volume.volume_size)['volume']
|
||||
self.manager.volumes_client.wait_for_volume_status(volume['id'],
|
||||
'available')
|
||||
self.logger.info("created volume: %s" % volume['id'])
|
||||
|
||||
# Step 2: create vm instance
|
||||
vm_name = data_utils.rand_name(self.__class__.__name__ + "-instance")
|
||||
self.logger.info("creating vm: %s" % vm_name)
|
||||
server = self.manager.servers_client.create_server(
|
||||
name=vm_name, imageRef=self.image, flavorRef=self.flavor)['server']
|
||||
server_id = server['id']
|
||||
waiters.wait_for_server_status(self.manager.servers_client, server_id,
|
||||
'ACTIVE')
|
||||
self.logger.info("created vm %s" % server_id)
|
||||
|
||||
# Step 3: attach volume to vm
|
||||
self.logger.info("attach volume (%s) to vm %s" %
|
||||
(volume['id'], server_id))
|
||||
self.manager.servers_client.attach_volume(server_id,
|
||||
volumeId=volume['id'],
|
||||
device='/dev/vdc')
|
||||
self.manager.volumes_client.wait_for_volume_status(volume['id'],
|
||||
'in-use')
|
||||
self.logger.info("volume (%s) attached to vm %s" %
|
||||
(volume['id'], server_id))
|
||||
|
||||
# Step 4: delete vm
|
||||
self.logger.info("deleting vm: %s" % vm_name)
|
||||
self.manager.servers_client.delete_server(server_id)
|
||||
waiters.wait_for_server_termination(self.manager.servers_client,
|
||||
server_id)
|
||||
self.logger.info("deleted vm: %s" % server_id)
|
||||
|
||||
# Step 5: delete volume
|
||||
self.logger.info("deleting volume: %s" % volume['id'])
|
||||
self.manager.volumes_client.delete_volume(volume['id'])
|
||||
self.manager.volumes_client.wait_for_resource_deletion(volume['id'])
|
||||
self.logger.info("deleted volume: %s" % volume['id'])
|
233
tempest_stress/actions/volume_attach_verify.py
Normal file
233
tempest_stress/actions/volume_attach_verify.py
Normal file
@ -0,0 +1,233 @@
|
||||
# Licensed 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.
|
||||
|
||||
import re
|
||||
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.common.utils.linux import remote_client
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import test_utils
|
||||
import tempest.stress.stressaction as stressaction
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class VolumeVerifyStress(stressaction.StressAction):
|
||||
|
||||
def _create_keypair(self):
|
||||
keyname = data_utils.rand_name("key")
|
||||
self.key = (self.manager.keypairs_client.create_keypair(name=keyname)
|
||||
['keypair'])
|
||||
|
||||
def _delete_keypair(self):
|
||||
self.manager.keypairs_client.delete_keypair(self.key['name'])
|
||||
|
||||
def _create_vm(self):
|
||||
self.name = name = data_utils.rand_name(
|
||||
self.__class__.__name__ + "-instance")
|
||||
servers_client = self.manager.servers_client
|
||||
self.logger.info("creating %s" % name)
|
||||
vm_args = self.vm_extra_args.copy()
|
||||
vm_args['security_groups'] = [self.sec_grp]
|
||||
vm_args['key_name'] = self.key['name']
|
||||
server = servers_client.create_server(name=name, imageRef=self.image,
|
||||
flavorRef=self.flavor,
|
||||
**vm_args)['server']
|
||||
self.server_id = server['id']
|
||||
waiters.wait_for_server_status(self.manager.servers_client,
|
||||
self.server_id, 'ACTIVE')
|
||||
|
||||
def _destroy_vm(self):
|
||||
self.logger.info("deleting server: %s" % self.server_id)
|
||||
self.manager.servers_client.delete_server(self.server_id)
|
||||
waiters.wait_for_server_termination(self.manager.servers_client,
|
||||
self.server_id)
|
||||
self.logger.info("deleted server: %s" % self.server_id)
|
||||
|
||||
def _create_sec_group(self):
|
||||
sec_grp_cli = self.manager.compute_security_groups_client
|
||||
s_name = data_utils.rand_name(self.__class__.__name__ + '-sec_grp')
|
||||
s_description = data_utils.rand_name('desc')
|
||||
self.sec_grp = sec_grp_cli.create_security_group(
|
||||
name=s_name, description=s_description)['security_group']
|
||||
create_rule = sec_grp_cli.create_security_group_rule
|
||||
create_rule(parent_group_id=self.sec_grp['id'], ip_protocol='tcp',
|
||||
from_port=22, to_port=22)
|
||||
create_rule(parent_group_id=self.sec_grp['id'], ip_protocol='icmp',
|
||||
from_port=-1, to_port=-1)
|
||||
|
||||
def _destroy_sec_grp(self):
|
||||
sec_grp_cli = self.manager.compute_security_groups_client
|
||||
sec_grp_cli.delete_security_group(self.sec_grp['id'])
|
||||
|
||||
def _create_floating_ip(self):
|
||||
floating_cli = self.manager.compute_floating_ips_client
|
||||
self.floating = (floating_cli.create_floating_ip(self.floating_pool)
|
||||
['floating_ip'])
|
||||
|
||||
def _destroy_floating_ip(self):
|
||||
cli = self.manager.compute_floating_ips_client
|
||||
cli.delete_floating_ip(self.floating['id'])
|
||||
cli.wait_for_resource_deletion(self.floating['id'])
|
||||
self.logger.info("Deleted Floating IP %s", str(self.floating['ip']))
|
||||
|
||||
def _create_volume(self):
|
||||
name = data_utils.rand_name(self.__class__.__name__ + "-volume")
|
||||
self.logger.info("creating volume: %s" % name)
|
||||
volumes_client = self.manager.volumes_client
|
||||
self.volume = volumes_client.create_volume(
|
||||
display_name=name, size=CONF.volume.volume_size)['volume']
|
||||
volumes_client.wait_for_volume_status(self.volume['id'],
|
||||
'available')
|
||||
self.logger.info("created volume: %s" % self.volume['id'])
|
||||
|
||||
def _delete_volume(self):
|
||||
self.logger.info("deleting volume: %s" % self.volume['id'])
|
||||
volumes_client = self.manager.volumes_client
|
||||
volumes_client.delete_volume(self.volume['id'])
|
||||
volumes_client.wait_for_resource_deletion(self.volume['id'])
|
||||
self.logger.info("deleted volume: %s" % self.volume['id'])
|
||||
|
||||
def _wait_disassociate(self):
|
||||
cli = self.manager.compute_floating_ips_client
|
||||
|
||||
def func():
|
||||
floating = (cli.show_floating_ip(self.floating['id'])
|
||||
['floating_ip'])
|
||||
return floating['instance_id'] is None
|
||||
|
||||
if not test_utils.call_until_true(func, CONF.compute.build_timeout,
|
||||
CONF.compute.build_interval):
|
||||
raise RuntimeError("IP disassociate timeout!")
|
||||
|
||||
def new_server_ops(self):
|
||||
self._create_vm()
|
||||
cli = self.manager.compute_floating_ips_client
|
||||
cli.associate_floating_ip_to_server(self.floating['ip'],
|
||||
self.server_id)
|
||||
if self.ssh_test_before_attach and self.enable_ssh_verify:
|
||||
self.logger.info("Scanning for block devices via ssh on %s"
|
||||
% self.server_id)
|
||||
self.part_wait(self.detach_match_count)
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
"""Note able configuration combinations:
|
||||
|
||||
Closest options to the test_stamp_pattern:
|
||||
new_server = True
|
||||
new_volume = True
|
||||
enable_ssh_verify = True
|
||||
ssh_test_before_attach = False
|
||||
Just attaching:
|
||||
new_server = False
|
||||
new_volume = False
|
||||
enable_ssh_verify = True
|
||||
ssh_test_before_attach = True
|
||||
Mostly API load by repeated attachment:
|
||||
new_server = False
|
||||
new_volume = False
|
||||
enable_ssh_verify = False
|
||||
ssh_test_before_attach = False
|
||||
Minimal Nova load, but cinder load not decreased:
|
||||
new_server = False
|
||||
new_volume = True
|
||||
enable_ssh_verify = True
|
||||
ssh_test_before_attach = True
|
||||
"""
|
||||
self.image = CONF.compute.image_ref
|
||||
self.flavor = CONF.compute.flavor_ref
|
||||
self.vm_extra_args = kwargs.get('vm_extra_args', {})
|
||||
self.floating_pool = kwargs.get('floating_pool', None)
|
||||
self.new_volume = kwargs.get('new_volume', True)
|
||||
self.new_server = kwargs.get('new_server', False)
|
||||
self.enable_ssh_verify = kwargs.get('enable_ssh_verify', True)
|
||||
self.ssh_test_before_attach = kwargs.get('ssh_test_before_attach',
|
||||
False)
|
||||
self.part_line_re = re.compile(kwargs.get('part_line_re', '.*vd.*'))
|
||||
self.detach_match_count = kwargs.get('detach_match_count', 1)
|
||||
self.attach_match_count = kwargs.get('attach_match_count', 2)
|
||||
self.part_name = kwargs.get('part_name', '/dev/vdc')
|
||||
|
||||
self._create_floating_ip()
|
||||
self._create_sec_group()
|
||||
self._create_keypair()
|
||||
private_key = self.key['private_key']
|
||||
username = CONF.validation.image_ssh_user
|
||||
self.remote_client = remote_client.RemoteClient(self.floating['ip'],
|
||||
username,
|
||||
pkey=private_key)
|
||||
if not self.new_volume:
|
||||
self._create_volume()
|
||||
if not self.new_server:
|
||||
self.new_server_ops()
|
||||
|
||||
# now we just test that the number of partitions has increased or decreased
|
||||
def part_wait(self, num_match):
|
||||
def _part_state():
|
||||
self.partitions = self.remote_client.get_partitions().split('\n')
|
||||
matching = 0
|
||||
for part_line in self.partitions[1:]:
|
||||
if self.part_line_re.match(part_line):
|
||||
matching += 1
|
||||
return matching == num_match
|
||||
if test_utils.call_until_true(_part_state,
|
||||
CONF.compute.build_timeout,
|
||||
CONF.compute.build_interval):
|
||||
return
|
||||
else:
|
||||
raise RuntimeError("Unexpected partitions: %s",
|
||||
str(self.partitions))
|
||||
|
||||
def run(self):
|
||||
if self.new_server:
|
||||
self.new_server_ops()
|
||||
if self.new_volume:
|
||||
self._create_volume()
|
||||
servers_client = self.manager.servers_client
|
||||
self.logger.info("attach volume (%s) to vm %s" %
|
||||
(self.volume['id'], self.server_id))
|
||||
servers_client.attach_volume(self.server_id,
|
||||
volumeId=self.volume['id'],
|
||||
device=self.part_name)
|
||||
self.manager.volumes_client.wait_for_volume_status(self.volume['id'],
|
||||
'in-use')
|
||||
if self.enable_ssh_verify:
|
||||
self.logger.info("Scanning for new block device on %s"
|
||||
% self.server_id)
|
||||
self.part_wait(self.attach_match_count)
|
||||
|
||||
servers_client.detach_volume(self.server_id,
|
||||
self.volume['id'])
|
||||
self.manager.volumes_client.wait_for_volume_status(self.volume['id'],
|
||||
'available')
|
||||
if self.enable_ssh_verify:
|
||||
self.logger.info("Scanning for block device disappearance on %s"
|
||||
% self.server_id)
|
||||
self.part_wait(self.detach_match_count)
|
||||
if self.new_volume:
|
||||
self._delete_volume()
|
||||
if self.new_server:
|
||||
self._destroy_vm()
|
||||
|
||||
def tearDown(self):
|
||||
cli = self.manager.compute_floating_ips_client
|
||||
cli.disassociate_floating_ip_from_server(self.floating['ip'],
|
||||
self.server_id)
|
||||
self._wait_disassociate()
|
||||
if not self.new_server:
|
||||
self._destroy_vm()
|
||||
self._delete_keypair()
|
||||
self._destroy_floating_ip()
|
||||
self._destroy_sec_grp()
|
||||
if not self.new_volume:
|
||||
self._delete_volume()
|
34
tempest_stress/actions/volume_create_delete.py
Normal file
34
tempest_stress/actions/volume_create_delete.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Licensed 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 tempest.common.utils import data_utils
|
||||
from tempest import config
|
||||
import tempest.stress.stressaction as stressaction
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class VolumeCreateDeleteTest(stressaction.StressAction):
|
||||
|
||||
def run(self):
|
||||
name = data_utils.rand_name("volume")
|
||||
self.logger.info("creating %s" % name)
|
||||
volumes_client = self.manager.volumes_client
|
||||
volume = volumes_client.create_volume(
|
||||
display_name=name, size=CONF.volume.volume_size)['volume']
|
||||
vol_id = volume['id']
|
||||
volumes_client.wait_for_volume_status(vol_id, 'available')
|
||||
self.logger.info("created %s" % volume['id'])
|
||||
self.logger.info("deleting %s" % name)
|
||||
volumes_client.delete_volume(vol_id)
|
||||
volumes_client.wait_for_resource_deletion(vol_id)
|
||||
self.logger.info("deleted %s" % vol_id)
|
118
tempest_stress/cleanup.py
Normal file
118
tempest_stress/cleanup.py
Normal file
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2013 Quanta Research Cambridge, Inc.
|
||||
#
|
||||
# Licensed 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 oslo_log import log as logging
|
||||
|
||||
from tempest.common import credentials_factory as credentials
|
||||
from tempest.common import waiters
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def cleanup():
|
||||
admin_manager = credentials.AdminManager()
|
||||
|
||||
body = admin_manager.servers_client.list_servers(all_tenants=True)
|
||||
LOG.info("Cleanup::remove %s servers" % len(body['servers']))
|
||||
for s in body['servers']:
|
||||
try:
|
||||
admin_manager.servers_client.delete_server(s['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for s in body['servers']:
|
||||
try:
|
||||
waiters.wait_for_server_termination(admin_manager.servers_client,
|
||||
s['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
keypairs = admin_manager.keypairs_client.list_keypairs()['keypairs']
|
||||
LOG.info("Cleanup::remove %s keypairs" % len(keypairs))
|
||||
for k in keypairs:
|
||||
try:
|
||||
admin_manager.keypairs_client.delete_keypair(k['name'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
secgrp_client = admin_manager.compute_security_groups_client
|
||||
secgrp = (secgrp_client.list_security_groups(all_tenants=True)
|
||||
['security_groups'])
|
||||
secgrp_del = [grp for grp in secgrp if grp['name'] != 'default']
|
||||
LOG.info("Cleanup::remove %s Security Group" % len(secgrp_del))
|
||||
for g in secgrp_del:
|
||||
try:
|
||||
secgrp_client.delete_security_group(g['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
admin_floating_ips_client = admin_manager.compute_floating_ips_client
|
||||
floating_ips = (admin_floating_ips_client.list_floating_ips()
|
||||
['floating_ips'])
|
||||
LOG.info("Cleanup::remove %s floating ips" % len(floating_ips))
|
||||
for f in floating_ips:
|
||||
try:
|
||||
admin_floating_ips_client.delete_floating_ip(f['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
users = admin_manager.users_client.list_users()['users']
|
||||
LOG.info("Cleanup::remove %s users" % len(users))
|
||||
for user in users:
|
||||
if user['name'].startswith("stress_user"):
|
||||
admin_manager.users_client.delete_user(user['id'])
|
||||
tenants = admin_manager.tenants_client.list_tenants()['tenants']
|
||||
LOG.info("Cleanup::remove %s tenants" % len(tenants))
|
||||
for tenant in tenants:
|
||||
if tenant['name'].startswith("stress_tenant"):
|
||||
admin_manager.tenants_client.delete_tenant(tenant['id'])
|
||||
|
||||
# We have to delete snapshots first or
|
||||
# volume deletion may block
|
||||
|
||||
_, snaps = admin_manager.snapshots_client.list_snapshots(
|
||||
all_tenants=True)['snapshots']
|
||||
LOG.info("Cleanup::remove %s snapshots" % len(snaps))
|
||||
for v in snaps:
|
||||
try:
|
||||
waiters.wait_for_snapshot_status(
|
||||
admin_manager.snapshots_client, v['id'], 'available')
|
||||
admin_manager.snapshots_client.delete_snapshot(v['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for v in snaps:
|
||||
try:
|
||||
admin_manager.snapshots_client.wait_for_resource_deletion(v['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
vols = admin_manager.volumes_client.list_volumes(
|
||||
params={"all_tenants": True})
|
||||
LOG.info("Cleanup::remove %s volumes" % len(vols))
|
||||
for v in vols:
|
||||
try:
|
||||
waiters.wait_for_volume_status(
|
||||
admin_manager.volumes_client, v['id'], 'available')
|
||||
admin_manager.volumes_client.delete_volume(v['id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for v in vols:
|
||||
try:
|
||||
admin_manager.volumes_client.wait_for_resource_deletion(v['id'])
|
||||
except Exception:
|
||||
pass
|
0
tempest_stress/cmd/__init__.py
Normal file
0
tempest_stress/cmd/__init__.py
Normal file
130
tempest_stress/cmd/run_stress.py
Executable file
130
tempest_stress/cmd/run_stress.py
Executable file
@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2013 Quanta Research Cambridge, Inc.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import json
|
||||
import sys
|
||||
try:
|
||||
from unittest import loader
|
||||
except ImportError:
|
||||
# unittest in python 2.6 does not contain loader, so uses unittest2
|
||||
from unittest2 import loader
|
||||
|
||||
from oslo_log import log as logging
|
||||
from testtools import testsuite
|
||||
|
||||
from tempest.stress import driver
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def discover_stress_tests(path="./", filter_attr=None, call_inherited=False):
|
||||
"""Discovers all tempest tests and create action out of them
|
||||
"""
|
||||
LOG.info("Start test discovery")
|
||||
tests = []
|
||||
testloader = loader.TestLoader()
|
||||
list = testloader.discover(path)
|
||||
for func in (testsuite.iterate_tests(list)):
|
||||
attrs = []
|
||||
try:
|
||||
method_name = getattr(func, '_testMethodName')
|
||||
full_name = "%s.%s.%s" % (func.__module__,
|
||||
func.__class__.__name__,
|
||||
method_name)
|
||||
test_func = getattr(func, method_name)
|
||||
# NOTE(mkoderer): this contains a list of all type attributes
|
||||
attrs = getattr(test_func, "__testtools_attrs")
|
||||
except Exception:
|
||||
next
|
||||
if 'stress' in attrs:
|
||||
if filter_attr is not None and filter_attr not in attrs:
|
||||
continue
|
||||
class_setup_per = getattr(test_func, "st_class_setup_per")
|
||||
|
||||
action = {'action':
|
||||
"tempest.stress.actions.unit_test.UnitTest",
|
||||
'kwargs': {"test_method": full_name,
|
||||
"class_setup_per": class_setup_per
|
||||
}
|
||||
}
|
||||
if (not call_inherited and
|
||||
getattr(test_func, "st_allow_inheritance") is not True):
|
||||
class_structure = inspect.getmro(test_func.im_class)
|
||||
if test_func.__name__ not in class_structure[0].__dict__:
|
||||
continue
|
||||
tests.append(action)
|
||||
return tests
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Run stress tests')
|
||||
parser.add_argument('-d', '--duration', default=300, type=int,
|
||||
help="Duration of test in secs")
|
||||
parser.add_argument('-s', '--serial', action='store_true',
|
||||
help="Trigger running tests serially")
|
||||
parser.add_argument('-S', '--stop', action='store_true',
|
||||
default=False, help="Stop on first error")
|
||||
parser.add_argument('-n', '--number', type=int,
|
||||
help="How often an action is executed for each process")
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('-a', '--all', action='store_true',
|
||||
help="Execute all stress tests")
|
||||
parser.add_argument('-T', '--type',
|
||||
help="Filters tests of a certain type (e.g. gate)")
|
||||
parser.add_argument('-i', '--call-inherited', action='store_true',
|
||||
default=False,
|
||||
help="Call also inherited function with stress attribute")
|
||||
group.add_argument('-t', "--tests", nargs='?',
|
||||
help="Name of the file with test description")
|
||||
|
||||
|
||||
def main():
|
||||
ns = parser.parse_args()
|
||||
result = 0
|
||||
if not ns.all:
|
||||
tests = json.load(open(ns.tests, 'r'))
|
||||
else:
|
||||
tests = discover_stress_tests(filter_attr=ns.type,
|
||||
call_inherited=ns.call_inherited)
|
||||
|
||||
if ns.serial:
|
||||
# Duration is total time
|
||||
duration = ns.duration / len(tests)
|
||||
for test in tests:
|
||||
step_result = driver.stress_openstack([test],
|
||||
duration,
|
||||
ns.number,
|
||||
ns.stop)
|
||||
# NOTE(mkoderer): we just save the last result code
|
||||
if (step_result != 0):
|
||||
result = step_result
|
||||
if ns.stop:
|
||||
return result
|
||||
else:
|
||||
result = driver.stress_openstack(tests,
|
||||
ns.duration,
|
||||
ns.number,
|
||||
ns.stop)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception:
|
||||
LOG.exception("Failure in the stress test framework")
|
||||
sys.exit(1)
|
55
tempest_stress/config.py
Normal file
55
tempest_stress/config.py
Normal file
@ -0,0 +1,55 @@
|
||||
# Copyright 2015 Intel Corp
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed 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 oslo_config import cfg
|
||||
|
||||
from tempest import config # noqa
|
||||
|
||||
stress_group = cfg.OptGroup(name='stress', title='Stress Test Options')
|
||||
|
||||
StressGroup = [
|
||||
cfg.StrOpt('nova_logdir',
|
||||
help='Directory containing log files on the compute nodes'),
|
||||
cfg.IntOpt('max_instances',
|
||||
default=16,
|
||||
help='Maximum number of instances to create during test.'),
|
||||
cfg.StrOpt('controller',
|
||||
help='Controller host.'),
|
||||
# new stress options
|
||||
cfg.StrOpt('target_controller',
|
||||
help='Controller host.'),
|
||||
cfg.StrOpt('target_ssh_user',
|
||||
help='ssh user.'),
|
||||
cfg.StrOpt('target_private_key_path',
|
||||
help='Path to private key.'),
|
||||
cfg.StrOpt('target_logfiles',
|
||||
help='regexp for list of log files.'),
|
||||
cfg.IntOpt('log_check_interval',
|
||||
default=60,
|
||||
help='time (in seconds) between log file error checks.'),
|
||||
cfg.IntOpt('default_thread_number_per_action',
|
||||
default=4,
|
||||
help='The number of threads created while stress test.'),
|
||||
cfg.BoolOpt('leave_dirty_stack',
|
||||
default=False,
|
||||
help='Prevent the cleaning (tearDownClass()) between'
|
||||
' each stress test run if an exception occurs'
|
||||
' during this run.'),
|
||||
cfg.BoolOpt('full_clean_stack',
|
||||
default=False,
|
||||
help='Allows a full cleaning process after a stress test.'
|
||||
' Caution : this cleanup will remove every objects of'
|
||||
' every project.')
|
||||
]
|
264
tempest_stress/driver.py
Normal file
264
tempest_stress/driver.py
Normal file
@ -0,0 +1,264 @@
|
||||
# Copyright 2013 Quanta Research Cambridge, Inc.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
|
||||
from tempest import clients
|
||||
from tempest.common import cred_client
|
||||
from tempest.common import credentials_factory as credentials
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.lib.common import ssh
|
||||
from tempest.stress import cleanup
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
processes = []
|
||||
|
||||
|
||||
def do_ssh(command, host, ssh_user, ssh_key=None):
|
||||