Merge "Adds delete api test to glance"
diff --git a/HACKING.rst b/HACKING.rst
index 377c647..a74ff73 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -186,3 +186,9 @@
- Scenario tests
- API tests that have a wide focus
+
+Sample Configuration File
+-------------------------
+The sample config file is autogenerated using a script. If any changes are made
+to the config variables in tempest then the sample config file must be
+regenerated. This can be done running the script: tools/generate_sample.sh
diff --git a/README.rst b/README.rst
index 0996022..96f6e4c 100644
--- a/README.rst
+++ b/README.rst
@@ -79,6 +79,9 @@
document. The etc/tempest.conf.sample attempts to be a self
documenting version of the configuration.
+The sample config file is auto generated using the script:
+tools/generate_sample.sh
+
The most important pieces that are needed are the user ids, openstack
endpoints, and basic flavors and images needed to run tests.
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 567b85a..251336e 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -674,7 +674,7 @@
#
# If false, skip all nova v3 tests. (boolean value)
-#api_v3=false
+#api_v3=true
# If false, skip disk config tests (boolean value)
#disk_config=true
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
index e6d28f5..24ee5e1 100644
--- a/etc/whitelist.yaml
+++ b/etc/whitelist.yaml
@@ -31,6 +31,12 @@
message: "Getting disk size of instance"
- module: "nova.virt.libvirt.driver"
message: "No such file or directory: '/opt/stack/data/nova/instances"
+ - module: "nova.compute.manager"
+ message: "error during stop\\(\\) in sync_power_state"
+ - module: "nova.compute.manager"
+ message: "Instance failed network setup after 1 attempt"
+ - module: "nova.compute.manager"
+ message: "Periodic sync_power_state task had an error"
g-api:
- module: "glance.store.sheepdog"
@@ -38,14 +44,24 @@
running command"
- module: "swiftclient"
message: "Container HEAD failed: .*404 Not Found"
+ - module: "glance.api.middleware.cache"
+ message: "however the registry did not contain metadata for that image"
ceilometer-acompute:
- module: "ceilometer.compute.pollsters.disk"
+ message: "Unable to read from monitor: Connection reset by peer"
+ - module: "ceilometer.compute.pollsters.disk"
+ message: "Requested operation is not valid: domain is not running"
+ - module: "ceilometer.compute.pollsters.net"
message: "Requested operation is not valid: domain is not running"
- module: "ceilometer.compute.pollsters.disk"
message: "Domain not found: no domain with matching uuid"
- module: "ceilometer.compute.pollsters.net"
message: "No module named libvirt"
+ - module: "ceilometer.compute.pollsters.net"
+ message: "Unable to write to monitor: Broken pipe"
+ - module: "ceilometer.compute.pollsters.cpu"
+ message: "Domain not found: no domain with matching uuid"
ceilometer-alarm-evaluator:
- module: "ceilometer.alarm.service"
@@ -81,20 +97,40 @@
message: "\\{u'kernel_id'.*u'ramdisk_id':"
- module: "nova.api.openstack.wsgi"
message: "takes exactly 4 arguments"
+ - module: "nova.api.openstack"
+ message: "Caught error: Instance .* could not be found"
+ - module: "nova.api.metadata.handler"
+ message: "Failed to get metadata for instance id:"
n-cond:
- module: "nova.notifications"
message: "Failed to send state update notification"
+ - module: "nova.openstack.common.rpc.amqp"
+ message: "Exception during message handling"
+ - module: "nova.openstack.common.rpc.common"
+ message: "but the actual state is deleting to caller"
+ - module: "nova.openstack.common.rpc.common"
+ message: "Traceback \\(most recent call last"
n-sch:
- module: "nova.scheduler.filter_scheduler"
message: "Error from last host: "
+n-net:
+ - module: "nova.openstack.common.rpc.amqp"
+ message: "Exception during message handling"
+ - module: "nova.openstack.common.rpc.common"
+ message: "'NoneType' object has no attribute '__getitem__'"
+
c-api:
- module: "cinder.api.middleware.fault"
message: "Caught error: Volume .* could not be found"
- module: "cinder.api.middleware.fault"
message: "Caught error: Snapshot .* could not be found"
+ - module: "cinder.api.openstack.wsgi"
+ message: "argument must be a string or a number, not 'NoneType'"
+ - module: "cinder.volume.api"
+ message: "Volume status must be available to reserve"
c-vol:
- module: "cinder.brick.iscsi.iscsi"
@@ -102,6 +138,8 @@
- module: "cinder.brick.local_dev.lvm"
message: "/dev/dm-1: stat failed: No such file or directory"
- module: "cinder.brick.local_dev.lvm"
+ message: "LV stack-volumes.*in use: not deactivating"
+ - module: "cinder.brick.local_dev.lvm"
message: "Can't remove open logical volume"
q-dhpc:
@@ -119,6 +157,12 @@
message: "duplicate key value violates unique constraint"
- module: "ceilometer.collector.dispatcher.database"
message: "Failed to record metering data: QueuePool limit"
+ - module: "ceilometer.collector.dispatcher.database"
+ message: "Failed to record metering data: .* integer out of range"
+ - module: "ceilometer.collector.dispatcher.database"
+ message: "Failed to record metering data: .* integer out of range"
+ - module: "ceilometer.openstack.common.db.sqlalchemy.session"
+ message: "DB exception wrapped"
q-agt:
- module: "neutron.agent.linux.ovs_lib"
@@ -159,4 +203,6 @@
message: "(Network|Pool|Subnet|Agent|Port) .* could not be found"
- module: "neutron.api.v2.resource"
message: ".* failed"
+ - module: ".*"
+ message: ".*"
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index a3bccc3..5028cad 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -33,7 +33,6 @@
def setUpClass(cls):
super(ServersAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.servers_client
- cls.non_adm_client = cls.servers_client
cls.flavors_client = cls.os_adm.flavors_client
cls.identity_client = cls._get_identity_admin_client()
tenant = cls.identity_client.get_tenant_by_name(
@@ -87,42 +86,6 @@
self.assertEqual('204', resp['status'])
self.servers_client.wait_for_server_termination(server['id'])
- @attr(type=['negative', 'gate'])
- def test_resize_server_using_overlimit_ram(self):
- flavor_name = data_utils.rand_name("flavor-")
- flavor_id = self._get_unused_flavor_id()
- resp, quota_set = self.quotas_client.get_default_quota_set(
- self.tenant_id)
- ram = int(quota_set['ram']) + 1
- vcpus = 8
- disk = 10
- resp, flavor_ref = self.flavors_client.create_flavor(flavor_name,
- ram, vcpus, disk,
- flavor_id)
- self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
- self.client.resize,
- self.servers[0]['id'],
- flavor_ref['id'])
-
- @attr(type=['negative', 'gate'])
- def test_resize_server_using_overlimit_vcpus(self):
- flavor_name = data_utils.rand_name("flavor-")
- flavor_id = self._get_unused_flavor_id()
- ram = 512
- resp, quota_set = self.quotas_client.get_default_quota_set(
- self.tenant_id)
- vcpus = int(quota_set['cores']) + 1
- disk = 10
- resp, flavor_ref = self.flavors_client.create_flavor(flavor_name,
- ram, vcpus, disk,
- flavor_id)
- self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
- self.client.resize,
- self.servers[0]['id'],
- flavor_ref['id'])
-
@attr(type='gate')
def test_reset_state_server(self):
# Reset server's state to 'error'
@@ -141,23 +104,6 @@
resp, server = self.client.get_server(self.s1_id)
self.assertEqual(server['status'], 'ACTIVE')
- @attr(type=['negative', 'gate'])
- def test_reset_state_server_invalid_state(self):
- self.assertRaises(exceptions.BadRequest,
- self.client.reset_state, self.s1_id,
- state='invalid')
-
- @attr(type=['negative', 'gate'])
- def test_reset_state_server_invalid_type(self):
- self.assertRaises(exceptions.BadRequest,
- self.client.reset_state, self.s1_id,
- state=1)
-
- @attr(type=['negative', 'gate'])
- def test_reset_state_server_nonexistent_server(self):
- self.assertRaises(exceptions.NotFound,
- self.client.reset_state, '999')
-
@attr(type='gate')
@skip_because(bug="1240043")
def test_get_server_diagnostics_by_admin(self):
@@ -170,13 +116,6 @@
for key in basic_attrs:
self.assertIn(key, str(diagnostic.keys()))
- @attr(type=['negative', 'gate'])
- def test_get_server_diagnostics_by_non_admin(self):
- # Non-admin user can not view server diagnostics according to policy
- self.assertRaises(exceptions.Unauthorized,
- self.non_adm_client.get_server_diagnostics,
- self.s1_id)
-
class ServersAdminTestXML(ServersAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
new file mode 100644
index 0000000..77d873b
--- /dev/null
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -0,0 +1,143 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+#
+# 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 uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Servers API using admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServersAdminNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.servers_client
+ cls.non_adm_client = cls.servers_client
+ cls.flavors_client = cls.os_adm.flavors_client
+ cls.identity_client = cls._get_identity_admin_client()
+ tenant = cls.identity_client.get_tenant_by_name(
+ cls.client.tenant_name)
+ cls.tenant_id = tenant['id']
+
+ cls.s1_name = data_utils.rand_name('server')
+ resp, server = cls.create_test_server(name=cls.s1_name,
+ wait_until='ACTIVE')
+ cls.s1_id = server['id']
+
+ def _get_unused_flavor_id(self):
+ flavor_id = data_utils.rand_int_id(start=1000)
+ while True:
+ try:
+ resp, body = self.flavors_client.get_flavor_details(flavor_id)
+ except exceptions.NotFound:
+ break
+ flavor_id = data_utils.rand_int_id(start=1000)
+ return flavor_id
+
+ @attr(type=['negative', 'gate'])
+ def test_resize_server_using_overlimit_ram(self):
+ flavor_name = data_utils.rand_name("flavor-")
+ flavor_id = self._get_unused_flavor_id()
+ resp, quota_set = self.quotas_client.get_default_quota_set(
+ self.tenant_id)
+ ram = int(quota_set['ram']) + 1
+ vcpus = 8
+ disk = 10
+ resp, flavor_ref = self.flavors_client.create_flavor(flavor_name,
+ ram, vcpus, disk,
+ flavor_id)
+ self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
+ self.assertRaises(exceptions.OverLimit,
+ self.client.resize,
+ self.servers[0]['id'],
+ flavor_ref['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_resize_server_using_overlimit_vcpus(self):
+ flavor_name = data_utils.rand_name("flavor-")
+ flavor_id = self._get_unused_flavor_id()
+ ram = 512
+ resp, quota_set = self.quotas_client.get_default_quota_set(
+ self.tenant_id)
+ vcpus = int(quota_set['cores']) + 1
+ disk = 10
+ resp, flavor_ref = self.flavors_client.create_flavor(flavor_name,
+ ram, vcpus, disk,
+ flavor_id)
+ self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
+ self.assertRaises(exceptions.OverLimit,
+ self.client.resize,
+ self.servers[0]['id'],
+ flavor_ref['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_reset_state_server_invalid_state(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.reset_state, self.s1_id,
+ state='invalid')
+
+ @attr(type=['negative', 'gate'])
+ def test_reset_state_server_invalid_type(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.reset_state, self.s1_id,
+ state=1)
+
+ @attr(type=['negative', 'gate'])
+ def test_reset_state_server_nonexistent_server(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.reset_state, '999')
+
+ @attr(type=['negative', 'gate'])
+ def test_get_server_diagnostics_by_non_admin(self):
+ # Non-admin user can not view server diagnostics according to policy
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_adm_client.get_server_diagnostics,
+ self.s1_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_migrate_non_existent_server(self):
+ # migrate a non existent server
+ self.assertRaises(exceptions.NotFound,
+ self.client.migrate_server,
+ str(uuid.uuid4()))
+
+ @attr(type=['negative', 'gate'])
+ def test_migrate_server_invalid_state(self):
+ # create server.
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.assertEqual(202, resp.status)
+ server_id = server['id']
+ # suspend the server.
+ resp, _ = self.client.suspend_server(server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(server_id, 'SUSPENDED')
+ # migrate an suspended server should fail
+ self.assertRaises(exceptions.Conflict,
+ self.client.migrate_server,
+ server_id)
+
+
+class ServersAdminNegativeTestXML(ServersAdminNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 4b6816b..7ef5466 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -225,6 +225,7 @@
def setUpClass(cls):
super(BaseV3ComputeTest, cls).setUpClass()
if not cls.config.compute_feature_enabled.api_v3:
+ cls.tearDownClass()
skip_msg = ("%s skipped as nova v3 api is not available" %
cls.__name__)
raise cls.skipException(skip_msg)
@@ -233,6 +234,9 @@
cls.images_client = cls.os.image_client
cls.services_client = cls.os.services_v3_client
cls.extensions_client = cls.os.extensions_v3_client
+ cls.availability_zone_client = cls.os.availability_zone_v3_client
+ cls.interfaces_client = cls.os.interfaces_v3_client
+ cls.hypervisor_client = cls.os.hypervisor_v3_client
@classmethod
def create_image_from_server(cls, server_id, **kwargs):
@@ -293,3 +297,6 @@
cls.os_adm = os_adm
cls.severs_admin_client = cls.os_adm.servers_v3_client
cls.services_admin_client = cls.os_adm.services_v3_client
+ cls.availability_zone_admin_client = \
+ cls.os_adm.availability_zone_v3_client
+ cls.hypervisor_admin_client = cls.os_adm.hypervisor_v3_client
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index db3acb5..b4e778c 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -19,8 +19,7 @@
from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
-from tempest.common.utils.data_utils import parse_image_id
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -86,7 +85,7 @@
@attr(type=['negative', 'gate'])
def test_create_image_specify_multibyte_character_image_name(self):
# Return an error if the image name has multi-byte characters
- snapshot_name = rand_name('\xef\xbb\xbf')
+ snapshot_name = data_utils.rand_name('\xef\xbb\xbf')
self.assertRaises(exceptions.BadRequest,
self.client.create_image, self.server_id,
snapshot_name)
@@ -94,7 +93,7 @@
@attr(type=['negative', 'gate'])
def test_create_image_specify_invalid_metadata(self):
# Return an error when creating image with invalid metadata
- snapshot_name = rand_name('test-snap-')
+ snapshot_name = data_utils.rand_name('test-snap-')
meta = {'': ''}
self.assertRaises(exceptions.BadRequest, self.client.create_image,
self.server_id, snapshot_name, meta)
@@ -102,7 +101,7 @@
@attr(type=['negative', 'gate'])
def test_create_image_specify_metadata_over_limits(self):
# Return an error when creating image with meta data over 256 chars
- snapshot_name = rand_name('test-snap-')
+ snapshot_name = data_utils.rand_name('test-snap-')
meta = {'a' * 260: 'b' * 260}
self.assertRaises(exceptions.BadRequest, self.client.create_image,
self.server_id, snapshot_name, meta)
@@ -112,15 +111,15 @@
# Disallow creating another image when first image is being saved
# Create first snapshot
- snapshot_name = rand_name('test-snap-')
+ snapshot_name = data_utils.rand_name('test-snap-')
resp, body = self.client.create_image(self.server_id,
snapshot_name)
self.assertEqual(202, resp.status)
- image_id = parse_image_id(resp['location'])
+ image_id = data_utils.parse_image_id(resp['location'])
self.image_ids.append(image_id)
# Create second snapshot
- alt_snapshot_name = rand_name('test-snap-')
+ alt_snapshot_name = data_utils.rand_name('test-snap-')
self.assertRaises(exceptions.Conflict, self.client.create_image,
self.server_id, alt_snapshot_name)
self.client.wait_for_image_status(image_id, 'ACTIVE')
@@ -129,7 +128,7 @@
def test_create_image_specify_name_over_256_chars(self):
# Return an error if snapshot name over 256 characters is passed
- snapshot_name = rand_name('a' * 260)
+ snapshot_name = data_utils.rand_name('a' * 260)
self.assertRaises(exceptions.BadRequest, self.client.create_image,
self.server_id, snapshot_name)
@@ -137,10 +136,10 @@
def test_delete_image_that_is_not_yet_active(self):
# Return an error while trying to delete an image what is creating
- snapshot_name = rand_name('test-snap-')
+ snapshot_name = data_utils.rand_name('test-snap-')
resp, body = self.client.create_image(self.server_id, snapshot_name)
self.assertEqual(202, resp.status)
- image_id = parse_image_id(resp['location'])
+ image_id = data_utils.parse_image_id(resp['location'])
self.image_ids.append(image_id)
# Do not wait, attempt to delete the image, ensure it's successful
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 1044ae1..5abde56 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -198,7 +198,6 @@
required time (%s s).' % (self.server_id, self.build_timeout)
raise exceptions.TimeoutException(message)
- @skip_because(bug="1251920")
@attr(type='gate')
def test_create_backup(self):
# Positive test:create backup successfully and rotate backups correctly
@@ -271,6 +270,16 @@
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
# for a given server_id and number of lines
+
+ # This reboot is necessary for outputting some console log after
+ # creating a instance backup. If a instance backup, the console
+ # log file is truncated and we cannot get any console log through
+ # "console-log" API.
+ # The detail is https://bugs.launchpad.net/nova/+bug/1251920
+ resp, body = self.servers_client.reboot(self.server_id, 'HARD')
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
def get_output():
resp, output = self.servers_client.get_console_output(
self.server_id, 10)
diff --git a/tempest/api/compute/v3/admin/test_availability_zone.py b/tempest/api/compute/v3/admin/test_availability_zone.py
index d6488c4..ff2765c 100644
--- a/tempest/api/compute/v3/admin/test_availability_zone.py
+++ b/tempest/api/compute/v3/admin/test_availability_zone.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class AvailabilityZoneAdminTestJSON(base.BaseV2ComputeAdminTest):
+class AvailabilityZoneAdminV3TestJSON(base.BaseV3ComputeAdminTest):
"""
Tests Availability Zone API List that require admin privileges
@@ -30,8 +30,8 @@
@classmethod
def setUpClass(cls):
- super(AvailabilityZoneAdminTestJSON, cls).setUpClass()
- cls.client = cls.os_adm.availability_zone_client
+ super(AvailabilityZoneAdminV3TestJSON, cls).setUpClass()
+ cls.client = cls.availability_zone_admin_client
cls.non_adm_client = cls.availability_zone_client
@attr(type='gate')
@@ -66,5 +66,5 @@
self.non_adm_client.get_availability_zone_list_detail)
-class AvailabilityZoneAdminTestXML(AvailabilityZoneAdminTestJSON):
+class AvailabilityZoneAdminV3TestXML(AvailabilityZoneAdminV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_hypervisor.py b/tempest/api/compute/v3/admin/test_hypervisor.py
new file mode 100644
index 0000000..3da3369
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_hypervisor.py
@@ -0,0 +1,105 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# 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.compute import base
+from tempest.test import attr
+
+
+class HypervisorAdminV3TestJSON(base.BaseV3ComputeAdminTest):
+
+ """
+ Tests Hypervisors API that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(HypervisorAdminV3TestJSON, cls).setUpClass()
+ cls.client = cls.hypervisor_admin_client
+
+ def _list_hypervisors(self):
+ # List of hypervisors
+ resp, hypers = self.client.get_hypervisor_list()
+ self.assertEqual(200, resp.status)
+ return hypers
+
+ @attr(type='gate')
+ def test_get_hypervisor_list(self):
+ # List of hypervisor and available hypervisors hostname
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ @attr(type='gate')
+ def test_get_hypervisor_list_details(self):
+ # Display the details of the all hypervisor
+ resp, hypers = self.client.get_hypervisor_list_details()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hypers) > 0)
+
+ @attr(type='gate')
+ def test_get_hypervisor_show_details(self):
+ # Display the details of the specified hypervisor
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ resp, details = (self.client.
+ get_hypervisor_show_details(hypers[0]['id']))
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(details) > 0)
+ self.assertEqual(details['hypervisor_hostname'],
+ hypers[0]['hypervisor_hostname'])
+
+ @attr(type='gate')
+ def test_get_hypervisor_show_servers(self):
+ # Show instances about the specific hypervisors
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ hypervisor_id = hypers[0]['id']
+ resp, hypervisors = self.client.get_hypervisor_servers(hypervisor_id)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hypervisors) > 0)
+
+ @attr(type='gate')
+ def test_get_hypervisor_stats(self):
+ # Verify the stats of the all hypervisor
+ resp, stats = self.client.get_hypervisor_stats()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(stats) > 0)
+
+ @attr(type='gate')
+ def test_get_hypervisor_uptime(self):
+ # Verify that GET shows the specified hypervisor uptime
+ hypers = self._list_hypervisors()
+
+ resp, uptime = self.client.get_hypervisor_uptime(hypers[0]['id'])
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(uptime) > 0)
+
+ @attr(type='gate')
+ def test_search_hypervisor(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+ resp, hypers = self.client.search_hypervisor(
+ hypers[0]['hypervisor_hostname'])
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hypers) > 0)
+
+
+class HypervisorAdminV3TestXML(HypervisorAdminV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_hypervisor_negative.py b/tempest/api/compute/v3/admin/test_hypervisor_negative.py
new file mode 100644
index 0000000..847679e
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_hypervisor_negative.py
@@ -0,0 +1,144 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+# 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.
+
+import uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class HypervisorAdminNegativeV3TestJSON(base.BaseV3ComputeAdminTest):
+
+ """
+ Tests Hypervisors API that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(HypervisorAdminNegativeV3TestJSON, cls).setUpClass()
+ cls.client = cls.hypervisor_admin_client
+ cls.non_adm_client = cls.hypervisor_client
+
+ def _list_hypervisors(self):
+ # List of hypervisors
+ resp, hypers = self.client.get_hypervisor_list()
+ self.assertEqual(200, resp.status)
+ return hypers
+
+ @attr(type=['negative', 'gate'])
+ def test_show_nonexistent_hypervisor(self):
+ nonexistent_hypervisor_id = str(uuid.uuid4())
+
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.get_hypervisor_show_details,
+ nonexistent_hypervisor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_show_hypervisor_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_show_details,
+ hypers[0]['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_show_servers_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_servers,
+ hypers[0]['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_show_servers_with_nonexistent_hypervisor(self):
+ nonexistent_hypervisor_id = str(uuid.uuid4())
+
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.get_hypervisor_servers,
+ nonexistent_hypervisor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_stats_with_non_admin_user(self):
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_stats)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_nonexistent_hypervisor_uptime(self):
+ nonexistent_hypervisor_id = str(uuid.uuid4())
+
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.get_hypervisor_uptime,
+ nonexistent_hypervisor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_uptime_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_uptime,
+ hypers[0]['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_list_with_non_admin_user(self):
+ # List of hypervisor and available services with non admin user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_list)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_list_details_with_non_admin_user(self):
+ # List of hypervisor details and available services with non admin user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_list_details)
+
+ @attr(type=['negative', 'gate'])
+ def test_search_nonexistent_hypervisor(self):
+ nonexistent_hypervisor_name = data_utils.rand_name('test_hypervisor')
+
+ resp, hypers = self.client.search_hypervisor(
+ nonexistent_hypervisor_name)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(hypers))
+
+ @attr(type=['negative', 'gate'])
+ def test_search_hypervisor_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.search_hypervisor,
+ hypers[0]['hypervisor_hostname'])
+
+
+class HypervisorAdminNegativeV3TestXML(HypervisorAdminNegativeV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
new file mode 100644
index 0000000..a599f06
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
@@ -0,0 +1,115 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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.
+
+import datetime
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+import time
+
+
+class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TenantUsagesTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.tenant_usages_client
+ cls.client = cls.os.tenant_usages_client
+ cls.identity_client = cls._get_identity_admin_client()
+
+ resp, tenants = cls.identity_client.list_tenants()
+ cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+ cls.client.tenant_name][0]
+
+ # Create a server in the demo tenant
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ time.sleep(2)
+
+ now = datetime.datetime.now()
+ cls.start = cls._parse_strtime(now - datetime.timedelta(days=1))
+ cls.end = cls._parse_strtime(now + datetime.timedelta(days=1))
+
+ @classmethod
+ def _parse_strtime(cls, at):
+ # Returns formatted datetime
+ return at.strftime('%Y-%m-%dT%H:%M:%S.%f')
+
+ @attr(type='gate')
+ def test_list_usage_all_tenants(self):
+ # Get usage for all tenants
+ params = {'start': self.start,
+ 'end': self.end,
+ 'detailed': int(bool(True))}
+ resp, tenant_usage = self.adm_client.list_tenant_usages(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type='gate')
+ def test_get_usage_tenant(self):
+ # Get usage for a specific tenant
+ params = {'start': self.start,
+ 'end': self.end}
+ resp, tenant_usage = self.adm_client.get_tenant_usage(
+ self.tenant_id, params)
+
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type='gate')
+ def test_get_usage_tenant_with_non_admin_user(self):
+ # Get usage for a specific tenant with non admin user
+ params = {'start': self.start,
+ 'end': self.end}
+ resp, tenant_usage = self.client.get_tenant_usage(
+ self.tenant_id, params)
+
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_usage_tenant_with_empty_tenant_id(self):
+ # Get usage for a specific tenant empty
+ params = {'start': self.start,
+ 'end': self.end}
+ self.assertRaises(exceptions.NotFound,
+ self.adm_client.get_tenant_usage,
+ '', params)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_usage_tenant_with_invalid_date(self):
+ # Get usage for tenant with invalid date
+ params = {'start': self.end,
+ 'end': self.start}
+ self.assertRaises(exceptions.BadRequest,
+ self.adm_client.get_tenant_usage,
+ self.tenant_id, params)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_usage_all_tenants_with_non_admin_user(self):
+ # Get usage for all tenants with non admin user
+ params = {'start': self.start,
+ 'end': self.end,
+ 'detailed': int(bool(True))}
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.list_tenant_usages, params)
+
+
+class TenantUsagesTestXML(TenantUsagesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_attach_interfaces.py b/tempest/api/compute/v3/servers/test_attach_interfaces.py
index a177cea..f208a4b 100644
--- a/tempest/api/compute/v3/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/v3/servers/test_attach_interfaces.py
@@ -19,15 +19,15 @@
import time
-class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
+class AttachInterfacesV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
if not cls.config.service_available.neutron:
raise cls.skipException("Neutron is required")
- super(AttachInterfacesTestJSON, cls).setUpClass()
- cls.client = cls.os.interfaces_client
+ super(AttachInterfacesV3TestJSON, cls).setUpClass()
+ cls.client = cls.interfaces_client
def _check_interface(self, iface, port_id=None, network_id=None,
fixed_ip=None):
@@ -114,5 +114,5 @@
self.assertEqual(len(ifs) - 1, len(_ifs))
-class AttachInterfacesTestXML(AttachInterfacesTestJSON):
+class AttachInterfacesV3TestXML(AttachInterfacesV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_list_server_filters.py b/tempest/api/compute/v3/servers/test_list_server_filters.py
index 5f14460..d333a1d 100644
--- a/tempest/api/compute/v3/servers/test_list_server_filters.py
+++ b/tempest/api/compute/v3/servers/test_list_server_filters.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest.api import utils
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
from tempest.test import attr
@@ -57,16 +57,16 @@
raise RuntimeError("Image %s (image_ref_alt) was not found!" %
cls.image_ref_alt)
- cls.s1_name = rand_name(cls.__name__ + '-instance')
+ cls.s1_name = data_utils.rand_name(cls.__name__ + '-instance')
resp, cls.s1 = cls.create_test_server(name=cls.s1_name,
wait_until='ACTIVE')
- cls.s2_name = rand_name(cls.__name__ + '-instance')
+ cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance')
resp, cls.s2 = cls.create_test_server(name=cls.s2_name,
image_id=cls.image_ref_alt,
wait_until='ACTIVE')
- cls.s3_name = rand_name(cls.__name__ + '-instance')
+ cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance')
resp, cls.s3 = cls.create_test_server(name=cls.s3_name,
flavor=cls.flavor_ref_alt,
wait_until='ACTIVE')
diff --git a/tempest/api/compute/v3/servers/test_server_addresses.py b/tempest/api/compute/v3/servers/test_server_addresses.py
index 7ca8a52..82588b6 100644
--- a/tempest/api/compute/v3/servers/test_server_addresses.py
+++ b/tempest/api/compute/v3/servers/test_server_addresses.py
@@ -20,12 +20,12 @@
from tempest.test import attr
-class ServerAddressesTest(base.BaseV2ComputeTest):
+class ServerAddressesV3Test(base.BaseV3ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(ServerAddressesTest, cls).setUpClass()
+ super(ServerAddressesV3Test, cls).setUpClass()
cls.client = cls.servers_client
resp, cls.server = cls.create_test_server(wait_until='ACTIVE')
@@ -80,5 +80,5 @@
self.assertTrue(any([a for a in addr if a == address]))
-class ServerAddressesTestXML(ServerAddressesTest):
+class ServerAddressesV3TestXML(ServerAddressesV3Test):
_interface = 'xml'
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 5b04cbb..9c187fd 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -15,7 +15,7 @@
# under the License.
from tempest.api.network import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -50,7 +50,7 @@
@attr(type='smoke')
def test_list_l3_agents_hosting_router(self):
- name = rand_name('router-')
+ name = data_utils.rand_name('router-')
resp, router = self.client.create_router(name)
self.assertEqual('201', resp['status'])
resp, body = self.admin_client.list_l3_agents_hosting_router(
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index c6639c2..e7cb806 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -18,6 +18,7 @@
from tempest.api.identity.base import DataGenerator
from tempest import clients
+from tempest.common import custom_matchers
from tempest.common import isolated_creds
from tempest import exceptions
import tempest.test
@@ -120,3 +121,12 @@
container_client.delete_container(cont)
except exceptions.NotFound:
pass
+
+ def assertHeaders(self, resp, target, method):
+ """
+ Common method to check the existence and the format of common response
+ headers
+ """
+ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+ target, method))
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 615b179..12c823b 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -46,6 +46,7 @@
params = {'format': 'json'}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
self.assertIsNotNone(container_list)
container_names = [c['name'] for c in container_list]
@@ -59,6 +60,8 @@
params = {'limit': limit}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+
self.assertEqual(len(container_list), limit)
@attr(type='smoke')
@@ -70,10 +73,15 @@
params = {'marker': self.containers[-1]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+
self.assertEqual(len(container_list), 0)
+
params = {'marker': self.containers[self.containers_count / 2]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+
self.assertEqual(len(container_list), self.containers_count / 2 - 1)
@attr(type='smoke')
@@ -85,10 +93,13 @@
params = {'end_marker': self.containers[0]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
self.assertEqual(len(container_list), 0)
+
params = {'end_marker': self.containers[self.containers_count / 2]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
self.assertEqual(len(container_list), self.containers_count / 2)
@attr(type='smoke')
@@ -101,6 +112,8 @@
'limit': limit}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+
self.assertTrue(len(container_list) <= limit, str(container_list))
@attr(type='smoke')
@@ -108,9 +121,7 @@
# list all account metadata
resp, metadata = self.account_client.list_account_metadata()
self.assertIn(int(resp['status']), HTTP_SUCCESS)
- self.assertIn('x-account-object-count', resp)
- self.assertIn('x-account-container-count', resp)
- self.assertIn('x-account-bytes-used', resp)
+ self.assertHeaders(resp, 'Account', 'HEAD')
@attr(type='smoke')
def test_create_and_delete_account_metadata(self):
@@ -120,8 +131,11 @@
resp, _ = self.account_client.create_account_metadata(
metadata={header: data})
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Account', 'POST')
resp, _ = self.account_client.list_account_metadata()
+ self.assertHeaders(resp, 'Account', 'HEAD')
+
self.assertIn('x-account-meta-' + header, resp)
self.assertEqual(resp['x-account-meta-' + header], data)
@@ -129,8 +143,10 @@
resp, _ = \
self.account_client.delete_account_metadata(metadata=[header])
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Account', 'POST')
resp, _ = self.account_client.list_account_metadata()
+ self.assertHeaders(resp, 'Account', 'HEAD')
self.assertNotIn('x-account-meta-' + header, resp)
@attr(type=['negative', 'gate'])
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 4fae953..04de072 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -38,6 +38,7 @@
resp, body = self.container_client.create_container(container_name)
self.containers.append(container_name)
self.assertIn(resp['status'], ('202', '201'))
+ self.assertHeaders(resp, 'Container', 'PUT')
@attr(type='smoke')
def test_delete_container(self):
@@ -48,6 +49,8 @@
# delete container
resp, _ = self.container_client.delete_container(container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'DELETE')
+
self.containers.remove(container_name)
@attr(type='smoke')
@@ -76,6 +79,8 @@
self.container_client.\
list_container_contents(container_name, params=params)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'GET')
+
self.assertIsNotNone(object_list)
object_names = [obj['name'] for obj in object_list]
@@ -97,11 +102,14 @@
self.container_client.update_container_metadata(container_name,
metadata=metadata)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'POST')
# list container metadata
resp, _ = self.container_client.list_container_metadata(
container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'HEAD')
+
self.assertIn('x-container-meta-name', resp)
self.assertIn('x-container-meta-description', resp)
self.assertEqual(resp['x-container-meta-name'], 'Pictures')
@@ -112,9 +120,12 @@
container_name,
metadata=metadata.keys())
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'POST')
# check if the metadata are no longer there
resp, _ = self.container_client.list_container_metadata(container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'HEAD')
+
self.assertNotIn('x-container-meta-name', resp)
self.assertNotIn('x-container-meta-description', resp)
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 7626af1..c7fdd0e 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -16,8 +16,10 @@
# under the License.
import hashlib
+import re
from tempest.api.object_storage import base
+from tempest.common import custom_matchers
from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import HTTP_SUCCESS
@@ -60,6 +62,7 @@
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
@attr(type='smoke')
def test_delete_object(self):
@@ -72,6 +75,7 @@
resp, _ = self.object_client.delete_object(self.container_name,
object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'DELETE')
@attr(type='smoke')
def test_object_metadata(self):
@@ -89,11 +93,14 @@
resp, _ = self.object_client.update_object_metadata(
self.container_name, object_name, orig_metadata)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'POST')
# get object metadata
resp, resp_metadata = self.object_client.list_object_metadata(
self.container_name, object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'HEAD')
+
actual_meta_key = 'x-object-meta-' + meta_key
self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@@ -111,6 +118,8 @@
resp, body = self.object_client.get_object(self.container_name,
object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+
self.assertEqual(body, data)
@attr(type='smoke')
@@ -133,6 +142,8 @@
resp, _ = self.object_client.copy_object_in_same_container(
self.container_name, src_object_name, dst_object_name)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
# check data
resp, body = self.object_client.get_object(self.container_name,
dst_object_name)
@@ -156,6 +167,8 @@
resp, _ = self.object_client.copy_object_in_same_container(
self.container_name, object_name, object_name, metadata)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
# check the content type
resp, _ = self.object_client.list_object_metadata(self.container_name,
object_name)
@@ -180,6 +193,17 @@
src_object_name,
dst_object_name)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ self.assertIn('last-modified', resp)
+ self.assertIn('x-copied-from', resp)
+ self.assertIn('x-copied-from-last-modified', resp)
+ self.assertNotEqual(len(resp['last-modified']), 0)
+ self.assertEqual(
+ resp['x-copied-from'],
+ self.container_name + "/" + src_object_name)
+ self.assertNotEqual(len(resp['x-copied-from-last-modified']), 0)
+
# check data
resp, body = self.object_client.get_object(self.container_name,
dst_object_name)
@@ -210,11 +234,15 @@
object_name,
orig_metadata)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'POST')
+
# copy object from source container to destination container
resp, _ = self.object_client.copy_object_across_containers(
src_container_name, object_name, dst_container_name,
object_name)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
# check if object is present in destination container
resp, body = self.object_client.get_object(dst_container_name,
object_name)
@@ -238,13 +266,34 @@
# creating a manifest file
metadata = {'X-Object-Manifest': '%s/%s/'
% (self.container_name, object_name)}
- self.object_client.create_object(self.container_name,
- object_name, data='')
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data='')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
resp, _ = self.object_client.update_object_metadata(
self.container_name, object_name, metadata, metadata_prefix='')
+ self.assertHeaders(resp, 'Object', 'POST')
+
resp, _ = self.object_client.list_object_metadata(
self.container_name, object_name)
+
+ # Check only the existence of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+ 'Object', 'HEAD'))
self.assertIn('x-object-manifest', resp)
+
+ # Etag value of a large object is enclosed in double-quotations.
+ # This is a special case, therefore the formats of response headers
+ # are checked without a custom matcher.
+ self.assertTrue(resp['etag'].startswith('\"'))
+ self.assertTrue(resp['etag'].endswith('\"'))
+ self.assertTrue(resp['etag'].strip('\"').isalnum())
+ self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+ self.assertNotEqual(len(resp['content-type']), 0)
+ self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$",
+ resp['x-trans-id']))
+ self.assertNotEqual(len(resp['date']), 0)
+ self.assertEqual(resp['accept-ranges'], 'bytes')
self.assertEqual(resp['x-object-manifest'],
'%s/%s/' % (self.container_name, object_name))
@@ -270,12 +319,23 @@
resp, _ = self.object_client.get(url, headers=headers)
self.assertEqual(resp['status'], '304')
+ # When the file is not downloaded from Swift server, response does
+ # not contain 'X-Timestamp' header. This is the special case, therefore
+ # the existence of response headers is checked without custom matcher.
+ self.assertIn('content-type', resp)
+ self.assertIn('x-trans-id', resp)
+ self.assertIn('date', resp)
+ self.assertIn('accept-ranges', resp)
+ # Check only the format of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+
# local copy is different, download
local_data = "something different"
md5 = hashlib.md5(local_data).hexdigest()
headers = {'If-None-Match': md5}
resp, body = self.object_client.get(url, headers=headers)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
class PublicObjectTest(base.BaseObjectTest):
@@ -298,6 +358,8 @@
resp_meta, body = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers, metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
+
# create object
object_name = data_utils.rand_name(name='Object')
data = data_utils.arbitrary_string(size=len(object_name),
@@ -305,17 +367,22 @@
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# list container metadata
resp_meta, _ = self.container_client.list_container_metadata(
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'HEAD')
+
self.assertIn('x-container-read', resp_meta)
self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings')
# trying to get object with empty headers as it is public readable
resp, body = self.custom_object_client.get_object(
self.container_name, object_name, metadata={})
+ self.assertHeaders(resp, 'Object', 'GET')
+
self.assertEqual(body, data)
@attr(type='smoke')
@@ -327,6 +394,7 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
@@ -335,11 +403,14 @@
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# list container metadata
resp, _ = self.container_client.list_container_metadata(
self.container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'HEAD')
+
self.assertIn('x-container-read', resp)
self.assertEqual(resp['x-container-read'], '.r:*,.rlistings')
@@ -350,4 +421,6 @@
resp, body = self.custom_object_client.get_object(
self.container_name, object_name,
metadata=headers)
+ self.assertHeaders(resp, 'Object', 'GET')
+
self.assertEqual(body, data)
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 6bc350a..03e8469 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -29,6 +29,7 @@
def setUpClass(cls):
super(VolumeMultiBackendTest, cls).setUpClass()
if not cls.config.volume_feature_enabled.multi_backend:
+ cls.tearDownClass()
raise cls.skipException("Cinder multi-backend feature disabled")
cls.backend1_name = cls.config.volume.backend1_name
@@ -89,12 +90,14 @@
@classmethod
def tearDownClass(cls):
# volumes deletion
- for volume_id in cls.volume_id_list:
+ volume_id_list = getattr(cls, 'volume_id_list', [])
+ for volume_id in volume_id_list:
cls.volume_client.delete_volume(volume_id)
cls.volume_client.wait_for_resource_deletion(volume_id)
# volume types deletion
- for volume_type_id in cls.volume_type_id_list:
+ volume_type_id_list = getattr(cls, 'volume_type_id_list', [])
+ for volume_type_id in volume_type_id_list:
cls.type_client.delete_volume_type(volume_type_id)
super(VolumeMultiBackendTest, cls).tearDownClass()
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 30c2c74..8581d16 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -140,6 +140,35 @@
self.assertEqual(200, resp.status)
self.assertIn('available', body['status'])
+ def _is_true(self, val):
+ return val in ['true', 'True', True]
+
+ @attr(type='gate')
+ def test_volume_readonly_update(self):
+ # Update volume readonly true
+ readonly = True
+ resp, body = self.client.update_volume_readonly(self.volume['id'],
+ readonly)
+ self.assertEqual(202, resp.status)
+
+ # Get Volume information
+ resp, fetched_volume = self.client.get_volume(self.volume['id'])
+ bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(True, bool_flag)
+
+ # Update volume readonly false
+ readonly = False
+ resp, body = self.client.update_volume_readonly(self.volume['id'],
+ readonly)
+ self.assertEqual(202, resp.status)
+
+ # Get Volume information
+ resp, fetched_volume = self.client.get_volume(self.volume['id'])
+ bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(False, bool_flag)
+
class VolumesActionsTestXML(VolumesActionsTest):
_interface = "xml"
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 25157a4..eb5df63 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -136,3 +136,139 @@
if not region:
region = self.config.identity.region
self.cinder('list', flags='--os-region-name ' + region)
+
+
+class SimpleReadOnlyCinderClientV2Test(tempest.cli.ClientTestBase):
+ """Basic, read-only tests for Cinder v2 CLI client.
+
+ Checks return values and output of read-only commands.
+ These tests do not presume any content, nor do they create
+ their own. They only verify the structure of output if present.
+ """
+
+ version_flag = '--os-volume-api-version'
+ version_support = 2
+
+ def _get_resp_from_diff_versions(self, version_tuple, cmd, param=None):
+ # Get each result from different versions.
+ # Note(wingwj): If you want to get each cli response on v1&v2 version,
+ # the value of version_tuple should be (1, 2).
+ # The function will be also available for v3 in future
+
+ resp_set = []
+ for i in set(version_tuple):
+ if not isinstance(i, int) or \
+ i not in range(1, self.version_support + 1):
+ LOG.error("version_num must be a positive integer, "
+ "Cinder supports %s versions now"
+ % self.version_support)
+ continue
+ else:
+ if not param:
+ item = self.cinder(cmd,
+ flags=self.version_flag + ' ' + str(i))
+ else:
+ item = self.cinder(cmd, params=param,
+ flags=self.version_flag + ' ' + str(i))
+ resp_set.append(item)
+
+ return resp_set
+
+ def _check_accordance_between_two_versions(self, input1, input2,
+ expected=True):
+ # Check input1 and input2 is not Empty first
+ self.assertNotEqual("", input1)
+ self.assertNotEqual("", input2)
+ # Compare input1 and input2 based on 'expected' parameter
+ if expected:
+ self.assertEqual(input1, input2)
+ else:
+ self.assertNotEqual(input1, input2)
+
+ def test_cinder_volumes_list_in_v2(self):
+ cmd = 'list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ # This CLI representation in v1/v2 is different.
+ self._check_accordance_between_two_versions(v1, v2, False)
+
+ def test_cinder_absolute_limit_list_in_v2(self):
+ cmd = 'absolute-limits'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_backup_list_in_v2(self):
+ cmd = 'backup-list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_extra_specs_list_in_v2(self):
+ cmd = 'extra-specs-list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_quota_class_show_in_v2(self):
+ cmd = 'quota-class-show'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd, 'abc')
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_quota_defaults_in_v2(self):
+ cmd = 'quota-defaults'
+ v1, v2 = self._get_resp_from_diff_versions(
+ (1, 2), cmd, self.identity.admin_tenant_name)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_quota_show_in_v2(self):
+ cmd = 'quota-show'
+ v1, v2 = self._get_resp_from_diff_versions(
+ (1, 2), cmd, self.identity.admin_tenant_name)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_rate_limits_in_v2(self):
+ cmd = 'rate-limits'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_snapshot_list_in_v2(self):
+ cmd = 'snapshot-list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ # This CLI representation in v1/v2 is different.
+ self._check_accordance_between_two_versions(v1, v2, False)
+
+ def test_cinder_type_list_in_v2(self):
+ cmd = 'type-list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_availability_zone_list_in_v2(self):
+ cmd = 'availability-zone-list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_service_list_in_v2(self):
+ cmd = 'service-list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self.assertNotEqual("", v1)
+ self.assertNotEqual("", v2)
+
+ # The 'Updated_at' and 'State' item may be changed
+ # due to the periodical update-task. Need to omit.
+ v1 = self.parser.listing(v1)
+ v2 = self.parser.listing(v2)
+ for i in v1:
+ del i['Updated_at']
+ del i['State']
+ for j in v2:
+ del j['Updated_at']
+ del j['State']
+ self.assertEqual(v1, v2)
+
+ def test_cinder_transfer_list_in_v2(self):
+ cmd = 'transfer-list'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ self._check_accordance_between_two_versions(v1, v2, True)
+
+ def test_cinder_bash_completion_in_v2(self):
+ cmd = 'bash-completion'
+ v1, v2 = self._get_resp_from_diff_versions((1, 2), cmd)
+ # This CLI representation in v1/v2 is different.
+ self._check_accordance_between_two_versions(v1, v2, False)
diff --git a/tempest/clients.py b/tempest/clients.py
index 9029d5f..291b946 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -50,14 +50,26 @@
TenantUsagesClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
+from tempest.services.compute.v3.json.availability_zone_client import \
+ AvailabilityZoneV3ClientJSON
from tempest.services.compute.v3.json.extensions_client import \
ExtensionsV3ClientJSON
+from tempest.services.compute.v3.json.hypervisor_client import \
+ HypervisorV3ClientJSON
+from tempest.services.compute.v3.json.interfaces_client import \
+ InterfacesV3ClientJSON
from tempest.services.compute.v3.json.servers_client import \
ServersV3ClientJSON
from tempest.services.compute.v3.json.services_client import \
ServicesV3ClientJSON
+from tempest.services.compute.v3.xml.availability_zone_client import \
+ AvailabilityZoneV3ClientXML
from tempest.services.compute.v3.xml.extensions_client import \
ExtensionsV3ClientXML
+from tempest.services.compute.v3.xml.hypervisor_client import \
+ HypervisorV3ClientXML
+from tempest.services.compute.v3.xml.interfaces_client import \
+ InterfacesV3ClientXML
from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
from tempest.services.compute.v3.xml.services_client import \
ServicesV3ClientXML
@@ -206,9 +218,12 @@
self.token_client = TokenClientXML(self.config)
self.security_groups_client = SecurityGroupsClientXML(
*client_args)
+ self.interfaces_v3_client = InterfacesV3ClientXML(*client_args)
self.interfaces_client = InterfacesClientXML(*client_args)
self.endpoints_client = EndPointClientXML(*client_args)
self.fixed_ips_client = FixedIPsClientXML(*client_args)
+ self.availability_zone_v3_client = AvailabilityZoneV3ClientXML(
+ *client_args)
self.availability_zone_client = AvailabilityZoneClientXML(
*client_args)
self.services_v3_client = ServicesV3ClientXML(*client_args)
@@ -217,6 +232,7 @@
self.services_client = ServicesClientXML(*client_args)
self.tenant_usages_client = TenantUsagesClientXML(*client_args)
self.policy_client = PolicyClientXML(*client_args)
+ self.hypervisor_v3_client = HypervisorV3ClientXML(*client_args)
self.hypervisor_client = HypervisorClientXML(*client_args)
self.token_v3_client = V3TokenClientXML(*client_args)
self.network_client = NetworkClientXML(*client_args)
@@ -250,9 +266,12 @@
self.token_client = TokenClientJSON(self.config)
self.security_groups_client = SecurityGroupsClientJSON(
*client_args)
+ self.interfaces_v3_client = InterfacesV3ClientJSON(*client_args)
self.interfaces_client = InterfacesClientJSON(*client_args)
self.endpoints_client = EndPointClientJSON(*client_args)
self.fixed_ips_client = FixedIPsClientJSON(*client_args)
+ self.availability_zone_v3_client = AvailabilityZoneV3ClientJSON(
+ *client_args)
self.availability_zone_client = AvailabilityZoneClientJSON(
*client_args)
self.services_v3_client = ServicesV3ClientJSON(*client_args)
@@ -261,6 +280,7 @@
self.services_client = ServicesClientJSON(*client_args)
self.tenant_usages_client = TenantUsagesClientJSON(*client_args)
self.policy_client = PolicyClientJSON(*client_args)
+ self.hypervisor_v3_client = HypervisorV3ClientJSON(*client_args)
self.hypervisor_client = HypervisorClientJSON(*client_args)
self.token_v3_client = V3TokenClientJSON(*client_args)
self.network_client = NetworkClientJSON(*client_args)
@@ -298,11 +318,12 @@
managed client objects
"""
- def __init__(self):
+ def __init__(self, interface='json'):
conf = config.TempestConfig()
super(AltManager, self).__init__(conf.identity.alt_username,
conf.identity.alt_password,
- conf.identity.alt_tenant_name)
+ conf.identity.alt_tenant_name,
+ interface=interface)
class AdminManager(Manager):
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
new file mode 100644
index 0000000..307d5db
--- /dev/null
+++ b/tempest/common/custom_matchers.py
@@ -0,0 +1,154 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NTT Corporation
+#
+# 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
+
+
+class ExistsAllResponseHeaders(object):
+ """
+ Specific matcher to check the existence of Swift's response headers
+
+ This matcher checks the existence of common headers for each HTTP method
+ or the target, which means account, container or object.
+ When checking the existence of 'specific' headers such as
+ X-Account-Meta-* or X-Object-Manifest for example, those headers must be
+ checked in each test code.
+ """
+
+ def __init__(self, target, method):
+ """
+ param: target Account/Container/Object
+ param: method PUT/GET/HEAD/DELETE/COPY/POST
+ """
+ self.target = target
+ self.method = method
+
+ def match(self, actual):
+ """
+ param: actual HTTP response headers
+ """
+ # Check common headers for all HTTP methods
+ if 'content-length' not in actual:
+ return NonExistentHeader('content-length')
+ if 'content-type' not in actual:
+ return NonExistentHeader('content-type')
+ if 'x-trans-id' not in actual:
+ return NonExistentHeader('x-trans-id')
+ if 'date' not in actual:
+ return NonExistentHeader('date')
+
+ # Check headers for a specific method or target
+ if self.method == 'GET' or self.method == 'HEAD':
+ if 'x-timestamp' not in actual:
+ return NonExistentHeader('x-timestamp')
+ if 'accept-ranges' not in actual:
+ return NonExistentHeader('accept-ranges')
+ if self.target == 'Account':
+ if 'x-account-bytes-used' not in actual:
+ return NonExistentHeader('x-account-bytes-used')
+ if 'x-account-container-count' not in actual:
+ return NonExistentHeader('x-account-container-count')
+ if 'x-account-object-count' not in actual:
+ return NonExistentHeader('x-account-object-count')
+ elif self.target == 'Container':
+ if 'x-container-bytes-used' not in actual:
+ return NonExistentHeader('x-container-bytes-used')
+ if 'x-container-object-count' not in actual:
+ return NonExistentHeader('x-container-object-count')
+ elif self.target == 'Object':
+ if 'etag' not in actual:
+ return NonExistentHeader('etag')
+ elif self.method == 'PUT' or self.method == 'COPY':
+ if self.target == 'Object':
+ if 'etag' not in actual:
+ return NonExistentHeader('etag')
+
+ return None
+
+
+class NonExistentHeader(object):
+ """
+ Informs an error message for end users in the case of missing a
+ certain header in Swift's responses
+ """
+
+ def __init__(self, header):
+ self.header = header
+
+ def describe(self):
+ return "%s header does not exist" % self.header
+
+ def get_details(self):
+ return {}
+
+
+class AreAllWellFormatted(object):
+ """
+ Specific matcher to check the correctness of formats of values of Swift's
+ response headers
+
+ This matcher checks the format of values of response headers.
+ When checking the format of values of 'specific' headers such as
+ X-Account-Meta-* or X-Object-Manifest for example, those values must be
+ checked in each test code.
+ """
+
+ def match(self, actual):
+ for key, value in actual.iteritems():
+ if key == 'content-length' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value):
+ return InvalidFormat(key, value)
+ elif key == 'x-account-bytes-used' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-account-container-count' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-account-object-count' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-container-bytes-used' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-container-object-count' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'content-type' and not value:
+ return InvalidFormat(key, value)
+ elif key == 'x-trans-id' and \
+ not re.match("^tx[0-9a-f]*-[0-9a-f]*$", value):
+ return InvalidFormat(key, value)
+ elif key == 'date' and not value:
+ return InvalidFormat(key, value)
+ elif key == 'accept-ranges' and not value == 'bytes':
+ return InvalidFormat(key, value)
+ elif key == 'etag' and not value.isalnum():
+ return InvalidFormat(key, value)
+
+ return None
+
+
+class InvalidFormat(object):
+ """
+ Informs an error message for end users if a format of a certain header
+ is invalid
+ """
+
+ def __init__(self, key, value):
+ self.key = key
+ self.value = value
+
+ def describe(self):
+ return "InvalidFormat (%s, %s)" % (self.key, self.value)
+
+ def get_details(self):
+ return {}
diff --git a/tempest/common/generate_sample_tempest.py b/tempest/common/generate_sample_tempest.py
index 3811088..545703b 100644
--- a/tempest/common/generate_sample_tempest.py
+++ b/tempest/common/generate_sample_tempest.py
@@ -21,6 +21,15 @@
from tempest import config
from tempest.openstack.common.config import generator
+# NOTE(mtreinish): This hack is needed because of how oslo config is used in
+# tempest. Tempest is run from inside a test runner and so we can't rely on the
+# global CONF object being fully populated when we run a test. (test runners
+# don't init every file for running a test) So to get around that we manually
+# load the config file in tempest for each test class to ensure that every
+# config option is set. However, the tool expects the CONF object to be fully
+# populated when it inits all the files in the project. This just works around
+# the issue by manually loading the config file (which may or may not exist)
+# which will populate all the options before running the generator.
config.TempestConfig()
generator.generate(sys.argv[1:])
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index 9459590..5dbb3a7 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -412,7 +412,11 @@
resp_body = self.network_admin_client.list_ports()
self.ports = resp_body['ports']
ports_to_delete = [
- port for port in self.ports if port['network_id'] == network_id]
+ port
+ for port in self.ports
+ if (port['network_id'] == network_id and
+ port['device_owner'] != 'network:router_interface')
+ ]
for port in ports_to_delete:
try:
LOG.info('Cleaning up port id %s, name %s' %
diff --git a/tempest/config.py b/tempest/config.py
index 009147b..6af7d51 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -211,7 +211,7 @@
ComputeFeaturesGroup = [
cfg.BoolOpt('api_v3',
- default=False,
+ default=True,
help="If false, skip all nova v3 tests."),
cfg.BoolOpt('disk_config',
default=True,
diff --git a/tempest/services/compute/v3/json/availability_zone_client.py b/tempest/services/compute/v3/json/availability_zone_client.py
index b11871b..9a3fe8b 100644
--- a/tempest/services/compute/v3/json/availability_zone_client.py
+++ b/tempest/services/compute/v3/json/availability_zone_client.py
@@ -20,20 +20,20 @@
from tempest.common.rest_client import RestClient
-class AvailabilityZoneClientJSON(RestClient):
+class AvailabilityZoneV3ClientJSON(RestClient):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(AvailabilityZoneClientJSON, self).__init__(config, username,
- password, auth_url,
- tenant_name)
- self.service = self.config.compute.catalog_type
+ super(AvailabilityZoneV3ClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def get_availability_zone_list(self):
resp, body = self.get('os-availability-zone')
body = json.loads(body)
- return resp, body['availabilityZoneInfo']
+ return resp, body['availability_zone_info']
def get_availability_zone_list_detail(self):
resp, body = self.get('os-availability-zone/detail')
body = json.loads(body)
- return resp, body['availabilityZoneInfo']
+ return resp, body['availability_zone_info']
diff --git a/tempest/services/compute/v3/json/hypervisor_client.py b/tempest/services/compute/v3/json/hypervisor_client.py
new file mode 100644
index 0000000..fa1255a
--- /dev/null
+++ b/tempest/services/compute/v3/json/hypervisor_client.py
@@ -0,0 +1,71 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation.
+# 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.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class HypervisorV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(HypervisorV3ClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def get_hypervisor_list(self):
+ """List hypervisors information."""
+ resp, body = self.get('os-hypervisors')
+ body = json.loads(body)
+ return resp, body['hypervisors']
+
+ def get_hypervisor_list_details(self):
+ """Show detailed hypervisors information."""
+ resp, body = self.get('os-hypervisors/detail')
+ body = json.loads(body)
+ return resp, body['hypervisors']
+
+ def get_hypervisor_show_details(self, hyper_id):
+ """Display the details of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s' % hyper_id)
+ body = json.loads(body)
+ return resp, body['hypervisor']
+
+ def get_hypervisor_servers(self, hyper_name):
+ """List instances belonging to the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/servers' % hyper_name)
+ body = json.loads(body)
+ return resp, body['hypervisor']
+
+ def get_hypervisor_stats(self):
+ """Get hypervisor statistics over all compute nodes."""
+ resp, body = self.get('os-hypervisors/statistics')
+ body = json.loads(body)
+ return resp, body['hypervisor_statistics']
+
+ def get_hypervisor_uptime(self, hyper_id):
+ """Display the uptime of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id)
+ body = json.loads(body)
+ return resp, body['hypervisor']
+
+ def search_hypervisor(self, hyper_name):
+ """Search specified hypervisor."""
+ resp, body = self.get('os-hypervisors/search?query=%s' % hyper_name)
+ body = json.loads(body)
+ return resp, body['hypervisors']
diff --git a/tempest/services/compute/v3/json/interfaces_client.py b/tempest/services/compute/v3/json/interfaces_client.py
index 06e6476..7fb0fa9 100644
--- a/tempest/services/compute/v3/json/interfaces_client.py
+++ b/tempest/services/compute/v3/json/interfaces_client.py
@@ -20,21 +20,22 @@
from tempest import exceptions
-class InterfacesClientJSON(RestClient):
+class InterfacesV3ClientJSON(RestClient):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(InterfacesClientJSON, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ super(InterfacesV3ClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def list_interfaces(self, server):
- resp, body = self.get('servers/%s/os-interface' % server)
+ resp, body = self.get('servers/%s/os-attach-interfaces' % server)
body = json.loads(body)
- return resp, body['interfaceAttachments']
+ return resp, body['interface_attachments']
def create_interface(self, server, port_id=None, network_id=None,
fixed_ip=None):
- post_body = dict(interfaceAttachment=dict())
+ post_body = dict(interface_attachment=dict())
if port_id:
post_body['port_id'] = port_id
if network_id:
@@ -42,20 +43,22 @@
if fixed_ip:
post_body['fixed_ips'] = [dict(ip_address=fixed_ip)]
post_body = json.dumps(post_body)
- resp, body = self.post('servers/%s/os-interface' % server,
+ resp, body = self.post('servers/%s/os-attach-interfaces' % server,
headers=self.headers,
body=post_body)
body = json.loads(body)
- return resp, body['interfaceAttachment']
+ return resp, body['interface_attachment']
def show_interface(self, server, port_id):
- resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id))
+ resp, body =\
+ self.get('servers/%s/os-attach-interfaces/%s' % (server, port_id))
body = json.loads(body)
- return resp, body['interfaceAttachment']
+ return resp, body['interface_attachment']
def delete_interface(self, server, port_id):
- resp, body = self.delete('servers/%s/os-interface/%s' % (server,
- port_id))
+ resp, body =\
+ self.delete('servers/%s/os-attach-interfaces/%s' % (server,
+ port_id))
return resp, body
def wait_for_interface_status(self, server, port_id, status):
diff --git a/tempest/services/compute/v3/json/tenant_usages_client.py b/tempest/services/compute/v3/json/tenant_usages_client.py
new file mode 100644
index 0000000..4dd6964
--- /dev/null
+++ b/tempest/services/compute/v3/json/tenant_usages_client.py
@@ -0,0 +1,47 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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.
+
+import json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class TenantUsagesClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(TenantUsagesClientJSON, self).__init__(
+ config, username, password, auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_tenant_usages(self, params=None):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['tenant_usages'][0]
+
+ def get_tenant_usage(self, tenant_id, params=None):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['tenant_usage']
diff --git a/tempest/services/compute/v3/xml/availability_zone_client.py b/tempest/services/compute/v3/xml/availability_zone_client.py
index ae93774..35fb2b1 100644
--- a/tempest/services/compute/v3/xml/availability_zone_client.py
+++ b/tempest/services/compute/v3/xml/availability_zone_client.py
@@ -21,13 +21,13 @@
from tempest.services.compute.xml.common import xml_to_json
-class AvailabilityZoneClientXML(RestClientXML):
+class AvailabilityZoneV3ClientXML(RestClientXML):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(AvailabilityZoneClientXML, self).__init__(config, username,
- password, auth_url,
- tenant_name)
- self.service = self.config.compute.catalog_type
+ super(AvailabilityZoneV3ClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def _parse_array(self, node):
return [xml_to_json(x) for x in node]
diff --git a/tempest/services/compute/v3/xml/hypervisor_client.py b/tempest/services/compute/v3/xml/hypervisor_client.py
new file mode 100644
index 0000000..ce0207d
--- /dev/null
+++ b/tempest/services/compute/v3/xml/hypervisor_client.py
@@ -0,0 +1,79 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# 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 lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class HypervisorV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(HypervisorV3ClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def _parse_array(self, node):
+ return [xml_to_json(x) for x in node]
+
+ def get_hypervisor_list(self):
+ """List hypervisors information."""
+ resp, body = self.get('os-hypervisors', self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
+
+ def get_hypervisor_list_details(self):
+ """Show detailed hypervisors information."""
+ resp, body = self.get('os-hypervisors/detail', self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
+
+ def get_hypervisor_show_details(self, hyper_id):
+ """Display the details of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s' % hyper_id,
+ self.headers)
+ hypervisor = xml_to_json(etree.fromstring(body))
+ return resp, hypervisor
+
+ def get_hypervisor_servers(self, hyper_name):
+ """List instances belonging to the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/servers' % hyper_name,
+ self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
+
+ def get_hypervisor_stats(self):
+ """Get hypervisor statistics over all compute nodes."""
+ resp, body = self.get('os-hypervisors/statistics', self.headers)
+ stats = xml_to_json(etree.fromstring(body))
+ return resp, stats
+
+ def get_hypervisor_uptime(self, hyper_id):
+ """Display the uptime of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id,
+ self.headers)
+ uptime = xml_to_json(etree.fromstring(body))
+ return resp, uptime
+
+ def search_hypervisor(self, hyper_name):
+ """Search specified hypervisor."""
+ resp, body = self.get('os-hypervisors/search?query=%s' % hyper_name,
+ self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
diff --git a/tempest/services/compute/v3/xml/interfaces_client.py b/tempest/services/compute/v3/xml/interfaces_client.py
index a84e0bd..870c130 100644
--- a/tempest/services/compute/v3/xml/interfaces_client.py
+++ b/tempest/services/compute/v3/xml/interfaces_client.py
@@ -25,12 +25,12 @@
from tempest.services.compute.xml.common import xml_to_json
-class InterfacesClientXML(RestClientXML):
+class InterfacesV3ClientXML(RestClientXML):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(InterfacesClientXML, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ super(InterfacesV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def _process_xml_interface(self, node):
iface = xml_to_json(node)
@@ -40,7 +40,8 @@
return iface
def list_interfaces(self, server):
- resp, body = self.get('servers/%s/os-interface' % server, self.headers)
+ resp, body = self.get('servers/%s/os-attach-interfaces' % server,
+ self.headers)
node = etree.fromstring(body)
interfaces = [self._process_xml_interface(x)
for x in node.getchildren()]
@@ -49,7 +50,7 @@
def create_interface(self, server, port_id=None, network_id=None,
fixed_ip=None):
doc = Document()
- iface = Element('interfaceAttachment')
+ iface = Element('interface_attachment')
if port_id:
_port_id = Element('port_id')
_port_id.append(Text(port_id))
@@ -67,21 +68,23 @@
_fixed_ips.append(_fixed_ip)
iface.append(_fixed_ips)
doc.append(iface)
- resp, body = self.post('servers/%s/os-interface' % server,
+ resp, body = self.post('servers/%s/os-attach-interfaces' % server,
headers=self.headers,
body=str(doc))
body = self._process_xml_interface(etree.fromstring(body))
return resp, body
def show_interface(self, server, port_id):
- resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id),
- self.headers)
+ resp, body =\
+ self.get('servers/%s/os-attach-interfaces/%s' % (server, port_id),
+ self.headers)
body = self._process_xml_interface(etree.fromstring(body))
return resp, body
def delete_interface(self, server, port_id):
- resp, body = self.delete('servers/%s/os-interface/%s' % (server,
- port_id))
+ resp, body =\
+ self.delete('servers/%s/os-attach-interfaces/%s' % (server,
+ port_id))
return resp, body
def wait_for_interface_status(self, server, port_id, status):
diff --git a/tempest/services/compute/v3/xml/tenant_usages_client.py b/tempest/services/compute/v3/xml/tenant_usages_client.py
new file mode 100644
index 0000000..cb92324
--- /dev/null
+++ b/tempest/services/compute/v3/xml/tenant_usages_client.py
@@ -0,0 +1,54 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class TenantUsagesClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(TenantUsagesClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _parse_array(self, node):
+ json = xml_to_json(node)
+ return json
+
+ def list_tenant_usages(self, params=None):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ tenant_usage = self._parse_array(etree.fromstring(body))
+ return resp, tenant_usage['tenant_usage']
+
+ def get_tenant_usage(self, tenant_id, params=None):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ tenant_usage = self._parse_array(etree.fromstring(body))
+ return resp, tenant_usage
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 3e5413c..7d40d0e 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -193,6 +193,10 @@
server = self._parse_server(etree.fromstring(body))
return resp, server
+ def migrate_server(self, server_id, **kwargs):
+ """Migrates the given server ."""
+ return self.action(server_id, 'migrate', None, **kwargs)
+
def lock_server(self, server_id, **kwargs):
"""Locks the given server."""
return self.action(server_id, 'lock', None, **kwargs)
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 93b28a2..b4a1a68 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -246,3 +246,13 @@
resp, body = self.post(url, post_body, self.headers)
body = json.loads(body)
return resp, body['transfer']
+
+ def update_volume_readonly(self, volume_id, readonly):
+ """Update the Specified Volume readonly."""
+ post_body = {
+ 'readonly': readonly
+ }
+ post_body = json.dumps({'os-update_readonly_flag': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index b1e54ed..21254aa 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -337,3 +337,13 @@
resp, body = self.post(url, str(Document(post_body)), self.headers)
volume = xml_to_json(etree.fromstring(body))
return resp, volume
+
+ def update_volume_readonly(self, volume_id, readonly):
+ """Update the Specified Volume readonly."""
+ post_body = Element("os-update_readonly_flag",
+ readonly=readonly)
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 2ad4f70..68ffced 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -26,6 +26,10 @@
import yaml
+is_neutron = os.environ.get('DEVSTACK_GATE_NEUTRON', "0") == "1"
+dump_all_errors = is_neutron
+
+
def process_files(file_specs, url_specs, whitelists):
regexp = re.compile(r"^.*(ERROR|CRITICAL).*\[.*\-.*\]")
had_errors = False
@@ -48,6 +52,7 @@
def scan_content(name, content, regexp, whitelist):
had_errors = False
+ print_log_name = True
for line in content:
if not line.startswith("Stderr:") and regexp.match(line):
whitelisted = False
@@ -57,10 +62,12 @@
if re.match(pat, line):
whitelisted = True
break
- if not whitelisted:
- if not had_errors:
+ if not whitelisted or dump_all_errors:
+ if not print_log_name:
print("Log File: %s" % name)
- had_errors = True
+ print_log_name = False
+ if not whitelisted:
+ had_errors = True
print(line)
return had_errors
@@ -115,6 +122,9 @@
whitelists = loaded
if process_files(files_to_process, urls_to_process, whitelists):
print("Logs have errors")
+ if is_neutron:
+ print("Currently not failing neutron builds with errors")
+ return 0
# Return non-zero to start failing builds
return 0
else:
diff --git a/tools/config/check_uptodate.sh b/tools/config/check_uptodate.sh
index 9882ab6..45c8629 100755
--- a/tools/config/check_uptodate.sh
+++ b/tools/config/check_uptodate.sh
@@ -5,6 +5,6 @@
if ! diff $TEMPDIR/$CFGFILE etc/$CFGFILE
then
echo "E: tempest.conf.sample is not up to date, please run:"
- echo "MODULEPATH=tempest.common.generate_sample_tempest tools/config/generate_sample.sh"
+ echo "tools/generate_sample.sh"
exit 42
fi
diff --git a/tools/generate_sample.sh b/tools/generate_sample.sh
new file mode 100755
index 0000000..9b312c9
--- /dev/null
+++ b/tools/generate_sample.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+MODULEPATH=tempest.common.generate_sample_tempest tools/config/generate_sample.sh $@