Merge "Add a new exception for invalid structure"
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 05b763a..759585e 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -170,10 +170,12 @@
flag = True
self.assertTrue(flag)
+ @test.skip_because(bug='1286297')
@test.attr(type='gate')
def test_list_non_public_flavor(self):
- # Create a flavor with os-flavor-access:is_public false should
- # be present in list_details.
+ # Create a flavor with os-flavor-access:is_public false.
+ # The flavor should not be present in list_details as the
+ # tenant is not automatically added access list.
# This operation requires the user to have 'admin' role
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
@@ -192,7 +194,7 @@
for flavor in flavors:
if flavor['name'] == flavor_name:
flag = True
- self.assertTrue(flag)
+ self.assertFalse(flag)
# Verify flavor is not retrieved with other user
flag = False
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 4804ce4..aa0138f 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -46,9 +46,11 @@
cls.vcpus = 1
cls.disk = 10
+ @test.skip_because(bug='1286297')
@test.attr(type='gate')
def test_flavor_access_list_with_private_flavor(self):
- # Test to list flavor access successfully by querying private flavor
+ # Test to make sure that list flavor access on a newly created
+ # private flavor will return an empty access list
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
resp, new_flavor = self.client.create_flavor(flavor_name,
@@ -60,10 +62,7 @@
self.assertEqual(resp.status, 200)
resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
self.assertEqual(resp.status, 200)
- self.assertEqual(len(flavor_access), 1, str(flavor_access))
- first_flavor = flavor_access[0]
- self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
- self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
+ self.assertEqual(len(flavor_access), 0, str(flavor_access))
@test.attr(type='gate')
def test_flavor_access_add_remove(self):
diff --git a/tempest/api/compute/admin/test_flavors_negative.py b/tempest/api/compute/admin/test_flavors_negative.py
index b882ff4..162e419 100644
--- a/tempest/api/compute/admin/test_flavors_negative.py
+++ b/tempest/api/compute/admin/test_flavors_negative.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testscenarios
import uuid
from tempest.api.compute import base
@@ -21,7 +20,7 @@
from tempest import exceptions
from tempest import test
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
class FlavorsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -108,8 +107,6 @@
_service = 'compute'
_schema_file = 'compute/admin/flavor_create.json'
- scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
@test.attr(type=['negative', 'gate'])
def test_create_flavor(self):
# flavor details are not returned for non-existent flavors
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index 4ba5023..a81b7d9 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -13,13 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testscenarios
from tempest.api.compute import base
from tempest import test
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
class FlavorsListNegativeTestJSON(base.BaseV2ComputeTest,
@@ -27,8 +26,6 @@
_service = 'compute'
_schema_file = 'compute/flavors/flavors_list.json'
- scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
@test.attr(type=['negative', 'gate'])
def test_list_flavors_with_detail(self):
self.execute(self._schema_file)
@@ -39,8 +36,6 @@
_service = 'compute'
_schema_file = 'compute/flavors/flavor_details.json'
- scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
@classmethod
def setUpClass(cls):
super(FlavorDetailsNegativeTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 26a75a2..72ccc71 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -14,7 +14,6 @@
# under the License.
import base64
-import time
import testtools
import urlparse
@@ -222,18 +221,8 @@
self.client.revert_resize(self.server_id)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
- # Need to poll for the id change until lp#924371 is fixed
resp, server = self.client.get_server(self.server_id)
- start = int(time.time())
-
- while server['flavor']['id'] != previous_flavor_ref:
- time.sleep(self.build_interval)
- resp, server = self.client.get_server(self.server_id)
-
- if int(time.time()) - start >= self.build_timeout:
- message = 'Server %s failed to revert resize within the \
- required time (%s s).' % (self.server_id, self.build_timeout)
- raise exceptions.TimeoutException(message)
+ self.assertEqual(previous_flavor_ref, server['flavor']['id'])
@test.attr(type='gate')
def test_create_backup(self):
diff --git a/tempest/api/compute/servers/test_servers_negative_new.py b/tempest/api/compute/servers/test_servers_negative_new.py
index 42ace76..f860ff9 100644
--- a/tempest/api/compute/servers/test_servers_negative_new.py
+++ b/tempest/api/compute/servers/test_servers_negative_new.py
@@ -13,13 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testscenarios
from tempest.api.compute import base
from tempest import test
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
class GetConsoleOutputNegativeTestJSON(base.BaseV2ComputeTest,
@@ -27,8 +26,6 @@
_service = 'compute'
_schema_file = 'compute/servers/get_console_output.json'
- scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
@classmethod
def setUpClass(cls):
super(GetConsoleOutputNegativeTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/v3/flavors/test_flavors_negative.py b/tempest/api/compute/v3/flavors/test_flavors_negative.py
index 346f6d6..1c0e4fb 100644
--- a/tempest/api/compute/v3/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/v3/flavors/test_flavors_negative.py
@@ -13,13 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testscenarios
-
from tempest.api.compute import base
from tempest import test
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
class FlavorsListNegativeV3Test(base.BaseV3ComputeTest,
@@ -27,8 +25,6 @@
_service = 'computev3'
_schema_file = 'compute/flavors/flavors_list_v3.json'
- scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
@test.attr(type=['negative', 'gate'])
def test_list_flavors_with_detail(self):
self.execute(self._schema_file)
@@ -39,8 +35,6 @@
_service = 'computev3'
_schema_file = 'compute/flavors/flavor_details_v3.json'
- scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
@classmethod
def setUpClass(cls):
super(FlavorDetailsNegativeV3Test, cls).setUpClass()
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 555d028..2582fa8 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
-
import testtools
from tempest.api.compute import base
@@ -212,18 +210,8 @@
self.client.revert_resize(self.server_id)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
- # Need to poll for the id change until lp#924371 is fixed
resp, server = self.client.get_server(self.server_id)
- start = int(time.time())
-
- while server['flavor']['id'] != previous_flavor_ref:
- time.sleep(self.build_interval)
- resp, server = self.client.get_server(self.server_id)
-
- if int(time.time()) - start >= self.build_timeout:
- message = 'Server %s failed to revert resize within the \
- required time (%s s).' % (self.server_id, self.build_timeout)
- raise exceptions.TimeoutException(message)
+ self.assertEqual(previous_flavor_ref, server['flavor']['id'])
@test.attr(type='gate')
def test_create_backup(self):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 3503aa0..f92ad68 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -90,7 +90,7 @@
cls.client.delete_vpnservice(vpnservice['id'])
# Clean up floating IPs
for floating_ip in cls.floating_ips:
- cls.client.delete_floating_ip(floating_ip['id'])
+ cls.client.delete_floatingip(floating_ip['id'])
# Clean up routers
for router in cls.routers:
resp, body = cls.client.list_router_interfaces(router['id'])
@@ -193,11 +193,10 @@
return router
@classmethod
- def create_floating_ip(cls, external_network_id, **kwargs):
+ def create_floatingip(cls, external_network_id):
"""Wrapper utility that returns a test floating IP."""
- resp, body = cls.client.create_floating_ip(
- external_network_id,
- **kwargs)
+ resp, body = cls.client.create_floatingip(
+ floating_network_id=external_network_id)
fip = body['floatingip']
cls.floating_ips.append(fip)
return fip
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index b31c090..06871ad 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -65,8 +65,12 @@
@test.attr(type='smoke')
def test_create_list_show_update_delete_floating_ip(self):
# Creates a floating IP
- created_floating_ip = self.create_floating_ip(
- self.ext_net_id, port_id=self.ports[0]['id'])
+ resp, body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id, port_id=self.ports[0]['id'])
+ self.assertEqual('201', resp['status'])
+ created_floating_ip = body['floatingip']
+ self.addCleanup(self.client.delete_floatingip,
+ created_floating_ip['id'])
self.assertIsNotNone(created_floating_ip['id'])
self.assertIsNotNone(created_floating_ip['tenant_id'])
self.assertIsNotNone(created_floating_ip['floating_ip_address'])
@@ -74,7 +78,7 @@
self.assertEqual(created_floating_ip['floating_network_id'],
self.ext_net_id)
# Verifies the details of a floating_ip
- resp, floating_ip = self.client.show_floating_ip(
+ resp, floating_ip = self.client.show_floatingip(
created_floating_ip['id'])
self.assertEqual('200', resp['status'])
shown_floating_ip = floating_ip['floatingip']
@@ -95,7 +99,7 @@
floatingip_id_list.append(f['id'])
self.assertIn(created_floating_ip['id'], floatingip_id_list)
# Associate floating IP to the other port
- resp, floating_ip = self.client.update_floating_ip(
+ resp, floating_ip = self.client.update_floatingip(
created_floating_ip['id'], port_id=self.ports[1]['id'])
self.assertEqual('200', resp['status'])
updated_floating_ip = floating_ip['floatingip']
@@ -105,7 +109,7 @@
self.assertEqual(updated_floating_ip['router_id'], self.router['id'])
# Disassociate floating IP from the port
- resp, floating_ip = self.client.update_floating_ip(
+ resp, floating_ip = self.client.update_floatingip(
created_floating_ip['id'], port_id=None)
self.assertEqual('200', resp['status'])
updated_floating_ip = floating_ip['floatingip']
@@ -116,17 +120,22 @@
@test.attr(type='smoke')
def test_floating_ip_delete_port(self):
# Create a floating IP
- created_floating_ip = self.create_floating_ip(self.ext_net_id)
+ resp, body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id)
+ self.assertEqual('201', resp['status'])
+ created_floating_ip = body['floatingip']
+ self.addCleanup(self.client.delete_floatingip,
+ created_floating_ip['id'])
# Create a port
resp, port = self.client.create_port(network_id=self.network['id'])
created_port = port['port']
- resp, floating_ip = self.client.update_floating_ip(
+ resp, floating_ip = self.client.update_floatingip(
created_floating_ip['id'], port_id=created_port['id'])
self.assertEqual('200', resp['status'])
# Delete port
self.client.delete_port(created_port['id'])
# Verifies the details of the floating_ip
- resp, floating_ip = self.client.show_floating_ip(
+ resp, floating_ip = self.client.show_floatingip(
created_floating_ip['id'])
self.assertEqual('200', resp['status'])
shown_floating_ip = floating_ip['floatingip']
@@ -139,8 +148,12 @@
@test.attr(type='smoke')
def test_floating_ip_update_different_router(self):
# Associate a floating IP to a port on a router
- created_floating_ip = self.create_floating_ip(
- self.ext_net_id, port_id=self.ports[1]['id'])
+ resp, body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id, port_id=self.ports[1]['id'])
+ self.assertEqual('201', resp['status'])
+ created_floating_ip = body['floatingip']
+ self.addCleanup(self.client.delete_floatingip,
+ created_floating_ip['id'])
self.assertEqual(created_floating_ip['router_id'], self.router['id'])
network2 = self.create_network()
subnet2 = self.create_subnet(network2)
@@ -149,7 +162,7 @@
self.create_router_interface(router2['id'], subnet2['id'])
port_other_router = self.create_port(network2)
# Associate floating IP to the other port on another router
- resp, floating_ip = self.client.update_floating_ip(
+ resp, floating_ip = self.client.update_floatingip(
created_floating_ip['id'], port_id=port_other_router['id'])
self.assertEqual('200', resp['status'])
updated_floating_ip = floating_ip['floatingip']
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 3424082..79f8f4a 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -13,6 +13,7 @@
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -73,14 +74,14 @@
for stack_identifier in cls.stacks:
try:
cls.orchestration_client.delete_stack(stack_identifier)
- except Exception:
+ except exceptions.NotFound:
pass
for stack_identifier in cls.stacks:
try:
cls.orchestration_client.wait_for_stack_status(
stack_identifier, 'DELETE_COMPLETE')
- except Exception:
+ except exceptions.NotFound:
pass
@classmethod
diff --git a/tempest/api/telemetry/test_telemetry_alarming_api.py b/tempest/api/telemetry/test_telemetry_alarming_api.py
index 907d3d0..a59d3ae 100644
--- a/tempest/api/telemetry/test_telemetry_alarming_api.py
+++ b/tempest/api/telemetry/test_telemetry_alarming_api.py
@@ -20,9 +20,29 @@
@attr(type="gate")
def test_alarm_list(self):
- resp, _ = self.telemetry_client.list_alarms()
+ # Create an alarm to verify in the list of alarms
+ created_alarm_ids = list()
+ fetched_ids = list()
+ rules = {'meter_name': 'cpu_util',
+ 'comparison_operator': 'gt',
+ 'threshold': 80.0,
+ 'period': 70}
+ for i in range(3):
+ resp, body = self.create_alarm(threshold_rule=rules)
+ created_alarm_ids.append(body['alarm_id'])
+
+ # List alarms
+ resp, alarm_list = self.telemetry_client.list_alarms()
self.assertEqual(int(resp['status']), 200)
+ # Verify created alarm in the list
+ fetched_ids = [a['alarm_id'] for a in alarm_list]
+ missing_alarms = [a for a in created_alarm_ids if a not in fetched_ids]
+ self.assertEqual(0, len(missing_alarms),
+ "Failed to find the following created alarm(s)"
+ " in a fetched list: %s" %
+ ', '.join(str(a) for a in missing_alarms))
+
@attr(type="gate")
def test_create_alarm(self):
rules = {'meter_name': 'cpu_util',
diff --git a/tempest/api_schema/compute/aggregates.py b/tempest/api_schema/compute/aggregates.py
new file mode 100644
index 0000000..49793fe
--- /dev/null
+++ b/tempest/api_schema/compute/aggregates.py
@@ -0,0 +1,43 @@
+# 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.
+
+list_aggregates = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'aggregates': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'availability_zone': {'type': ['string', 'null']},
+ 'created_at': {'type': 'string'},
+ 'deleted': {'type': 'boolean'},
+ 'deleted_at': {'type': ['string', 'null']},
+ 'hosts': {'type': 'array'},
+ 'id': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'name': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']}
+ },
+ 'required': ['availability_zone', 'created_at', 'deleted',
+ 'deleted_at', 'hosts', 'id', 'metadata',
+ 'name', 'updated_at']
+ }
+ }
+ },
+ 'required': ['aggregates']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/keypairs.py b/tempest/api_schema/compute/v2/keypairs.py
new file mode 100644
index 0000000..3225b0d
--- /dev/null
+++ b/tempest/api_schema/compute/v2/keypairs.py
@@ -0,0 +1,47 @@
+# 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.
+
+get_keypair = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'keypair': {
+ 'type': 'object',
+ 'properties': {
+ 'public_key': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'fingerprint': {'type': 'string'},
+ # NOTE: Now the type of 'user_id' is integer, but here
+ # allows 'string' also because we will be able to change
+ # it to 'uuid' in the future.
+ 'user_id': {'type': ['integer', 'string']},
+ 'deleted': {'type': 'boolean'},
+ 'created_at': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']},
+ 'deleted_at': {'type': ['string', 'null']},
+ 'id': {'type': 'integer'}
+
+ },
+ # When we run the get keypair API, response body includes
+ # all the above mentioned attributes.
+ # But in Nova API sample file, response body includes only
+ # 'public_key', 'name' & 'fingerprint'. So only 'public_key',
+ # 'name' & 'fingerprint' are defined as 'required'.
+ 'required': ['public_key', 'name', 'fingerprint']
+ }
+ },
+ 'required': ['keypair']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/security_groups.py b/tempest/api_schema/compute/v2/security_groups.py
new file mode 100644
index 0000000..68b65b4
--- /dev/null
+++ b/tempest/api_schema/compute/v2/security_groups.py
@@ -0,0 +1,38 @@
+# 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.
+
+list_security_groups = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_groups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': ['integer', 'string']},
+ 'name': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'rules': {'type': 'array'},
+ 'description': {'type': 'string'},
+ },
+ 'required': ['id', 'name', 'tenant_id', 'rules',
+ 'description'],
+ }
+ }
+ },
+ 'required': ['security_groups']
+ }
+}
diff --git a/tempest/api_schema/compute/v3/keypairs.py b/tempest/api_schema/compute/v3/keypairs.py
new file mode 100644
index 0000000..0197c84
--- /dev/null
+++ b/tempest/api_schema/compute/v3/keypairs.py
@@ -0,0 +1,32 @@
+# 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.
+
+get_keypair = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'keypair': {
+ 'type': 'object',
+ 'properties': {
+ 'public_key': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'fingerprint': {'type': 'string'}
+ },
+ 'required': ['public_key', 'name', 'fingerprint']
+ }
+ },
+ 'required': ['keypair']
+ }
+}
diff --git a/tempest/auth.py b/tempest/auth.py
index 0e45161..5fc923f 100644
--- a/tempest/auth.py
+++ b/tempest/auth.py
@@ -164,6 +164,8 @@
class KeystoneAuthProvider(AuthProvider):
+ token_expiry_threshold = datetime.timedelta(seconds=60)
+
def __init__(self, credentials, client_type='tempest', interface=None):
super(KeystoneAuthProvider, self).__init__(credentials, client_type,
interface)
@@ -293,7 +295,8 @@
_, access = auth_data
expiry = datetime.datetime.strptime(access['token']['expires'],
self.EXPIRY_DATE_FORMAT)
- return expiry <= datetime.datetime.now()
+ return expiry - self.token_expiry_threshold <= \
+ datetime.datetime.utcnow()
class KeystoneV3AuthProvider(KeystoneAuthProvider):
@@ -393,4 +396,5 @@
_, access = auth_data
expiry = datetime.datetime.strptime(access['expires_at'],
self.EXPIRY_DATE_FORMAT)
- return expiry <= datetime.datetime.now()
+ return expiry - self.token_expiry_threshold <= \
+ datetime.datetime.utcnow()
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 7e7a2d6..95d50e2 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -62,7 +62,7 @@
"admin_client": {"type": "boolean"},
"url": {"type": "string"},
"default_result_code": {"type": "integer"},
- "json-schema": jsonschema._utils.load_schema("draft4"),
+ "json-schema": {},
"resources": {
"type": "array",
"items": {
@@ -105,6 +105,8 @@
self.types_dict[type].append(method)
def validate_schema(self, schema):
+ if "json-schema" in schema:
+ jsonschema.Draft4Validator.check_schema(schema['json-schema'])
jsonschema.validate(schema, self.schema)
def generate(self, schema):
diff --git a/tempest/services/compute/json/aggregates_client.py b/tempest/services/compute/json/aggregates_client.py
index 700a29b..ccb85c4 100644
--- a/tempest/services/compute/json/aggregates_client.py
+++ b/tempest/services/compute/json/aggregates_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute import aggregates as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -32,6 +33,7 @@
"""Get aggregate list."""
resp, body = self.get("os-aggregates")
body = json.loads(body)
+ self.validate_response(schema.list_aggregates, resp, body)
return resp, body['aggregates']
def get_aggregate(self, aggregate_id):
diff --git a/tempest/services/compute/json/keypairs_client.py b/tempest/services/compute/json/keypairs_client.py
index 28f3c31..889e2ed 100644
--- a/tempest/services/compute/json/keypairs_client.py
+++ b/tempest/services/compute/json/keypairs_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import keypairs as schema
from tempest.common import rest_client
from tempest import config
@@ -40,6 +41,7 @@
def get_keypair(self, key_name):
resp, body = self.get("os-keypairs/%s" % str(key_name))
body = json.loads(body)
+ self.validate_response(schema.get_keypair, resp, body)
return resp, body['keypair']
def create_keypair(self, name, pub_key=None):
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index 899d4ef..9267be7 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -16,6 +16,7 @@
import json
import urllib
+from tempest.api_schema.compute.v2 import security_groups as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -38,6 +39,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_security_groups, resp, body)
return resp, body['security_groups']
def get_security_group(self, security_group_id):
@@ -119,6 +121,7 @@
"""List all rules for a security group."""
resp, body = self.get('os-security-groups')
body = json.loads(body)
+ self.validate_response(schema.list_security_groups, resp, body)
for sg in body['security_groups']:
if sg['id'] == security_group_id:
return resp, sg['rules']
diff --git a/tempest/services/compute/v3/json/aggregates_client.py b/tempest/services/compute/v3/json/aggregates_client.py
index fddf5df..7f73622 100644
--- a/tempest/services/compute/v3/json/aggregates_client.py
+++ b/tempest/services/compute/v3/json/aggregates_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute import aggregates as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -32,6 +33,7 @@
"""Get aggregate list."""
resp, body = self.get("os-aggregates")
body = json.loads(body)
+ self.validate_response(schema.list_aggregates, resp, body)
return resp, body['aggregates']
def get_aggregate(self, aggregate_id):
diff --git a/tempest/services/compute/v3/json/keypairs_client.py b/tempest/services/compute/v3/json/keypairs_client.py
index 9ca4885..f412e30 100644
--- a/tempest/services/compute/v3/json/keypairs_client.py
+++ b/tempest/services/compute/v3/json/keypairs_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v3 import keypairs as schema
from tempest.common import rest_client
from tempest import config
@@ -40,6 +41,7 @@
def get_keypair(self, key_name):
resp, body = self.get("keypairs/%s" % str(key_name))
body = json.loads(body)
+ self.validate_response(schema.get_keypair, resp, body)
return resp, body['keypair']
def create_keypair(self, name, pub_key=None):
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index a804e8e..27f4655 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -144,25 +144,6 @@
body = json.loads(body)
return resp, body
- def create_floating_ip(self, ext_network_id, **kwargs):
- post_body = {
- 'floatingip': kwargs}
- post_body['floatingip']['floating_network_id'] = ext_network_id
- body = json.dumps(post_body)
- uri = '%s/floatingips' % (self.uri_prefix)
- resp, body = self.post(uri, body=body)
- body = json.loads(body)
- return resp, body
-
- def update_floating_ip(self, floating_ip_id, **kwargs):
- post_body = {
- 'floatingip': kwargs}
- body = json.dumps(post_body)
- uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
- resp, body = self.put(uri, body)
- body = json.loads(body)
- return resp, body
-
def associate_health_monitor_with_pool(self, health_monitor_id,
pool_id):
post_body = {
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index 41a7aa4..e21abe1 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -44,7 +44,6 @@
'security_groups': 'security_groups',
'security_group_rules': 'security_group_rules',
'ikepolicy': 'ikepolicies',
- 'floating_ip': 'floatingips',
'quotas': 'quotas'
}
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index 2a5083c..68bc424 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -166,33 +166,6 @@
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def create_floating_ip(self, ext_network_id, **kwargs):
- uri = '%s/floatingips' % (self.uri_prefix)
- floatingip = common.Element('floatingip')
- floatingip.append(common.Element("floating_network_id",
- ext_network_id))
- for element, content in kwargs.iteritems():
- floatingip.append(common.Element(element, content))
- resp, body = self.post(uri, str(common.Document(floatingip)))
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def update_floating_ip(self, floating_ip_id, **kwargs):
- uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
- floatingip = common.Element('floatingip')
- floatingip.add_attr('xmlns:xsi',
- 'http://www.w3.org/2001/XMLSchema-instance')
- for element, content in kwargs.iteritems():
- if content is None:
- xml_elem = common.Element(element)
- xml_elem.add_attr("xsi:nil", "true")
- floatingip.append(xml_elem)
- else:
- floatingip.append(common.Element(element, content))
- resp, body = self.put(uri, str(common.Document(floatingip)))
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
def list_router_interfaces(self, uuid):
uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
resp, body = self.get(uri)
diff --git a/tempest/test.py b/tempest/test.py
index d2568a6..abf42c0 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -24,10 +24,9 @@
import fixtures
import testresources
+import testscenarios
import testtools
-from oslo.config import cfg
-
from tempest import clients
import tempest.common.generator.valid_generator as valid
from tempest.common import isolated_creds
@@ -407,6 +406,24 @@
return json.load(open(fn))
@staticmethod
+ def load_tests(*args):
+ """
+ Wrapper for testscenarios to set the mandatory scenarios variable
+ only in case a real test loader is in place. Will be automatically
+ called in case the variable "load_tests" is set.
+ """
+ if getattr(args[0], 'suiteClass', None) is not None:
+ loader, standard_tests, pattern = args
+ else:
+ standard_tests, module, loader = args
+ for test in testtools.iterate_tests(standard_tests):
+ schema_file = getattr(test, '_schema_file', None)
+ if schema_file is not None:
+ setattr(test, 'scenarios',
+ NegativeAutoTest.generate_scenario(schema_file))
+ return testscenarios.load_tests_apply_scenarios(*args)
+
+ @staticmethod
def generate_scenario(description_file):
"""
Generates the test scenario list for a given description.
@@ -429,17 +446,8 @@
"""
description = NegativeAutoTest.load_schema(description_file)
LOG.debug(description)
-
- # NOTE(mkoderer): since this will be executed on import level the
- # config doesn't have to be in place (e.g. for the pep8 job).
- # In this case simply return.
- try:
- generator = importutils.import_class(
- CONF.negative.test_generator)()
- except cfg.ConfigFilesNotFoundError:
- LOG.critical(
- "Tempest config not found. Test scenarios aren't created")
- return
+ generator = importutils.import_class(
+ CONF.negative.test_generator)()
generator.validate_schema(description)
schema = description.get("json-schema", None)
resources = description.get("resources", [])
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
new file mode 100644
index 0000000..f2ed999
--- /dev/null
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -0,0 +1,81 @@
+# 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.
+
+import jsonschema
+import mock
+
+import tempest.common.generator.base_generator as base_generator
+from tempest.tests import base
+
+
+class TestNegativeBasicGenerator(base.TestCase):
+ valid_desc = {
+ "name": "list-flavors-with-detail",
+ "http-method": "GET",
+ "url": "flavors/detail",
+ "json-schema": {
+ "type": "object",
+ "properties": {
+ "minRam": {"type": "integer"},
+ "minDisk": {"type": "integer"}
+ }
+ },
+ "resources": ["flavor", "volume", "image"]
+ }
+
+ minimal_desc = {
+ "name": "list-flavors-with-detail",
+ "http-method": "GET",
+ "url": "flavors/detail",
+ }
+
+ add_prop_desc = {
+ "name": "list-flavors-with-detail",
+ "http-method": "GET",
+ "url": "flavors/detail",
+ "unknown_field": [12]
+ }
+
+ invalid_json_schema_desc = {
+ "name": "list-flavors-with-detail",
+ "http-method": "GET",
+ "url": "flavors/detail",
+ "json-schema": {"type": "NotExistingType"}
+ }
+
+ def setUp(self):
+ super(TestNegativeBasicGenerator, self).setUp()
+ self.generator = base_generator.BasicGeneratorSet()
+
+ def _assert_valid_jsonschema_call(self, jsonschema_mock, desc):
+ self.assertEqual(jsonschema_mock.call_count, 1)
+ jsonschema_mock.assert_called_with(desc, self.generator.schema)
+
+ @mock.patch('jsonschema.validate', wraps=jsonschema.validate)
+ def test_validate_schema_with_valid_input(self, jsonschema_mock):
+ self.generator.validate_schema(self.valid_desc)
+ self._assert_valid_jsonschema_call(jsonschema_mock, self.valid_desc)
+
+ @mock.patch('jsonschema.validate', wraps=jsonschema.validate)
+ def test_validate_schema_with_minimal_input(self, jsonschema_mock):
+ self.generator.validate_schema(self.minimal_desc)
+ self._assert_valid_jsonschema_call(jsonschema_mock, self.minimal_desc)
+
+ def test_validate_schema_with_invalid_input(self):
+ self.assertRaises(jsonschema.ValidationError,
+ self.generator.validate_schema, self.add_prop_desc)
+ self.assertRaises(jsonschema.SchemaError,
+ self.generator.validate_schema,
+ self.invalid_json_schema_desc)
diff --git a/tempest/tests/test_auth.py b/tempest/tests/test_auth.py
index b6e15bd..62c20e3 100644
--- a/tempest/tests/test_auth.py
+++ b/tempest/tests/test_auth.py
@@ -14,6 +14,7 @@
# under the License.
import copy
+import datetime
from tempest import auth
from tempest.common import http
@@ -131,6 +132,11 @@
self.assertEqual(expected['token'], headers['X-Auth-Token'])
self.assertEqual(expected['body'], body)
+ def _auth_data_with_expiry(self, date_as_string):
+ token, access = self.auth_provider.auth_data
+ access['token']['expires'] = date_as_string
+ return token, access
+
def test_request(self):
filters = {
'service': 'compute',
@@ -292,6 +298,25 @@
expected = 'http://fake_url/'
self._test_base_url_helper(expected, self.filters)
+ def test_token_not_expired(self):
+ expiry_data = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+ auth_data = self._auth_data_with_expiry(
+ expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT))
+ self.assertFalse(self.auth_provider.is_expired(auth_data))
+
+ def test_token_expired(self):
+ expiry_data = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
+ auth_data = self._auth_data_with_expiry(
+ expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT))
+ self.assertTrue(self.auth_provider.is_expired(auth_data))
+
+ def test_token_not_expired_to_be_renewed(self):
+ expiry_data = datetime.datetime.utcnow() + \
+ self.auth_provider.token_expiry_threshold / 2
+ auth_data = self._auth_data_with_expiry(
+ expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT))
+ self.assertTrue(self.auth_provider.is_expired(auth_data))
+
class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider):
_endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
@@ -316,6 +341,11 @@
return ep['url'].replace('v3', replacement)
return ep['url']
+ def _auth_data_with_expiry(self, date_as_string):
+ token, access = self.auth_provider.auth_data
+ access['expires_at'] = date_as_string
+ return token, access
+
def test_check_credentials_missing_tenant_name(self):
cred = copy.copy(self.credentials)
del cred['domain_name']