Merge "White space after # in tempest/api files"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 825965f..3aa0497 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -1,5 +1,13 @@
[DEFAULT]
# log_config = /opt/stack/tempest/etc/logging.conf.sample
+
+# disable logging to the stderr
+use_stderr = False
+
+# log file
+log_file = tempest.log
+
+# lock/semaphore base directory
lock_path=/tmp
[identity]
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 7de7821..ad7be39 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -105,3 +105,8 @@
image_list = map(lambda x: x['id'], images_list)
for image in self.created_images:
self.assertIn(image, image_list)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_image_meta_by_null_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_image_metadata, '')
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 142ad7d..8785e31 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -38,6 +38,11 @@
tenant_network_mask_bits with the mask bits to be used to partition the
block defined by tenant-network_cidr
+
+ Finally, it is assumed that the following option is defined in the
+ [service_available] section of etc/tempest.conf
+
+ neutron as True
"""
@classmethod
diff --git a/tempest/api/network/test_quotas.py b/tempest/api/network/test_quotas.py
new file mode 100644
index 0000000..ba70f34
--- /dev/null
+++ b/tempest/api/network/test_quotas.py
@@ -0,0 +1,91 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack, LLC
+# 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 tempest.api.network import base
+from tempest import clients
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class QuotasTest(base.BaseNetworkTest):
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ list quotas for tenants who have non-default quota values
+ show quotas for a specified tenant
+ update quotas for a specified tenant
+ reset quotas to default values for a specified tenant
+
+ v2.0 of the API is assumed. It is also assumed that the following
+ option is defined in the [service_available] section of etc/tempest.conf:
+
+ neutron as True
+
+ Finally, it is assumed that the per-tenant quota extension API is
+ configured in /etc/neutron/neutron.conf as follows:
+
+ quota_driver = neutron.db.quota_db.DbQuotaDriver
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(QuotasTest, cls).setUpClass()
+ admin_manager = clients.AdminManager()
+ cls.admin_client = admin_manager.network_client
+ cls.identity_admin_client = admin_manager.identity_client
+
+ @attr(type='gate')
+ def test_quotas(self):
+ # Add a tenant to conduct the test
+ test_tenant = rand_name('test_tenant_')
+ test_description = rand_name('desc_')
+ _, tenant = self.identity_admin_client.create_tenant(
+ name=test_tenant,
+ description=test_description)
+ tenant_id = tenant['id']
+ self.addCleanup(self.identity_admin_client.delete_tenant, tenant_id)
+ # Change quotas for tenant
+ new_quotas = {'network': 0, 'security_group': 0}
+ resp, quota_set = self.admin_client.update_quotas(tenant_id,
+ **new_quotas)
+ self.assertEqual('200', resp['status'])
+ self.addCleanup(self.admin_client.reset_quotas, tenant_id)
+ self.assertEqual(0, quota_set['network'])
+ self.assertEqual(0, quota_set['security_group'])
+ # Confirm our tenant is listed among tenants with non default quotas
+ resp, non_default_quotas = self.admin_client.list_quotas()
+ self.assertEqual('200', resp['status'])
+ found = False
+ for qs in non_default_quotas:
+ if qs['tenant_id'] == tenant_id:
+ found = True
+ self.assertTrue(found)
+ # Confirm from APi quotas were changed as requested for tenant
+ resp, quota_set = self.admin_client.show_quotas(tenant_id)
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(0, quota_set['network'])
+ self.assertEqual(0, quota_set['security_group'])
+ # Reset quotas to default and confirm
+ resp, body = self.admin_client.reset_quotas(tenant_id)
+ self.assertEqual('204', resp['status'])
+ resp, non_default_quotas = self.admin_client.list_quotas()
+ self.assertEqual('200', resp['status'])
+ for q in non_default_quotas:
+ self.assertNotEqual(tenant_id, q['tenant_id'])
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index 4860090..7b8340d 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -56,7 +56,8 @@
self.assertTableStruct(ext, ['alias', 'name'])
def test_neutron_dhcp_agent_list_hosting_net(self):
- self.neutron('dhcp-agent-list-hosting-net', params="private")
+ self.neutron('dhcp-agent-list-hosting-net',
+ params=CONF.compute.fixed_network_name)
def test_neutron_agent_list(self):
agents = self.parser.listing(self.neutron('agent-list'))
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 446a674..f96ed91 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -23,13 +23,11 @@
Tempest REST client for Neutron. Uses v2 of the Neutron API, since the
V1 API has been removed from the code base.
- Implements the following operations for each one of the basic Neutron
+ Implements create, delete, list and show for the basic Neutron
abstractions (networks, sub-networks and ports):
- create
- delete
- list
- show
+ It also implements list, show, update and reset for OpenStack Networking
+ quotas
"""
def __init__(self, config, username, password, auth_url, tenant_name=None):
@@ -128,3 +126,28 @@
resp, body = self.get(uri, self.headers)
body = json.loads(body)
return resp, body
+
+ def update_quotas(self, tenant_id, **kwargs):
+ put_body = {'quota': kwargs}
+ body = json.dumps(put_body)
+ uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
+ resp, body = self.put(uri, body, self.headers)
+ body = json.loads(body)
+ return resp, body['quota']
+
+ def show_quotas(self, tenant_id):
+ uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body['quota']
+
+ def reset_quotas(self, tenant_id):
+ uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
+ resp, body = self.delete(uri, self.headers)
+ return resp, body
+
+ def list_quotas(self):
+ uri = '%s/quotas' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body['quotas']
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index 661763c..31642b0 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -30,7 +30,7 @@
To test installation, do the following (from the tempest/stress directory):
- ./run_stress.py etc/sample-test.json -d 30
+ ./run_stress.py etc/server-create-destroy-test.json -d 30
This sample test tries to create a few VMs and kill a few VMs.
diff --git a/tempest/stress/actions/create_destroy_server.py b/tempest/stress/actions/server_create_destroy.py
similarity index 96%
rename from tempest/stress/actions/create_destroy_server.py
rename to tempest/stress/actions/server_create_destroy.py
index 68dc148..1a1e30b 100644
--- a/tempest/stress/actions/create_destroy_server.py
+++ b/tempest/stress/actions/server_create_destroy.py
@@ -16,7 +16,7 @@
import tempest.stress.stressaction as stressaction
-class CreateDestroyServerTest(stressaction.StressAction):
+class ServerCreateDestroyTest(stressaction.StressAction):
def setUp(self, **kwargs):
self.image = self.manager.config.compute.image_ref
diff --git a/tempest/stress/actions/volume_create_delete.py b/tempest/stress/actions/volume_create_delete.py
index 184f870..e29d9c4 100644
--- a/tempest/stress/actions/volume_create_delete.py
+++ b/tempest/stress/actions/volume_create_delete.py
@@ -14,20 +14,20 @@
import tempest.stress.stressaction as stressaction
-class CreateDeleteTest(stressaction.StressAction):
+class VolumeCreateDeleteTest(stressaction.StressAction):
def run(self):
name = rand_name("volume")
self.logger.info("creating %s" % name)
- resp, volume = self.manager.volumes_client.\
- create_volume(size=1, display_name=name)
+ volumes_client = self.manager.volumes_client
+ resp, volume = volumes_client.create_volume(size=1,
+ display_name=name)
assert(resp.status == 200)
vol_id = volume['id']
- status = 'available'
- self.manager.volumes_client.wait_for_volume_status(vol_id, status)
+ volumes_client.wait_for_volume_status(vol_id, 'available')
self.logger.info("created %s" % volume['id'])
self.logger.info("deleting %s" % name)
- resp, _ = self.manager.volumes_client.delete_volume(vol_id)
+ resp, _ = volumes_client.delete_volume(vol_id)
assert(resp.status == 202)
- self.manager.volumes_client.wait_for_resource_deletion(vol_id)
+ volumes_client.wait_for_resource_deletion(vol_id)
self.logger.info("deleted %s" % vol_id)
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index c4c2041..d9b95e0 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -14,6 +14,7 @@
import logging
import multiprocessing
+import signal
import time
from tempest import clients
@@ -45,6 +46,7 @@
# add the handler to the root logger
logger = logging.getLogger('tempest.stress')
logger.addHandler(_console)
+processes = []
def do_ssh(command, host):
@@ -93,10 +95,29 @@
return None
-def stress_openstack(tests, duration, max_runs=None):
+def sigchld_handler(signal, frame):
+ """
+ Signal handler (only active if stop_on_error is True).
+ """
+ terminate_all_processes()
+
+
+def terminate_all_processes():
+ """
+ Goes through the process list and terminates all child processes.
+ """
+ for process in processes:
+ if process['process'].is_alive():
+ try:
+ process['process'].terminate()
+ except Exception:
+ pass
+ process['process'].join()
+
+
+def stress_openstack(tests, duration, max_runs=None, stop_on_error=False):
"""
Workload driver. Executes an action function against a nova-cluster.
-
"""
logfiles = admin_manager.config.stress.target_logfiles
log_check_interval = int(admin_manager.config.stress.log_check_interval)
@@ -105,7 +126,6 @@
computes = _get_compute_nodes(controller)
for node in computes:
do_ssh("rm -f %s" % logfiles, node)
- processes = []
for test in tests:
if test.get('use_admin', False):
manager = admin_manager
@@ -127,7 +147,7 @@
tenant_name=tenant_name)
test_obj = importutils.import_class(test['action'])
- test_run = test_obj(manager, logger, max_runs)
+ test_run = test_obj(manager, logger, max_runs, stop_on_error)
kwargs = test.get('kwargs', {})
test_run.setUp(**dict(kwargs.iteritems()))
@@ -150,6 +170,9 @@
processes.append(process)
p.start()
+ if stop_on_error:
+ # NOTE(mkoderer): only the parent should register the handler
+ signal.signal(signal.SIGCHLD, sigchld_handler)
end_time = time.time() + duration
had_errors = False
while True:
@@ -168,6 +191,11 @@
break
time.sleep(min(remaining, log_check_interval))
+ if stop_on_error:
+ for process in processes:
+ if process['statistic']['fails'] > 0:
+ break
+
if not logfiles:
continue
errors = _error_in_logs(logfiles, computes)
@@ -175,10 +203,7 @@
had_errors = True
break
- for process in processes:
- if process['process'].is_alive():
- process['process'].terminate()
- process['process'].join()
+ terminate_all_processes()
sum_fails = 0
sum_runs = 0
diff --git a/tempest/stress/etc/sample-test.json b/tempest/stress/etc/sample-test.json
deleted file mode 100644
index 494c823..0000000
--- a/tempest/stress/etc/sample-test.json
+++ /dev/null
@@ -1,7 +0,0 @@
-[{"action": "tempest.stress.actions.create_destroy_server.CreateDestroyServerTest",
- "threads": 8,
- "use_admin": false,
- "use_isolated_tenants": false,
- "kwargs": {}
- }
-]
diff --git a/tempest/stress/etc/server-create-destroy-test.json b/tempest/stress/etc/server-create-destroy-test.json
new file mode 100644
index 0000000..17d5e1a
--- /dev/null
+++ b/tempest/stress/etc/server-create-destroy-test.json
@@ -0,0 +1,7 @@
+[{"action": "tempest.stress.actions.server_create_destroy.ServerCreateDestroyTest",
+ "threads": 8,
+ "use_admin": false,
+ "use_isolated_tenants": false,
+ "kwargs": {}
+ }
+]
diff --git a/tempest/stress/etc/stress-tox-job.json b/tempest/stress/etc/stress-tox-job.json
index 3534c26..dffc469 100644
--- a/tempest/stress/etc/stress-tox-job.json
+++ b/tempest/stress/etc/stress-tox-job.json
@@ -1,10 +1,10 @@
-[{"action": "tempest.stress.actions.create_destroy_server.CreateDestroyServerTest",
+[{"action": "tempest.stress.actions.server_create_destroy.ServerCreateDestroyTest",
"threads": 8,
"use_admin": false,
"use_isolated_tenants": false,
"kwargs": {}
},
- {"action": "tempest.stress.actions.volume_create_delete.CreateDeleteTest",
+ {"action": "tempest.stress.actions.volume_create_delete.VolumeCreateDeleteTest",
"threads": 4,
"use_admin": false,
"use_isolated_tenants": false,
diff --git a/tempest/stress/etc/volume-assign-delete-test.json b/tempest/stress/etc/volume-attach-delete-test.json
similarity index 100%
rename from tempest/stress/etc/volume-assign-delete-test.json
rename to tempest/stress/etc/volume-attach-delete-test.json
diff --git a/tempest/stress/etc/volume-create-delete-test.json b/tempest/stress/etc/volume-create-delete-test.json
index 6325bdc..e8a58f7 100644
--- a/tempest/stress/etc/volume-create-delete-test.json
+++ b/tempest/stress/etc/volume-create-delete-test.json
@@ -1,4 +1,4 @@
-[{"action": "tempest.stress.actions.volume_create_delete.CreateDeleteTest",
+[{"action": "tempest.stress.actions.volume_create_delete.VolumeCreateDeleteTest",
"threads": 4,
"use_admin": false,
"use_isolated_tenants": false,
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index 106049d..32e3ae0 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -22,7 +22,7 @@
def main(ns):
- #NOTE(kodererm): moved import to make "-h" possible without OpenStack
+ # NOTE(mkoderer): moved import to make "-h" possible without OpenStack
from tempest.stress import driver
result = 0
tests = json.load(open(ns.tests, 'r'))
@@ -30,12 +30,13 @@
for test in tests:
step_result = driver.stress_openstack([test],
ns.duration,
- ns.number)
- #NOTE(kodererm): we just save the last result code
+ ns.number,
+ ns.stop)
+ # NOTE(mkoderer): we just save the last result code
if (step_result != 0):
result = step_result
else:
- driver.stress_openstack(tests, ns.duration, ns.number)
+ driver.stress_openstack(tests, ns.duration, ns.number, ns.stop)
return result
@@ -44,6 +45,8 @@
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.")
parser.add_argument('tests', help="Name of the file with test description.")
diff --git a/tempest/stress/stressaction.py b/tempest/stress/stressaction.py
index 77ddd1c..ab09adc 100644
--- a/tempest/stress/stressaction.py
+++ b/tempest/stress/stressaction.py
@@ -20,10 +20,11 @@
class StressAction(object):
- def __init__(self, manager, logger, max_runs=None):
+ def __init__(self, manager, logger, max_runs=None, stop_on_error=False):
self.manager = manager
self.logger = logger
self.max_runs = max_runs
+ self.stop_on_error = stop_on_error
def _shutdown_handler(self, signal, frame):
self.tearDown()
@@ -63,6 +64,11 @@
self.logger.exception("Failure in run")
finally:
shared_statistic['runs'] += 1
+ if self.stop_on_error and (shared_statistic['fails'] > 1):
+ self.logger.warn("Stop process due to"
+ "\"stop-on-error\" argument")
+ self.tearDown()
+ sys.exit(1)
def run(self):
"""This method is where the stress test code runs."""