Merge "Update docs for regenerating sample conf file"
diff --git a/HACKING.rst b/HACKING.rst
index ad5e821..e57b670 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -36,11 +36,11 @@
In most cases the very first issue is the most important information.
-Try to avoid using ``try`` blocks in the test cases, both the ``except``
-and ``finally`` block could replace the original exception,
+Try to avoid using ``try`` blocks in the test cases, as both the ``except``
+and ``finally`` blocks could replace the original exception,
when the additional operations leads to another exception.
-Just letting an exception to propagate, is not bad idea in a test case,
+Just letting an exception to propagate, is not a bad idea in a test case,
at all.
Try to avoid using any exception handling construct which can hide the errors
@@ -54,10 +54,10 @@
exceptions and still ensure resources are correctly cleaned up if the
test fails part way through.
-Use the ``self.assert*`` methods provided by the unit test framework
-the signal failures early.
+Use the ``self.assert*`` methods provided by the unit test framework.
+This signals the failures early on.
-Avoid using the ``self.fail`` alone, it's stack trace will signal
+Avoid using the ``self.fail`` alone, its stack trace will signal
the ``self.fail`` line as the origin of the error.
Avoid constructing complex boolean expressions for assertion.
@@ -69,7 +69,7 @@
Most other assert method can include more information by default.
For example ``self.assertIn`` can include the whole set.
-Recommended to use testtools matcher for more tricky assertion.
+It is recommended to use testtools matcher for the more tricky assertions.
`[doc] <http://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers>`_
You can implement your own specific matcher as well.
@@ -77,8 +77,8 @@
If the test case fails you can see the related logs and the information
carried by the exception (exception class, backtrack and exception info).
-This and the service logs are your only guide to find the root cause of flaky
-issue.
+This and the service logs are your only guide to finding the root cause of flaky
+issues.
Test cases are independent
--------------------------
@@ -87,7 +87,7 @@
Test cases MAY depend on commonly initialized resources/facilities, like
credentials management, testresources and so on. These facilities, MUST be able
-to work even if just one ``test_method`` selected for execution.
+to work even if just one ``test_method`` is selected for execution.
Service Tagging
---------------
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 6a5da58..55931a4 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -116,6 +116,7 @@
self.assertEqual('204', resp['status'])
self.client.wait_for_server_termination(server['id'])
+ @test.services('volume')
@test.attr(type='gate')
def test_delete_server_while_in_attached_volume(self):
# Delete a server while a volume is attached to it
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index a984ade..5986f41 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -45,12 +45,6 @@
cls.sg_desc)
cls.sg_id = cls.sg['id']
- # Create a volume and wait for it to become ready for attach
- resp, cls.volume = cls.volumes_extensions_client.create_volume(
- 1, display_name=data_utils.rand_name(cls.__name__ + '_volume'))
- cls.volumes_extensions_client.wait_for_volume_status(
- cls.volume['id'], 'available')
-
# Server for positive tests
resp, server = cls.create_test_server(wait_until='BUILD')
cls.server_id = server['id']
@@ -64,8 +58,6 @@
def resource_cleanup(cls):
# Deleting the floating IP which is created in this method
cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id)
- if getattr(cls, 'volume', None):
- cls.delete_volume(cls.volume['id'])
resp, cls.sg = cls.security_groups_client.delete_security_group(
cls.sg_id)
super(ServerRescueTestJSON, cls).resource_cleanup()
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 0d29968..de43164 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -35,12 +35,6 @@
super(ServerRescueNegativeTestJSON, cls).resource_setup()
cls.device = CONF.compute.volume_device_name
- # Create a volume and wait for it to become ready for attach
- resp, cls.volume = cls.volumes_extensions_client.create_volume(
- 1, display_name=data_utils.rand_name(cls.__name__ + '_volume'))
- cls.volumes_extensions_client.wait_for_volume_status(
- cls.volume['id'], 'available')
-
# Server for negative tests
resp, server = cls.create_test_server(wait_until='BUILD')
resp, resc_server = cls.create_test_server(wait_until='ACTIVE')
@@ -54,11 +48,14 @@
cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE')
cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
- @classmethod
- def resource_cleanup(cls):
- if getattr(cls, 'volume', None):
- cls.delete_volume(cls.volume['id'])
- super(ServerRescueNegativeTestJSON, cls).resource_cleanup()
+ def _create_volume(self):
+ resp, volume = self.volumes_extensions_client.create_volume(
+ 1, display_name=data_utils.rand_name(
+ self.__class__.__name__ + '_volume'))
+ self.addCleanup(self.delete_volume, volume['id'])
+ self.volumes_extensions_client.wait_for_volume_status(
+ volume['id'], 'available')
+ return volume
def _detach(self, server_id, volume_id):
self.servers_client.detach_volume(server_id, volume_id)
@@ -108,8 +105,11 @@
self.rescue_id,
self.image_ref_alt)
+ @test.services('volume')
@test.attr(type=['negative', 'gate'])
def test_rescued_vm_attach_volume(self):
+ volume = self._create_volume()
+
# Rescue the server
self.servers_client.rescue_server(self.server_id,
adminPass=self.password)
@@ -120,31 +120,34 @@
self.assertRaises(exceptions.Conflict,
self.servers_client.attach_volume,
self.server_id,
- self.volume['id'],
+ volume['id'],
device='/dev/%s' % self.device)
+ @test.services('volume')
@test.attr(type=['negative', 'gate'])
def test_rescued_vm_detach_volume(self):
+ volume = self._create_volume()
+
# Attach the volume to the server
self.servers_client.attach_volume(self.server_id,
- self.volume['id'],
+ volume['id'],
device='/dev/%s' % self.device)
self.volumes_extensions_client.wait_for_volume_status(
- self.volume['id'], 'in-use')
+ volume['id'], 'in-use')
# Rescue the server
self.servers_client.rescue_server(self.server_id,
adminPass=self.password)
self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
# addCleanup is a LIFO queue
- self.addCleanup(self._detach, self.server_id, self.volume['id'])
+ self.addCleanup(self._detach, self.server_id, volume['id'])
self.addCleanup(self._unrescue, self.server_id)
# Detach the volume from the server expecting failure
self.assertRaises(exceptions.Conflict,
self.servers_client.detach_volume,
self.server_id,
- self.volume['id'])
+ volume['id'])
class ServerRescueNegativeTestXML(ServerRescueNegativeTestJSON):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 484c34d..75f9795 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -27,9 +27,7 @@
def __init__(self, *args, **kwargs):
super(AttachVolumeTestJSON, self).__init__(*args, **kwargs)
- self.server = None
- self.volume = None
- self.attached = False
+ self.attachment = None
@classmethod
def resource_setup(cls):
@@ -41,13 +39,15 @@
raise cls.skipException(skip_msg)
def _detach(self, server_id, volume_id):
- if self.attached:
+ if self.attachment:
self.servers_client.detach_volume(server_id, volume_id)
self.volumes_client.wait_for_volume_status(volume_id, 'available')
def _delete_volume(self):
+ # Delete the created Volumes
if self.volume:
self.volumes_client.delete_volume(self.volume['id'])
+ self.volumes_client.wait_for_resource_deletion(self.volume['id'])
self.volume = None
def _create_and_attach(self):
@@ -57,8 +57,8 @@
adminPass=admin_pass)
# Record addresses so that we can ssh later
- _, self.server['addresses'] = \
- self.servers_client.list_addresses(self.server['id'])
+ _, self.server['addresses'] = (
+ self.servers_client.list_addresses(self.server['id']))
# Create a volume and wait for it to become ready
_, self.volume = self.volumes_client.create_volume(
@@ -68,12 +68,12 @@
'available')
# Attach the volume to the server
- self.servers_client.attach_volume(self.server['id'],
- self.volume['id'],
- device='/dev/%s' % self.device)
+ _, self.attachment = self.servers_client.attach_volume(
+ self.server['id'],
+ self.volume['id'],
+ device='/dev/%s' % self.device)
self.volumes_client.wait_for_volume_status(self.volume['id'], 'in-use')
- self.attached = True
self.addCleanup(self._detach, self.server['id'], self.volume['id'])
@testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test')
@@ -97,8 +97,7 @@
self.assertIn(self.device, partitions)
self._detach(self.server['id'], self.volume['id'])
- self.attached = False
-
+ self.attachment = None
self.servers_client.stop(self.server['id'])
self.servers_client.wait_for_server_status(self.server['id'],
'SHUTOFF')
@@ -112,6 +111,25 @@
partitions = linux_client.get_partitions()
self.assertNotIn(self.device, partitions)
+ @test.skip_because(bug="1323591", interface="xml")
+ @test.attr(type='gate')
+ def test_list_get_volume_attachments(self):
+ # Create Server, Volume and attach that Volume to Server
+ self._create_and_attach()
+ # List Volume attachment of the server
+ _, body = self.servers_client.list_volume_attachments(
+ self.server['id'])
+ self.assertEqual(1, len(body))
+ self.assertIn(self.attachment, body)
+
+ # Get Volume attachment of the server
+ _, body = self.servers_client.get_volume_attachment(
+ self.server['id'],
+ self.attachment['id'])
+ self.assertEqual(self.server['id'], body['serverId'])
+ self.assertEqual(self.volume['id'], body['volumeId'])
+ self.assertEqual(self.attachment['id'], body['id'])
+
class AttachVolumeTestXML(AttachVolumeTestJSON):
_interface = 'xml'
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index dd81a09..b9086cc 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -12,6 +12,7 @@
# 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 itertools
import netaddr
import testtools
@@ -42,6 +43,7 @@
network update
subnet update
delete a network also deletes its subnets
+ list external networks
All subnet tests are run once with ipv4 and once with ipv6.
@@ -345,6 +347,28 @@
enable_dhcp=True,
**self.subnet_dict(['gateway', 'host_routes', 'dns_nameservers']))
+ @test.attr(type='smoke')
+ def test_external_network_visibility(self):
+ """Verifies user can see external networks but not subnets."""
+ _, body = self.client.list_networks(**{'router:external': True})
+ networks = [network['id'] for network in body['networks']]
+ self.assertNotEmpty(networks, "No external networks found")
+
+ nonexternal = [net for net in body['networks'] if
+ not net['router:external']]
+ self.assertEmpty(nonexternal, "Found non-external networks"
+ " in filtered list (%s)." % nonexternal)
+ self.assertIn(CONF.network.public_network_id, networks)
+
+ subnets_iter = (network['subnets'] for network in body['networks'])
+ # subnets_iter is a list (iterator) of lists. This flattens it to a
+ # list of UUIDs
+ public_subnets_iter = itertools.chain(*subnets_iter)
+ _, body = self.client.list_subnets()
+ subnets = [sub['id'] for sub in body['subnets']
+ if sub['id'] in public_subnets_iter]
+ self.assertEmpty(subnets, "Public subnets visible")
+
class NetworksTestXML(NetworksTestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 042cde9..9e24993 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -66,13 +66,14 @@
params = {self.name_field: vol_name, 'volume_type': type_name}
- _, self.volume = self.volume_client.create_volume(size=1, **params)
+ _, self.volume = self.admin_volume_client.create_volume(size=1,
+ **params)
if with_prefix:
self.volume_id_list_with_prefix.append(self.volume['id'])
else:
self.volume_id_list_without_prefix.append(
self.volume['id'])
- self.volume_client.wait_for_volume_status(
+ self.admin_volume_client.wait_for_volume_status(
self.volume['id'], 'available')
@classmethod
@@ -80,13 +81,13 @@
# volumes deletion
vid_prefix = getattr(cls, 'volume_id_list_with_prefix', [])
for volume_id in vid_prefix:
- cls.volume_client.delete_volume(volume_id)
- cls.volume_client.wait_for_resource_deletion(volume_id)
+ cls.admin_volume_client.delete_volume(volume_id)
+ cls.admin_volume_client.wait_for_resource_deletion(volume_id)
vid_no_pre = getattr(cls, 'volume_id_list_without_prefix', [])
for volume_id in vid_no_pre:
- cls.volume_client.delete_volume(volume_id)
- cls.volume_client.wait_for_resource_deletion(volume_id)
+ cls.admin_volume_client.delete_volume(volume_id)
+ cls.admin_volume_client.wait_for_resource_deletion(volume_id)
# volume types deletion
volume_type_id_list = getattr(cls, 'volume_type_id_list', [])
@@ -130,7 +131,7 @@
# the multi backend feature has been enabled
# if multi-backend is enabled: os-vol-attr:host should be like:
# host@backend_name
- _, volume = self.volume_client.get_volume(volume_id)
+ _, volume = self.admin_volume_client.get_volume(volume_id)
volume1_host = volume['os-vol-host-attr:host']
msg = ("multi-backend reporting incorrect values for volume %s" %
@@ -141,10 +142,10 @@
# this test checks that the two volumes created at setUp don't
# belong to the same backend (if they are, than the
# volume backend distinction is not working properly)
- _, volume = self.volume_client.get_volume(volume1_id)
+ _, volume = self.admin_volume_client.get_volume(volume1_id)
volume1_host = volume['os-vol-host-attr:host']
- _, volume = self.volume_client.get_volume(volume2_id)
+ _, volume = self.admin_volume_client.get_volume(volume2_id)
volume2_host = volume['os-vol-host-attr:host']
msg = ("volumes %s and %s were created in the same backend" %
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index ece4299..1189c8f 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -29,7 +29,6 @@
@classmethod
def resource_setup(cls):
super(VolumeQuotasAdminTestJSON, cls).resource_setup()
- cls.admin_volume_client = cls.os_adm.volumes_client
cls.demo_tenant_id = cls.isolated_creds.get_primary_creds().tenant_id
@test.attr(type='gate')
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index a0792f1..a481224 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -21,7 +21,7 @@
CONF = config.CONF
-class VolumeTypesTest(base.BaseVolumeV1AdminTest):
+class VolumeTypesV2Test(base.BaseVolumeAdminTest):
_interface = "json"
def _delete_volume(self, volume_id):
@@ -43,6 +43,7 @@
volume = {}
vol_name = data_utils.rand_name("volume-")
vol_type_name = data_utils.rand_name("volume-type-")
+ self.name_field = self.special_fields['name_field']
proto = CONF.volume.storage_protocol
vendor = CONF.volume.vendor_name
extra_specs = {"storage_protocol": proto,
@@ -54,21 +55,20 @@
self.assertIn('id', body)
self.addCleanup(self._delete_volume_type, body['id'])
self.assertIn('name', body)
+ params = {self.name_field: vol_name, 'volume_type': vol_type_name}
_, volume = self.volumes_client.create_volume(
- size=1, display_name=vol_name,
- volume_type=vol_type_name)
+ size=1, **params)
self.assertIn('id', volume)
self.addCleanup(self._delete_volume, volume['id'])
- self.assertIn('display_name', volume)
- self.assertEqual(volume['display_name'], vol_name,
+ self.assertIn(self.name_field, volume)
+ self.assertEqual(volume[self.name_field], vol_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.volumes_client.wait_for_volume_status(volume['id'],
- 'available')
+ self.volumes_client.wait_for_volume_status(volume['id'], 'available')
_, fetched_volume = self.volumes_client.get_volume(volume['id'])
- self.assertEqual(vol_name, fetched_volume['display_name'],
+ self.assertEqual(vol_name, fetched_volume[self.name_field],
'The fetched Volume is different '
'from the created Volume')
self.assertEqual(volume['id'], fetched_volume['id'],
@@ -154,3 +154,7 @@
self.volume_types_client.get_encryption_type(
encryption_type['volume_type_id']))
self.assertEmpty(deleted_encryption_type)
+
+
+class VolumeTypesV1Test(VolumeTypesV2Test):
+ _api_version = 1
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index a4d6431..9c9913f 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -20,16 +20,18 @@
from tempest import test
-class VolumeTypesNegativeTest(base.BaseVolumeV1AdminTest):
+class VolumeTypesNegativeV2Test(base.BaseVolumeAdminTest):
_interface = 'json'
@test.attr(type='gate')
def test_create_with_nonexistent_volume_type(self):
# Should not be able to create volume with nonexistent volume_type.
+ self.name_field = self.special_fields['name_field']
+ params = {self.name_field: str(uuid.uuid4()),
+ 'volume_type': str(uuid.uuid4())}
self.assertRaises(exceptions.NotFound,
self.volumes_client.create_volume, size=1,
- display_name=str(uuid.uuid4()),
- volume_type=str(uuid.uuid4()))
+ **params)
@test.attr(type='gate')
def test_create_with_empty_name(self):
@@ -52,5 +54,9 @@
str(uuid.uuid4()))
-class VolumesTypesNegativeTestXML(VolumeTypesNegativeTest):
+class VolumeTypesNegativeV1Test(VolumeTypesNegativeV2Test):
+ _api_version = 1
+
+
+class VolumeTypesNegativeV1TestXML(VolumeTypesNegativeV1Test):
_interface = 'xml'
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index f85718b..3857fdb 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -26,9 +26,6 @@
super(VolumesActionsTest, cls).resource_setup()
cls.client = cls.volumes_client
- # Create admin volume client
- cls.admin_volume_client = cls.os_adm.volumes_client
-
# Create a test shared volume for tests
vol_name = utils.rand_name(cls.__name__ + '-Volume-')
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 8b90b07..bf014a8 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -33,7 +33,6 @@
if not CONF.volume_feature_enabled.backup:
raise cls.skipException("Cinder backup feature disabled")
- cls.volumes_adm_client = cls.os_adm.volumes_client
cls.backups_adm_client = cls.os_adm.backups_client
cls.volume = cls.create_volume()
@@ -47,8 +46,8 @@
self.addCleanup(self.backups_adm_client.delete_backup,
backup['id'])
self.assertEqual(backup_name, backup['name'])
- self.volumes_adm_client.wait_for_volume_status(self.volume['id'],
- 'available')
+ self.admin_volume_client.wait_for_volume_status(
+ self.volume['id'], 'available')
self.backups_adm_client.wait_for_backup_status(backup['id'],
'available')
@@ -65,10 +64,10 @@
_, restore = self.backups_adm_client.restore_backup(backup['id'])
# Delete backup
- self.addCleanup(self.volumes_adm_client.delete_volume,
+ self.addCleanup(self.admin_volume_client.delete_volume,
restore['volume_id'])
self.assertEqual(backup['id'], restore['backup_id'])
self.backups_adm_client.wait_for_backup_status(backup['id'],
'available')
- self.volumes_adm_client.wait_for_volume_status(restore['volume_id'],
- 'available')
+ self.admin_volume_client.wait_for_volume_status(
+ restore['volume_id'], 'available')
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index d78ddb6..8170cbf 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -174,14 +174,14 @@
raise cls.skipException(msg)
cls.volume_qos_client = cls.os_adm.volume_qos_client
cls.volume_types_client = cls.os_adm.volume_types_client
- cls.volume_client = cls.os_adm.volumes_client
+ cls.admin_volume_client = cls.os_adm.volumes_client
elif cls._api_version == 2:
if not CONF.volume_feature_enabled.api_v2:
msg = "Volume API v2 is disabled"
raise cls.skipException(msg)
cls.volume_qos_client = cls.os_adm.volume_qos_v2_client
cls.volume_types_client = cls.os_adm.volume_types_v2_client
- cls.volume_client = cls.os_adm.volumes_v2_client
+ cls.admin_volume_client = cls.os_adm.volumes_v2_client
@classmethod
def resource_cleanup(cls):
diff --git a/tempest/api_schema/response/compute/availability_zone.py b/tempest/api_schema/response/compute/availability_zone.py
index c1abc64..ab3e2ea 100644
--- a/tempest/api_schema/response/compute/availability_zone.py
+++ b/tempest/api_schema/response/compute/availability_zone.py
@@ -27,7 +27,7 @@
'properties': {
'available': {'type': 'boolean'},
'active': {'type': 'boolean'},
- 'updated_at': {'type': 'string'}
+ 'updated_at': {'type': ['string', 'null']}
},
'required': ['available', 'active', 'updated_at']
}
diff --git a/tempest/api_schema/response/compute/services.py b/tempest/api_schema/response/compute/services.py
index eaba129..fc42b89 100644
--- a/tempest/api_schema/response/compute/services.py
+++ b/tempest/api_schema/response/compute/services.py
@@ -28,7 +28,7 @@
'state': {'type': 'string'},
'binary': {'type': 'string'},
'status': {'type': 'string'},
- 'updated_at': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']},
'disabled_reason': {'type': ['string', 'null']}
},
'required': ['id', 'zone', 'host', 'state', 'binary',
diff --git a/tempest/api_schema/response/compute/v2/servers.py b/tempest/api_schema/response/compute/v2/servers.py
index 5fc2008..09abaed 100644
--- a/tempest/api_schema/response/compute/v2/servers.py
+++ b/tempest/api_schema/response/compute/v2/servers.py
@@ -117,21 +117,23 @@
}
}
+common_attach_volume_info = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': ['integer', 'string']}
+ },
+ 'required': ['id', 'device', 'volumeId', 'serverId']
+}
+
attach_volume = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
- 'volumeAttachment': {
- 'type': 'object',
- 'properties': {
- 'id': {'type': 'string'},
- 'device': {'type': 'string'},
- 'volumeId': {'type': 'string'},
- 'serverId': {'type': ['integer', 'string']}
- },
- 'required': ['id', 'device', 'volumeId', 'serverId']
- }
+ 'volumeAttachment': common_attach_volume_info
},
'required': ['volumeAttachment']
}
@@ -141,6 +143,27 @@
'status_code': [202]
}
+get_volume_attachment = copy.deepcopy(attach_volume)
+get_volume_attachment['response_body']['properties'][
+ 'volumeAttachment']['properties'].update({'serverId': {'type': 'string'}})
+
+list_volume_attachments = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumeAttachments': {
+ 'type': 'array',
+ 'items': common_attach_volume_info
+ }
+ },
+ 'required': ['volumeAttachments']
+ }
+}
+list_volume_attachments['response_body']['properties'][
+ 'volumeAttachments']['items']['properties'].update(
+ {'serverId': {'type': 'string'}})
+
set_get_server_metadata_item = {
'status_code': [200],
'response_body': {
diff --git a/tempest/clients.py b/tempest/clients.py
index 19b4e11..4269812 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -291,7 +291,8 @@
self.telemetry_client = TelemetryClientXML(
self.auth_provider)
self.token_client = TokenClientXML()
- self.token_v3_client = V3TokenClientXML()
+ if CONF.identity_feature_enabled.api_v3:
+ self.token_v3_client = V3TokenClientXML()
self.volume_availability_zone_client = \
VolumeAvailabilityZoneClientXML(self.auth_provider)
self.volume_v2_availability_zone_client = \
@@ -307,8 +308,6 @@
self.servers_v3_client = ServersV3ClientJSON(self.auth_provider)
self.limits_client = LimitsClientJSON(self.auth_provider)
self.images_client = ImagesClientJSON(self.auth_provider)
- self.keypairs_v3_client = KeyPairsV3ClientJSON(
- self.auth_provider)
self.keypairs_client = KeyPairsClientJSON(self.auth_provider)
self.keypairs_v3_client = KeyPairsV3ClientJSON(
self.auth_provider)
@@ -397,7 +396,8 @@
self.telemetry_client = TelemetryClientJSON(
self.auth_provider)
self.token_client = TokenClientJSON()
- self.token_v3_client = V3TokenClientJSON()
+ if CONF.identity_feature_enabled.api_v3:
+ self.token_v3_client = V3TokenClientJSON()
self.negative_client = rest_client.NegativeRestClient(
self.auth_provider)
self.negative_client.service = service
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 0adc7e0..6879db9 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -26,6 +26,7 @@
import sys
import unittest
+import netaddr
import yaml
import tempest.auth
@@ -34,14 +35,17 @@
from tempest.openstack.common import log as logging
from tempest.openstack.common import timeutils
from tempest.services.compute.json import flavors_client
+from tempest.services.compute.json import security_groups_client
from tempest.services.compute.json import servers_client
from tempest.services.identity.json import identity_client
from tempest.services.image.v2.json import image_client
+from tempest.services.network.json import network_client
from tempest.services.object_storage import container_client
from tempest.services.object_storage import object_client
from tempest.services.telemetry.json import telemetry_client
from tempest.services.volume.json import volumes_client
+CONF = config.CONF
OPTS = {}
USERS = {}
RES = collections.defaultdict(list)
@@ -69,7 +73,9 @@
self.images = image_client.ImageClientV2JSON(_auth)
self.flavors = flavors_client.FlavorsClientJSON(_auth)
self.telemetry = telemetry_client.TelemetryClientJSON(_auth)
+ self.secgroups = security_groups_client.SecurityGroupsClientJSON(_auth)
self.volumes = volumes_client.VolumesClientJSON(_auth)
+ self.networks = network_client.NetworkClientJSON(_auth)
def load_resources(fname):
@@ -90,6 +96,10 @@
else:
LOG.error("%s not found in USERS: %s" % (name, USERS))
+
+def resp_ok(response):
+ return 200 >= int(response['status']) < 300
+
###################
#
# TENANTS
@@ -212,12 +222,36 @@
def runTest(self, *args):
pass
+ def _ping_ip(self, ip_addr, count, namespace=None):
+ if namespace is None:
+ ping_cmd = "ping -c1 " + ip_addr
+ else:
+ ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
+ ip_addr)
+ for current in range(count):
+ return_code = os.system(ping_cmd)
+ if return_code is 0:
+ break
+ self.assertNotEqual(current, count - 1,
+ "Server is not pingable at %s" % ip_addr)
+
def check(self):
self.check_users()
self.check_objects()
self.check_servers()
self.check_volumes()
self.check_telemetry()
+ self.check_secgroups()
+
+ # validate neutron is enabled and ironic disabled:
+ # Tenant network isolation is not supported when using ironic.
+ # "admin" has set up a neutron flat network environment within a shared
+ # fixed network for all tenants to use.
+ # In this case, network/subnet/router creation can be skipped and the
+ # server booted the same as nova network.
+ if (CONF.service_available.neutron and
+ not CONF.baremetal.driver_enabled):
+ self.check_networking()
def check_users(self):
"""Check that the users we expect to exist, do.
@@ -264,15 +298,32 @@
"Couldn't find expected server %s" % server['name'])
r, found = client.servers.get_server(found['id'])
- # get the ipv4 address
- addr = found['addresses']['private'][0]['addr']
- for count in range(60):
- return_code = os.system("ping -c1 " + addr)
- if return_code is 0:
- break
- self.assertNotEqual(count, 59,
- "Server %s is not pingable at %s" % (
- server['name'], addr))
+ # validate neutron is enabled and ironic disabled:
+ if (CONF.service_available.neutron and
+ not CONF.baremetal.driver_enabled):
+ for network_name, body in found['addresses'].items():
+ for addr in body:
+ ip = addr['addr']
+ if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
+ namespace = _get_router_namespace(client,
+ network_name)
+ self._ping_ip(ip, 60, namespace)
+ else:
+ self._ping_ip(ip, 60)
+ else:
+ addr = found['addresses']['private'][0]['addr']
+ self._ping_ip(addr, 60)
+
+ def check_secgroups(self):
+ """Check that the security groups are still existing."""
+ LOG.info("Checking security groups")
+ for secgroup in self.res['secgroups']:
+ client = client_for_user(secgroup['owner'])
+ found = _get_resource_by_name(client.secgroups, 'security_groups',
+ secgroup['name'])
+ self.assertIsNotNone(
+ found,
+ "Couldn't find expected secgroup %s" % secgroup['name'])
def check_telemetry(self):
"""Check that ceilometer provides a sane sample.
@@ -334,6 +385,17 @@
'timestamp should come before start of second javelin run'
)
+ def check_networking(self):
+ """Check that the networks are still there."""
+ for res_type in ('networks', 'subnets', 'routers'):
+ for res in self.res[res_type]:
+ client = client_for_user(res['owner'])
+ found = _get_resource_by_name(client.networks, res_type,
+ res['name'])
+ self.assertIsNotNone(
+ found,
+ "Couldn't find expected resource %s" % res['name'])
+
#######################
#
@@ -440,6 +502,147 @@
#######################
#
+# NETWORKS
+#
+#######################
+
+def _get_router_namespace(client, network):
+ network_id = _get_resource_by_name(client.networks,
+ 'networks', network)['id']
+ resp, n_body = client.networks.list_routers()
+ if not resp_ok(resp):
+ raise ValueError("unable to routers list: [%s] %s" % (resp, n_body))
+ for router in n_body['routers']:
+ router_id = router['id']
+ resp, r_body = client.networks.list_router_interfaces(router_id)
+ if not resp_ok(resp):
+ raise ValueError("unable to router interfaces list: [%s] %s" %
+ (resp, r_body))
+ for port in r_body['ports']:
+ if port['network_id'] == network_id:
+ return "qrouter-%s" % router_id
+
+
+def _get_resource_by_name(client, resource, name):
+ get_resources = getattr(client, 'list_%s' % resource)
+ if get_resources is None:
+ raise AttributeError("client doesn't have method list_%s" % resource)
+ r, body = get_resources()
+ if not resp_ok(r):
+ raise ValueError("unable to list %s: [%s] %s" % (resource, r, body))
+ if isinstance(body, dict):
+ body = body[resource]
+ for res in body:
+ if name == res['name']:
+ return res
+ raise ValueError('%s not found in %s resources' % (name, resource))
+
+
+def create_networks(networks):
+ LOG.info("Creating networks")
+ for network in networks:
+ client = client_for_user(network['owner'])
+
+ # only create a network if the name isn't here
+ r, body = client.networks.list_networks()
+ if any(item['name'] == network['name'] for item in body['networks']):
+ LOG.warning("Dupplicated network name: %s" % network['name'])
+ continue
+
+ client.networks.create_network(name=network['name'])
+
+
+def destroy_networks(networks):
+ LOG.info("Destroying subnets")
+ for network in networks:
+ client = client_for_user(network['owner'])
+ network_id = _get_resource_by_name(client.networks, 'networks',
+ network['name'])['id']
+ client.networks.delete_network(network_id)
+
+
+def create_subnets(subnets):
+ LOG.info("Creating subnets")
+ for subnet in subnets:
+ client = client_for_user(subnet['owner'])
+
+ network = _get_resource_by_name(client.networks, 'networks',
+ subnet['network'])
+ ip_version = netaddr.IPNetwork(subnet['range']).version
+ # ensure we don't overlap with another subnet in the network
+ try:
+ client.networks.create_subnet(network_id=network['id'],
+ cidr=subnet['range'],
+ name=subnet['name'],
+ ip_version=ip_version)
+ except exceptions.BadRequest as e:
+ is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+ if not is_overlapping_cidr:
+ raise
+
+
+def destroy_subnets(subnets):
+ LOG.info("Destroying subnets")
+ for subnet in subnets:
+ client = client_for_user(subnet['owner'])
+ subnet_id = _get_resource_by_name(client.networks,
+ 'subnets', subnet['name'])['id']
+ client.networks.delete_subnet(subnet_id)
+
+
+def create_routers(routers):
+ LOG.info("Creating routers")
+ for router in routers:
+ client = client_for_user(router['owner'])
+
+ # only create a router if the name isn't here
+ r, body = client.networks.list_routers()
+ if any(item['name'] == router['name'] for item in body['routers']):
+ LOG.warning("Dupplicated router name: %s" % router['name'])
+ continue
+
+ client.networks.create_router(router['name'])
+
+
+def destroy_routers(routers):
+ LOG.info("Destroying routers")
+ for router in routers:
+ client = client_for_user(router['owner'])
+ router_id = _get_resource_by_name(client.networks,
+ 'routers', router['name'])['id']
+ for subnet in router['subnet']:
+ subnet_id = _get_resource_by_name(client.networks,
+ 'subnets', subnet)['id']
+ client.networks.remove_router_interface_with_subnet_id(router_id,
+ subnet_id)
+ client.networks.delete_router(router_id)
+
+
+def add_router_interface(routers):
+ for router in routers:
+ client = client_for_user(router['owner'])
+ router_id = _get_resource_by_name(client.networks,
+ 'routers', router['name'])['id']
+
+ for subnet in router['subnet']:
+ subnet_id = _get_resource_by_name(client.networks,
+ 'subnets', subnet)['id']
+ # connect routers to their subnets
+ client.networks.add_router_interface_with_subnet_id(router_id,
+ subnet_id)
+ # connect routers to exteral network if set to "gateway"
+ if router['gateway']:
+ if CONF.network.public_network_id:
+ ext_net = CONF.network.public_network_id
+ client.networks._update_router(
+ router_id, set_enable_snat=True,
+ external_gateway_info={"network_id": ext_net})
+ else:
+ raise ValueError('public_network_id is not configured.')
+
+
+#######################
+#
# SERVERS
#
#######################
@@ -473,10 +676,21 @@
image_id = _get_image_by_name(client, server['image'])['id']
flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
- resp, body = client.servers.create_server(server['name'], image_id,
- flavor_id)
+ # validate neutron is enabled and ironic disabled
+ kwargs = dict()
+ if (CONF.service_available.neutron and
+ not CONF.baremetal.driver_enabled and server.get('networks')):
+ get_net_id = lambda x: (_get_resource_by_name(
+ client.networks, 'networks', x)['id'])
+ kwargs['networks'] = [{'uuid': get_net_id(network)}
+ for network in server['networks']]
+ resp, body = client.servers.create_server(
+ server['name'], image_id, flavor_id, **kwargs)
server_id = body['id']
client.servers.wait_for_server_status(server_id, 'ACTIVE')
+ # create to security group(s) after server spawning
+ for secgroup in server['secgroups']:
+ client.servers.add_security_group(server_id, secgroup)
def destroy_servers(servers):
@@ -496,6 +710,44 @@
ignore_error=True)
+def create_secgroups(secgroups):
+ LOG.info("Creating security groups")
+ for secgroup in secgroups:
+ client = client_for_user(secgroup['owner'])
+
+ # only create a security group if the name isn't here
+ # i.e. a security group may be used by another server
+ # only create a router if the name isn't here
+ r, body = client.secgroups.list_security_groups()
+ if any(item['name'] == secgroup['name'] for item in body):
+ LOG.warning("Security group '%s' already exists" %
+ secgroup['name'])
+ continue
+
+ resp, body = client.secgroups.create_security_group(
+ secgroup['name'], secgroup['description'])
+ if not resp_ok(resp):
+ raise ValueError("Failed to create security group: [%s] %s" %
+ (resp, body))
+ secgroup_id = body['id']
+ # for each security group, create the rules
+ for rule in secgroup['rules']:
+ ip_proto, from_port, to_port, cidr = rule.split()
+ client.secgroups.create_security_group_rule(
+ secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
+
+
+def destroy_secgroups(secgroups):
+ LOG.info("Destroying security groups")
+ for secgroup in secgroups:
+ client = client_for_user(secgroup['owner'])
+ sg_id = _get_resource_by_name(client.secgroups,
+ 'security_groups',
+ secgroup['name'])
+ # sg rules are deleted automatically
+ client.secgroups.delete_security_group(sg_id['id'])
+
+
#######################
#
# VOLUMES
@@ -563,6 +815,15 @@
# next create resources in a well known order
create_objects(RES['objects'])
create_images(RES['images'])
+
+ # validate neutron is enabled and ironic is disabled
+ if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
+ create_networks(RES['networks'])
+ create_subnets(RES['subnets'])
+ create_routers(RES['routers'])
+ add_router_interface(RES['routers'])
+
+ create_secgroups(RES['secgroups'])
create_servers(RES['servers'])
create_volumes(RES['volumes'])
attach_volumes(RES['volumes'])
@@ -575,6 +836,11 @@
destroy_images(RES['images'])
destroy_objects(RES['objects'])
destroy_volumes(RES['volumes'])
+ if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
+ destroy_routers(RES['routers'])
+ destroy_subnets(RES['subnets'])
+ destroy_networks(RES['networks'])
+ destroy_secgroups(RES['secgroups'])
destroy_users(RES['users'])
destroy_tenants(RES['tenants'])
LOG.warn("Destroy mode incomplete")
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
index 2d5e686..2d6664c 100644
--- a/tempest/cmd/resources.yaml
+++ b/tempest/cmd/resources.yaml
@@ -17,11 +17,17 @@
tenant: discuss
secgroups:
- - angon:
+ - name: angon
owner: javelin
+ description: angon
rules:
- 'icmp -1 -1 0.0.0.0/0'
- 'tcp 22 22 0.0.0.0/0'
+ - name: baobab
+ owner: javelin
+ description: baobab
+ rules:
+ - 'tcp 80 80 0.0.0.0/0'
# resources that we want to create
images:
@@ -43,15 +49,45 @@
owner: javelin
gb: 2
device: /dev/vdb
+networks:
+ - name: world1
+ owner: javelin
+ - name: world2
+ owner: javelin
+subnets:
+ - name: subnet1
+ range: 10.1.0.0/24
+ network: world1
+ owner: javelin
+ - name: subnet2
+ range: 192.168.1.0/24
+ network: world2
+ owner: javelin
+routers:
+ - name: connector
+ owner: javelin
+ gateway: true
+ subnet:
+ - subnet1
+ - subnet2
servers:
- name: peltast
owner: javelin
flavor: m1.small
image: javelin_cirros
+ networks:
+ - world1
+ secgroups:
+ - angon
+ - baobab
- name: hoplite
owner: javelin
flavor: m1.medium
image: javelin_cirros
+ networks:
+ - world2
+ secgroups:
+ - angon
objects:
- container: jc1
name: javelin1
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 89904b2..6a238d0 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -91,7 +91,7 @@
return self.exec_command(cmd)
def get_mac_address(self):
- cmd = "/sbin/ifconfig | awk '/HWaddr/ {print $5}'"
+ cmd = "/bin/ip addr | awk '/ether/ {print $2}'"
return self.exec_command(cmd)
def get_ip_list(self):
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 990a392..2ebfdd1 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -411,9 +411,8 @@
def nova_volume_attach(self):
# TODO(andreaf) Device should be here CONF.compute.volume_device_name
- _, volume_attachment = self.servers_client.attach_volume(
+ _, volume = self.servers_client.attach_volume(
self.server['id'], self.volume['id'], '/dev/vdb')
- volume = volume_attachment['volumeAttachment']
self.assertEqual(self.volume['id'], volume['id'])
self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
# Refresh the volume after the attachment
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 3725477..ead021e 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -78,9 +78,8 @@
def nova_volume_attach(self):
volume_device_path = '/dev/' + CONF.compute.volume_device_name
- _, volume_attachment = self.servers_client.attach_volume(
+ _, volume = self.servers_client.attach_volume(
self.server['id'], self.volume['id'], volume_device_path)
- volume = volume_attachment['volumeAttachment']
self.assertEqual(self.volume['id'], volume['id'])
self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
# Refresh the volume after the attachment
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 6c36034..6ea3253 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -120,16 +120,6 @@
cls.enabled = False
raise cls.skipException(msg)
super(TestSecurityGroupsBasicOps, cls).check_preconditions()
- # need alt_creds here to check preconditions
- cls.alt_creds = cls.alt_credentials()
- cls.alt_manager = clients.Manager(cls.alt_creds)
- # Credentials from the manager are filled with both IDs and Names
- cls.alt_creds = cls.alt_manager.credentials
- if (cls.alt_creds is None) or \
- (cls.tenant_id is cls.alt_creds.tenant_id):
- msg = 'No alt_tenant defined'
- cls.enabled = False
- raise cls.skipException(msg)
if not (CONF.network.tenant_networks_reachable or
CONF.network.public_network_id):
msg = ('Either tenant_networks_reachable must be "true", or '
@@ -144,6 +134,13 @@
super(TestSecurityGroupsBasicOps, cls).resource_setup()
# TODO(mnewby) Consider looking up entities as needed instead
# of storing them as collections on the class.
+
+ # get credentials for secondary tenant
+ cls.alt_creds = cls.isolated_creds.get_alt_creds()
+ cls.alt_manager = clients.Manager(cls.alt_creds)
+ # Credentials from the manager are filled with both IDs and Names
+ cls.alt_creds = cls.alt_manager.credentials
+
cls.floating_ips = {}
cls.tenants = {}
creds = cls.credentials()
@@ -437,6 +434,8 @@
@test.attr(type='smoke')
@test.services('compute', 'network')
def test_cross_tenant_traffic(self):
+ if not self.isolated_creds.is_multi_tenant():
+ raise self.skipException("No secondary tenant defined")
try:
# deploy new tenant
self._deploy_tenant(self.alt_tenant)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 8ea2814..7fc1edf 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -115,7 +115,6 @@
# TODO(andreaf) we should use device from config instead if vdb
_, attached_volume = self.servers_client.attach_volume(
server['id'], volume['id'], device='/dev/vdb')
- attached_volume = attached_volume['volumeAttachment']
self.assertEqual(volume['id'], attached_volume['id'])
self._wait_for_volume_status(attached_volume, 'in-use')
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 947ba7a..4268b1a 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -369,7 +369,7 @@
post_body)
body = json.loads(body)
self.validate_response(schema.attach_volume, resp, body)
- return resp, body
+ return resp, body['volumeAttachment']
def detach_volume(self, server_id, volume_id):
"""Detaches a volume from a server instance."""
@@ -378,6 +378,22 @@
self.validate_response(schema.detach_volume, resp, body)
return resp, body
+ def get_volume_attachment(self, server_id, attach_id):
+ """Return details about the given volume attachment."""
+ resp, body = self.get('servers/%s/os-volume_attachments/%s' % (
+ str(server_id), attach_id))
+ body = json.loads(body)
+ self.validate_response(schema.get_volume_attachment, resp, body)
+ return resp, body['volumeAttachment']
+
+ def list_volume_attachments(self, server_id):
+ """Returns the list of volume attachments for a given instance."""
+ resp, body = self.get('servers/%s/os-volume_attachments' % (
+ str(server_id)))
+ body = json.loads(body)
+ self.validate_response(schema.list_volume_attachments, resp, body)
+ return resp, body['volumeAttachments']
+
def add_security_group(self, server_id, name):
"""Adds a security group to the server."""
return self.action(server_id, 'addSecurityGroup', None, name=name)
diff --git a/tempest/services/volume/xml/admin/volume_types_client.py b/tempest/services/volume/xml/admin/volume_types_client.py
index 2464016..03d39a8 100644
--- a/tempest/services/volume/xml/admin/volume_types_client.py
+++ b/tempest/services/volume/xml/admin/volume_types_client.py
@@ -25,14 +25,14 @@
CONF = config.CONF
-class VolumeTypesClientXML(rest_client.RestClient):
+class BaseVolumeTypesClientXML(rest_client.RestClient):
"""
Client class to send CRUD Volume Types API requests to a Cinder endpoint
"""
TYPE = "xml"
def __init__(self, auth_provider):
- super(VolumeTypesClientXML, self).__init__(auth_provider)
+ super(BaseVolumeTypesClientXML, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
self.build_interval = CONF.compute.build_interval
self.build_timeout = CONF.compute.build_timeout
@@ -210,3 +210,9 @@
def resource_type(self):
"""Returns the primary type of resource this client works with."""
return 'volume-type'
+
+
+class VolumeTypesClientXML(BaseVolumeTypesClientXML):
+ """
+ Client class to send CRUD Volume Type API V1 requests to a Cinder endpoint
+ """
diff --git a/tempest/test.py b/tempest/test.py
index 1c6265d..db8736e 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -342,10 +342,12 @@
"""
force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
- cls.isolated_creds = credentials.get_isolated_credentials(
- name=cls.__name__, network_resources=cls.network_resources,
- force_tenant_isolation=force_tenant_isolation,
- )
+ if (not hasattr(cls, 'isolated_creds') or
+ not cls.isolated_creds.name == cls.__name__):
+ cls.isolated_creds = credentials.get_isolated_credentials(
+ name=cls.__name__, network_resources=cls.network_resources,
+ force_tenant_isolation=force_tenant_isolation,
+ )
creds = cls.isolated_creds.get_primary_creds()
params = dict(credentials=creds, service=cls._service)
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 0db4cfa..e8650c5 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -117,7 +117,7 @@
self.assertEqual(self.conn.get_mac_address(), macs)
self._assert_exec_called_with(
- "/sbin/ifconfig | awk '/HWaddr/ {print $5}'")
+ "/bin/ip addr | awk '/ether/ {print $2}'")
def test_get_ip_list(self):
ips = """1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue
diff --git a/tox.ini b/tox.ini
index 9f52f0d..f75e868 100644
--- a/tox.ini
+++ b/tox.ini
@@ -91,7 +91,7 @@
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
commands =
- run-tempest-stress '{posargs}'
+ run-tempest-stress {posargs}
[testenv:venv]
commands = {posargs}