Merge "Skip more pause tests if pause is not available"
diff --git a/tempest/api/baremetal/test_nodestates.py b/tempest/api/baremetal/test_nodestates.py
new file mode 100644
index 0000000..c658d7f
--- /dev/null
+++ b/tempest/api/baremetal/test_nodestates.py
@@ -0,0 +1,33 @@
+# Copyright 2014 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.
+
+from tempest.api.baremetal import base
+from tempest import test
+
+
+class TestNodeStates(base.BaseBaremetalTest):
+ """Tests for baremetal NodeStates."""
+
+ @classmethod
+ def setUpClass(self):
+ super(TestNodeStates, self).setUpClass()
+ chassis = self.create_chassis()['chassis']
+ self.node = self.create_node(chassis['uuid'])['node']
+
+ @test.attr(type='smoke')
+ def test_list_nodestates(self):
+ resp, nodestates = self.client.list_nodestates(self.node['uuid'])
+ self.assertEqual('200', resp['status'])
+ for key in nodestates:
+ self.assertEqual(nodestates[key], self.node[key])
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index fb249e5..c2376c9 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -16,6 +16,7 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest import exceptions
from tempest import test
@@ -39,11 +40,20 @@
filter(lambda y: y['service'] == 'compute', hosts_all))
cls.host = hosts[0]
+ def _try_delete_aggregate(self, aggregate_id):
+ # delete aggregate, if it exists
+ try:
+ self.client.delete_aggregate(aggregate_id)
+ # if aggregate not found, it depict it was deleted in the test
+ except exceptions.NotFound:
+ pass
+
@test.attr(type='gate')
def test_aggregate_create_delete(self):
# Create and delete an aggregate.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(name=aggregate_name)
+ self.addCleanup(self._try_delete_aggregate, aggregate['id'])
self.assertEqual(200, resp.status)
self.assertEqual(aggregate_name, aggregate['name'])
self.assertIsNone(aggregate['availability_zone'])
@@ -59,6 +69,7 @@
az_name = data_utils.rand_name(self.az_name_prefix)
resp, aggregate = self.client.create_aggregate(
name=aggregate_name, availability_zone=az_name)
+ self.addCleanup(self._try_delete_aggregate, aggregate['id'])
self.assertEqual(200, resp.status)
self.assertEqual(aggregate_name, aggregate['name'])
self.assertEqual(az_name, aggregate['availability_zone'])
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 1f2ddf4..49af645 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -14,7 +14,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest import test
@@ -44,16 +43,6 @@
wait_until='ACTIVE')
cls.s2_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
-
@test.attr(type='gate')
def test_list_servers_by_admin(self):
# Listing servers by admin user returns empty list by default
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index abd8a4c..b3789f8 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -45,6 +45,14 @@
resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
super(FloatingIPsTestJSON, cls).tearDownClass()
+ def _try_delete_floating_ip(self, floating_ip_id):
+ # delete floating ip, if it exists
+ try:
+ self.client.delete_floating_ip(floating_ip_id)
+ # if not found, it depicts it was deleted in the test
+ except exceptions.NotFound:
+ pass
+
@test.attr(type='gate')
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
@@ -66,6 +74,7 @@
# should be successful
# Creating the floating IP that is to be deleted in this method
resp, floating_ip_body = self.client.create_floating_ip()
+ self.addCleanup(self._try_delete_floating_ip, floating_ip_body['id'])
# Storing the details of floating IP before deleting it
cli_resp = self.client.get_floating_ip_details(floating_ip_body['id'])
resp, floating_ip_details = cli_resp
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index f0913f1..f66020c 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -28,6 +28,7 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
+ cls.set_network_resources(network=True, subnet=True, dhcp=True)
super(ListServerFiltersTestJSON, cls).setUpClass()
cls.client = cls.servers_client
@@ -69,8 +70,12 @@
resp, cls.s3 = cls.create_test_server(name=cls.s3_name,
flavor=cls.flavor_ref_alt,
wait_until='ACTIVE')
-
- cls.fixed_network_name = CONF.compute.fixed_network_name
+ if (CONF.service_available.neutron and
+ CONF.compute.allow_tenant_isolation):
+ network = cls.isolated_creds.get_primary_network()
+ cls.fixed_network_name = network['name']
+ else:
+ cls.fixed_network_name = CONF.compute.fixed_network_name
@utils.skip_unless_attr('multiple_images', 'Only one image found')
@test.attr(type='gate')
@@ -226,7 +231,6 @@
self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
- @test.skip_because(bug="1170718")
@test.attr(type='gate')
def test_list_servers_filtered_by_ip(self):
# Filter servers by ip
diff --git a/tempest/api/compute/v3/admin/test_servers.py b/tempest/api/compute/v3/admin/test_servers.py
index 579a535..366cfc6 100644
--- a/tempest/api/compute/v3/admin/test_servers.py
+++ b/tempest/api/compute/v3/admin/test_servers.py
@@ -14,7 +14,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest import test
@@ -44,16 +43,6 @@
wait_until='ACTIVE')
cls.s2_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
-
@test.attr(type='gate')
def test_list_servers_by_admin(self):
# Listing servers by admin user returns empty list by default
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 2cb176c..778b033 100644
--- a/tempest/api/compute/v3/servers/test_list_server_filters.py
+++ b/tempest/api/compute/v3/servers/test_list_server_filters.py
@@ -28,6 +28,7 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
+ cls.set_network_resources(network=True, subnet=True, dhcp=True)
super(ListServerFiltersV3Test, cls).setUpClass()
cls.client = cls.servers_client
@@ -70,7 +71,12 @@
flavor=cls.flavor_ref_alt,
wait_until='ACTIVE')
- cls.fixed_network_name = CONF.compute.fixed_network_name
+ if (CONF.service_available.neutron and
+ CONF.compute.allow_tenant_isolation):
+ network = cls.isolated_creds.get_primary_network()
+ cls.fixed_network_name = network['name']
+ else:
+ cls.fixed_network_name = CONF.compute.fixed_network_name
@utils.skip_unless_attr('multiple_images', 'Only one image found')
@test.attr(type='gate')
@@ -226,7 +232,6 @@
self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
- @test.skip_because(bug="1170718")
@test.attr(type='gate')
def test_list_servers_filtered_by_ip(self):
# Filter servers by ip
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 66dcaa5..bda5b2b 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -182,15 +182,31 @@
@test.attr(type='smoke')
def test_list_ports_binding_ext_attr(self):
- resp, body = self.admin_client.list_ports(
- **{'tenant_id': self.tenant['id']})
+ # Create a new port
+ post_body = {"network_id": self.network['id']}
+ resp, body = self.admin_client.create_port(**post_body)
+ self.assertEqual('201', resp['status'])
+ port = body['port']
+ self.addCleanup(self.admin_client.delete_port, port['id'])
+
+ # Update the port's binding attributes so that is now 'bound'
+ # to a host
+ update_body = {"binding:host_id": self.host_id}
+ resp, _ = self.admin_client.update_port(port['id'], **update_body)
+ self.assertEqual('200', resp['status'])
+
+ # List all ports, ensure new port is part of list and its binding
+ # attributes are set and accurate
+ resp, body = self.admin_client.list_ports()
self.assertEqual('200', resp['status'])
ports_list = body['ports']
- for port in ports_list:
- vif_type = port['binding:vif_type']
- self.assertIsNotNone(vif_type)
- vif_details = port['binding:vif_details']['port_filter']
- self.assertIsNotNone(vif_details)
+ pids_list = [p['id'] for p in ports_list]
+ self.assertIn(port['id'], pids_list)
+ listed_port = [p for p in ports_list if p['id'] == port['id']]
+ self.assertEqual(1, len(listed_port),
+ 'Multiple ports listed with id %s in ports listing: '
+ '%s' % (port['id'], ports_list))
+ self.assertEqual(self.host_id, listed_port[0]['binding:host_id'])
@test.attr(type='smoke')
def test_show_port_binding_ext_attr(self):
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index e0d15ac..81db252 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -39,6 +39,18 @@
cls.metadata = {'Temp-URL-Key': cls.key}
cls.account_client.create_account_metadata(metadata=cls.metadata)
+ def setUp(self):
+ super(ObjectFormPostTest, self).setUp()
+
+ # make sure the metadata has been set
+ account_client_metadata, _ = \
+ self.account_client.list_account_metadata()
+ self.assertIn('x-account-meta-temp-url-key',
+ account_client_metadata)
+ self.assertEqual(
+ account_client_metadata['x-account-meta-temp-url-key'],
+ self.key)
+
@classmethod
def tearDownClass(cls):
cls.account_client.delete_account_metadata(metadata=cls.metadata)
@@ -100,13 +112,9 @@
headers = {'Content-Type': content_type,
'Content-Length': str(len(body))}
- url = "%s/%s/%s" % (self.container_client.base_url,
- self.container_name,
- self.object_name)
+ url = "%s/%s" % (self.container_name, self.object_name)
- # Use a raw request, otherwise authentication headers are used
- resp, body = self.object_client.http_obj.request(url, "POST",
- body, headers=headers)
+ resp, body = self.object_client.post(url, body, headers=headers)
self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertHeaders(resp, "Object", "POST")
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index a52c248..fe0c994 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -20,6 +20,7 @@
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
+from tempest import exceptions
from tempest import test
@@ -38,6 +39,18 @@
cls.metadata = {'Temp-URL-Key': cls.key}
cls.account_client.create_account_metadata(metadata=cls.metadata)
+ def setUp(self):
+ super(ObjectFormPostNegativeTest, self).setUp()
+
+ # make sure the metadata has been set
+ account_client_metadata, _ = \
+ self.account_client.list_account_metadata()
+ self.assertIn('x-account-meta-temp-url-key',
+ account_client_metadata)
+ self.assertEqual(
+ account_client_metadata['x-account-meta-temp-url-key'],
+ self.key)
+
@classmethod
def tearDownClass(cls):
cls.account_client.delete_account_metadata(metadata=cls.metadata)
@@ -100,12 +113,25 @@
headers = {'Content-Type': content_type,
'Content-Length': str(len(body))}
- url = "%s/%s/%s" % (self.container_client.base_url,
- self.container_name,
- self.object_name)
+ url = "%s/%s" % (self.container_name, self.object_name)
+ exc = self.assertRaises(
+ exceptions.Unauthorized,
+ self.object_client.post,
+ url, body, headers=headers)
+ self.assertIn('FormPost: Form Expired', str(exc))
- # Use a raw request, otherwise authentication headers are used
- resp, body = self.object_client.http_obj.request(url, "POST",
- body, headers=headers)
- self.assertEqual(int(resp['status']), 401)
- self.assertIn('FormPost: Form Expired', body)
+ @test.requires_ext(extension='formpost', service='object')
+ @test.attr(type='gate')
+ def test_post_object_using_form_invalid_signature(self):
+ self.key = "Wrong"
+ body, content_type = self.get_multipart_form()
+
+ headers = {'Content-Type': content_type,
+ 'Content-Length': str(len(body))}
+
+ url = "%s/%s" % (self.container_name, self.object_name)
+ exc = self.assertRaises(
+ exceptions.Unauthorized,
+ self.object_client.post,
+ url, body, headers=headers)
+ self.assertIn('FormPost: Invalid Signature', str(exc))
diff --git a/tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml b/tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml
index 23ad06f..fa5345e 100644
--- a/tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml
+++ b/tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml
@@ -11,6 +11,8 @@
Type: String
network:
Type: String
+ timeout:
+ Type: Number
Resources:
CfnUser:
Type: AWS::IAM::User
@@ -68,7 +70,7 @@
DependsOn: SmokeServer
Properties:
Handle: {Ref: WaitHandle}
- Timeout: '600'
+ Timeout: {Ref: timeout}
Outputs:
WaitConditionStatus:
Description: Contents of /tmp/smoke-status on SmokeServer
diff --git a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
index 9d90e31..275d040 100644
--- a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
@@ -12,6 +12,8 @@
type: string
ExternalNetworkId:
type: string
+ timeout:
+ type: number
resources:
Network:
type: OS::Neutron::Net
@@ -41,15 +43,16 @@
router_id: {get_param: ExternalRouterId}
subnet_id: {get_resource: Subnet}
Server:
- type: AWS::EC2::Instance
+ type: OS::Nova::Server
metadata:
Name: SmokeServerNeutron
properties:
- ImageId: {get_param: ImageId}
- InstanceType: {get_param: InstanceType}
- KeyName: {get_param: KeyName}
- SubnetId: {get_resource: Subnet}
- UserData:
+ image: {get_param: ImageId}
+ flavor: {get_param: InstanceType}
+ key_name: {get_param: KeyName}
+ networks:
+ - network: {get_resource: Network}
+ user_data:
str_replace:
template: |
#!/bin/bash -v
@@ -65,4 +68,4 @@
depends_on: Server
properties:
Handle: {get_resource: WaitHandleNeutron}
- Timeout: '600'
+ Timeout: {get_param: timeout}
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 83470be..b96f6ce 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -53,7 +53,8 @@
'InstanceType': CONF.orchestration.instance_type,
'ImageId': CONF.orchestration.image_ref,
'ExternalRouterId': cls.external_router_id,
- 'ExternalNetworkId': cls.external_network_id
+ 'ExternalNetworkId': cls.external_network_id,
+ 'timeout': CONF.orchestration.build_timeout
})
cls.stack_id = cls.stack_identifier.split('/')[1]
try:
@@ -90,7 +91,7 @@
resources = [('Network', 'OS::Neutron::Net'),
('Subnet', 'OS::Neutron::Subnet'),
('RouterInterface', 'OS::Neutron::RouterInterface'),
- ('Server', 'AWS::EC2::Instance')]
+ ('Server', 'OS::Nova::Server')]
for resource_name, resource_type in resources:
resource = self.test_resources.get(resource_name, None)
self.assertIsInstance(resource, dict)
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
index 3f29269..cb5d941 100644
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_server_cfn_init.py
@@ -50,7 +50,8 @@
'key_name': keypair_name,
'flavor': CONF.orchestration.instance_type,
'image': CONF.orchestration.image_ref,
- 'network': cls._get_default_network()['id']
+ 'network': cls._get_default_network()['id'],
+ 'timeout': CONF.orchestration.build_timeout
})
@test.attr(type='slow')
diff --git a/tempest/api/orchestration/stacks/test_update.py b/tempest/api/orchestration/stacks/test_update.py
index 1bda0b4..a9a43b6 100644
--- a/tempest/api/orchestration/stacks/test_update.py
+++ b/tempest/api/orchestration/stacks/test_update.py
@@ -14,7 +14,7 @@
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
LOG = logging.getLogger(__name__)
@@ -47,7 +47,7 @@
self.assertEqual('202', resp[0]['status'])
self.client.wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_stack_update_nochange(self):
stack_name = data_utils.rand_name('heat')
stack_identifier = self.create_stack(stack_name, self.template)
@@ -61,7 +61,8 @@
self.assertEqual(expected_resources,
self.list_resources(stack_identifier))
- @attr(type='gate')
+ @test.attr(type='gate')
+ @test.skip_because(bug='1308682')
def test_stack_update_add_remove(self):
stack_name = data_utils.rand_name('heat')
stack_identifier = self.create_stack(stack_name, self.template)
diff --git a/tempest/api_schema/compute/v2/agents.py b/tempest/api_schema/compute/v2/agents.py
new file mode 100644
index 0000000..837731f
--- /dev/null
+++ b/tempest/api_schema/compute/v2/agents.py
@@ -0,0 +1,17 @@
+# Copyright 2014 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.
+
+delete_agent = {
+ 'status_code': [200]
+}
diff --git a/tempest/api_schema/compute/v2/instance_usage_audit_logs.py b/tempest/api_schema/compute/v2/instance_usage_audit_logs.py
index c1509b4..658f574 100644
--- a/tempest/api_schema/compute/v2/instance_usage_audit_logs.py
+++ b/tempest/api_schema/compute/v2/instance_usage_audit_logs.py
@@ -46,3 +46,14 @@
'required': ['instance_usage_audit_log']
}
}
+
+list_instance_usage_audit_log = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instance_usage_audit_logs': common_instance_usage_audit_log
+ },
+ 'required': ['instance_usage_audit_logs']
+ }
+}
diff --git a/tempest/api_schema/compute/v3/agents.py b/tempest/api_schema/compute/v3/agents.py
new file mode 100644
index 0000000..63d1c46
--- /dev/null
+++ b/tempest/api_schema/compute/v3/agents.py
@@ -0,0 +1,17 @@
+# Copyright 2014 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.
+
+delete_agent = {
+ 'status_code': [204]
+}
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 87d65d0..57b98f7 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -123,7 +123,7 @@
raise Exception("non-integer list types not supported")
result = []
if schema_type not in self.types_dict:
- raise Exception("generator (%s) doesn't support type: %s"
+ raise TypeError("generator (%s) doesn't support type: %s"
% (self.__class__.__name__, schema_type))
for generator in self.types_dict[schema_type]:
ret = generator(schema)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 0d32f41..8c07d4f 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -490,7 +490,7 @@
raise exceptions.InvalidContentType(str(resp.status))
if resp.status == 401 or resp.status == 403:
- raise exceptions.Unauthorized()
+ raise exceptions.Unauthorized(resp_body)
if resp.status == 404:
raise exceptions.NotFound(resp_body)
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
new file mode 100644
index 0000000..857e1e8
--- /dev/null
+++ b/tempest/exceptions.py
@@ -0,0 +1,209 @@
+# Copyright 2012 OpenStack Foundation
+# 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 testtools
+
+
+class TempestException(Exception):
+ """
+ Base Tempest Exception
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+ """
+ message = "An unknown exception occurred"
+
+ def __init__(self, *args, **kwargs):
+ super(TempestException, self).__init__()
+ try:
+ self._error_string = self.message % kwargs
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+ if len(args) > 0:
+ # If there is a non-kwarg parameter, assume it's the error
+ # message or reason description and tack it on to the end
+ # of the exception message
+ # Convert all arguments into their string representations...
+ args = ["%s" % arg for arg in args]
+ self._error_string = (self._error_string +
+ "\nDetails: %s" % '\n'.join(args))
+
+ def __str__(self):
+ return self._error_string
+
+
+class RestClientException(TempestException,
+ testtools.TestCase.failureException):
+ pass
+
+
+class RFCViolation(RestClientException):
+ message = "RFC Violation"
+
+
+class InvalidConfiguration(TempestException):
+ message = "Invalid Configuration"
+
+
+class InvalidCredentials(TempestException):
+ message = "Invalid Credentials"
+
+
+class InvalidHttpSuccessCode(RestClientException):
+ message = "The success code is different than the expected one"
+
+
+class NotFound(RestClientException):
+ message = "Object not found"
+
+
+class Unauthorized(RestClientException):
+ message = 'Unauthorized'
+
+
+class InvalidServiceTag(RestClientException):
+ message = "Invalid service tag"
+
+
+class TimeoutException(TempestException):
+ message = "Request timed out"
+
+
+class BuildErrorException(TempestException):
+ message = "Server %(server_id)s failed to build and is in ERROR status"
+
+
+class ImageKilledException(TempestException):
+ message = "Image %(image_id)s 'killed' while waiting for '%(status)s'"
+
+
+class AddImageException(TempestException):
+ message = "Image %(image_id)s failed to become ACTIVE in the allotted time"
+
+
+class EC2RegisterImageException(TempestException):
+ message = ("Image %(image_id)s failed to become 'available' "
+ "in the allotted time")
+
+
+class VolumeBuildErrorException(TempestException):
+ message = "Volume %(volume_id)s failed to build and is in ERROR status"
+
+
+class SnapshotBuildErrorException(TempestException):
+ message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
+
+
+class VolumeBackupException(TempestException):
+ message = "Volume backup %(backup_id)s failed and is in ERROR status"
+
+
+class StackBuildErrorException(TempestException):
+ message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
+ "due to '%(stack_status_reason)s'")
+
+
+class StackResourceBuildErrorException(TempestException):
+ message = ("Resource %(resource_name) in stack %(stack_identifier)s is "
+ "in %(resource_status)s status due to "
+ "'%(resource_status_reason)s'")
+
+
+class BadRequest(RestClientException):
+ message = "Bad request"
+
+
+class UnprocessableEntity(RestClientException):
+ message = "Unprocessable entity"
+
+
+class AuthenticationFailure(RestClientException):
+ message = ("Authentication with user %(user)s and password "
+ "%(password)s failed auth using tenant %(tenant)s.")
+
+
+class EndpointNotFound(TempestException):
+ message = "Endpoint not found"
+
+
+class RateLimitExceeded(TempestException):
+ message = "Rate limit exceeded"
+
+
+class OverLimit(TempestException):
+ message = "Quota exceeded"
+
+
+class ServerFault(TempestException):
+ message = "Got server fault"
+
+
+class ImageFault(TempestException):
+ message = "Got image fault"
+
+
+class IdentityError(TempestException):
+ message = "Got identity error"
+
+
+class Conflict(RestClientException):
+ message = "An object with that identifier already exists"
+
+
+class SSHTimeout(TempestException):
+ message = ("Connection to the %(host)s via SSH timed out.\n"
+ "User: %(user)s, Password: %(password)s")
+
+
+class SSHExecCommandFailed(TempestException):
+ """Raised when remotely executed command returns nonzero status."""
+ message = ("Command '%(command)s', exit status: %(exit_status)d, "
+ "Error:\n%(strerror)s")
+
+
+class ServerUnreachable(TempestException):
+ message = "The server is not reachable via the configured network"
+
+
+class TearDownException(TempestException):
+ message = "%(num)d cleanUp operation failed"
+
+
+class ResponseWithNonEmptyBody(RFCViolation):
+ message = ("RFC Violation! Response with %(status)d HTTP Status Code "
+ "MUST NOT have a body")
+
+
+class ResponseWithEntity(RFCViolation):
+ message = ("RFC Violation! Response with 205 HTTP Status Code "
+ "MUST NOT have an entity")
+
+
+class InvalidHTTPResponseBody(RestClientException):
+ message = "HTTP response body is invalid json or xml"
+
+
+class InvalidContentType(RestClientException):
+ message = "Invalid content type provided"
+
+
+class UnexpectedResponseCode(RestClientException):
+ message = "Unexpected response code received"
+
+
+class InvalidStructure(TempestException):
+ message = "Invalid structure of table with details"
diff --git a/tempest/exceptions/README.rst b/tempest/exceptions/README.rst
deleted file mode 100644
index dbe42b2..0000000
--- a/tempest/exceptions/README.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-Tempest Field Guide to Exceptions
-=================================
-
-
-What are these exceptions?
---------------------------
-
-These exceptions are used by Tempest for covering OpenStack specific exceptional
-cases.
-
-How to add new exceptions?
---------------------------
-
-Each exception-template for inheritance purposes should be added into 'base'
-submodule.
-All other exceptions can be added in two ways:
-- in main module
-- in submodule
-But only in one of the ways. Need to make sure, that new exception is not
-present already.
-
-How to use exceptions?
-----------------------
-
-Any exceptions from this module or its submodules should be used in appropriate
-places to handle exceptional cases.
-Classes from 'base' module should be used only for inheritance.
diff --git a/tempest/exceptions/__init__.py b/tempest/exceptions/__init__.py
deleted file mode 100644
index 9eb9c1e..0000000
--- a/tempest/exceptions/__init__.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# 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.exceptions import base
-
-
-class InvalidConfiguration(base.TempestException):
- message = "Invalid Configuration"
-
-
-class InvalidCredentials(base.TempestException):
- message = "Invalid Credentials"
-
-
-class InvalidHttpSuccessCode(base.RestClientException):
- message = "The success code is different than the expected one"
-
-
-class NotFound(base.RestClientException):
- message = "Object not found"
-
-
-class Unauthorized(base.RestClientException):
- message = 'Unauthorized'
-
-
-class InvalidServiceTag(base.RestClientException):
- message = "Invalid service tag"
-
-
-class TimeoutException(base.TempestException):
- message = "Request timed out"
-
-
-class BuildErrorException(base.TempestException):
- message = "Server %(server_id)s failed to build and is in ERROR status"
-
-
-class ImageKilledException(base.TempestException):
- message = "Image %(image_id)s 'killed' while waiting for '%(status)s'"
-
-
-class AddImageException(base.TempestException):
- message = "Image %(image_id)s failed to become ACTIVE in the allotted time"
-
-
-class EC2RegisterImageException(base.TempestException):
- message = ("Image %(image_id)s failed to become 'available' "
- "in the allotted time")
-
-
-class VolumeBuildErrorException(base.TempestException):
- message = "Volume %(volume_id)s failed to build and is in ERROR status"
-
-
-class SnapshotBuildErrorException(base.TempestException):
- message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
-
-
-class VolumeBackupException(base.TempestException):
- message = "Volume backup %(backup_id)s failed and is in ERROR status"
-
-
-class StackBuildErrorException(base.TempestException):
- message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
- "due to '%(stack_status_reason)s'")
-
-
-class StackResourceBuildErrorException(base.TempestException):
- message = ("Resource %(resource_name) in stack %(stack_identifier)s is "
- "in %(resource_status)s status due to "
- "'%(resource_status_reason)s'")
-
-
-class BadRequest(base.RestClientException):
- message = "Bad request"
-
-
-class UnprocessableEntity(base.RestClientException):
- message = "Unprocessable entity"
-
-
-class AuthenticationFailure(base.RestClientException):
- message = ("Authentication with user %(user)s and password "
- "%(password)s failed auth using tenant %(tenant)s.")
-
-
-class EndpointNotFound(base.TempestException):
- message = "Endpoint not found"
-
-
-class RateLimitExceeded(base.TempestException):
- message = "Rate limit exceeded"
-
-
-class OverLimit(base.TempestException):
- message = "Quota exceeded"
-
-
-class ServerFault(base.TempestException):
- message = "Got server fault"
-
-
-class ImageFault(base.TempestException):
- message = "Got image fault"
-
-
-class IdentityError(base.TempestException):
- message = "Got identity error"
-
-
-class Conflict(base.RestClientException):
- message = "An object with that identifier already exists"
-
-
-class SSHTimeout(base.TempestException):
- message = ("Connection to the %(host)s via SSH timed out.\n"
- "User: %(user)s, Password: %(password)s")
-
-
-class SSHExecCommandFailed(base.TempestException):
- """Raised when remotely executed command returns nonzero status."""
- message = ("Command '%(command)s', exit status: %(exit_status)d, "
- "Error:\n%(strerror)s")
-
-
-class ServerUnreachable(base.TempestException):
- message = "The server is not reachable via the configured network"
-
-
-class TearDownException(base.TempestException):
- message = "%(num)d cleanUp operation failed"
-
-
-class ResponseWithNonEmptyBody(base.RFCViolation):
- message = ("RFC Violation! Response with %(status)d HTTP Status Code "
- "MUST NOT have a body")
-
-
-class ResponseWithEntity(base.RFCViolation):
- message = ("RFC Violation! Response with 205 HTTP Status Code "
- "MUST NOT have an entity")
-
-
-class InvalidHTTPResponseBody(base.RestClientException):
- message = "HTTP response body is invalid json or xml"
-
-
-class InvalidContentType(base.RestClientException):
- message = "Invalid content type provided"
-
-
-class UnexpectedResponseCode(base.RestClientException):
- message = "Unexpected response code received"
-
-
-class InvalidStructure(base.TempestException):
- message = "Invalid structure of table with details"
diff --git a/tempest/exceptions/base.py b/tempest/exceptions/base.py
deleted file mode 100644
index b8e470e..0000000
--- a/tempest/exceptions/base.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# 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 testtools
-
-
-class TempestException(Exception):
- """
- Base Tempest Exception
-
- To correctly use this class, inherit from it and define
- a 'message' property. That message will get printf'd
- with the keyword arguments provided to the constructor.
- """
- message = "An unknown exception occurred"
-
- def __init__(self, *args, **kwargs):
- super(TempestException, self).__init__()
- try:
- self._error_string = self.message % kwargs
- except Exception:
- # at least get the core message out if something happened
- self._error_string = self.message
- if len(args) > 0:
- # If there is a non-kwarg parameter, assume it's the error
- # message or reason description and tack it on to the end
- # of the exception message
- # Convert all arguments into their string representations...
- args = ["%s" % arg for arg in args]
- self._error_string = (self._error_string +
- "\nDetails: %s" % '\n'.join(args))
-
- def __str__(self):
- return self._error_string
-
-
-class RestClientException(TempestException,
- testtools.TestCase.failureException):
- pass
-
-
-class RFCViolation(RestClientException):
- message = "RFC Violation"
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 5895c37..1e7ddb1 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -19,7 +19,6 @@
import six
import subprocess
-from ironicclient import exc as ironic_exceptions
import netaddr
from neutronclient.common import exceptions as exc
from novaclient import exceptions as nova_exceptions
@@ -490,6 +489,8 @@
def wait_node(self, instance_id):
"""Waits for a node to be associated with instance_id."""
+ from ironicclient import exc as ironic_exceptions
+
def _get_node():
node = None
try:
diff --git a/tempest/services/baremetal/v1/base_v1.py b/tempest/services/baremetal/v1/base_v1.py
index 2145c88..296a199 100644
--- a/tempest/services/baremetal/v1/base_v1.py
+++ b/tempest/services/baremetal/v1/base_v1.py
@@ -42,6 +42,11 @@
return self._list_request('ports', **kwargs)
@base.handle_errors
+ def list_nodestates(self, uuid):
+ """List all existing states."""
+ return self._list_request('/nodes/%s/states' % uuid)
+
+ @base.handle_errors
def list_ports_detail(self):
"""Details list all existing ports."""
return self._list_request('/ports/detail')
diff --git a/tempest/services/compute/json/agents_client.py b/tempest/services/compute/json/agents_client.py
index 1edd1e9..19821e7 100644
--- a/tempest/services/compute/json/agents_client.py
+++ b/tempest/services/compute/json/agents_client.py
@@ -15,6 +15,7 @@
import json
import urllib
+from tempest.api_schema.compute.v2 import agents as schema
from tempest.common import rest_client
from tempest import config
@@ -46,7 +47,9 @@
def delete_agent(self, agent_id):
"""Delete an existing agent build."""
- return self.delete('os-agents/%s' % str(agent_id))
+ resp, body = self.delete("os-agents/%s" % str(agent_id))
+ self.validate_response(schema.delete_agent, resp, body)
+ return resp, body
def update_agent(self, agent_id, **kwargs):
"""Update an agent build."""
diff --git a/tempest/services/compute/json/instance_usage_audit_log_client.py b/tempest/services/compute/json/instance_usage_audit_log_client.py
index 4088be9..4700ca7 100644
--- a/tempest/services/compute/json/instance_usage_audit_log_client.py
+++ b/tempest/services/compute/json/instance_usage_audit_log_client.py
@@ -34,6 +34,8 @@
url = 'os-instance_usage_audit_log'
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_instance_usage_audit_log,
+ resp, body)
return resp, body["instance_usage_audit_logs"]
def get_instance_usage_audit_log(self, time_before):
diff --git a/tempest/services/compute/v3/json/agents_client.py b/tempest/services/compute/v3/json/agents_client.py
index 6893af2..e1c286c 100644
--- a/tempest/services/compute/v3/json/agents_client.py
+++ b/tempest/services/compute/v3/json/agents_client.py
@@ -15,6 +15,7 @@
import json
import urllib
+from tempest.api_schema.compute.v3 import agents as schema
from tempest.common import rest_client
from tempest import config
@@ -43,7 +44,9 @@
def delete_agent(self, agent_id):
"""Delete an existing agent build."""
- return self.delete('os-agents/%s' % str(agent_id))
+ resp, body = self.delete("os-agents/%s" % str(agent_id))
+ self.validate_response(schema.delete_agent, resp, body)
+ return resp, body
def update_agent(self, agent_id, **kwargs):
"""Update an agent build."""
diff --git a/tempest/services/compute/xml/aggregates_client.py b/tempest/services/compute/xml/aggregates_client.py
index b5f7678..9c2d4aa 100644
--- a/tempest/services/compute/xml/aggregates_client.py
+++ b/tempest/services/compute/xml/aggregates_client.py
@@ -61,9 +61,11 @@
def create_aggregate(self, name, availability_zone=None):
"""Creates a new aggregate."""
- post_body = xml_utils.Element("aggregate",
- name=name,
- availability_zone=availability_zone)
+ if availability_zone is not None:
+ post_body = xml_utils.Element("aggregate", name=name,
+ availability_zone=availability_zone)
+ else:
+ post_body = xml_utils.Element("aggregate", name=name)
resp, body = self.post('os-aggregates',
str(xml_utils.Document(post_body)))
aggregate = self._format_aggregate(etree.fromstring(body))
@@ -71,9 +73,11 @@
def update_aggregate(self, aggregate_id, name, availability_zone=None):
"""Update a aggregate."""
- put_body = xml_utils.Element("aggregate",
- name=name,
- availability_zone=availability_zone)
+ if availability_zone is not None:
+ put_body = xml_utils.Element("aggregate", name=name,
+ availability_zone=availability_zone)
+ else:
+ put_body = xml_utils.Element("aggregate", name=name)
resp, body = self.put('os-aggregates/%s' % str(aggregate_id),
str(xml_utils.Document(put_body)))
aggregate = self._format_aggregate(etree.fromstring(body))
diff --git a/tempest/tests/negative/test_generate_json.py b/tempest/tests/negative/test_generate_json.py
deleted file mode 100644
index e09fcdf..0000000
--- a/tempest/tests/negative/test_generate_json.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2014 Deutsche Telekom AG
-# 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.common.generator import negative_generator
-import tempest.test
-
-
-class TestNegativeGenerator(tempest.test.BaseTestCase):
-
- fake_input_str = {"type": "string",
- "minLength": 2,
- "maxLength": 8,
- 'results': {'gen_number': 404}}
-
- fake_input_int = {"type": "integer",
- "maximum": 255,
- "minimum": 1}
-
- fake_input_obj = {"type": "object",
- "properties": {"minRam": {"type": "integer"},
- "diskName": {"type": "string"},
- "maxRam": {"type": "integer", }
- }
- }
-
- def setUp(self):
- super(TestNegativeGenerator, self).setUp()
- self.negative = negative_generator.NegativeTestGenerator()
-
- def _validate_result(self, data):
- self.assertTrue(isinstance(data, list))
- for t in data:
- self.assertTrue(isinstance(t, tuple))
-
- def test_generate_invalid_string(self):
- result = self.negative.generate(self.fake_input_str)
- self._validate_result(result)
-
- def test_generate_invalid_integer(self):
- result = self.negative.generate(self.fake_input_int)
- self._validate_result(result)
-
- def test_generate_invalid_obj(self):
- result = self.negative.generate(self.fake_input_obj)
- self._validate_result(result)
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
index f2ed999..c77faca 100644
--- a/tempest/tests/negative/test_negative_generators.py
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -16,7 +16,9 @@
import jsonschema
import mock
-import tempest.common.generator.base_generator as base_generator
+from tempest.common.generator import base_generator
+from tempest.common.generator import negative_generator
+from tempest.common.generator import valid_generator
from tempest.tests import base
@@ -79,3 +81,73 @@
self.assertRaises(jsonschema.SchemaError,
self.generator.validate_schema,
self.invalid_json_schema_desc)
+
+
+class BaseNegativeGenerator(object):
+ types = ['string', 'integer', 'object']
+
+ fake_input_str = {"type": "string",
+ "minLength": 2,
+ "maxLength": 8,
+ 'results': {'gen_int': 404}}
+
+ fake_input_int = {"type": "integer",
+ "maximum": 255,
+ "minimum": 1}
+
+ fake_input_obj = {"type": "object",
+ "properties": {"minRam": {"type": "integer"},
+ "diskName": {"type": "string"},
+ "maxRam": {"type": "integer", }
+ }
+ }
+
+ unkown_type_schema = {
+ "type": "not_defined"
+ }
+
+ def _validate_result(self, data):
+ self.assertTrue(isinstance(data, list))
+ for t in data:
+ self.assertIsInstance(t, tuple)
+ self.assertEqual(3, len(t))
+ self.assertIsInstance(t[0], str)
+
+ def test_generate_string(self):
+ result = self.generator.generate(self.fake_input_str)
+ self._validate_result(result)
+
+ def test_generate_integer(self):
+ result = self.generator.generate(self.fake_input_int)
+ self._validate_result(result)
+
+ def test_generate_obj(self):
+ result = self.generator.generate(self.fake_input_obj)
+ self._validate_result(result)
+
+ def test_generator_mandatory_functions(self):
+ for data_type in self.types:
+ self.assertIn(data_type, self.generator.types_dict)
+
+ def test_generate_with_unknown_type(self):
+ self.assertRaises(TypeError, self.generator.generate,
+ self.unkown_type_schema)
+
+
+class TestNegativeValidGenerator(base.TestCase, BaseNegativeGenerator):
+ def setUp(self):
+ super(TestNegativeValidGenerator, self).setUp()
+ self.generator = valid_generator.ValidTestGenerator()
+
+ def test_generate_valid(self):
+ result = self.generator.generate_valid(self.fake_input_obj)
+ self.assertIn("minRam", result)
+ self.assertIsInstance(result["minRam"], int)
+ self.assertIn("diskName", result)
+ self.assertIsInstance(result["diskName"], str)
+
+
+class TestNegativeNegativeGenerator(base.TestCase, BaseNegativeGenerator):
+ def setUp(self):
+ super(TestNegativeNegativeGenerator, self).setUp()
+ self.generator = negative_generator.NegativeTestGenerator()