Merge "Move wait_for_interface_status from service client"
diff --git a/requirements.txt b/requirements.txt
index d15a5a1..66e5b16 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,3 +24,4 @@
testscenarios>=0.4
tempest-lib>=0.6.1
PyYAML>=3.1.0
+stevedore>=1.5.0 # Apache-2.0
diff --git a/tempest/api/identity/v2/test_tokens.py b/tempest/api/identity/v2/test_tokens.py
index 5a8afa0..3b508f4 100644
--- a/tempest/api/identity/v2/test_tokens.py
+++ b/tempest/api/identity/v2/test_tokens.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_utils import timeutils
+import six
from tempest.api.identity import base
from tempest import test
@@ -30,9 +32,19 @@
password = creds.password
tenant_name = creds.tenant_name
- body = token_client.auth(username,
- password,
- tenant_name)
+ body = token_client.auth(username, password, tenant_name)
+ self.assertNotEmpty(body['token']['id'])
+ self.assertIsInstance(body['token']['id'], six.string_types)
+
+ now = timeutils.utcnow()
+ expires_at = timeutils.normalize_time(
+ timeutils.parse_isotime(body['token']['expires']))
+ self.assertGreater(expires_at, now)
+
+ self.assertEqual(body['token']['tenant']['id'],
+ creds.credentials.tenant_id)
self.assertEqual(body['token']['tenant']['name'],
tenant_name)
+
+ self.assertEqual(body['user']['id'], creds.credentials.user_id)
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index ab4a09f..3151763 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_utils import timeutils
+import six
from tempest.api.identity import base
from tempest import test
@@ -26,8 +28,25 @@
user_id = creds.user_id
username = creds.username
password = creds.password
- resp = self.non_admin_token.auth(user_id=user_id,
- password=password)
- subject_name = resp['token']['user']['name']
+ token_id, resp = self.non_admin_token.get_token(user_id=user_id,
+ password=password,
+ auth_data=True)
+
+ self.assertNotEmpty(token_id)
+ self.assertIsInstance(token_id, six.string_types)
+
+ now = timeutils.utcnow()
+ expires_at = timeutils.normalize_time(
+ timeutils.parse_isotime(resp['expires_at']))
+ self.assertGreater(resp['expires_at'],
+ resp['issued_at'])
+ self.assertGreater(expires_at, now)
+
+ subject_id = resp['user']['id']
+ self.assertEqual(subject_id, user_id)
+
+ subject_name = resp['user']['name']
self.assertEqual(subject_name, username)
+
+ self.assertEqual(resp['methods'][0], 'password')
diff --git a/tempest/api/network/test_networks_negative.py b/tempest/api/network/test_networks_negative.py
index 520342e..5dc1c21 100644
--- a/tempest/api/network/test_networks_negative.py
+++ b/tempest/api/network/test_networks_negative.py
@@ -57,3 +57,38 @@
non_exist_id = data_utils.rand_name('network')
self.assertRaises(lib_exc.NotFound, self.client.delete_network,
non_exist_id)
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('1cc47884-ac52-4415-a31c-e7ce5474a868')
+ def test_update_non_existent_subnet(self):
+ non_exist_id = data_utils.rand_uuid()
+ self.assertRaises(lib_exc.NotFound, self.client.update_subnet,
+ non_exist_id, name='new_name')
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('a176c859-99fb-42ec-a208-8a85b552a239')
+ def test_delete_non_existent_subnet(self):
+ non_exist_id = data_utils.rand_uuid()
+ self.assertRaises(lib_exc.NotFound,
+ self.client.delete_subnet, non_exist_id)
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('13d3b106-47e6-4b9b-8d53-dae947f092fe')
+ def test_create_port_on_non_existent_network(self):
+ non_exist_net_id = data_utils.rand_uuid()
+ self.assertRaises(lib_exc.NotFound,
+ self.client.create_port, network_id=non_exist_net_id)
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('cf8eef21-4351-4f53-adcd-cc5cb1e76b92')
+ def test_update_non_existent_port(self):
+ non_exist_port_id = data_utils.rand_uuid()
+ self.assertRaises(lib_exc.NotFound, self.client.update_port,
+ non_exist_port_id, name='new_name')
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('49ec2bbd-ac2e-46fd-8054-798e679ff894')
+ def test_delete_non_existent_port(self):
+ non_exist_port_id = data_utils.rand_uuid()
+ self.assertRaises(lib_exc.NotFound,
+ self.client.delete_port, non_exist_port_id)
diff --git a/tempest/api_schema/response/compute/v2_1/fixed_ips.py b/tempest/api_schema/response/compute/v2_1/fixed_ips.py
index 6d5ba67..3586b70 100644
--- a/tempest/api_schema/response/compute/v2_1/fixed_ips.py
+++ b/tempest/api_schema/response/compute/v2_1/fixed_ips.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.api_schema.response.compute.v2_1 import parameter_types
+
get_fixed_ip = {
'status_code': [200],
'response_body': {
@@ -20,10 +22,7 @@
'fixed_ip': {
'type': 'object',
'properties': {
- 'address': {
- 'type': 'string',
- 'format': 'ip-address'
- },
+ 'address': parameter_types.ip_address,
'cidr': {'type': 'string'},
'host': {'type': 'string'},
'hostname': {'type': 'string'}
diff --git a/tempest/api_schema/response/compute/v2_1/floating_ips.py b/tempest/api_schema/response/compute/v2_1/floating_ips.py
index 28dd40a..3551681 100644
--- a/tempest/api_schema/response/compute/v2_1/floating_ips.py
+++ b/tempest/api_schema/response/compute/v2_1/floating_ips.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.api_schema.response.compute.v2_1 import parameter_types
+
common_floating_ip_info = {
'type': 'object',
'properties': {
@@ -21,14 +23,8 @@
'id': {'type': ['integer', 'string']},
'pool': {'type': ['string', 'null']},
'instance_id': {'type': ['string', 'null']},
- 'ip': {
- 'type': 'string',
- 'format': 'ip-address'
- },
- 'fixed_ip': {
- 'type': ['string', 'null'],
- 'format': 'ip-address'
- }
+ 'ip': parameter_types.ip_address,
+ 'fixed_ip': parameter_types.ip_address
},
'additionalProperties': False,
'required': ['id', 'pool', 'instance_id',
@@ -131,18 +127,12 @@
'items': {
'type': 'object',
'properties': {
- 'address': {
- 'type': 'string',
- 'format': 'ip-address'
- },
+ 'address': parameter_types.ip_address,
'instance_uuid': {'type': ['string', 'null']},
'interface': {'type': ['string', 'null']},
'pool': {'type': ['string', 'null']},
'project_id': {'type': ['string', 'null']},
- 'fixed_ip': {
- 'type': ['string', 'null'],
- 'format': 'ip-address'
- }
+ 'fixed_ip': parameter_types.ip_address
},
'additionalProperties': False,
# NOTE: fixed_ip is introduced after JUNO release,
diff --git a/tempest/api_schema/response/compute/v2_1/hypervisors.py b/tempest/api_schema/response/compute/v2_1/hypervisors.py
index e24389d..05901b6 100644
--- a/tempest/api_schema/response/compute/v2_1/hypervisors.py
+++ b/tempest/api_schema/response/compute/v2_1/hypervisors.py
@@ -14,6 +14,8 @@
import copy
+from tempest.api_schema.response.compute.v2_1 import parameter_types
+
get_hypervisor_statistics = {
'status_code': [200],
'response_body': {
@@ -57,10 +59,7 @@
'cpu_info': {'type': 'string'},
'current_workload': {'type': 'integer'},
'disk_available_least': {'type': ['integer', 'null']},
- 'host_ip': {
- 'type': 'string',
- 'format': 'ip-address'
- },
+ 'host_ip': parameter_types.ip_address,
'free_disk_gb': {'type': 'integer'},
'free_ram_mb': {'type': 'integer'},
'hypervisor_hostname': {'type': 'string'},
diff --git a/tempest/api_schema/response/compute/v2_1/interfaces.py b/tempest/api_schema/response/compute/v2_1/interfaces.py
index b18fba6..130775b 100644
--- a/tempest/api_schema/response/compute/v2_1/interfaces.py
+++ b/tempest/api_schema/response/compute/v2_1/interfaces.py
@@ -27,10 +27,7 @@
'type': 'string',
'format': 'uuid'
},
- 'ip_address': {
- 'type': 'string',
- 'format': 'ipv4'
- }
+ 'ip_address': parameter_types.ip_address
},
'additionalProperties': False,
'required': ['subnet_id', 'ip_address']
diff --git a/tempest/api_schema/response/compute/v2_1/parameter_types.py b/tempest/api_schema/response/compute/v2_1/parameter_types.py
index 7b4264c..07cc890 100644
--- a/tempest/api_schema/response/compute/v2_1/parameter_types.py
+++ b/tempest/api_schema/response/compute/v2_1/parameter_types.py
@@ -33,14 +33,27 @@
'pattern': '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}'
}
+ip_address = {
+ 'oneOf': [
+ {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ },
+ {'type': 'null'}
+ ]
+}
+
access_ip_v4 = {
'type': 'string',
- 'anyOf': [{'format': 'ipv4'}, {'enum': ['']}]
+ 'oneOf': [{'format': 'ipv4'}, {'enum': ['']}]
}
access_ip_v6 = {
'type': 'string',
- 'anyOf': [{'format': 'ipv6'}, {'enum': ['']}]
+ 'oneOf': [{'format': 'ipv6'}, {'enum': ['']}]
}
addresses = {
@@ -55,7 +68,7 @@
'version': {'type': 'integer'},
'addr': {
'type': 'string',
- 'anyOf': [
+ 'oneOf': [
{'format': 'ipv4'},
{'format': 'ipv6'}
]
diff --git a/tempest/scenario/test_swift_telemetry_middleware.py b/tempest/scenario/test_swift_telemetry_middleware.py
index 302ccbe..29ce1a0 100644
--- a/tempest/scenario/test_swift_telemetry_middleware.py
+++ b/tempest/scenario/test_swift_telemetry_middleware.py
@@ -74,7 +74,7 @@
called again.
"""
results = self.telemetry_client.list_samples(
- 'storage.api.request')
+ 'storage.objects.incoming.bytes')
LOG.debug('got samples %s', results)
# Extract container info from samples.
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
new file mode 100644
index 0000000..197bd0c
--- /dev/null
+++ b/tempest/test_discover/plugins.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import abc
+
+import six
+import stevedore
+from tempest_lib.common.utils import misc
+
+
+@six.add_metaclass(abc.ABCMeta)
+class TempestPlugin(object):
+ """A TempestPlugin class provides the basic hooks for an external
+ plugin to provide tempest the necessary information to run the plugin.
+ """
+
+ @abc.abstractmethod
+ def load_tests(self):
+ """Method to return the information necessary to load the tests in the
+ plugin.
+
+ :return: a tuple with the first value being the test_dir and the second
+ being the top_level
+ :rtype: tuple
+ """
+ return
+
+
+@misc.singleton
+class TempestTestPluginManager(object):
+ """Tempest test plugin manager class
+
+ This class is used to manage the lifecycle of external tempest test
+ plugins. It provides functions for getting set
+ """
+ def __init__(self):
+ self.ext_plugins = stevedore.ExtensionManager(
+ 'tempest.test.plugins', invoke_on_load=True,
+ propagate_map_exceptions=True)
+
+ def get_plugin_load_tests_tuple(self):
+ load_tests_dict = {}
+ for plug in self.ext_plugins:
+ load_tests_dict[plug.name] = plug.obj.load_tests()
+ return load_tests_dict
diff --git a/tempest/test_discover/test_discover.py b/tempest/test_discover/test_discover.py
index 4a4b43a..a871d10 100644
--- a/tempest/test_discover/test_discover.py
+++ b/tempest/test_discover/test_discover.py
@@ -15,6 +15,8 @@
import os
import sys
+from tempest.test_discover import plugins
+
if sys.version_info >= (2, 7):
import unittest
else:
@@ -22,9 +24,12 @@
def load_tests(loader, tests, pattern):
+ ext_plugins = plugins.TempestTestPluginManager()
+
suite = unittest.TestSuite()
base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
base_path = os.path.split(base_path)[0]
+ # Load local tempest tests
for test_dir in ['./tempest/api', './tempest/scenario',
'./tempest/thirdparty']:
if not pattern:
@@ -32,4 +37,17 @@
else:
suite.addTests(loader.discover(test_dir, pattern=pattern,
top_level_dir=base_path))
+
+ plugin_load_tests = ext_plugins.get_plugin_load_tests_tuple()
+ if not plugin_load_tests:
+ return suite
+
+ # Load any installed plugin tests
+ for plugin in plugin_load_tests:
+ test_dir, top_path = plugin_load_tests[plugin]
+ if not pattern:
+ suite.addTests(loader.discover(test_dir, top_level=top_path))
+ else:
+ suite.addTests(loader.discover(test_dir, pattern=pattern,
+ top_level=top_path))
return suite
diff --git a/test-requirements.txt b/test-requirements.txt
index 9bd3f46..2b3607d 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -10,4 +10,3 @@
mock>=1.0
coverage>=3.6
oslotest>=1.5.1 # Apache-2.0
-stevedore>=1.5.0 # Apache-2.0