Merge "Fix skip_path logic if path part is empty"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 8a7ad9c..f80fc1b 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -391,12 +391,14 @@
# A list of enabled compute extensions with a special entry
# all which indicates every extension is enabled. Each
-# extension should be specified with alias name (list value)
+# extension should be specified with alias name. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# A list of enabled v3 extensions with a special entry all
# which indicates every extension is enabled. Each extension
-# should be specified with alias name (list value)
+# should be specified with alias name. Empty list indicates
+# all extensions are disabled (list value)
#api_v3_extensions=all
# Does the test environment support changing the admin
@@ -441,6 +443,11 @@
# (boolean value)
#rescue=true
+# Enables returning of the instance password by the relevant
+# server API calls such as create, rebuild or rescue. (boolean
+# value)
+#enable_instance_password=true
+
[dashboard]
@@ -750,7 +757,8 @@
#ipv6=true
# A list of enabled network extensions with a special entry
-# all which indicates every extension is enabled (list value)
+# all which indicates every extension is enabled. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# Allow the execution of IPv6 subnet tests that use the
@@ -1105,7 +1113,8 @@
#snapshot=true
# A list of enabled volume extensions with a special entry all
-# which indicates every extension is enabled (list value)
+# which indicates every extension is enabled. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# Is the v1 volume API enabled (boolean value)
diff --git a/tempest/README.rst b/tempest/README.rst
index 18c7cf3..fb25151 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -23,9 +23,8 @@
belongs in each directory, the rules and examples for good tests, are
documented in a README.rst file in the directory.
-
-api
----
+:ref:`api_field_guide`
+----------------------
API tests are validation tests for the OpenStack API. They should not
use the existing python clients for OpenStack, but should instead use
@@ -39,8 +38,8 @@
frameworks.
-cli
----
+:ref:`cli_field_guide`
+----------------------
CLI tests use the openstack CLI to interact with the OpenStack
cloud. CLI testing in unit tests is somewhat difficult because unlike
@@ -49,8 +48,8 @@
prereqs having a running OpenStack cloud.
-scenario
---------
+:ref:`scenario_field_guide`
+---------------------------
Scenario tests are complex "through path" tests for OpenStack
functionality. They are typically a series of steps where complicated
@@ -59,18 +58,26 @@
Scenario tests can and should use the OpenStack python clients.
-stress
-------
+:ref:`stress_field_guide`
+-------------------------
Stress tests are designed to stress an OpenStack environment by running a high
workload against it and seeing what breaks. The stress test framework runs
several test jobs in parallel and can run any existing test in Tempest as a
stress job.
-thirdparty
-----------
+:ref:`third_party_field_guide`
+-----------------------------
Many openstack components include 3rdparty API support. It is
completely legitimate for Tempest to include tests of 3rdparty APIs,
but those should be kept separate from the normal OpenStack
validation.
+
+:ref:`unit_tests_field_guide`
+-----------------------------
+
+Unit tests are the self checks for Tempest. They provide functional
+verification and regression checking for the internal components of tempest.
+They should be used to just verify that the individual pieces of tempest are
+working as expected.
diff --git a/tempest/api/README.rst b/tempest/api/README.rst
index 9eac19d..91e6ad6 100644
--- a/tempest/api/README.rst
+++ b/tempest/api/README.rst
@@ -1,3 +1,5 @@
+.. _api_field_guide:
+
Tempest Field Guide to API tests
================================
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index f147b9c..599b058 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -111,7 +111,9 @@
security_groups=default_sg_quota)
# Check we cannot create anymore
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
self.sg_client.create_security_group,
"sg-overlimit", "sg-desc")
@@ -147,7 +149,9 @@
ip_protocol = 'tcp'
# Check we cannot create SG rule anymore
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.OverLimit, exceptions.Unauthorized),
self.sg_client.create_security_group_rule,
secgroup_id, ip_protocol, 1025, 1025)
diff --git a/tempest/api/compute/admin/test_security_group_default_rules.py b/tempest/api/compute/admin/test_security_group_default_rules.py
new file mode 100644
index 0000000..07408a8
--- /dev/null
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -0,0 +1,127 @@
+# 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.
+
+import testtools
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import exceptions
+from tempest import test
+
+CONF = config.CONF
+
+
+class SecurityGroupDefaultRulesTest(base.BaseV2ComputeAdminTest):
+
+ @classmethod
+ # TODO(GMann): Once Bug# 1311500 is fixed, these test can run
+ # for Neutron also.
+ @testtools.skipIf(CONF.service_available.neutron,
+ "Skip as this functionality is not yet "
+ "implemented in Neutron. Related Bug#1311500")
+ @test.safe_setup
+ def setUpClass(cls):
+ # A network and a subnet will be created for these tests
+ cls.set_network_resources(network=True, subnet=True)
+ super(SecurityGroupDefaultRulesTest, cls).setUpClass()
+ cls.adm_client = cls.os_adm.security_group_default_rules_client
+
+ def _create_security_group_default_rules(self, ip_protocol='tcp',
+ from_port=22, to_port=22,
+ cidr='10.10.0.0/24'):
+ # Create Security Group default rule
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr=cidr)
+ self.assertEqual(ip_protocol, rule['ip_protocol'])
+ self.assertEqual(from_port, rule['from_port'])
+ self.assertEqual(to_port, rule['to_port'])
+ self.assertEqual(cidr, rule['ip_range']['cidr'])
+ return rule
+
+ @test.attr(type='smoke')
+ def test_create_delete_security_group_default_rules(self):
+ # Create and delete Security Group default rule
+ ip_protocols = {'tcp', 'udp', 'icmp'}
+ for ip_protocol in ip_protocols:
+ rule = self._create_security_group_default_rules(ip_protocol)
+ # Delete Security Group default rule
+ self.adm_client.delete_security_group_default_rule(rule['id'])
+ self.assertRaises(exceptions.NotFound,
+ self.adm_client.get_security_group_default_rule,
+ rule['id'])
+
+ @test.attr(type='smoke')
+ def test_create_security_group_default_rule_without_cidr(self):
+ ip_protocol = 'udp'
+ from_port = 80
+ to_port = 80
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ self.assertNotEqual(0, rule['id'])
+ self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr'])
+
+ @test.attr(type='smoke')
+ def test_create_security_group_default_rule_with_blank_cidr(self):
+ ip_protocol = 'icmp'
+ from_port = 10
+ to_port = 10
+ cidr = ''
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr=cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ self.assertNotEqual(0, rule['id'])
+ self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr'])
+
+ @test.attr(type='smoke')
+ def test_security_group_default_rules_list(self):
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ cidr = '10.10.0.0/24'
+ rule = self._create_security_group_default_rules(ip_protocol,
+ from_port,
+ to_port,
+ cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ _, rules = self.adm_client.list_security_group_default_rules()
+ self.assertNotEqual(0, len(rules))
+ self.assertIn(rule, rules)
+
+ @test.attr(type='smoke')
+ def test_default_security_group_default_rule_show(self):
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ cidr = '10.10.0.0/24'
+ rule = self._create_security_group_default_rules(ip_protocol,
+ from_port,
+ to_port,
+ cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ _, fetched_rule = self.adm_client.get_security_group_default_rule(
+ rule['id'])
+ self.assertEqual(rule, fetched_rule)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 70a9604..a3295eb 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -83,6 +83,8 @@
cls.hypervisor_client = cls.os.hypervisor_client
cls.certificates_client = cls.os.certificates_client
cls.migrations_client = cls.os.migrations_client
+ cls.security_group_default_rules_client = (
+ cls.os.security_group_default_rules_client)
elif cls._api_version == 3:
if not CONF.compute_feature_enabled.api_v3:
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index b55833c..fbda401 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -119,20 +119,22 @@
@test.attr(type=['negative', 'gate'])
def test_metadata_items_limit(self):
- # Raise a 413 OverLimit exception while exceeding metadata items limit
- # for tenant.
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised while exceeding metadata items limit for
+ # tenant.
_, quota_set = self.quotas.get_quota_set(self.tenant_id)
quota_metadata = quota_set['metadata_items']
req_metadata = {}
for num in range(1, quota_metadata + 2):
req_metadata['key' + str(num)] = 'val' + str(num)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises((exceptions.OverLimit, exceptions.Unauthorized),
self.client.set_server_metadata,
self.server_id, req_metadata)
- # Raise a 413 OverLimit exception while exceeding metadata items limit
- # for tenant (update).
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised while exceeding metadata items limit for
+ # tenant.
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
self.client.update_server_metadata,
self.server_id, req_metadata)
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index e61b738..bd08614 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -48,6 +48,7 @@
self.assertRaises(exceptions.NotFound, self.client.get_token,
subject_token)
+ @test.skip_because(bug="1351026")
@test.attr(type='gate')
def test_rescope_token(self):
"""Rescope a token.
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 02d391b..c875b2f 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -100,7 +100,7 @@
cls.alt_tenant_id = cls.alt_img_cli.tenant_id
def _create_image(self):
- image_file = StringIO.StringIO('*' * 1024)
+ image_file = StringIO.StringIO(data_utils.random_bytes())
resp, image = self.create_image(container_format='bare',
disk_format='raw',
is_public=False,
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 8528e42..bf55b89 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -44,7 +44,7 @@
self.assertEqual(val, body.get('properties')[key])
# Now try uploading an image file
- image_file = StringIO.StringIO(('*' * 1024))
+ image_file = StringIO.StringIO(data_utils.random_bytes())
_, body = self.client.update_image(image_id, data=image_file)
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@@ -157,7 +157,7 @@
image. Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = StringIO.StringIO('*' * size)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
_, image = cls.create_image(name=name,
container_format=container_format,
@@ -338,10 +338,9 @@
disk_format, size):
"""
Create a new standard image and return the ID of the newly-registered
- image. Note that the size of the new image is a random number between
- 1024 and 4096
+ image.
"""
- image_file = StringIO.StringIO('*' * size)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
_, image = cls.create_image(name=name,
container_format=container_format,
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 4226815..a974ebb 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -52,7 +52,7 @@
self.assertEqual('queued', body['status'])
# Now try uploading an image file
- file_content = '*' * 1024
+ file_content = data_utils.random_bytes()
image_file = StringIO.StringIO(file_content)
self.client.store_image(image_id, image_file)
@@ -104,8 +104,7 @@
image_id = body['id']
# Now try uploading an image file
- file_content = '*' * 1024
- image_file = StringIO.StringIO(file_content)
+ image_file = StringIO.StringIO(data_utils.random_bytes())
self.client.store_image(image_id, image_file)
# Update Image
@@ -146,7 +145,8 @@
image. Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
+ size = random.randint(1024, 4096)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = data_utils.rand_name('image-')
_, body = cls.create_image(name=name,
container_format=container_format,
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 087b87a..d75339c 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -85,57 +85,58 @@
@classmethod
def tearDownClass(cls):
- # Clean up ipsec policies
- for ipsecpolicy in cls.ipsecpolicies:
- cls.client.delete_ipsecpolicy(ipsecpolicy['id'])
- # Clean up firewall policies
- for fw_policy in cls.fw_policies:
- cls.client.delete_firewall_policy(fw_policy['id'])
- # Clean up firewall rules
- for fw_rule in cls.fw_rules:
- cls.client.delete_firewall_rule(fw_rule['id'])
- # Clean up ike policies
- for ikepolicy in cls.ikepolicies:
- cls.client.delete_ikepolicy(ikepolicy['id'])
- # Clean up vpn services
- for vpnservice in cls.vpnservices:
- cls.client.delete_vpnservice(vpnservice['id'])
- # Clean up floating IPs
- for floating_ip in cls.floating_ips:
- cls.client.delete_floatingip(floating_ip['id'])
- # Clean up routers
- for router in cls.routers:
- cls.delete_router(router)
+ if CONF.service_available.neutron:
+ # Clean up ipsec policies
+ for ipsecpolicy in cls.ipsecpolicies:
+ cls.client.delete_ipsecpolicy(ipsecpolicy['id'])
+ # Clean up firewall policies
+ for fw_policy in cls.fw_policies:
+ cls.client.delete_firewall_policy(fw_policy['id'])
+ # Clean up firewall rules
+ for fw_rule in cls.fw_rules:
+ cls.client.delete_firewall_rule(fw_rule['id'])
+ # Clean up ike policies
+ for ikepolicy in cls.ikepolicies:
+ cls.client.delete_ikepolicy(ikepolicy['id'])
+ # Clean up vpn services
+ for vpnservice in cls.vpnservices:
+ cls.client.delete_vpnservice(vpnservice['id'])
+ # Clean up floating IPs
+ for floating_ip in cls.floating_ips:
+ cls.client.delete_floatingip(floating_ip['id'])
+ # Clean up routers
+ for router in cls.routers:
+ cls.delete_router(router)
- # Clean up health monitors
- for health_monitor in cls.health_monitors:
- cls.client.delete_health_monitor(health_monitor['id'])
- # Clean up members
- for member in cls.members:
- cls.client.delete_member(member['id'])
- # Clean up vips
- for vip in cls.vips:
- cls.client.delete_vip(vip['id'])
- # Clean up pools
- for pool in cls.pools:
- cls.client.delete_pool(pool['id'])
- # Clean up metering label rules
- for metering_label_rule in cls.metering_label_rules:
- cls.admin_client.delete_metering_label_rule(
- metering_label_rule['id'])
- # Clean up metering labels
- for metering_label in cls.metering_labels:
- cls.admin_client.delete_metering_label(metering_label['id'])
- # Clean up ports
- for port in cls.ports:
- cls.client.delete_port(port['id'])
- # Clean up subnets
- for subnet in cls.subnets:
- cls.client.delete_subnet(subnet['id'])
- # Clean up networks
- for network in cls.networks:
- cls.client.delete_network(network['id'])
- cls.clear_isolated_creds()
+ # Clean up health monitors
+ for health_monitor in cls.health_monitors:
+ cls.client.delete_health_monitor(health_monitor['id'])
+ # Clean up members
+ for member in cls.members:
+ cls.client.delete_member(member['id'])
+ # Clean up vips
+ for vip in cls.vips:
+ cls.client.delete_vip(vip['id'])
+ # Clean up pools
+ for pool in cls.pools:
+ cls.client.delete_pool(pool['id'])
+ # Clean up metering label rules
+ for metering_label_rule in cls.metering_label_rules:
+ cls.admin_client.delete_metering_label_rule(
+ metering_label_rule['id'])
+ # Clean up metering labels
+ for metering_label in cls.metering_labels:
+ cls.admin_client.delete_metering_label(metering_label['id'])
+ # Clean up ports
+ for port in cls.ports:
+ cls.client.delete_port(port['id'])
+ # Clean up subnets
+ for subnet in cls.subnets:
+ cls.client.delete_subnet(subnet['id'])
+ # Clean up networks
+ for network in cls.networks:
+ cls.client.delete_network(network['id'])
+ cls.clear_isolated_creds()
super(BaseNetworkTest, cls).tearDownClass()
@classmethod
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
index 5b45d82..d5e66e8 100644
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -64,3 +64,4 @@
# delete the stack
resp = self.client.delete_stack(stack_identifier)
self.assertEqual('204', resp[0]['status'])
+ self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE')
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index c5be1f3..abf3c6b 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -61,12 +61,20 @@
cls.volumes_extension_client = cls.os.volumes_extension_client
cls.availability_zone_client = (
cls.os.volume_availability_zone_client)
+ # Special fields and resp code for cinder v1
+ cls.special_fields = {'name_field': 'display_name',
+ 'descrip_field': 'display_description',
+ 'create_resp': 200}
elif cls._api_version == 2:
if not CONF.volume_feature_enabled.api_v2:
msg = "Volume API v2 is disabled"
raise cls.skipException(msg)
cls.volumes_client = cls.os.volumes_v2_client
+ # Special fields and resp code for cinder v2
+ cls.special_fields = {'name_field': 'name',
+ 'descrip_field': 'description',
+ 'create_resp': 202}
else:
msg = ("Invalid Cinder API version (%s)" % cls._api_version)
@@ -82,15 +90,15 @@
@classmethod
def create_volume(cls, size=1, **kwargs):
"""Wrapper utility that returns a test volume."""
- vol_name = data_utils.rand_name('Volume')
- if cls._api_version == 1:
- resp, volume = cls.volumes_client.create_volume(
- size, display_name=vol_name, **kwargs)
- assert 200 == resp.status
- elif cls._api_version == 2:
- resp, volume = cls.volumes_client.create_volume(
- size, name=vol_name, **kwargs)
- assert 202 == resp.status
+ name = data_utils.rand_name('Volume')
+
+ name_field = cls.special_fields['name_field']
+ expect_status = cls.special_fields['create_resp']
+
+ kwargs[name_field] = name
+ resp, volume = cls.volumes_client.create_volume(size, **kwargs)
+ assert expect_status == resp.status
+
cls.volumes.append(volume)
cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
return volume
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index 0d57d47..0505f19 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -19,13 +19,12 @@
from tempest import test
-class VolumeMetadataTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2MetadataTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumeMetadataTest, cls).setUpClass()
+ super(VolumesV2MetadataTest, cls).setUpClass()
# Create a volume
cls.volume = cls.create_volume()
cls.volume_id = cls.volume['id']
@@ -33,7 +32,7 @@
def tearDown(self):
# Update the metadata to {}
self.volumes_client.update_volume_metadata(self.volume_id, {})
- super(VolumeMetadataTest, self).tearDown()
+ super(VolumesV2MetadataTest, self).tearDown()
@test.attr(type='gate')
def test_create_get_delete_volume_metadata(self):
@@ -117,5 +116,13 @@
self.assertThat(body.items(), matchers.ContainsAll(expect.items()))
-class VolumeMetadataTestXML(VolumeMetadataTest):
+class VolumesV2MetadataTestXML(VolumesV2MetadataTest):
+ _interface = "xml"
+
+
+class VolumesV1MetadataTest(VolumesV2MetadataTest):
+ _api_version = 1
+
+
+class VolumesV1MetadataTestXML(VolumesV1MetadataTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 82d1364..bf61222 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -23,12 +23,11 @@
CONF = config.CONF
-class VolumesTransfersTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2TransfersTest(base.BaseVolumeTest):
@classmethod
def setUpClass(cls):
- super(VolumesTransfersTest, cls).setUpClass()
+ super(VolumesV2TransfersTest, cls).setUpClass()
# Add another tenant to test volume-transfer
if CONF.compute.allow_tenant_isolation:
@@ -110,5 +109,13 @@
self.client.wait_for_volume_status(volume['id'], 'available')
-class VolumesTransfersTestXML(VolumesTransfersTest):
+class VolumesV2TransfersTestXML(VolumesV2TransfersTest):
+ _interface = "xml"
+
+
+class VolumesV1TransfersTest(VolumesV2TransfersTest):
+ _api_version = 1
+
+
+class VolumesV1TransfersTestXML(VolumesV1TransfersTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index cfab0bd..6fef564 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -21,13 +21,12 @@
CONF = config.CONF
-class VolumesActionsTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2ActionsTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumesActionsTest, cls).setUpClass()
+ super(VolumesV2ActionsTest, cls).setUpClass()
cls.client = cls.volumes_client
cls.image_client = cls.os.image_client
@@ -47,7 +46,7 @@
cls.servers_client.delete_server(cls.server['id'])
cls.servers_client.wait_for_server_termination(cls.server['id'])
- super(VolumesActionsTest, cls).tearDownClass()
+ super(VolumesV2ActionsTest, cls).tearDownClass()
@test.stresstest(class_setup_per='process')
@test.attr(type='smoke')
@@ -165,5 +164,13 @@
self.assertEqual(False, bool_flag)
-class VolumesActionsTestXML(VolumesActionsTest):
+class VolumesV2ActionsTestXML(VolumesV2ActionsTest):
+ _interface = "xml"
+
+
+class VolumesV1ActionsTest(VolumesV2ActionsTest):
+ _api_version = 1
+
+
+class VolumesV1ActionsTestXML(VolumesV1ActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 2745b95..82208aa 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -23,14 +23,17 @@
CONF = config.CONF
-class VolumesGetTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2GetTest(base.BaseVolumeTest):
@classmethod
def setUpClass(cls):
- super(VolumesGetTest, cls).setUpClass()
+ super(VolumesV2GetTest, cls).setUpClass()
cls.client = cls.volumes_client
+ cls.name_field = cls.special_fields['name_field']
+ cls.descrip_field = cls.special_fields['descrip_field']
+ cls.create_resp = cls.special_fields['create_resp']
+
def _delete_volume(self, volume_id):
resp, _ = self.client.delete_volume(volume_id)
self.assertEqual(202, resp.status)
@@ -51,24 +54,24 @@
v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'Test'}
# Create a volume
- resp, volume = self.client.create_volume(display_name=v_name,
- metadata=metadata,
- **kwargs)
- self.assertEqual(200, resp.status)
+ kwargs[self.name_field] = v_name
+ kwargs['metadata'] = metadata
+ resp, volume = self.client.create_volume(**kwargs)
+ self.assertEqual(self.create_resp, resp.status)
self.assertIn('id', volume)
self.addCleanup(self._delete_volume, volume['id'])
- self.assertIn('display_name', volume)
- self.assertEqual(volume['display_name'], v_name,
+ self.client.wait_for_volume_status(volume['id'], 'available')
+ self.assertIn(self.name_field, volume)
+ self.assertEqual(volume[self.name_field], v_name,
"The created volume name is not equal "
"to the requested name")
self.assertTrue(volume['id'] is not None,
"Field volume id is empty or not found.")
- self.client.wait_for_volume_status(volume['id'], 'available')
# Get Volume information
resp, fetched_volume = self.client.get_volume(volume['id'])
self.assertEqual(200, resp.status)
self.assertEqual(v_name,
- fetched_volume['display_name'],
+ fetched_volume[self.name_field],
'The fetched Volume name is different '
'from the created Volume')
self.assertEqual(volume['id'],
@@ -90,27 +93,25 @@
# Update Volume
# Test volume update when display_name is same with original value
- resp, update_volume = \
- self.client.update_volume(volume['id'],
- display_name=v_name)
+ params = {self.name_field: v_name}
+ resp, update_volume = self.client.update_volume(volume['id'], **params)
self.assertEqual(200, resp.status)
# Test volume update when display_name is new
new_v_name = data_utils.rand_name('new-Volume')
new_desc = 'This is the new description of volume'
- resp, update_volume = \
- self.client.update_volume(volume['id'],
- display_name=new_v_name,
- display_description=new_desc)
+ params = {self.name_field: new_v_name,
+ self.descrip_field: new_desc}
+ resp, update_volume = self.client.update_volume(volume['id'], **params)
# Assert response body for update_volume method
self.assertEqual(200, resp.status)
- self.assertEqual(new_v_name, update_volume['display_name'])
- self.assertEqual(new_desc, update_volume['display_description'])
+ self.assertEqual(new_v_name, update_volume[self.name_field])
+ self.assertEqual(new_desc, update_volume[self.descrip_field])
# Assert response body for get_volume method
resp, updated_volume = self.client.get_volume(volume['id'])
self.assertEqual(200, resp.status)
self.assertEqual(volume['id'], updated_volume['id'])
- self.assertEqual(new_v_name, updated_volume['display_name'])
- self.assertEqual(new_desc, updated_volume['display_description'])
+ self.assertEqual(new_v_name, updated_volume[self.name_field])
+ self.assertEqual(new_desc, updated_volume[self.descrip_field])
self.assertThat(updated_volume['metadata'].items(),
matchers.ContainsAll(metadata.items()),
'The fetched Volume metadata misses data '
@@ -120,20 +121,18 @@
# then test volume update if display_name is duplicated
new_volume = {}
new_v_desc = data_utils.rand_name('@#$%^* description')
- resp, new_volume = \
- self.client.create_volume(
- size=1,
- display_description=new_v_desc,
- availability_zone=volume['availability_zone'])
- self.assertEqual(200, resp.status)
+ params = {self.descrip_field: new_v_desc,
+ 'availability_zone': volume['availability_zone']}
+ resp, new_volume = self.client.create_volume(size=1, **params)
+ self.assertEqual(self.create_resp, resp.status)
self.assertIn('id', new_volume)
self.addCleanup(self._delete_volume, new_volume['id'])
self.client.wait_for_volume_status(new_volume['id'], 'available')
- resp, update_volume = \
- self.client.update_volume(
- new_volume['id'],
- display_name=volume['display_name'],
- display_description=volume['display_description'])
+
+ params = {self.name_field: volume[self.name_field],
+ self.descrip_field: volume[self.descrip_field]}
+ resp, update_volume = self.client.update_volume(new_volume['id'],
+ **params)
self.assertEqual(200, resp.status)
# NOTE(jdg): Revert back to strict true/false checking
@@ -159,5 +158,13 @@
self._volume_create_get_update_delete(source_volid=origin['id'])
-class VolumesGetTestXML(VolumesGetTest):
+class VolumesV2GetTestXML(VolumesV2GetTest):
+ _interface = "xml"
+
+
+class VolumesV1GetTest(VolumesV2GetTest):
+ _api_version = 1
+
+
+class VolumesV1GetTestXML(VolumesV1GetTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index bc5b1dc..8bd4c88 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -21,15 +21,16 @@
from tempest import test
-class VolumesNegativeTest(base.BaseVolumeV1Test):
- _interface = 'json'
+class VolumesV2NegativeTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumesNegativeTest, cls).setUpClass()
+ super(VolumesV2NegativeTest, cls).setUpClass()
cls.client = cls.volumes_client
+ cls.name_field = cls.special_fields['name_field']
+
# Create a test shared instance and volume for attach/detach tests
cls.volume = cls.create_volume()
cls.mountpoint = "/dev/vdc"
@@ -237,7 +238,7 @@
@test.attr(type=['negative', 'gate'])
def test_list_volumes_with_nonexistent_name(self):
v_name = data_utils.rand_name('Volume-')
- params = {'display_name': v_name}
+ params = {self.name_field: v_name}
resp, fetched_volume = self.client.list_volumes(params)
self.assertEqual(200, resp.status)
self.assertEqual(0, len(fetched_volume))
@@ -245,7 +246,7 @@
@test.attr(type=['negative', 'gate'])
def test_list_volumes_detail_with_nonexistent_name(self):
v_name = data_utils.rand_name('Volume-')
- params = {'display_name': v_name}
+ params = {self.name_field: v_name}
resp, fetched_volume = self.client.list_volumes_with_detail(params)
self.assertEqual(200, resp.status)
self.assertEqual(0, len(fetched_volume))
@@ -265,5 +266,14 @@
self.assertEqual(0, len(fetched_volume))
-class VolumesNegativeTestXML(VolumesNegativeTest):
+class VolumesV2NegativeTestXML(VolumesV2NegativeTest):
+ _interface = 'xml'
+
+
+class VolumesV1NegativeTest(VolumesV2NegativeTest):
+ _api_version = 1
+ _name = 'display_name'
+
+
+class VolumesV1NegativeTestXML(VolumesV1NegativeTest):
_interface = 'xml'
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
index 95c5760..405ebe7 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -270,3 +270,25 @@
# without these attributes. So they are not 'required'.
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('hostId')
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+del rebuild_server['response_body']['properties']['server'][
+ 'properties']['OS-DCF:diskConfig']
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+rescue_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'adminPass': {'type': 'string'}
+ },
+ 'required': ['adminPass']
+ }
+}
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
index dc800cd..5f348e0 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -181,3 +181,27 @@
# attributes. So they are not 'required'.
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('host_id')
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'admin_password': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('admin_password')
+
+rescue_server_with_admin_pass = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'admin_password': {'type': 'string'}
+ },
+ 'required': ['admin_password']
+ }
+}
+
+rescue_server = copy.deepcopy(rescue_server_with_admin_pass)
+del rescue_server['response_body']['properties']
+del rescue_server['response_body']['required']
diff --git a/tempest/auth.py b/tempest/auth.py
index bcf5776..5df6224 100644
--- a/tempest/auth.py
+++ b/tempest/auth.py
@@ -13,10 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import abc
import copy
import datetime
import exceptions
import re
+import six
import urlparse
from tempest import config
@@ -31,6 +33,7 @@
LOG = logging.getLogger(__name__)
+@six.add_metaclass(abc.ABCMeta)
class AuthProvider(object):
"""
Provide authentication
@@ -70,18 +73,21 @@
interface=self.interface, cache=self.cache
)
+ @abc.abstractmethod
def _decorate_request(self, filters, method, url, headers=None, body=None,
auth_data=None):
"""
Decorate request with authentication data
"""
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _get_auth(self):
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _fill_credentials(self, auth_data_body):
- raise NotImplementedError
+ return
def fill_credentials(self):
"""
@@ -130,8 +136,9 @@
self.cache = None
self.credentials.reset()
+ @abc.abstractmethod
def is_expired(self, auth_data):
- raise NotImplementedError
+ return
def auth_request(self, method, url, headers=None, body=None, filters=None):
"""
@@ -188,11 +195,12 @@
self.alt_part = request_part
self.alt_auth_data = auth_data
+ @abc.abstractmethod
def base_url(self, filters, auth_data=None):
"""
Extracts the base_url based on provided filters
"""
- raise NotImplementedError
+ return
class KeystoneAuthProvider(AuthProvider):
@@ -225,11 +233,13 @@
# no change to method or body
return str(_url), _headers, body
+ @abc.abstractmethod
def _auth_client(self):
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _auth_params(self):
- raise NotImplementedError
+ return
def _get_auth(self):
# Bypasses the cache
diff --git a/tempest/cli/README.rst b/tempest/cli/README.rst
index dcd940b..bc18084 100644
--- a/tempest/cli/README.rst
+++ b/tempest/cli/README.rst
@@ -1,3 +1,5 @@
+.. _cli_field_guide:
+
Tempest Field Guide to CLI tests
================================
diff --git a/tempest/clients.py b/tempest/clients.py
index 4e2205e..519e686 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -52,6 +52,8 @@
MigrationsClientJSON
from tempest.services.compute.json.quotas_client import QuotaClassesClientJSON
from tempest.services.compute.json.quotas_client import QuotasClientJSON
+from tempest.services.compute.json.security_group_default_rules_client import \
+ SecurityGroupDefaultRulesClientJSON
from tempest.services.compute.json.security_groups_client import \
SecurityGroupsClientJSON
from tempest.services.compute.json.servers_client import ServersClientJSON
@@ -406,6 +408,8 @@
self.data_processing_client = DataProcessingClient(
self.auth_provider)
self.migrations_client = MigrationsClientJSON(self.auth_provider)
+ self.security_group_default_rules_client = (
+ SecurityGroupDefaultRulesClientJSON(self.auth_provider))
class AltManager(Manager):
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index c1a2e46..67b92b0 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -219,7 +219,7 @@
def check_objects(self):
"""Check that the objects created are still there."""
- if 'objects' not in self.res:
+ if not self.res.get('objects'):
return
LOG.info("checking objects")
for obj in self.res['objects']:
@@ -231,7 +231,7 @@
def check_servers(self):
"""Check that the servers are still up and running."""
- if 'servers' not in self.res:
+ if not self.res.get('servers'):
return
LOG.info("checking servers")
for server in self.res['servers']:
@@ -254,7 +254,7 @@
def check_volumes(self):
"""Check that the volumes are still there and attached."""
- if 'volumes' not in self.res:
+ if not self.res.get('volumes'):
return
LOG.info("checking volumes")
for volume in self.res['volumes']:
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index 174e557..5a29ea0 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -71,3 +71,11 @@
if not base_text:
base_text = 'test'
return ''.join(itertools.islice(itertools.cycle(base_text), size))
+
+
+def random_bytes(size=1024):
+ """
+ Return size randomly selected bytes as a string.
+ """
+ return ''.join([chr(random.randint(0, 255))
+ for i in range(size)])
diff --git a/tempest/config.py b/tempest/config.py
index c83f500..01bc243 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -268,12 +268,14 @@
default=['all'],
help='A list of enabled compute extensions with a special '
'entry all which indicates every extension is enabled. '
- 'Each extension should be specified with alias name'),
+ 'Each extension should be specified with alias name. '
+ 'Empty list indicates all extensions are disabled'),
cfg.ListOpt('api_v3_extensions',
default=['all'],
help='A list of enabled v3 extensions with a special entry all'
' which indicates every extension is enabled. '
- 'Each extension should be specified with alias name'),
+ 'Each extension should be specified with alias name. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('change_password',
default=False,
help="Does the test environment support changing the admin "
@@ -314,7 +316,12 @@
cfg.BoolOpt('rescue',
default=True,
help='Does the test environment support instance rescue '
- 'mode?')
+ 'mode?'),
+ cfg.BoolOpt('enable_instance_password',
+ default=True,
+ help='Enables returning of the instance password by the '
+ 'relevant server API calls such as create, rebuild '
+ 'or rescue.')
]
@@ -441,7 +448,8 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled network extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('ipv6_subnet_attributes',
default=False,
help="Allow the execution of IPv6 subnet tests that use "
@@ -546,7 +554,8 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled volume extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('api_v1',
default=True,
help="Is the v1 volume API enabled"),
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index 835ba99..5a287d6 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -1,3 +1,5 @@
+.. _scenario_field_guide:
+
Tempest Field Guide to Scenario tests
=====================================
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index aa24c31..3cfc698 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -113,6 +113,11 @@
cls.ceilometer_client = cls.manager.ceilometer_client
@classmethod
+ def tearDownClass(cls):
+ cls.isolated_creds.clear_isolated_creds()
+ super(OfficialClientTest, cls).tearDownClass()
+
+ @classmethod
def _get_credentials(cls, get_creds, ctype):
if CONF.compute.allow_tenant_isolation:
creds = get_creds()
diff --git a/tempest/services/compute/json/security_group_default_rules_client.py b/tempest/services/compute/json/security_group_default_rules_client.py
new file mode 100644
index 0000000..6d29837
--- /dev/null
+++ b/tempest/services/compute/json/security_group_default_rules_client.py
@@ -0,0 +1,74 @@
+# 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.
+
+import json
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class SecurityGroupDefaultRulesClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(SecurityGroupDefaultRulesClientJSON,
+ self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def create_security_default_group_rule(self, ip_protocol, from_port,
+ to_port, **kwargs):
+ """
+ Creating security group default rules.
+ ip_protocol : ip_protocol (icmp, tcp, udp).
+ from_port: Port at start of range.
+ to_port : Port at end of range.
+ cidr : CIDR for address range.
+ """
+ post_body = {
+ 'ip_protocol': ip_protocol,
+ 'from_port': from_port,
+ 'to_port': to_port,
+ 'cidr': kwargs.get('cidr'),
+ }
+ post_body = json.dumps({'security_group_default_rule': post_body})
+ url = 'os-security-group-default-rules'
+ resp, body = self.post(url, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rule']
+
+ def delete_security_group_default_rule(self,
+ security_group_default_rule_id):
+ """Deletes the provided Security Group default rule."""
+ resp, body = self.delete('os-security-group-default-rules/%s' % str(
+ security_group_default_rule_id))
+ self.expected_success(204, resp.status)
+ return resp, body
+
+ def list_security_group_default_rules(self):
+ """List all Security Group default rules."""
+ resp, body = self.get('os-security-group-default-rules')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rules']
+
+ def get_security_group_default_rule(self, security_group_default_rule_id):
+ """Return the details of provided Security Group default rule."""
+ resp, body = self.get('os-security-group-default-rules/%s' % str(
+ security_group_default_rule_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rule']
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 80bb711..a0ffc91 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -269,7 +269,12 @@
if 'disk_config' in kwargs:
kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', None, **kwargs)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild', 'server',
+ rebuild_schema, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -450,7 +455,8 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'adminPass', None, **kwargs)
+ return self.action(server_id, 'rescue', 'adminPass',
+ schema.rescue_server, **kwargs)
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index a5b31d3..51c4499 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -272,7 +272,12 @@
if 'disk_config' in kwargs:
kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', None, **kwargs)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild', 'server',
+ rebuild_schema, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -445,8 +450,16 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'admin_password',
- None, **kwargs)
+ post_body = json.dumps({'rescue': kwargs})
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ post_body)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rescue_schema = schema.rescue_server_with_admin_pass
+ else:
+ rescue_schema = schema.rescue_server
+ body = json.loads(body)
+ self.validate_response(rescue_schema, resp, body)
+ return resp, body
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index 0a63679..4f1f56c 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -1,3 +1,5 @@
+.. _stress_field_guide:
+
Tempest Field Guide to Stress Tests
===================================
diff --git a/tempest/test.py b/tempest/test.py
index afe7a96..5b7330b 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -215,6 +215,8 @@
'network': CONF.network_feature_enabled.api_extensions,
'object': CONF.object_storage_feature_enabled.discoverable_apis,
}
+ if len(config_dict[service]) == 0:
+ return False
if config_dict[service][0] == 'all':
return True
if extension_name in config_dict[service]:
diff --git a/tempest/tests/README.rst b/tempest/tests/README.rst
index 33d321f..e54d4c0 100644
--- a/tempest/tests/README.rst
+++ b/tempest/tests/README.rst
@@ -1,3 +1,5 @@
+.. _unit_tests_field_guide:
+
Tempest Field Guide to Unit tests
=================================
diff --git a/tempest/tests/test_auth.py b/tempest/tests/test_auth.py
index 1dcddad..6a2e335 100644
--- a/tempest/tests/test_auth.py
+++ b/tempest/tests/test_auth.py
@@ -59,12 +59,24 @@
obviously don't test not implemented method or the ones which strongly
depends on them.
"""
- _auth_provider_class = auth.AuthProvider
- def test_check_credentials_class(self):
- self.assertRaises(NotImplementedError,
- self.auth_provider.check_credentials,
- auth.Credentials())
+ class FakeAuthProviderImpl(auth.AuthProvider):
+ def _decorate_request():
+ pass
+
+ def _fill_credentials():
+ pass
+
+ def _get_auth():
+ pass
+
+ def base_url():
+ pass
+
+ def is_expired():
+ pass
+
+ _auth_provider_class = FakeAuthProviderImpl
def test_check_credentials_bad_type(self):
self.assertFalse(self.auth_provider.check_credentials([]))
@@ -74,16 +86,6 @@
auth_provider = self._auth(credentials={})
self.assertIsInstance(auth_provider.credentials, auth.Credentials)
- def test_instantiate_with_bad_credentials_type(self):
- """
- Assure that credentials with bad type fail with TypeError
- """
- self.assertRaises(TypeError, self._auth, [])
-
- def test_auth_data_property(self):
- self.assertRaises(NotImplementedError, getattr, self.auth_provider,
- 'auth_data')
-
def test_auth_data_property_when_cache_exists(self):
self.auth_provider.cache = 'foo'
self.useFixture(mockpatch.PatchObject(self.auth_provider,
@@ -110,9 +112,10 @@
self.assertIsNone(self.auth_provider.alt_part)
self.assertIsNone(self.auth_provider.alt_auth_data)
- def test_fill_credentials(self):
- self.assertRaises(NotImplementedError,
- self.auth_provider.fill_credentials)
+ def test_auth_class(self):
+ self.assertRaises(TypeError,
+ auth.AuthProvider,
+ fake_credentials.FakeCredentials)
class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp):
diff --git a/tempest/thirdparty/README.rst b/tempest/thirdparty/README.rst
index 53cb54b..b0bfdf7 100644
--- a/tempest/thirdparty/README.rst
+++ b/tempest/thirdparty/README.rst
@@ -1,3 +1,5 @@
+.. _third_party_field_guide:
+
Tempest Field Guide to Third Party API tests
============================================